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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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, "<console>")==0)
{
lua_pushstring(L, "in the console");
if (strcmp(ar.short_src, "<console>")==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);
}

View File

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

View File

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

View File

@@ -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<const Tangible*>;
using Redirects = eng::map<int64_t, int64_t>;
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<int64_t, int64_t> 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);

View File

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

View File

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