143 lines
6.1 KiB
Markdown
143 lines
6.1 KiB
Markdown
|
|
### 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.
|