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:
mihacooper 2017-04-06 18:54:00 +03:00 committed by Ilia
parent c09adc8115
commit cac3cd09e7
15 changed files with 696 additions and 188 deletions

@ -1 +1 @@
Subproject commit 5b12924d9eab43b9621b8784b5ed576686c2fb50
Subproject commit 3342e65b385aac57caca3b8284713682c9ea1211

View File

@ -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);
}
} // namespace effil
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

View File

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

View File

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

View File

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

View File

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

View File

@ -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()); }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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