diff --git a/luprex/Makefile b/luprex/Makefile index d6bbcbae..c03fa847 100644 --- a/luprex/Makefile +++ b/luprex/Makefile @@ -11,6 +11,7 @@ CPP_FILES=\ cpp/globaldb.cpp\ cpp/table.cpp\ cpp/gui.cpp\ + cpp/luasnap.cpp\ cpp/animqueue.cpp\ cpp/source.cpp\ cpp/world.cpp\ diff --git a/luprex/cpp/luasnap.cpp b/luprex/cpp/luasnap.cpp new file mode 100644 index 00000000..0335daf8 --- /dev/null +++ b/luprex/cpp/luasnap.cpp @@ -0,0 +1,195 @@ + +#include "luasnap.hpp" +#include +#include + +extern "C" { +void *lj_alloc_create(); +void *lj_alloc_f(void *, void *, size_t, size_t); +lua_State *lj_state_newstate(lua_Alloc f, void *ud); +} + +static void *lsnap_create() { + return lj_alloc_create(); +} +static lua_State *lsnap_newstate(lua_Alloc f, void *ud) { + return lj_state_newstate(f, ud); +} +static void *lsnap_malloc(void *mptr, size_t nsize) { + return lj_alloc_f(mptr, 0, 0, nsize); +} +static void *lsnap_realloc(void *mptr, void *ptr, size_t osize, size_t nsize) { + return lj_alloc_f(mptr, ptr, osize, nsize); +} +static void *lsnap_free(void *mptr, size_t osize, void *ptr) { + return lj_alloc_f(mptr, ptr, osize, 0); +} + +struct BlockHeader { + BlockHeader *prev_; + BlockHeader *next_; + int32_t sanity_; // 0x12345678 only when linked. + size_t size_; + bool in_use_; + void *snapshot_; +}; + +size_t blocksize(size_t base) { + return base + sizeof(BlockHeader); +} + +BlockHeader *pointer_to_header(void *ptr) { + return (BlockHeader*)(((char *)ptr) - sizeof(BlockHeader)); +} + +void *header_to_pointer(BlockHeader *blk) { + return (void *)(((char *)blk) + sizeof(BlockHeader)); +} + +class LuaSnapData { +public: + lua_State *state_; + bool have_snapshot_; + void *lj_mstate_; + BlockHeader sentinel_; + + LuaSnapData(); + ~LuaSnapData(); + lua_State *state() const { return state_; } + bool have_snapshot() const { return have_snapshot_; } + void snapshot(); + void rollback(); + void link(BlockHeader *blk); + void unlink(BlockHeader *blk); + void *allocf(void *ptr, size_t osize, size_t nsize); +}; + +void *lsd_alloc_f(void *lsd, void *ptr, size_t osize, size_t nsize) { + return ((LuaSnapData*)lsd)->allocf(ptr, osize, nsize); +} + +LuaSnapData::LuaSnapData() { + sentinel_.prev_ = &sentinel_; + sentinel_.next_ = &sentinel_; + sentinel_.size_ = 0; + sentinel_.in_use_ = false; + sentinel_.snapshot_ = 0; + lj_mstate_ = lsnap_create(); + have_snapshot_ = false; + state_ = lsnap_newstate(lsd_alloc_f, (void*)this); +} + +LuaSnapData::~LuaSnapData() { + std::cerr << "LuaSnapData destructor not implemented." << std::endl; + exit(1); +} + +void LuaSnapData::snapshot() { + assert(!have_snapshot_); + for (BlockHeader *blk = sentinel_.next_; blk != &sentinel_; blk = blk->next_) { + assert(blk->in_use_); + blk->snapshot_ = malloc(blk->size_); + memcpy(blk->snapshot_, header_to_pointer(blk), blk->size_); + } + have_snapshot_ = true; +} + +void LuaSnapData::rollback() { + assert(have_snapshot_); + for (BlockHeader *blk = sentinel_.next_; blk != &sentinel_; ) { + BlockHeader *next = blk->next_; + if (blk->snapshot_) { + memcpy(header_to_pointer(blk), blk->snapshot_, blk->size_); + free(blk->snapshot_); + blk->snapshot_ = nullptr; + blk->in_use_ = true; + } else { + assert(blk->in_use_); + unlink(blk); + lsnap_free(lj_mstate_, blocksize(blk->size_), blk); + } + blk = next; + } + have_snapshot_ = false; +} + +void LuaSnapData::link(BlockHeader *blk) { + blk->prev_ = sentinel_.prev_; + blk->next_ = &sentinel_; + blk->prev_->next_ = blk; + blk->next_->prev_ = blk; + blk->sanity_ = 0x12345678; +} + +void LuaSnapData::unlink(BlockHeader *blk) { + blk->prev_->next_ = blk->next_; + blk->next_->prev_ = blk->prev_; + blk->prev_ = nullptr; + blk->next_ = nullptr; + blk->sanity_ = 0; +} + +void *LuaSnapData::allocf(void *ptr, size_t osize, size_t nsize) { + if (nsize == 0) { + if (ptr == 0) return nullptr; + BlockHeader *blk = pointer_to_header(ptr); + assert(blk->sanity_ = 0x12345678); + assert(blk->in_use_); + if (blk->snapshot_ == nullptr) { + unlink(blk); + lsnap_free(lj_mstate_, blocksize(blk->size_), blk); + } else { + blk->in_use_ = false; + } + return nullptr; + } else if (ptr == NULL) { + BlockHeader *blk = (BlockHeader*)lsnap_malloc(lj_mstate_, blocksize(nsize)); + link(blk); + blk->size_ = nsize; + blk->in_use_ = true; + blk->snapshot_ = nullptr; + return header_to_pointer(blk); + } else { + BlockHeader *blk = pointer_to_header(ptr); + assert(blk->sanity_ = 0x12345678); + assert(blk->in_use_); + if (blk->snapshot_ == nullptr) { + unlink(blk); + blk = (BlockHeader*)lsnap_realloc(lj_mstate_, blk, blocksize(blk->size_), blocksize(nsize)); + blk->size_ = nsize; + link(blk); + return header_to_pointer(blk); + } else { + BlockHeader *nblk = (BlockHeader*)lsnap_malloc(lj_mstate_, blocksize(nsize)); + memcpy(nblk, blk, (blk->size_ < nsize) ? blk->size_ : nsize); + blk->in_use_ = false; + link(nblk); + nblk->size_ = nsize; + nblk->in_use_ = true; + nblk->snapshot_ = nullptr; + return header_to_pointer(nblk); + } + } +} + + +LuaSnap::LuaSnap() { + data_ = new LuaSnapData; + state_ = data_->state(); +} + +LuaSnap::~LuaSnap() { + delete data_; +} + +bool LuaSnap::have_snapshot() const { + return data_->have_snapshot(); +} + +void LuaSnap::snapshot() { + data_->snapshot(); +} + +void LuaSnap::rollback() { + data_->rollback(); +} diff --git a/luprex/cpp/luasnap.hpp b/luprex/cpp/luasnap.hpp new file mode 100644 index 00000000..1ed28396 --- /dev/null +++ b/luprex/cpp/luasnap.hpp @@ -0,0 +1,60 @@ +//////////////////////////////////////////////////////////// +// +// LUASNAP +// +// A lua interpreter that can be checkpointed (snapshotted). +// This makes it possible to roll the entire interpreter back +// to a previously-snapshotted state. +// +// To accomplish this, we take advantage of the 'allocf' parameter +// to lua_newstate. This lets us hook the lua allocator, which +// in turn lets us find every block of memory currently in use by +// lua. To snapshot, we 'memcpy' every block of memory +// that lua is using into a buffer. To rollback, we just 'memcpy' +// the data back. +// +// The current implementation is a proof-of-concept. It's quite +// wasteful of memory, roughly doubling the amount of RAM that +// LUA uses. But it does demonstrate that this method of snapshot +// and rollback is feasible. +// +//////////////////////////////////////////////////////////// + +#ifndef LUASNAP_HPP +#define LUASNAP_HPP + +#include "luastack.hpp" + +class LuaSnapData; +class LuaSnap { +private: + lua_State *state_; + LuaSnapData *data_; + +public: + LuaSnap(); + ~LuaSnap(); + + // Get the lua intepreter. + // + lua_State *state() const { return state_; } + + // Return true if there's a saved snapshot. + // + bool have_snapshot() const; + + // snapshot the state of the lua interpreter. + // + // If there is already a snapshot, this panics. + // + void snapshot(); + + // Rollback the lua intepreter to the snapshotted state. + // + // If there is no snapshot, this panics. + // + void rollback(); +}; + + +#endif // LUASNAP_HPP diff --git a/luprex/cpp/luastack.cpp b/luprex/cpp/luastack.cpp index fbfe1772..e215246e 100644 --- a/luprex/cpp/luastack.cpp +++ b/luprex/cpp/luastack.cpp @@ -38,17 +38,20 @@ int LuaStack::collect_tagged_pointer(lua_State *L) { } void LuaStack::register_all_userdata(lua_State *L) { - LuaVar tab, lud; - LuaStack LS(L, tab, lud); + LuaVar tab, lud, classtab, classname; + LuaStack LS(L, tab, lud, classtab, classname); auto regs = LuaFunctionReg::all(); for (const LuaFunctionReg *r : regs) { const std::string &name = util::tolower(r->get_name()); lua_CFunction tag = r->get_func(); std::string mode = r->get_mode(); + LS.set(classname, name); if (mode.find('t') != std::string::npos) { // Register type LS.newtable(tab); LS.setfield(tab, "type", name); LS.setfield(tab, "__gc", collect_tagged_pointer); + LS.makeclass(classtab, classname); + LS.setfield(tab, "__index", classtab); LS.setlightuserdata(lud, (void *)tag); LS.rawset(LuaRegistry, lud, tab); } @@ -202,6 +205,37 @@ void LuaStack::newtable(LuaSlot target) const { lua_replace(L_, target); } +void LuaStack::makeclass(LuaSlot classtab, LuaSlot classname) const { + int top = lua_gettop(L_); + checkstring(classname); + + // Special case: if the classname is _G, return global env. + lua_pushstring(L_, "_G"); + int eqlg = lua_equal(L_, -1, classname.index()); + lua_settop(L_, top); + if (eqlg) { + set(classtab, LuaGlobals); + return; + } + + // Get the classtab from the global environment. + // Create it if it doesn't exist. + rawget(classtab, LuaGlobals, classname); + if (isnil(classtab)) { + newtable(classtab); + rawset(LuaGlobals, classname, classtab); + } + + // If the name isn't bound to a table, abort. + if (!istable(classtab)) { + luaL_error(L_, "%s is not a class", ckstring(classname).c_str()); + } + + // Repair the special fields. + setfield(classtab, "__index", classtab); + setfield(classtab, "__class", classname); +} + void LuaStack::setlightuserdata(LuaSlot target, void *p) const { lua_pushlightuserdata(L_, p); lua_replace(L_, target); diff --git a/luprex/cpp/luastack.hpp b/luprex/cpp/luastack.hpp index 066cdfcb..9b236da7 100644 --- a/luprex/cpp/luastack.hpp +++ b/luprex/cpp/luastack.hpp @@ -425,6 +425,9 @@ public: void checknometa(LuaSlot index) const; void newtable(LuaSlot target) const; + + void makeclass(LuaSlot tab, LuaSlot name) const; + void setlightuserdata(LuaSlot target, void *p) const; template diff --git a/luprex/cpp/source.cpp b/luprex/cpp/source.cpp index b143a777..ec54734f 100644 --- a/luprex/cpp/source.cpp +++ b/luprex/cpp/source.cpp @@ -34,42 +34,9 @@ util::stringvec read_control_lst(const std::string &path) { LuaDefine(source_makeclass, "f") { LuaArg classname; - LuaVar action, gname; LuaRet classtab; - LuaStack LS(L, classname, classtab, action, gname); - - LS.checkstring(classname); - - // Special case: if the classname is _G, return global env. - LS.set(gname, "_G"); - if (LS.equal(classname, gname)) { - LS.set(classtab, LuaGlobals); - return LS.result(); - } - - // Get the classtab from the global environment. - // Create it if it doesn't exist. - LS.rawget(classtab, LuaGlobals, classname); - if (LS.isnil(classtab)) { - LS.newtable(classtab); - LS.rawset(LuaGlobals, classname, classtab); - } - - // If the name isn't bound to a table, abort. - if (!LS.istable(classtab)) { - luaL_error(L, "%s is not a class", LS.ckstring(classname).c_str()); - } - - // Repair the special fields. - LS.setfield(classtab, "__index", classtab); - LS.setfield(classtab, "__class", classname); - - // Repair the action table. - LS.getfield(action, classtab, "action"); - if (!LS.istable(action)) { - LS.setfield(classtab, "action", LuaNewTable); - } - + LuaStack LS(L, classname, classtab); + LS.makeclass(classtab, classname); return LS.result(); } @@ -200,26 +167,17 @@ void SourceDB::update() { } // Delete everything from the global environment except -// the class tables and the class action tables. +// the class tables. // static void source_clear_globals(lua_State *L) { - LuaVar classname, classtab, action, key; - LuaStack LS(L, classname, classtab, action, key); + LuaVar classname, classtab, key; + LuaStack LS(L, classname, classtab, key); LS.setfield(LuaGlobals, "_G", LuaNil); LS.set(classname, LuaNil); while (LS.next(LuaGlobals, classname, classtab) != 0) { if (LS.istable(classtab)) { - bool keep_action = false; - LS.getfield(action, classtab, "action"); - if (LS.istable(action)) { - LS.call(table_clear, action); - keep_action = true; - } LS.call(table_clear, classtab); - if (keep_action) { - LS.setfield(classtab, "action", action); - } } else { LS.rawset(LuaGlobals, classname, LuaNil); } @@ -239,7 +197,7 @@ static void source_restore_builtins(lua_State *L) { LS.set(key, LuaNil); while (LS.next(snapshot, key, value) != 0) { LS.checktable(value); - LS.call(subglobal, source_makeclass, key); + LS.makeclass(subglobal, key); LS.set(skey, LuaNil); while (LS.next(value, skey, svalue) != 0) { LS.rawset(subglobal, skey, svalue); diff --git a/luprex/cpp/textgame.cpp b/luprex/cpp/textgame.cpp index 952b7fca..71fb971e 100644 --- a/luprex/cpp/textgame.cpp +++ b/luprex/cpp/textgame.cpp @@ -43,9 +43,8 @@ static void l_message(const char *msg) fflush(stderr); } - void TextGame::do_lua(const std::string &exp) { - lua_State *L = viewer_.get_lua_state(); + lua_State *L = viewer_.state(); int status = luaL_loadbuffer(L, exp.c_str(), exp.size(), "=stdin"); assert(status == LUA_OK); globalL = L; @@ -119,6 +118,22 @@ void TextGame::do_choose_command(const StringVec &cmd) { std::cerr << "Choose command (index " << index << ") not implemented yet." << std::endl; } +void TextGame::do_snapshot_command(const StringVec &cmd) { + if (cmd.size() != 1) { + std::cerr << "s command (snapshot) takes no arguments" << std::endl; + return; + } + viewer_.snapshot(); +} + +void TextGame::do_rollback_command(const StringVec &cmd) { + if (cmd.size() != 1) { + std::cerr << "r command (rollback) takes no arguments" << std::endl; + return; + } + viewer_.rollback(); +} + void TextGame::do_quit_command(const StringVec &cmd) { if (cmd.size() != 1) { std::cerr << "q command (quit) takes no arguments" << std::endl; @@ -133,6 +148,8 @@ void TextGame::do_command(const StringVec &words) { case 'm': do_menu_command(words); break; case 'c': do_choose_command(words); break; case 'q': do_quit_command(words); break; + case 's': do_snapshot_command(words); break; + case 'r': do_rollback_command(words); break; default: std::cerr << "Unknown command: " << words[0] << std::endl; } diff --git a/luprex/cpp/textgame.hpp b/luprex/cpp/textgame.hpp index a4739e62..859302f8 100644 --- a/luprex/cpp/textgame.hpp +++ b/luprex/cpp/textgame.hpp @@ -17,7 +17,9 @@ private: void do_menu_command(const StringVec &cmd); void do_choose_command(const StringVec &cmd); void do_quit_command(const StringVec &cmd); - + void do_snapshot_command(const StringVec &cmd); + void do_rollback_command(const StringVec &cmd); + void do_lua(const std::string &exp); void do_command(const StringVec &exp); public: diff --git a/luprex/cpp/viewer.hpp b/luprex/cpp/viewer.hpp index 69d6d9dd..85f7c5fa 100644 --- a/luprex/cpp/viewer.hpp +++ b/luprex/cpp/viewer.hpp @@ -16,9 +16,13 @@ public: Viewer(); ~Viewer(); + // Snapshot/rollback the lua state (temporary hack) + void snapshot() { world_->lua_snap_.snapshot(); } + void rollback() { world_->lua_snap_.rollback(); } + // Get the lua state for interaction. // - lua_State *get_lua_state() { return world_->get_lua_state(); } + lua_State *state() { return world_->state(); } // Get the player ID of the current player. // diff --git a/luprex/cpp/world.cpp b/luprex/cpp/world.cpp index 85381153..55b77394 100644 --- a/luprex/cpp/world.cpp +++ b/luprex/cpp/world.cpp @@ -15,22 +15,15 @@ World::~World() { } World::World() { - // Create the lua state. - lua_state_ = lua_open(); - if (lua_state_ == nullptr) { - std::cerr << "Cannot create lua state." << std::endl; - exit(1); - } - // Initialize the userdata metatables. - LuaStack::register_all_userdata(lua_state_); + LuaStack::register_all_userdata(state()); // Initialize the ID allocator in master mode. id_global_pool_.init_master(10); // Prepare to manipulate the lua state. LuaVar world; - LuaStack LS(lua_state_, world); + LuaStack LS(state(), world); // Put the world pointer into the lua registry. LS.newpointer(world, this, false); @@ -40,11 +33,11 @@ World::World() { LS.setfield(LuaRegistry, "tangibles", LuaNewTable); // Initialize the SourceDB - source_db_.initialize(lua_state_); + source_db_.initialize(state()); source_db_.rebuild(); LS.result(); - assert (lua_gettop(lua_state_) == 0); + assert (lua_gettop(state()) == 0); } void Tangible::be_a_player() { @@ -53,6 +46,16 @@ void Tangible::be_a_player() { 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.call(classtab, source_makeclass, "player"); + LS.getfield(tangibles, LuaRegistry, "tangibles"); + LS.rawget(place, tangibles, anim_queue_.get_id()); + LS.getmetatable(mt, place); + LS.setfield(mt, "__index", classtab); + LS.result(); } } @@ -65,7 +68,7 @@ void World::init_standalone() { source_db_.run_unittests(); // Create the player tangible. - Tangible *player = tangible_make(lua_state_, 1, false); + Tangible *player = tangible_make(state(), 1, false); player->be_a_player(); } @@ -135,7 +138,7 @@ World *World::fetch(lua_State *L) { void World::update_gui(int64_t actor_id, int64_t place_id, Gui *gui) { gui->clear(); - lua_State *L = get_lua_state(); + lua_State *L = state(); LuaVar actor, place, ugui, func, tangibles; LuaStack LS(L, actor, place, ugui, func, tangibles); @@ -179,8 +182,6 @@ void World::update_gui(int64_t actor_id, int64_t place_id, Gui *gui) { LS.result(); } - - LuaDefine(tangible_get, "c") { LuaArg id; LuaRet database; diff --git a/luprex/cpp/world.hpp b/luprex/cpp/world.hpp index 45a03f72..33f845fe 100644 --- a/luprex/cpp/world.hpp +++ b/luprex/cpp/world.hpp @@ -8,6 +8,7 @@ #include "animqueue.hpp" #include "source.hpp" #include "gui.hpp" +#include "luasnap.hpp" #include #include @@ -43,10 +44,10 @@ public: class World { public: - // A pointer to the lua State. + // A lua intepreter with snapshot function. // - lua_State *lua_state_; - + LuaSnap lua_snap_; + // The Global ID Pool. // IdGlobalPool id_global_pool_; @@ -81,7 +82,7 @@ public: // // Get the lua interpreter associated with this world model. // - lua_State *get_lua_state() { return lua_state_; } + lua_State *state() { return lua_snap_.state(); } // get_near //