diff --git a/luprex/core/Makefile b/luprex/core/Makefile index f70dba17..3a0eb886 100644 --- a/luprex/core/Makefile +++ b/luprex/core/Makefile @@ -10,7 +10,7 @@ CPP_FILES=\ cpp/luastack.cpp\ cpp/traceback.cpp\ cpp/planemap.cpp\ - cpp/print.cpp\ + cpp/pprint.cpp\ cpp/luaconsole.cpp\ cpp/idalloc.cpp\ cpp/globaldb.cpp\ diff --git a/luprex/core/cpp/print.cpp b/luprex/core/cpp/pprint.cpp similarity index 96% rename from luprex/core/cpp/print.cpp rename to luprex/core/cpp/pprint.cpp index 4ca5e598..0db59fb9 100644 --- a/luprex/core/cpp/print.cpp +++ b/luprex/core/cpp/pprint.cpp @@ -1,9 +1,18 @@ -#include "print.hpp" +#include "pprint.hpp" #include "util.hpp" #include "table.hpp" #include #include +// This file provides these functions for lua. +// +// They direct all output to std::cout +// +extern "C" { +void luai_writestring(const char *s, size_t len); +void luai_writeline(); +} + void luai_writestring(const char *s, size_t len) { std::cout.write(s, len); } @@ -250,15 +259,6 @@ void pprint(LuaStack &LS0, LuaSlot root, bool indent, std::ostream *os) { LS.result(); } -LuaDefine(pprint_pprint, "f") { - LuaStack LS(L); - for (int i = 1; i <= lua_gettop(L); i++) { - LuaSpecial root(i); - pprint(LS, root, true, &std::cout); - std::cout << std::endl; - } - return LS.result(); -} LuaDefine(string_pprint, "c") { LuaArg root, indent; diff --git a/luprex/core/cpp/print.hpp b/luprex/core/cpp/pprint.hpp similarity index 83% rename from luprex/core/cpp/print.hpp rename to luprex/core/cpp/pprint.hpp index 34f94ced..03465aa3 100644 --- a/luprex/core/cpp/print.hpp +++ b/luprex/core/cpp/pprint.hpp @@ -6,20 +6,11 @@ // ///////////////////////////////////////////////////////// -#ifndef PRINT_HPP -#define PRINT_HPP +#ifndef PPRINT_HPP +#define PPRINT_HPP #include "luastack.hpp" -// This file provides these functions for lua. -// -// They direct all output to std::cout -// -extern "C" { -void luai_writestring(const char *s, size_t len); -void luai_writeline(); -} - // Output a simple value to a stream. // // If the value is a string, number, boolean, or nil, it is @@ -52,4 +43,4 @@ void pprint(LuaStack &LS, LuaSlot val, bool indent, std::ostream *os); int lfn_pprint_pprint(lua_State *L); int lfn_string_pprint(lua_State *L); -#endif // PRINT_HPP \ No newline at end of file +#endif // PPRINT_HPP \ No newline at end of file diff --git a/luprex/core/cpp/printbuffer.hpp b/luprex/core/cpp/printbuffer.hpp index f939df90..a9e87148 100644 --- a/luprex/core/cpp/printbuffer.hpp +++ b/luprex/core/cpp/printbuffer.hpp @@ -96,22 +96,30 @@ private: // a newline (or any other weird control characters). void add_line(const char *line, int len); - // Return the line number beyond the end of the buffer. - int last_line() const { return first_line_ + int(lines_.size()); } - // Get the specified line number. - const std::string &nth(int n) const { return lines_[n - first_line_]; } public: PrintBuffer(util::WorldType wt); + // Get the current first line. + int first_line() const { return first_line_; } + + // Return the line number beyond the end of the buffer. + int last_line() const { return first_line_ + int(lines_.size()); } + + // Get the current first unchecked line. + int first_unchecked() const { return first_unchecked_; } + + // Get the specified line number. + const std::string &nth(int n) const { return lines_[n - first_line_]; } + // Add a string. If the string doesn't end in a newline, a newline // is added. The string is broken into lines, and the lines are added // to the PrintBuffer. void add_string(const char *text, int len); void add_string(const std::string &s); - + // Discard lines up to but not including line N. void discard_upto(int n); diff --git a/luprex/core/cpp/textgame.cpp b/luprex/core/cpp/textgame.cpp index 263ac1df..eca08921 100644 --- a/luprex/core/cpp/textgame.cpp +++ b/luprex/core/cpp/textgame.cpp @@ -14,8 +14,9 @@ #include "traceback.hpp" #include "textgame.hpp" #include "luaconsole.hpp" -#include "print.hpp" +#include "pprint.hpp" #include +#include class TextGame : public DrivenEngine { @@ -26,6 +27,7 @@ private: Gui gui_; int64_t gui_place_; int64_t actor_id_; + int64_t printbuffer_line_; void do_view_command(const StringVec &cmd); void do_menu_command(const StringVec &cmd); @@ -39,6 +41,7 @@ private: void do_command(const StringVec &exp); void check_redirects(); + void channel_printbuffer(); public: virtual void event_init(int argc, char *argv[]); @@ -167,12 +170,26 @@ void TextGame::check_redirects() { } } +void TextGame::channel_printbuffer() { + const PrintBuffer *printbuffer = world_->get_printbuffer(actor_id_); + if (printbuffer == nullptr) return; + if (printbuffer->first_line() > printbuffer_line_) { + printbuffer_line_ = printbuffer->first_line(); + } + while (printbuffer_line_ < printbuffer->first_unchecked()) { + std::cerr << "* " << printbuffer->nth(printbuffer_line_) << std::endl; + printbuffer_line_ += 1; + } +} + + void TextGame::event_init(int argc, char *argv[]) { world_.reset(new World(util::WORLD_TYPE_STANDALONE)); world_->update_source(get_lua_source()); world_->run_unittests(); actor_id_ = world_->create_login_actor(); + printbuffer_line_ = 0; std::cerr << "Login actor ID: " << actor_id_ << std::endl; get_stdio_channel()->out()->write_bytes(console_.get_prompt()); } @@ -188,9 +205,11 @@ void TextGame::event_update() if (action == LuaConsole::DO_LUA) { do_lua(console_.lua_expression()); console_.clear(); + channel_printbuffer(); } else if (action == LuaConsole::DO_COMMAND) { do_command(console_.words()); console_.clear(); + channel_printbuffer(); } else if (action == LuaConsole::DO_SYNTAX) { std::cerr << console_.syntax() << std::endl; console_.clear(); diff --git a/luprex/core/cpp/world-accessor.cpp b/luprex/core/cpp/world-accessor.cpp index 985dd30c..75fc493e 100644 --- a/luprex/core/cpp/world-accessor.cpp +++ b/luprex/core/cpp/world-accessor.cpp @@ -1,5 +1,6 @@ #include "world.hpp" +#include "pprint.hpp" LuaDefine(tangible_animstate, "c") { LuaArg tanobj; @@ -209,3 +210,14 @@ LuaDefine(world_settabletype, "f") { return LS.result(); } +LuaDefine(world_pprint, "f") { + World *w = World::fetch_global_pointer(L); + std::ostream *ostream = w->lthread_print_stream(); + LuaStack LS(L); + for (int i = 1; i <= lua_gettop(L); i++) { + LuaSpecial root(i); + pprint(LS, root, true, ostream); + (*ostream) << std::endl; + } + return LS.result(); +} \ No newline at end of file diff --git a/luprex/core/cpp/world-core.cpp b/luprex/core/cpp/world-core.cpp index b2c5d266..06c45425 100644 --- a/luprex/core/cpp/world-core.cpp +++ b/luprex/core/cpp/world-core.cpp @@ -4,7 +4,7 @@ #include "animqueue.hpp" #include "gui.hpp" #include "traceback.hpp" -#include "print.hpp" +#include "pprint.hpp" #include void World::store_global_pointer(lua_State *L, World *v) { @@ -49,7 +49,7 @@ World::World(util::WorldType wt) { Gui::store_global_pointer(state(), nullptr); // Clear the lthread state. - set_lthread_state(0, 0, false); + clear_lthread_state(); // Set the tabletype of the registry. LS.settabletype(LuaRegistry, LUA_TT_REGISTRY); @@ -262,6 +262,7 @@ int64_t World::create_login_actor() { LS.rawset(mt, "__index", classtab); LS.result(); tan->configure_id_pool_for_actor(); + tan->print_buffer_.reset(new PrintBuffer(world_type_)); assert(stack_is_clear()); return tan->id(); } @@ -305,9 +306,9 @@ void World::update_gui(int64_t actor_id, int64_t place_id, Gui *gui) { lua_pushvalue(L, actor.index()); lua_pushvalue(L, place.index()); Gui::store_global_pointer(L, gui); - set_lthread_state(actor_id, place_id, false); + open_lthread_state(actor_id, place_id, false, false); int status = traceback_pcall(L, 2, 0); - set_lthread_state(0, 0, false); + close_lthread_state(); Gui::store_global_pointer(L, nullptr); if (status != 0) { gui->clear(); @@ -372,25 +373,27 @@ void World::invoke_lua(int64_t actor_id, int64_t place_id, const std::string &ac // Call the closure. int top = lua_gettop(L); lua_pushvalue(L, closure.index()); - set_lthread_state(actor_id, place_id, false); + open_lthread_state(actor_id, place_id, false, true); status = traceback_pcall(L, 0, LUA_MULTRET); - set_lthread_state(0, 0, false); - + // If there's an error message, print it. // Otherwise, pretty-print the results. + std::ostream *ostream = lthread_print_stream(); if (status == LUA_OK) { for (int i = top + 1; i <= lua_gettop(L); i++) { LuaSpecial root(i); - pprint(LS, root, true, &std::cout); - std::cout << std::endl; + pprint(LS, root, true, ostream); + (*ostream) << std::endl; } } else { - const char *msg = lua_tostring(L, top); + const char *msg = lua_tostring(L, -1); if (msg == NULL) { msg = "(error object is not a string)"; } - std::cerr << msg << std::endl; + (*ostream) << msg << std::endl; } + close_lthread_state(); + LS.result(); assert(stack_is_clear()); } @@ -535,10 +538,10 @@ void World::run_scheduled_threads(int64_t clk) { // Resume the coroutine. lua_State *CO = LS.ckthread(thread); - set_lthread_state(LS.ckinteger(actorid), sched.place_id(), LS.ckboolean(useppool)); + open_lthread_state(LS.ckinteger(actorid), sched.place_id(), LS.ckboolean(useppool), true); int status = lua_resume(CO, nullptr, LS.ckboolean(isnew) ? 3 : 0); - set_lthread_state(0, 0, false); - + close_lthread_state(); + // Three possible outcomes: finished, yielded, or errored. if (status == LUA_YIELD) { // When the wait statement yields, it yields the desired timestamp. @@ -580,10 +583,60 @@ int64_t World::alloc_id_predictable() { return t->id_player_pool_.get_one(); } -void World::set_lthread_state(int64_t actor, int64_t place, bool ppool) { +const PrintBuffer *World::get_printbuffer(int64_t actor_id) { + Tangible *actor = tangible_get(actor_id); + if (actor != nullptr) { + return actor->print_buffer_.get(); + } + return nullptr; +} + +void World::clear_lthread_state() { + lthread_prints_.reset(); + lthread_actor_id_ = 0; + lthread_place_id_ = 0; + lthread_use_ppool_ = false; +} + +void World::open_lthread_state(int64_t actor, int64_t place, bool ppool, bool prints) { lthread_actor_id_ = actor; lthread_place_id_ = place; lthread_use_ppool_ = ppool; + if (prints) { + lthread_prints_.reset(new std::ostringstream); + } else { + lthread_prints_.reset(); + } +} + +void World::close_lthread_state() { + // Copy prints from lthread_prints_ stringstream into + // the appropriate actor's PrintBuffer. If for some reason + // there isn't an actor, or if the actor doesn't have a PrintBuffer, + // send the output to std::cerr. + if (lthread_prints_ != nullptr) { + const std::string &output = lthread_prints_->str(); + PrintBuffer *pbuffer = nullptr; + Tangible *actor = tangible_get(lthread_actor_id_); + if (actor != nullptr) { + pbuffer = actor->print_buffer_.get(); + } + if (pbuffer != nullptr) { + pbuffer->add_string(output); + } else { + std::cerr << output; + } + } + // 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::serialize(StreamBuffer *sb) { diff --git a/luprex/core/cpp/world-testing.cpp b/luprex/core/cpp/world-testing.cpp index 81a13778..bbe6e886 100644 --- a/luprex/core/cpp/world-testing.cpp +++ b/luprex/core/cpp/world-testing.cpp @@ -1,5 +1,5 @@ #include "world.hpp" -#include "print.hpp" +#include "pprint.hpp" #include void World::tangible_walkto(int64_t id, int64_t animid, float x, float y) { diff --git a/luprex/core/cpp/world.hpp b/luprex/core/cpp/world.hpp index de957214..3c2bd2d5 100644 --- a/luprex/core/cpp/world.hpp +++ b/luprex/core/cpp/world.hpp @@ -182,6 +182,10 @@ public: // void invoke(const Invocation &inv); + // Get the PrintBuffer of the actor. + // + const PrintBuffer *get_printbuffer(int64_t actor_id); + // Update the source database from disk. // // Special case: if the source pointer is nullptr, does not update. @@ -225,11 +229,16 @@ public: // * lthread_actor_id: current actor // * lthread_place_id: current place // * lthread_use_ppool: true if we should use the player ID pool. + // * lthread_prints_: a stringstream which will collect 'print' statements. // // As soon as the lua code stops executing, these variables are // cleared. // - void set_lthread_state(int64_t actor_id, int64_t place_id, bool ppool); + void clear_lthread_state(); + void open_lthread_state(int64_t actor_id, int64_t place_id, bool ppool, bool prints); + void close_lthread_state(); + + std::ostream *lthread_print_stream() const; // Allocate a single ID. // @@ -448,6 +457,7 @@ private: int64_t lthread_actor_id_; int64_t lthread_place_id_; int64_t lthread_use_ppool_; + std::unique_ptr lthread_prints_; friend class Tangible; friend int lfn_tangible_animate(lua_State *L);