Dealing with userdata types and metatable (#35)
* merge fixes * add Lua version logging to test * move StoredObject to shared_ptr * rewrite metamethods impl * fix for SEGFAULT, I hope
This commit is contained in:
parent
c09adc8115
commit
cac3cd09e7
2
libs/sol
2
libs/sol
@ -1 +1 @@
|
||||
Subproject commit 5b12924d9eab43b9621b8784b5ed576686c2fb50
|
||||
Subproject commit 3342e65b385aac57caca3b8284713682c9ea1211
|
||||
@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "stored-object.h"
|
||||
#include "utils.h"
|
||||
#include <sol.hpp>
|
||||
|
||||
@ -26,4 +27,21 @@ inline sol::function loadString(const sol::state_view& lua, const std::string& s
|
||||
return loader(str);
|
||||
}
|
||||
|
||||
typedef std::vector<effil::StoredObject> StoredArray;
|
||||
|
||||
} // namespace effil
|
||||
|
||||
namespace sol {
|
||||
namespace stack {
|
||||
template<>
|
||||
struct pusher<effil::StoredArray> {
|
||||
int push(lua_State* state, const effil::StoredArray& args) {
|
||||
int p = 0;
|
||||
for (const auto& i : args) {
|
||||
p += stack::push(state, i->unpack(sol::this_state{state}));
|
||||
}
|
||||
return p;
|
||||
}
|
||||
};
|
||||
} // stack
|
||||
} // sol
|
||||
|
||||
@ -15,7 +15,7 @@ sol::object createThread(const sol::this_state& lua,
|
||||
unsigned int step,
|
||||
const sol::function& function,
|
||||
const sol::variadic_args& args) {
|
||||
return sol::make_object(lua, std::make_unique<Thread>(path, cpath, stepwise, step, function, args));
|
||||
return sol::make_object(lua, std::make_shared<Thread>(path, cpath, stepwise, step, function, args));
|
||||
}
|
||||
|
||||
sol::object createTable(sol::this_state lua) { return sol::make_object(lua, getGC().create<SharedTable>()); }
|
||||
@ -26,12 +26,18 @@ extern "C" int luaopen_libeffil(lua_State* L) {
|
||||
sol::state_view lua(L);
|
||||
Thread::getUserType(lua);
|
||||
SharedTable::getUserType(lua);
|
||||
sol::table publicApi = lua.create_table_with("thread", createThread,
|
||||
"thread_id", threadId,
|
||||
"sleep", sleep,
|
||||
"yield", yield,
|
||||
"table", createTable
|
||||
);
|
||||
sol::table publicApi = lua.create_table_with(
|
||||
"thread", createThread,
|
||||
"thread_id", threadId,
|
||||
"sleep", sleep,
|
||||
"yield", yield,
|
||||
"table", createTable,
|
||||
"rawset", SharedTable::luaRawSet,
|
||||
"rawget", SharedTable::luaRawGet,
|
||||
"size", SharedTable::luaSize,
|
||||
"setmetatable", SharedTable::luaSetMetatable,
|
||||
"getmetatable", SharedTable::luaGetMetatable
|
||||
);
|
||||
sol::stack::push(lua, publicApi);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@ -7,24 +7,44 @@
|
||||
|
||||
namespace effil {
|
||||
|
||||
SharedTable::SharedTable()
|
||||
: GCObject()
|
||||
, data_(std::make_shared<SharedData>()) {}
|
||||
namespace {
|
||||
|
||||
template<typename SolObject>
|
||||
bool isSharedTable(const SolObject& obj) {
|
||||
return obj.valid() && obj.get_type() == sol::type::userdata && obj.template is<SharedTable>();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
SharedTable::SharedTable() : GCObject(), data_(std::make_shared<SharedData>()) {}
|
||||
|
||||
SharedTable::SharedTable(const SharedTable& init)
|
||||
: GCObject(init)
|
||||
, data_(init.data_) {}
|
||||
|
||||
sol::object SharedTable::getUserType(sol::state_view& lua) {
|
||||
sol::usertype<SharedTable> type("new", sol::no_constructor, //
|
||||
sol::meta_function::new_index, &SharedTable::luaSet, //
|
||||
sol::meta_function::index, &SharedTable::luaGet, //
|
||||
sol::meta_function::length, &SharedTable::length, //
|
||||
"__pairs", &SharedTable::pairs, //
|
||||
"__ipairs", &SharedTable::ipairs //
|
||||
);
|
||||
void SharedTable::getUserType(sol::state_view& lua) {
|
||||
sol::usertype<SharedTable> type("new", sol::no_constructor,
|
||||
"__pairs", &SharedTable::luaPairs,
|
||||
"__ipairs", &SharedTable::luaIPairs,
|
||||
sol::meta_function::new_index, &SharedTable::luaNewIndex,
|
||||
sol::meta_function::index, &SharedTable::luaIndex,
|
||||
sol::meta_function::length, &SharedTable::luaLength,
|
||||
sol::meta_function::to_string, &SharedTable::luaToString,
|
||||
sol::meta_function::addition, &SharedTable::luaAdd,
|
||||
sol::meta_function::subtraction, &SharedTable::luaSub,
|
||||
sol::meta_function::multiplication, &SharedTable::luaMul,
|
||||
sol::meta_function::division, &SharedTable::luaDiv,
|
||||
sol::meta_function::modulus, &SharedTable::luaMod,
|
||||
sol::meta_function::power_of, &SharedTable::luaPow,
|
||||
sol::meta_function::concatenation, &SharedTable::luaConcat,
|
||||
sol::meta_function::less_than, &SharedTable::luaLt,
|
||||
sol::meta_function::unary_minus, &SharedTable::luaUnm,
|
||||
sol::meta_function::call, &SharedTable::luaCall,
|
||||
sol::meta_function::equal_to, &SharedTable::luaEq,
|
||||
sol::meta_function::less_than_or_equal_to, &SharedTable::luaLe
|
||||
);
|
||||
sol::stack::push(lua, type);
|
||||
return sol::stack::pop<sol::object>(lua);
|
||||
sol::stack::pop<sol::object>(lua);
|
||||
}
|
||||
|
||||
void SharedTable::set(StoredObject&& key, StoredObject&& value) {
|
||||
@ -38,7 +58,7 @@ void SharedTable::set(StoredObject&& key, StoredObject&& value) {
|
||||
data_->entries[std::move(key)] = std::move(value);
|
||||
}
|
||||
|
||||
sol::object SharedTable::get(const StoredObject& key, const sol::this_state& state) const {
|
||||
sol::object SharedTable::get(const StoredObject& key, sol::this_state state) const {
|
||||
std::lock_guard<SpinMutex> g(data_->lock);
|
||||
auto val = data_->entries.find(key);
|
||||
if (val == data_->entries.end()) {
|
||||
@ -48,7 +68,7 @@ sol::object SharedTable::get(const StoredObject& key, const sol::this_state& sta
|
||||
}
|
||||
}
|
||||
|
||||
void SharedTable::luaSet(const sol::stack_object& luaKey, const sol::stack_object& luaValue) {
|
||||
void SharedTable::rawSet(const sol::stack_object& luaKey, const sol::stack_object& luaValue) {
|
||||
REQUIRE(luaKey.valid()) << "Indexing by nil";
|
||||
|
||||
StoredObject key = createStoredObject(luaKey);
|
||||
@ -70,18 +90,121 @@ void SharedTable::luaSet(const sol::stack_object& luaKey, const sol::stack_objec
|
||||
}
|
||||
}
|
||||
|
||||
sol::object SharedTable::luaGet(const sol::stack_object& luaKey, const sol::this_state& state) const {
|
||||
sol::object SharedTable::rawGet(const sol::stack_object& luaKey, sol::this_state state) const {
|
||||
REQUIRE(luaKey.valid()) << "Indexing by nil";
|
||||
StoredObject key = createStoredObject(luaKey);
|
||||
return get(key, state);
|
||||
}
|
||||
|
||||
size_t SharedTable::size() const {
|
||||
std::lock_guard<SpinMutex> g(data_->lock);
|
||||
return data_->entries.size();
|
||||
/*
|
||||
* Lua Meta API methods
|
||||
*/
|
||||
#define DEFFINE_METAMETHOD_CALL_0(methodName) DEFFINE_METAMETHOD_CALL(methodName, *this)
|
||||
#define DEFFINE_METAMETHOD_CALL(methodName, ...) \
|
||||
{ \
|
||||
std::unique_lock<SpinMutex> lock(data_->lock); \
|
||||
if (data_->metatable != GCNull) { \
|
||||
SharedTable tableHolder = *static_cast<SharedTable*>(getGC().get(data_->metatable)); \
|
||||
lock.unlock(); \
|
||||
sol::function handler = tableHolder.get(createStoredObject(methodName), state); \
|
||||
if (handler.valid()) { \
|
||||
return handler(__VA_ARGS__); \
|
||||
} \
|
||||
} \
|
||||
}
|
||||
|
||||
#define PROXY_METAMETHOD_IMPL(tableMethod, methodName, errMsg) \
|
||||
sol::object SharedTable:: tableMethod(sol::this_state state, \
|
||||
const sol::stack_object& leftObject, const sol::stack_object& rightObject) { \
|
||||
return basicMetaMethod(methodName, errMsg, state, leftObject, rightObject); \
|
||||
}
|
||||
|
||||
namespace {
|
||||
const std::string ARITHMETIC_ERR_MSG = "attempt to perform arithmetic on a effil::table value";
|
||||
const std::string COMPARE_ERR_MSG = "attempt to compare a effil::table value";
|
||||
const std::string CONCAT_ERR_MSG = "attempt to concatenate a effil::table value";
|
||||
}
|
||||
|
||||
size_t SharedTable::length() const {
|
||||
sol::object SharedTable::basicMetaMethod(const std::string& metamethodName, const std::string& errMsg,
|
||||
sol::this_state state, const sol::stack_object& leftObject, const sol::stack_object& rightObject) {
|
||||
if (isSharedTable(leftObject)) {
|
||||
SharedTable table = leftObject.as<SharedTable>();
|
||||
auto data_ = table.data_;
|
||||
DEFFINE_METAMETHOD_CALL(metamethodName, table, rightObject)
|
||||
}
|
||||
if (isSharedTable(rightObject)) {
|
||||
SharedTable table = rightObject.as<SharedTable>();
|
||||
auto data_ = table.data_;
|
||||
DEFFINE_METAMETHOD_CALL(metamethodName, leftObject, table)
|
||||
}
|
||||
throw Exception() << errMsg;
|
||||
}
|
||||
|
||||
PROXY_METAMETHOD_IMPL(luaConcat, "__concat", CONCAT_ERR_MSG)
|
||||
PROXY_METAMETHOD_IMPL(luaAdd, "__add", ARITHMETIC_ERR_MSG)
|
||||
PROXY_METAMETHOD_IMPL(luaSub, "__sub", ARITHMETIC_ERR_MSG)
|
||||
PROXY_METAMETHOD_IMPL(luaMul, "__mul", ARITHMETIC_ERR_MSG)
|
||||
PROXY_METAMETHOD_IMPL(luaDiv, "__div", ARITHMETIC_ERR_MSG)
|
||||
PROXY_METAMETHOD_IMPL(luaMod, "__mod", ARITHMETIC_ERR_MSG)
|
||||
PROXY_METAMETHOD_IMPL(luaPow, "__pow", ARITHMETIC_ERR_MSG)
|
||||
PROXY_METAMETHOD_IMPL(luaLe, "__le", ARITHMETIC_ERR_MSG)
|
||||
PROXY_METAMETHOD_IMPL(luaLt, "__lt", ARITHMETIC_ERR_MSG)
|
||||
PROXY_METAMETHOD_IMPL(luaEq, "__eq", ARITHMETIC_ERR_MSG)
|
||||
|
||||
sol::object SharedTable::luaUnm(sol::this_state state) {
|
||||
DEFFINE_METAMETHOD_CALL_0("__unm")
|
||||
throw Exception() << ARITHMETIC_ERR_MSG;
|
||||
}
|
||||
|
||||
void SharedTable::luaNewIndex(const sol::stack_object& luaKey, const sol::stack_object& luaValue, sol::this_state state) {
|
||||
{
|
||||
std::unique_lock<SpinMutex> lock(data_->lock);
|
||||
if (data_->metatable != GCNull) {
|
||||
SharedTable tableHolder = *static_cast<SharedTable*>(getGC().get(data_->metatable));
|
||||
lock.unlock();
|
||||
sol::function handler = tableHolder.get(createStoredObject("__newindex"), state);
|
||||
if (handler.valid()) {
|
||||
handler(*this, luaKey, luaValue);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
rawSet(luaKey, luaValue);
|
||||
}
|
||||
|
||||
sol::object SharedTable::luaIndex(const sol::stack_object& luaKey, sol::this_state state) {
|
||||
DEFFINE_METAMETHOD_CALL("__index", *this, luaKey)
|
||||
return rawGet(luaKey, state);
|
||||
}
|
||||
|
||||
StoredArray SharedTable::luaCall(sol::this_state state, const sol::variadic_args& args) {
|
||||
std::unique_lock<SpinMutex> lock(data_->lock);
|
||||
if (data_->metatable != GCNull) {
|
||||
sol::function handler = static_cast<SharedTable*>(getGC().get(data_->metatable))->get(createStoredObject(std::string("__call")), state);
|
||||
lock.unlock();
|
||||
if (handler.valid()) {
|
||||
StoredArray storedResults;
|
||||
const int savedStackTop = lua_gettop(state);
|
||||
sol::function_result callResults = handler(*this, args);
|
||||
(void)callResults;
|
||||
sol::variadic_args funcReturns(state, savedStackTop - lua_gettop(state));
|
||||
for (const auto& param : funcReturns)
|
||||
storedResults.emplace_back(createStoredObject(param.get<sol::object>()));
|
||||
return storedResults;
|
||||
}
|
||||
}
|
||||
throw Exception() << "attempt to call a table";
|
||||
}
|
||||
|
||||
sol::object SharedTable::luaToString(sol::this_state state) {
|
||||
DEFFINE_METAMETHOD_CALL_0("__tostring");
|
||||
std::stringstream ss;
|
||||
ss << "effil::table (0x" << std::hex << this << ")";
|
||||
return sol::make_object(state, ss.str());
|
||||
}
|
||||
|
||||
sol::object SharedTable::luaLength(sol::this_state state) {
|
||||
DEFFINE_METAMETHOD_CALL_0("__len");
|
||||
std::lock_guard<SpinMutex> g(data_->lock);
|
||||
size_t len = 0u;
|
||||
sol::optional<double> value;
|
||||
@ -93,7 +216,7 @@ size_t SharedTable::length() const {
|
||||
} while ((iter != data_->entries.end()) && (value = storedObjectToDouble(iter->first)) &&
|
||||
(static_cast<size_t>(value.value()) == len + 1));
|
||||
}
|
||||
return len;
|
||||
return sol::make_object(state, len);
|
||||
}
|
||||
|
||||
SharedTable::PairsIterator SharedTable::getNext(const sol::object& key, sol::this_state lua) {
|
||||
@ -102,38 +225,80 @@ SharedTable::PairsIterator SharedTable::getNext(const sol::object& key, sol::thi
|
||||
auto obj = createStoredObject(key);
|
||||
auto upper = data_->entries.upper_bound(obj);
|
||||
if (upper != data_->entries.end())
|
||||
return std::tuple<sol::object, sol::object>(upper->first->unpack(lua), upper->second->unpack(lua));
|
||||
return PairsIterator(upper->first->unpack(lua), upper->second->unpack(lua));
|
||||
} else {
|
||||
if (!data_->entries.empty()) {
|
||||
const auto& begin = data_->entries.begin();
|
||||
return std::tuple<sol::object, sol::object>(begin->first->unpack(lua), begin->second->unpack(lua));
|
||||
return PairsIterator(begin->first->unpack(lua), begin->second->unpack(lua));
|
||||
}
|
||||
}
|
||||
return std::tuple<sol::object, sol::object>(sol::nil, sol::nil);
|
||||
return PairsIterator(sol::nil, sol::nil);
|
||||
}
|
||||
|
||||
SharedTable::PairsIterator SharedTable::pairs(sol::this_state lua) const {
|
||||
auto next = [](sol::this_state lua, SharedTable table, sol::stack_object key) { return table.getNext(key, lua); };
|
||||
return std::tuple<sol::function, sol::object>(
|
||||
sol::make_object(
|
||||
lua, std::function<PairsIterator(sol::this_state lua, SharedTable table, sol::stack_object key)>(next))
|
||||
.as<sol::function>(),
|
||||
sol::make_object(lua, *this));
|
||||
SharedTable::PairsIterator SharedTable::luaPairs(sol::this_state state) {
|
||||
DEFFINE_METAMETHOD_CALL_0("__pairs");
|
||||
auto next = [](sol::this_state state, SharedTable table, sol::stack_object key) { return table.getNext(key, state); };
|
||||
return PairsIterator(
|
||||
sol::make_object(state, std::function<PairsIterator(sol::this_state state, SharedTable table, sol::stack_object key)>(next)).as<sol::function>(),
|
||||
sol::make_object(state, *this));
|
||||
}
|
||||
|
||||
std::tuple<sol::object, sol::object> ipairsNext(sol::this_state lua, SharedTable table,
|
||||
sol::optional<unsigned long> key) {
|
||||
std::pair<sol::object, sol::object> ipairsNext(sol::this_state lua, SharedTable table, sol::optional<unsigned long> key) {
|
||||
size_t index = key ? key.value() + 1 : 1;
|
||||
auto objKey = createStoredObject(static_cast<double>(index));
|
||||
sol::object value = table.get(objKey, lua);
|
||||
if (!value.valid())
|
||||
return std::tuple<sol::object, sol::object>(sol::nil, sol::nil);
|
||||
return std::tuple<sol::object, sol::object>(objKey->unpack(lua), value);
|
||||
return std::pair<sol::object, sol::object>(sol::nil, sol::nil);
|
||||
return std::pair<sol::object, sol::object>(objKey->unpack(lua), value);
|
||||
}
|
||||
|
||||
std::tuple<sol::function, sol::object> SharedTable::ipairs(sol::this_state lua) const {
|
||||
return std::tuple<sol::function, sol::object>(sol::make_object(lua, ipairsNext).as<sol::function>(),
|
||||
sol::make_object(lua, *this));
|
||||
SharedTable::PairsIterator SharedTable::luaIPairs(sol::this_state state) {
|
||||
DEFFINE_METAMETHOD_CALL_0("__ipairs");
|
||||
return PairsIterator(sol::make_object(state, ipairsNext).as<sol::function>(),
|
||||
sol::make_object(state, *this));
|
||||
}
|
||||
|
||||
/*
|
||||
* Lua static API functions
|
||||
*/
|
||||
|
||||
SharedTable SharedTable::luaSetMetatable(SharedTable& stable, const sol::stack_object& mt) {
|
||||
REQUIRE((!mt.valid()) || mt.get_type() == sol::type::table || isSharedTable(mt)) << "Unexpected type of setmetatable argument";
|
||||
std::lock_guard<SpinMutex> lock(stable.data_->lock);
|
||||
if (stable.data_->metatable != GCNull)
|
||||
{
|
||||
stable.refs_->erase(stable.data_->metatable);
|
||||
stable.data_->metatable = GCNull;
|
||||
}
|
||||
if (mt.valid()) {
|
||||
stable.data_->metatable = createStoredObject(mt)->gcHandle();
|
||||
stable.refs_->insert(stable.data_->metatable);
|
||||
}
|
||||
return stable;
|
||||
}
|
||||
|
||||
sol::object SharedTable::luaGetMetatable(const SharedTable& stable, sol::this_state state) {
|
||||
std::lock_guard<SpinMutex> lock(stable.data_->lock);
|
||||
return stable.data_->metatable == GCNull ? sol::nil :
|
||||
sol::make_object(state, *static_cast<SharedTable*>(getGC().get(stable.data_->metatable)));
|
||||
}
|
||||
|
||||
sol::object SharedTable::luaRawGet(const SharedTable& stable, const sol::stack_object& key, sol::this_state state) {
|
||||
return stable.rawGet(key, state);
|
||||
}
|
||||
|
||||
SharedTable SharedTable::luaRawSet(SharedTable& stable, const sol::stack_object& key, const sol::stack_object& value) {
|
||||
stable.rawSet(key, value);
|
||||
return stable;
|
||||
}
|
||||
|
||||
size_t SharedTable::luaSize(SharedTable& stable) {
|
||||
std::lock_guard<SpinMutex> g(stable.data_->lock);
|
||||
return stable.data_->entries.size();
|
||||
}
|
||||
|
||||
#undef DEFFINE_METAMETHOD_CALL_0
|
||||
#undef DEFFINE_METAMETHOD_CALL
|
||||
#undef PROXY_METAMETHOD_IMPL
|
||||
|
||||
} // effil
|
||||
|
||||
@ -3,6 +3,8 @@
|
||||
#include "garbage-collector.h"
|
||||
#include "stored-object.h"
|
||||
#include "spin-mutex.h"
|
||||
#include "utils.h"
|
||||
#include "lua-helpers.h"
|
||||
|
||||
#include <sol.hpp>
|
||||
|
||||
@ -13,7 +15,7 @@ namespace effil {
|
||||
|
||||
class SharedTable : public GCObject {
|
||||
private:
|
||||
typedef std::tuple<sol::function, sol::object> PairsIterator;
|
||||
typedef std::pair<sol::object, sol::object> PairsIterator;
|
||||
typedef std::map<StoredObject, StoredObject, StoredObjectLess> DataEntries;
|
||||
|
||||
public:
|
||||
@ -22,26 +24,56 @@ public:
|
||||
SharedTable(const SharedTable& init);
|
||||
virtual ~SharedTable() = default;
|
||||
|
||||
static sol::object getUserType(sol::state_view& lua);
|
||||
void set(StoredObject&&, StoredObject&&);
|
||||
sol::object get(const StoredObject& key, const sol::this_state& state) const;
|
||||
PairsIterator getNext(const sol::object& key, sol::this_state lua);
|
||||
static void getUserType(sol::state_view& lua);
|
||||
|
||||
// These functions could be invoked from lua scripts
|
||||
void luaSet(const sol::stack_object& luaKey, const sol::stack_object& luaValue);
|
||||
sol::object luaGet(const sol::stack_object& key, const sol::this_state& state) const;
|
||||
size_t size() const;
|
||||
size_t length() const;
|
||||
PairsIterator pairs(sol::this_state) const;
|
||||
PairsIterator ipairs(sol::this_state) const;
|
||||
void set(StoredObject&&, StoredObject&&);
|
||||
void rawSet(const sol::stack_object& luaKey, const sol::stack_object& luaValue);
|
||||
sol::object get(const StoredObject& key, sol::this_state state) const;
|
||||
sol::object rawGet(const sol::stack_object& key, sol::this_state state) const;
|
||||
static sol::object basicMetaMethod(const std::string&, const std::string&, sol::this_state,
|
||||
const sol::stack_object&, const sol::stack_object&);
|
||||
|
||||
// These functions are metamethods available in Lua
|
||||
void luaNewIndex(const sol::stack_object& luaKey, const sol::stack_object& luaValue, sol::this_state);
|
||||
sol::object luaIndex(const sol::stack_object& key, sol::this_state state);
|
||||
sol::object luaToString(sol::this_state state);
|
||||
sol::object luaLength(sol::this_state state);
|
||||
PairsIterator luaPairs(sol::this_state);
|
||||
PairsIterator luaIPairs(sol::this_state);
|
||||
StoredArray luaCall(sol::this_state state, const sol::variadic_args& args);
|
||||
sol::object luaUnm(sol::this_state);
|
||||
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&);
|
||||
static sol::object luaDiv(sol::this_state, const sol::stack_object&, const sol::stack_object&);
|
||||
static sol::object luaMod(sol::this_state, const sol::stack_object&, const sol::stack_object&);
|
||||
static sol::object luaPow(sol::this_state, const sol::stack_object&, const sol::stack_object&);
|
||||
static sol::object luaEq(sol::this_state, const sol::stack_object&, const sol::stack_object&);
|
||||
static sol::object luaLe(sol::this_state, const sol::stack_object&, const sol::stack_object&);
|
||||
static sol::object luaLt(sol::this_state, const sol::stack_object&, const sol::stack_object&);
|
||||
static sol::object luaConcat(sol::this_state, const sol::stack_object&, const sol::stack_object&);
|
||||
|
||||
// Stand alone functions for effil::table available in Lua
|
||||
static SharedTable luaSetMetatable(SharedTable& stable, const sol::stack_object& mt);
|
||||
static sol::object luaGetMetatable(const SharedTable& stable, const sol::this_state state);
|
||||
static sol::object luaRawGet(const SharedTable& stable, const sol::stack_object& key, sol::this_state state);
|
||||
static SharedTable luaRawSet(SharedTable& stable, const sol::stack_object& key, const sol::stack_object& value);
|
||||
static size_t luaSize(SharedTable& stable);
|
||||
|
||||
private:
|
||||
PairsIterator getNext(const sol::object& key, sol::this_state lua);
|
||||
|
||||
private:
|
||||
struct SharedData {
|
||||
SpinMutex lock;
|
||||
DataEntries entries;
|
||||
GCObjectHandle metatable;
|
||||
|
||||
SharedData() : metatable(GCNull) {}
|
||||
};
|
||||
|
||||
std::shared_ptr<SharedData> data_;
|
||||
};
|
||||
|
||||
|
||||
} // effil
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
#include "stored-object.h"
|
||||
|
||||
#include "threading.h"
|
||||
#include "shared-table.h"
|
||||
#include "utils.h"
|
||||
|
||||
@ -144,8 +145,15 @@ StoredObject fromSolObject(const SolObject& luaObject) {
|
||||
return std::make_unique<PrimitiveHolder<double>>(luaObject);
|
||||
case sol::type::string:
|
||||
return std::make_unique<PrimitiveHolder<std::string>>(luaObject);
|
||||
case sol::type::lightuserdata:
|
||||
return std::make_unique<PrimitiveHolder<void*>>(luaObject);
|
||||
case sol::type::userdata:
|
||||
return std::make_unique<TableHolder>(luaObject);
|
||||
if (luaObject.template is<SharedTable>())
|
||||
return std::make_unique<TableHolder>(luaObject);
|
||||
else if (luaObject.template is<std::shared_ptr<Thread>>())
|
||||
return std::make_unique<PrimitiveHolder<std::shared_ptr<Thread>>>(luaObject);
|
||||
else
|
||||
throw Exception() << "Unable to store userdata object\n";
|
||||
case sol::type::function:
|
||||
return std::make_unique<FunctionHolder>(luaObject);
|
||||
case sol::type::table: {
|
||||
|
||||
@ -27,7 +27,7 @@ private:
|
||||
BaseHolder(BaseHolder&) = delete;
|
||||
};
|
||||
|
||||
typedef std::unique_ptr<BaseHolder> StoredObject;
|
||||
typedef std::shared_ptr<BaseHolder> StoredObject;
|
||||
|
||||
struct StoredObjectLess {
|
||||
bool operator()(const StoredObject& lhs, const StoredObject& rhs) const { return lhs->compare(rhs.get()); }
|
||||
|
||||
@ -3,7 +3,6 @@
|
||||
#include "stored-object.h"
|
||||
#include "notifier.h"
|
||||
#include "spin-mutex.h"
|
||||
#include "lua-helpers.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include <thread>
|
||||
@ -57,7 +56,7 @@ public:
|
||||
|
||||
sol::state lua;
|
||||
Status status;
|
||||
StoredObject result;
|
||||
StoredArray result;
|
||||
|
||||
Notifier completion;
|
||||
// on thread resume
|
||||
@ -126,8 +125,8 @@ private:
|
||||
};
|
||||
|
||||
void runThread(std::shared_ptr<ThreadHandle> handle,
|
||||
const std::string &strFunction,
|
||||
std::vector<sol::object> &&arguments) {
|
||||
std::string strFunction,
|
||||
effil::StoredArray arguments) {
|
||||
|
||||
ScopeGuard reportComplete([=](){
|
||||
DEBUG << "Finished " << std::endl;
|
||||
@ -139,15 +138,19 @@ void runThread(std::shared_ptr<ThreadHandle> handle,
|
||||
|
||||
try {
|
||||
sol::function userFuncObj = loadString(handle->lua, strFunction);
|
||||
sol::object result = userFuncObj(sol::as_args(arguments));
|
||||
handle->result = createStoredObject(result);
|
||||
|
||||
sol::function_result results = userFuncObj(std::move(arguments));
|
||||
(void)results; // just leave all returns on the stack
|
||||
sol::variadic_args args(handle->lua, -lua_gettop(handle->lua));
|
||||
for (const auto& iter : args) {
|
||||
StoredObject store = createStoredObject(iter.get<sol::object>());
|
||||
handle->result.emplace_back(std::move(store));
|
||||
}
|
||||
handle->status = Status::Completed;
|
||||
} catch (const LuaHookStopException&) {
|
||||
handle->status = Status::Canceled;
|
||||
} catch (const sol::error& err) {
|
||||
DEBUG << "Failed with msg: " << err.what() << std::endl;
|
||||
handle->result = createStoredObject(err.what());
|
||||
handle->result.emplace_back(createStoredObject(err.what()));
|
||||
handle->status = Status::Failed;
|
||||
}
|
||||
}
|
||||
@ -199,22 +202,20 @@ Thread::Thread(const std::string& path,
|
||||
|
||||
std::string strFunction = dumpFunction(function);
|
||||
|
||||
std::vector<sol::object> arguments;
|
||||
effil::StoredArray arguments;
|
||||
for (const auto& arg : variadicArgs) {
|
||||
StoredObject store = createStoredObject(arg.get<sol::object>());
|
||||
arguments.push_back(store->unpack(sol::this_state{handle_->lua}));
|
||||
arguments.emplace_back(createStoredObject(arg.get<sol::object>()));
|
||||
}
|
||||
|
||||
|
||||
std::thread thr(&runThread,
|
||||
handle_,
|
||||
strFunction,
|
||||
std::move(strFunction),
|
||||
std::move(arguments));
|
||||
DEBUG << "Created " << thr.get_id() << std::endl;
|
||||
thr.detach();
|
||||
}
|
||||
|
||||
sol::object Thread::getUserType(sol::state_view& lua) {
|
||||
void Thread::getUserType(sol::state_view& lua) {
|
||||
sol::usertype<Thread> type(
|
||||
"new", sol::no_constructor,
|
||||
"get", &Thread::get,
|
||||
@ -225,15 +226,15 @@ sol::object Thread::getUserType(sol::state_view& lua) {
|
||||
"status", &Thread::status);
|
||||
|
||||
sol::stack::push(lua, type);
|
||||
return sol::stack::pop<sol::object>(lua);
|
||||
sol::stack::pop<sol::object>(lua);
|
||||
}
|
||||
|
||||
std::pair<sol::object, sol::object> Thread::status(const sol::this_state& lua) {
|
||||
sol::object luaStatus = sol::make_object(lua, statusToString(handle_->status));
|
||||
|
||||
if (handle_->status == Status::Failed) {
|
||||
assert(handle_->result);
|
||||
return std::make_pair(luaStatus, handle_->result->unpack(lua));
|
||||
assert(!handle_->result.empty());
|
||||
return std::make_pair(luaStatus, handle_->result[0]->unpack(lua));
|
||||
} else {
|
||||
return std::make_pair(luaStatus, sol::nil);
|
||||
}
|
||||
@ -256,15 +257,13 @@ std::pair<sol::object, sol::object> Thread::wait(const sol::this_state& lua,
|
||||
return status(lua);
|
||||
}
|
||||
|
||||
sol::object Thread::get(const sol::this_state& lua,
|
||||
const sol::optional<int>& duration,
|
||||
StoredArray Thread::get(const sol::optional<int>& duration,
|
||||
const sol::optional<std::string>& period) {
|
||||
bool completed = waitFor(duration, period);
|
||||
|
||||
if (completed && handle_->status == Status::Completed)
|
||||
return handle_->result->unpack(lua);
|
||||
return handle_->result;
|
||||
else
|
||||
return sol::nil;
|
||||
return StoredArray();
|
||||
}
|
||||
|
||||
bool Thread::cancel(const sol::this_state&,
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <sol.hpp>
|
||||
#include "lua-helpers.h"
|
||||
|
||||
namespace effil {
|
||||
|
||||
@ -20,14 +21,13 @@ public:
|
||||
const sol::function& function,
|
||||
const sol::variadic_args& args);
|
||||
|
||||
static sol::object getUserType(sol::state_view& lua);
|
||||
static void getUserType(sol::state_view& lua);
|
||||
|
||||
std::pair<sol::object, sol::object> status(const sol::this_state& state);
|
||||
std::pair<sol::object, sol::object> wait(const sol::this_state& state,
|
||||
const sol::optional<int>& duration,
|
||||
const sol::optional<std::string>& period);
|
||||
sol::object get(const sol::this_state& state,
|
||||
const sol::optional<int>& duration,
|
||||
StoredArray get(const sol::optional<int>& duration,
|
||||
const sol::optional<std::string>& period);
|
||||
bool cancel(const sol::this_state& state,
|
||||
const sol::optional<int>& duration,
|
||||
@ -43,7 +43,6 @@ private:
|
||||
private:
|
||||
bool waitFor(const sol::optional<int>& duration,
|
||||
const sol::optional<std::string>& period);
|
||||
|
||||
private:
|
||||
Thread(const Thread&) = delete;
|
||||
Thread& operator=(const Thread&) = delete;
|
||||
|
||||
@ -16,7 +16,12 @@ local api = {
|
||||
table = capi.table,
|
||||
thread_id = capi.thread_id,
|
||||
sleep = capi.sleep,
|
||||
yield = capi.yield
|
||||
yield = capi.yield,
|
||||
size = capi.size,
|
||||
rawget = capi.rawget,
|
||||
rawset = capi.rawset,
|
||||
setmetatable = capi.setmetatable,
|
||||
getmetatable = capi.getmetatable,
|
||||
}
|
||||
|
||||
local function run_thread(config, f, ...)
|
||||
|
||||
@ -15,12 +15,12 @@ TEST(sharedTable, primitiveTypes) {
|
||||
lua["st"] = SharedTable();
|
||||
|
||||
auto res1 = lua.script(R"(
|
||||
st.fst = "first"
|
||||
st.snd = 2
|
||||
st.thr = true
|
||||
st.del = "secret"
|
||||
st.del = nil
|
||||
)");
|
||||
st.fst = "first"
|
||||
st.snd = 2
|
||||
st.thr = true
|
||||
st.del = "secret"
|
||||
st.del = nil
|
||||
)");
|
||||
|
||||
EXPECT_TRUE(res1.valid()) << "Set res1 failed";
|
||||
EXPECT_EQ(lua["st"]["fst"], std::string("first"));
|
||||
@ -30,13 +30,13 @@ st.del = nil
|
||||
EXPECT_EQ(lua["st"]["nex"], sol::nil);
|
||||
|
||||
auto res2 = lua.script(R"(
|
||||
st[1] = 3
|
||||
st[2] = "number"
|
||||
st[-1] = false
|
||||
st[42] = "answer"
|
||||
st[42] = nil
|
||||
st.deleted = st[42] == nil
|
||||
)");
|
||||
st[1] = 3
|
||||
st[2] = "number"
|
||||
st[-1] = false
|
||||
st[42] = "answer"
|
||||
st[42] = nil
|
||||
st.deleted = st[42] == nil
|
||||
)");
|
||||
|
||||
EXPECT_TRUE(res2.valid()) << "Set res2 failed";
|
||||
EXPECT_EQ(lua["st"][1], 3);
|
||||
@ -45,9 +45,9 @@ st.deleted = st[42] == nil
|
||||
EXPECT_EQ(lua["st"]["deleted"], true);
|
||||
|
||||
auto res3 = lua.script(R"(
|
||||
st[true] = false
|
||||
st[false] = 9
|
||||
)");
|
||||
st[true] = false
|
||||
st[false] = 9
|
||||
)");
|
||||
|
||||
EXPECT_TRUE(res3.valid()) << "Set res3 failed";
|
||||
EXPECT_EQ(lua["st"][true], false);
|
||||
@ -65,10 +65,10 @@ TEST(sharedTable, multipleStates) {
|
||||
lua2["dogs"] = st.get();
|
||||
|
||||
auto res1 = lua1.script(R"(
|
||||
cats.fluffy = "gav"
|
||||
cats.sparky = false
|
||||
cats.wow = 3
|
||||
)");
|
||||
cats.fluffy = "gav"
|
||||
cats.sparky = false
|
||||
cats.wow = 3
|
||||
)");
|
||||
|
||||
EXPECT_EQ(lua2["dogs"]["fluffy"], std::string("gav"));
|
||||
EXPECT_EQ(lua2["dogs"]["sparky"], false);
|
||||
@ -86,8 +86,9 @@ TEST(sharedTable, multipleThreads) {
|
||||
;
|
||||
lua["st"] = st;
|
||||
lua.script(R"(
|
||||
while not st.ready do end
|
||||
st.fst = true)");
|
||||
while not st.ready do end
|
||||
st.fst = true
|
||||
)");
|
||||
});
|
||||
|
||||
threads.emplace_back([=]() {
|
||||
@ -95,8 +96,9 @@ st.fst = true)");
|
||||
bootstrapState(lua);
|
||||
lua["st"] = st;
|
||||
lua.script(R"(
|
||||
while not st.ready do end
|
||||
st.snd = true)");
|
||||
while not st.ready do end
|
||||
st.snd = true
|
||||
)");
|
||||
});
|
||||
|
||||
threads.emplace_back([=]() {
|
||||
@ -104,8 +106,9 @@ st.snd = true)");
|
||||
bootstrapState(lua);
|
||||
lua["st"] = st;
|
||||
lua.script(R"(
|
||||
while not st.ready do end
|
||||
st.thr = true)");
|
||||
while not st.ready do end
|
||||
st.thr = true
|
||||
)");
|
||||
});
|
||||
|
||||
sol::state lua;
|
||||
@ -131,11 +134,11 @@ TEST(sharedTable, playingWithSharedTables) {
|
||||
lua["st2"] = getGC().create<SharedTable>();
|
||||
|
||||
lua.script(R"(
|
||||
st1.proxy = st2
|
||||
st1.proxy.value = true
|
||||
recursive.next = recursive
|
||||
recursive.val = "yes"
|
||||
)");
|
||||
st1.proxy = st2
|
||||
st1.proxy.value = true
|
||||
recursive.next = recursive
|
||||
recursive.val = "yes"
|
||||
)");
|
||||
EXPECT_EQ(lua["st2"]["value"], true);
|
||||
EXPECT_EQ(lua["recursive"]["next"]["next"]["next"]["val"], std::string("yes"));
|
||||
}
|
||||
@ -148,12 +151,12 @@ TEST(sharedTable, playingWithFunctions) {
|
||||
lua["st"] = st;
|
||||
|
||||
lua.script(R"(
|
||||
st.fn = function ()
|
||||
print "Hello C++"
|
||||
return true
|
||||
end
|
||||
st.fn()
|
||||
)");
|
||||
st.fn = function ()
|
||||
print "Hello C++"
|
||||
return true
|
||||
end
|
||||
st.fn()
|
||||
)");
|
||||
|
||||
sol::function sf = lua["st"]["fn"];
|
||||
EXPECT_TRUE((bool)sf());
|
||||
@ -163,10 +166,10 @@ st.fn()
|
||||
|
||||
lua2["st2"] = st;
|
||||
lua2.script(R"(
|
||||
st2.fn2 = function(str)
|
||||
return "*" .. str .. "*"
|
||||
end
|
||||
)");
|
||||
st2.fn2 = function(str)
|
||||
return "*" .. str .. "*"
|
||||
end
|
||||
)");
|
||||
|
||||
sol::function sf2 = lua["st"]["fn2"];
|
||||
|
||||
@ -180,22 +183,22 @@ TEST(sharedTable, playingWithTables) {
|
||||
|
||||
lua["st"] = st;
|
||||
auto res = lua.script(R"(
|
||||
st.works = "fine"
|
||||
st.person = {name = 'John Doe', age = 25}
|
||||
pet = {
|
||||
type = "cat",
|
||||
name = "Tomas",
|
||||
real = "Яша",
|
||||
owner = "Mama",
|
||||
spec = { colour = "grey", legs = 4, eyes = 2 }
|
||||
}
|
||||
st.pet = pet
|
||||
recursive = {}
|
||||
recursive.next = recursive
|
||||
recursive.prev = recursive
|
||||
recursive.val = "recursive"
|
||||
st.recursive = recursive
|
||||
)");
|
||||
st.works = "fine"
|
||||
st.person = {name = 'John Doe', age = 25}
|
||||
pet = {
|
||||
type = "cat",
|
||||
name = "Tomas",
|
||||
real = "Яша",
|
||||
owner = "Mama",
|
||||
spec = { colour = "grey", legs = 4, eyes = 2 }
|
||||
}
|
||||
st.pet = pet
|
||||
recursive = {}
|
||||
recursive.next = recursive
|
||||
recursive.prev = recursive
|
||||
recursive.val = "recursive"
|
||||
st.recursive = recursive
|
||||
)");
|
||||
|
||||
EXPECT_TRUE(res.valid());
|
||||
EXPECT_EQ(lua["st"]["person"]["name"], std::string("John Doe"));
|
||||
@ -216,21 +219,21 @@ TEST(sharedTable, stress) {
|
||||
lua["st"] = st;
|
||||
|
||||
auto res1 = lua.script(R"(
|
||||
for i = 1, 1000000 do
|
||||
st[i] = tostring(i)
|
||||
end
|
||||
)");
|
||||
for i = 1, 1000000 do
|
||||
st[i] = tostring(i)
|
||||
end
|
||||
)");
|
||||
|
||||
EXPECT_TRUE(res1.valid());
|
||||
EXPECT_TRUE(st.size() == 1'000'000);
|
||||
EXPECT_TRUE(SharedTable::luaSize(st) == 1'000'000);
|
||||
|
||||
auto res2 = lua.script(R"(
|
||||
for i = 1000000, 1, -1 do
|
||||
st[i] = nil
|
||||
end
|
||||
)");
|
||||
for i = 1000000, 1, -1 do
|
||||
st[i] = nil
|
||||
end
|
||||
)");
|
||||
EXPECT_TRUE(res2.valid());
|
||||
EXPECT_TRUE(st.size() == 0);
|
||||
EXPECT_TRUE(SharedTable::luaSize(st) == 0);
|
||||
}
|
||||
|
||||
TEST(sharedTable, stressWithThreads) {
|
||||
@ -264,3 +267,33 @@ TEST(sharedTable, stressWithThreads) {
|
||||
EXPECT_TRUE(lua["st"][i] == 100'001) << (double)lua["st"][i] << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(sharedTable, ExternalUserdata) {
|
||||
SharedTable st;
|
||||
sol::state lua;
|
||||
bootstrapState(lua);
|
||||
lua["st"] = st;
|
||||
|
||||
struct TestUserdata
|
||||
{
|
||||
int field;
|
||||
};
|
||||
|
||||
lua["udata"] = TestUserdata{17};
|
||||
EXPECT_THROW(lua.script("st.userdata = udata"), sol::error);
|
||||
}
|
||||
|
||||
TEST(sharedTable, LightUserdata) {
|
||||
SharedTable st;
|
||||
sol::state lua;
|
||||
bootstrapState(lua);
|
||||
lua["st"] = st;
|
||||
|
||||
int lightUserdata = 19;
|
||||
lua_pushlightuserdata(lua, (void*)&lightUserdata);
|
||||
lua["light_udata"] = sol::stack::pop<sol::object>(lua);
|
||||
EXPECT_TRUE((bool)lua.script("st.light_userdata = light_udata; return st.light_userdata == light_udata"));
|
||||
std::string strRet = lua.script("return type(light_udata)");
|
||||
EXPECT_EQ(strRet, "userdata");
|
||||
EXPECT_EQ(lua["light_udata"].get<sol::lightuserdata_value>().value, &lightUserdata);
|
||||
}
|
||||
|
||||
@ -3,6 +3,10 @@
|
||||
-- TODO: remove hardcode
|
||||
package.path = package.path .. ";../libs/luaunit/?.lua;../tests/lua/?.lua"
|
||||
|
||||
print("---------------")
|
||||
print("-- " .. _VERSION .. " --")
|
||||
print("---------------")
|
||||
|
||||
do
|
||||
-- Hack input arguments to make tests verbose by default
|
||||
local found = false
|
||||
@ -23,8 +27,23 @@ end
|
||||
-----------
|
||||
|
||||
test = require "luaunit"
|
||||
effil = require 'effil'
|
||||
require 'test_utils'
|
||||
require 'thread'
|
||||
require 'shared_table'
|
||||
|
||||
-- Hack tests functions to print when test starts
|
||||
for suite_name, suite in pairs(_G) do
|
||||
if string.sub(suite_name, 1, 4):lower() == 'test' and type(_G[suite_name]) == 'table' then -- is a test suite
|
||||
for test_name, test_func in pairs(suite) do
|
||||
if string.sub(test_name, 1, 4):lower() == 'test' then -- is a test function
|
||||
suite[test_name] = function(...)
|
||||
print("# Starting test: " .. suite_name .. '.' .. test_name)
|
||||
return test_func(...)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
os.exit( test.LuaUnit.run() )
|
||||
@ -1,7 +1,6 @@
|
||||
TestSharedTable = {tearDown = tearDown}
|
||||
|
||||
function TestSharedTable:testPairs()
|
||||
local effil = require 'effil'
|
||||
local share = effil.table()
|
||||
local data = { 0, 0, 0, ["key1"] = 0, ["key2"] = 0, ["key3"] = 0 }
|
||||
|
||||
@ -33,7 +32,6 @@ function TestSharedTable:testPairs()
|
||||
end
|
||||
|
||||
function TestSharedTable:testLength()
|
||||
local effil = require 'effil'
|
||||
local share = effil.table()
|
||||
share[1] = 10
|
||||
share[2] = 20
|
||||
@ -47,3 +45,207 @@ function TestSharedTable:testLength()
|
||||
share[1] = nil
|
||||
test.assertEquals(#share, 0)
|
||||
end
|
||||
|
||||
function TestSharedTable:testSize()
|
||||
local share = effil.table()
|
||||
test.assertEquals(effil.size(share), 0)
|
||||
share[1] = 10
|
||||
test.assertEquals(effil.size(share), 1)
|
||||
share[2] = "value1"
|
||||
share["key1"] = function() end
|
||||
test.assertEquals(effil.size(share), 3)
|
||||
share[2] = nil
|
||||
test.assertEquals(effil.size(share), 2)
|
||||
end
|
||||
|
||||
function TestSharedTable:testUserDataClassification()
|
||||
local share = effil.table()
|
||||
share.thread = effil.thread(function(a, b) return a + b end)(19, 33)
|
||||
share.sub_table = effil.table()
|
||||
share.sub_table.some_key = "some_value"
|
||||
|
||||
local result = share.thread:get()
|
||||
test.assertEquals(result, 52)
|
||||
test.assertEquals(share.sub_table.some_key, "some_value")
|
||||
end
|
||||
|
||||
TestGeneralSharedTableMetaTable = { tearDown = tearDown }
|
||||
|
||||
function TestGeneralSharedTableMetaTable:useMetatable(shared_table, metatable)
|
||||
local mt = self.test_param()
|
||||
for k, v in pairs(metatable) do
|
||||
mt[k] = v
|
||||
end
|
||||
effil.setmetatable(shared_table, mt)
|
||||
end
|
||||
|
||||
function TestGeneralSharedTableMetaTable:testMetamethodIndex()
|
||||
local share = effil.table()
|
||||
self:useMetatable(share, {
|
||||
__index = function(t, key)
|
||||
return "mt_" .. effil.rawget(t, key)
|
||||
end
|
||||
}
|
||||
)
|
||||
share.table_key = "table_value"
|
||||
test.assertEquals(share.table_key, "mt_table_value")
|
||||
end
|
||||
|
||||
function TestGeneralSharedTableMetaTable:testMetamethodNewIndex()
|
||||
local share = effil.table()
|
||||
self:useMetatable(share, {
|
||||
__newindex = function(t, key, value)
|
||||
effil.rawset(t, "mt_" .. key, "mt_" .. value)
|
||||
end
|
||||
}
|
||||
)
|
||||
share.table_key = "table_value"
|
||||
test.assertEquals(share.mt_table_key, "mt_table_value")
|
||||
end
|
||||
|
||||
function TestGeneralSharedTableMetaTable:testMetamethodCall()
|
||||
local share = effil.table()
|
||||
self:useMetatable(share, {
|
||||
__call = function(t, val1, val2, val3)
|
||||
return tostring(val1) .. "_" .. tostring(val2), tostring(val2) .. "_" .. tostring(val3)
|
||||
end
|
||||
}
|
||||
)
|
||||
local first_ret, second_ret = share("val1", "val2", "val3")
|
||||
test.assertEquals(first_ret, "val1_val2")
|
||||
test.assertEquals(second_ret, "val2_val3")
|
||||
end
|
||||
|
||||
local function CreateMetatableTestForBinaryOperator(method_info, op)
|
||||
for _, reversed in pairs({true, false}) do
|
||||
TestGeneralSharedTableMetaTable["testMetamethod" .. method_info.metamethod
|
||||
.. (reversed and "Reversed" or "")] =
|
||||
function(self)
|
||||
local testTable, operand = effil.table(), effil.table()
|
||||
self:useMetatable(testTable, {
|
||||
['__' .. string.lower(method_info.metamethod)] =
|
||||
reversed and function(left, right)
|
||||
right.was_called = true
|
||||
return right.value .. '_'.. left.value
|
||||
end
|
||||
or function(left, right)
|
||||
left.was_called = true
|
||||
return left.value .. '_'.. right.value
|
||||
end
|
||||
}
|
||||
)
|
||||
testTable.was_called = false
|
||||
testTable.value = "left"
|
||||
operand.value = "right"
|
||||
local left_operand, right_operand = unpack(reversed and {operand, testTable} or {testTable, operand})
|
||||
test.assertEquals(op(left_operand, right_operand),
|
||||
method_info.exp_value == nil and "left_right" or method_info.exp_value)
|
||||
test.assertTrue(testTable.was_called)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
CreateMetatableTestForBinaryOperator({ metamethod = "Concat"}, function(a, b) return a.. b end)
|
||||
CreateMetatableTestForBinaryOperator({ metamethod = "Add"}, function(a, b) return a + b end)
|
||||
CreateMetatableTestForBinaryOperator({ metamethod = "Sub"}, function(a, b) return a - b end)
|
||||
CreateMetatableTestForBinaryOperator({ metamethod = "Mul"}, function(a, b) return a * b end)
|
||||
CreateMetatableTestForBinaryOperator({ metamethod = "Div"}, function(a, b) return a / b end)
|
||||
CreateMetatableTestForBinaryOperator({ metamethod = "Mod"}, function(a, b) return a % b end)
|
||||
CreateMetatableTestForBinaryOperator({ metamethod = "Pow"}, function(a, b) return a ^ b end)
|
||||
CreateMetatableTestForBinaryOperator({ metamethod = "Le", exp_value = true }, function(a, b) return a <= b end)
|
||||
CreateMetatableTestForBinaryOperator({ metamethod = "Lt", exp_value = true }, function(a, b) return a < b end)
|
||||
CreateMetatableTestForBinaryOperator({ metamethod = "Eq", exp_value = true },
|
||||
function(a, b) return a == b end)
|
||||
|
||||
local function CreateMetatableTestForUnaryOperator(methodName, op)
|
||||
TestGeneralSharedTableMetaTable["testMetamethod" .. methodName] =
|
||||
function(self)
|
||||
local share = effil.table()
|
||||
self:useMetatable(share, {
|
||||
['__' .. string.lower(methodName)] = function(t)
|
||||
t.was_called = true
|
||||
return t.value .. "_suffix"
|
||||
end
|
||||
}
|
||||
)
|
||||
share.was_called = false
|
||||
share.value = "value"
|
||||
test.assertEquals(op(share), "value_suffix")
|
||||
test.assertTrue(share.was_called)
|
||||
end
|
||||
end
|
||||
|
||||
CreateMetatableTestForUnaryOperator("Unm", function(a) return -a end)
|
||||
CreateMetatableTestForUnaryOperator("ToString", function(a) return tostring(a) end)
|
||||
CreateMetatableTestForUnaryOperator("Len", function(a) return #a end)
|
||||
|
||||
make_test_with_param(TestGeneralSharedTableMetaTable, ".+" --[[ Any test in this test suite]],
|
||||
function() return {} end,
|
||||
function() return effil.table() end
|
||||
)
|
||||
|
||||
TestSharedTableWithMetaTable = { tearDown = tearDown }
|
||||
|
||||
function TestSharedTableWithMetaTable:testMetamethodIterators()
|
||||
local share = effil.table()
|
||||
local iterator = self.test_param
|
||||
effil.setmetatable(share, {
|
||||
["__" .. iterator] = function(table)
|
||||
return function(t, key)
|
||||
local effil = require 'effil'
|
||||
local ret = (key and key * 2) or 1
|
||||
if ret > 2 ^ 10 then
|
||||
return nil
|
||||
end
|
||||
return ret, effil.rawget(t, ret)
|
||||
end, table
|
||||
end
|
||||
}
|
||||
)
|
||||
-- Add some values
|
||||
for i = 0, 10 do
|
||||
local pow = 2 ^ i
|
||||
share[pow] = math.random(pow)
|
||||
end
|
||||
-- Add some noise
|
||||
for i = 1, 100 do
|
||||
share[math.random(1000) * 10 - 1] = math.random(1000)
|
||||
end
|
||||
-- Check that *pairs iterator works
|
||||
local pow_iter = 1
|
||||
for k,v in _G[iterator](share) do
|
||||
test.assertEquals(k, pow_iter)
|
||||
test.assertEquals(v, share[pow_iter])
|
||||
pow_iter = pow_iter * 2
|
||||
end
|
||||
test.assertEquals(pow_iter, 2 ^ 11)
|
||||
end
|
||||
|
||||
make_test_with_param(TestSharedTableWithMetaTable, "testMetamethodIterators", "pairs", "ipairs")
|
||||
|
||||
function TestSharedTableWithMetaTable:testMetatableAsSharedTable()
|
||||
local share = effil.table()
|
||||
local mt = effil.table()
|
||||
effil.setmetatable(share, mt)
|
||||
-- Empty metatable
|
||||
test.assertEquals(share.table_key, nil)
|
||||
|
||||
-- Only __index metamethod
|
||||
mt.__index = function(t, key)
|
||||
return "mt_" .. effil.rawget(t, key)
|
||||
end
|
||||
share.table_key = "table_value"
|
||||
test.assertEquals(share.table_key, "mt_table_value")
|
||||
|
||||
-- Both __index and __newindex metamethods
|
||||
mt.__newindex = function(t, key, value)
|
||||
effil.rawset(t, key, "mt_" .. value)
|
||||
end
|
||||
share.table_key = "table_value"
|
||||
test.assertEquals(share.table_key, "mt_mt_table_value")
|
||||
|
||||
-- Remove __index, use only __newindex metamethods
|
||||
mt.__index = nil
|
||||
share.table_key = "table_value"
|
||||
test.assertEquals(share.table_key, "mt_table_value")
|
||||
end
|
||||
|
||||
@ -47,3 +47,25 @@ end
|
||||
function tearDown()
|
||||
collectgarbage()
|
||||
end
|
||||
|
||||
function make_test_with_param(test_suite, test_case_pattern, ...)
|
||||
local tests_to_delete, tests_to_add = {}, {}
|
||||
for test_name, test_func in pairs(test_suite) do
|
||||
if string.sub(test_name, 1, 4):lower() == 'test' and string.match(test_name, test_case_pattern) then
|
||||
table.insert(tests_to_delete, test_name)
|
||||
for i, param in ipairs({...}) do
|
||||
tests_to_add[test_name .. "/" .. i] = function(t)
|
||||
print("# with params: " .. tostring(param))
|
||||
t.test_param = param
|
||||
return test_func(t)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
for _, test_name in ipairs(tests_to_delete) do
|
||||
test_suite[test_name] = nil
|
||||
end
|
||||
for test_name, test_func in pairs(tests_to_add) do
|
||||
test_suite[test_name] = test_func
|
||||
end
|
||||
end
|
||||
|
||||
@ -171,6 +171,38 @@ function TestThread:testAsyncPauseResumeCancel()
|
||||
test.assertTrue(wait(5, function() return thread:status() == "canceled" end))
|
||||
end
|
||||
|
||||
function TestThread:testCheckThreadReturns()
|
||||
local share = effil.table()
|
||||
share.value = "some value"
|
||||
|
||||
local thread_factory = effil.thread(
|
||||
function(share)
|
||||
return 100500, "string value", true, share, function(a,b) return a + b end
|
||||
end
|
||||
)
|
||||
local thread = thread_factory(share)
|
||||
local status = thread:wait()
|
||||
local returns = { thread:get() }
|
||||
|
||||
log "Check values"
|
||||
test.assertEquals(status, "completed")
|
||||
|
||||
test.assertNumber(returns[1])
|
||||
test.assertEquals(returns[1], 100500)
|
||||
|
||||
test.assertString(returns[2])
|
||||
test.assertEquals(returns[2], "string value")
|
||||
|
||||
test.assertBoolean(returns[3])
|
||||
test.assertTrue(returns[3])
|
||||
|
||||
test.assertUserdata(returns[4])
|
||||
test.assertEquals(returns[4].value, share.value)
|
||||
|
||||
test.assertFunction(returns[5])
|
||||
test.assertEquals(returns[5](11, 89), 100)
|
||||
end
|
||||
|
||||
function TestThread:testTimedCancel()
|
||||
local thread = effil.thread(function()
|
||||
require("effil").sleep(2)
|
||||
@ -179,38 +211,6 @@ function TestThread:testTimedCancel()
|
||||
thread:wait()
|
||||
end
|
||||
|
||||
--function TestThread:testCheckThreadReturns()
|
||||
-- local effil = require 'effil'
|
||||
-- local share = effil.table()
|
||||
-- share.value = "some value"
|
||||
--
|
||||
-- local thread_factory = effil.thread(
|
||||
-- function(share)
|
||||
-- return 100500, "string value", true, share, function(a,b) return a + b end
|
||||
-- end
|
||||
-- )
|
||||
-- local thread = thread_factory(share)
|
||||
-- local status, returns = thread:get()
|
||||
--
|
||||
-- log "Check values"
|
||||
-- test.assertEquals(status, "completed")
|
||||
--
|
||||
-- test.assertNumber(returns[1])
|
||||
-- test.assertEquals(returns[1], 100500)
|
||||
--
|
||||
-- test.assertString(returns[2])
|
||||
-- test.assertEquals(returns[2], "string value")
|
||||
--
|
||||
-- test.assertBoolean(returns[3])
|
||||
-- test.assertTrue(returns[3])
|
||||
--
|
||||
-- test.assertUserdata(returns[4])
|
||||
-- test.assertEquals(returns[4].value, share.value)
|
||||
--
|
||||
-- test.assertFunction(returns[5])
|
||||
-- test.assertEquals(returns[5](11, 89), 100)
|
||||
--end
|
||||
--
|
||||
TestThreadWithTable = {tearDown = tearDown }
|
||||
|
||||
function TestThreadWithTable:testSharedTableTypes()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user