From f0f4ad86096bb9c020aa34581ba50e8f31474f76 Mon Sep 17 00:00:00 2001 From: jyelon Date: Wed, 5 Apr 2023 18:41:03 -0400 Subject: [PATCH] global.set has been implemented, but not diff xmit for globals --- luprex/cpp/core/drivenengine.cpp | 2 +- luprex/cpp/core/enginewrapper.hpp | 2 +- luprex/cpp/core/globaldb.cpp | 74 ------------- luprex/cpp/core/globaldb.hpp | 20 +--- luprex/cpp/core/util.cpp | 51 +++++---- luprex/cpp/core/util.hpp | 70 ++++++------ luprex/cpp/core/world-accessor.cpp | 169 +++++++++++++++++++++++++++++ luprex/cpp/core/world-core.cpp | 3 + luprex/cpp/core/world.hpp | 13 ++- luprex/cpp/drv/driver-common.cpp | 8 +- 10 files changed, 260 insertions(+), 152 deletions(-) diff --git a/luprex/cpp/core/drivenengine.cpp b/luprex/cpp/core/drivenengine.cpp index 8a2ab6e5..2ebbe43e 100644 --- a/luprex/cpp/core/drivenengine.cpp +++ b/luprex/cpp/core/drivenengine.cpp @@ -1016,7 +1016,7 @@ static void init_engine_wrapper_helper(EngineWrapper *w) { w->replay_initialize = replaycore_initialize; w->replay_step = replaycore_step; - w->hook_dprintf = util::hook_dprintf; + w->hook_dprint = util::hook_dprint; w->release = release; }; diff --git a/luprex/cpp/core/enginewrapper.hpp b/luprex/cpp/core/enginewrapper.hpp index 783d24c9..caf4ad28 100644 --- a/luprex/cpp/core/enginewrapper.hpp +++ b/luprex/cpp/core/enginewrapper.hpp @@ -242,7 +242,7 @@ struct EngineWrapper { // will only contain printable characters. The line will not contain a // newline - the newline is implied. // - void (*hook_dprintf)(void (*func)(const char *oneline)); + void (*hook_dprint)(void (*func)(const char *oneline, size_t size)); // Restore the wrapper to its initial blank state. // diff --git a/luprex/cpp/core/globaldb.cpp b/luprex/cpp/core/globaldb.cpp index 2e372a00..f8fc1d01 100644 --- a/luprex/cpp/core/globaldb.cpp +++ b/luprex/cpp/core/globaldb.cpp @@ -1,77 +1,3 @@ #include "luastack.hpp" #include "globaldb.hpp" - -LuaDefine(global_set, "varname, value", - "|Store data in the global data table." - "|" - "|The variable name must be a string which is a valid" - "|lua identifier." - "|" - "|You can store global data using global.set, then you can" - "|retrieve it using global.get. You can also retrieve data using" - "|gv.varname, which is just shorthand for global.get. You may not" - "|store data using gv.varname=value, this yields the error 'Use " - "|global.set to store data in the global data table.'" - "|" - "|Values stored using global.set are transmitted to all" - "|connected clients immediately. When a new client connects," - "|he will receive all the global data." - "|" - "|The global data table is not the same thing as the lua " - "|environment table. Trying to store data in the lua environment" - "|table will seem to work, at first, but the data will not get" - "|difference transmitted, and will eventually be cleared and lost." - "|Therefore, it is essential that global data be stored in the" - "|global data table (using global.set) instead of in the lua" - "|environment table." - "|" - "|There are certain restrictions on the values that you store." - "|The value can contain strings, numbers, simple tables, and" - "|tangibles. Nothing else is allowed. Simple tables are tables" - "|that don't have metatables, and that aren't special tables such" - "|as classes, the lua environment table, the registry, or the like." - "|" - "|When you store the value, a recursive copy is made and stored." - "|When you call global.get, you obtain the copy. Any attempt to" - "|mutate the copy will fail with this lua error message: 'Tables" - "|returned by global.get are immutable.' This rule prevents'" - "|aliasing between global data and other data structures." - "|") { - return 0; -} - -LuaDefine(global_get, "varname", - "|Get data stored using global.set" - "|" - "|See doc(global.set) for information on how to store global data." - "|") { - return 0; -} - -LuaDefine(global_once, "name", - "|For a given string, returns true exactly once" - "|" - "|The semantics and difference transmission behavior of global.once" - "|are identical to the semantics of global.set, since global.once" - "|uses global.set under the covers." - "|") { - LuaArg name; - LuaRet flag; - LuaVar oncedb, val; - LuaStack LS(L, name, flag, oncedb, val); - return 0; -} - -LuaDefine(global_clearonce, "name", - "|Reset the specified once-flag" - "|" - "|The semantics and difference transmission behavior of global.clearonce" - "|are identical to the semantics of global.set, since global.once" - "|uses global.set under the covers." - "|") { - LuaArg name; - LuaVar oncedb; - LuaStack LS(L, name, oncedb); - return 0; -} diff --git a/luprex/cpp/core/globaldb.hpp b/luprex/cpp/core/globaldb.hpp index 423a2a7e..c04321ef 100644 --- a/luprex/cpp/core/globaldb.hpp +++ b/luprex/cpp/core/globaldb.hpp @@ -2,31 +2,13 @@ // // GLOBALDB // -// The master world model is allowed to maintain global data structures. -// -// Unfortunately, any attempt to put a global data structure into the global -// environment will fail, because the global environment periodically gets wiped -// clean by the "source rebuild" procedure (see module 'source'). -// -// This module creates a safe space where you can put global data structures -// that won't get wiped out. -// -// THE GLOBAL OPERATOR -// -// This module adds a new function to lua: "global". Global takes a global data -// structure name (a string) and returns a table for that global data structure. -// You can put anything you want into the table. -// -// Since you're only allowed to use global data structures in the master world -// model, any attempt to call "global" in a synchronous world model will -// result in a 'donotpredict' error. -// //////////////////////////////////////////////////////////// #ifndef GLOBALDB_HPP #define GLOBALDB_HPP + #endif // GLOBALDB_HPP diff --git a/luprex/cpp/core/util.cpp b/luprex/cpp/core/util.cpp index 887282ca..e2ef05f0 100644 --- a/luprex/cpp/core/util.cpp +++ b/luprex/cpp/core/util.cpp @@ -721,43 +721,52 @@ eng::string XYZ::debug_string() const { } -void (*dprintf_hook)(const char *oneline); +void (*dprint_hook)(const char *oneline, size_t size); -void hook_dprintf(void (*func)(const char *oneline)) { - dprintf_hook = func; +void hook_dprint(void (*func)(const char *oneline, size_t size)) { + dprint_hook = func; } -static void chop_up_dprintf(char *buffer, int size) { +void dprintview(std::string_view view) { // Drop the final newline, if any. We're going // to automatically end with a newline and we don't // want to double up. - if ((size > 0) && (buffer[size-1] == '\n')) { - size -= 1; + if ((view.size() > 0) && (view.back() == '\n')) { + view.remove_suffix(1); } // Chop it up into lines and call the hook one line - // at a time. Replace control characters as we go. + // at a time. + const char *buffer = view.data(); const char *base = buffer; - for (int i = 0; i <= size; i++) { - if (buffer[i] < ' ') { - if ((buffer[i] == '\n') || (buffer[i] == 0)) { - buffer[i] = 0; - if (dprintf_hook == nullptr) { - fprintf(stderr, "%s\n", base); - } else { - dprintf_hook(base); - } - base = buffer + i + 1; + for (int i = 0; i < int(view.size()); i++) { + if ((buffer[i] == '\n') || (buffer[i] == 0)) { + size_t sz = buffer + i - base; + if (dprint_hook == nullptr) { + fwrite(base, 1, sz, stderr); + fwrite("\n", 1, 1, stderr); } else { - buffer[i] = ' '; + dprint_hook(base, sz); } + base = buffer + i + 1; + } + } + + // Output the final line. + size_t sz = buffer + view.size() - base; + if (sz > 0) { + if (dprint_hook == nullptr) { + fwrite(base, 1, sz, stderr); + fwrite("\n", 1, 1, stderr); + } else { + dprint_hook(base, sz); } } // If we're sending to stderr, flush. If not, then // the hook routine is responsible for flushing its own // output. - if (dprintf_hook == nullptr) { + if (dprint_hook == nullptr) { fflush(stderr); } } @@ -768,11 +777,11 @@ void dprintf(const char *format, ...) { va_start (args, format); int n = vsnprintf(buffer, 256, format, args); if (n <= 255) { - chop_up_dprintf(buffer, n); + dprintview(std::string_view(buffer, n)); } else { char *lbuffer = (char *)malloc(n + 1); vsnprintf(lbuffer, n+1, format, args); - chop_up_dprintf(lbuffer, n); + dprintview(std::string_view(lbuffer, n)); free(lbuffer); } } diff --git a/luprex/cpp/core/util.hpp b/luprex/cpp/core/util.hpp index 7485f475..17a2db65 100644 --- a/luprex/cpp/core/util.hpp +++ b/luprex/cpp/core/util.hpp @@ -328,10 +328,35 @@ struct XYZ { eng::string debug_string() const; }; -class NullStreamBuffer : public std::streambuf -{ +// util::ostringstream +// +// This is a variant of ostringstream in which it is possible +// to get the contents without copying. To get the contents +// without copying, use oss.view(). +// +class ostringstream : public eng::ostringstream { + class rstringbuf : public std::basic_stringbuf { + public: + char *eback() const { return std::streambuf::eback(); } + char *pptr() const { return std::streambuf::pptr(); } + }; + rstringbuf rstringbuf_; public: - int overflow(int c) { return c; } + ostringstream() { + std::basic_ostream::rdbuf(&rstringbuf_); + } + char *data() const { + return rstringbuf_.eback(); + } + size_t size() const { + return rstringbuf_.pptr() - rstringbuf_.eback(); + } + std::string_view view() const { + return std::string_view(data(), size()); + } + eng::string str() const { + return rstringbuf_.str(); + } }; // send_to_stream: send all arguments to the specified stream. @@ -350,34 +375,7 @@ inline eng::string ss(const ARGS & ... args) { return oss.str(); } -// util::ostringstream -// -// This is a variant of ostringstream in which it is possible -// to get the contents without copying. To get the contents -// without copying, use oss.view(). -// -class ostringstream : public eng::ostringstream { - class rstringbuf : public std::basic_stringbuf { - public: - char *eback() const { return std::streambuf::eback(); } - char *pptr() const { return std::streambuf::pptr(); } - }; - rstringbuf rstringbuf_; -public: - ostringstream() { - std::basic_ostream::rdbuf(&rstringbuf_); - } - std::string_view view() const { - char *p = rstringbuf_.eback(); - size_t size = rstringbuf_.pptr() - p; - return std::string_view(p, size); - } - eng::string str() const { - return rstringbuf_.str(); - } -}; - -// dprintf +// dprintf / dprint // // Send a debugging message to somewhere that it can be seen. This routine // initially just sends output to stderr. But it can be hooked to send output @@ -388,8 +386,16 @@ public: // characters only. There will be no control characters. The newline is // implied. // +void dprintview(std::string_view view); void dprintf(const char *format, ...); -void hook_dprintf(void (*func)(const char *oneline)); +void hook_dprint(void (*func)(const char *oneline, size_t size)); + +template +inline void dprint(const ARGS & ... args) { + util::ostringstream oss; + send_to_stream(oss, args...); + dprintview(oss.view()); +} // A better API than std::setfill, std::hex, std::setw, std::setprecision // diff --git a/luprex/cpp/core/world-accessor.cpp b/luprex/cpp/core/world-accessor.cpp index b98107ac..06d88ecd 100644 --- a/luprex/cpp/core/world-accessor.cpp +++ b/luprex/cpp/core/world-accessor.cpp @@ -1,6 +1,7 @@ #include "world.hpp" #include "pprint.hpp" +#include "serializelua.hpp" #include #include @@ -854,3 +855,171 @@ LuaDefine(http_post, "request", "|See doc(http.clientrequest) and doc(http.clientresponse).") { return lfn_http_request(L, "POST"); } + +void global_set(LuaStack &LS0, const eng::string &gvar, LuaSlot value) { + lua_State *L = LS0.state(); + World *w = World::fetch_global_pointer(L); + LuaVar globaldb, copy; + LuaStack LS(L, globaldb, copy); + + // Serialize then deserialize the data, to produce a copy. + StreamBuffer sb; + eng::string error = serialize_lua(LS, value, &sb); + if (!error.empty()) { + luaL_error(L, "%s", error.c_str()); + return; + } + eng::string serialized(sb.view()); + error = deserialize_lua(LS, copy, &sb); + if (!error.empty()) { + luaL_error(L, "%s", error.c_str()); + return; + } + + // Store the copy in the globalDB. + LS.rawget(globaldb, LuaRegistry, "globaldb"); + LS.rawset(globaldb, gvar, copy); + + // Store the serialized blob in the master model. + w->gvar_to_serial_.emplace(gvar, serialized); + + // In an authoritative model only, update the assignment counters. + if (w->is_authoritative()) { + int64_t newassign = w->next_gvar_assign_++; + auto oldassigniter = w->gvar_to_assign_.find(gvar); + if (oldassigniter != w->gvar_to_assign_.end()) { + int64_t oldassign = oldassigniter->second; + w->assign_to_gvar_.erase(oldassign); + } + w->assign_to_gvar_.emplace(newassign, gvar); + w->gvar_to_assign_[gvar] = newassign; + } + LS.result(); +} + +LuaDefine(global_set, "varname, value", + "|Store data in the global data table." + "|" + "|The variable name must be a string which is a valid" + "|lua identifier." + "|" + "|You can store global data using global.set, then you can" + "|retrieve it using global.get. You can also retrieve data using" + "|gv.varname, which is just shorthand for global.get. You may not" + "|store data using gv.varname=value, this yields the error 'Use " + "|global.set to store data in the global data table.'" + "|" + "|Values stored using global.set are transmitted to all" + "|connected clients immediately. When a new client connects," + "|he will receive all the global data." + "|" + "|The global data table is not the same thing as the lua " + "|environment table. Trying to store data in the lua environment" + "|table will seem to work, at first, but the data will not get" + "|difference transmitted, and will eventually be cleared and lost." + "|Therefore, it is essential that global data be stored in the" + "|global data table (using global.set) instead of in the lua" + "|environment table." + "|" + "|There are certain restrictions on the values that you store." + "|Only data that can be serialized according to doc(table.serialize)" + "|can be stored." + "|" + "|When you store the value, it is immediately serialized and then" + "|deserialized again, and the deserialized copy is stored in the" + "|variable." + "|" +// "|When you call global.get, you obtain the copy. Any attempt to" +// "|mutate the copy will fail with this lua error message: 'Tables" +// "|returned by global.get are immutable.' This rule prevents'" +// "|aliasing between global data and other data structures." + "|") { + LuaArg varname; + LuaArg value; + LuaStack LS(L, varname, value); + + // Check the varname argument. + eng::string gvar = LS.ckstring(varname); + if (!sv::is_lua_id(gvar)) { + luaL_error(L, "variable name must be a valid lua identifier: %s", gvar.c_str()); + return LS.result(); + } + + global_set(LS, gvar, value); + return LS.result(); +} + +LuaDefine(global_get, "varname", + "|Get data stored using global.set" + "|" + "|See doc(global.set) for information on how to store global data." + "|" + "|Do not mutate data returned by global.get: doing so will produce" + "|unpredictable results. Instead, using global.set to mutate global" + "|variables." + "|") { + LuaArg varname; + LuaRet value; + LuaVar globaldb; + LuaStack LS(L, varname, value, globaldb); + LS.rawget(globaldb, LuaRegistry, "globaldb"); + LS.rawget(value, globaldb, varname); + return LS.result(); +} + +LuaDefine(global_once, "varname", + "|For a given string, returns true exactly once" + "|" + "|The semantics and difference transmission behavior of global.once" + "|are identical to the semantics of global.set, since global.once" + "|uses global.set under the covers." + "|") { + LuaArg varname; + LuaRet result; + LuaVar globaldb, flag; + LuaStack LS(L, varname, flag, result, globaldb); + + // Check the varname argument. + eng::string gvar = LS.ckstring(varname); + if (!sv::is_lua_id(gvar)) { + luaL_error(L, "variable name must be a valid lua identifier: %s", gvar.c_str()); + return LS.result(); + } + gvar += ":once"; + + LS.rawget(globaldb, LuaRegistry, "globaldb"); + LS.rawget(flag, globaldb, gvar); + + if (!LS.isnil(flag)) { + LS.set(result, false); + return LS.result(); + } + + LS.set(result, true); + global_set(LS, gvar, result); + return LS.result(); +} + +LuaDefine(global_clearonce, "varname", + "|Reset the specified once-flag" + "|" + "|The semantics and difference transmission behavior of global.clearonce" + "|are identical to the semantics of global.set, since global.once" + "|uses global.set under the covers." + "|") { + LuaArg varname; + LuaVar null; + LuaStack LS(L, varname, null); + + // Check the varname argument. + eng::string gvar = LS.ckstring(varname); + if (!sv::is_lua_id(gvar)) { + luaL_error(L, "variable name must be a valid lua identifier: %s", gvar.c_str()); + return LS.result(); + } + gvar += ":once"; + + LS.set(null, LuaNil); + global_set(LS, gvar, null); + return LS.result(); +} diff --git a/luprex/cpp/core/world-core.cpp b/luprex/cpp/core/world-core.cpp index 11866fc1..7340a4f2 100644 --- a/luprex/cpp/core/world-core.cpp +++ b/luprex/cpp/core/world-core.cpp @@ -74,6 +74,9 @@ World::World(WorldType wt) { // Clear the clock. clock_ = 0; + // Initialize global variable state. + next_gvar_assign_ = 1; + // There shouldn't be any lua errors in the sourceDB at this // point, since there's no lua code in the sourceDB. assert(srcerrs == ""); diff --git a/luprex/cpp/core/world.hpp b/luprex/cpp/core/world.hpp index bf584fc8..9fb7ba69 100644 --- a/luprex/cpp/core/world.hpp +++ b/luprex/cpp/core/world.hpp @@ -379,7 +379,9 @@ public: // void tangible_set_string(int64_t id, const eng::string &path, const eng::string &value); - // Copy a lua global variable into the tangible's database. + // Copy a from the lua global environment into the tangible's database. + // + // This is for unit testing. // void tangible_copy_global(int64_t id, const eng::string &path, const eng::string &global); @@ -516,6 +518,13 @@ private: SourceDB source_db_; PlaneMap plane_map_; + // Lua Globals + // + int64_t next_gvar_assign_; + eng::map gvar_to_assign_; + eng::map gvar_to_serial_; + eng::map assign_to_gvar_; + // Tangibles table. // eng::unordered_map tangibles_; @@ -551,6 +560,7 @@ private: std::unique_ptr lthread_prints_; friend class Tangible; + friend void global_set(LuaStack &LS0, const eng::string &gvar, LuaSlot value); friend int lfn_tangible_animate(lua_State *L); friend int lfn_tangible_build(lua_State *L); friend int lfn_tangible_redirect(lua_State *L); @@ -566,6 +576,7 @@ private: friend int lfn_wait(lua_State *L); friend int lfn_nopredict(lua_State *L); friend int lfn_http_request(lua_State *L, const char *method); + friend int lfn_global_set(lua_State *L); }; using UniqueWorld = std::unique_ptr; diff --git a/luprex/cpp/drv/driver-common.cpp b/luprex/cpp/drv/driver-common.cpp index 49feff6c..d35515c5 100644 --- a/luprex/cpp/drv/driver-common.cpp +++ b/luprex/cpp/drv/driver-common.cpp @@ -9,8 +9,10 @@ static void if_error_print_and_exit(const std::string_view str) { } } -static void dprintf_callback(const char *oneline) { - fprintf(stderr, "DPRINTF: %s\n", oneline); +static void dprint_callback(const char *oneline, size_t size) { + fwrite("DPRINT:", 1, 7, stderr); + fwrite(oneline, 1, size, stderr); + fwrite("\n", 1, 1, stderr); fflush(stderr); } @@ -546,7 +548,7 @@ class Driver { // Load the DLL and gain access to its functions. call_init_engine_wrapper(&engw); engw.replay_cb_sent_outgoing = replay_cb_sent_outgoing; - engw.hook_dprintf(dprintf_callback); + engw.hook_dprint(dprint_callback); // If argv contains "replay ", do a replay, // and then skip everything else.