More work on redirect

This commit is contained in:
2026-06-02 18:34:03 -04:00
parent 6c9f75bfac
commit d951d2ab61
12 changed files with 298 additions and 201 deletions

View File

@@ -8,11 +8,16 @@
* Object-Oriented Lua Support * Object-Oriented Lua Support
* Investigate whether "delayed_invocations_" in lpxclient/lpxserver is really necessary. * 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, * 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. 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 / semi-secret variables
Secret functions Secret functions
@@ -29,7 +34,6 @@ The Spectra game
Heroes of Rock,Paper,Scissors 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. 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.

View File

@@ -45,7 +45,7 @@ public:
// the invariant that there's always an actor. When the first difference // the invariant that there's always an actor. When the first difference
// transmission arrives, this actor may be deleted, or it may just be // transmission arrives, this actor may be deleted, or it may just be
// ignored, at the server's discretion. // ignored, at the server's discretion.
actor_id_ = world_->create_login_actor(); actor_id_ = world_->connection_create();
// Clear the unack command queue. // Clear the unack command queue.
unack_.clear(); unack_.clear();
@@ -70,7 +70,7 @@ public:
channel_.reset(); channel_.reset();
// Create the standalone actor. // Create the standalone actor.
actor_id_ = world_->create_login_actor(); actor_id_ = world_->connection_create();
// Clear the unack command queue. // Clear the unack command queue.
unack_.clear(); unack_.clear();
@@ -102,7 +102,7 @@ public:
if (world_->snapshot_empty()) { if (world_->snapshot_empty()) {
world_->snapshot(); world_->snapshot();
for (const Invocation &inv : unack_) { for (const Invocation &inv : unack_) {
world_->invoke(inv); world_->invoke(0, inv);
} }
} }
} }
@@ -117,10 +117,10 @@ public:
void send_invocation(const Invocation &inv) { void send_invocation(const Invocation &inv) {
if (channel_ == nullptr) { if (channel_ == nullptr) {
world_->invoke(inv); world_->invoke(0, inv);
} else { } else {
world_to_asynchronous(); world_to_asynchronous();
world_->invoke(inv); world_->invoke(0, inv);
unack_.push_back(inv); unack_.push_back(inv);
StreamBuffer *sb = channel_->out(); StreamBuffer *sb = channel_->out();
sb->write_uint8(util::MSG_INVOKE); sb->write_uint8(util::MSG_INVOKE);
@@ -134,12 +134,6 @@ public:
// set_initial_state_connect(util::ss("nocert:", hostname, ":8085")); // 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) { void receive_ack_from_server(StreamBuffer *sb) {
// An ack is just a single byte, so there's nothing left to read. // An ack is just a single byte, so there's nothing left to read.
if (unack_.empty()) { if (unack_.empty()) {
@@ -148,7 +142,7 @@ public:
return; return;
} }
world_to_synchronous(); world_to_synchronous();
world_->invoke(unack_.front()); world_->invoke(0, unack_.front());
unack_.pop_front(); unack_.pop_front();
} }
@@ -157,7 +151,11 @@ public:
try { try {
DebugCollector dbc(""); DebugCollector dbc("");
int64_t nactor = world_->patch(sb, &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(...); // dbc.dump(...);
} catch (const StreamException &sexcept) { } catch (const StreamException &sexcept) {
abandon_server(); abandon_server();

View File

@@ -11,6 +11,7 @@
class Client : public eng::opnew { class Client : public eng::opnew {
public: public:
int64_t client_id_;
int64_t actor_id_; int64_t actor_id_;
SharedChannel channel_; SharedChannel channel_;
UniqueWorld sync_; UniqueWorld sync_;
@@ -44,7 +45,7 @@ public:
world_->expose_world_to_driver(wrapper_); world_->expose_world_to_driver(wrapper_);
// Create the admin actor. Note: there isn't any 'init' function yet. // 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. // Print out admin ID for debugging purposes.
util::dprint("Admin actor id = ", actor_id_); util::dprint("Admin actor id = ", actor_id_);
@@ -73,12 +74,13 @@ public:
} }
void delete_client(UniqueClient &client) { void delete_client(UniqueClient &client) {
util::dprint("Client closed: actor id=", client->actor_id_); util::dprint("Client closed: client_id=", client->client_id_, " actor_id=", client->actor_id_);
world_->disconnected(client->actor_id_); world_->connection_delete(client->client_id_);
client.reset(); client.reset();
} }
void send_diffs(UniqueClient &client, bool full) { void send_diffs(UniqueClient &client, bool full) {
client->actor_id_ = world_->connection_get_actor(client->client_id_);
StreamBuffer *sb = client->channel_->out(); StreamBuffer *sb = client->channel_->out();
sb->write_uint8(util::MSG_DIFF); sb->write_uint8(util::MSG_DIFF);
sb->write_uint32(0); sb->write_uint32(0);
@@ -89,27 +91,6 @@ public:
sb->overwrite_int32(tw_1, tw_2 - tw_1); 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) { bool handle_invocation(UniqueClient &client) {
if (client == nullptr) return false; if (client == nullptr) return false;
StreamBuffer *sb = client->channel_->in(); StreamBuffer *sb = client->channel_->in();
@@ -131,18 +112,12 @@ public:
return false; 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. // Acknowledge the invocation.
client->channel_->out()->write_uint8(util::MSG_ACK); client->channel_->out()->write_uint8(util::MSG_ACK);
client->channel_->out()->write_uint32(0); client->channel_->out()->write_uint32(0);
// Execute the invocation with the sync model. // Execute the invocation with the sync model.
client->sync_->invoke(inv); client->sync_->invoke(0, inv);
client->async_diff_ = true; client->async_diff_ = true;
// Process the invocation in the master model. // Process the invocation in the master model.
@@ -152,8 +127,7 @@ public:
// who may not know their new actor_id yet. // who may not know their new actor_id yet.
// //
if (inv.actor() == client->actor_id_) { if (inv.actor() == client->actor_id_) {
world_->invoke(inv); world_->invoke(client->client_id_, inv);
process_redirects();
} }
return true; return true;
} }
@@ -210,7 +184,7 @@ public:
// come from the local command line. We just feed // come from the local command line. We just feed
// these directly into the master model. // these directly into the master model.
for (const Invocation &inv : delayed_invocations_) { for (const Invocation &inv : delayed_invocations_) {
world_->invoke(inv); world_->invoke(0, inv);
} }
delayed_invocations_.clear(); delayed_invocations_.clear();
@@ -220,7 +194,8 @@ public:
if (chan == nullptr) break; if (chan == nullptr) break;
if (chan->port() == 8085) { if (chan->port() == 8085) {
Client *client = new Client; 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. // TODO: initialize the login actor on the master.
client->channel_ = std::move(chan); client->channel_ = std::move(chan);
client->async_diff_ = true; client->async_diff_ = true;
@@ -229,7 +204,7 @@ public:
client->sync_.reset(new World(WORLD_TYPE_PREDICTIVE)); client->sync_.reset(new World(WORLD_TYPE_PREDICTIVE));
// This login actor is never used, it is just to preserve the invariant that // This login actor is never used, it is just to preserve the invariant that
// the client model and the server synchronous model are identical. // the client model and the server synchronous model are identical.
client->sync_->create_login_actor(); client->sync_->connection_create();
clients_.emplace_back(client); clients_.emplace_back(client);
util::dprint("New client: actor id=", client->actor_id_); util::dprint("New client: actor id=", client->actor_id_);
} else if (chan->port() == 8080) { } else if (chan->port() == 8080) {
@@ -241,7 +216,7 @@ public:
// If the clock has advanced far enough, tick the master model. // If the clock has advanced far enough, tick the master model.
if (clock >= next_tick_) { 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_) { for (UniqueClient &client : clients_) {
client->async_diff_ = true; client->async_diff_ = true;
} }
@@ -249,11 +224,6 @@ public:
if (next_tick_ < clock + 0.3) next_tick_ = clock + 0.3; 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. // Traverse all existing channels, process any communication.
for (UniqueClient &client : clients_) { for (UniqueClient &client : clients_) {
if (client->channel_->closed()) { if (client->channel_->closed()) {

View File

@@ -76,6 +76,7 @@ lua_State *LuaCoreStack::newstate (lua_Alloc allocf) {
LS.rawset(LuaRegistry, "tangibles", LuaNewTable); LS.rawset(LuaRegistry, "tangibles", LuaNewTable);
LS.rawset(LuaRegistry, "persist", LuaNewTable); LS.rawset(LuaRegistry, "persist", LuaNewTable);
LS.rawset(LuaRegistry, "unpersist", LuaNewTable); LS.rawset(LuaRegistry, "unpersist", LuaNewTable);
LS.rawset(LuaRegistry, "funcnames", LuaNewTable);
// Tag the registry and global environment with their tabletypes. // Tag the registry and global environment with their tabletypes.
LS.settabletype(LuaRegistry, LUA_TT_REGISTRY); LS.settabletype(LuaRegistry, LUA_TT_REGISTRY);

View File

@@ -292,8 +292,8 @@ void SourceDB::update(const util::LuaSourceVec &source) {
// Clear all the classes in the registry classes table. // Clear all the classes in the registry classes table.
// //
static void source_clear_globals(lua_State *L) { static void source_clear_globals(lua_State *L) {
LuaVar classname, classtab, key, globtab, classes; LuaVar classname, classtab, key, globtab, classes, funcnames;
LuaExtStack LS(L, classname, classtab, key, globtab, classes); LuaExtStack LS(L, classname, classtab, key, globtab, classes, funcnames);
LS.getglobaltable(globtab); LS.getglobaltable(globtab);
LS.cleartable(globtab, true); LS.cleartable(globtab, true);
@@ -306,8 +306,45 @@ static void source_clear_globals(lua_State *L) {
assert(LS.istable(classtab)); assert(LS.istable(classtab));
LS.cleartable(classtab, true); 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. // Load all the 'LuaDefine' C functions into the lua state.
// //

View File

@@ -172,6 +172,13 @@ public:
// //
void rebuild_core(); 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. // Difference transmission.
// //
// Note: The patch routine applies the differences to the source // Note: The patch routine applies the differences to the source

View File

@@ -43,33 +43,46 @@ int traceback_coroutine(lua_State *L) {
firstpart = 0; firstpart = 0;
continue; 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 ((!any) && (*ar.what == 'C') && (ar.name != 0)) {
if (strcmp(ar.name, "__newindex") == 0) continue; if (strcmp(ar.name, "__newindex") == 0) continue;
} }
if ((ar.currentline > 0) || (*ar.namewhat != 0) || (*ar.what != 'C')) { if ((ar.currentline > 0) || (*ar.namewhat != 0) || (*ar.what != 'C')) {
any = true; any = true;
lua_pushliteral(L, "\n\t"); lua_pushliteral(L, "\n\t");
if (strcmp(ar.short_src, "<console>")==0)
{ if (strcmp(ar.short_src, "<console>")==0) {
lua_pushstring(L, "in the console"); 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
{ if (!storedname.empty()) {
lua_pushfstring(L, "in %s", ar.short_src); lua_pushfstring(L, " function " LUA_QS, storedname.c_str());
if (ar.currentline > 0) } else if (*ar.namewhat != 0) {
lua_pushfstring(L, " line %d", ar.currentline); lua_pushfstring(L, " function " LUA_QS, ar.name);
} } else if (*ar.what == 'm') {
if (*ar.namewhat != '\0') /* is there a name? */ lua_pushfstring(L, " top-level expression ");
lua_pushfstring(L, " in function " LUA_QS, ar.name); } else {
else { lua_pushliteral(L, " unknown function");
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 (1 + lua_gettop(L) - top > 5) { if (1 + lua_gettop(L) - top > 5) {
lua_concat(L, 1 + lua_gettop(L) - top); lua_concat(L, 1 + lua_gettop(L) - top);
} }

View File

@@ -368,26 +368,22 @@ LuaDefine(tangible_redirect, "actor1, actor2",
"|client types his name and password, tangible.redirect is used " "|client types his name and password, tangible.redirect is used "
"|to tell the client to control the real actor." "|to tell the client to control the real actor."
"|" "|"
"|Actor1 must be currently logged in. Actor2 must not be. This " "|Actor1 must be currently logged in. Actor2 must be a valid actor "
"|function doesn't error check that the actors are valid: it just " "|tangible that is not curretly logged in."
"|queues the redirect, then the system error checks later. This "
"|is because conditions can change. An invalid redirect will cause "
"|a disconnection."
"|") { "|") {
LuaArg lactor1, lactor2; LuaArg lactor1, lactor2;
LuaDefStack LS(L, lactor1, lactor2); LuaDefStack LS(L, lactor1, lactor2);
World *w = World::fetch_global_pointer(L); World *w = World::fetch_global_pointer(L);
Tangible *actor1 = w->tangible_get(LS, lactor1, true); Tangible *actor1 = w->tangible_get(LS, lactor1, true);
Tangible *actor2 = w->tangible_get(LS, lactor2, true); Tangible *actor2 = w->tangible_get(LS, lactor2, true);
// if (!actor1->is_controlled_) { int64_t client_id = w->connection_get_client(actor1->id());
// luaL_error(L, "Actor1 is not a controlled actor."); eng::string error = w->connection_redirect(client_id, actor2->id());
// return 0; if (!error.empty()) {
// } eng::ostringstream oss;
// if ((!actor2->can_be_controlled_) || (actor2->is_controlled_)) { oss << "redirect from " << actor1->id() << " to " << actor2->id() << " failed: " << error;
// luaL_error(L, "Actor2 is not an uncontrolled actor."); luaL_error(L, "%s", oss.str().c_str());
// return 0; return LS.result();
// } }
w->add_redirect(actor1->id(), actor2->id());
return LS.result(); return LS.result();
} }

View File

@@ -152,12 +152,20 @@ void Tangible::serialize(StreamBuffer *sb) {
anim_queue_.serialize(sb); anim_queue_.serialize(sb);
id_player_pool_.serialize(sb); id_player_pool_.serialize(sb);
print_buffer_.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) { void Tangible::deserialize(StreamBuffer *sb) {
anim_queue_.deserialize(sb); anim_queue_.deserialize(sb);
id_player_pool_.deserialize(sb); id_player_pool_.deserialize(sb);
print_buffer_.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(); 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); get_near(scan, into);
} }
World::Redirects World::fetch_redirects() { int64_t World::connection_create() {
World::Redirects result = std::move(redirects_);
redirects_.clear();
return result;
}
int64_t World::create_login_actor() {
assert(stack_is_clear()); assert(stack_is_clear());
int64_t id = id_global_pool_.get_one(); 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); spawn(LS, id, id, func, 0, false);
} }
} }
connections_.emplace(id, id);
if (is_authoritative()) { if (is_authoritative()) {
run_scheduled_threads(); run_scheduled_threads();
} }
return id; 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); Tangible *tan = tangible_get(actor_id);
assert(tan != nullptr); assert(tan != nullptr);
assert(tan->is_controlled_); assert(tan->is_controlled_);
@@ -343,16 +350,39 @@ void World::disconnected(int64_t actor_id) {
util::dprintf("Deleted actor: %lld\n", actor_id); util::dprintf("Deleted actor: %lld\n", actor_id);
tangible_delete(actor_id); tangible_delete(actor_id);
} }
return "";
} }
bool World::redirected(int64_t actor_id, int64_t new_id) { int64_t World::connection_get_actor(int64_t client_id) const {
disconnected(actor_id); auto iter = connections_.find(client_id);
Tangible *tan = tangible_get(new_id); if (iter == connections_.end()) return 0;
if (tan && (tan->can_be_controlled_) && (!tan->is_controlled_)) { return iter->second;
tan->is_controlled_ = true; }
return true;
} else { int64_t World::connection_get_client(int64_t actor_id) const {
return false; 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); if (actor_id != 0) lthread_prints_to_actor(actor_id);
} }
} }
source_db_.rebuild_funcnames();
lthread_prints_ << "Compiled " << successes << " modules successfully." << std::endl; lthread_prints_ << "Compiled " << successes << " modules successfully." << std::endl;
if (failures > 0) { if (failures > 0) {
lthread_prints_ << "Compiled " << failures << " modules with errors." << std::endl; lthread_prints_ << "Compiled " << failures << " modules with errors." << std::endl;
@@ -743,7 +774,13 @@ HttpServerResponse World::http_serve(const HttpParser &request) {
return response; 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()) { switch (inv.kind()) {
case AccessKind::INVOKE_LUA_CALL: case AccessKind::INVOKE_LUA_CALL:
invoke_lua_call(inv.actor(), inv.place(), inv.datapack()); invoke_lua_call(inv.actor(), inv.place(), inv.datapack());
@@ -1230,7 +1267,6 @@ const eng::string &World::get_global(const eng::string &gvar) {
void World::serialize(StreamBuffer *sb) { void World::serialize(StreamBuffer *sb) {
assert(stack_is_clear()); assert(stack_is_clear());
assert(redirects_.empty());
// int64_t wc0 = sb->total_writes(); // int64_t wc0 = sb->total_writes();
lua_snap_.serialize(sb); lua_snap_.serialize(sb);
id_global_pool_.serialize(sb); id_global_pool_.serialize(sb);
@@ -1242,12 +1278,16 @@ void World::serialize(StreamBuffer *sb) {
sb->write_int64(p.first); sb->write_int64(p.first);
p.second->serialize(sb); 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()); assert(stack_is_clear());
} }
void World::deserialize(StreamBuffer *sb) { void World::deserialize(StreamBuffer *sb) {
assert(stack_is_clear()); assert(stack_is_clear());
redirects_.clear();
lua_snap_.deserialize(sb); lua_snap_.deserialize(sb);
id_global_pool_.deserialize(sb); id_global_pool_.deserialize(sb);
clock_ = sb->read_int64(); clock_ = sb->read_int64();
@@ -1259,7 +1299,7 @@ void World::deserialize(StreamBuffer *sb) {
} }
// Deserialize tangibles. // Deserialize tangibles.
size_t ntan = sb->read_uint32(); 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(); int64_t id = sb->read_int64();
UniqueTangible &t = tangibles_[id]; UniqueTangible &t = tangibles_[id];
if (t == nullptr) { if (t == nullptr) {
@@ -1269,6 +1309,14 @@ void World::deserialize(StreamBuffer *sb) {
} }
t->deserialize(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. // Delete tangibles that didn't get deserialized.
for (auto iter = tangibles_.begin(); iter != tangibles_.end(); ) { for (auto iter = tangibles_.begin(); iter != tangibles_.end(); ) {
if (iter->second->plane_item_.id() == 0) { if (iter->second->plane_item_.id() == 0) {
@@ -1277,8 +1325,6 @@ void World::deserialize(StreamBuffer *sb) {
++iter; ++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()); assert(stack_is_clear());
} }

View File

@@ -92,7 +92,8 @@ public:
// //
// This flag is set to true when a client is controlling this player. // 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 // 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_; bool is_controlled_;
@@ -129,7 +130,6 @@ class World : public eng::opnew {
public: public:
using IdVector = util::IdVector; using IdVector = util::IdVector;
using TanVector = eng::vector<const Tangible*>; using TanVector = eng::vector<const Tangible*>;
using Redirects = eng::map<int64_t, int64_t>;
const float RadiusVisibility = 1000.0; const float RadiusVisibility = 1000.0;
const float RadiusClose = 1000.0; const float RadiusClose = 1000.0;
@@ -222,45 +222,6 @@ public:
// //
void tangible_delete(int64_t id); 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. // Probe an arbitrary lua expression.
// //
// Any print-statements in the lua code are sent into // 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 // It is legal to mutate a world model without using 'Invoke', but
// only in authoritative world models. // 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. // Get the PrintBuffer of the actor.
// //
@@ -351,6 +316,11 @@ public:
// Serialize and deserialize. // 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 serialize(StreamBuffer *sb);
void deserialize(StreamBuffer *sb); void deserialize(StreamBuffer *sb);
@@ -395,19 +365,6 @@ public:
// //
void lthread_prints_to_actor(int64_t actor_id); 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. // Allocate a single ID.
// //
// The rules are as follows: // The rules are as follows:
@@ -427,7 +384,75 @@ public:
// Otherwise, return. // Otherwise, return.
void guard_nopredict(lua_State *L, const char *fn); 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: private:
////////////////////////////////////////////////////////////////////////////
//
// Invocation and scheduling.
//
////////////////////////////////////////////////////////////////////////////
// Add a thread to the scheduler queue. // Add a thread to the scheduler queue.
// //
void schedule(int64_t clk, int64_t thid, int64_t plid); void schedule(int64_t clk, int64_t thid, int64_t plid);
@@ -533,14 +558,21 @@ public:
public: public:
/////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////
// //
// difference transmission internals related to table comparison // Difference transmission entry point.
//
// 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.
// //
/////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////
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 patch_numbered_tables(StreamBuffer *sb, DebugCollector *dbc);
void diff_numbered_tables(lua_State *master, StreamBuffer *sb); void diff_numbered_tables(lua_State *master, StreamBuffer *sb);
@@ -550,14 +582,6 @@ public:
void patch_tangible_classes(StreamBuffer *sb, DebugCollector *dbc); void patch_tangible_classes(StreamBuffer *sb, DebugCollector *dbc);
void diff_tangible_classes(const IdVector &basis, lua_State *master, StreamBuffer *sb); 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); int64_t patch_actor(StreamBuffer *sb, DebugCollector *dbc);
void diff_actor(int64_t actor_id, World *master, StreamBuffer *sb); void diff_actor(int64_t actor_id, World *master, StreamBuffer *sb);
@@ -576,15 +600,6 @@ public:
void patch_globals(StreamBuffer *sb, DebugCollector *dbc); void patch_globals(StreamBuffer *sb, DebugCollector *dbc);
void diff_globals(World *master, StreamBuffer *sb); 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: public:
/////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////
// //
@@ -640,6 +655,13 @@ public:
void unnumber_lua_tables(); void unnumber_lua_tables();
private: private:
///////////////////////////////////////////////////////////
//
// Instance Variables
//
///////////////////////////////////////////////////////////
// Type of model // Type of model
WorldType world_type_; WorldType world_type_;
@@ -692,9 +714,9 @@ private:
// //
StreamBuffer snapshot_; StreamBuffer snapshot_;
// Redirects. // Connections. A Map from client_id to current actor ID.
// //
Redirects redirects_; std::map<int64_t, int64_t> connections_;
// Storage for wrapper_get_tangibles_near and wrapper_get_animation_queues. // Storage for wrapper_get_tangibles_near and wrapper_get_animation_queues.
// These hold results alive while the driver reads from the raw pointers. // These hold results alive while the driver reads from the raw pointers.
@@ -713,7 +735,6 @@ private:
friend class Tangible; friend class Tangible;
friend int lfn_tangible_animate(lua_State *L); friend int lfn_tangible_animate(lua_State *L);
friend int lfn_tangible_build(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_actor(lua_State *L);
friend int lfn_tangible_place(lua_State *L); friend int lfn_tangible_place(lua_State *L);
friend int lfn_tangible_nopredict(lua_State *L); friend int lfn_tangible_nopredict(lua_State *L);

View File

@@ -10,6 +10,7 @@ function login.init()
global.set("nextplayer", player + 1) global.set("nextplayer", player + 1)
dprint("login.init initializing player ", player) dprint("login.init initializing player ", player)
actor.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}}} tangible.animinit{tan=actor, anim={bp="character", mesh="manny", plane="earth", xyz={player * 100, 0, 90}}}
end end
@@ -24,7 +25,6 @@ end
function engio.move(action, xyz, facing) function engio.move(action, xyz, facing)
-- todo: sanity check the parameters. -- 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}} tangible.animate{tan=actor, anim={action=action, interactive=true, xyz=xyz, facing=facing}}
end end
@@ -33,6 +33,10 @@ function moveto(x, y)
tangible.animate{tan=actor, anim={action="moveto", xyz={x, y, z}, facing=math.auto}} tangible.animate{tan=actor, anim={action="moveto", xyz={x, y, z}, facing=math.auto}}
end end
function login.lookmenu(add)
add("Redirect", function() tangible.redirect(actor, place) end)
end
function cube.lookmenu(add) function cube.lookmenu(add)
add("Cube A", function () dprint("Doing Cube A") end) add("Cube A", function () dprint("Doing Cube A") end)
add("Cube B", function () dprint("Doing Cube B") end) add("Cube B", function () dprint("Doing Cube B") end)

View File

@@ -55,14 +55,14 @@ function engio.pressmenu(label)
return return
end end
local result = nil local menuclosure = nil
local add = function(lbl, closure) local add = function(lbl, closure)
if (lbl == label) then if (lbl == label) then
result = closure menuclosure = closure
end end
end end
class.lookmenu(add) class.lookmenu(add)
if result ~= nil then if menuclosure ~= nil then
result() menuclosure()
end end
end end