6.1 KiB
Handling Print Statements
This document describes how "print" statements are handled within Luprex. It documents the full path by which print statements originate with Lua, and gradually travel to the Unreal virtual console and debug log. There are two types of print statements:
- dprint, for messages that go to the debug logs.
- print, for messages that go to a window in the user's GUI.
The following sections explain the differences, and how each of these is implemented.
dprint, for messages that go to the debug logs
The lua code can use 'dprint' to print a message into Unreal's debugging logs. A 'dprint' goes to the same places that a debugging message within Unreal goes. That includes:
- Visual studio's debug message window.
- The Unreal Editor's debug message window.
- Unreal's debug message log file.
In the lua server, currently, dprints just go to stderr. We're probably going to enhance that by adding a log file as well.
The unreal logs are not managed by predictive reexecution. To put it differently, once a message goes into the unreal logs, it can't be corrected by difference transmission. Because of this, if a dprint is reexecuted, you will get multiple possibly conflicting messages in the unreal logs.
Implementation of dprint
The luprex DLL contains a function util::dprintview, along with a couple of convenience wrappers like util::dprintf. This function is used to make a debugging print. It is used in a number of places throughout Luprex. It can also be called from lua, via the 'dprint' function.
The Luprex DLL exports an API: EngineWrapper::hook_dprint. This function accepts a pointer to a C callback function, which takes a string as a parameter. The driver is expected to call hook_dprint early in 'main', to set the dprint callback. The pointer to the callback function is stored in the global variable util::dprint_hook. The function util::dprintview breaks the string into lines, and calls util::dprint_hook once per line.
In the Unreal client, the dprint callback is set to a function that calls UE_LOG, the unreal error logging macro, with a log category of LogLuprex. So therefore, in the Unreal client, calling dprint is equivalent to calling UE_LOG.
In the server, the dprint callback is set to a function that outputs the string to the console (ie, stdout).
The print-callback is the only place where the Luprex DLL actually calls into the driver (via a callback). Normally, the driver is supposed to call into the Luprex DLL, but not the other way around. This one exception is necessary to handle the case where the Luprex DLL is about to crash and wants to print a message before it does.
print, for messages that go to a window in the user's GUI
The client is expected to have a GUI of some sort that includes a text console, where messages can be displayed. The print statement puts a message into this GUI console.
Every person who is logged in has their own GUI text console. Therefore, it is possible to print a message onto any one of those. When the lua code executes a print statement, it sends its output to the GUI console of the actor.
The contents of the GUI text console is part of the world model. Therefore, it can be corrected by difference transmission. If a print-statement is predicted incorrectly, the user's GUI text console will temporarily contain the wrong text, but it will get fixed by difference transmission.
Implementation of print
Inside the Luprex DLL, class PrintBuffer is used to store the contents of the GUI console. Every logged in player has a PrintBuffer as part of their character tangible. Difference transmission is capable of amending the contents of the PrintBuffer.
When the luprex thread scheduler starts executing a thread, it sets up an ostringstream to collect any print statements. When the thread pauses or ends, the contents of the ostringstream are copied into the PrintBuffer. The code to create the stringstream and to copy it into the PrintBuffer are both in the thread scheduling code in world-core.cpp.
The stringstream isn't always copied into the PrintBuffer. There are exceptions: for example, in a 'probe', print statements don't go into the PrintBuffer, they get rerouted to dprint instead.
The difference transmitter handles PrintBuffers specially. It doesn't just fix the contents of the PrintBuffer. It also leaves an 'authoritative' bit inside the PrintBuffer to indicate which print statements are confirmed as authoritative, and conversely, to indicate which ones are still just predictions. Because of this, the PrintBuffer knows the difference between a "final" print and a "tentative" print.
The client and server in lpxclient.cpp and lpxserver.cpp both contain objects of class PrintChanneler. This class is designed to monitor a PrintBuffer to see if anything has changed. Specifically, it is capable of answering the question: does the PrintBuffer contain any new authoritative print statements?
Periodically, lpxserver.cpp and lpxclient.cpp will check their PrintChannelers to see if there are any new authoritative print statements. If so, they will set a flag called "have_prints" in the DrivenEngine. Unreal periodically polls this flag using EngineWrapper::get_have_prints.
If the flag is set, Unreal then calls EngineWrapper::play_access(... CHANNEL_PRINTS ...). This asks the PrintChanneler to fetch all new authoritative prints. Then, CHANNEL_PRINTS will send an invoke via the standard predictive reexecution channels to FLUSH_PRINTS - ie, to forget the print statements that it just channeled.
When the PrintChanneler returns prints to Unreal, Unreal passes those prints to the LuprexGameMode blueprint by calling LuprexGameMode::AddConsoleOutput. The LuprexGameMode is currently responsible for implementing the GUI text console. That will probably change at some point.
The LuprexGameMode maintains an object of class UlxConsoleOutput which keeps a record of what's in the GUI Console. When AddConsoleOutput feeds new prints into the LuprexGameMode, those prints get added to the UlxConsoleOutput. This stores the contents of the console as one big string. From there, the string is copied into a text widget.