Add Packer/Unpacker, add serialization to LuaSnap
This commit is contained in:
@@ -14,6 +14,7 @@ CPP_FILES=\
|
|||||||
cpp/gui.cpp\
|
cpp/gui.cpp\
|
||||||
cpp/luasnap.cpp\
|
cpp/luasnap.cpp\
|
||||||
cpp/animqueue.cpp\
|
cpp/animqueue.cpp\
|
||||||
|
cpp/packer.cpp\
|
||||||
cpp/source.cpp\
|
cpp/source.cpp\
|
||||||
cpp/world.cpp\
|
cpp/world.cpp\
|
||||||
cpp/textgame.cpp\
|
cpp/textgame.cpp\
|
||||||
|
|||||||
@@ -26,39 +26,8 @@ LuaSnap::~LuaSnap() {
|
|||||||
std::cerr << "LuaSnap destructor not implemented yet" << std::endl;
|
std::cerr << "LuaSnap destructor not implemented yet" << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool LuaSnap::have_snapshot() const {
|
void LuaSnap::serialize(Packer *pk) {
|
||||||
return !snapshot_.empty();
|
// Lua stack should be empty.
|
||||||
}
|
|
||||||
|
|
||||||
static int oss_writer(lua_State *L, const void *p, size_t sz, void *ud) {
|
|
||||||
std::ostringstream *oss = (std::ostringstream *)ud;
|
|
||||||
oss->write((const char *)p, sz);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert a C++ string into a lua_Reader.
|
|
||||||
class StringReader {
|
|
||||||
public:
|
|
||||||
const std::string &str_;
|
|
||||||
bool done_;
|
|
||||||
StringReader(const std::string &s) : str_(s), done_(false) {}
|
|
||||||
|
|
||||||
static const char *fn(lua_State *L, void *data, size_t *size) {
|
|
||||||
StringReader *sr = (StringReader*)data;
|
|
||||||
if (sr->done_) {
|
|
||||||
*size = 0;
|
|
||||||
return nullptr;
|
|
||||||
} else {
|
|
||||||
sr->done_ = true;
|
|
||||||
*size = sr->str_.size();
|
|
||||||
return sr->str_.c_str();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
void LuaSnap::snapshot() {
|
|
||||||
// Snapshot and the lua stack should both be empty.
|
|
||||||
assert(snapshot_.empty());
|
|
||||||
assert(lua_gettop(state_) == 0);
|
assert(lua_gettop(state_) == 0);
|
||||||
|
|
||||||
// lua variables that we'll need.
|
// lua variables that we'll need.
|
||||||
@@ -93,24 +62,26 @@ void LuaSnap::snapshot() {
|
|||||||
LS.result();
|
LS.result();
|
||||||
assert(lua_gettop(state_) == 2);
|
assert(lua_gettop(state_) == 2);
|
||||||
|
|
||||||
// Call eris to dump the state to an ostringstream,
|
// Write dummy length, use eris to write data, then overwrite length.
|
||||||
// then save the result.
|
pk->write_int64(0);
|
||||||
std::ostringstream oss;
|
size_t tell = pk->tellp();
|
||||||
eris_dump(state_, oss_writer, (void *)&oss);
|
eris_dump(state_, pk->lua_writer, pk->lua_writer_ud());
|
||||||
snapshot_ = oss.str();
|
pk->overwrite_int64(tell, pk->tellp() - tell);
|
||||||
std::cerr << "Eris dump is " << snapshot_.size() << " bytes." << std::endl;
|
|
||||||
lua_settop(state_, 0);
|
lua_settop(state_, 0);
|
||||||
|
std::cerr << "Eris dump is " << (pk->tellp() - tell) << " bytes." << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
void LuaSnap::rollback() {
|
void LuaSnap::deserialize(Unpacker *unpk) {
|
||||||
// Snapshot should have data, lua stack should be empty.
|
// Lua stack should be empty.
|
||||||
assert(!snapshot_.empty());
|
|
||||||
assert(lua_gettop(state_) == 0);
|
assert(lua_gettop(state_) == 0);
|
||||||
|
|
||||||
|
// Get a reader subsection containing the eris data.
|
||||||
|
size_t len = unpk->read_int64();
|
||||||
|
Unpacker subsec = unpk->read_section(len);
|
||||||
|
|
||||||
// Call eris with the permanents table and passing the snapshot as a lua_Reader.
|
// Call eris with the permanents table and passing the snapshot as a lua_Reader.
|
||||||
lua_getfield(state_, LUA_REGISTRYINDEX, "unpersist");
|
lua_getfield(state_, LUA_REGISTRYINDEX, "unpersist");
|
||||||
StringReader sr(snapshot_);
|
eris_undump(state_, subsec.lua_reader, subsec.lua_reader_ud());
|
||||||
eris_undump(state_, StringReader::fn, &sr);
|
|
||||||
assert(lua_gettop(state_) == 2);
|
assert(lua_gettop(state_) == 2);
|
||||||
|
|
||||||
// Set up a stack frame.
|
// Set up a stack frame.
|
||||||
@@ -127,6 +98,47 @@ void LuaSnap::rollback() {
|
|||||||
}
|
}
|
||||||
LS.result();
|
LS.result();
|
||||||
assert(lua_gettop(state_) == 0);
|
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.
|
||||||
|
|
||||||
|
bool LuaSnap::have_snapshot() const {
|
||||||
|
return !snapshot_.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LuaSnap::snapshot() {
|
||||||
|
assert(snapshot_.empty());
|
||||||
|
std::ostringstream oss;
|
||||||
|
Packer pk(&oss);
|
||||||
|
serialize(&pk);
|
||||||
|
snapshot_ = oss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LuaSnap::rollback() {
|
||||||
|
assert(!snapshot_.empty());
|
||||||
|
Unpacker unpk(snapshot_.c_str(), snapshot_.size());
|
||||||
|
deserialize(&unpk);
|
||||||
snapshot_.clear();
|
snapshot_.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
#ifndef LUASNAP_HPP
|
#ifndef LUASNAP_HPP
|
||||||
#define LUASNAP_HPP
|
#define LUASNAP_HPP
|
||||||
|
|
||||||
|
#include "packer.hpp"
|
||||||
#include "luastack.hpp"
|
#include "luastack.hpp"
|
||||||
|
|
||||||
class LuaSnap {
|
class LuaSnap {
|
||||||
@@ -29,6 +30,14 @@ public:
|
|||||||
//
|
//
|
||||||
lua_State *state() const { return state_; }
|
lua_State *state() const { return state_; }
|
||||||
|
|
||||||
|
// Serialize the state of the lua interpreter.
|
||||||
|
//
|
||||||
|
void serialize(Packer *pk);
|
||||||
|
|
||||||
|
// Restore the the lua interpreter given a serialized state.
|
||||||
|
//
|
||||||
|
void deserialize(Unpacker *unpk);
|
||||||
|
|
||||||
// Return true if there's a saved snapshot.
|
// Return true if there's a saved snapshot.
|
||||||
//
|
//
|
||||||
bool have_snapshot() const;
|
bool have_snapshot() const;
|
||||||
@@ -44,6 +53,7 @@ public:
|
|||||||
// If there is no snapshot, this panics.
|
// If there is no snapshot, this panics.
|
||||||
//
|
//
|
||||||
void rollback();
|
void rollback();
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
115
luprex/core/cpp/packer.cpp
Normal file
115
luprex/core/cpp/packer.cpp
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
#include <packer.hpp>
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
void Packer::write_int8(int8_t v) {
|
||||||
|
oss_->write((const char *)&v, sizeof(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Packer::write_int16(int16_t v) {
|
||||||
|
oss_->write((const char *)&v, sizeof(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Packer::write_int32(int32_t v) {
|
||||||
|
oss_->write((const char *)&v, sizeof(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Packer::write_int64(int64_t v) {
|
||||||
|
oss_->write((const char *)&v, sizeof(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Packer::overwrite_int8(size_t tell_after, int8_t v) {
|
||||||
|
oss_->seekp(tell_after - 1);
|
||||||
|
write_int8(v);
|
||||||
|
oss_->seekp(0, std::ios_base::end);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Packer::overwrite_int16(size_t tell_after, int16_t v) {
|
||||||
|
oss_->seekp(tell_after - 2);
|
||||||
|
write_int16(v);
|
||||||
|
oss_->seekp(0, std::ios_base::end);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Packer::overwrite_int32(size_t tell_after, int32_t v) {
|
||||||
|
oss_->seekp(tell_after - 4);
|
||||||
|
write_int32(v);
|
||||||
|
oss_->seekp(0, std::ios_base::end);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Packer::overwrite_int64(size_t tell_after, int64_t v) {
|
||||||
|
oss_->seekp(tell_after - 8);
|
||||||
|
write_int64(v);
|
||||||
|
oss_->seekp(0, std::ios_base::end);
|
||||||
|
}
|
||||||
|
|
||||||
|
int Packer::lua_writer(lua_State *L, const void *p, size_t sz, void *ud) {
|
||||||
|
Packer *pkr = (Packer *)ud;
|
||||||
|
pkr->oss_->write((const char *)p, sz);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Unpacker::Unpacker(const char *d, size_t s) {
|
||||||
|
assert(d != nullptr);
|
||||||
|
assert(s >= 0);
|
||||||
|
data_ = d;
|
||||||
|
size_ = s;
|
||||||
|
}
|
||||||
|
|
||||||
|
int8_t Unpacker::read_int8() {
|
||||||
|
assert(size_ >= 1);
|
||||||
|
int8_t result;
|
||||||
|
memcpy(&result, data_, 1);
|
||||||
|
data_ += 1;
|
||||||
|
size_ -= 1;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
int16_t Unpacker::read_int16() {
|
||||||
|
assert(size_ >= 2);
|
||||||
|
int16_t result;
|
||||||
|
memcpy(&result, data_, 2);
|
||||||
|
data_ += 2;
|
||||||
|
size_ -= 2;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t Unpacker::read_int32() {
|
||||||
|
assert(size_ >= 4);
|
||||||
|
int32_t result;
|
||||||
|
memcpy(&result, data_, 4);
|
||||||
|
data_ += 4;
|
||||||
|
size_ -= 4;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t Unpacker::read_int64() {
|
||||||
|
assert(size_ >= 8);
|
||||||
|
int64_t result;
|
||||||
|
memcpy(&result, data_, 8);
|
||||||
|
data_ += 8;
|
||||||
|
size_ -= 8;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Unpacker::skip(size_t n) {
|
||||||
|
assert(size_ >= n);
|
||||||
|
data_ += n;
|
||||||
|
size_ -= n;
|
||||||
|
}
|
||||||
|
|
||||||
|
Unpacker Unpacker::read_section(size_t n) {
|
||||||
|
assert(size_ >= n);
|
||||||
|
const char *d = data_;
|
||||||
|
data_ += n;
|
||||||
|
size_ -= n;
|
||||||
|
return Unpacker(d, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *Unpacker::lua_reader(lua_State *L, void *ud, size_t *size) {
|
||||||
|
Unpacker *unpk = (Unpacker *)ud;
|
||||||
|
const char *retval = unpk->data_;
|
||||||
|
*size = unpk->size_;
|
||||||
|
if (unpk->size_ == 0) retval = nullptr;
|
||||||
|
unpk->data_ += unpk->size_;
|
||||||
|
unpk->size_ = 0;
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
72
luprex/core/cpp/packer.hpp
Normal file
72
luprex/core/cpp/packer.hpp
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
#ifndef PACKER_HPP
|
||||||
|
#define PACKER_HPP
|
||||||
|
|
||||||
|
#include "luastack.hpp"
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
#include <sstream>
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
class Packer {
|
||||||
|
private:
|
||||||
|
std::ostringstream *oss_;
|
||||||
|
|
||||||
|
// Packer is not copyable.
|
||||||
|
Packer(const Packer &other) { assert(false); }
|
||||||
|
public:
|
||||||
|
using fixhandle = size_t;
|
||||||
|
Packer(std::ostringstream *s) : oss_(s) {}
|
||||||
|
|
||||||
|
// Get the current file position.
|
||||||
|
size_t tellp() { return oss_->tellp(); }
|
||||||
|
|
||||||
|
void write_int8(int8_t v);
|
||||||
|
void write_int16(int16_t v);
|
||||||
|
void write_int32(int32_t v);
|
||||||
|
void write_int64(int64_t v);
|
||||||
|
|
||||||
|
// Sometimes, you want to write a length followed by a block of data.
|
||||||
|
// Sometimes, you don't know the length until after you've written the block
|
||||||
|
// of data. To help with this situation, we provide the overwrite_int
|
||||||
|
// functions. Here's what you do:
|
||||||
|
//
|
||||||
|
// 1. write a dummy length into the stream.
|
||||||
|
// 2. use 'tellp' to fetch the stream position *after* the dummy length.
|
||||||
|
// 3. write the block of data.
|
||||||
|
// 4. use an overwrite function below to overwrite the dummy length.
|
||||||
|
//
|
||||||
|
void overwrite_int8(size_t tell_after, int8_t value);
|
||||||
|
void overwrite_int16(size_t tell_after, int16_t value);
|
||||||
|
void overwrite_int32(size_t tell_after, int32_t value);
|
||||||
|
void overwrite_int64(size_t tell_after, int64_t value);
|
||||||
|
|
||||||
|
// The packer can be used as a lua_Writer.
|
||||||
|
static int lua_writer(lua_State *L, const void *p, size_t sz, void *ud);
|
||||||
|
void *lua_writer_ud() { return this; }
|
||||||
|
};
|
||||||
|
|
||||||
|
class Unpacker {
|
||||||
|
private:
|
||||||
|
const char *data_;
|
||||||
|
size_t size_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Unpacker(const char *d, size_t s);
|
||||||
|
const char *data() { return data_; }
|
||||||
|
size_t size() { return size_; }
|
||||||
|
|
||||||
|
int8_t read_int8();
|
||||||
|
int16_t read_int16();
|
||||||
|
int32_t read_int32();
|
||||||
|
int64_t read_int64();
|
||||||
|
void skip(size_t n);
|
||||||
|
Unpacker read_section(size_t n);
|
||||||
|
|
||||||
|
// The unpacker can be used as a lua_Reader
|
||||||
|
static const char *lua_reader(lua_State *L, void *ud, size_t *size);
|
||||||
|
void *lua_reader_ud() { return this; }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#endif // PACKER_HPP
|
||||||
Reference in New Issue
Block a user