diff --git a/luprex/cpp/core/luastack.hpp b/luprex/cpp/core/luastack.hpp index a5a4daeb..248b7bbe 100644 --- a/luprex/cpp/core/luastack.hpp +++ b/luprex/cpp/core/luastack.hpp @@ -217,13 +217,16 @@ int LuaTypeTagValue(lua_State *L) { return 0; } // Lua table types. These deliberately do not overlap // with lua type values. // -#define LUA_TT_GENERAL 16 -#define LUA_TT_REGISTRY 17 -#define LUA_TT_GLOBALENV 18 -#define LUA_TT_TANGIBLE 19 -#define LUA_TT_TANGIBLEMETA 20 -#define LUA_TT_GLOBALDB 21 -#define LUA_TT_CLASS 22 +enum LuaTableType { + LUA_TT_GENERAL = LUA_NUMTAGS, + LUA_TT_REGISTRY, + LUA_TT_GLOBALENV, + LUA_TT_TANGIBLE, + LUA_TT_TANGIBLEMETA, + LUA_TT_CLASS, + + LUA_TT_SENTINEL +}; // World types enum. diff --git a/luprex/cpp/core/serializelua.cpp b/luprex/cpp/core/serializelua.cpp new file mode 100644 index 00000000..633c8c67 --- /dev/null +++ b/luprex/cpp/core/serializelua.cpp @@ -0,0 +1,189 @@ +#include "serializelua.hpp" + +enum PackCodes { + LUA_PK_TRUE = LUA_TT_SENTINEL, + LUA_PK_FALSE, + LUA_PK_REFERENCE, + LUA_PK_ENDTABLE +}; + +class Deserializer { + LuaVar id_to_value_; + LuaStack LS_; + int next_id_; + eng::string &error_; + StreamBuffer *sb_; + + Deserializer(LuaStack &LS0, LuaSlot val, StreamBuffer *sb, eng::string &error) : + LS_(LS0.state(), lookup_, value_to_id_), error_(error) { + next_id_ = 1; + LS_.newtable(id_to_value_); + try { + uint8_t b = sb_->read_uint8(); + deserialize_r(b, val); + } catch (StreamEof e) { + error_ = "EOF reached while deserializing data"; + LS_.set(val, LuaNil); + } + LS_.result(); + } +}; + + +// If the serialize routine encounters an error, it must set the +// error message string. In that case, it is assumed that the contents +// of the serialization buffer is no longer valid and there is no +// requirement to try to keep it valid. + +class Serializer { + LuaVar lookup_; + LuaVar value_to_id_; + LuaStack LS_; + int next_id_; + eng::string &error_; + StreamBuffer *sb_; + + void serialize_keyvals_r(LuaSlot tab) { + LuaVar key, val; + LuaStack SLS(LS_.state(), key, val); + SLS.getmetatable(val, tab); + if (!LS.isnil(val)) { + error_ = "Cannot serialize metatables"; + SLS.result(); + return; + } + sb_->pack_uint8(LUA_TT_GENERAL); + SLS.set(key, LuaNil); + while (LS.next(tab, key, val)) { + serialize_r(key); + if (!error_.empty()) { + SLS.result(); + return; + } + serialize_r(val); + if (!error_.empty()) { + SLS.result(); + return; + } + } + sb_->pack_uint8(LUA_PK_ENDTABLE); + SLS.result(); + } + + void serialize_r(LuaSlot val) { + int tt = LS_.xtype(val); + switch (tt) { + case LUA_TNIL: { + sb_->pack_uint8(LUA_TNIL); + return; + } + case LUA_TBOOLEAN: { + if (LS_.ckboolean(val)) { + sb_->pack_uint8(LUA_PK_TRUE); + } else { + sb_->pack_uint8(LUA_PK_FALSE); + } + return; + } + case LUA_TLIGHTUSERDATA: { + sb_->pack_uint8(LUA_TLIGHTUSERDATA); + sb_->pack_uint64(LS_.cktoken(val).value); + return; + } + case LUA_TNUMBER: { + sb_->pack_uint8(LUA_TNUMBER); + sb_->pack_double(LS_.cknumber(val)); + return; + } + case LUA_TSTRING: { + LS_.rawget(lookup_, value_to_id_, val); + if (!LS_.isnil(lookup_)) { + sb_->pack_uint8(LUA_PK_REFERENCE); + sb_->pack_uint32(LS.ckint(lookup_)); + } else { + LS_.rawset(value_to_id_, val, next_id_++); + sb_->pack_uint8(LUA_TSTRING); + sb_->pack_string(LS_.ckstring(val)); + } + return; + } + case LUA_TTABLE: { + // LS.xtype should never return LUA_TTABLE. + error_ = "Bad xtype in serialization"; + return; + } + case LUA_TFUNCTION: { + error_ = "Cannot serialize closures"; + return; + } + case LUA_TUSERDATA: { + error_ = "Cannot serialize userdata"; + return; + } + case LUA_TTHREAD: { + error_ = "Cannot serialize coroutines"; + return; + } + case LUA_TT_GENERAL: { + LS_.rawget(lookup_, value_to_id_, val); + if (!LS_.isnil(lookup_)) { + sb_->pack_uint8(LUA_PK_REFERENCE); + sb_->pack_uint32(LS.ckint(lookup_)); + } else { + LS_.rawset(value_to_id_, val, next_id_++); + serialize_table_r(val); + } + return; + } + case LUA_TT_REGISTRY: { + error_ = "Pointer to registry found in serialization, shouldn't happen"; + return; + } + case LUA_TT_GLOBALENV: { + sb_->pack_uint8(LUA_TT_GLOBALENV); + return; + } + case LUA_TT_TANGIBLE: { + sb_->pack_uint8(LUA_TT_TANGIBLE); + sb_->pack_int64(LS_.tanid(val)); + return; + } + case LUA_TT_TANGIBLEMETA: { + error_ = "Pointer to a tangible metatable found in serialization, shouldn't happen"; + return; + } + case LUA_TT_CLASS: { + sb_->pack_uint8(LUA_TT_CLASS); + sb_->pack_string(LS_.classname(val)); + return; + } + default: { + error_ = "Unrecognized xtype in serialization"; + return; + } + } + + Serializer(LuaStack &LS0, LuaSlot val, StreamBuffer *sb, eng::string &error) : + LS_(LS0.state(), lookup_, value_to_id_), error_(error) { + next_id_ = 1; + LS_.newtable(value_to_id_); + serialize_r(val); + LS_.result(); + } +}; + +eng::string serialize_lua(LuaStack &LS0, LuaSlot val, StreamBuffer *sb) { + eng::string error; + Serializer sz(LS0, val, sb, error); + return error; +}; + +eng::string deserialize_lua(LuaStack &LS0, LuaSlot val, StreamBuffer *sb) { + eng::string error; + Deserializer dsz(LS0, val, sb, error); + if (!error_.empty()) { + LS0.set(val, LuaNil); + } + return error; +} + diff --git a/luprex/cpp/core/serializelua.hpp b/luprex/cpp/core/serializelua.hpp new file mode 100644 index 00000000..feafced6 --- /dev/null +++ b/luprex/cpp/core/serializelua.hpp @@ -0,0 +1,40 @@ +//////////////////////////////////////////////////////////// +// +// This module contains routines to serialize and deserialize +// lua data structures. These routines can handle cyclic data +// structures. +// +// These routines are used for some specific difference +// transmission cases, but they're not the heart of the lua +// difference transmission system. +// +//////////////////////////////////////////////////////////// + + +#ifndef SERIALIZELUA_HPP +#define SERIALIZELUA_HPP + +#include "luastack.hpp" +#include "streambuffer.hpp" + +// serialize_lua +// +// Serialize a value from the LuaSlot and store it in the StreamBuffer. +// On success, returns empty string. +// +// If there is an error, returns an error message. In this case, the +// streambuffer contains garbage. +// +eng::string serialize_lua(LuaStack &LS0, LuaSlot val, StreamBuffer *sb); + +// deserialize_lua +// +// Deserialize a value from the StreamBuffer and store it in the LuaSlot. +// On success, returns empty string. +// +// If there is an error, returns an error message. In this case, the +// streambuffer is likely only partially consumed. +// +eng::string deserialize_lua(LuaStack &LS0, LuaSlot val, StreamBuffer *sb); + +#endif // SERIALIZELUA_HPP