diff --git a/README.md b/README.md index 5079428..d6d6c50 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,7 @@ Requires C++14 compiler compliance. Tested with GCC 4.9+, clang 3.8 and Visual S * [effil.rawset()](#tbl--effilrawsettbl-key-value) * [effil.rawget()](#value--effilrawgettbl-key) * [effil.G](#effilg) + * [effil.dump()](#result--effildumpobj) * [Channel](#channel) * [effil.channel()](#channel--effilchannelcapacity) * [channel:push()](#pushed--channelpush) @@ -422,6 +423,14 @@ effil.thread(job)():wait() print(effil.G.key) -- will print "value" ``` +### `result = effil.dump(obj)` +Truns `effil.table` into regular Lua table. +```lua +tbl = effil.table({}) +effil.type(tbl) -- 'effil.table' +effil.type(effil.dump(tbl)) -- 'table' +``` + ## Channel `effil.channel` is a way to sequentially exchange data between effil threads. It allows to push message from one thread and pop it from another. Channel's **message** is a set of values of [supported types](#important-notes). All operations with channels are thread safe. See examples of channel usage [here](#examples) diff --git a/src/cpp/function.cpp b/src/cpp/function.cpp index 9a0af46..3013c59 100644 --- a/src/cpp/function.cpp +++ b/src/cpp/function.cpp @@ -56,7 +56,8 @@ Function::Function(const sol::function& luaObject) { sol::stack::pop(state); } -sol::object Function::loadFunction(lua_State* state) { +sol::object Function::convert(lua_State* state, const Converter& clbk) const +{ sol::function result = loadString(state, ctx_->function); assert(result.valid()); @@ -71,11 +72,22 @@ sol::object Function::loadFunction(lua_State* state) { #endif // LUA_VERSION_NUM > 501 assert(ctx_->upvalues[i].get() != nullptr); - const auto& obj = ctx_->upvalues[i]->unpack(sol::this_state{state}); - sol::stack::push(state, obj); + sol::stack::push(state, clbk(ctx_->upvalues[i])); lua_setupvalue(state, -2, i + 1); } return sol::stack::pop(state); } +sol::object Function::loadFunction(lua_State* state) const { + return convert(state, [&](const StoredObject& obj){ + return obj->unpack(sol::this_state{state}); + }); +} + +sol::object Function::convertToLua(lua_State* state, BaseHolder::DumpCache& cache) const { + return convert(state, [&](const StoredObject& obj) { + return obj->convertToLua(sol::this_state{state}, cache); + }); +} + } // namespace effil diff --git a/src/cpp/function.h b/src/cpp/function.h index e06d8e9..6cfa587 100644 --- a/src/cpp/function.h +++ b/src/cpp/function.h @@ -18,9 +18,13 @@ public: class Function : public GCObject { public: - sol::object loadFunction(lua_State* state); + sol::object loadFunction(lua_State* state) const; + sol::object convertToLua(lua_State* state, BaseHolder::DumpCache& cache) const; private: + using Converter = std::function; + sol::object convert(lua_State* state, const Converter& clbk) const; + explicit Function(const sol::function& luaObject); friend class GC; }; diff --git a/src/cpp/gc-object.h b/src/cpp/gc-object.h index 530f40d..65cc618 100644 --- a/src/cpp/gc-object.h +++ b/src/cpp/gc-object.h @@ -16,7 +16,7 @@ static const GCHandle GCNull = nullptr; class BaseGCObject { public: virtual ~BaseGCObject() = default; - virtual GCHandle handle() = 0; + virtual GCHandle handle() const = 0; virtual size_t instances() const = 0; virtual std::unordered_set refers() const = 0; }; @@ -32,7 +32,7 @@ public: GCObject& operator=(const GCObject&) = default; // Unique handle for any copy of GCData in any lua state - GCHandle handle() final { + GCHandle handle() const final { return reinterpret_cast(ctx_.get()); } @@ -51,4 +51,4 @@ protected: std::shared_ptr ctx_; }; -} // namespace effil \ No newline at end of file +} // namespace effil diff --git a/src/cpp/lua-module.cpp b/src/cpp/lua-module.cpp index 3881044..8a95eb8 100644 --- a/src/cpp/lua-module.cpp +++ b/src/cpp/lua-module.cpp @@ -39,6 +39,19 @@ size_t luaSize(const sol::stack_object& obj) { << luaTypename(obj) << " for effil.size()"; } +sol::object luaDump(sol::this_state lua, const sol::stack_object& obj) { + if (obj.is()) { + BaseHolder::DumpCache cache; + return obj.as().luaDump(lua, cache); + } + else if (obj.get_type() == sol::type::table) { + return obj; + } + + throw effil::Exception() << "bad argument #1 to 'effil.dump' (table expected, got " + << luaTypename(obj) << ")"; +} + sol::table luaThreadConfig(sol::this_state state, const sol::stack_object& obj) { REQUIRE(obj.valid() && obj.get_type() == sol::type::function) << "bad argument #1 to 'effil.thread' (function expected, got " @@ -107,6 +120,7 @@ int luaopen_effil(lua_State* L) { "pairs", SharedTable::globalLuaPairs, "ipairs", SharedTable::globalLuaIPairs, "size", luaSize, + "dump", luaDump, "hardware_threads", std::thread::hardware_concurrency, sol::meta_function::index, luaIndex ); diff --git a/src/cpp/shared-table.cpp b/src/cpp/shared-table.cpp index 6af0d4c..89a3674 100644 --- a/src/cpp/shared-table.cpp +++ b/src/cpp/shared-table.cpp @@ -97,6 +97,22 @@ sol::object SharedTable::rawGet(const sol::stack_object& luaKey, sol::this_state return get(key, state); } +sol::object SharedTable::luaDump(sol::this_state state, BaseHolder::DumpCache& cache) const { + const auto iter = cache.find(handle()); + if (iter == cache.end()) { + SharedLock lock(ctx_->lock); + + auto result = sol::table::create(state.L); + cache.insert(iter, {handle(), result.registry_index()}); + for (const auto& pair: ctx_->entries) { + result.set(pair.first->convertToLua(state, cache), + pair.second->convertToLua(state, cache)); + } + return result; + } + return sol::table(state.L, sol::ref_index(iter->second)); +} + /* * Lua Meta API methods */ diff --git a/src/cpp/shared-table.h b/src/cpp/shared-table.h index 416a282..267cde5 100644 --- a/src/cpp/shared-table.h +++ b/src/cpp/shared-table.h @@ -47,6 +47,8 @@ public: PairsIterator luaIPairs(sol::this_state); StoredArray luaCall(sol::this_state state, const sol::variadic_args& args); sol::object luaUnm(sol::this_state); + sol::object luaDump(sol::this_state state, BaseHolder::DumpCache& cache) const; + static sol::object luaAdd(sol::this_state, const sol::stack_object&, const sol::stack_object&); static sol::object luaSub(sol::this_state, const sol::stack_object&, const sol::stack_object&); static sol::object luaMul(sol::this_state, const sol::stack_object&, const sol::stack_object&); diff --git a/src/cpp/stored-object.cpp b/src/cpp/stored-object.cpp index 3c73f11..c954215 100644 --- a/src/cpp/stored-object.cpp +++ b/src/cpp/stored-object.cpp @@ -94,6 +94,15 @@ protected: sol::optional strongRef_; }; +class SharedTableHolder : public GCObjectHolder { +public: + using GCObjectHolder::GCObjectHolder; + + sol::object convertToLua(sol::this_state state, DumpCache& cache) const final { + return GC::instance().get(handle_).luaDump(state, cache); + } +}; + class FunctionHolder : public GCObjectHolder { public: template @@ -103,6 +112,10 @@ public: sol::object unpack(sol::this_state state) const final { return GC::instance().get(handle_).loadFunction(state); } + + sol::object convertToLua(sol::this_state state, DumpCache& cache) const final { + return GC::instance().get(handle_).convertToLua(state, cache); + } }; // This class is used as a storage for visited sol::tables @@ -124,9 +137,9 @@ StoredObject makeStoredObject(sol::object luaObject, SolTableToShared& visited) SharedTable table = GC::instance().create(); visited.emplace_back(std::make_pair(luaTable, table.handle())); dumpTable(&table, luaTable, visited); - return std::make_unique>(table.handle()); + return std::make_unique(table.handle()); } else { - return std::make_unique>(st->second); + return std::make_unique(st->second); } } else { return createStoredObject(luaObject); @@ -165,7 +178,7 @@ StoredObject fromSolObject(const SolObject& luaObject) { return std::make_unique>(luaObject); case sol::type::userdata: if (luaObject.template is()) - return std::make_unique>(luaObject); + return std::make_unique(luaObject); else if (luaObject.template is()) return std::make_unique>(luaObject); else if (luaObject.template is()) @@ -191,7 +204,7 @@ StoredObject fromSolObject(const SolObject& luaObject) { // SolTableToShared is used to prevent from infinity recursion // in recursive tables dumpTable(&table, luaTable, visited); - return std::make_unique>(table.handle()); + return std::make_unique(table.handle()); } default: throw Exception() << "unable to store object of " << luaTypename(luaObject) << " type"; diff --git a/src/cpp/stored-object.h b/src/cpp/stored-object.h index 0f33865..7ec16fe 100644 --- a/src/cpp/stored-object.h +++ b/src/cpp/stored-object.h @@ -28,6 +28,12 @@ public: virtual void releaseStrongReference() { } virtual void holdStrongReference() { } + + using DumpCache = std::unordered_map; + virtual sol::object convertToLua(sol::this_state state, DumpCache&) const { + return unpack(state); + } + private: BaseHolder(const BaseHolder&) = delete; }; @@ -46,7 +52,6 @@ StoredObject createStoredObject(const char*); StoredObject createStoredObject(const sol::object&); StoredObject createStoredObject(const sol::stack_object&); - sol::optional storedObjectToBool(const StoredObject&); sol::optional storedObjectToDouble(const StoredObject&); sol::optional storedObjectToIndexType(const StoredObject&); diff --git a/tests/lua/dump_table.lua b/tests/lua/dump_table.lua new file mode 100644 index 0000000..3677312 --- /dev/null +++ b/tests/lua/dump_table.lua @@ -0,0 +1,90 @@ +require "bootstrap-tests" + +local function table_included(left, right, path) + local path = path or "" + if type(left) ~= type(right) then + return false, "[" .. path .. "]: " .." got " .. type(right) .. "instead of " .. type(left) + end + + for k, v in pairs(left) do + local subpath = path .. '.' .. tostring(k) + if type(v) == 'table' then + local ret, msg = table_included(v, right[k], subpath) + if not ret then + return false, msg + end + elseif right[k] ~= v then + return false, "[" .. subpath .. "]: got " .. tostring(right[k]) .. " instead of " .. tostring(v) + end + end + return true +end + +local function table_equals(left, right) + local ret, msg = table_included(left, right) + if not ret then + return false, msg + end + return table_included(right, left) +end + +test.dump_table.tear_down = default_tear_down + +test.dump_table.compare_primitives = function() + local origin = { + 1, "str", key = "value", + key2 = { 2, [false] = "asd", { [44] = {true} } } + } + + local result = effil.dump(effil.table(origin)) + assert(table_equals(origin, result)) +end + +test.dump_table.compare_functions = function() + local origin = { + func = function(a, b) return a + b end, + nested = { + [function(a, b) return a - b end] = 2 + }, + } + + local result = effil.dump(effil.table(origin)) + test.equal(origin.func(2, 53), result.func(2, 53)) + for origin_key, origin_value in pairs(origin.nested) do + for res_key, res_value in pairs(result.nested) do + test.equal(origin_key(23, 11), res_key(23, 11)) + test.equal(origin_value, res_value) + end + end +end + +test.dump_table.reference_loop = function() + local origin = {} + origin.nested = {1, origin, 2} + origin.nested.nested_loop = { [origin] = origin.nested } + + local result = effil.dump(effil.table(origin)) + test.equal(result.nested[1], 1) + test.equal(result.nested[2], result) + test.equal(result.nested[3], 2) + test.equal(result.nested.nested_loop[result], result.nested) +end + +test.dump_table.regular_table = function() + local origin = {} + test.equal(origin, effil.dump(origin)) +end + +test.dump_table.upvalues_with_loop = function() + local origin = {} + local function foo() + origin.key = "value" + end + origin.foo = foo + + local result = effil.dump(origin) + local name, value = debug.getupvalue(result.foo, 1) + test.equal(value, result) + result.foo() + test.equal(result.key, "value") +end diff --git a/tests/lua/run_tests b/tests/lua/run_tests index 97f1f58..06d9808 100755 --- a/tests/lua/run_tests +++ b/tests/lua/run_tests @@ -16,6 +16,7 @@ require "shared-table" require "metatable" require "type_mismatch" require "upvalues" +require "dump_table" if os.getenv("STRESS") then require "channel-stress" diff --git a/tests/lua/type_mismatch.lua b/tests/lua/type_mismatch.lua index 00eecfd..9686bc8 100644 --- a/tests/lua/type_mismatch.lua +++ b/tests/lua/type_mismatch.lua @@ -122,6 +122,11 @@ local function generate_tests() -- effil.gc.step test.type_mismatch.input_types_mismatch_p(1, "number", "gc.step", type_instance) end + + -- effil.dump + if typename ~= "table" and typename ~= "effil.table" then + test.type_mismatch.input_types_mismatch_p(1, "table", "dump", type_instance) + end end -- Below presented tests which support everything except coroutines