diff --git a/luprex/core/cpp/invocation.hpp b/luprex/core/cpp/invocation.hpp index 3c728327..a006a3ef 100644 --- a/luprex/core/cpp/invocation.hpp +++ b/luprex/core/cpp/invocation.hpp @@ -21,6 +21,7 @@ public: KIND_INVALID, KIND_PLAN, KIND_LUA, + KIND_FLUSH_PRINTS, }; private: diff --git a/luprex/core/cpp/luastack.cpp b/luprex/core/cpp/luastack.cpp index b7245ac6..4238ace2 100644 --- a/luprex/core/cpp/luastack.cpp +++ b/luprex/core/cpp/luastack.cpp @@ -315,7 +315,7 @@ void LuaStack::makesubtable(LuaSlot sub, LuaSlot tab, const char *name) const { } } -void LuaStack::cleartable(LuaSlot tab) const { +void LuaStack::cleartable(LuaSlot tab, bool clearmeta) const { checktable(tab); lua_pushnil(L_); while (lua_next(L_, tab.index()) != 0) { @@ -324,6 +324,10 @@ void LuaStack::cleartable(LuaSlot tab) const { lua_pushnil(L_); // Push the new value. lua_rawset(L_, tab.index()); } + if (clearmeta) { + lua_pushnil(L_); + lua_setmetatable(L_, tab.index()); + } } int LuaStack::rawlen(LuaSlot obj) const { diff --git a/luprex/core/cpp/luastack.hpp b/luprex/core/cpp/luastack.hpp index 89718f84..f3405f76 100644 --- a/luprex/core/cpp/luastack.hpp +++ b/luprex/core/cpp/luastack.hpp @@ -380,7 +380,7 @@ public: lua_State *newthread(LuaSlot target) const; void getglobaltable(LuaSlot gltab) const; void makesubtable(LuaSlot sub, LuaSlot tab, const char *name) const; - void cleartable(LuaSlot tab) const; + void cleartable(LuaSlot tab, bool clearmeta) const; int rawlen(LuaSlot val) const; diff --git a/luprex/core/cpp/printbuffer.cpp b/luprex/core/cpp/printbuffer.cpp index 42404afe..56d70311 100644 --- a/luprex/core/cpp/printbuffer.cpp +++ b/luprex/core/cpp/printbuffer.cpp @@ -46,9 +46,6 @@ void PrintBuffer::discard_upto(int n) { lines_.pop_front(); first_line_ += 1; } - if (first_line_ < n) { - first_line_ = n; - } if (first_unchecked_ < first_line_) { first_unchecked_ = first_line_; } @@ -95,6 +92,9 @@ void PrintBuffer::patch(StreamBuffer *sb) { } first_unchecked_ = first_line_ + lines_.size(); discard_upto(auth_first); + if (first_line_ < auth_first) { + first_line_ = auth_first; + } } LuaDefine(unittests_printbuffer, "c") { @@ -110,7 +110,7 @@ LuaDefine(unittests_printbuffer, "c") { pbm.discard_upto(2); LuaAssertStrEq(L, pbm.debug_string(), "2,6:baz;a;b;c;"); pbm.discard_upto(8); - LuaAssertStrEq(L, pbm.debug_string(), "8,8:"); + LuaAssertStrEq(L, pbm.debug_string(), "6,6:"); LuaAssertStrEq(L, pbs.debug_string(), "0,0:"); pbs.add_string("foo\nbar\nbaz\n"); @@ -120,7 +120,7 @@ LuaDefine(unittests_printbuffer, "c") { pbs.discard_upto(2); LuaAssertStrEq(L, pbs.debug_string(), "2,2:baz;a;b;c;"); pbs.discard_upto(8); - LuaAssertStrEq(L, pbs.debug_string(), "8,8:"); + LuaAssertStrEq(L, pbs.debug_string(), "6,6:"); pbm.clear(); pbs.clear(); diff --git a/luprex/core/cpp/source.cpp b/luprex/core/cpp/source.cpp index 3d6ee224..e1e8e3c3 100644 --- a/luprex/core/cpp/source.cpp +++ b/luprex/core/cpp/source.cpp @@ -172,7 +172,7 @@ void SourceDB::diff(const SourceDB &auth, StreamBuffer *sb) { SLS.result(); } -void SourceDB::patch(StreamBuffer *sb) { +bool SourceDB::patch(StreamBuffer *sb) { lua_State *L = lua_state_; LuaVar db, info; LuaStack LS(L, db, info); @@ -195,7 +195,7 @@ void SourceDB::patch(StreamBuffer *sb) { } } LS.result(); - if (nupdates > 0) rebuild(false); + return (nupdates > 0); } void SourceDB::set(const std::string &fn, const std::string &code, int sequence) { @@ -250,7 +250,7 @@ void SourceDB::update(const util::LuaSourceVec &source) { LS.newtable(sourcedb); LS.rawset(LuaRegistry, "sourcedb", sourcedb); } - LS.cleartable(sourcedb); + LS.cleartable(sourcedb, true); for (int i = 0; i < int(source.size()); i++) { const std::string &file = source[i].first; @@ -278,8 +278,10 @@ static void source_clear_globals(lua_State *L) { LS.rawset(globtab, "_G", LuaNil); LS.set(classname, LuaNil); while (LS.next(globtab, classname, classtab) != 0) { - if (LS.istable(classtab)) { - table_clear(LS, classtab); + if (LS.rawequal(globtab, classtab)) { + LS.rawset(globtab, classname, LuaNil); + } else if (LS.istable(classtab)) { + LS.cleartable(classtab, true); } else { LS.rawset(globtab, classname, LuaNil); } @@ -292,6 +294,8 @@ static void source_clear_globals(lua_State *L) { // Load all the 'LuaDefine' C functions into the lua state. // static void source_load_cfunctions(lua_State *L) { + LuaVar classobj; + LuaStack LS(L, classobj); auto regs = LuaFunctionReg::all(); for (const LuaFunctionReg *r : regs) { const std::string &name = r->get_name(); @@ -307,24 +311,22 @@ static void source_load_cfunctions(lua_State *L) { lua_CFunction func = r->get_func(); std::string mode = r->get_mode(); if (mode.find('c') != std::string::npos) { // Insert into class - lua_pushlstring(L, classname.c_str(), classname.size()); - lfn_source_makeclass(L); - lua_pushcfunction(L, func); - lua_setfield(L, -2, funcname.c_str()); + LS.makeclass(classobj, classname); + LS.rawset(classobj, funcname, func); } if (mode.find('f') != std::string::npos) { // Make global function - lua_pushcfunction(L, func); - lua_setglobal(L, funcname.c_str()); + LS.getglobaltable(classobj); + LS.rawset(classobj, funcname, func); } } + LS.result(); } // Run all the closures from the source database. // -static void source_load_lfunctions(lua_State *L, bool print_errors) { - LuaRet errors; +static std::string source_load_lfunctions(lua_State *L) { LuaVar sourcedb, key, info, seq, closure, err; - LuaStack LS(L, sourcedb, errors, key, info, seq, closure, err); + LuaStack LS(L, sourcedb, key, info, seq, closure, err); // Get the source database. LS.rawget(sourcedb, LuaRegistry, "sourcedb"); @@ -349,9 +351,7 @@ static void source_load_lfunctions(lua_State *L, bool print_errors) { // If there's already an error in the sourcedb, collect it. if (!LS.isfunction(closure)) { - if (print_errors) { - errss << LS.ckstring(closure) << "\n"; - } + errss << LS.ckstring(closure) << "\n"; continue; } @@ -362,22 +362,17 @@ static void source_load_lfunctions(lua_State *L, bool print_errors) { errss << LS.ckstring(err); } } - LS.set(errors, errss.str()); LS.result(); + return errss.str(); } -void SourceDB::rebuild(bool print_errors) { +std::string SourceDB::rebuild() { lua_State *L = lua_state_; - LuaVar errs; - LuaStack LS(L, errs); source_clear_globals(L); source_install_builtins(L); source_load_cfunctions(L); - source_load_lfunctions(L, print_errors); - lua_replace(L, errs.index()); - std::string errstr = LS.ckstring(errs); - std::cerr << errstr; - LS.result(); + std::string errs = source_load_lfunctions(L); + return errs; } void SourceDB::run_unittests() { diff --git a/luprex/core/cpp/source.hpp b/luprex/core/cpp/source.hpp index e6b8dfea..fba7355b 100644 --- a/luprex/core/cpp/source.hpp +++ b/luprex/core/cpp/source.hpp @@ -141,18 +141,21 @@ public: // // Rebuild the lua environment: clear it out, then reinstall all the // functions that should be there. See above for more information. - // If an error exists in any of the source files, or when loading any - // of the closures, the error can optionally be printed or ignored. // - void rebuild(bool print_errors); - + // Any error messages will be collected into a single long string + // containing one error per line, and returned. If the return value + // is the empty string, there were no errors. + // + std::string rebuild(); + // Difference transmission. // // Note: The patch routine applies the differences to the source // database, and if there are any changes, it does a source rebuild. + // The patch routine returns true if anything was modified. // void diff(const SourceDB &auth, StreamBuffer *sb); - void patch(StreamBuffer *sb); + bool patch(StreamBuffer *sb); // run_unittests // @@ -170,19 +173,6 @@ public: std::string get(const std::string &fn); }; -// The Lua 'makeclass' operator. -// -// Creates a table in the global environment with the specified name. -// Adds a __class field and an __index field, and an action subtable. -// If there's already a table with this name in the global environment, -// leaves it there, and repairs the __class and __index fields. -// -int lfn_source_makeclass(lua_State *L); - -// Return true if the specified table is a class created by 'makeclass' -// -int lfn_source_isclass(lua_State *L); - #endif // SOURCE_HPP diff --git a/luprex/core/cpp/table.cpp b/luprex/core/cpp/table.cpp index 41ee306f..cf156449 100644 --- a/luprex/core/cpp/table.cpp +++ b/luprex/core/cpp/table.cpp @@ -128,14 +128,23 @@ LuaDefine(table_count, "c") { return 1; } -void table_clear(LuaStack &LS0, LuaSlot table) { - LS0.cleartable(table); -} - LuaDefine(table_clear, "c") { - LuaArg tab; - LuaStack LS(L, tab); - table_clear(LS, tab); + LuaArg tab, clearmeta; + LuaVar metatable, metafield; + LuaStack LS(L, tab, clearmeta, metatable, metafield); + if (LS.ckboolean(clearmeta)) { + LS.getmetatable(metatable, tab); + if (LS.istable(metatable)) { + LS.rawget(metafield, metatable, "__metatable"); + if (!LS.isnil(metafield)) { + luaL_error(L, "Cannot clear metatable."); + return LS.result(); + } + } + LS.cleartable(tab, true); + } else { + LS.cleartable(tab, false); + } return LS.result(); } diff --git a/luprex/core/cpp/table.hpp b/luprex/core/cpp/table.hpp index 20d133a5..99d3e883 100644 --- a/luprex/core/cpp/table.hpp +++ b/luprex/core/cpp/table.hpp @@ -12,13 +12,6 @@ #include "luastack.hpp" -// table_clear -// -// Remove all key/value pairs from the table. Does not remove -// the metatable. -// -void table_clear(LuaStack &LS0, LuaSlot tab); - // table_equal // // True if two tables contain the same key/value pairs. diff --git a/luprex/core/cpp/textgame.cpp b/luprex/core/cpp/textgame.cpp index eca08921..e2488d51 100644 --- a/luprex/core/cpp/textgame.cpp +++ b/luprex/core/cpp/textgame.cpp @@ -180,6 +180,11 @@ void TextGame::channel_printbuffer() { std::cerr << "* " << printbuffer->nth(printbuffer_line_) << std::endl; printbuffer_line_ += 1; } + if (printbuffer_line_ > printbuffer->first_line()) { + InvocationData data; + Invocation inv(Invocation::KIND_FLUSH_PRINTS, actor_id_, actor_id_, std::to_string(printbuffer_line_), data); + world_->invoke(inv); + } } diff --git a/luprex/core/cpp/world-core.cpp b/luprex/core/cpp/world-core.cpp index 06c45425..f4c39792 100644 --- a/luprex/core/cpp/world-core.cpp +++ b/luprex/core/cpp/world-core.cpp @@ -67,7 +67,11 @@ World::World(util::WorldType wt) { // Initialize the SourceDB. At this stage, the sourcedb is // empty, so it's just populating the lua builtins. source_db_.init(state()); - source_db_.rebuild(true); + std::string srcerrs = source_db_.rebuild(); + + // There shouldn't be any lua errors in the sourceDB at this + // point, since there's no lua code in the sourceDB. + assert(srcerrs == ""); LS.result(); assert (stack_is_clear()); @@ -212,8 +216,7 @@ void World::tangible_delete(int64_t id) { assert(LS.istable(database)); // Clear out the database. - LS.clearmetatable(database); - LS.cleartable(database); + LS.cleartable(database, true); LS.settabletype(database, LUA_TT_DEADTANGIBLE); // Remove the lua portion from the tangibles table. @@ -252,6 +255,7 @@ World::Redirects World::fetch_redirects() { } int64_t World::create_login_actor() { + assert(stack_is_clear()); int64_t id = id_global_pool_.get_one(); Tangible *tan = tangible_make(state(), id, "nowhere", true); LuaArg database; @@ -323,10 +327,23 @@ void World::update_gui(int64_t actor_id, int64_t place_id, Gui *gui) { } void World::update_source(util::LuaSourcePtr source) { + assert(stack_is_clear()); if (source != nullptr) { source_db_.update(*source); - source_db_.rebuild(true); + assert(stack_is_clear()); + std::string errs = source_db_.rebuild(); + // I don't have a good place to send the error messages right + // now. The engine needs a catch-all place to send errors that + // occur at unexpected times. + std::cerr << errs; } + assert(stack_is_clear()); +} + +void World::run_unittests() { + assert(stack_is_clear()); + source_db_.run_unittests(); + assert(stack_is_clear()); } void World::invoke(const Invocation &inv) { @@ -337,6 +354,9 @@ void World::invoke(const Invocation &inv) { case Invocation::KIND_LUA: invoke_lua(inv.actor(), inv.place(), inv.action(), inv.data()); break; + case Invocation::KIND_FLUSH_PRINTS: + invoke_flush_prints(inv.actor(), inv.place(), inv.action(), inv.data()); + break; default: // Do nothing. Standard behavior for any invalid command is to // simply do nothing at all. Perhaps eventually we may add a flag @@ -347,6 +367,28 @@ void World::invoke(const Invocation &inv) { } } +void World::invoke_flush_prints(int64_t actor_id, int64_t place_id, const std::string &action, const InvocationData &data) { + assert(stack_is_clear()); + // Check argument sanity. + if (actor_id != place_id) { + return; + } + int line = util::strtoint(action, -1); + if (line < 0) { + return; + } + Tangible *tactor = tangible_get(actor_id); + if (tactor == nullptr) { + return; + } + if (tactor->print_buffer_ == nullptr) { + return; + } + tactor->print_buffer_->discard_upto(line); + assert(stack_is_clear()); +} + + void World::invoke_lua(int64_t actor_id, int64_t place_id, const std::string &action, const InvocationData &data) { assert(stack_is_clear()); diff --git a/luprex/core/cpp/world-diffxmit.cpp b/luprex/core/cpp/world-diffxmit.cpp index b33aa610..e1011dec 100644 --- a/luprex/core/cpp/world-diffxmit.cpp +++ b/luprex/core/cpp/world-diffxmit.cpp @@ -1,4 +1,5 @@ #include "world.hpp" +#include util::IdVector World::get_visible_union(int64_t actor_id, World *master) { return util::sort_union_id_vectors( @@ -263,7 +264,13 @@ void World::diff_tanclass(int64_t actor_id, World *master, StreamBuffer *xsb) { } void World::patch_source(StreamBuffer *sb) { - source_db_.patch(sb); + bool modified = source_db_.patch(sb); + if (modified) { + std::string errs = source_db_.rebuild(); + // TODO: I don't currently have any good place to send the + // error messages. This is a stopgap. + std::cerr << errs; + } } void World::diff_source(World *master, StreamBuffer *sb) { diff --git a/luprex/core/cpp/world.hpp b/luprex/core/cpp/world.hpp index 3c2bd2d5..62ecb997 100644 --- a/luprex/core/cpp/world.hpp +++ b/luprex/core/cpp/world.hpp @@ -194,7 +194,7 @@ public: // Run all unit tests. // - void run_unittests() { source_db_.run_unittests(); } + void run_unittests(); // fetch_global_pointer // @@ -263,6 +263,10 @@ private: // void invoke_lua(int64_t actor_id, int64_t place_id, const std::string &action, const InvocationData &data); + // Invoke the flush-prints operation. + // + void invoke_flush_prints(int64_t actor_id, int64_t place_id, const std::string &action, const InvocationData &data); + public: //////////////////////////////////////////////////////////////////////////// // diff --git a/luprex/core/lua/ut-table.lua b/luprex/core/lua/ut-table.lua index bf8e48f6..6831d98f 100644 --- a/luprex/core/lua/ut-table.lua +++ b/luprex/core/lua/ut-table.lua @@ -1,12 +1,6 @@ makeclass("unittests") -function cmt() - for i=1,100000 do - local t = {1,2,3,4,5,a=1,b=2,c=3,d=4,e=5} - table.clear(t) - end -end function unittests.tables() -- check table.count @@ -16,7 +10,7 @@ function unittests.tables() -- check table.clear local t = { a = 1, b = 2 } - table.clear(t) + table.clear(t, true) assert(t.a == nil) assert(t.b == nil) assert(table.count(t) == 0)