From 80ff7d7d927c52ca870dfb1fb753140a7b71e364 Mon Sep 17 00:00:00 2001 From: jyelon Date: Mon, 16 Jun 2025 19:58:26 -0400 Subject: [PATCH] Refactor code for invoke_lua_source and world.init. Also, add compile_commands.json to luprex --- Source/Integration/LuprexGameModeBase.cpp | 42 ++--- build.py | 1 + luprex/Makefile | 196 +++++++++------------- luprex/add-compile-commands.py | 23 +++ luprex/cpp/core/drivenengine.cpp | 49 +----- luprex/cpp/core/drivenengine.hpp | 6 - luprex/cpp/core/eng-tests.cpp | 35 ++-- luprex/cpp/core/enginewrapper.hpp | 13 +- luprex/cpp/core/lpxclient.cpp | 64 ++++--- luprex/cpp/core/lpxserver.cpp | 62 +++---- luprex/cpp/core/world-core.cpp | 74 +++++--- luprex/cpp/core/world.hpp | 24 +-- luprex/cpp/drv/driver.cpp | 8 +- luprex/lua/login.lua | 13 +- 14 files changed, 279 insertions(+), 331 deletions(-) create mode 100644 luprex/add-compile-commands.py diff --git a/Source/Integration/LuprexGameModeBase.cpp b/Source/Integration/LuprexGameModeBase.cpp index f0f45f64..ee558a5b 100644 --- a/Source/Integration/LuprexGameModeBase.cpp +++ b/Source/Integration/LuprexGameModeBase.cpp @@ -54,6 +54,21 @@ ALuprexGameModeBase::~ALuprexGameModeBase() // uint32 ALuprexGameModeBase::Run() { FlxLockedWrapper lockedwrap(LockableWrapper); + if (lockedwrap->get_rescan_lua_source(lockedwrap.Get())) + { + drvutil::ostringstream srcpak; + std::string srcpakerr = drvutil::package_lua_source(LUPREX_ROOT_PATH, &srcpak); + if (!srcpakerr.empty()) + { + FString FMessage((const UTF8CHAR *)(srcpakerr.c_str())); + UE_LOG(LogLuprexIntegration, Error, TEXT("Trying to read Lua source: %s"), *FMessage); + } + else + { + std::string_view srcpakv = srcpak.view(); + lockedwrap->play_access(lockedwrap.Get(), AccessKind::INVOKE_LUA_SOURCE, 0, srcpakv.size(), srcpakv.data(), nullptr, nullptr); + } + } Sockets->Update(lockedwrap); lockedwrap->play_update(lockedwrap.Get(), EngineSeconds); Sockets->Update(lockedwrap); @@ -290,28 +305,15 @@ void ALuprexGameModeBase::InitializeGlobalState() // If wrapper is initialized, try to initialize the luprex engine. if (w->play_initialize != nullptr) { - drvutil::ostringstream srcpak; - std::string srcpakerr = drvutil::package_lua_source(LUPREX_ROOT_PATH, &srcpak); - if (!srcpakerr.empty()) + w->play_initialize(w.Get(), "lpxclient", ""); + if (w->error[0]) { - FString FMessage((const UTF8CHAR *)(srcpakerr.c_str())); - UE_LOG(LogLuprexIntegration, Error, TEXT("Trying to read Lua source: %s"), *FMessage); + FString FMessage((const UTF8CHAR *)w->error); + UE_LOG(LogLuprexIntegration, Error, TEXT("Calling Luprex play_initialize: %s"), *FMessage); } - else - { - std::string_view srcpakv = srcpak.view(); - char* argv[1]; - argv[0] = const_cast("lpxclient"); - w->play_initialize(w.Get(), 1, argv, srcpakv.size(), srcpakv.data(), ""); - if (w->error[0]) - { - FString FMessage((const UTF8CHAR *)w->error); - UE_LOG(LogLuprexIntegration, Error, TEXT("Calling Luprex play_initialize: %s"), *FMessage); - } - if (w->engine != nullptr) { - UE_LOG(LogLuprexIntegration, Verbose, TEXT("Luprex initialization success.")); - Playing = true; - } + if (w->engine != nullptr) { + UE_LOG(LogLuprexIntegration, Verbose, TEXT("Luprex initialization success.")); + Playing = true; } } diff --git a/build.py b/build.py index 7559506b..0307095d 100755 --- a/build.py +++ b/build.py @@ -266,6 +266,7 @@ def build_intellisense_database_for_clangd(force): shell(INTEGRATION, f"{UNREALENGINE}/Engine/Build/BatchFiles/{BUILD_BAT} -waitmutex IntegrationEditor {OS} DebugGame {INTEGRATION}/Integration.uproject -mode=GenerateClangDatabase -OutputDir={UNREALENGINE}/.vscode") shell(UNREALENGINE, f"{UNREALENGINE}/Engine/Build/BatchFiles/{BUILD_BAT} -waitmutex UnrealEditor {OS} DebugGame -mode=GenerateClangDatabase -OutputDir={UNREALENGINE}/.vscode") shell(INTEGRATION, f"cat {UNREALENGINE}/.vscode/compile_commands.json >> {INTEGRATION}/.vscode/compile_commands.json") + shell(INTEGRATION, f"cat {INTEGRATION}/luprex/build/{OS}/compile_commands.json >> {INTEGRATION}/.vscode/compile_commands.json") except Exception as e: error = e else: diff --git a/luprex/Makefile b/luprex/Makefile index d2906f6c..95bb03d2 100644 --- a/luprex/Makefile +++ b/luprex/Makefile @@ -5,11 +5,11 @@ ####################################################################### ifneq "" "$(findstring -linux-,$(MAKE_HOST))" - OS=Linux + OS:=Linux else ifneq "" "$(VSINSTALLDIR)" - OS=Windows + OS:=Windows else - OS="" + OS:="" endif ifeq "$(OS)" "" @@ -18,91 +18,6 @@ endif $(info Building for $(OS)...) -####################################################################### -## -## List of all OBJ files -## -####################################################################### - -OBJ_ERIS=\ - build/$(OS)/eris/lapi.obj \ - build/$(OS)/eris/lcode.obj \ - build/$(OS)/eris/lctype.obj \ - build/$(OS)/eris/ldebug.obj \ - build/$(OS)/eris/ldo.obj \ - build/$(OS)/eris/ldump.obj \ - build/$(OS)/eris/lfunc.obj \ - build/$(OS)/eris/lgc.obj \ - build/$(OS)/eris/llex.obj \ - build/$(OS)/eris/lmem.obj \ - build/$(OS)/eris/lobject.obj \ - build/$(OS)/eris/lopcodes.obj \ - build/$(OS)/eris/lparser.obj \ - build/$(OS)/eris/lstate.obj \ - build/$(OS)/eris/lstring.obj \ - build/$(OS)/eris/ltable.obj \ - build/$(OS)/eris/ltm.obj \ - build/$(OS)/eris/lundump.obj \ - build/$(OS)/eris/lvm.obj \ - build/$(OS)/eris/lzio.obj \ - build/$(OS)/eris/lauxlib.obj \ - build/$(OS)/eris/lbaselib.obj \ - build/$(OS)/eris/lbitlib.obj \ - build/$(OS)/eris/lcorolib.obj \ - build/$(OS)/eris/ldblib.obj \ - build/$(OS)/eris/liolib.obj \ - build/$(OS)/eris/lmathlib.obj \ - build/$(OS)/eris/loslib.obj \ - build/$(OS)/eris/lstrlib.obj \ - build/$(OS)/eris/ltablib.obj \ - build/$(OS)/eris/loadlib.obj \ - build/$(OS)/eris/linit.obj \ - build/$(OS)/eris/eris.obj \ - -OBJ_CORE=\ - build/$(OS)/core/invocation.obj\ - build/$(OS)/core/spookyv2.obj\ - build/$(OS)/core/eng-malloc.obj\ - build/$(OS)/core/debugcollector.obj\ - build/$(OS)/core/drivenengine.obj\ - build/$(OS)/core/util.obj\ - build/$(OS)/core/luastack.obj\ - build/$(OS)/core/traceback.obj\ - build/$(OS)/core/planemap.obj\ - build/$(OS)/core/pprint.obj\ - build/$(OS)/core/luaconsole.obj\ - build/$(OS)/core/luavector.obj\ - build/$(OS)/core/idalloc.obj\ - build/$(OS)/core/globaldb.obj\ - build/$(OS)/core/sched.obj\ - build/$(OS)/core/http.obj\ - build/$(OS)/core/json.obj\ - build/$(OS)/core/table.obj\ - build/$(OS)/core/luasnap.obj\ - build/$(OS)/core/animqueue.obj\ - build/$(OS)/core/streambuffer.obj\ - build/$(OS)/core/source.obj\ - build/$(OS)/core/world-core.obj\ - build/$(OS)/core/world-accessor.obj\ - build/$(OS)/core/world-difftab.obj\ - build/$(OS)/core/world-diffxmit.obj\ - build/$(OS)/core/world-pairtab.obj\ - build/$(OS)/core/world-testing.obj\ - build/$(OS)/core/lpxserver.obj\ - build/$(OS)/core/lpxclient.obj\ - build/$(OS)/core/eng-tests.obj\ - build/$(OS)/core/printbuffer.obj\ - build/$(OS)/core/serializelua.obj\ - - -OBJ_DRV=\ - build/$(OS)/drv/driver.obj\ - build/$(OS)/drv/drvutil.obj\ - build/$(OS)/drv/osdrvutil.obj\ - build/$(OS)/drv/sslutil.obj\ - build/$(OS)/drv/readline.obj\ - - ####################################################################### ## ## Make rules for linux @@ -110,17 +25,20 @@ OBJ_DRV=\ ####################################################################### ifeq "$(OS)" "Linux" - OPT=-g -O0 - LUPREX_EXE=luprex - LUPREXLIB_DLL=luprexlib.so - LUPREXSTATIC_EXE=luprexstatic - COMPILE=g++ -Wall $(OPT) -std=c++20 -fvisibility=hidden -c -MMD -fPIC -o - LINKDLL=g++ -Wall $(OPT) -std=c++20 -export-dynamic -Wl,--no-allow-shlib-undefined -Wl,-z,defs -shared -o - LINKEXE=g++ -Wall $(OPT) -std=c++20 -export-dynamic -o - MAKEDEPS=true - OPENSSL_INCLUDE=-I./ext/openssl-3.0.1/inc - LUA_FLAGS=-DLUA_USE_APICHECK -DLUA_USE_POSIX - LIBS=-L./ext/openssl-3.0.1/lib/linux -lssl -lcrypto -ldl + OPT:=-g -O0 + LUPREX_EXE:=luprex + LUPREXLIB_DLL:=luprexlib.so + LUPREXSTATIC_EXE:=luprexstatic + COMPILE:=g++ -Wall $(OPT) -std=c++20 -fvisibility=hidden -c -MMD -fPIC -o + LINKDLL:=g++ -Wall $(OPT) -std=c++20 -export-dynamic -Wl,--no-allow-shlib-undefined -Wl,-z,defs -shared -o + LINKEXE:=g++ -Wall $(OPT) -std=c++20 -export-dynamic -o + MAKEDEPS:=true + LIBS:=-L./ext/openssl-3.0.1/lib/linux -lssl -lcrypto -ldl + CCJSON="build/$(OS)/compile_commands.json" + + FLAGS_ERIS:=-DLUA_USE_APICHECK -DLUA_USE_POSIX + FLAGS_CORE:=-I./ext/eris-master/src -I./cpp/wrap -I./cpp/core -I./ext + FLAGS_DRV:=-I./ext/openssl-3.1.0/inc -I./src/drv -I./ext endif ####################################################################### @@ -133,26 +51,60 @@ ifeq "$(OS)" "Windows" ifeq "" "$(VSINSTALLDIR)" $(error You must use vcvars64.bat to set up the visual studio environment variables) endif - OPT=/Od /Zi - LUPREX_EXE=luprex.exe - LUPREXLIB_DLL=luprexlib.dll - LUPREXSTATIC_EXE=luprexstatic.exe - COMPILE=CL $(OPT) /std:c++20 /EHsc /nologo /MD /TP /c /Fo: - LINKDLL=CL $(OPT) /std:c++20 /EHsc /nologo /LDd /Fe: - LINKEXE=CL $(OPT) /std:c++20 /EHsc /nologo /Fe: - MAKEDEPS=g++ -Wall -std=c++20 -MMD -E -o - OPENSSL_INCLUDE=-I./ext/openssl-3.1.0/inc - LUA_FLAGS=-DLUA_USE_APICHECK -DLUA_COMPAT_ALL - LIBS=ext/openssl-3.1.0/lib/visual/libcrypto.lib ext/openssl-3.1.0/lib/visual/libssl.lib ws2_32.lib crypt32.lib cryptui.lib user32.lib advapi32.lib + OPT:=/Od /Zi + LUPREX_EXE:=luprex.exe + LUPREXLIB_DLL:=luprexlib.dll + LUPREXSTATIC_EXE:=luprexstatic.exe + COMPILE:=CL $(OPT) /std:c++20 /EHsc /nologo /MD /TP /c /Fo: + LINKDLL:=CL $(OPT) /std:c++20 /EHsc /nologo /LDd /Fe: + LINKEXE:=CL $(OPT) /std:c++20 /EHsc /nologo /Fe: + MAKEDEPS:=g++ -Wall -std=c++20 -MMD -E -o + LIBS:=ext/openssl-3.1.0/lib/visual/libcrypto.lib ext/openssl-3.1.0/lib/visual/libssl.lib ws2_32.lib crypt32.lib cryptui.lib user32.lib advapi32.lib + CCJSON="build/$(OS)/compile_commands.json" + + FLAGS_ERIS:=-DLUA_USE_APICHECK -DLUA_COMPAT_ALL + FLAGS_CORE:=-I./ext/eris-master/src -I./cpp/wrap -I./cpp/core -I./ext + FLAGS_DRV:=-I./ext/openssl-3.1.0/inc -I./src/drv -I./ext endif +####################################################################### +## +## List of all source files +## +####################################################################### + +BASE_ERIS := \ + lapi lcode lctype ldebug ldo ldump lfunc lgc llex lmem lobject lopcodes \ + lparser lstate lstring ltable ltm lundump lvm lzio \ + lauxlib lbaselib lbitlib lcorolib ldblib liolib lmathlib loslib \ + lstrlib ltablib loadlib linit eris + +BASE_CORE := \ + invocation spookyv2 eng-malloc debugcollector drivenengine util luastack \ + traceback planemap pprint luaconsole luavector idalloc globaldb sched http \ + json table luasnap animqueue streambuffer source world-core world-accessor \ + world-difftab world-diffxmit world-pairtab world-testing lpxserver lpxclient \ + eng-tests printbuffer serializelua + +BASE_DRV := driver drvutil osdrvutil sslutil readline + +####################################################################### +## +## Generate lists of OBJ files. +## +####################################################################### + +OBJ_ERIS := $(patsubst %,build/$(OS)/eris/%.obj,$(BASE_ERIS)) +OBJ_CORE := $(patsubst %,build/$(OS)/core/%.obj,$(BASE_CORE)) +OBJ_DRV := $(patsubst %,build/$(OS)/drv/%.obj,$(BASE_DRV)) + ####################################################################### ## ## Make Rules ## ####################################################################### -all: build/$(OS)/$(LUPREX_EXE) build/$(OS)/$(LUPREXSTATIC_EXE) build/$(OS)/$(LUPREXLIB_DLL) +all: build/$(OS)/$(LUPREX_EXE) build/$(OS)/$(LUPREXSTATIC_EXE) build/$(OS)/$(LUPREXLIB_DLL) $(CCJSON) build/$(OS)/DIRECTORY: mkdir -p build/$(OS)/core build/$(OS)/eris build/$(OS)/drv @@ -168,23 +120,29 @@ build/$(OS)/$(LUPREXLIB_DLL): $(OBJ_ERIS) $(OBJ_CORE) $(LINKDLL) $@ $^ build/$(OS)/eris/%.obj: ext/eris-master/src/%.c build/$(OS)/DIRECTORY - $(MAKEDEPS) $@d $(LUA_FLAGS) $< - $(COMPILE) $@ $(LUA_FLAGS) $< + $(MAKEDEPS) $@d $(FLAGS_ERIS) $< + $(COMPILE) $@ $(FLAGS_ERIS) $< build/$(OS)/core/%.obj: cpp/core/%.cpp build/$(OS)/DIRECTORY - $(MAKEDEPS) $@d -I./ext/eris-master/src -I./cpp/wrap -I./cpp/core -I./ext $< - $(COMPILE) $@ -I./ext/eris-master/src -I./cpp/wrap -I./cpp/core -I./ext $< + $(MAKEDEPS) $@d $(FLAGS_CORE) $< + $(COMPILE) $@ $(FLAGS_CORE) $< build/$(OS)/drv/%.obj: cpp/drv/%.cpp build/$(OS)/DIRECTORY - $(MAKEDEPS) $@d $(OPENSSL_INCLUDE) -I./src/drv -I./ext $< - $(COMPILE) $@ $(OPENSSL_INCLUDE) -I./src/drv -I./ext $< + $(MAKEDEPS) $@d $(FLAGS_DRV) $< + $(COMPILE) $@ $(FLAGS_DRV) $< + +$(CCJSON): Makefile add-compile-commands.py + rm -rf $(CCJSON) + python3 ./add-compile-commands.py "$(CCJSON)" "$(COMPILE)" "$(FLAGS_ERIS)" "build/$(OS)/eris/FILE.obj" "ext/eris-master/src/FILE.c" $(BASE_ERIS) + python3 ./add-compile-commands.py "$(CCJSON)" "$(COMPILE)" "$(FLAGS_CORE)" "build/$(OS)/cpp/FILE.obj" "cpp/core/FILE.cpp" $(BASE_CORE) + python3 ./add-compile-commands.py "$(CCJSON)" "$(COMPILE)" "$(FLAGS_DRV)" "build/$(OS)/drv/FILE.obj" "cpp/drv/FILE.cpp" $(BASE_DRV) clean: - rm -f luprex* luprex*.* *.pdb + rm -f *.pdb rm -rf build clean-os: - rm -f luprex* luprex*.* *.pdb build/$(OS)/* build/$(OS)/*/* + rm -f *.pdb build/$(OS)/* build/$(OS)/*/* ####################################################################### ## diff --git a/luprex/add-compile-commands.py b/luprex/add-compile-commands.py new file mode 100644 index 00000000..1eca4507 --- /dev/null +++ b/luprex/add-compile-commands.py @@ -0,0 +1,23 @@ + + +import sys, os + +LUPREXDIR=os.path.dirname(os.path.abspath(sys.argv[0])) + +JSON=sys.argv[1] +COMPILE=sys.argv[2] +FLAGS=sys.argv[3] +OBJPAT=sys.argv[4] +CPAT=sys.argv[5] +OBJECTS=sys.argv[6:] + +with open(JSON, "a") as cc: + for base in OBJECTS: + obj = LUPREXDIR + "/" + OBJPAT.replace("FILE", base) + file = LUPREXDIR + "/" + CPAT.replace("FILE", base) + flags = FLAGS.replace("-I./", f"-I{LUPREXDIR}/") + cc.write('{\n') + cc.write(f' "file" : "{file}",\n') + cc.write(f' "command" : "{COMPILE} {obj} {flags} {file}",\n') + cc.write(f' "directory" : "{LUPREXDIR}"\n') + cc.write('}\n') diff --git a/luprex/cpp/core/drivenengine.cpp b/luprex/cpp/core/drivenengine.cpp index 471b8216..525df564 100644 --- a/luprex/cpp/core/drivenengine.cpp +++ b/luprex/cpp/core/drivenengine.cpp @@ -436,9 +436,6 @@ void DrivenEngine::drv_get_animation_queues(uint32_t count, const int64_t *ids, ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// -void DrivenEngine::drv_initialize(uint32_t srcpklen, const char *srcpk, int argc, char **argv) { - event_init(std::string_view(srcpk, srcpklen), argc, argv); -} void DrivenEngine::drv_clear_new_outgoing() { new_outgoing_.clear(); @@ -569,7 +566,7 @@ static void drv_get_animation_queues(EngineWrapper *w, uint32_t count, const int ////////////////////////////////////////////////////////////////////////////// -static void play_initialize(EngineWrapper *w, uint32_t argc, char **argv, uint32_t srcpklen, const char *srcpk, const char *logfn) { +static void play_initialize(EngineWrapper *w, const char *engtype, const char *logfn) { if (w->engine != nullptr) { return reset_wrapper(w, "Cannot initialize wrapper, it's already initialized."); } @@ -590,63 +587,31 @@ static void play_initialize(EngineWrapper *w, uint32_t argc, char **argv, uint32 // If we have a logfile, then log this initialization. if (w->wlog != nullptr) { w->wlog->write_cmd_hash(PLAY_INITIALIZE, eng::memhash()); - w->wlog->write_uint32(argc); - for (uint32_t i = 0; i < argc; i++) { - w->wlog->write_string(argv[i]); - } - w->wlog->write_string(std::string_view(srcpk, srcpklen)); + w->wlog->write_string(engtype); w->wlog->flush(); } // Create the engine of the appropriate type. - if (argc < 1) { - std::ostringstream oss; - oss << "Must pass an engine type on the command line. Known types:\n"; - for (auto reg = DrivenEngineReg::All; reg != nullptr; reg=reg->next) { - oss << " " << reg->name << std::endl; - } - std::string err = oss.str(); - return reset_wrapper(w, err.c_str()); - } - w->engine = make_engine(argv[0]); + w->engine = make_engine(engtype); if (w->engine == nullptr) { - return reset_wrapper(w, "No such driven engine type: %s", argv[0]); + return reset_wrapper(w, "No such driven engine type: %s", engtype); } - - // Call the engine initialization sequence. - w->engine->drv_initialize(srcpklen, srcpk, argc - 1, argv + 1); } static void replay_initialize(EngineWrapper *w) { assert(w->rlog != nullptr); - std::vector argvstr; - uint32_t argc = w->rlog->read_uint32(); - for (uint32_t i = 0; i < argc; i++) { - argvstr.push_back(w->rlog->read_string()); - } - std::string srcpk = w->rlog->read_string(); + std::string engtype = w->rlog->read_string(); if (!w->rlog->good()) { return reset_wrapper(w, "replay log corrupt in replay_initialize"); } - // We need to convert the argument vector from an array - // of C++ strings into the canonical argc, argv format. - std::vector argvec; - for (uint32_t i = 0; i < argc; i++) { - argvec.push_back(&argvstr[i][0]); - } - char **argv = &argvec[0]; - // Create the engine. - w->engine = make_engine(argv[0]); + w->engine = make_engine(engtype.c_str()); if (w->engine == nullptr) { - return reset_wrapper(w, "No such driven engine type: %s", argvstr[0]); + return reset_wrapper(w, "No such driven engine type: %s", engtype.c_str()); } - - - w->engine->drv_initialize(srcpk.size(), srcpk.c_str(), argc - 1, argv + 1); } diff --git a/luprex/cpp/core/drivenengine.hpp b/luprex/cpp/core/drivenengine.hpp index 389534f0..12e2c3e5 100644 --- a/luprex/cpp/core/drivenengine.hpp +++ b/luprex/cpp/core/drivenengine.hpp @@ -142,11 +142,6 @@ public: // ////////////////////////////////////////////////////////////// - // The init callback. You may override this in a subclass. - // This will be called once at program initialization. - // - virtual void event_init(std::string_view srcpk, int argc, char *argv[]) = 0; - // The call-function callback. This is invoked whenever drv_access // is called. This is the main entry point for "general" access into the // DrivenEngine. The datapk parameter can contain any arbitrary data needed @@ -289,7 +284,6 @@ public: void drv_get_tangibles_near(int64_t tanid, double rx, double ry, double rz, uint32_t *count, int64_t **ids); void drv_get_animation_queues(uint32_t count, const int64_t *ids, uint32_t *lengths, const char **strings); - void drv_initialize(uint32_t srcpklen, const char *srcpk, int argc, char **argv); void drv_clear_new_outgoing(); void drv_sent_outgoing(uint32_t chid, uint32_t nbytes); void drv_recv_incoming(uint32_t chid, uint32_t nbytes, const char *bytes); diff --git a/luprex/cpp/core/eng-tests.cpp b/luprex/cpp/core/eng-tests.cpp index e99b3c6f..b26b26b5 100644 --- a/luprex/cpp/core/eng-tests.cpp +++ b/luprex/cpp/core/eng-tests.cpp @@ -22,21 +22,12 @@ static void dump_lines(StreamBuffer *in, StreamBuffer *out, int chid) { } } -// This test is the minimal possible DrivenEngine. -class DriverStubTest : public DrivenEngine { - virtual void event_init(std::string_view srcpk, int argc, char *argv[]) override { - stop_driver(); - } - virtual void event_update() override { - } -}; - // This test connects to a public webserver and prints // the output from the server. class DriverWebServerTest : public DrivenEngine { public: eng::vector channels_; - virtual void event_init(std::string_view srcpk, int argc, char *argv[]) override { + DriverWebServerTest() { SharedChannel ch = new_outgoing_channel("cert:stanford.edu:443"); ch->out()->write_bytes("GET https://stanford.edu/xbanankjdsh.html HTTP/1.1\n\n"); channels_.emplace_back(std::move(ch)); @@ -62,7 +53,7 @@ public: class DriverDNSFailTest : public DrivenEngine { public: eng::vector channels_; - virtual void event_init(std::string_view srcpk, int argc, char *argv[]) override { + DriverDNSFailTest() { SharedChannel ch = new_outgoing_channel("akjsdkajshdakjshd.alk:80"); ch->out()->write_bytes("GET http://stanford.edu/index.html HTTP/1.1\n\n"); channels_.emplace_back(std::move(ch)); @@ -89,7 +80,7 @@ class DriverPrintClockTest : public DrivenEngine { public: int count_; double last_clock_; - virtual void event_init(std::string_view srcpk, int argc, char *argv[]) override { + DriverPrintClockTest() { count_ = 0; last_clock_ = 0.0; } @@ -111,21 +102,29 @@ public: class RunUnitTests : public DrivenEngine { -private: +public: UniqueWorld world_; - void event_init(std::string_view srcpk, int argc, char *argv[]) override - { + RunUnitTests() { world_.reset(new World(WORLD_TYPE_MASTER)); - world_->update_source(srcpk); - world_->run_unittests(); stop_driver(); + rescan_lua_source(); + } + + 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); + world_->run_unittests(); + break; + } + default: break; + } } void event_update() override {} }; -DrivenEngineDefine("driverstubtest", DriverStubTest); DrivenEngineDefine("driverwebservertest", DriverWebServerTest); DrivenEngineDefine("driverdnsfailtest", DriverDNSFailTest); DrivenEngineDefine("driverprintclocktest", DriverPrintClockTest); diff --git a/luprex/cpp/core/enginewrapper.hpp b/luprex/cpp/core/enginewrapper.hpp index 4bcde2ff..09d288f9 100644 --- a/luprex/cpp/core/enginewrapper.hpp +++ b/luprex/cpp/core/enginewrapper.hpp @@ -28,6 +28,7 @@ enum class AccessKind { INVOKE_TICK, INVOKE_LUA_SOURCE, PROBE_LUA_CALL, + CONNECT_TO_SERVER, }; class DrivenEngine; @@ -154,15 +155,11 @@ struct EngineWrapper { ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// - // Create the driven engine. argc and argv allow you to specify what - // kind of engine you want. You must pass in the initial state of the lua - // source, if you have any. You may optionally also specify a replay log. - // If you don't want to create a replay log, pass a null pointer. + // Create the driven engine. You must specify the type of engine you + // want (typically 'lpxserver' or 'lpxclient'), and a filename for the + // logfile. // - // Check to see if the error buffer contains a message after calling - // this function. - // - void (*play_initialize)(EngineWrapper *w, uint32_t argc, char **argv, uint32_t srcpklen, const char *srcpk, const char *logfn); + void (*play_initialize)(EngineWrapper *w, const char *engtype, const char *logfn); // Clear the list of recently-opened channels. You are meant to fetch // new outgoing channels using get_new_outgoing, then you call diff --git a/luprex/cpp/core/lpxclient.cpp b/luprex/cpp/core/lpxclient.cpp index 16b095f6..d2a796a3 100644 --- a/luprex/cpp/core/lpxclient.cpp +++ b/luprex/cpp/core/lpxclient.cpp @@ -22,6 +22,12 @@ public: eng::vector delayed_invocations_; public: + LpxClient() { + set_console_prompt(console_.get_prompt()); + + set_initial_state_standalone(); + } + void set_initial_state_connect(const eng::string &hostspec) { // Create the world model. world_.reset(new World(WORLD_TYPE_PREDICTIVE)); @@ -46,21 +52,16 @@ public: print_channeler_.reset(); } - void set_initial_state_standalone(std::string_view srcpk) { + void set_initial_state_standalone() { // Create the world model. world_.reset(new World(WORLD_TYPE_MASTER)); - // Update the source code of the master model. - world_->update_source(srcpk); - // Make sure the channel is empty. channel_.reset(); // Create the standalone actor. actor_id_ = world_->create_login_actor(); - // TODO: initialize the standalone actor. - // Clear the unack command queue. unack_.clear(); @@ -69,8 +70,10 @@ public: // Reset the print channeler print_channeler_.reset(); - } + // Trigger lua source loading. + rescan_lua_source(); + } // When the world is in synchronous mode, there's no // snapshot, and the commands in the unack queue are not @@ -96,37 +99,15 @@ public: } void abandon_server() { - // When we abandon the server, we leave the world model - // hanging around to preserve the invariant that there - // is always a world model. Then, we trigger a rescan - // of the lua source. When the lua source shows up, then - // we will create a standalone model to replace the client - // model. - - channel_.reset(); - rescan_lua_source(); - } - - virtual void event_init(std::string_view srcpk, int argc, char *argv[]) { - // Put the world into the starting state. - set_initial_state_standalone(srcpk); - - // Set the console prompt - set_console_prompt(console_.get_prompt()); + if (channel_) + { + set_initial_state_standalone(); + } } void send_invocation(const Invocation &inv) { if (channel_ == nullptr) { - if ((!world_->is_authoritative()) && (inv.kind() == AccessKind::INVOKE_LUA_SOURCE)) { - // We have a client model, but no client connection. That means we're - // in the process of shutting down a client model. The client model - // is supposed to linger until the lua source is reread. Once we have - // the lua source, we're supposed to throw out the client model and - // create a standalone model. - set_initial_state_standalone(inv.datapack()); - } else { - world_->invoke(inv); - } + world_->invoke(inv); } else { world_to_asynchronous(); world_->invoke(inv); @@ -247,14 +228,27 @@ public: } virtual void event_access(AccessKind kind, int64_t place_id, std::string_view datapk, StreamBuffer *retpk) override { + if (place_id == 0) place_id = actor_id_; switch (kind) { + case AccessKind::INVOKE_LUA_CALL: + case AccessKind::INVOKE_LUA_EXPR: + case AccessKind::INVOKE_FLUSH_PRINTS: + case AccessKind::INVOKE_TICK: + case AccessKind::INVOKE_LUA_SOURCE: { + delayed_invocations_.emplace_back(kind, actor_id_, place_id, datapk); + break; + } case AccessKind::PROBE_LUA_CALL: { world_to_asynchronous(); world_->probe_lua_call(actor_id_, place_id, datapk, retpk); break; } + case AccessKind::CONNECT_TO_SERVER: { + set_initial_state_connect(util::ss("nocert:", datapk, ":8085")); + break; + } default: { - delayed_invocations_.emplace_back(kind, actor_id_, place_id, datapk); + stdostream() << "Invalid event_access: " << int(kind) << std::endl; } } } diff --git a/luprex/cpp/core/lpxserver.cpp b/luprex/cpp/core/lpxserver.cpp index 1a7252ed..de834774 100644 --- a/luprex/cpp/core/lpxserver.cpp +++ b/luprex/cpp/core/lpxserver.cpp @@ -29,24 +29,19 @@ public: PrintChanneler print_channeler_; HttpChannelMap http_client_channels_; HttpChannelVec http_server_channels_; - int64_t admin_id_; - int next_diff_chan_; - double next_tick_; eng::vector delayed_invocations_; + int64_t admin_id_ = 0; + double next_tick_ = 0; public: - virtual void event_init(std::string_view srcpk, int argc, char *argv[]) override { + LpxServer() + { // Create the master world model. master_.reset(new World(WORLD_TYPE_MASTER)); - // Update the source code of the master model. - master_->update_source(srcpk); - - // Create an actor for administrative commands. + // Create the admin actor. Note: there isn't any 'init' function yet. admin_id_ = master_->create_login_actor(); - // TODO: initialize the admin actor. - // Print out admin ID for debugging purposes. stdostream() << "Admin actor id = " << admin_id_ << std::endl; @@ -61,9 +56,9 @@ public: // Export stuff to the graphics engine. set_visible_world_and_actor(master_.get(), admin_id_); - - // for ticking. - next_tick_ = 0.0; + + // Trigger the loading of the lua source. + rescan_lua_source(); } virtual void do_syntax_error(std::string_view error) override { @@ -91,35 +86,7 @@ public: } virtual void do_work_command() override { - master_->snapshot(); - StreamBuffer datapack; - StreamBuffer retvals; - datapack.write_string("engio"); - datapack.write_string("myfunction"); - datapack.write_simple_dynamic_tag(SimpleDynamicTag::NUMBER); - datapack.write_double(3.7); - datapack.write_simple_dynamic_tag(SimpleDynamicTag::STRING); - datapack.write_string("banana"); - datapack.write_simple_dynamic_tag(SimpleDynamicTag::BOOLEAN); - datapack.write_bool(true); - master_->probe_lua_call(admin_id_, admin_id_, datapack.view(), &retvals); - while (!retvals.empty()) { - SimpleDynamicValue value; - retvals.read_simple_dynamic(&value); - if (value.type == SimpleDynamicTag::NUMBER) { - util::dprint("retval: ", value.x); - } else if (value.type == SimpleDynamicTag::STRING) { - util::dprint("retval: ", value.s); - } else if (value.type == SimpleDynamicTag::BOOLEAN) { - util::dprint(value.x ? "retval: true" : "retval: false"); - } else if (value.type == SimpleDynamicTag::VECTOR) { - util::dprint("retval: ", value.x, " ", value.y, " ", value.z); - } else { - util::dprint("retval: invalid"); - break; - } - } - master_->rollback(); + do_unknown_command("work"); } virtual void do_display_command() override { @@ -194,7 +161,16 @@ public: } virtual void event_access(AccessKind kind, int64_t place_id, std::string_view datapk, StreamBuffer *retpk) override { + if (place_id == 0) place_id = admin_id_; switch (kind) { + case AccessKind::INVOKE_LUA_CALL: + case AccessKind::INVOKE_LUA_EXPR: + case AccessKind::INVOKE_FLUSH_PRINTS: + case AccessKind::INVOKE_TICK: + case AccessKind::INVOKE_LUA_SOURCE: { + delayed_invocations_.emplace_back(kind, admin_id_, place_id, datapk); + break; + } case AccessKind::PROBE_LUA_CALL: { master_->snapshot(); master_->probe_lua_call(admin_id_, place_id, datapk, retpk); @@ -202,7 +178,7 @@ public: break; } default: { - delayed_invocations_.emplace_back(kind, admin_id_, place_id, datapk); + stdostream() << "Invalid event_access: " << int(kind) << std::endl; } } } diff --git a/luprex/cpp/core/world-core.cpp b/luprex/cpp/core/world-core.cpp index c7fb7d7b..f322cdcd 100644 --- a/luprex/cpp/core/world-core.cpp +++ b/luprex/cpp/core/world-core.cpp @@ -546,44 +546,49 @@ void World::probe_lua_call(int64_t actor_id, int64_t place_id, std::string_view // some lua source file tries to modify, say, tangible state // in top-level code. // -void World::rebuild_sourcedb() { +bool World::rebuild_sourcedb() { + bool ok = true; for (const eng::string &mod: source_db_.modules()) { open_lthread_state(0, 0, 0, false, true); eng::string err = source_db_.rebuild_module(mod); eng::string prints = lthread_prints_->str(); lthread_prints_.reset(); close_lthread_state(); + if (!err.empty()) ok = false; if (!err.empty() || !prints.empty()) { util::dprint("Loading Module ", mod); if (!err.empty()) util::dprint(err); if (!prints.empty()) util::dprint(prints); } } + return ok; } -void World::update_source(const util::LuaSourceVec &source) { +bool World::update_source(const util::LuaSourceVec &source) { assert(stack_is_clear()); source_db_.update(source); - rebuild_sourcedb(); + return rebuild_sourcedb(); assert(stack_is_clear()); } -void World::update_source(const util::LuaSourcePtr &source) { - if (source != nullptr) { - update_source(*source); +bool World::update_source(const util::LuaSourcePtr &source) { + if (source == nullptr) { + return false; } + return update_source(*source); } -void World::update_source(std::string_view sourcepack) { - if (!sourcepack.empty()) { - try { - StreamBuffer sb(sourcepack); - util::LuaSourceVec sv; - SourceDB::deserialize_source(&sv, &sb); - update_source(sv); - } catch (const StreamException &ex) { - return; - } +bool World::update_source(std::string_view sourcepack) { + if (sourcepack.empty()) { + return false; + } + try { + StreamBuffer sb(sourcepack); + util::LuaSourceVec sv; + SourceDB::deserialize_source(&sv, &sb); + return update_source(sv); + } catch (const StreamException &ex) { + return false; } } @@ -960,11 +965,40 @@ void World::invoke_tick(int64_t actor_id, int64_t place_id, std::string_view dat } void World::invoke_lua_source(int64_t actor_id, int64_t place_id, std::string_view datapack) { - if (!is_authoritative()) { - return; + // Sanity check arguments. + // We also need some kind of authentication here. + if (!is_authoritative()) return; + 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); + + // Compile and load the source. + bool success = update_source(datapack); + + // Call world.init + if (brand_new) { + if (success) { + { + lua_State *L = state(); + LuaVar lclass, lfunc; + LuaExtStack LS(L, lclass, lfunc); + + LS.getclass(lclass, "world"); + if (LS.classname(lclass) != "") { + LS.rawget(lfunc, lclass, "init"); + spawn(LS, actor_id, place_id, lfunc, 0, false); + } + } + run_scheduled_threads(); + } else { + util::dprint("Did not run world.init because of lua errors."); + util::dprint("You will need to fix the errors then run it manually."); + } } - // We need some kind of authentication here. - update_source(datapack); + + // Run the new thread and return. + assert(stack_is_clear()); } void World::guard_blockable(lua_State *L, const char *fn) { diff --git a/luprex/cpp/core/world.hpp b/luprex/cpp/core/world.hpp index 1de42167..12826f6b 100644 --- a/luprex/cpp/core/world.hpp +++ b/luprex/cpp/core/world.hpp @@ -216,11 +216,11 @@ public: // This is used to create a temporary actor which is used during // the login process. // - // If this is a master model, The function 'login.initialize' + // If this is a master model, The function 'login.init' // called. Then, the following login flags are set: // can_be_controlled, is_controlled, and delete_on_disconnect. // - // In a client model, 'login.initialize' is not called, + // In a client model, 'login.init' is not called, // and the login flags are not used in client models. // int64_t create_login_actor(); @@ -265,19 +265,21 @@ public: // const PrintBuffer *get_printbuffer(int64_t actor_id); - // Update the source database from disk. + // Rebuild the global environment from the source database. + // + // Returns true if the rebuild goes without errors. + // + bool rebuild_sourcedb(); + + // Update the source database from disk, then rebuild the global environment. // // Special case: if the source pointer is nullptr, does not update. - // The final form takes a sourcepk, a serialized representation - // of a LuaSourceVec. // - void update_source(const util::LuaSourceVec &source); - void update_source(const util::LuaSourcePtr &source); - void update_source(std::string_view sourcepk); - - // Rebuild the source database. + // Returns true if the update goes without errors. // - void rebuild_sourcedb(); + bool update_source(const util::LuaSourceVec &source); + bool update_source(const util::LuaSourcePtr &source); + bool update_source(std::string_view sourcepk); // Supply an HTTP response to an outstanding HTTP request. // diff --git a/luprex/cpp/drv/driver.cpp b/luprex/cpp/drv/driver.cpp index b846f6a9..752d5e0a 100644 --- a/luprex/cpp/drv/driver.cpp +++ b/luprex/cpp/drv/driver.cpp @@ -646,14 +646,8 @@ class Driver { ssl_load_certificate_authorities(ssl_client_secure_ctx_.get()); sslutil::ctx_load_dummy_cert(ssl_server_ctx_.get()); - // Read the initial lua source code. - drvutil::ostringstream srcpak; - std::string srcpakerr = drvutil::package_lua_source(luprexroot, &srcpak); - if_error_print_and_exit(srcpakerr); - // Initialize the engine. - std::string_view srcpakv = srcpak.view(); - engw.play_initialize(&engw, argc, argv, srcpakv.size(), srcpakv.data(), replaylogfn.c_str()); + engw.play_initialize(&engw, argv[0], replaylogfn.c_str()); if_error_print_and_exit(engw.error); // Set up listening ports. diff --git a/luprex/lua/login.lua b/luprex/lua/login.lua index 23df8ad6..1f51d0c8 100644 --- a/luprex/lua/login.lua +++ b/luprex/lua/login.lua @@ -1,14 +1,23 @@ +makeclass("world") makeclass('login') makeclass("engio") makeclass('cube') makeclass('sphere') +-- This gets called on every login except the admin user. function login.init() local actor = tangible.actor() dprint("login.init:", actor) tangible.animinit(actor, {bp="character", plane="earth", xyz={0, 0, 90}}) - tangible.build{class=cube, xyz={500,-100,0}} - tangible.build{class=sphere, xyz={500,100,0}} +end + +-- This gets called on the admin user. You can call login.init in here if you want. +function world.init() + local actor = tangible.actor() + dprint("world.init:", actor) + tangible.build{class=cube, plane="earth", xyz={500,-100,0}} + tangible.build{class=sphere, plane="earth", xyz={500,100,0}} + login.init() end function engio.move(action, xyz, facing)