Dump table (effil.table -> table) (#134)

implement effil.dump()
This commit is contained in:
mihacooper 2020-02-16 18:55:14 +03:00 committed by GitHub
parent 3ac975ddf1
commit fd7a981d88
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 183 additions and 12 deletions

View File

@ -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)

View File

@ -56,7 +56,8 @@ Function::Function(const sol::function& luaObject) {
sol::stack::pop<sol::object>(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<sol::function>(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

View File

@ -18,9 +18,13 @@ public:
class Function : public GCObject<FunctionData> {
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(const StoredObject&)>;
sol::object convert(lua_State* state, const Converter& clbk) const;
explicit Function(const sol::function& luaObject);
friend class GC;
};

View File

@ -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<GCHandle> 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<GCHandle>(ctx_.get());
}

View File

@ -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<SharedTable>()) {
BaseHolder::DumpCache cache;
return obj.as<SharedTable>().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
);

View File

@ -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
*/

View File

@ -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&);

View File

@ -94,6 +94,15 @@ protected:
sol::optional<T> strongRef_;
};
class SharedTableHolder : public GCObjectHolder<SharedTable> {
public:
using GCObjectHolder<SharedTable>::GCObjectHolder;
sol::object convertToLua(sol::this_state state, DumpCache& cache) const final {
return GC::instance().get<SharedTable>(handle_).luaDump(state, cache);
}
};
class FunctionHolder : public GCObjectHolder<Function> {
public:
template <typename SolType>
@ -103,6 +112,10 @@ public:
sol::object unpack(sol::this_state state) const final {
return GC::instance().get<Function>(handle_).loadFunction(state);
}
sol::object convertToLua(sol::this_state state, DumpCache& cache) const final {
return GC::instance().get<Function>(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<SharedTable>();
visited.emplace_back(std::make_pair(luaTable, table.handle()));
dumpTable(&table, luaTable, visited);
return std::make_unique<GCObjectHolder<SharedTable>>(table.handle());
return std::make_unique<SharedTableHolder>(table.handle());
} else {
return std::make_unique<GCObjectHolder<SharedTable>>(st->second);
return std::make_unique<SharedTableHolder>(st->second);
}
} else {
return createStoredObject(luaObject);
@ -165,7 +178,7 @@ StoredObject fromSolObject(const SolObject& luaObject) {
return std::make_unique<PrimitiveHolder<void*>>(luaObject);
case sol::type::userdata:
if (luaObject.template is<SharedTable>())
return std::make_unique<GCObjectHolder<SharedTable>>(luaObject);
return std::make_unique<SharedTableHolder>(luaObject);
else if (luaObject.template is<Channel>())
return std::make_unique<GCObjectHolder<Channel>>(luaObject);
else if (luaObject.template is<Function>())
@ -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<GCObjectHolder<SharedTable>>(table.handle());
return std::make_unique<SharedTableHolder>(table.handle());
}
default:
throw Exception() << "unable to store object of " << luaTypename(luaObject) << " type";

View File

@ -28,6 +28,12 @@ public:
virtual void releaseStrongReference() { }
virtual void holdStrongReference() { }
using DumpCache = std::unordered_map<GCHandle, int>;
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<bool> storedObjectToBool(const StoredObject&);
sol::optional<double> storedObjectToDouble(const StoredObject&);
sol::optional<LUA_INDEX_TYPE> storedObjectToIndexType(const StoredObject&);

90
tests/lua/dump_table.lua Normal file
View File

@ -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

View File

@ -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"

View File

@ -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