#include "world.hpp" #include "idalloc.hpp" #include "animqueue.hpp" #include "gui.hpp" #include "traceback.hpp" #include "pprint.hpp" #include "util.hpp" #include "serializelua.hpp" #include void World::store_global_pointer(lua_State *L, World *v) { lua_pushstring(L, "world"); lua_pushlightuserdata(L, v); lua_rawset(L, LUA_REGISTRYINDEX); } World *World::fetch_global_pointer(lua_State *L) { lua_pushstring(L, "world"); lua_rawget(L, LUA_REGISTRYINDEX); World *result = (World *)lua_touserdata(L, -1); if (result == nullptr) { luaL_error(L, "No world pointer stored."); } lua_pop(L, 1); return result; } World::~World() { } World::World(WorldType wt) { // Master world model by default. world_type_ = wt; // Initialize the ID allocator in master mode. if (is_authoritative()) { id_global_pool_.init_master(); } else { id_global_pool_.init_synch(); } // Prepare to manipulate the lua state. LuaVar world, globtab; LuaExtStack LS(state(), world, globtab); // Put the world pointer into the lua registry. World::store_global_pointer(state(), this); // Clear the global GUI pointer. Gui::store_global_pointer(state(), nullptr); // Clear the lthread state. clear_lthread_state(); // Set the tabletype of the registry. LS.settabletype(LuaRegistry, LUA_TT_REGISTRY); // Set the tabletype of the global environment. LS.getglobaltable(globtab); LS.settabletype(globtab, LUA_TT_GLOBALENV); // Store the world type in the registry. LS.rawset(LuaRegistry, "worldtype", wt); // Create the globaldb in the registry. LS.rawset(LuaRegistry, "globaldb", LuaNewTable); // Initialize the SourceDB. source_db_.init(state()); // Clear the clock. clock_ = 0; // Initialize global variable state. assign_seqno_ = 1; } Tangible::Tangible(World *w, int64_t id) : world_(w), anim_queue_(), id_player_pool_(&w->id_global_pool_) { plane_item_.set_id(id); plane_item_.track(&w->plane_map_); } void Tangible::update_plane_item() { AnimCoreState pos = anim_queue_.get_final_core_state(); plane_item_.set_pos(pos.plane, pos.xyz.x, pos.xyz.y, pos.xyz.z); } void Tangible::serialize(StreamBuffer *sb) { anim_queue_.serialize(sb); id_player_pool_.serialize(sb); print_buffer_.serialize(sb); } void Tangible::deserialize(StreamBuffer *sb) { anim_queue_.deserialize(sb); id_player_pool_.deserialize(sb); print_buffer_.deserialize(sb); update_plane_item(); } Tangible *World::tangible_get(int64_t id) { auto iter = tangibles_.find(id); if (iter == tangibles_.end()) { return nullptr; } else { return iter->second.get(); } } const Tangible *World::tangible_get(int64_t id) const { auto iter = tangibles_.find(id); if (iter == tangibles_.end()) { return nullptr; } else { return iter->second.get(); } } World::TanVector World::tangible_get_all(const IdVector &ids) const { TanVector result(ids.size()); for (int i = 0; i < int(ids.size()); i++) { result[i] = tangible_get(ids[i]); } return result; } Tangible *World::tangible_get(const LuaCoreStack &LS, LuaSlot tab, bool allowdel) { int64_t id = LS.tanid(tab); if (id == 0) { luaL_error(LS.state(), "parameter is not a tangible"); } Tangible *result = tangible_get(id); if (!allowdel) { if (result == nullptr) { luaL_error(LS.state(), "argument is a deleted tangible, which is not allowed here"); } } return result; } Tangible *World::tangible_make(const LuaCoreStack &LS0, LuaSlot database, int64_t id) { assert(id != 0); LuaVar metatab; LuaExtStack LS(LS0.state(), metatab); // Create the C++ part of the structure. UniqueTangible &t = tangibles_[id]; assert (t == nullptr); t.reset(new Tangible(this, id)); // AnimQueue initializes itself to a valid default state. AnimState state; state.add_defaults(nullptr); t->anim_queue_.clear(state); t->update_plane_item(); // Fetch the tangible's Lua database and metatable. LS.maketan(database, id); LS.getmetatable(metatab, database); // Set up the inventory and thread table. LS.rawset(database, "inventory", LuaNewTable); LS.rawset(metatab, "threads", LuaNewTable); return t.get(); } Tangible *World::tangible_make(int64_t id) { LuaVar database; LuaExtStack LS(state(), database); return tangible_make(LS, database, id); } void World::tangible_delete(int64_t id) { lua_State *L = state(); LuaVar tangibles, database, metatab; LuaExtStack LS(L, tangibles, database, metatab); // Fetch the C++ side of the tangible. auto iter = tangibles_.find(id); if (iter == tangibles_.end()) { return; // Nothing to delete. } // Fetch the lua side of the tangible. LS.maketan(database, id); assert(LS.istable(database)); LS.getmetatable(metatab, database); // Clear out the database and the metatable. LS.cleartable(database, false); LS.cleartable(metatab, true); // Now put the bare minimum info back into the metatable. LS.rawset(metatab, "id", id); LS.rawset(metatab, "__metatable", false); // Remove the C++ portion from the tangibles table. tangibles_.erase(iter); } void World::get_near(PlaneScan &scan, util::IdVector *into) const { into->clear(); // If 'near' is set, update the plane and center. int64_t actor_id = scan.near(); if (actor_id != 0) { const Tangible *player = tangible_get(actor_id); if (player == nullptr) { return; } const PlaneItem &pi = player->plane_item_; scan.set_plane(pi.plane()); scan.set_center(util::XYZ(pi.x(), pi.y(), pi.z())); } plane_map_.scan(scan, into); } void World::get_near(int64_t player_id, float radius, bool exclude_nowhere, bool omit_player, bool sorted, util::IdVector *into) const { PlaneScan scan; scan.set_radius(radius); scan.set_shape(PlaneScan::SPHERE); scan.set_sorted(sorted); scan.set_omit_nowhere(exclude_nowhere); scan.set_near(player_id, !omit_player); get_near(scan, into); } void World::get_encoded_animation_queues(uint32_t count, const int64_t *ids, util::SharedStdStringVec &into) { util::SharedStdString empty = std::make_shared(""); into.resize(count); for (int i = 0; i < int(count); i++) { Tangible *tan = tangible_get(ids[i]); if (tan == nullptr) { into[i] = empty; } else { into[i] = tan->anim_queue_.get_encoded_queue(); } } } World::Redirects World::fetch_redirects() { World::Redirects result = std::move(redirects_); redirects_.clear(); return result; } int64_t World::create_login_actor() { assert(stack_is_clear()); int64_t id = id_global_pool_.get_one(); LuaVar database, classtab, mt; LuaExtStack LS(state(), database, classtab, mt); Tangible *tan = tangible_make(LS, database, id); LS.makeclass(classtab, "login"); LS.getmetatable(mt, database); LS.rawset(mt, "__index", classtab); tan->configure_id_pool_for_actor(); tan->print_buffer_.clear(); return tan->id(); } eng::string World::probe_lua(int64_t actor_id, const eng::string &lua) { assert(stack_is_clear()); lua_State *L = state(); Tangible *actor = tangible_get(actor_id); if (actor == nullptr) { return ""; } LuaVar closure; LuaExtStack LS(L, closure); // create the compiled closure. int status = luaL_loadbuffer(L, lua.c_str(), lua.size(), "=probe"); lua_replace(L, closure.index()); if (status != LUA_OK) { // The closure is actually an error message. Do nothing. // This should normally not happen: LuaConsole should filter // out syntax errors. return ""; } // Call the closure. int top = lua_gettop(L); lua_pushvalue(L, closure.index()); open_lthread_state(actor_id, actor_id, 0, false, true); eng::string msg = traceback_pcall(L, 0, LUA_MULTRET); // If there's an error message, print it. // Otherwise, pretty-print the results. std::ostream *ostream = lthread_print_stream(); if (msg.empty()) { for (int i = top + 1; i <= lua_gettop(L); i++) { LuaSpecial root(i); pprint(LS, root, PrettyPrintOptions(), ostream); // TODO: this endl is unnecessary if we just printed a newline. (*ostream) << std::endl; } } else { (*ostream) << msg << std::endl; } // Collect the lthread_prints (and also make sure they // don't go into the printbuffer). eng::string result = lthread_prints_->str(); lthread_prints_.reset(); close_lthread_state(); return result; } void World::update_gui(int64_t actor_id, int64_t place_id, Gui *gui) { assert(stack_is_clear()); gui->clear(place_id); lua_State *L = state(); LuaVar actor, place, ugui, func, tangibles, mt, index; LuaExtStack LS(L, actor, place, ugui, func, tangibles, mt, index); // Get the actor and place. LS.rawget(tangibles, LuaRegistry, "tangibles"); LS.rawget(actor, tangibles, actor_id); LS.rawget(place, tangibles, place_id); if (!LS.istable(actor) || !LS.istable(place)) { return; } // Get the interface closure. LS.getmetatable(mt, place); if (!LS.istable(mt)) { return; } LS.rawget(index, mt, "__index"); if (!LS.istable(index)) { return; } LS.rawget(func, index, "interface"); if (!LS.isfunction(func)) { return; } // Call the interface function. lua_pushvalue(L, func.index()); lua_pushvalue(L, actor.index()); lua_pushvalue(L, place.index()); Gui::store_global_pointer(L, gui); open_lthread_state(actor_id, place_id, 0, false, false); eng::string msg = traceback_pcall(L, 2, 0); close_lthread_state(); Gui::store_global_pointer(L, nullptr); if (!msg.empty()) { gui->clear(0); util::dprint(msg); return; } } void World::update_source(const util::LuaSourcePtr &source) { if (source != nullptr) { update_source(*source); } } // This is called from World::update_source, and also // from World::patch_source in the difference transmitter. // // For the moment, errors are channeled to util::dprint, // and 'print' statements just go to std::cerr. Neither // of these is ideal. We need to get serious about setting // up error handling. // // We also need to figure out a solution for what happens if // some lua source file tries to modify, say, tangible state // in top-level code. // void World::rebuild_sourcedb() { for (const eng::string &mod: source_db_.modules()) { open_lthread_state(0, 0, 0, false, true); eng::string err = source_db_.rebuild_module(mod); eng::string prints = lthread_prints_->str(); lthread_prints_.reset(); close_lthread_state(); if (!err.empty() || !prints.empty()) { util::dprint("Loading Module ", mod); if (!err.empty()) util::dprint(err); if (!prints.empty()) util::dprint(prints); } } } void World::update_source(const util::LuaSourceVec &source) { assert(stack_is_clear()); source_db_.update(source); rebuild_sourcedb(); assert(stack_is_clear()); } void World::http_response(const HttpParser &response) { // Find the request. auto iter = http_requests_.find(response.request_id()); if (iter == http_requests_.end()) { return; } HttpClientRequest request = iter->second; http_requests_.erase(iter); lua_State *CO; { // Get the place and thread as lua objects. LuaVar tangibles, place, mt, threads, thinfo, thread; LuaExtStack LS(state(), tangibles, place, mt, threads, thinfo, thread); LS.rawget(tangibles, LuaRegistry, "tangibles"); LS.rawget(place, tangibles, request.place_id()); if (!LS.istable(place)) { return; } LS.getmetatable(mt, place); if (!LS.istable(mt)) { return; } LS.rawget(threads, mt, "threads"); if (!LS.istable(threads)) { return; } LS.rawget(thinfo, threads, request.thread_id()); if (!LS.istable(thinfo)) { return; } LS.rawget(thread, thinfo, "thread"); if (!LS.isthread(thread)) { return; } CO = LS.ckthread(thread); } // Push the response onto the awakening thread. lua_pushnil(CO); LuaSpecial responsetable(lua_gettop(CO)); LuaCoreStack LSCO(CO); response.store(LSCO, responsetable); // Awaken the thread, with its new return value. schedule(0, request.thread_id(), request.place_id()); run_scheduled_threads(); } void World::http_responses(const HttpParserVec &responses) { for (const HttpParser &response : responses) { http_response(response); } } void World::abort_all_http_requests(int status_code, std::string_view error) { HttpParser abortresponse; abortresponse.fail(status_code, error); while (!http_requests_.empty()) { abortresponse.set_request_id(http_requests_.begin()->first); http_response(abortresponse); } } HttpServerResponse World::http_serve(const HttpParser &request) { assert(stack_is_clear()); HttpServerResponse response; // We're only supposed to be passed complete requests. assert(request.complete()); // If the request is HTTP/1.1, then the response should be HTTP/1.1 response.set_http11(request.http11()); // If the incoming request has already been detected to be // invalid by the HTTP parser, then just send the error // message back to the client without involving lua at all. if (request.errstatus()) { response.fail(request.status(), request.error()); return response; } lua_State *L = state(); LuaVar www, func, reqtab; LuaExtStack LS(L, www, func, reqtab); // Get the www class. If there's no such class, // return a 503 Service Unavailable to the client. eng::string err = LS.getclass(www, "www"); if (!err.empty()) { response.fail(503, "class www doesn't exist"); return response; } // Get the name of the desired function. std::string_view orig_fn = request.first_path_component("index"); eng::string lua_fn = HttpParser::to_lua_identifier(orig_fn); if (lua_fn.empty()) { response.fail(404, util::ss("cannot convert to lua function name: ", orig_fn)); return response; } // Get the closure. If there's no such closure, // return a 404 Not Found to the client. LS.rawget(func, www, lua_fn); if (!LS.isfunction(func)) { response.fail(404, util::ss("no such lua function: www.", lua_fn)); return response; } // Store the request into a lua table. request.store(LS, reqtab); // Call the function. int oldtop = lua_gettop(L); lua_pushvalue(L, func.index()); lua_pushvalue(L, reqtab.index()); Gui::store_global_pointer(L, nullptr); open_lthread_state(0, 0, 0, false, false); eng::string msg = traceback_pcall(L, 1, LUA_MULTRET); close_lthread_state(); // If the call threw an error, return // a 500 Internal Server Error to the client. if (!msg.empty()) { response.fail(500, msg); return response; } // If the call didn't return a single table, return // a 500 Internal Server Error to the client. int newtop = lua_gettop(L); if ((newtop != oldtop + 1) || (!lua_istable(L, newtop))) { response.fail(500, util::ss("lua function www.", lua_fn, " didn't return a table")); return response; } // Try to convert the table into a response. LuaKeywordParser kp(LS, LuaSpecial(newtop)); response.configure(kp); response.set_defaults(); eng::string kperr = kp.final_check(); if (!kperr.empty()) { response.fail(500, kperr); } return response; } void World::run_unittests() { assert(stack_is_clear()); source_db_.run_unittests(); assert(stack_is_clear()); } void World::invoke(const Invocation &inv) { switch (inv.kind()) { case Invocation::KIND_PLAN: invoke_plan(inv.actor(), inv.place(), inv.action(), inv.data()); break; case Invocation::KIND_LUA: invoke_lua(inv.actor(), inv.place(), inv.action(), inv.data()); break; case Invocation::KIND_FLUSH_PRINTS: invoke_flush_prints(inv.actor(), inv.place(), inv.action(), inv.data()); break; case Invocation::KIND_TICK: invoke_tick(inv.actor(), inv.place(), inv.action(), inv.data()); break; case Invocation::KIND_LUA_SOURCE: invoke_lua_source(inv.actor(), inv.place(), inv.action(), inv.data()); break; default: // Do nothing. Standard behavior for any invalid command is to // simply do nothing at all. Perhaps eventually we may add a flag // to the world model to indicate that we've detected an invalid // command, to allow us to close the connection to a client that // is misbehaving. break; } } void World::invoke_flush_prints(int64_t actor_id, int64_t place_id, const eng::string &action, const InvocationData &data) { assert(stack_is_clear()); // Check argument sanity. if (actor_id != place_id) { return; } int64_t line = sv::to_int64(action, -1); if ((line < 0)||(line > INT_MAX)) { return; } Tangible *tactor = tangible_get(actor_id); if (tactor == nullptr) { return; } tactor->print_buffer_.discard_upto(line); assert(stack_is_clear()); } void World::invoke_lua(int64_t actor_id, int64_t place_id, const eng::string &action, const InvocationData &data) { assert(stack_is_clear()); // Make sure that actor and place exist and are not stubs. Tangible *tactor = tangible_get(actor_id); Tangible *tplace = tangible_get(place_id); if ((tactor == nullptr) || (tplace == nullptr)) { return; } // Get a thread ID for the new thread. int64_t tid = tplace->id_player_pool_.get_one(); // Set up for lua manipulation. { lua_State *L = state(); LuaVar func, tangibles, place, mt, thread, thinfo, threads; LuaExtStack LS(L, func, tangibles, place, mt, thread, thinfo, threads); // create the compiled closure. int status = luaL_loadbuffer(L, action.c_str(), action.size(), "=invoke"); lua_replace(L, func.index()); if (status != LUA_OK) { // The closure is actually an error message. Do nothing. // This should normally not happen: LuaConsole should filter // out syntax errors. return; } // Get the place. LS.rawget(tangibles, LuaRegistry, "tangibles"); LS.rawget(place, tangibles, place_id); if (!LS.istable(place)) { return; } // Get the place's metatable. LS.getmetatable(mt, place); if (!LS.istable(mt)) { return; } // Create a new thread, set up function and parameters. lua_State *CO = LS.newthread(thread); lua_pushvalue(L, func.index()); lua_xmove(L, CO, 1); // Create the thread info table. LS.newtable(thinfo); LS.rawset(thinfo, "thread", thread); LS.rawset(thinfo, "actorid", actor_id); LS.rawset(thinfo, "isnew", true); LS.rawset(thinfo, "useppool", true); LS.rawset(thinfo, "print", true); // Store the thread into place's thread table. LS.rawget(threads, mt, "threads"); if (!LS.istable(threads)) { return; } LS.rawset(threads, tid, thinfo); } schedule(0, tid, place_id); run_scheduled_threads(); assert(stack_is_clear()); } void World::invoke_plan(int64_t actor_id, int64_t place_id, const eng::string &action, const InvocationData &data) { assert(stack_is_clear()); // Validate that the action is legal. Gui validation_gui; update_gui(actor_id, place_id, &validation_gui); if (!validation_gui.has_action(action)) { return; } // Make sure the action starts with "cb_" if (!sv::has_prefix(action, "cb_")) { return; } // Get the actor and place. Make sure both exist. Tangible *tactor = tangible_get(actor_id); Tangible *tplace = tangible_get(place_id); if ((tactor == nullptr) || (tplace == nullptr)) { return; } // Get an ID for the thread. We always use the player // pool in this case. int64_t tid = tactor->id_player_pool_.get_one(); { // Set up for Lua manipulation. lua_State *L = state(); LuaVar actor, place, func, tangibles, mt, index, thread, threads, thinfo, message, invdata; LuaExtStack LS(L, actor, place, func, tangibles, mt, index, thread, threads, thinfo, message, invdata); // Get the actor and place. LS.rawget(tangibles, LuaRegistry, "tangibles"); LS.rawget(actor, tangibles, actor_id); LS.rawget(place, tangibles, place_id); if (!LS.istable(actor) || !LS.istable(place)) { return; } // Get the action closure. LS.getmetatable(mt, place); if (!LS.istable(mt)) { return; } LS.rawget(index, mt, "__index"); if (!LS.istable(index)) { return; } LS.rawget(func, index, action); if (!LS.isfunction(func)) { return; } // Convert the InvocationData into a lua table. LS.newtable(invdata); for (const auto &p : data) { LS.rawset(invdata, p.first, p.second); } // Create a new thread, set up function and parameters. lua_State *CO = LS.newthread(thread); lua_pushvalue(L, func.index()); lua_pushvalue(L, actor.index()); lua_pushvalue(L, place.index()); lua_pushvalue(L, invdata.index()); lua_xmove(L, CO, 4); // Create the thread info table. LS.newtable(thinfo); LS.rawset(thinfo, "thread", thread); LS.rawset(thinfo, "actorid", actor_id); LS.rawset(thinfo, "isnew", true); LS.rawset(thinfo, "useppool", true); LS.rawset(thinfo, "print", false); // Store the thread into place's thread table. LS.rawget(threads, mt, "threads"); if (!LS.istable(threads)) { return; } LS.rawset(threads, tid, thinfo); } // Push the thread's ID into the runnable thread queue, // then run the thread queue. schedule(0, tid, place_id); run_scheduled_threads(); assert(stack_is_clear()); } void World::invoke_tick(int64_t actor_id, int64_t place_id, const eng::string &action, const InvocationData &data) { if (!is_authoritative()) { return; } clock_ += 1; run_scheduled_threads(); } void World::invoke_lua_source(int64_t actor_id, int64_t place_id, const eng::string &action, const InvocationData &data) { if (!is_authoritative()) { return; } // We need some kind of authentication here. try { StreamBuffer sb(action); util::LuaSourceVec sv; SourceDB::deserialize_source(&sv, &sb); update_source(sv); } catch (const StreamException &ex) { return; } } void World::guard_blockable(lua_State *L, const char *fn) { if (lthread_thread_id_ == 0) { // in a probe, blocking functions like http.get throw an error. luaL_error(L, "cannot %s in a probe", fn); assert(false); } if (!is_authoritative()) { // in a nonauth model, blocking functions like http.get are converted to nopredict. lua_yield(L, 0); luaL_error(L, "unexplained nopredict failure in %s", fn); assert(false); } } void World::guard_nopredict(lua_State *L, const char *fn) { // Caution: this code must be equivalent to the // code in LuaCoreStack::guard_nopredict. if (lthread_thread_id_ == 0) { return; } if (!is_authoritative()) { lua_yield(L, 0); luaL_error(L, "unexplained nopredict failure in %s", fn); } } void World::schedule(int64_t clk, int64_t thid, int64_t plid) { if (clk > 0) { assert(is_authoritative()); } thread_sched_.add(clk, thid, plid); } void World::run_scheduled_threads() { assert(stack_is_clear()); lua_State *L = state(); LuaVar tangibles, place, mt, threads, thinfo, actorid, isnew, useppool, thread, print; LuaExtStack LS(L, tangibles, place, mt, threads, thinfo, actorid, isnew, useppool, thread, print); LS.rawget(tangibles, LuaRegistry, "tangibles"); while (thread_sched_.ready(clock_)) { SchedEntry sched = thread_sched_.pop(); LS.rawget(place, tangibles, sched.place_id()); if (!LS.istable(place)) { continue; } LS.getmetatable(mt, place); if (!LS.istable(mt)) { continue; } LS.rawget(threads, mt, "threads"); if (!LS.istable(threads)) { continue; } LS.rawget(thinfo, threads, sched.thread_id()); if (!LS.istable(thinfo)) { continue; } LS.rawget(actorid, thinfo, "actorid"); if (!LS.isnumber(actorid)) { continue; } LS.rawget(isnew, thinfo, "isnew"); if (!LS.isboolean(isnew)) { continue; } LS.rawget(useppool, thinfo, "useppool"); if (!LS.isboolean(useppool)) { continue; } LS.rawget(thread, thinfo, "thread"); if (!LS.isthread(thread)) { continue; } // Resume the coroutine. lua_State *CO = LS.ckthread(thread); open_lthread_state(LS.ckinteger(actorid), sched.place_id(), sched.thread_id(), LS.ckboolean(useppool), true); int nargs = LS.ckboolean(isnew) ? (lua_gettop(CO) - 1) : lua_gettop(CO); int status = lua_resume(CO, nullptr, nargs); std::ostream *ostream = lthread_print_stream(); if (status == LUA_OK) { // Successfully ran to completion. Print any return values. // Remove from thread table. LS.rawget(print, thinfo, "print"); LS.rawset(threads, sched.thread_id(), LuaNil); LuaCoreStack LSCO(CO); if (LS.ckboolean(print)) { for (int i = 1; i <= lua_gettop(CO); i++) { pprint(LSCO, LuaSpecial(i), PrettyPrintOptions(), ostream); (*ostream) << std::endl; } } } else if (status == LUA_YIELD) { if (is_authoritative()) { LS.rawset(thinfo, "isnew", false); LS.rawset(thinfo, "useppool", false); } else { // When a nonauthoritative model yields, for any reason, // the thread is discarded. This is also used as a way to implement // nopredict: the thread that wants to 'nopredict' just yields, // knowing that this will cause it to be killed. LS.rawset(threads, sched.thread_id(), LuaNil); } } else { // Generated an error. Add a traceback, print, and kill the coroutine. // Currently, the error is sent to the actor. That seems... not right in the long run. if (is_authoritative()) { traceback_coroutine(CO); (*ostream) << lua_tostring(CO, -1); } LS.rawset(threads, sched.thread_id(), LuaNil); } close_lthread_state(); } } int64_t World::alloc_id_predictable() { if (!lthread_use_ppool_) { return id_global_pool_.get_one(); } Tangible *t = tangible_get(lthread_actor_id_); if (t == nullptr) { return id_global_pool_.get_one(); } return t->id_player_pool_.get_one(); } const PrintBuffer *World::get_printbuffer(int64_t actor_id) { Tangible *actor = tangible_get(actor_id); if (actor != nullptr) { return &actor->print_buffer_; } return nullptr; } void World::clear_lthread_state() { lthread_prints_.reset(); lthread_actor_id_ = 0; lthread_place_id_ = 0; lthread_thread_id_ = 0; lthread_use_ppool_ = false; } void World::open_lthread_state(int64_t actor, int64_t place, int64_t thread, bool ppool, bool prints) { lthread_actor_id_ = actor; lthread_place_id_ = place; lthread_thread_id_ = thread; lthread_use_ppool_ = ppool; if (prints) { lthread_prints_.reset(new eng::ostringstream); } else { lthread_prints_.reset(); } } void World::close_lthread_state() { // Copy prints from lthread_prints_ stringstream into // the appropriate actor's PrintBuffer. If for some reason // there isn't an actor, or if the actor doesn't have a PrintBuffer, // send the output to std::cerr. if (lthread_prints_ != nullptr) { const eng::string &output = lthread_prints_->str(); Tangible *actor = tangible_get(lthread_actor_id_); if (actor != nullptr) { actor->print_buffer_.add_string(output, is_authoritative()); } } // Now clean up everything. clear_lthread_state(); } std::ostream *World::lthread_print_stream() const { if (lthread_prints_ != nullptr) { return lthread_prints_.get(); } else { return &std::cerr; } } void World::set_global(LuaCoreStack &LS0, const eng::string &gvar, LuaSlot value) { lua_State *L = LS0.state(); LuaVar globaldb, copy; LuaExtStack LS(L, globaldb, copy); // Serialize then deserialize the data, to produce a copy. StreamBuffer sb; eng::string error = serialize_lua(LS, value, &sb); if (!error.empty()) { luaL_error(L, "%s", error.c_str()); return; } eng::string serialized(sb.view()); error = deserialize_lua(LS, copy, &sb); if (!error.empty()) { luaL_error(L, "%s", error.c_str()); return; } // Store the copy in the globalDB. LS.rawget(globaldb, LuaRegistry, "globaldb"); LS.rawset(globaldb, gvar, copy); // Store the serialized blob. gvname_to_serial_[gvar] = serialized; // Implement the tracking so that we can rapidly determine which global // variables need to be difference transmitted. // // In the master model, we generate a sequence number for the assignment. // We store the mapping from global variable name to that sequence number // and vice versa. // // On the client side, we just record the global variable in a list // of recently modified globals. // if (is_authoritative()) { int64_t &seqno = gvname_to_seqno_[gvar]; seqno_to_gvname_.erase(seqno); seqno = assign_seqno_++; seqno_to_gvname_[seqno] = gvar; } else { gvname_modified_.insert(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); sb->write_int64(clock_); thread_sched_.serialize(sb); http_requests_.serialize(sb); sb->write_uint32(tangibles_.size()); for (const auto &p : tangibles_) { sb->write_int64(p.first); p.second->serialize(sb); } 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(); thread_sched_.deserialize(sb); http_requests_.deserialize(sb); // Mark all tangibles for deletion by setting ID to zero. for (const auto &p : tangibles_) { p.second->plane_item_.set_id(0); } // Deserialize tangibles. size_t ntan = sb->read_uint32(); for (size_t i = 0; i < ntan; i++) { int64_t id = sb->read_int64(); UniqueTangible &t = tangibles_[id]; if (t == nullptr) { t.reset(new Tangible(this, id)); } else { t->plane_item_.set_id(id); } t->deserialize(sb); } // Delete tangibles that didn't get deserialized. for (auto iter = tangibles_.begin(); iter != tangibles_.end(); ) { if (iter->second->plane_item_.id() == 0) { tangibles_.erase(iter++); } else { ++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()); } void World::snapshot() { assert(snapshot_.empty()); serialize(&snapshot_); assert(!snapshot_.empty()); } void World::rollback() { assert(!snapshot_.empty()); deserialize(&snapshot_); assert(snapshot_.empty()); } // This is the main routine for the DLL. We have to use a registration device // to register this main routine with DrivenEngine. DrivenEngine will then call // it exactly once the first time that the driver initializes an EngineWrapper. // void engine_initialization() { SourceDB::register_lua_builtins(); } static DrivenEngineInitializerReg eireg(engine_initialization);