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
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.
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.
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.
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 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 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
@@ -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
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 ...).
This asks the PrintChanneler to fetch all new authoritative prints.
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
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.
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.

View File

@@ -387,29 +387,24 @@ eng::string World::probe_lua_expr(int64_t actor_id, std::string_view lua) {
// Call the closure.
int top = lua_gettop(L);
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);
// If there's an error message, print it.
// Otherwise, pretty-print the results.
std::ostream *ostream = lthread_print_stream();
if (msg.empty()) {
for (int i = top + 1; i <= lua_gettop(L); 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.
(*ostream) << std::endl;
lthread_prints_ << std::endl;
}
} else {
(*ostream) << msg << std::endl;
lthread_prints_ << msg << std::endl;
}
// Collect the lthread_prints (and also make sure they
// don't go into the printbuffer).
eng::string result = lthread_prints_->str();
lthread_prints_.reset();
close_lthread_state();
eng::string result = lthread_prints_.str();
clear_lthread_state();
return result;
}
@@ -478,15 +473,10 @@ void World::probe_lua_call(int64_t actor_id, int64_t place_id, std::string_view
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);
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.
// Instead, we're supposed to send it back to unreal as the first
// 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 ok = true;
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 prints = lthread_prints_->str();
lthread_prints_.reset();
close_lthread_state();
eng::string prints = lthread_prints_.str();
clear_lthread_state();
if (!err.empty()) ok = false;
if (!err.empty() || !prints.empty()) {
util::dprint("Loading Module ", mod);
@@ -705,9 +694,11 @@ HttpServerResponse World::http_serve(const HttpParser &request) {
int oldtop = lua_gettop(L);
lua_pushvalue(L, func.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);
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
// a 500 Internal Server Error to the client.
@@ -1073,10 +1064,9 @@ void World::run_scheduled_threads() {
// Resume the coroutine.
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 status = lua_resume(CO, nullptr, nargs);
std::ostream *ostream = lthread_print_stream();
if (status == LUA_OK) {
// Successfully ran to completion. Print any return values.
@@ -1086,8 +1076,8 @@ void World::run_scheduled_threads() {
LuaCoreStack LSCO(CO);
if (LS.ckboolean(print)) {
for (int i = 1; i <= lua_gettop(CO); i++) {
pprint(LSCO, LuaSpecial(i), PrettyPrintOptions(), ostream);
(*ostream) << std::endl;
pprint(LSCO, LuaSpecial(i), PrettyPrintOptions(), &lthread_prints_);
lthread_prints_ << std::endl;
}
}
} 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.
if (is_authoritative()) {
traceback_coroutine(CO);
(*ostream) << lua_tostring(CO, -1);
lthread_prints_ << lua_tostring(CO, -1);
}
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, "place", LuaNil);
lthread_prints_.reset();
lthread_actor_id_ = 0;
lthread_place_id_ = 0;
lthread_thread_id_ = 0;
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.
LuaVar lactor, lplace, tangibles, globals;
LuaExtStack LS(state(), lactor, lplace, tangibles, globals);
@@ -1170,37 +1162,30 @@ void World::open_lthread_state(int64_t actor, int64_t place, int64_t thread, boo
lthread_place_id_ = place;
lthread_thread_id_ = thread;
lthread_use_ppool_ = ppool;
if (prints) {
lthread_prints_.reset(new eng::ostringstream);
} else {
lthread_prints_.reset();
}
lthread_prints_.str();
lthread_prints_.clear();
}
void World::close_lthread_state() {
// Copy prints from lthread_prints_ stringstream into
// the appropriate actor's PrintBuffer.
if (lthread_prints_ != nullptr) {
const eng::string &output = lthread_prints_->str();
if (output.size() > 0) {
Tangible *actor = tangible_get(lthread_actor_id_);
if (actor != nullptr) {
actor->print_buffer_.add_string(output, is_authoritative());
}
void World::lthread_prints_to_printbuffer()
{
const eng::string &output = lthread_prints_.str();
if (output.size() > 0) {
Tangible *actor = tangible_get(lthread_actor_id_);
if (actor != nullptr) {
actor->print_buffer_.add_string(output, is_authoritative());
}
}
// Now clean up everything.
clear_lthread_state();
}
std::ostream *World::lthread_print_stream() const {
if (lthread_prints_ != nullptr) {
return lthread_prints_.get();
} else {
return &std::cerr;
void World::lthread_prints_to_dprint()
{
const eng::string &output = lthread_prints_.str();
if (output.size() > 0) {
util::dprintview(output);
}
}
void World::set_global(const eng::string &gvar, std::string_view value) {
// Store the serialized blob.
//

View File

@@ -358,10 +358,12 @@ public:
// cleared.
//
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 close_lthread_state();
void open_lthread_state(int64_t actor_id, int64_t place_id, int64_t thread_id, bool ppool);
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.
//
@@ -670,7 +672,7 @@ private:
int64_t lthread_place_id_;
int64_t lthread_thread_id_;
int64_t lthread_use_ppool_;
std::unique_ptr<eng::ostringstream> lthread_prints_;
eng::ostringstream lthread_prints_;
friend class Tangible;
friend int lfn_tangible_animate(lua_State *L);