Files
integration/luprex/cpp/core/luasnap.cpp

131 lines
4.1 KiB
C++
Raw Normal View History

2021-02-09 17:15:54 -05:00
#include "wrap-sstream.hpp"
#include "wrap-string.hpp"
2021-02-09 17:15:54 -05:00
#include "luasnap.hpp"
#include "luastack.hpp"
#include "streambuffer.hpp"
2021-02-09 17:15:54 -05:00
#include <cassert>
LuaSnap::LuaSnap() {
2022-02-28 21:57:54 -05:00
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);
2021-02-09 17:15:54 -05:00
}
LuaSnap::~LuaSnap() {
lua_close(state_);
2021-02-09 17:15:54 -05:00
}
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);
2021-02-09 17:15:54 -05:00
}
// 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);
2021-08-13 17:02:35 -04:00
LS.rawset(regcopy, "tnmap", LuaNil);
LS.rawset(regcopy, "ntmap", LuaNil);
2021-02-09 17:15:54 -05:00
// Get the eris permanents table from the registry.
LS.rawget(permstable, LuaRegistry, "persist");
assert(LS.istable(permstable));
2021-02-09 17:15:54 -05:00
// When we call 'LS.result', this should leave the
// permstable and the regcopy on the stack.
LS.result();
assert(lua_gettop(state_) == 2);
2021-02-09 17:15:54 -05:00
// Write dummy length, use eris to write data, then overwrite length.
sb->write_int64(0);
2021-07-20 14:48:53 -04:00
int64_t pos1 = sb->total_writes();
eris_dump(state_, sb->lua_writer, sb->lua_writer_ud());
2021-07-20 14:48:53 -04:00
int64_t pos2 = sb->total_writes();
sb->overwrite_int64(pos1, pos2 - pos1);
lua_settop(state_, 0);
2021-02-09 17:15:54 -05:00
}
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.