parent
3ac975ddf1
commit
fd7a981d88
@ -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)
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
};
|
||||
|
||||
@ -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());
|
||||
}
|
||||
|
||||
@ -51,4 +51,4 @@ protected:
|
||||
std::shared_ptr<Impl> ctx_;
|
||||
};
|
||||
|
||||
} // namespace effil
|
||||
} // namespace effil
|
||||
|
||||
@ -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
|
||||
);
|
||||
|
||||
@ -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
|
||||
*/
|
||||
|
||||
@ -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&);
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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
90
tests/lua/dump_table.lua
Normal 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
|
||||
@ -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"
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user