#include "wrap-sstream.hpp" #include "wrap-string.hpp" #include "luasnap.hpp" #include "luastack.hpp" #include "streambuffer.hpp" #include LuaSnap::LuaSnap() { state_ = LuaStack::newstate(eng::l_alloc); LuaStack LS(state_); // Create the persist table and the unpersist table. // // These tables need to contain all C functions. Whenever // the source module inserts a C function into the lua environment, // it also needs to register the C function in these tables. // // I don't think anything else needs to go in these tables. // LS.rawset(LuaRegistry, "persist", LuaNewTable); LS.rawset(LuaRegistry, "unpersist", LuaNewTable); } LuaSnap::~LuaSnap() { lua_close(state_); } void LuaSnap::serialize(StreamBuffer *sb) { // Lua stack should be empty. assert(lua_gettop(state_) == 0); // lua variables that we'll need. LuaVar key, value; LuaRet permstable, regcopy; LuaStack LS(state_, permstable, regcopy, key, value); // Construct a copy of the registry table. LS.set(regcopy, LuaNewTable); LS.set(key, LuaNil); while (LS.next(LuaRegistry, key, value) != 0) { LS.rawset(regcopy, key, value); } // Remove certain things from the copy. These items // don't get included in the snapshot. // // From a modularity perspective, this is bad. // LS.rawset(regcopy, LUA_RIDX_MAINTHREAD, LuaNil); LS.rawset(regcopy, "persist", LuaNil); LS.rawset(regcopy, "unpersist", LuaNil); LS.rawset(regcopy, "world", LuaNil); LS.rawset(regcopy, "gui", LuaNil); LS.rawset(regcopy, "tnmap", LuaNil); LS.rawset(regcopy, "ntmap", LuaNil); // Get the eris permanents table from the registry. LS.rawget(permstable, LuaRegistry, "persist"); assert(LS.istable(permstable)); // When we call 'LS.result', this should leave the // permstable and the regcopy on the stack. LS.result(); assert(lua_gettop(state_) == 2); // Write dummy length, use eris to write data, then overwrite length. sb->write_int64(0); int64_t pos1 = sb->total_writes(); eris_dump(state_, sb->lua_writer, sb->lua_writer_ud()); int64_t pos2 = sb->total_writes(); sb->overwrite_int64(pos1, pos2 - pos1); lua_settop(state_, 0); } void LuaSnap::deserialize(StreamBuffer *sb) { // Lua stack should be empty. assert(lua_gettop(state_) == 0); // Get the length of the eris dump. int64_t len = sb->read_int64(); void *ud = sb->lua_reader_ud(len); // Call eris with the permanents table and passing the snapshot as a lua_Reader. lua_pushstring(state_, "unpersist"); lua_rawget(state_, LUA_REGISTRYINDEX); eris_undump(state_, sb->lua_reader, ud); assert(lua_gettop(state_) == 2); // Set up a stack frame. LuaArg permstable, regcopy; LuaVar key, value; LuaStack LS(state_, permstable, regcopy, key, value); assert(LS.istable(regcopy)); // Copy the contents of the snapshot over to the registry. LS.set(key, LuaNil); while (LS.next(regcopy, key, value) != 0) { LS.rawset(LuaRegistry, key, value); } LS.result(); assert(lua_gettop(state_) == 0); } // Snapshot and rollback can trivially be implemented on top of serialize and // deserialize. However, it's also possible to implement snapshot and rollback // using an alternative technique: // // 1. When constructing the lua interpreter, use a custom memory allocator that // keeps track of all the memory blocks used by lua. // // 2. Snapshot simply copies all the memory blocks used by lua into a buffer. // // 3. Rollback restores lua's memory blocks back to their previous state. This // has the effect of restoring lua's state. // // A proof-of-concept implementation of the memory-snapshotting design was // created, and it worked. It is probably faster than using serialize and // deserialize. // // Note: even if we implement this alternative design, we still need to keep // serialize and deserialize around in order to implement the save-game // functionality. So for now, we're sticking with this design, which doesn't // require us to maintain any additional code.