diff --git a/Docs/TODO-List.md b/Docs/TODO-List.md index 12aa6815..5aa72b1e 100644 --- a/Docs/TODO-List.md +++ b/Docs/TODO-List.md @@ -8,11 +8,16 @@ * Object-Oriented Lua Support * Investigate whether "delayed_invocations_" in lpxclient/lpxserver is really necessary. -Is it just a performance optimization, or does it need to be that way? +Is it just a performance optimization, or does it need to be that way? In particular, +I'm thinking lpxserver can do without. * Consider targeted difference transmission for just one tangible, meant to be used for rapid update after the player invokes a thread on that one tangible. +* Do we really need can_be_controlled? + +* Replace engio with 'invoke.' and 'probe.' + Secret / semi-secret variables Secret functions @@ -29,7 +34,6 @@ The Spectra game Heroes of Rock,Paper,Scissors -Come up with a plan for 'redirect'. probes should never modify the state of the world. Currently, there is no protection to guarantee this, except for something in lpxclient where it does a snapshot and rollback before and after the probe. We should come up with a more comprehensive mechanism. diff --git a/luprex/cpp/core/lpxclient.cpp b/luprex/cpp/core/lpxclient.cpp index e12b0f56..57ed8825 100644 --- a/luprex/cpp/core/lpxclient.cpp +++ b/luprex/cpp/core/lpxclient.cpp @@ -45,7 +45,7 @@ public: // the invariant that there's always an actor. When the first difference // transmission arrives, this actor may be deleted, or it may just be // ignored, at the server's discretion. - actor_id_ = world_->create_login_actor(); + actor_id_ = world_->connection_create(); // Clear the unack command queue. unack_.clear(); @@ -70,7 +70,7 @@ public: channel_.reset(); // Create the standalone actor. - actor_id_ = world_->create_login_actor(); + actor_id_ = world_->connection_create(); // Clear the unack command queue. unack_.clear(); @@ -102,7 +102,7 @@ public: if (world_->snapshot_empty()) { world_->snapshot(); for (const Invocation &inv : unack_) { - world_->invoke(inv); + world_->invoke(0, inv); } } } @@ -117,10 +117,10 @@ public: void send_invocation(const Invocation &inv) { if (channel_ == nullptr) { - world_->invoke(inv); + world_->invoke(0, inv); } else { world_to_asynchronous(); - world_->invoke(inv); + world_->invoke(0, inv); unack_.push_back(inv); StreamBuffer *sb = channel_->out(); sb->write_uint8(util::MSG_INVOKE); @@ -134,12 +134,6 @@ public: // set_initial_state_connect(util::ss("nocert:", hostname, ":8085")); } - void change_actor_id(int64_t actor_id) { - util::dprint("Actor ID changing: ", actor_id); - print_channeler_.reset(); - actor_id_ = actor_id; - } - void receive_ack_from_server(StreamBuffer *sb) { // An ack is just a single byte, so there's nothing left to read. if (unack_.empty()) { @@ -148,7 +142,7 @@ public: return; } world_to_synchronous(); - world_->invoke(unack_.front()); + world_->invoke(0, unack_.front()); unack_.pop_front(); } @@ -157,7 +151,11 @@ public: try { DebugCollector dbc(""); int64_t nactor = world_->patch(sb, &dbc); - if (nactor != actor_id_) change_actor_id(nactor); + if (nactor != actor_id_) { + util::dprint("Actor ID changing: ", nactor); + print_channeler_.reset(); + actor_id_ = nactor; + } // dbc.dump(...); } catch (const StreamException &sexcept) { abandon_server(); diff --git a/luprex/cpp/core/lpxserver.cpp b/luprex/cpp/core/lpxserver.cpp index 864bcd8b..ae6701a3 100644 --- a/luprex/cpp/core/lpxserver.cpp +++ b/luprex/cpp/core/lpxserver.cpp @@ -11,6 +11,7 @@ class Client : public eng::opnew { public: + int64_t client_id_; int64_t actor_id_; SharedChannel channel_; UniqueWorld sync_; @@ -44,7 +45,7 @@ public: world_->expose_world_to_driver(wrapper_); // Create the admin actor. Note: there isn't any 'init' function yet. - actor_id_ = world_->create_login_actor(); + actor_id_ = world_->connection_create(); // Print out admin ID for debugging purposes. util::dprint("Admin actor id = ", actor_id_); @@ -73,12 +74,13 @@ public: } void delete_client(UniqueClient &client) { - util::dprint("Client closed: actor id=", client->actor_id_); - world_->disconnected(client->actor_id_); + util::dprint("Client closed: client_id=", client->client_id_, " actor_id=", client->actor_id_); + world_->connection_delete(client->client_id_); client.reset(); } void send_diffs(UniqueClient &client, bool full) { + client->actor_id_ = world_->connection_get_actor(client->client_id_); StreamBuffer *sb = client->channel_->out(); sb->write_uint8(util::MSG_DIFF); sb->write_uint32(0); @@ -89,27 +91,6 @@ public: sb->overwrite_int32(tw_1, tw_2 - tw_1); } - void redirect(int64_t id1, int64_t id2) { - for (auto &client : clients_) { - if (client->actor_id_ == id1) { - bool ok = world_->redirected(id1, id2); - if (!ok) delete_client(client); - } - } - } - - // Redirection is touchy: almost any world->invoke could - // call tangible.redirect. After that point, the world is - // relying on us not to control the wrong character. - void process_redirects() { - if (world_->have_redirects()) { - World::Redirects redirects = world_->fetch_redirects(); - for (const auto &pair : redirects) { - redirect(pair.first, pair.second); - } - } - } - bool handle_invocation(UniqueClient &client) { if (client == nullptr) return false; StreamBuffer *sb = client->channel_->in(); @@ -131,18 +112,12 @@ public: return false; } - // If the user sent an invalid kind, log them out. - if (!Invocation::is_valid_network_kind(inv.kind())) { - delete_client(client); - return false; - } - // Acknowledge the invocation. client->channel_->out()->write_uint8(util::MSG_ACK); client->channel_->out()->write_uint32(0); // Execute the invocation with the sync model. - client->sync_->invoke(inv); + client->sync_->invoke(0, inv); client->async_diff_ = true; // Process the invocation in the master model. @@ -152,8 +127,7 @@ public: // who may not know their new actor_id yet. // if (inv.actor() == client->actor_id_) { - world_->invoke(inv); - process_redirects(); + world_->invoke(client->client_id_, inv); } return true; } @@ -210,7 +184,7 @@ public: // come from the local command line. We just feed // these directly into the master model. for (const Invocation &inv : delayed_invocations_) { - world_->invoke(inv); + world_->invoke(0, inv); } delayed_invocations_.clear(); @@ -220,7 +194,8 @@ public: if (chan == nullptr) break; if (chan->port() == 8085) { Client *client = new Client; - client->actor_id_ = world_->create_login_actor(); + client->actor_id_ = world_->connection_create(); + client->client_id_ = client->actor_id_; // TODO: initialize the login actor on the master. client->channel_ = std::move(chan); client->async_diff_ = true; @@ -229,7 +204,7 @@ public: client->sync_.reset(new World(WORLD_TYPE_PREDICTIVE)); // This login actor is never used, it is just to preserve the invariant that // the client model and the server synchronous model are identical. - client->sync_->create_login_actor(); + client->sync_->connection_create(); clients_.emplace_back(client); util::dprint("New client: actor id=", client->actor_id_); } else if (chan->port() == 8080) { @@ -241,7 +216,7 @@ public: // If the clock has advanced far enough, tick the master model. if (clock >= next_tick_) { - world_->invoke(Invocation(AccessKind::INVOKE_TICK, 0, 0, "")); + world_->invoke(0, Invocation(AccessKind::INVOKE_TICK, 0, 0, "")); for (UniqueClient &client : clients_) { client->async_diff_ = true; } @@ -249,11 +224,6 @@ public: if (next_tick_ < clock + 0.3) next_tick_ = clock + 0.3; } - // This handles any redirects triggered from the server command - // line or by the tick function. - process_redirects(); - util::remove_nullptrs(clients_); - // Traverse all existing channels, process any communication. for (UniqueClient &client : clients_) { if (client->channel_->closed()) { diff --git a/luprex/cpp/core/luastack.cpp b/luprex/cpp/core/luastack.cpp index 4b9eaf3b..605593e5 100644 --- a/luprex/cpp/core/luastack.cpp +++ b/luprex/cpp/core/luastack.cpp @@ -76,6 +76,7 @@ lua_State *LuaCoreStack::newstate (lua_Alloc allocf) { LS.rawset(LuaRegistry, "tangibles", LuaNewTable); LS.rawset(LuaRegistry, "persist", LuaNewTable); LS.rawset(LuaRegistry, "unpersist", LuaNewTable); + LS.rawset(LuaRegistry, "funcnames", 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 91e17715..047cd454 100644 --- a/luprex/cpp/core/source.cpp +++ b/luprex/cpp/core/source.cpp @@ -292,8 +292,8 @@ void SourceDB::update(const util::LuaSourceVec &source) { // Clear all the classes in the registry classes table. // static void source_clear_globals(lua_State *L) { - LuaVar classname, classtab, key, globtab, classes; - LuaExtStack LS(L, classname, classtab, key, globtab, classes); + LuaVar classname, classtab, key, globtab, classes, funcnames; + LuaExtStack LS(L, classname, classtab, key, globtab, classes, funcnames); LS.getglobaltable(globtab); LS.cleartable(globtab, true); @@ -306,8 +306,45 @@ static void source_clear_globals(lua_State *L) { assert(LS.istable(classtab)); LS.cleartable(classtab, true); } + + LS.rawget(funcnames, LuaRegistry, "funcnames"); + assert(LS.istable(funcnames)); + LS.cleartable(funcnames, true); } +void SourceDB::rebuild_funcnames() { + lua_State *L = lua_state_; + LuaVar globtab, funcnames, key, val, key2, val2; + LuaExtStack LS(L, globtab, funcnames, key, val, key2, val2); + + LS.getglobaltable(globtab); + LS.rawget(funcnames, LuaRegistry, "funcnames"); + + // Top-level functions in the global environment. + LS.set(key, LuaNil); + while (LS.next(globtab, key, val) != 0) { + if (!LS.isstring(key)) continue; + if (LS.isfunction(val)) { + LS.rawset(funcnames, val, key); + } + } + + // Functions one level deep inside class tables. + LS.set(key, LuaNil); + while (LS.next(globtab, key, val) != 0) { + if (!LS.isstring(key)) continue; + if (!LS.istable(val)) continue; + eng::string classname = LS.ckstring(key); + LS.set(key2, LuaNil); + while (LS.next(val, key2, val2) != 0) { + if (!LS.isstring(key2)) continue; + if (LS.isfunction(val2)) { + eng::string fullname = classname + "." + LS.ckstring(key2); + LS.rawset(funcnames, val2, fullname); + } + } + } +} // Load all the 'LuaDefine' C functions into the lua state. // @@ -406,7 +443,7 @@ void SourceDB::rebuild_core() { source_clear_globals(lua_state_); source_load_cfunctions(lua_state_); source_load_cconstants(lua_state_); -} +} eng::string SourceDB::rebuild_module(const eng::string &mod) { if (mod == "CORE") { diff --git a/luprex/cpp/core/source.hpp b/luprex/cpp/core/source.hpp index 73011feb..7b9dfad3 100644 --- a/luprex/cpp/core/source.hpp +++ b/luprex/cpp/core/source.hpp @@ -172,6 +172,13 @@ public: // void rebuild_core(); + // rebuild_funcnames + // + // Traverses the global environment and populates the registry "funcnames" + // table, mapping each closure to its name. + // + void rebuild_funcnames(); + // Difference transmission. // // Note: The patch routine applies the differences to the source diff --git a/luprex/cpp/core/traceback.cpp b/luprex/cpp/core/traceback.cpp index 556d556a..b6141899 100644 --- a/luprex/cpp/core/traceback.cpp +++ b/luprex/cpp/core/traceback.cpp @@ -43,33 +43,46 @@ int traceback_coroutine(lua_State *L) { firstpart = 0; continue; } - lua_getinfo(L, "Snl", &ar); + + lua_getinfo(L, "Snlf", &ar); + + eng::string storedname; + { + lua_getfield(L, LUA_REGISTRYINDEX, "funcnames"); + lua_pushvalue(L, -2); + lua_rawget(L, -2); + const char *fname = lua_tostring(L, -1); + if (fname) storedname = fname; + lua_pop(L, 3); + } + if ((!any) && (*ar.what == 'C') && (ar.name != 0)) { if (strcmp(ar.name, "__newindex") == 0) continue; } if ((ar.currentline > 0) || (*ar.namewhat != 0) || (*ar.what != 'C')) { any = true; lua_pushliteral(L, "\n\t"); - if (strcmp(ar.short_src, "")==0) - { - lua_pushstring(L, "in the console"); + + if (strcmp(ar.short_src, "")==0) { + lua_pushstring(L, "in the console in"); + } else if (strcmp(ar.short_src, "[C]")==0) { + lua_pushstring(L, "in builtin C++ "); + } else if (ar.currentline > 0) { + lua_pushfstring(L, "in %s line %d in", ar.short_src, ar.currentline); + } else { + lua_pushfstring(L, "in %s in", ar.short_src); } - else - { - lua_pushfstring(L, "in %s", ar.short_src); - if (ar.currentline > 0) - lua_pushfstring(L, " line %d", ar.currentline); - } - if (*ar.namewhat != '\0') /* is there a name? */ - lua_pushfstring(L, " in function " LUA_QS, ar.name); - else { - if (*ar.what == 'm') /* main? */ - lua_pushfstring(L, " in top-level expression "); - else if (*ar.what == 'C' || *ar.what == 't') - lua_pushliteral(L, " in unknown C function"); - else - lua_pushfstring(L, " in function on line %d", ar.linedefined); + + if (!storedname.empty()) { + lua_pushfstring(L, " function " LUA_QS, storedname.c_str()); + } else if (*ar.namewhat != 0) { + lua_pushfstring(L, " function " LUA_QS, ar.name); + } else if (*ar.what == 'm') { + lua_pushfstring(L, " top-level expression "); + } else { + lua_pushliteral(L, " unknown function"); } + if (1 + lua_gettop(L) - top > 5) { lua_concat(L, 1 + lua_gettop(L) - top); } diff --git a/luprex/cpp/core/world-accessor.cpp b/luprex/cpp/core/world-accessor.cpp index b29fab2d..26ebc1a4 100644 --- a/luprex/cpp/core/world-accessor.cpp +++ b/luprex/cpp/core/world-accessor.cpp @@ -368,26 +368,22 @@ LuaDefine(tangible_redirect, "actor1, actor2", "|client types his name and password, tangible.redirect is used " "|to tell the client to control the real actor." "|" - "|Actor1 must be currently logged in. Actor2 must not be. This " - "|function doesn't error check that the actors are valid: it just " - "|queues the redirect, then the system error checks later. This " - "|is because conditions can change. An invalid redirect will cause " - "|a disconnection." + "|Actor1 must be currently logged in. Actor2 must be a valid actor " + "|tangible that is not curretly logged in." "|") { LuaArg lactor1, lactor2; LuaDefStack LS(L, lactor1, lactor2); World *w = World::fetch_global_pointer(L); Tangible *actor1 = w->tangible_get(LS, lactor1, true); Tangible *actor2 = w->tangible_get(LS, lactor2, true); - // if (!actor1->is_controlled_) { - // luaL_error(L, "Actor1 is not a controlled actor."); - // return 0; - // } - // if ((!actor2->can_be_controlled_) || (actor2->is_controlled_)) { - // luaL_error(L, "Actor2 is not an uncontrolled actor."); - // return 0; - // } - w->add_redirect(actor1->id(), actor2->id()); + int64_t client_id = w->connection_get_client(actor1->id()); + eng::string error = w->connection_redirect(client_id, actor2->id()); + if (!error.empty()) { + eng::ostringstream oss; + oss << "redirect from " << actor1->id() << " to " << actor2->id() << " failed: " << error; + luaL_error(L, "%s", oss.str().c_str()); + return LS.result(); + } return LS.result(); } diff --git a/luprex/cpp/core/world-core.cpp b/luprex/cpp/core/world-core.cpp index 4e977f64..ed84185b 100644 --- a/luprex/cpp/core/world-core.cpp +++ b/luprex/cpp/core/world-core.cpp @@ -152,12 +152,20 @@ void Tangible::serialize(StreamBuffer *sb) { anim_queue_.serialize(sb); id_player_pool_.serialize(sb); print_buffer_.serialize(sb); + sb->write_bool(can_be_controlled_); + sb->write_bool(is_controlled_); + sb->write_bool(force_disconnect_); + sb->write_bool(delete_on_disconnect_); } void Tangible::deserialize(StreamBuffer *sb) { anim_queue_.deserialize(sb); id_player_pool_.deserialize(sb); print_buffer_.deserialize(sb); + can_be_controlled_ = sb->read_bool(); + is_controlled_ = sb->read_bool(); + force_disconnect_ = sb->read_bool(); + delete_on_disconnect_ = sb->read_bool(); update_plane_item(); } @@ -294,13 +302,7 @@ void World::get_near(int64_t player_id, float radius, bool exclude_nowhere, bool get_near(scan, into); } -World::Redirects World::fetch_redirects() { - World::Redirects result = std::move(redirects_); - redirects_.clear(); - return result; -} - -int64_t World::create_login_actor() { +int64_t World::connection_create() { assert(stack_is_clear()); int64_t id = id_global_pool_.get_one(); { @@ -327,13 +329,18 @@ int64_t World::create_login_actor() { spawn(LS, id, id, func, 0, false); } } + connections_.emplace(id, id); if (is_authoritative()) { run_scheduled_threads(); } return id; } -void World::disconnected(int64_t actor_id) { +eng::string World::connection_delete(int64_t client_id) { + auto iter = connections_.find(client_id); + if (iter == connections_.end()) return "no such client id"; + int64_t actor_id = iter->second; + connections_.erase(iter); Tangible *tan = tangible_get(actor_id); assert(tan != nullptr); assert(tan->is_controlled_); @@ -343,16 +350,39 @@ void World::disconnected(int64_t actor_id) { util::dprintf("Deleted actor: %lld\n", actor_id); tangible_delete(actor_id); } + return ""; } -bool World::redirected(int64_t actor_id, int64_t new_id) { - disconnected(actor_id); - Tangible *tan = tangible_get(new_id); - if (tan && (tan->can_be_controlled_) && (!tan->is_controlled_)) { - tan->is_controlled_ = true; - return true; - } else { - return false; +int64_t World::connection_get_actor(int64_t client_id) const { + auto iter = connections_.find(client_id); + if (iter == connections_.end()) return 0; + return iter->second; +} + +int64_t World::connection_get_client(int64_t actor_id) const { + for (const auto &pair : connections_) { + if (pair.second == actor_id) return pair.first; + } + return 0; +} + +eng::string World::connection_redirect(int64_t client_id, int64_t new_id) { + Tangible *newtan = tangible_get(new_id); + if (newtan == nullptr) return "no such target tangible"; + if (newtan->is_controlled_) return "target tangible is already controlled"; + if (!newtan->can_be_controlled_) return "target tangible is not a potential actor"; + eng::string delresult = connection_delete(client_id); + if (!delresult.empty()) return delresult; + newtan->is_controlled_ = true; + newtan->force_disconnect_ = false; + connections_[client_id] = new_id; + return ""; +} + +void World::connection_close_all() { + while (!connections_.empty()) { + int64_t client_id = connections_.begin()->first; + connection_delete(client_id); } } @@ -564,6 +594,7 @@ bool World::rebuild_sourcedb(int64_t actor_id) { 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; @@ -743,7 +774,13 @@ HttpServerResponse World::http_serve(const HttpParser &request) { return response; } -void World::invoke(const Invocation &inv) { +void World::invoke(int64_t client_id, const Invocation &inv) { + if (client_id != 0) { + int64_t actor_id = connection_get_actor(client_id); + if (actor_id == 0) return; + if (inv.actor() != actor_id) return; + if (!Invocation::is_valid_network_kind(inv.kind())) return; + } switch (inv.kind()) { case AccessKind::INVOKE_LUA_CALL: invoke_lua_call(inv.actor(), inv.place(), inv.datapack()); @@ -838,7 +875,7 @@ bool World::spawn(LuaCoreStack &LS0, int64_t actor_id, int64_t place_id, LuaSlot LS.rawset(thinfo, "isnew", true); LS.rawset(thinfo, "useppool", true); LS.rawset(thinfo, "print", print); - + // Store the thread into place's thread table. LS.rawget(threads, mt, "threads"); if (!LS.istable(threads)) { @@ -1230,7 +1267,6 @@ const eng::string &World::get_global(const eng::string &gvar) { void World::serialize(StreamBuffer *sb) { assert(stack_is_clear()); - assert(redirects_.empty()); // int64_t wc0 = sb->total_writes(); lua_snap_.serialize(sb); id_global_pool_.serialize(sb); @@ -1242,12 +1278,16 @@ void World::serialize(StreamBuffer *sb) { sb->write_int64(p.first); p.second->serialize(sb); } + sb->write_uint32(connections_.size()); + for (const auto &p : connections_) { + sb->write_int64(p.first); + sb->write_int64(p.second); + } assert(stack_is_clear()); } void World::deserialize(StreamBuffer *sb) { assert(stack_is_clear()); - redirects_.clear(); lua_snap_.deserialize(sb); id_global_pool_.deserialize(sb); clock_ = sb->read_int64(); @@ -1259,7 +1299,7 @@ void World::deserialize(StreamBuffer *sb) { } // Deserialize tangibles. size_t ntan = sb->read_uint32(); - for (size_t i = 0; i < ntan; i++) { + for (uint32_t i = 0; i < ntan; i++) { int64_t id = sb->read_int64(); UniqueTangible &t = tangibles_[id]; if (t == nullptr) { @@ -1269,6 +1309,14 @@ void World::deserialize(StreamBuffer *sb) { } t->deserialize(sb); } + // Deserialize connections. + connections_.clear(); + uint32_t nconn = sb->read_uint32(); + for (uint32_t i = 0; i < nconn; i++) { + int64_t client_id = sb->read_int64(); + int64_t actor_id = sb->read_int64(); + connections_.emplace(client_id, actor_id); + } // Delete tangibles that didn't get deserialized. for (auto iter = tangibles_.begin(); iter != tangibles_.end(); ) { if (iter->second->plane_item_.id() == 0) { @@ -1277,8 +1325,6 @@ void World::deserialize(StreamBuffer *sb) { ++iter; } } - // After a save and load, http requests no longer should exist - abort_all_http_requests(425, "http requests aborted by loading a save game"); assert(stack_is_clear()); } diff --git a/luprex/cpp/core/world.hpp b/luprex/cpp/core/world.hpp index 747acfbc..7dab701e 100644 --- a/luprex/cpp/core/world.hpp +++ b/luprex/cpp/core/world.hpp @@ -92,7 +92,8 @@ public: // // This flag is set to true when a client is controlling this player. // It gets set back to false when the client logs out or attaches - // to a different player. This can only be set in master models. + // to a different player. If this is set, then the connections_ + // table contains a map from a client ID to this actor. // bool is_controlled_; @@ -129,7 +130,6 @@ class World : public eng::opnew { public: using IdVector = util::IdVector; using TanVector = eng::vector; - using Redirects = eng::map; const float RadiusVisibility = 1000.0; const float RadiusClose = 1000.0; @@ -221,46 +221,7 @@ public: // If there's no such tangible, this is a no-op. // void tangible_delete(int64_t id); - - // Create a login actor. - // - // Creates a tangible of class 'login' and returns its ID. - // This is used to create a temporary actor which is used during - // the login process. - // - // 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.init' is not called, - // and the login flags are not used in client models. - // - int64_t create_login_actor(); - - // Log out a connected player. - // - // This is to be called after a client disconnects. - // - void disconnected(int64_t actor_id); - - // Notify the world that the front end has implemented a - // redirect. This can return null to indicate a last-minute - // failure, in which case the front end must disconnect. - // - bool redirected(int64_t actor_id, int64_t new_id); - // Add a redirect. - // - void add_redirect(int64_t a, int64_t b) { redirects_.emplace(a,b); } - - // Fetch all redirects and clear the redirects table. - // - Redirects fetch_redirects(); - - // Return true if there are any redirects. - // - bool have_redirects() const { return !redirects_.empty(); } - // Probe an arbitrary lua expression. // // Any print-statements in the lua code are sent into @@ -285,7 +246,11 @@ public: // It is legal to mutate a world model without using 'Invoke', but // only in authoritative world models. // - void invoke(const Invocation &inv); + // If you pass in a nonzero client id, then that means the invocation was + // sent by a connected client. In that case, this checks that the invocation + // is legal for that client. + // + void invoke(int64_t client_id, const Invocation &inv); // Get the PrintBuffer of the actor. // @@ -351,6 +316,11 @@ public: // Serialize and deserialize. // + // Caution: this will save all state, including state like current connections + // and ongoing HTTP requests. It may be desirable, after saving and restoring, + // to purge connections and HTTP requests. There are separate functions to do + // these things. + // void serialize(StreamBuffer *sb); void deserialize(StreamBuffer *sb); @@ -395,19 +365,6 @@ public: // void lthread_prints_to_actor(int64_t actor_id); - // Set a lua global variable. - // - // The table just stores strings, and the difference transmitter - // just difference transmits those strings. The strings are meant - // to be serialized lua data structures, but there is no enforcement - // of that here. - // - void set_global(const eng::string &var, std::string_view value); - - // Get a lua global variable. - // - const eng::string &get_global(const eng::string &var); - // Allocate a single ID. // // The rules are as follows: @@ -427,7 +384,75 @@ public: // Otherwise, return. void guard_nopredict(lua_State *L, const char *fn); +public: + ////////////////////////////////////////////////////////////////////////// + // + // Global variables. + // + ////////////////////////////////////////////////////////////////////////// + + // Set a lua global variable. + // + // The table just stores strings, and the difference transmitter + // just difference transmits those strings. The strings are meant + // to be serialized lua data structures, but there is no enforcement + // of that here. + // + void set_global(const eng::string &var, std::string_view value); + + // Get a lua global variable. + // + const eng::string &get_global(const eng::string &var); + + +public: + ////////////////////////////////////////////////////////////////////////// + // + // Connection Management + // + ////////////////////////////////////////////////////////////////////////// + + // Create a connection. + // + // This creates a login actor, and also records the existence of + // the connection. Returns the actor_id of the login actor, which + // is also the client id. + // + int64_t connection_create(); + + // This is to be called after a client disconnects. This removes the + // connection. On error, return an error message. + // + eng::string connection_delete(int64_t client_id); + + // Get the current client_id for an actor_id. + // Returns 0 if the actor is not a connected actor. + // + int64_t connection_get_client(int64_t actor_id) const; + + // Get the current actor_id for a client_id. + // Returns 0 if the client_id is not a connected client. + // + int64_t connection_get_actor(int64_t client_id) const; + + // Add a redirect. On error, return an error message. + // + eng::string connection_redirect(int64_t client_id, int64_t actor_id); + + // Close all connections. + // + // This is often useful after reloading a save game - the save contains + // the connection state! + // + void connection_close_all(); + private: + //////////////////////////////////////////////////////////////////////////// + // + // Invocation and scheduling. + // + //////////////////////////////////////////////////////////////////////////// + // Add a thread to the scheduler queue. // void schedule(int64_t clk, int64_t thid, int64_t plid); @@ -533,14 +558,21 @@ public: public: /////////////////////////////////////////////////////////// // - // difference transmission internals related to table comparison - // - // These routines compare tables in the master lua to the corresponding - // tables in the synchronous lua. This is a nonrecursive process, because - // the recursion has already been done during the table enumeration process. + // Difference transmission entry point. // /////////////////////////////////////////////////////////// + int64_t patch(StreamBuffer *sb, DebugCollector *dbc); + void diff(int64_t actor, bool full, World *master, StreamBuffer *sb); + + /////////////////////////////////////////////////////////// + // + // Difference transmission internals + // + /////////////////////////////////////////////////////////// + + util::IdVector get_visible_union(int64_t actor_id, World *master); + void patch_numbered_tables(StreamBuffer *sb, DebugCollector *dbc); void diff_numbered_tables(lua_State *master, StreamBuffer *sb); @@ -550,14 +582,6 @@ public: void patch_tangible_classes(StreamBuffer *sb, DebugCollector *dbc); void diff_tangible_classes(const IdVector &basis, lua_State *master, StreamBuffer *sb); - /////////////////////////////////////////////////////////// - // - // Difference transmission internals - // - /////////////////////////////////////////////////////////// - - util::IdVector get_visible_union(int64_t actor_id, World *master); - int64_t patch_actor(StreamBuffer *sb, DebugCollector *dbc); void diff_actor(int64_t actor_id, World *master, StreamBuffer *sb); @@ -576,15 +600,6 @@ public: void patch_globals(StreamBuffer *sb, DebugCollector *dbc); void diff_globals(World *master, StreamBuffer *sb); - /////////////////////////////////////////////////////////// - // - // Difference transmission entry point. - // - /////////////////////////////////////////////////////////// - - int64_t patch(StreamBuffer *sb, DebugCollector *dbc); - void diff(int64_t actor, bool full, World *master, StreamBuffer *sb); - public: /////////////////////////////////////////////////////////// // @@ -640,6 +655,13 @@ public: void unnumber_lua_tables(); private: + /////////////////////////////////////////////////////////// + // + // Instance Variables + // + /////////////////////////////////////////////////////////// + + // Type of model WorldType world_type_; @@ -692,9 +714,9 @@ private: // StreamBuffer snapshot_; - // Redirects. + // Connections. A Map from client_id to current actor ID. // - Redirects redirects_; + std::map connections_; // Storage for wrapper_get_tangibles_near and wrapper_get_animation_queues. // These hold results alive while the driver reads from the raw pointers. @@ -713,7 +735,6 @@ private: friend class Tangible; friend int lfn_tangible_animate(lua_State *L); friend int lfn_tangible_build(lua_State *L); - friend int lfn_tangible_redirect(lua_State *L); friend int lfn_tangible_actor(lua_State *L); friend int lfn_tangible_place(lua_State *L); friend int lfn_tangible_nopredict(lua_State *L); diff --git a/luprex/lua/login.lua b/luprex/lua/login.lua index 27c68370..0bcb61df 100644 --- a/luprex/lua/login.lua +++ b/luprex/lua/login.lua @@ -10,6 +10,7 @@ function login.init() global.set("nextplayer", player + 1) dprint("login.init initializing player ", player) actor.player = player + tangible.keepactor(actor) -- do not delete this login when the client disconnects tangible.animinit{tan=actor, anim={bp="character", mesh="manny", plane="earth", xyz={player * 100, 0, 90}}} end @@ -24,7 +25,6 @@ end function engio.move(action, xyz, facing) -- todo: sanity check the parameters. - dprint("engio.move ", action, " ", xyz[1], " ", xyz[2], " ", xyz[3]) tangible.animate{tan=actor, anim={action=action, interactive=true, xyz=xyz, facing=facing}} end @@ -33,6 +33,10 @@ function moveto(x, y) tangible.animate{tan=actor, anim={action="moveto", xyz={x, y, z}, facing=math.auto}} end +function login.lookmenu(add) + add("Redirect", function() tangible.redirect(actor, place) end) +end + function cube.lookmenu(add) add("Cube A", function () dprint("Doing Cube A") end) add("Cube B", function () dprint("Doing Cube B") end) diff --git a/luprex/lua/menu.lua b/luprex/lua/menu.lua index f5562aa1..377c1c9f 100644 --- a/luprex/lua/menu.lua +++ b/luprex/lua/menu.lua @@ -55,14 +55,14 @@ function engio.pressmenu(label) return end - local result = nil + local menuclosure = nil local add = function(lbl, closure) if (lbl == label) then - result = closure + menuclosure = closure end end class.lookmenu(add) - if result ~= nil then - result() + if menuclosure ~= nil then + menuclosure() end end