Overhaul the lthread_prints_ buffer.

This commit is contained in:
2026-02-09 17:03:22 -05:00
parent bf7cb9d258
commit 8fc4a3b94c
3 changed files with 69 additions and 79 deletions

View File

@@ -83,23 +83,25 @@ it will get fixed by difference transmission.
## Implementation of print ## Implementation of print
Inside the Luprex DLL, class PrintBuffer is used to store The world model contains an ostringstream lthread_prints_
the contents of the GUI console. Every logged in player has which is used to temporarily collect normal print statements.
a PrintBuffer as part of their character tangible. Whenever a thread pauses or ends, the contents of the
Difference transmission is capable of amending the contents ostringstream are transferred to where they belong.
of the PrintBuffer.
When the luprex thread scheduler starts executing a thread, If the thread is executing normally, as part of an 'invoke',
it sets up an ostringstream to collect any print statements. then the contents of the stringstream are transferred into
When the thread pauses or ends, the contents of the ostringstream a "PrintBuffer." Class PrintBuffer is a class that the
are copied into the PrintBuffer. The code to create the Luprex DLL uses to store the contents of a player's GUI
stringstream and to copy it into the PrintBuffer are both console. The PrintBuffer is part of the world model: every
in the thread scheduling code in world-core.cpp. 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 isn't *always* copied into the PrintBuffer. The stringstream doesn't always get transferred to a
There are exceptions: for example, in a 'probe', PrintBuffer. If the thread is a 'probe', then the stringstream
print statements don't go into the PrintBuffer, they get is sent to 'dprint'. If the thread is running under the HTTP
rerouted to dprint instead. server, then the stringstream may be sent back as part of
the HTTP response.
The difference transmitter handles PrintBuffers specially. The difference transmitter handles PrintBuffers specially.
It doesn't just fix the contents of the PrintBuffer. It also It doesn't just fix the contents of the PrintBuffer. It also
@@ -121,7 +123,7 @@ print statements. If so, they will set a flag called "have_prints"
in the DrivenEngine. Unreal periodically polls this flag using in the DrivenEngine. Unreal periodically polls this flag using
EngineWrapper::get_have_prints. EngineWrapper::get_have_prints.
If the flag is set, Unreal then calls If the have_prints is set, Unreal then calls
EngineWrapper::play_access(... CHANNEL_PRINTS ...). EngineWrapper::play_access(... CHANNEL_PRINTS ...).
This asks the PrintChanneler to fetch all new authoritative prints. This asks the PrintChanneler to fetch all new authoritative prints.
Then, CHANNEL_PRINTS will send an invoke via the standard Then, CHANNEL_PRINTS will send an invoke via the standard
@@ -136,7 +138,8 @@ That will probably change at some point.
The LuprexGameMode maintains an object of class UlxConsoleOutput The LuprexGameMode maintains an object of class UlxConsoleOutput
which keeps a record of what's in the GUI Console. When which keeps a record of what's in the GUI Console. When
AddConsoleOutput feeds new prints into the LuprexGameMode, the LuprexGameMode receives new strings via AddConsoleOutput,
those prints get added to the UlxConsoleOutput. This stores it adds those string to the UlxConsoleOutput. The UlxConsoleOutput
the contents of the console as one big string. From there, turns those individual lines into one big string. From there,
the string is copied into a text widget. the LuprexGameMode copies the entire string into the body of
a UMG text widget.

View File

