From 6a230e3ab28530a7f175d072e792a08b99fc77b8 Mon Sep 17 00:00:00 2001 From: jyelon Date: Mon, 8 Jun 2026 17:47:10 -0400 Subject: [PATCH] First attempt at error-resistant /cpl directive --- .../DefaultEditorPerProjectUserSettings.ini | 2 +- luprex/cpp/core/luastack.cpp | 1 + luprex/cpp/core/source.cpp | 149 ++++++++++-------- luprex/cpp/core/source.hpp | 106 ++++++++----- luprex/cpp/core/unit-testing.cpp | 3 +- luprex/cpp/core/world-core.cpp | 86 ++-------- luprex/cpp/core/world-diffxmit.cpp | 7 +- luprex/cpp/core/world.hpp | 17 -- luprex/lua/login.lua | 1 - 9 files changed, 169 insertions(+), 203 deletions(-) diff --git a/Config/DefaultEditorPerProjectUserSettings.ini b/Config/DefaultEditorPerProjectUserSettings.ini index 73a0d865..7b233817 100644 --- a/Config/DefaultEditorPerProjectUserSettings.ini +++ b/Config/DefaultEditorPerProjectUserSettings.ini @@ -13,5 +13,5 @@ RestoreOpenAssetTabsOnRestart=AlwaysRestore AutoSaveWarningInSeconds=0 [/Script/Integration.lxProjectSettings] -ActiveServer=/Game/Luprex/KnownServers/SS_Localhost.SS_Localhost +ActiveServer=/Game/Luprex/KnownServers/SS_Standalone.SS_Standalone diff --git a/luprex/cpp/core/luastack.cpp b/luprex/cpp/core/luastack.cpp index 605593e5..c98bb450 100644 --- a/luprex/cpp/core/luastack.cpp +++ b/luprex/cpp/core/luastack.cpp @@ -77,6 +77,7 @@ lua_State *LuaCoreStack::newstate (lua_Alloc allocf) { LS.rawset(LuaRegistry, "persist", LuaNewTable); LS.rawset(LuaRegistry, "unpersist", LuaNewTable); LS.rawset(LuaRegistry, "funcnames", LuaNewTable); + LS.rawset(LuaRegistry, "sourcedb", LuaNewTable); // Tag the registry and global environment with their tabletypes. LS.settabletype(LuaRegistry, LUA_TT_REGISTRY); diff --git a/luprex/cpp/core/source.cpp b/luprex/cpp/core/source.cpp index 047cd454..f4e4b3d4 100644 --- a/luprex/cpp/core/source.cpp +++ b/luprex/cpp/core/source.cpp @@ -132,16 +132,6 @@ static void get_info_table(LuaCoreStack &LS, LuaSlot db, LuaSlot info, const eng LS.rawset(info, "name", fn); } -static void calculate_loadresult(LuaCoreStack &LS0, LuaSlot info, const eng::string &fn, const eng::string &code) { - LuaVar loadresult; - LuaExtStack LS(LS0.state(), loadresult); - if (code == "") { - LS.rawset(info, "loadresult", "missing or empty source file"); - } else { - LS.load(loadresult, code, fn); - LS.rawset(info, "loadresult", loadresult); - } -} void SourceDB::diff(const SourceDB &auth, StreamBuffer *sb) { LuaVar sdb, sfn, sinfo, shash, sseq; @@ -202,8 +192,8 @@ void SourceDB::diff(const SourceDB &auth, StreamBuffer *sb) { bool SourceDB::patch(StreamBuffer *sb, DebugCollector *dbc) { lua_State *L = lua_state_; - LuaVar db, info; - LuaExtStack LS(L, db, info); + LuaVar db, info, closure; + LuaExtStack LS(L, db, info, closure); LS.rawget(db, LuaRegistry, "sourcedb"); int nupdates = sb->read_int32(); for (int i = 0; i < nupdates; i++) { @@ -219,24 +209,27 @@ bool SourceDB::patch(StreamBuffer *sb, DebugCollector *dbc) { if (code != "\001") { LS.rawset(info, "code", code); LS.rawset(info, "hash", util::hash_to_hex(util::hash_string(code))); - calculate_loadresult(LS, info, fn, code); + LS.load(closure, code, fn); + LS.rawset(info, "loadresult", closure); } } } + if (nupdates > 0) rebuild(); return (nupdates > 0); } void SourceDB::set(const eng::string &fn, const eng::string &code, int sequence) { lua_State *L = lua_state_; - LuaVar db, info; - LuaExtStack LS(L, db, info); + LuaVar db, info, closure; + LuaExtStack LS(L, db, info, closure); LS.rawget(db, LuaRegistry, "sourcedb"); get_info_table(LS, db, info, fn); LS.rawset(info, "sequence", sequence); LS.rawset(info, "code", code); LS.rawset(info, "fingerprint", ""); LS.rawset(info, "hash", util::hash_to_hex(util::hash_string(code))); - calculate_loadresult(LS, info, fn, code); + LS.load(closure, code, fn); + LS.rawset(info, "loadresult", closure); } eng::string SourceDB::get(const eng::string &fn) { @@ -262,19 +255,15 @@ eng::string SourceDB::get(const eng::string &fn) { return util::ss(seqno, ":", ccode, ":", cloadresult); } -void SourceDB::update(const util::LuaSourceVec &source) { +eng::string SourceDB::update(const util::LuaSourceVec &source) { lua_State *L = lua_state_; - LuaVar sourcedb, info; - LuaExtStack LS(L, sourcedb, info); + LuaVar newsourcedb, oldsourcedb, info, closure; + LuaExtStack LS(L, newsourcedb, oldsourcedb, info, closure); - // Get and clear the source database. - LS.rawget(sourcedb, LuaRegistry, "sourcedb"); - if (!LS.istable(sourcedb)) { - LS.newtable(sourcedb); - LS.rawset(LuaRegistry, "sourcedb", sourcedb); - } - LS.cleartable(sourcedb, true); + eng::ostringstream errors; + // Step one. Build the new sourcedb. + LS.newtable(newsourcedb); for (int i = 0; i < int(source.size()); i++) { const eng::string &file = source[i].first; const eng::string &code = source[i].second; @@ -283,9 +272,32 @@ void SourceDB::update(const util::LuaSourceVec &source) { LS.rawset(info, "code", code); LS.rawset(info, "hash", util::hash_to_hex(util::hash_string(code))); LS.rawset(info, "sequence", i + 1); - calculate_loadresult(LS, info, file, code); - LS.rawset(sourcedb, file, info); + LS.load(closure, code, file); + LS.rawset(info, "loadresult", closure); + if (LS.isstring(closure)) { + errors << file << ":" << LS.ckstring(closure) << std::endl; + } + LS.rawset(newsourcedb, file, info); } + + // If there were compile errors, return the errors and don't update the env. + if (!errors.view().empty()) { + return errors.str(); + } + + // Cache the old source database and store the new one. + LS.rawget(oldsourcedb, LuaRegistry, "sourcedb"); + LS.rawset(LuaRegistry, "sourcedb", newsourcedb); + + // Rebuild the source database. + eng::string rebuild_errors = rebuild(); + + // Restore the old state, if there were errors. + if (!rebuild_errors.empty()) { + LS.rawset(LuaRegistry, "sourcedb", oldsourcedb); + rebuild(); + } + return rebuild_errors; } // Delete everything from the global environment. @@ -411,14 +423,11 @@ eng::string SourceDB::get_source(const eng::string &fn) return LS.ckstring(code); } - eng::vector SourceDB::modules() { eng::vector result; LuaVar sourcedb, key, info, seq; LuaExtStack LS(lua_state_, sourcedb, key, info, seq); - result.push_back("CORE"); - // Get the source database. LS.rawget(sourcedb, LuaRegistry, "sourcedb"); if (!LS.istable(sourcedb)) { @@ -439,35 +448,38 @@ eng::vector SourceDB::modules() { return result; } -void SourceDB::rebuild_core() { +eng::string SourceDB::rebuild_module(const eng::string &mod) { + LuaVar sourcedb, info, closure; + LuaExtStack LS(lua_state_, sourcedb, info, closure); + LS.rawget(sourcedb, LuaRegistry, "sourcedb"); + LS.rawget(info, sourcedb, mod); + if (!LS.istable(info)) { + return util::ss("No such module: ", mod); + } + LS.rawget(closure, info, "loadresult"); + if (!LS.isfunction(closure)) { + return util::ss(mod, ":", LS.ckstring(closure)); + } + lua_pushvalue(lua_state_, closure.index()); + return traceback_pcall(lua_state_, 0, 0); +} + +eng::string SourceDB::rebuild() { source_clear_globals(lua_state_); source_load_cfunctions(lua_state_); source_load_cconstants(lua_state_); + + eng::ostringstream errors; + + for (const eng::string &mod: modules()) { + eng::string err = rebuild_module(mod); + if (!err.empty()) errors << err << std::endl; + } + + rebuild_funcnames(); + return errors.str(); } -eng::string SourceDB::rebuild_module(const eng::string &mod) { - if (mod == "CORE") { - rebuild_core(); - return ""; - } else { - LuaVar sourcedb, info, closure; - LuaExtStack LS(lua_state_, sourcedb, info, closure); - LS.rawget(sourcedb, LuaRegistry, "sourcedb"); - if (!LS.istable(sourcedb)) { - return "SourceDB not initialized"; - } - LS.rawget(info, sourcedb, mod); - if (!LS.istable(info)) { - return util::ss("No such module: ", mod); - } - LS.rawget(closure, info, "loadresult"); - if (!LS.isfunction(closure)) { - return util::ss(mod, ":", LS.ckstring(closure)); - } - lua_pushvalue(lua_state_, closure.index()); - return traceback_pcall(lua_state_, 0, 0); - } -} void SourceDB::init(lua_State *L) { register_lua_builtins(); @@ -482,8 +494,7 @@ void SourceDB::init(lua_State *L) { LS.set(nullstring, ""); LS.setmetatable(nullstring, classtab); - // Rebuild the global environment. - rebuild_core(); + rebuild(); // We need to register all C functions with the eris permanents tables. LS.rawget(persist, LuaRegistry, "persist"); @@ -516,15 +527,22 @@ void SourceDB::serialize_source(const util::LuaSourceVec &sv, StreamBuffer *sb) } } -void SourceDB::deserialize_source(util::LuaSourceVec *sv, StreamBuffer *sb) { - sv->clear(); - int count = sb->read_int32(); - if ((count < 0) || (count > 10000)) throw StreamCorruption(); - for (int i = 0; i < count; i++) { - eng::string fn = sb->read_string(); - eng::string code = sb->read_string(); - sv->emplace_back(fn, code); +util::LuaSourceVec SourceDB::deserialize_source(std::string_view datapack) { + StreamBuffer sb(datapack); + util::LuaSourceVec sv; + try { + sv.clear(); + int count = sb.read_int32(); + if ((count < 0) || (count > 10000)) throw StreamCorruption(); + for (int i = 0; i < count; i++) { + eng::string fn = sb.read_string(); + eng::string code = sb.read_string(); + sv.emplace_back(fn, code); + } + } catch (const StreamException &ex) { + sv.clear(); } + return sv; } // Register lua builtins. @@ -630,7 +648,6 @@ bool SourceDB::search_docs(const eng::string &substring, std::ostream &ostream) // Search the lua source code. for (const eng::string &module : modules()) { - if (module == "CORE") continue; eng::string code = get_source(module); if (code.empty()) continue; util::StringVec lines = util::split_lines(code); diff --git a/luprex/cpp/core/source.hpp b/luprex/cpp/core/source.hpp index 7b9dfad3..1f75012a 100644 --- a/luprex/cpp/core/source.hpp +++ b/luprex/cpp/core/source.hpp @@ -132,58 +132,31 @@ private: public: void init(lua_State *L); - // Update - // - // Update the database using the specified lua source code. - // Compiles these files using lua's "load" function. - // - void update(const util::LuaSourceVec &source); - - // modules - // - // Returns a list of all the modules. The first item in the list - // is always the string "CORE" which represents the lua core - // functionality with all the builtins. This is then followed by - // all the lua sourcefiles in the correct order. + // modules() + // + // Returns a list of all the lua modules, in the proper order. // eng::vector modules(); - - // get_source + // Update // - // Get the source code for a given module. + // Try to compile and load the specified source. Then, rebuild the + // global environment. // - eng::string get_source(const eng::string &fn); - - // rebuild_module + // If this generates any errors, puts back the old code, and rebuilds + // the global environment using the old code. + // + // Returns any error messages. If this returns empty string, it means + // there were no errors and the code was successfully update. If there + // are any error messages, it means we restored the old code as best as + // possible. // - // To rebuild the lua environment, fetch the module list, then - // call rebuild_module on each module in turn. This will return - // an error message for the module, or empty string if no error. - // - // This is a thin wrapper around traceback_pcall. The return - // value is the return value of traceback_pcall. - // - eng::string rebuild_module(const eng::string &mod); - - // rebuild_core - // - // This is equivalent to rebuild_module("CORE"). Clears the environment - // and installs all the builtins. No error conditions. - // - void rebuild_core(); - - // rebuild_funcnames - // - // Traverses the global environment and populates the registry "funcnames" - // table, mapping each closure to its name. - // - void rebuild_funcnames(); + eng::string update(const util::LuaSourceVec &source); // 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); bool patch(StreamBuffer *sb, DebugCollector *dbc); @@ -213,7 +186,54 @@ public: // Serialize and unserialize a source vector. // static void serialize_source(const util::LuaSourceVec &sv, StreamBuffer *sb); - static void deserialize_source(util::LuaSourceVec *sv, StreamBuffer *sb); + static util::LuaSourceVec deserialize_source(std::string_view datapack); + +private: + ////////////////////////////////////////////////////////////////////////// + // + // Internal implementation stuff. + // + ////////////////////////////////////////////////////////////////////////// + + // rebuild + // + // Rebuild the global environment from the sourcedb: + // + // * Clears the environment + // * Installs the builtins + // * Executes all the closures in the sourcedb. + // * Regenerates the function-names table. + // + // The closures may generate errors, if so, this returns the error + // messages. Note that if there are errors, there is no automatic cleanup. + // + eng::string rebuild(); + + // get_source + // + // Get the source code for a given module. + // + eng::string get_source(const eng::string &fn); + + // rebuild_module + // + // To rebuild the lua environment, fetch the module list, then + // call rebuild_module on each module in turn. This will return + // an error message for the module, or empty string if no error. + // + // This is a thin wrapper around traceback_pcall. The return + // value is the return value of traceback_pcall. + // + eng::string rebuild_module(const eng::string &mod); + + + // rebuild_funcnames + // + // Traverses the global environment and populates the registry "funcnames" + // table, mapping each closure to its name. + // + void rebuild_funcnames(); + }; diff --git a/luprex/cpp/core/unit-testing.cpp b/luprex/cpp/core/unit-testing.cpp index dff24ae5..b35175aa 100644 --- a/luprex/cpp/core/unit-testing.cpp +++ b/luprex/cpp/core/unit-testing.cpp @@ -55,7 +55,8 @@ public: virtual void event_access(AccessKind kind, int64_t place_id, std::string_view datapk, StreamBuffer *retpk) override { switch (kind) { case AccessKind::INVOKE_LUA_SOURCE: { - world_->update_source(datapk, 0); + Invocation inv(kind, place_id, place_id, datapk); + world_->invoke(0, inv); run_unittests(world_->state()); stop_driver(); break; diff --git a/luprex/cpp/core/world-core.cpp b/luprex/cpp/core/world-core.cpp index 7ec83e1e..a9a09549 100644 --- a/luprex/cpp/core/world-core.cpp +++ b/luprex/cpp/core/world-core.cpp @@ -572,71 +572,6 @@ void World::probe_lua_call(int64_t actor_id, int64_t place_id, std::string_view clear_lthread_state(); } - -// This is called from World::update_source, and also -// from World::patch_source in the difference transmitter. -// -// When called from the difference transmitter, we suppress -// error messages. -// -// For the moment, errors are channeled to util::dprint, -// and 'print' statements just go to std::cerr. Neither -// of these is ideal. We need to get serious about setting -// up error handling. -// -// We also need to figure out a solution for what happens if -// some lua source file tries to modify, say, tangible state -// in top-level code. -// -bool World::rebuild_sourcedb(int64_t actor_id) { - int successes = 0; - int failures = 0; - for (const eng::string &mod: source_db_.modules()) { - open_lthread_state(0, 0, 0, false); - eng::string err = source_db_.rebuild_module(mod); - eng::string prints = lthread_prints_.str(); - clear_lthread_state(); - if (err.empty()) successes ++; - else failures ++; - if ((!err.empty()) || (!prints.empty())) { - lthread_prints_ << "Compiling " << mod << ":" << std::endl; - if (!err.empty()) lthread_prints_ << err << std::endl; - if (!prints.empty()) lthread_prints_ << prints; - util::dprintview(lthread_prints_.view()); - if (actor_id != 0) lthread_prints_to_actor(actor_id); - } - } - source_db_.rebuild_funcnames(); - lthread_prints_ << "Compiled " << successes << " modules successfully." << std::endl; - if (failures > 0) { - lthread_prints_ << "Compiled " << failures << " modules with errors." << std::endl; - } - util::dprintview(lthread_prints_.view()); - if (actor_id > 0) lthread_prints_to_actor(actor_id); - return (failures == 0); -} - -bool World::update_source(const util::LuaSourceVec &source, int64_t actor_id) { - assert(stack_is_clear()); - source_db_.update(source); - return rebuild_sourcedb(actor_id); - assert(stack_is_clear()); -} - -bool World::update_source(std::string_view sourcepack, int64_t actor_id) { - if (sourcepack.empty()) { - return false; - } - try { - StreamBuffer sb(sourcepack); - util::LuaSourceVec sv; - SourceDB::deserialize_source(&sv, &sb); - return update_source(sv, actor_id); - } catch (const StreamException &ex) { - return false; - } -} - void World::http_response(const HttpParser &response) { // Find the request. auto iter = http_requests_.find(response.request_id()); @@ -1019,14 +954,27 @@ void World::invoke_lua_source(int64_t actor_id, int64_t place_id, std::string_vi if (actor_id != place_id) return; // Check if this is the first time we're loading the source. - bool brand_new = (source_db_.modules().size() == 1); + bool brand_new = (source_db_.modules().size() == 0); - // Compile and load the source. - bool success = update_source(datapack, actor_id); + // Deserialize the datapack. + util::LuaSourceVec sv = SourceDB::deserialize_source(datapack); + if (sv.empty()) return; + + // Try to compile the code. + open_lthread_state(actor_id, actor_id, 0, false); + eng::string errors = source_db_.update(sv); + + if (errors.empty()) { + lthread_prints_ << "Compiled source successfully.\n"; + } else { + lthread_prints_ << "Compiling source: \n" << errors; + } + util::dprint(lthread_prints_.view()); + lthread_prints_to_actor(actor_id); // Call world.init if (brand_new) { - if (success) { + if (errors.empty()) { { lua_State *L = state(); LuaVar lclass, lfunc; diff --git a/luprex/cpp/core/world-diffxmit.cpp b/luprex/cpp/core/world-diffxmit.cpp index adb90a29..1aa1314c 100644 --- a/luprex/cpp/core/world-diffxmit.cpp +++ b/luprex/cpp/core/world-diffxmit.cpp @@ -290,11 +290,7 @@ void World::diff_tanclass(int64_t actor_id, World *master, StreamBuffer *xsb) { void World::patch_source(StreamBuffer *sb, DebugCollector *dbc) { DebugBlock dbb(dbc, "patch_source"); - bool modified = source_db_.patch(sb, dbc); - if (modified) { - rebuild_sourcedb(0); - DebugLine(dbc) << "Source DB rebuilt"; - } + source_db_.patch(sb, dbc); } void World::diff_source(World *master, StreamBuffer *sb) { @@ -359,6 +355,7 @@ void World::diff_globals(World *master, StreamBuffer *sb) { int64_t World::patch(StreamBuffer *sb, DebugCollector *dbc) { DebugBlock dbb(dbc, "patch"); + clear_lthread_state(); int64_t actor_id = patch_actor(sb, dbc); patch_visible(sb, dbc); bool full = sb->read_bool(); diff --git a/luprex/cpp/core/world.hpp b/luprex/cpp/core/world.hpp index 45331e73..4627d4d3 100644 --- a/luprex/cpp/core/world.hpp +++ b/luprex/cpp/core/world.hpp @@ -242,23 +242,6 @@ public: // SourceDB &get_source() { return source_db_; } - // Rebuild the global environment from the source database. - // - // Error messages go to the specified actor, and also dprint. - // - // Returns true if the rebuild goes without errors. - // - bool rebuild_sourcedb(int64_t actor_id); - - // Update the source database from disk, then rebuild the global environment. - // - // Error messages go to the specified actor, and also dprint. - // - // Returns true if the update goes without errors. - // - bool update_source(const util::LuaSourceVec &source, int64_t actor_id); - bool update_source(std::string_view sourcepk, int64_t actor_id); - // Supply an HTTP response to an outstanding HTTP request. // void http_response(const HttpParser &response); diff --git a/luprex/lua/login.lua b/luprex/lua/login.lua index c07e085b..6666d780 100644 --- a/luprex/lua/login.lua +++ b/luprex/lua/login.lua @@ -54,7 +54,6 @@ function sphere.lookhotkeys(add) add("FaceR", "Sphere Yo", function () dprint("Doing Sphere Yo") end) end - function probe.getlookat() local class = tangible.getclass(place)