#include "world.hpp" #include "idalloc.hpp" #include "animqueue.hpp" #include "gui.hpp" #include "traceback.hpp" #include Tangible::Tangible(World *w, int64_t id) : world_(w), id_player_pool_(&w->id_global_pool_) { plane_item_.set_id(id); w->plane_map_.track(&plane_item_); } 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); } void Tangible::update_plane_item() { util::XYZ xyz = anim_queue_.get_xyz(); std::string plane = anim_queue_.get_plane(); plane_item_.set_pos(plane, xyz.x, xyz.y, 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 Tangible::be_a_player() { if (!id_player_pool_.fifo_enabled()) { id_player_pool_.enable_fifo(); anim_queue_.add(world_->id_global_pool_.get_one(), ""); anim_queue_.set_graphic("player"); 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(); } } void World::init_standalone() { // Load the lua source from disk then rebuild the environment. source_db_.update(); source_db_.rebuild(); // Run unit tests. source_db_.run_unittests(); // Create the player tangible. Tangible *player = tangible_make(state(), 1, false); player->be_a_player(); } 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; } std::vector World::get_near(int64_t player_id, float radius) { Tangible *player = tangible_get(player_id); // Find out where's the center of the world. std::string plane = player->anim_queue_.get_plane(); util::XYZ xyz = player->anim_queue_.get_xyz(); return plane_map_.scan_radius(plane, xyz.x, 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(); } 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; } void World::update_gui(int64_t actor_id, int64_t place_id, Gui *gui) { 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(); } void World::invoke_plan(int64_t actor_id, int64_t place_id, const std::string &action, const GuiResult &gres) { // 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); } void World::run_scheduled_threads(int64_t clk) { 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(); } void World::serialize(StreamBuffer *sb) { 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; } void World::deserialize(StreamBuffer *sb) { 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; } } } void World::snapshot() { snapshot_.clear(); serialize(&snapshot_); } void World::rollback() { assert(!snapshot_.at_eof()); deserialize(&snapshot_); } LuaDefine(tangible_xyz, "c") { LuaArg tanobj; LuaRet X, Y, Z; LuaStack LS(L, tanobj, X, Y, Z); World *w = World::fetch_global_pointer(L); Tangible *tan = w->tangible_get(L, tanobj.index()); util::XYZ xyz = tan->anim_queue_.get_xyz(); LS.set(X, xyz.x); LS.set(Y, xyz.y); LS.set(Z, xyz.z); return LS.result(); } LuaDefine(tangible_plane, "c") { LuaArg tanobj; LuaRet plane; LuaStack LS(L, tanobj, plane); World *w = World::fetch_global_pointer(L); Tangible *tan = w->tangible_get(L, tanobj.index()); LS.set(plane, tan->anim_queue_.get_plane()); 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); tan->anim_queue_.add(id, L, config.index()); tan->update_plane_item(); return LS.result(); } LuaDefine(tangible_make, "c") { World::fetch_global_pointer(L)->tangible_make(L, 0, true); return 1; } 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; }