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.rawset()](#tbl--effilrawsettbl-key-value)
|
||||||
* [effil.rawget()](#value--effilrawgettbl-key)
|
* [effil.rawget()](#value--effilrawgettbl-key)
|
||||||
* [effil.G](#effilg)
|
* [effil.G](#effilg)
|
||||||
|
* [effil.dump()](#result--effildumpobj)
|
||||||
* [Channel](#channel)
|
* [Channel](#channel)
|
||||||
* [effil.channel()](#channel--effilchannelcapacity)
|
* [effil.channel()](#channel--effilchannelcapacity)
|
||||||
* [channel:push()](#pushed--channelpush)
|
* [channel:push()](#pushed--channelpush)
|
||||||
@ -422,6 +423,14 @@ effil.thread(job)():wait()
|
|||||||
print(effil.G.key) -- will print "value"
|
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
|
## 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)
|
`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::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);
|
sol::function result = loadString(state, ctx_->function);
|
||||||
assert(result.valid());
|
assert(result.valid());
|
||||||
|
|
||||||
@ -71,11 +72,22 @@ sol::object Function::loadFunction(lua_State* state) {
|
|||||||
#endif // LUA_VERSION_NUM > 501
|
#endif // LUA_VERSION_NUM > 501
|
||||||
assert(ctx_->upvalues[i].get() != nullptr);
|
assert(ctx_->upvalues[i].get() != nullptr);
|
||||||
|
|
||||||
const auto& obj = ctx_->upvalues[i]->unpack(sol::this_state{state});
|
sol::stack::push(state, clbk(ctx_->upvalues[i]));
|
||||||
sol::stack::push(state, obj);
|
|
||||||
lua_setupvalue(state, -2, i + 1);
|
lua_setupvalue(state, -2, i + 1);
|
||||||
}
|
}
|
||||||
return sol::stack::pop<sol::function>(state);
|
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
|
} // namespace effil
|
||||||
|
|||||||
@ -18,9 +18,13 @@ public:
|
|||||||
|
|
||||||
class Function : public GCObject<FunctionData> {
|
class Function : public GCObject<FunctionData> {
|
||||||
public:
|
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:
|
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);
|
explicit Function(const sol::function& luaObject);
|
||||||
friend class GC;
|
friend class GC;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -16,7 +16,7 @@ static const GCHandle GCNull = nullptr;
|
|||||||
class BaseGCObject {
|
class BaseGCObject {
|
||||||
public:
|
public:
|
||||||
virtual ~BaseGCObject() = default;
|
virtual ~BaseGCObject() = default;
|
||||||
virtual GCHandle handle() = 0;
|
virtual GCHandle handle() const = 0;
|
||||||
virtual size_t instances() const = 0;
|
virtual size_t instances() const = 0;
|
||||||
virtual std::unordered_set<GCHandle> refers() const = 0;
|
virtual std::unordered_set<GCHandle> refers() const = 0;
|
||||||
};
|
};
|
||||||
@ -32,7 +32,7 @@ public:
|
|||||||
GCObject& operator=(const GCObject&) = default;
|
GCObject& operator=(const GCObject&) = default;
|
||||||
|
|
||||||
// Unique handle for any copy of GCData in any lua state
|
// Unique handle for any copy of GCData in any lua state
|
||||||
GCHandle handle() final {
|
GCHandle handle() const final {
|
||||||
return reinterpret_cast<GCHandle>(ctx_.get());
|
return reinterpret_cast<GCHandle>(ctx_.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,4 +51,4 @@ protected:
|
|||||||
std::shared_ptr<Impl> ctx_;
|
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()";
|
<< 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) {
|
sol::table luaThreadConfig(sol::this_state state, const sol::stack_object& obj) {
|
||||||
REQUIRE(obj.valid() && obj.get_type() == sol::type::function)
|
REQUIRE(obj.valid() && obj.get_type() == sol::type::function)
|
||||||
<< "bad argument #1 to 'effil.thread' (function expected, got "
|
<< "bad argument #1 to 'effil.thread' (function expected, got "
|
||||||
@ -107,6 +120,7 @@ int luaopen_effil(lua_State* L) {
|
|||||||
"pairs", SharedTable::globalLuaPairs,
|
"pairs", SharedTable::globalLuaPairs,
|
||||||
"ipairs", SharedTable::globalLuaIPairs,
|
"ipairs", SharedTable::globalLuaIPairs,
|
||||||
"size", luaSize,
|
"size", luaSize,
|
||||||
|
"dump", luaDump,
|
||||||
"hardware_threads", std::thread::hardware_concurrency,
|
"hardware_threads", std::thread::hardware_concurrency,
|
||||||
sol::meta_function::index, luaIndex
|
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);
|
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
|
* Lua Meta API methods
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -47,6 +47,8 @@ public:
|
|||||||
PairsIterator luaIPairs(sol::this_state);
|
PairsIterator luaIPairs(sol::this_state);
|
||||||
StoredArray luaCall(sol::this_state state, const sol::variadic_args& args);
|
StoredArray luaCall(sol::this_state state, const sol::variadic_args& args);
|
||||||
sol::object luaUnm(sol::this_state);
|
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 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 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&);
|
static sol::object luaMul(sol::this_state, const sol::stack_object&, const sol::stack_object&);
|
||||||
|
|||||||
@ -94,6 +94,15 @@ protected:
|
|||||||
sol::optional<T> strongRef_;
|
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> {
|
class FunctionHolder : public GCObjectHolder<Function> {
|
||||||
public:
|
public:
|
||||||
template <typename SolType>
|
template <typename SolType>
|
||||||
@ -103,6 +112,10 @@ public:
|
|||||||
sol::object unpack(sol::this_state state) const final {
|
sol::object unpack(sol::this_state state) const final {
|
||||||
return GC::instance().get<Function>(handle_).loadFunction(state);
|
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
|
// 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>();
|
SharedTable table = GC::instance().create<SharedTable>();
|
||||||
visited.emplace_back(std::make_pair(luaTable, table.handle()));
|
visited.emplace_back(std::make_pair(luaTable, table.handle()));
|
||||||
dumpTable(&table, luaTable, visited);
|
dumpTable(&table, luaTable, visited);
|
||||||
return std::make_unique<GCObjectHolder<SharedTable>>(table.handle());
|
return std::make_unique<SharedTableHolder>(table.handle());
|
||||||
} else {
|
} else {
|
||||||
return std::make_unique<GCObjectHolder<SharedTable>>(st->second);
|
return std::make_unique<SharedTableHolder>(st->second);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return createStoredObject(luaObject);
|
return createStoredObject(luaObject);
|
||||||
@ -165,7 +178,7 @@ StoredObject fromSolObject(const SolObject& luaObject) {
|
|||||||
return std::make_unique<PrimitiveHolder<void*>>(luaObject);
|
return std::make_unique<PrimitiveHolder<void*>>(luaObject);
|
||||||
case sol::type::userdata:
|
case sol::type::userdata:
|
||||||
if (luaObject.template is<SharedTable>())
|
if (luaObject.template is<SharedTable>())
|
||||||
return std::make_unique<GCObjectHolder<SharedTable>>(luaObject);
|
return std::make_unique<SharedTableHolder>(luaObject);
|
||||||
else if (luaObject.template is<Channel>())
|
else if (luaObject.template is<Channel>())
|
||||||
return std::make_unique<GCObjectHolder<Channel>>(luaObject);
|
return std::make_unique<GCObjectHolder<Channel>>(luaObject);
|
||||||
else if (luaObject.template is<Function>())
|
else if (luaObject.template is<Function>())
|
||||||
@ -191,7 +204,7 @@ StoredObject fromSolObject(const SolObject& luaObject) {
|
|||||||
// SolTableToShared is used to prevent from infinity recursion
|
// SolTableToShared is used to prevent from infinity recursion
|
||||||
// in recursive tables
|
// in recursive tables
|
||||||
dumpTable(&table, luaTable, visited);
|
dumpTable(&table, luaTable, visited);
|
||||||
return std::make_unique<GCObjectHolder<SharedTable>>(table.handle());
|
return std::make_unique<SharedTableHolder>(table.handle());
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
throw Exception() << "unable to store object of " << luaTypename(luaObject) << " type";
|
throw Exception() << "unable to store object of " << luaTypename(luaObject) << " type";
|
||||||
|
|||||||
@ -28,6 +28,12 @@ public:
|
|||||||
virtual void releaseStrongReference() { }
|
virtual void releaseStrongReference() { }
|
||||||
virtual void holdStrongReference() { }
|
virtual void holdStrongReference() { }
|
||||||
|
|
||||||
|
|
||||||
|
using DumpCache = std::unordered_map<GCHandle, int>;
|
||||||
|
virtual sol::object convertToLua(sol::this_state state, DumpCache&) const {
|
||||||
|
return unpack(state);
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
BaseHolder(const BaseHolder&) = delete;
|
BaseHolder(const BaseHolder&) = delete;
|
||||||
};
|
};
|
||||||
@ -46,7 +52,6 @@ StoredObject createStoredObject(const char*);
|
|||||||
StoredObject createStoredObject(const sol::object&);
|
StoredObject createStoredObject(const sol::object&);
|
||||||
StoredObject createStoredObject(const sol::stack_object&);
|
StoredObject createStoredObject(const sol::stack_object&);
|
||||||
|
|
||||||
|
|
||||||
sol::optional<bool> storedObjectToBool(const StoredObject&);
|
sol::optional<bool> storedObjectToBool(const StoredObject&);
|
||||||
sol::optional<double> storedObjectToDouble(const StoredObject&);
|
sol::optional<double> storedObjectToDouble(const StoredObject&);
|
||||||
sol::optional<LUA_INDEX_TYPE> storedObjectToIndexType(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 "metatable"
|
||||||
require "type_mismatch"
|
require "type_mismatch"
|
||||||
require "upvalues"
|
require "upvalues"
|
||||||
|
require "dump_table"
|
||||||
|
|
||||||
if os.getenv("STRESS") then
|
if os.getenv("STRESS") then
|
||||||
require "channel-stress"
|
require "channel-stress"
|
||||||
|
|||||||
@ -122,6 +122,11 @@ local function generate_tests()
|
|||||||
-- effil.gc.step
|
-- effil.gc.step
|
||||||
test.type_mismatch.input_types_mismatch_p(1, "number", "gc.step", type_instance)
|
test.type_mismatch.input_types_mismatch_p(1, "number", "gc.step", type_instance)
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
-- Below presented tests which support everything except coroutines
|
-- Below presented tests which support everything except coroutines
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user