diff --git a/Docs/Print-Statement-Handling.md b/Docs/Print-Statement-Handling.md index 80c57a4d..3c59d5b4 100644 --- a/Docs/Print-Statement-Handling.md +++ b/Docs/Print-Statement-Handling.md @@ -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. diff --git a/luprex/cpp/core/world-core.cpp b/luprex/cpp/core/world-core.cpp index fef7b65b..0bd52ec5 100644 --- a/luprex/cpp/core/world-core.cpp +++ b/luprex/cpp/core/world-core.cpp @@ -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(), <hread_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(), <hread_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. // diff --git a/luprex/cpp/core/world.hpp b/luprex/cpp/core/world.hpp index 6c71a05e..043f32f0 100644 --- a/luprex/cpp/core/world.hpp +++ b/luprex/cpp/core/world.hpp @@ -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 <hread_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 lthread_prints_; + eng::ostringstream lthread_prints_; friend class Tangible; friend int lfn_tangible_animate(lua_State *L);