Files
integration/Docs/Print-Statement-Handling.md

149 lines
6.3 KiB
Markdown
Raw Normal View History

### 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
2026-02-09 17:03:22 -05:00
The world model contains an ostringstream lthread_prints_
which is used to temporarily collect normal print statements.
Whenever a thread pauses or ends, the contents of the
ostringstream are transferred to where they belong.
If the thread is executing normally, as part of an 'invoke',
then the contents of the stringstream are transferred into
a "PrintBuffer." Class PrintBuffer is a class that the
Luprex DLL uses to store the contents of a player's GUI
console. The PrintBuffer is part of the world model: every
logged in player has a PrintBuffer as part of their
character tangible. Difference transmission is capable of
amending the contents of the PrintBuffer.
The stringstream doesn't always get transferred to a
PrintBuffer. If the thread is a 'probe', then the stringstream
is sent to 'dprint'. If the thread is running under the HTTP
server, then the stringstream may be sent back as part of
the HTTP response.
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.
2026-02-09 17:03:22 -05:00
If the have_prints 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
2026-02-09 17:03:22 -05:00
the LuprexGameMode receives new strings via AddConsoleOutput,
it adds those string to the UlxConsoleOutput. The UlxConsoleOutput
turns those individual lines into one big string. From there,
the LuprexGameMode copies the entire string into the body of
a UMG text widget.
## A Possible new lua print function