Refactor code for invoke_lua_source and world.init. Also, add compile_commands.json to luprex

This commit is contained in:
2025-06-16 19:58:26 -04:00
parent f150b14d30
commit 80ff7d7d92
14 changed files with 279 additions and 331 deletions

View File

@@ -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<std::string> 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<char *> 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);
}

View File

@@ -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);

View File

@@ -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<SharedChannel> 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<SharedChannel> 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);

View File

@@ -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

View File

@@ -22,6 +22,12 @@ public:
eng::vector<Invocation> 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;
}
}
}

View File

@@ -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<Invocation> 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;
}
}
}

View File

@@ -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) {

View File

@@ -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.
//

View File

@@ -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.