@@ -387,29 +387,24 @@ eng::string World::probe_lua_expr(int64_t actor_id, std::string_view lua) {
// Call the closure. // Call the closure.
int top = lua_gettop(L); int top = lua_gettop(L);
lua_pushvalue(L, closure.index()); lua_pushvalue(L, closure.index());
open_lthread_state(actor_id, actor_id, 0, false, true); open_lthread_state(actor_id, actor_id, 0, false);
eng::string msg = traceback_pcall(L, 0, LUA_MULTRET); eng::string msg = traceback_pcall(L, 0, LUA_MULTRET);
// If there's an error message, print it. // If there's an error message, print it.
// Otherwise, pretty-print the results. // Otherwise, pretty-print the results.
std::ostream *ostream = lthread_print_stream();
if (msg.empty()) { if (msg.empty()) {
for (int i = top + 1; i <= lua_gettop(L); i++) { for (int i = top + 1; i <= lua_gettop(L); i++) {
LuaSpecial root(i); LuaSpecial root(i);
pprint(LS, root, PrettyPrintOptions(), ostream); pprint(LS, root, PrettyPrintOptions(), &lthread_prints_);
// TODO: this endl is unnecessary if we just printed a newline. // TODO: this endl is unnecessary if we just printed a newline.
(*ostream) << std::endl; lthread_prints_ << std::endl;
} }
} else { } else {
(*ostream) << msg << std::endl; lthread_prints_ << msg << std::endl;
} }
// Collect the lthread_prints (and also make sure they eng::string result = lthread_prints_.str();
// don't go into the printbuffer). clear_lthread_state();
eng::string result = lthread_prints_->str();
lthread_prints_.reset();
close_lthread_state();
return result; return result;
} }
@@ -478,15 +473,10 @@ void World::probe_lua_call(int64_t actor_id, int64_t place_id, std::string_view
return; return;
} }
open_lthread_state(actor_id, place_id, 0, false, true); open_lthread_state(actor_id, place_id, 0, false);
eng::string msg = traceback_pcall(L, nargs, LUA_MULTRET); eng::string msg = traceback_pcall(L, nargs, LUA_MULTRET);
LuaExtraArgs returnvalues(calltop + 1, lua_gettop(L) - calltop); LuaExtraArgs returnvalues(calltop + 1, lua_gettop(L) - calltop);
// Send any prints to the console.
eng::string prints = lthread_prints_->str();
lthread_prints_.reset();
util::dprint(prints);
// If a probe generates a lua error, we're not supposed to dprint it. // If a probe generates a lua error, we're not supposed to dprint it.
// Instead, we're supposed to send it back to unreal as the first // Instead, we're supposed to send it back to unreal as the first
// return value of the function. // return value of the function.
@@ -526,7 +516,7 @@ void World::probe_lua_call(int64_t actor_id, int64_t place_id, std::string_view
} }
} }
close_lthread_state(); clear_lthread_state();
} }
@@ -545,11 +535,10 @@ void World::probe_lua_call(int64_t actor_id, int64_t place_id, std::string_view
bool World::rebuild_sourcedb() { bool World::rebuild_sourcedb() {
bool ok = true; bool ok = true;
for (const eng::string &mod: source_db_.modules()) { for (const eng::string &mod: source_db_.modules()) {
open_lthread_state(0, 0, 0, false, true); open_lthread_state(0, 0, 0, false);
eng::string err = source_db_.rebuild_module(mod); eng::string err = source_db_.rebuild_module(mod);
eng::string prints = lthread_prints_->str(); eng::string prints = lthread_prints_.str();
lthread_prints_.reset(); clear_lthread_state();
close_lthread_state();
if (!err.empty()) ok = false; if (!err.empty()) ok = false;
if (!err.empty() || !prints.empty()) { if (!err.empty() || !prints.empty()) {
util::dprint("Loading Module ", mod); util::dprint("Loading Module ", mod);
@@ -705,9 +694,11 @@ HttpServerResponse World::http_serve(const HttpParser &request) {
int oldtop = lua_gettop(L); int oldtop = lua_gettop(L);
lua_pushvalue(L, func.index()); lua_pushvalue(L, func.index());
lua_pushvalue(L, reqtab.index()); lua_pushvalue(L, reqtab.index());
open_lthread_state(0, 0, 0, false, false); open_lthread_state(0, 0, 0, false);
eng::string msg = traceback_pcall(L, 1, LUA_MULTRET); eng::string msg = traceback_pcall(L, 1, LUA_MULTRET);
close_lthread_state(); if (!msg.empty()) lthread_prints_ << msg << std::endl;
lthread_prints_to_dprint();
clear_lthread_state();
// If the call threw an error, return // If the call threw an error, return
// a 500 Internal Server Error to the client. // a 500 Internal Server Error to the client.
@@ -1073,10 +1064,9 @@ void World::run_scheduled_threads() {
// Resume the coroutine. // Resume the coroutine.
lua_State *CO = LS.ckthread(thread); lua_State *CO = LS.ckthread(thread);
open_lthread_state(LS.ckinteger(actorid), sched.place_id(), sched.thread_id(), LS.ckboolean(useppool), true); open_lthread_state(LS.ckinteger(actorid), sched.place_id(), sched.thread_id(), LS.ckboolean(useppool));
int nargs = LS.ckboolean(isnew) ? (lua_gettop(CO) - 1) : lua_gettop(CO); int nargs = LS.ckboolean(isnew) ? (lua_gettop(CO) - 1) : lua_gettop(CO);
int status = lua_resume(CO, nullptr, nargs); int status = lua_resume(CO, nullptr, nargs);
std::ostream *ostream = lthread_print_stream();
if (status == LUA_OK) { if (status == LUA_OK) {
// Successfully ran to completion. Print any return values. // Successfully ran to completion. Print any return values.
@@ -1086,8 +1076,8 @@ void World::run_scheduled_threads() {
LuaCoreStack LSCO(CO); LuaCoreStack LSCO(CO);
if (LS.ckboolean(print)) { if (LS.ckboolean(print)) {
for (int i = 1; i <= lua_gettop(CO); i++) { for (int i = 1; i <= lua_gettop(CO); i++) {
pprint(LSCO, LuaSpecial(i), PrettyPrintOptions(), ostream); pprint(LSCO, LuaSpecial(i), PrettyPrintOptions(), &lthread_prints_);
(*ostream) << std::endl; lthread_prints_ << std::endl;
} }
} }
} else if (status == LUA_YIELD) { } else if (status == LUA_YIELD) {
@@ -1106,11 +1096,12 @@ void World::run_scheduled_threads() {
// Currently, the error is sent to the actor. That seems... not right in the long run. // Currently, the error is sent to the actor. That seems... not right in the long run.
if (is_authoritative()) { if (is_authoritative()) {
traceback_coroutine(CO); traceback_coroutine(CO);
(*ostream) << lua_tostring(CO, -1); lthread_prints_ << lua_tostring(CO, -1);
} }
LS.rawset(threads, sched.thread_id(), LuaNil); LS.rawset(threads, sched.thread_id(), LuaNil);
} }
close_lthread_state(); lthread_prints_to_printbuffer();
clear_lthread_state();
} }
} }
@@ -1140,14 +1131,15 @@ void World::clear_lthread_state() {
LS.rawset(globals, "actor", LuaNil); LS.rawset(globals, "actor", LuaNil);
LS.rawset(globals, "place", LuaNil); LS.rawset(globals, "place", LuaNil);
lthread_prints_.reset();
lthread_actor_id_ = 0; lthread_actor_id_ = 0;
lthread_place_id_ = 0; lthread_place_id_ = 0;
lthread_thread_id_ = 0; lthread_thread_id_ = 0;
lthread_use_ppool_ = false; lthread_use_ppool_ = false;
lthread_prints_.str();
lthread_prints_.clear();
} }
void World::open_lthread_state(int64_t actor, int64_t place, int64_t thread, bool ppool, bool prints) { void World::open_lthread_state(int64_t actor, int64_t place, int64_t thread, bool ppool) {
// Store actor and place in global variables. // Store actor and place in global variables.
LuaVar lactor, lplace, tangibles, globals; LuaVar lactor, lplace, tangibles, globals;
LuaExtStack LS(state(), lactor, lplace, tangibles, globals); LuaExtStack LS(state(), lactor, lplace, tangibles, globals);
@@ -1170,18 +1162,13 @@ void World::open_lthread_state(int64_t actor, int64_t place, int64_t thread, boo
lthread_place_id_ = place; lthread_place_id_ = place;
lthread_thread_id_ = thread; lthread_thread_id_ = thread;
lthread_use_ppool_ = ppool; lthread_use_ppool_ = ppool;
if (prints) { lthread_prints_.str();
lthread_prints_.reset(new eng::ostringstream); lthread_prints_.clear();
} else {
lthread_prints_.reset();
}
} }
void World::close_lthread_state() { void World::lthread_prints_to_printbuffer()
// Copy prints from lthread_prints_ stringstream into {
// the appropriate actor's PrintBuffer. const eng::string &output = lthread_prints_.str();
if (lthread_prints_ != nullptr) {
const eng::string &output = lthread_prints_->str();
if (output.size() > 0) { if (output.size() > 0) {
Tangible *actor = tangible_get(lthread_actor_id_); Tangible *actor = tangible_get(lthread_actor_id_);
if (actor != nullptr) { if (actor != nullptr) {
@@ -1189,17 +1176,15 @@ void World::close_lthread_state() {
} }
} }
} }
// Now clean up everything.
clear_lthread_state(); void World::lthread_prints_to_dprint()
{
const eng::string &output = lthread_prints_.str();
if (output.size() > 0) {
util::dprintview(output);
}
} }
std::ostream *World::lthread_print_stream() const {
if (lthread_prints_ != nullptr) {
return lthread_prints_.get();
} else {
return &std::cerr;
}
}
void World::set_global(const eng::string &gvar, std::string_view value) { void World::set_global(const eng::string &gvar, std::string_view value) {
// Store the serialized blob. // Store the serialized blob.

View File

@@ -358,10 +358,12 @@ public:
// cleared. // cleared.
// //
void clear_lthread_state(); void clear_lthread_state();
void open_lthread_state(int64_t actor_id, int64_t place_id, int64_t thread_id, bool ppool, bool prints); void open_lthread_state(int64_t actor_id, int64_t place_id, int64_t thread_id, bool ppool);
void close_lthread_state();
std::ostream *lthread_print_stream() const; std::ostream *lthread_print_stream() { return &lthread_prints_; }
void lthread_prints_to_printbuffer();
void lthread_prints_to_dprint();
// Set a lua global variable. // Set a lua global variable.
// //
@@ -670,7 +672,7 @@ private:
int64_t lthread_place_id_; int64_t lthread_place_id_;
int64_t lthread_thread_id_; int64_t lthread_thread_id_;
int64_t lthread_use_ppool_; int64_t lthread_use_ppool_;
std::unique_ptr<eng::ostringstream> lthread_prints_; eng::ostringstream lthread_prints_;
friend class Tangible; friend class Tangible;
friend int lfn_tangible_animate(lua_State *L); friend int lfn_tangible_animate(lua_State *L);