From 8d88c88fa052bfd839fd375312a59ced76a1ed76 Mon Sep 17 00:00:00 2001 From: jyelon Date: Fri, 5 Jun 2026 17:52:46 -0400 Subject: [PATCH] Working on redirect/connect/etc --- Docs/TODO-List.md | 2 - luprex/cpp/core/lpxserver.cpp | 16 +++---- luprex/cpp/core/world-accessor.cpp | 64 ++++++++++++------------- luprex/cpp/core/world-core.cpp | 75 ++++++++++++++++++------------ luprex/cpp/core/world.hpp | 66 +++++++++++++------------- 5 files changed, 117 insertions(+), 106 deletions(-) diff --git a/Docs/TODO-List.md b/Docs/TODO-List.md index c13379a5..af66ce2a 100644 --- a/Docs/TODO-List.md +++ b/Docs/TODO-List.md @@ -14,8 +14,6 @@ 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? - * Think about terrain / backgrounds * Think about downloading assets diff --git a/luprex/cpp/core/lpxserver.cpp b/luprex/cpp/core/lpxserver.cpp index ae6701a3..47fe676c 100644 --- a/luprex/cpp/core/lpxserver.cpp +++ b/luprex/cpp/core/lpxserver.cpp @@ -121,14 +121,7 @@ public: client->async_diff_ = true; // Process the invocation in the master model. - // - // An invoke with the wrong actor_id is quietly a noop. This is - // to make leeway for clients who have recently been redirected, and - // who may not know their new actor_id yet. - // - if (inv.actor() == client->actor_id_) { - world_->invoke(client->client_id_, inv); - } + world_->invoke(client->client_id_, inv); return true; } @@ -235,6 +228,13 @@ public: while (handle_invocation(client)); if (client == nullptr) continue; + // Check for force disconnect. + int64_t actor_id = world_->connection_get_actor(client->client_id_); + if (actor_id == 0) { + delete_client(client); + continue; + } + // Possibly send a diff. // Currently, it's configured to send about // ten mini-diffs per second, and two full diffs diff --git a/luprex/cpp/core/world-accessor.cpp b/luprex/cpp/core/world-accessor.cpp index 79d69f99..30ecbec2 100644 --- a/luprex/cpp/core/world-accessor.cpp +++ b/luprex/cpp/core/world-accessor.cpp @@ -293,36 +293,9 @@ LuaDefine(tangible_delete, "tan", luaL_error(L, "Not a tangible."); return 0; } - if (tan->can_be_controlled_) { - luaL_error(L, "Cannot delete a player using tangible.delete, use tangible.deleteactor instead."); - return 0; - } - w->tangible_delete(tan->id()); - return LS.result(); -} - -LuaDefine(tangible_deleteactor, "tan", - "|Delete an actor tangible, or mark it for deletion on logout." - "|" - "|This function is used to delete an actor tangible." - "|" - "|If the actor is not currently logged in, then the tangible is" - "|immediately deleted. If the actor is logged in, then the tangible" - "|is marked as delete_on_disconnect, and the force_disconnect flag" - "|is set. This will cause the actor to cleanly disconnect, and then" - "|the deletion will take place." - "|") { - LuaArg tanobj; - LuaDefStack LS(L, tanobj); - World *w = World::fetch_global_pointer(L); - Tangible *tan = w->tangible_get(LS, tanobj, true); - if ((tan == nullptr) || (!tan->can_be_controlled_)) { - luaL_error(L, "Tangible is not an actor."); - return 0; - } if (tan->is_controlled_) { tan->delete_on_disconnect_ = true; - tan->force_disconnect_ = true; + w->connection_redirect(tan, nullptr); } else { w->tangible_delete(tan->id()); } @@ -351,7 +324,7 @@ LuaDefine(tangible_keepactor, "tan", LuaDefStack LS(L, tanobj); World *w = World::fetch_global_pointer(L); Tangible *tan = w->tangible_get(LS, tanobj, true); - if ((tan == nullptr) || (!tan->can_be_controlled_)) { + if ((tan == nullptr) || (!tan->is_controlled_)) { luaL_error(L, "Tangible is not an actor."); return 0; } @@ -368,16 +341,18 @@ 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 be a valid actor " - "|tangible that is not curretly logged in." + "|If actor1 is not logged in, an error will be reported." + "|" + "|If actor2 is already logged in, they will be booted in order to " + "|enable actor1 to take control." + "|" "|") { 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); - int64_t client_id = w->connection_get_client(actor1->id()); - eng::string error = w->connection_redirect(client_id, actor2->id()); + eng::string error = w->connection_redirect(actor1, actor2); if (!error.empty()) { eng::ostringstream oss; oss << "redirect from " << actor1->id() << " to " << actor2->id() << " failed: " << error; @@ -387,6 +362,29 @@ LuaDefine(tangible_redirect, "actor1, actor2", return LS.result(); } +LuaDefine(tangible_forcedisconnect, "actor", + "|Cause the client controlling actor to be disconnected" + "|" + "|This is a no-op if nobody is logged in to the specified actor." + "|") { + LuaArg lactor1, lactor2; + LuaDefStack LS(L, lactor1, lactor2); + World *w = World::fetch_global_pointer(L); + Tangible *actor = w->tangible_get(LS, lactor1, true); + if (actor->is_controlled_) { + eng::string error = w->connection_redirect(actor, nullptr); + if (!error.empty()) { + eng::ostringstream oss; + oss << "forcedisconnect of " << actor->id() << " failed: " << error; + luaL_error(L, "%s", oss.str().c_str()); + return LS.result(); + } + } + return LS.result(); +} + + + LuaDefine(tangible_build, "config", "|Build a new tangible object." "|" diff --git a/luprex/cpp/core/world-core.cpp b/luprex/cpp/core/world-core.cpp index 632b75f7..09400606 100644 --- a/luprex/cpp/core/world-core.cpp +++ b/luprex/cpp/core/world-core.cpp @@ -152,9 +152,7 @@ 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_); } @@ -162,9 +160,7 @@ 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(); } @@ -220,9 +216,7 @@ Tangible *World::tangible_make(const LuaCoreStack &LS0, LuaSlot database, int64_ t.reset(new Tangible(this, id)); // Set the login flags. - t->can_be_controlled_ = false; t->is_controlled_ = false; - t->force_disconnect_ = false; t->delete_on_disconnect_ = false; // AnimQueue initializes itself to a valid default state. @@ -302,6 +296,12 @@ void World::get_near(int64_t player_id, float radius, bool exclude_nowhere, bool get_near(scan, into); } +void World::connection_prepare(Tangible *tan) { + tan->configure_id_pool_for_actor(); + tan->print_buffer_.clear(); +} + + int64_t World::connection_create() { assert(stack_is_clear()); int64_t id = id_global_pool_.get_one(); @@ -312,17 +312,14 @@ int64_t World::connection_create() { // Set the login flags. if (is_authoritative()) { - tan->can_be_controlled_ = true; tan->is_controlled_ = true; - tan->force_disconnect_ = false; tan->delete_on_disconnect_ = true; } LS.makeclass(classtab, "login"); LS.getmetatable(mt, database); LS.rawset(mt, "__index", classtab); - tan->configure_id_pool_for_actor(); - tan->print_buffer_.clear(); + connection_prepare(tan); if (is_authoritative()) { LS.rawget(func, classtab, "init"); @@ -336,46 +333,63 @@ int64_t World::connection_create() { return id; } -eng::string World::connection_delete(int64_t client_id) { +void World::connection_delete(int64_t client_id) { auto iter = connections_.find(client_id); - if (iter == connections_.end()) return "no such client id"; + if (iter == connections_.end()) return; int64_t actor_id = iter->second; connections_.erase(iter); Tangible *tan = tangible_get(actor_id); assert(tan != nullptr); assert(tan->is_controlled_); tan->is_controlled_ = false; - tan->force_disconnect_ = false; if (tan->delete_on_disconnect_) { util::dprintf("Deleted actor: %lld\n", actor_id); tangible_delete(actor_id); } - return ""; } 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; + if (iter != connections_.end()) return iter->second; + return 0; } -int64_t World::connection_get_client(int64_t actor_id) const { +int64_t World::connection_get_client(Tangible *actor) const { + if (!actor->is_controlled_) return 0; for (const auto &pair : connections_) { - if (pair.second == actor_id) return pair.first; + 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; +eng::string World::connection_redirect(Tangible *actor1, Tangible *actor2) { + if (actor1 == actor2) return ""; + + if (!actor1->is_controlled_) { + return "actor1 is not logged in, cannot redirect"; + } + + // If neccessary, bump actor2. Do not implement delete-on-disconnect, + // because actor2 isn't really "disconnected" yet. + if ((actor2 != nullptr) && (actor2->is_controlled_)) { + int64_t client_id2 = connection_get_client(actor2); + actor2->is_controlled_ = false; + connections_.erase(client_id2); + } + + int64_t client_id1 = connection_get_client(actor1); + actor1->is_controlled_ = false; + connections_.erase(client_id1); + + if (actor1->delete_on_disconnect_) { + tangible_delete(actor1->id()); + } + + if (actor2 != nullptr) { + connection_prepare(actor2); + actor2->is_controlled_ = true; + connections_[client_id1] = actor2->id(); + } return ""; } @@ -781,8 +795,9 @@ HttpServerResponse World::http_serve(const HttpParser &request) { 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; + auto iter = connections_.find(client_id); + if (iter == connections_.end()) return; + int64_t actor_id = iter->second; if (inv.actor() != actor_id) return; if (!Invocation::is_valid_network_kind(inv.kind())) return; } diff --git a/luprex/cpp/core/world.hpp b/luprex/cpp/core/world.hpp index 7dab701e..281d61f0 100644 --- a/luprex/cpp/core/world.hpp +++ b/luprex/cpp/core/world.hpp @@ -78,16 +78,6 @@ public: // PrintBuffer print_buffer_; - // Can-Be-Controlled flag. - // - // This flag indicates whether the tangible can be controlled - // by a client. Clients will not be allowed to attach to tangibles - // who don't have this flag. If this flag is true, the - // tangible cannot be deleted using a mere 'tangible.delete', instead, - // you have to use 'tangible.deleteplayer'. - // - bool can_be_controlled_; - // Is Controlled Flag. // // This flag is set to true when a client is controlling this player. @@ -97,13 +87,6 @@ public: // bool is_controlled_; - // Force disconnect flag. - // - // This flag is used to force the client to log out ASAP. This flag - // can only be set in master models. - // - bool force_disconnect_; - // Delete on Logout Flag. // // This flag can be set on a controlled player. When the player @@ -411,33 +394,50 @@ public: // Connection Management // ////////////////////////////////////////////////////////////////////////// - + + // Prepare a tangible to be controlled. + // + // This does whatever initialization is necessary to turn an ordinary + // tangible into a player-controlled tangible. + // + void connection_prepare(Tangible *tan); + // 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. + // This is used by the DrivenEngine to create the login + // actor. It 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. + // Delete a connection. + // + // This is used by the DrivenEngine to report that the specified + // client has been disconnected. // - eng::string connection_delete(int64_t client_id); + void 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. + // Find out what actor the given client is controlling. + // + // This is used by the DrivenEngine to check if the given client has + // changed actor, or if it has been forced to disconnect. // int64_t connection_get_actor(int64_t client_id) const; - // Add a redirect. On error, return an error message. + // Connection get client. // - eng::string connection_redirect(int64_t client_id, int64_t actor_id); + // Get the ID of the client that is controlling the specified actor. + // + int64_t connection_get_client(Tangible *tan) const; + + // Connection redirection. + // + // This is used by lua code to force changes in connection status. + // It can be used to redirect a client, or disconnect a client + // if actor2 == nullptr. + // + eng::string connection_redirect(Tangible *actor1, Tangible *actor2); // Close all connections. //