diff --git a/libs/sol b/libs/sol index 5b12924..3342e65 160000 --- a/libs/sol +++ b/libs/sol @@ -1 +1 @@ -Subproject commit 5b12924d9eab43b9621b8784b5ed576686c2fb50 +Subproject commit 3342e65b385aac57caca3b8284713682c9ea1211 diff --git a/src/cpp/lua-helpers.h b/src/cpp/lua-helpers.h index 8b8361c..7c7b7aa 100644 --- a/src/cpp/lua-helpers.h +++ b/src/cpp/lua-helpers.h @@ -1,5 +1,6 @@ #pragma once +#include "stored-object.h" #include "utils.h" #include @@ -26,4 +27,21 @@ inline sol::function loadString(const sol::state_view& lua, const std::string& s return loader(str); } -} // namespace effil \ No newline at end of file +typedef std::vector StoredArray; + +} // namespace effil + +namespace sol { +namespace stack { + template<> + struct pusher { + 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 diff --git a/src/cpp/lua-module.cpp b/src/cpp/lua-module.cpp index 0d5789c..5a1d544 100644 --- a/src/cpp/lua-module.cpp +++ b/src/cpp/lua-module.cpp @@ -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(path, cpath, stepwise, step, function, args)); + return sol::make_object(lua, std::make_shared(path, cpath, stepwise, step, function, args)); } sol::object createTable(sol::this_state lua) { return sol::make_object(lua, getGC().create()); } @@ -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; } diff --git a/src/cpp/shared-table.cpp b/src/cpp/shared-table.cpp index bbce185..30daa8e 100644 --- a/src/cpp/shared-table.cpp +++ b/src/cpp/shared-table.cpp @@ -7,24 +7,44 @@ namespace effil { -SharedTable::SharedTable() - : GCObject() - , data_(std::make_shared()) {} +namespace { + +template +bool isSharedTable(const SolObject& obj) { + return obj.valid() && obj.get_type() == sol::type::userdata && obj.template is(); +} + +} + +SharedTable::SharedTable() : GCObject(), data_(std::make_shared()) {} SharedTable::SharedTable(const SharedTable& init) : GCObject(init) , data_(init.data_) {} -sol::object SharedTable::getUserType(sol::state_view& lua) { - sol::usertype 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 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(lua); + sol::stack::pop(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 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 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 lock(data_->lock); \ + if (data_->metatable != GCNull) { \ + SharedTable tableHolder = *static_cast(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(); + auto data_ = table.data_; + DEFFINE_METAMETHOD_CALL(metamethodName, table, rightObject) + } + if (isSharedTable(rightObject)) { + SharedTable table = rightObject.as(); + 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 lock(data_->lock); + if (data_->metatable != GCNull) { + SharedTable tableHolder = *static_cast(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 lock(data_->lock); + if (data_->metatable != GCNull) { + sol::function handler = static_cast(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())); + 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 g(data_->lock); size_t len = 0u; sol::optional value; @@ -93,7 +216,7 @@ size_t SharedTable::length() const { } while ((iter != data_->entries.end()) && (value = storedObjectToDouble(iter->first)) && (static_cast(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(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(begin->first->unpack(lua), begin->second->unpack(lua)); + return PairsIterator(begin->first->unpack(lua), begin->second->unpack(lua)); } } - return std::tuple(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::make_object( - lua, std::function(next)) - .as(), - 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(next)).as(), + sol::make_object(state, *this)); } -std::tuple ipairsNext(sol::this_state lua, SharedTable table, - sol::optional key) { +std::pair ipairsNext(sol::this_state lua, SharedTable table, sol::optional key) { size_t index = key ? key.value() + 1 : 1; auto objKey = createStoredObject(static_cast(index)); sol::object value = table.get(objKey, lua); if (!value.valid()) - return std::tuple(sol::nil, sol::nil); - return std::tuple(objKey->unpack(lua), value); + return std::pair(sol::nil, sol::nil); + return std::pair(objKey->unpack(lua), value); } -std::tuple SharedTable::ipairs(sol::this_state lua) const { - return std::tuple(sol::make_object(lua, ipairsNext).as(), - 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::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 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 lock(stable.data_->lock); + return stable.data_->metatable == GCNull ? sol::nil : + sol::make_object(state, *static_cast(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 g(stable.data_->lock); + return stable.data_->entries.size(); +} + +#undef DEFFINE_METAMETHOD_CALL_0 +#undef DEFFINE_METAMETHOD_CALL +#undef PROXY_METAMETHOD_IMPL + } // effil diff --git a/src/cpp/shared-table.h b/src/cpp/shared-table.h index ac1a0fe..ee8a276 100644 --- a/src/cpp/shared-table.h +++ b/src/cpp/shared-table.h @@ -3,6 +3,8 @@ #include "garbage-collector.h" #include "stored-object.h" #include "spin-mutex.h" +#include "utils.h" +#include "lua-helpers.h" #include @@ -13,7 +15,7 @@ namespace effil { class SharedTable : public GCObject { private: - typedef std::tuple PairsIterator; + typedef std::pair PairsIterator; typedef std::map 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 data_; }; + } // effil diff --git a/src/cpp/stored-object.cpp b/src/cpp/stored-object.cpp index c4fd65a..11be2dd 100644 --- a/src/cpp/stored-object.cpp +++ b/src/cpp/stored-object.cpp @@ -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>(luaObject); case sol::type::string: return std::make_unique>(luaObject); + case sol::type::lightuserdata: + return std::make_unique>(luaObject); case sol::type::userdata: - return std::make_unique(luaObject); + if (luaObject.template is()) + return std::make_unique(luaObject); + else if (luaObject.template is>()) + return std::make_unique>>(luaObject); + else + throw Exception() << "Unable to store userdata object\n"; case sol::type::function: return std::make_unique(luaObject); case sol::type::table: { diff --git a/src/cpp/stored-object.h b/src/cpp/stored-object.h index f551bdb..17f3b0a 100644 --- a/src/cpp/stored-object.h +++ b/src/cpp/stored-object.h @@ -27,7 +27,7 @@ private: BaseHolder(BaseHolder&) = delete; }; -typedef std::unique_ptr StoredObject; +typedef std::shared_ptr StoredObject; struct StoredObjectLess { bool operator()(const StoredObject& lhs, const StoredObject& rhs) const { return lhs->compare(rhs.get()); } diff --git a/src/cpp/threading.cpp b/src/cpp/threading.cpp index cf86559..78ee97c 100644 --- a/src/cpp/threading.cpp +++ b/src/cpp/threading.cpp @@ -3,7 +3,6 @@ #include "stored-object.h" #include "notifier.h" #include "spin-mutex.h" -#include "lua-helpers.h" #include "utils.h" #include @@ -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 handle, - const std::string &strFunction, - std::vector &&arguments) { + std::string strFunction, + effil::StoredArray arguments) { ScopeGuard reportComplete([=](){ DEBUG << "Finished " << std::endl; @@ -139,15 +138,19 @@ void runThread(std::shared_ptr 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()); + 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 arguments; + effil::StoredArray arguments; for (const auto& arg : variadicArgs) { - StoredObject store = createStoredObject(arg.get()); - arguments.push_back(store->unpack(sol::this_state{handle_->lua})); + arguments.emplace_back(createStoredObject(arg.get())); } - 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 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(lua); + sol::stack::pop(lua); } std::pair 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 Thread::wait(const sol::this_state& lua, return status(lua); } -sol::object Thread::get(const sol::this_state& lua, - const sol::optional& duration, +StoredArray Thread::get(const sol::optional& duration, const sol::optional& 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&, diff --git a/src/cpp/threading.h b/src/cpp/threading.h index 1fead13..697a40c 100644 --- a/src/cpp/threading.h +++ b/src/cpp/threading.h @@ -1,6 +1,7 @@ #pragma once #include +#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 status(const sol::this_state& state); std::pair wait(const sol::this_state& state, const sol::optional& duration, const sol::optional& period); - sol::object get(const sol::this_state& state, - const sol::optional& duration, + StoredArray get(const sol::optional& duration, const sol::optional& period); bool cancel(const sol::this_state& state, const sol::optional& duration, @@ -43,7 +43,6 @@ private: private: bool waitFor(const sol::optional& duration, const sol::optional& period); - private: Thread(const Thread&) = delete; Thread& operator=(const Thread&) = delete; diff --git a/src/lua/effil.lua b/src/lua/effil.lua index 6d06203..b4dfe13 100644 --- a/src/lua/effil.lua +++ b/src/lua/effil.lua @@ -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, ...) diff --git a/tests/cpp/shared-table.cpp b/tests/cpp/shared-table.cpp index dd1bb0d..a07fddf 100644 --- a/tests/cpp/shared-table.cpp +++ b/tests/cpp/shared-table.cpp @@ -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(); 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(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().value, &lightUserdata); +} diff --git a/tests/lua/run_tests.lua b/tests/lua/run_tests.lua index 3d4d780..ce915a7 100755 --- a/tests/lua/run_tests.lua +++ b/tests/lua/run_tests.lua @@ -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() ) \ No newline at end of file diff --git a/tests/lua/shared_table.lua b/tests/lua/shared_table.lua index d849bd5..70ca408 100644 --- a/tests/lua/shared_table.lua +++ b/tests/lua/shared_table.lua @@ -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 diff --git a/tests/lua/test_utils.lua b/tests/lua/test_utils.lua index 6c7c178..0a8efe4 100644 --- a/tests/lua/test_utils.lua +++ b/tests/lua/test_utils.lua @@ -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 diff --git a/tests/lua/thread.lua b/tests/lua/thread.lua index 5eaf1af..1237b8d 100644 --- a/tests/lua/thread.lua +++ b/tests/lua/thread.lua @@ -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()