#include "world.hpp" #include "idalloc.hpp" #include "animqueue.hpp" #include "gui.hpp" #include "traceback.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() { // Initialize the ID allocator in master mode. id_global_pool_.init_master(10); // Prepare to manipulate the lua state. LuaVar world; LuaStack LS(state(), world); // 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); // Create the tangibles table in the registry. LS.rawset(LuaRegistry, "tangibles", LuaNewTable); // Initialize the SourceDB source_db_.init(state()); source_db_.rebuild(); LS.result(); assert (lua_gettop(state()) == 0); } Tangible::Tangible(World *w, int64_t id) : world_(w), id_player_pool_(&w->id_global_pool_) { plane_item_.set_id(id); plane_item_.track(&w->plane_map_); } void Tangible::update_plane_item() { const AnimStep &aqback = anim_queue_.back(); plane_item_.set_pos(aqback.plane(), aqback.xyz().x, aqback.xyz().y, aqback.xyz().z); } void Tangible::serialize(StreamBuffer *sb) { anim_queue_.serialize(sb); id_player_pool_.serialize(sb); } void Tangible::deserialize(StreamBuffer *sb) { anim_queue_.deserialize(sb); id_player_pool_.deserialize(sb); update_plane_item(); } void World::init_standalone() { assert(stack_is_clear()); // Load the lua source from disk then rebuild the environment. source_db_.update(); source_db_.rebuild(); // Run unit tests. source_db_.run_unittests(); assert(stack_is_clear()); } Tangible *World::tangible_get(int64_t id) { auto iter = tangibles_.find(id); if (iter == tangibles_.end()) { return nullptr; } else { return iter->second.get(); } } Tangible *World::tangible_get(lua_State *L, int idx) { Tangible *result = nullptr; int top = lua_gettop(L); if (lua_istable(L, idx)) { lua_getmetatable(L, idx); if (lua_istable(L, -1)) { lua_pushstring(L, "id"); lua_rawget(L, -2); lua_Number id = lua_tonumber(L, -1); result = tangible_get(int64_t(id)); } } lua_settop(L, top); if (result == nullptr) { luaL_error(L, "parameter is not a tangible"); } return result; } void World::tangible_delete(lua_State *L, int64_t id) { LuaVar tangibles, database; LuaStack LS(L, tangibles, database); // Fetch the C++ side of the tangible. auto iter = tangibles_.find(id); if (iter == tangibles_.end()) { luaL_error(L, "Not a tangible: %lld", id); } // Fetch the lua side of the tangible. LS.rawget(tangibles, LuaRegistry, "tangibles"); LS.rawget(database, tangibles, id); assert(LS.istable(database)); // Clear out the database. LS.clearmetatable(database); LS.cleartable(database); // Remove the lua portion from the table. LS.rawset(tangibles, id, LuaNil); // Remove the C++ portion from the table. tangibles_.erase(iter); LS.result(); } std::vector World::get_near(int64_t player_id, float radius, bool exclude_nowhere) { Tangible *player = tangible_get(player_id); // Find out where's the center of the world. const AnimStep &aqback = player->anim_queue_.back(); if (exclude_nowhere && (aqback.plane() == "nowhere")) { return std::vector(); } return plane_map_.scan_radius(aqback.plane(), aqback.xyz().x, aqback.xyz().y, radius, player_id); } Tangible *World::tangible_make(lua_State *L, int64_t id, bool pushdb) { LuaVar tangibles, metatab; LuaRet database; LuaStack LS(L, tangibles, database, metatab); // Allocate an ID if we don't already have one. if (id == 0) id = id_global_pool_.alloc_id_for_thread(L); // Create the C++ part of the structure. std::unique_ptr &t = tangibles_[id]; assert (t == nullptr); t.reset(new Tangible(this, id)); // Create the tangible's database and metatable. LS.set(database, LuaNewTable); LS.set(metatab, LuaNewTable); LS.setmetatable(database, metatab); // Store the database into the tangibles table. LS.rawget(tangibles, LuaRegistry, "tangibles"); LS.rawset(tangibles, id, database); // Populate the database and metatable with initial stuff. LS.rawset(database, "inventory", LuaNewTable); LS.rawset(metatab, "id", id); LS.rawset(metatab, "threads", LuaNewTable); // LS.rawset(metatab, "__metatable", LuaNil); LS.result(); if (!pushdb) lua_pop(L, 1); return t.get(); } World::Redirects World::fetch_redirects() { World::Redirects result = std::move(redirects_); redirects_.clear(); return std::move(result); } // void Tangible::be_a_player() { // if (!id_player_pool_.fifo_enabled()) { // id_player_pool_.enable_fifo(); // AnimStep asinit; // asinit.set_graphic("player"); // anim_queue_.add(world_->id_global_pool_.get_one(), asinit); // anim_queue_.keep_only(1); // update_plane_item(); // LuaVar classtab, mt, place, tangibles; // LuaStack LS(world_->state(), classtab, mt, place, tangibles); // LS.makeclass(classtab, "player"); // LS.rawget(tangibles, LuaRegistry, "tangibles"); // LS.rawget(place, tangibles, id()); // LS.getmetatable(mt, place); // LS.rawset(mt, "__index", classtab); // LS.result(); // } // } int64_t World::create_login_actor() { Tangible *tan = tangible_make(state(), 0, true); LuaArg database; LuaVar classtab, mt; LuaStack LS(state(), database, classtab, mt); LS.makeclass(classtab, "login"); LS.getmetatable(mt, database); LS.rawset(mt, "__index", classtab); LS.result(); tan->id_player_pool_.enable_fifo(); assert(stack_is_clear()); return tan->id(); } void World::update_gui(int64_t actor_id, int64_t place_id, Gui *gui) { assert(stack_is_clear()); gui->clear(); lua_State *L = state(); LuaVar actor, place, ugui, func, tangibles, mt, index; LuaStack 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)) { LS.result(); return; } // Get the interface closure. LS.getmetatable(mt, place); if (!LS.istable(mt)) { LS.result(); return; } LS.rawget(index, mt, "__index"); if (!LS.istable(index)) { LS.result(); return; } LS.rawget(func, index, "interface"); if (!LS.isfunction(func)) { LS.result(); 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); int status = traceback_pcall(L, 2, 0); Gui::store_global_pointer(L, nullptr); if (status != 0) { gui->clear(); std::cerr << lua_tostring(L, -1); LS.result(); return; } // And we're done. LS.result(); assert(stack_is_clear()); } void World::invoke_plan(int64_t actor_id, int64_t place_id, const std::string &action, const GuiResult &gres) { 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; } // Get an ID batch for the thread, and take one for the thread itself. Tangible *tactor = tangible_get(actor_id); int64_t id_batch = tactor->id_player_pool_.get_batch(); int64_t tid = id_batch++; // Set up for Lua manipulation. lua_State *L = state(); LuaVar actor, place, func, tangibles, mt, index, actions, thread, threads, message, guires; LuaStack LS(L, actor, place, func, tangibles, mt, index, actions, thread, threads, message, guires); // 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)) { LS.result(); return; } // Get the action closure. LS.getmetatable(mt, place); if (!LS.istable(mt)) { LS.result(); return; } LS.rawget(index, mt, "__index"); if (!LS.istable(index)) { LS.result(); return; } LS.rawget(actions, index, "action"); if (!LS.istable(actions)) { LS.result(); return; } LS.rawget(func, actions, action); if (!LS.isfunction(func)) { LS.result(); return; } // Convert the GuiResult into a lua table. LS.newtable(guires); for (const auto &p : gres) { LS.rawset(guires, 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, guires.index()); lua_xmove(L, CO, 4); // Store the thread into place's thread table. LS.rawget(threads, mt, "threads"); if (!LS.istable(threads)) { LS.result(); return; } LS.rawset(threads, tid, thread); LS.result(); // Push the thread's ID into the runnable thread queue, // then run the thread queue. thread_sched_.add(0, tid, place_id); run_scheduled_threads(0); assert(stack_is_clear()); } void World::run_scheduled_threads(int64_t clk) { assert(stack_is_clear()); lua_State *L = state(); LuaVar tangibles, place, mt, threads, thread; LuaStack LS(L, tangibles, place, mt, threads, thread); LS.rawget(tangibles, LuaRegistry, "tangibles"); while (thread_sched_.ready(clk)) { 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(thread, threads, sched.thread_id()); if (!LS.isthread(thread)) { continue; } // Resume the coroutine. lua_State *CO = LS.ckthread(thread); int top = lua_gettop(CO); int status = lua_resume(CO, nullptr, (top > 0) ? (top - 1) : 0); // Three possible outcomes: finished, yielded, or errored. if (status == LUA_YIELD) { // When the wait statement yields, it yields the desired timestamp. if ((lua_gettop(CO) != 1) || (!lua_isnumber(CO, 1))) { std::cerr << "Thread yielded incorrectly. Killing it." << std::endl; LS.rawset(threads, sched.thread_id(), LuaNil); } else { lua_Number delay = lua_tonumber(CO, 1); lua_settop(CO, 0); std::cerr << "Thread wait = " << delay << std::endl; thread_sched_.add(sched.clock() + int64_t(delay), sched.thread_id(), sched.place_id()); std::cerr << "Added to schedule." << std::endl; } } else if (status == 0) { // Successfully ran to completion. Remove from thread table. std::cerr << "Thread ran to completion." << std::endl; LS.rawset(threads, sched.thread_id(), LuaNil); } else { // Generated an error. Add a traceback, print, and kill the coroutine. traceback_coroutine(CO); std::cerr << lua_tostring(CO, -1); LS.rawset(threads, sched.thread_id(), LuaNil); } } LS.result(); assert(stack_is_clear()); } void World::serialize(StreamBuffer *sb) { assert(stack_is_clear()); assert(redirects_.empty()); int64_t wc0 = sb->write_count(); lua_snap_.serialize(sb); id_global_pool_.serialize(sb); thread_sched_.serialize(sb); sb->write_size(tangibles_.size()); for (const auto &p : tangibles_) { sb->write_int64(p.first); p.second->serialize(sb); } int64_t wc1 = sb->write_count(); std::cerr << "World serialized to " << wc1-wc0 << " bytes." << std::endl; assert(stack_is_clear()); } void World::deserialize(StreamBuffer *sb) { assert(stack_is_clear()); redirects_.clear(); lua_snap_.deserialize(sb); id_global_pool_.deserialize(sb); thread_sched_.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_size(); for (size_t i = 0; i < ntan; i++) { int64_t id = sb->read_int64(); std::unique_ptr &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; } } assert(stack_is_clear()); } void World::snapshot() { snapshot_.clear(); serialize(&snapshot_); } void World::rollback() { assert(!snapshot_.at_eof()); deserialize(&snapshot_); } LuaDefine(tangible_animstate, "c") { LuaArg tanobj; LuaRet graphic, plane, x, y, z, facing; LuaStack LS(L, tanobj, graphic, plane, x, y, z, facing); World *w = World::fetch_global_pointer(L); Tangible *tan = w->tangible_get(L, tanobj.index()); const AnimStep &aqback = tan->anim_queue_.back(); LS.set(graphic, aqback.graphic()); LS.set(plane, aqback.plane()); LS.set(x, aqback.xyz().x); LS.set(y, aqback.xyz().y); LS.set(z, aqback.xyz().z); LS.set(facing, aqback.facing()); return LS.result(); } LuaDefine(tangible_animate, "c") { LuaArg tanobj, config; LuaStack LS(L, tanobj, config); World *w = World::fetch_global_pointer(L); Tangible *tan = w->tangible_get(L, tanobj.index()); int64_t id = w->id_global_pool_.alloc_id_for_thread(L); AnimStep step; step.from_lua(L, config.index()); if (step.action() == "") { luaL_error(L, "animation action must be specified"); } tan->anim_queue_.add(id, step); tan->update_plane_item(); return LS.result(); } LuaDefine(tangible_setclass, "c") { LuaArg tanobj, classname; LuaVar classtab, mt; LuaStack LS(L, tanobj, classname, classtab, mt); World *w = World::fetch_global_pointer(L); w->tangible_get(L, tanobj.index()); LS.getclass(classtab, classname); LS.getmetatable(mt, tanobj); LS.rawset(mt, "__index", classtab); return LS.result(); } LuaDefine(tangible_delete, "c") { // TODO: we need some sanity checks to make sure you're not deleting // the current player, or anything like that. LuaArg tanobj; LuaStack LS(L, tanobj); World *w = World::fetch_global_pointer(L); Tangible *tan = w->tangible_get(L, tanobj.index()); w->tangible_delete(L, tan->id()); return LS.result(); } LuaDefine(tangible_build, "c") { LuaArg config; LuaVar classname, classtab, mt; LuaRet database; LuaStack LS(L, config, classname, classtab, database, mt); LS.checktable(config); // Get the class of the new tangible. LS.rawget(classname, config, "class"); if (LS.isnil(classname)) { luaL_error(L, "must specify a class name"); } LS.getclass(classtab, classname); // Parse the initial animation step. AnimStep initstep; initstep.from_lua(L, config.index()); if (!initstep.has_xyz()) { luaL_error(L, "You must specify (X,Y,Z) for new tangible"); } if (!initstep.has_plane()) { luaL_error(L, "You must specify plane for new tangible"); } if (!initstep.has_graphic()) { luaL_error(L, "You must specify graphic for new tangible"); } // TODO: generate error if there's extra crap in the config table. World *w = World::fetch_global_pointer(L); Tangible *tan = w->tangible_make(L, 0, true); lua_replace(L, database.index()); // Update the class of the new tangible. LS.getmetatable(mt, database); LS.rawset(mt, "__index", classtab); // Update the animation queue and planemap of the new tangible. int64_t stepid = w->id_global_pool_.alloc_id_for_thread(L); tan->anim_queue_.add(stepid, initstep); tan->update_plane_item(); return LS.result(); } LuaDefine(tangible_get, "c") { LuaArg id; LuaVar tangibles; LuaRet database; LuaStack LS(L, id, tangibles, database); int64_t nid = LS.ckinteger(id); LS.rawget(tangibles, LuaRegistry, "tangibles"); LS.rawget(database, tangibles, id); if (!LS.istable(database)) { luaL_error(L, "Not a tangible ID: %d", nid); } return LS.result(); } LuaDefine(tangible_redirect, "c") { LuaArg actor1, actor2; LuaStack LS(L, actor1, actor2); World *w = World::fetch_global_pointer(L); Tangible *tan1 = w->tangible_get(L, actor1.index()); Tangible *tan2 = w->tangible_get(L, actor2.index()); w->redirects_[tan1->id()] = tan2->id(); return LS.result(); } LuaDefine(world_wait, "f") { if ((lua_gettop(L) != 1) || (lua_type(L, -1) != LUA_TNUMBER)) { luaL_error(L, "Argument to wait must be a number."); } return lua_yield(L, 1); } LuaDefine(world_getregistry, "f") { lua_pushvalue(L, LUA_REGISTRYINDEX); return 1; }