diff --git a/CMakeLists.txt b/CMakeLists.txt index c5ee49e..33b54fd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,7 +14,7 @@ FILE(GLOB SOURCES src/*.cpp src/*.h) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/build) if(APPLE) - # Supress CMP0042 + # Supress warning CMP0042 set(CMAKE_MACOSX_RPATH 1) endif() diff --git a/src/lua-module.cpp b/src/lua-module.cpp index 5bbf001..710d46b 100644 --- a/src/lua-module.cpp +++ b/src/lua-module.cpp @@ -3,24 +3,25 @@ #include -static sol::object create_thread(sol::this_state lua, sol::function func, const sol::variadic_args& args) -{ +namespace { + +static sol::object createThread(sol::this_state lua, sol::function func, const sol::variadic_args &args) noexcept { return sol::make_object(lua, std::make_unique(func, args)); } -static sol::object create_share(sol::this_state lua) -{ +static sol::object createShare(sol::this_state lua) noexcept { return sol::make_object(lua, std::make_unique()); } -extern "C" int luaopen_libwoofer(lua_State *L) -{ +} // namespace + +extern "C" int luaopen_libwoofer(lua_State *L) { sol::state_view lua(L); - threading::LuaThread::get_user_type(lua); - share_data::SharedTable::get_user_type(lua); + threading::LuaThread::getUserType(lua); + share_data::SharedTable::getUserType(lua); sol::table public_api = lua.create_table_with( - "thread", create_thread, - "share", create_share + "thread", createThread, + "share", createShare ); sol::stack::push(lua, public_api); return 1; diff --git a/src/shared-table.cpp b/src/shared-table.cpp index 7fbb396..fcea503 100644 --- a/src/shared-table.cpp +++ b/src/shared-table.cpp @@ -5,7 +5,7 @@ namespace share_data { -sol::object SharedTable::get_user_type(sol::state_view& lua) noexcept { +sol::object SharedTable::getUserType(sol::state_view &lua) noexcept { static sol::usertype type( sol::call_construction(), sol::default_constructor, sol::meta_function::new_index, &share_data::SharedTable::luaSet, @@ -16,6 +16,11 @@ sol::object SharedTable::get_user_type(sol::state_view& lua) noexcept { return sol::stack::pop(lua); } +void SharedTable::set(StoredObject key, StoredObject value) noexcept { + std::lock_guard g(lock_); + data_[std::move(key)] = std::move(value); +} + void SharedTable::luaSet(sol::stack_object luaKey, sol::stack_object luaValue) noexcept { assert(luaKey.valid()); @@ -31,7 +36,7 @@ void SharedTable::luaSet(sol::stack_object luaKey, sol::stack_object luaValue) n } } -sol::object SharedTable::luaGet(sol::stack_object key, sol::this_state state) noexcept { +sol::object SharedTable::luaGet(sol::stack_object key, sol::this_state state) const noexcept { assert(key.valid()); StoredObject cppKey(key); @@ -44,15 +49,16 @@ sol::object SharedTable::luaGet(sol::stack_object key, sol::this_state state) no } } -size_t SharedTable::size() noexcept { +size_t SharedTable::size() const noexcept { std::lock_guard g(lock_); return data_.size(); } SharedTable* TablePool::getNew() noexcept { + SharedTable* ptr = new SharedTable(); std::lock_guard g(lock_); - data_.push_back(std::make_unique()); - return (*data_.rend()).get(); + data_.emplace_back(ptr); + return ptr; } std::size_t TablePool::size() const noexcept { std::lock_guard g(lock_); diff --git a/src/shared-table.h b/src/shared-table.h index f1c9a37..d131f1b 100644 --- a/src/shared-table.h +++ b/src/shared-table.h @@ -15,16 +15,17 @@ class SharedTable { public: SharedTable() = default; virtual ~SharedTable() = default; + + static sol::object getUserType(sol::state_view &lua) noexcept; + void set(StoredObject, StoredObject) noexcept; + size_t size() const noexcept; + +public: // lua bindings void luaSet(sol::stack_object luaKey, sol::stack_object luaValue) noexcept; - sol::object luaGet(sol::stack_object key, sol::this_state state) noexcept; - - static sol::object get_user_type(sol::state_view& lua) noexcept; - -private: // lau bindings - size_t size() noexcept; + sol::object luaGet(sol::stack_object key, sol::this_state state) const noexcept; protected: - SpinMutex lock_; + mutable SpinMutex lock_; std::unordered_map data_; private: @@ -49,4 +50,4 @@ private: TablePool& defaultPool() noexcept; -} // core +} // share_data diff --git a/src/spin-mutex.h b/src/spin-mutex.h index f5b6537..be04e79 100644 --- a/src/spin-mutex.h +++ b/src/spin-mutex.h @@ -21,4 +21,4 @@ private: std::atomic_flag lock_ = ATOMIC_FLAG_INIT; }; -} // core +} // share_data diff --git a/src/stored-object.cpp b/src/stored-object.cpp index a3a05f6..80a0812 100644 --- a/src/stored-object.cpp +++ b/src/stored-object.cpp @@ -1,51 +1,183 @@ #include "stored-object.h" #include "shared-table.h" +#include "utils.h" #include #include +#include #include namespace share_data { -bool FunctionHolder::rawCompare(const BaseHolder* other) const noexcept { - return function_ == static_cast(other)->function_; -} +namespace { -bool FunctionHolder::rawLess(const BaseHolder* other) const noexcept { - return function_ < static_cast(other)->function_; -} +template +class PrimitiveHolder : public BaseHolder { +public: + PrimitiveHolder(sol::stack_object luaObject) noexcept + : data_(luaObject.as()) {} -std::size_t FunctionHolder::hash() const noexcept { - return std::hash()(function_); -} + PrimitiveHolder(sol::object luaObject) noexcept + : data_(luaObject.as()) {} -sol::object FunctionHolder::unpack(sol::this_state state) const noexcept { - sol::state_view lua((lua_State*)state); - sol::function loader = lua["loadstring"]; - assert(loader.valid()); + PrimitiveHolder(const StoredType& init) noexcept + : data_(init) {} - sol::function result = loader(function_); - if (!result.valid()) { - ERROR << "Unable to restore function!" << std::endl; - ERROR << "Content:" << std::endl; - ERROR << function_ << std::endl; + bool rawCompare(const BaseHolder* other) const noexcept final { + assert(type_ == other->type()); + return static_cast*>(other)->data_ == data_; + } + + bool rawLess(const BaseHolder* other) const noexcept final { + assert(type_ == other->type()); + return data_ < static_cast*>(other)->data_; + } + + std::size_t hash() const noexcept final { + return std::hash()(data_); + } + + sol::object unpack(sol::this_state state) const noexcept final { + return sol::make_object(state, data_); + } + +private: + StoredType data_; +}; + +class FunctionHolder : public BaseHolder { +public: + template + FunctionHolder(SolObject luaObject) noexcept { + sol::state_view lua(luaObject.lua_state()); + sol::function dumper = lua["string"]["dump"]; + + assert(dumper.valid()); + function_ = dumper(luaObject); + } + + bool rawCompare(const BaseHolder* other) const noexcept final { + return function_ == static_cast(other)->function_; + } + + bool rawLess(const BaseHolder* other) const noexcept final { + return function_ < static_cast(other)->function_; + } + + std::size_t hash() const noexcept final { + return std::hash()(function_); + } + + sol::object unpack(sol::this_state state) const noexcept final { + sol::state_view lua((lua_State*)state); + sol::function loader = lua["loadstring"]; + assert(loader.valid()); + + sol::function result = loader(function_); + if (!result.valid()) { + ERROR << "Unable to restore function!" << std::endl; + ERROR << "Content:" << std::endl; + ERROR << function_ << std::endl; + } + return sol::make_object(state, result); + } + +private: + std::string function_; +}; + +// This class is used as a storage for visited sol::tables +// TODO: try to use map or unordered map instead of linear search in vector +// TODO: Trick is - sol::object has only operator==:/ +typedef std::vector> SolTableToShared; + +void dumpTable(SharedTable* target, sol::table luaTable, SolTableToShared& visited) noexcept; + +StoredObject makeStoredObject(sol::object luaObject, SolTableToShared& visited) noexcept { + if (luaObject.get_type() == sol::type::table) { + sol::table luaTable = luaObject; + auto comparator = [&luaTable](const std::pair& element){ + return element.first == luaTable; + }; + auto st = std::find_if(visited.begin(), visited.end(), comparator); + + if (st == std::end(visited)) { + SharedTable* table = defaultPool().getNew(); + visited.emplace_back(std::make_pair(luaTable, table)); + dumpTable(table, luaTable, visited); + return StoredObject(table); + } else { + return StoredObject(st->second); + } + } else { + return StoredObject(luaObject); } - return sol::make_object(state, result); } +void dumpTable(SharedTable* target, sol::table luaTable, SolTableToShared& visited) noexcept { + for(auto& row : luaTable) { + target->set(makeStoredObject(row.first, visited), makeStoredObject(row.second, visited)); + } +} + +template +std::unique_ptr fromSolObject(SolObject luaObject) { + switch(luaObject.get_type()) { + case sol::type::nil: + break; + case sol::type::boolean: + return std::make_unique>(luaObject); + case sol::type::number: + return std::make_unique>(luaObject); + case sol::type::string: + return std::make_unique>(luaObject); + case sol::type::userdata: + return std::make_unique>(luaObject); + case sol::type::function: + return std::make_unique(luaObject); + case sol::type::table: + { + sol::table luaTable = luaObject; + // Tables pool is used to store tables. + // Right now not defiantly clear how ownership between states works. + SharedTable* table = defaultPool().getNew(); + SolTableToShared visited{{luaTable, table}}; + + // Let's dump table and all subtables + // SolTableToShared is used to prevent from infinity recursion + // in recursive tables + dumpTable(table, luaTable, visited); + return std::make_unique>(table); + } + default: + ERROR << "Unable to store object of that type: " << (int)luaObject.get_type() << std::endl; + } + return nullptr; +} + +} // namespace + StoredObject::StoredObject(StoredObject&& init) noexcept : data_(std::move(init.data_)) {} -StoredObject::operator bool() const noexcept { - return (bool)data_; -} - StoredObject::StoredObject(SharedTable* table) noexcept : data_(new PrimitiveHolder(table)) { } +StoredObject::StoredObject(sol::object object) noexcept + : data_(fromSolObject(object)) { +} + +StoredObject::StoredObject(sol::stack_object object) noexcept + : data_(fromSolObject(object)) { +} + +StoredObject::operator bool() const noexcept { + return (bool)data_; +} + std::size_t StoredObject::hash() const noexcept { if (data_) return data_->hash(); @@ -79,4 +211,4 @@ bool StoredObject::operator<(const StoredObject& o) const noexcept { return data_.get() < o.data_.get(); } -} +} // share_data diff --git a/src/stored-object.h b/src/stored-object.h index 92dd1e4..50bdcc2 100644 --- a/src/stored-object.h +++ b/src/stored-object.h @@ -7,8 +7,6 @@ namespace share_data { -#define ERROR std::cerr - class BaseHolder { public: BaseHolder() noexcept : type_(sol::type::nil) {} @@ -40,60 +38,6 @@ private: BaseHolder(BaseHolder&) = delete; }; -template -class PrimitiveHolder : public BaseHolder { -public: - PrimitiveHolder(sol::stack_object luaObject) noexcept - : data_(luaObject.as()) {} - - PrimitiveHolder(sol::object luaObject) noexcept - : data_(luaObject.as()) {} - - PrimitiveHolder(const StoredType& init) noexcept - : data_(init) {} - - bool rawCompare(const BaseHolder* other) const noexcept final { - assert(type_ == other->type()); - return static_cast*>(other)->data_ == data_; - } - - bool rawLess(const BaseHolder* other) const noexcept final { - assert(type_ == other->type()); - return data_ < static_cast*>(other)->data_; - } - - std::size_t hash() const noexcept final { - return std::hash()(data_); - } - - sol::object unpack(sol::this_state state) const noexcept final { - return sol::make_object(state, data_); - } - -private: - StoredType data_; -}; - -class FunctionHolder : public BaseHolder { -public: - template - FunctionHolder(SolObject luaObject) noexcept { - sol::state_view lua(luaObject.lua_state()); - sol::function dumper = lua["string"]["dump"]; - - assert(dumper.valid()); - function_ = dumper(luaObject); - } - - bool rawCompare(const BaseHolder* other) const noexcept final; - bool rawLess(const BaseHolder* other) const noexcept final; - std::size_t hash() const noexcept final; - sol::object unpack(sol::this_state state) const noexcept final; - -private: - std::string function_; -}; - class SharedTable; class StoredObject { @@ -101,32 +45,8 @@ public: StoredObject() = default; StoredObject(StoredObject&& init) noexcept; StoredObject(SharedTable*) noexcept; - - template - StoredObject(SolObject luaObject) - { - switch(luaObject.get_type()) { - case sol::type::nil: - break; - case sol::type::boolean: - data_.reset(new PrimitiveHolder(luaObject)); - break; - case sol::type::number: - data_.reset(new PrimitiveHolder(luaObject)); - break; - case sol::type::string: - data_.reset(new PrimitiveHolder(luaObject)); - break; - case sol::type::userdata: - data_.reset(new PrimitiveHolder(luaObject)); - break; - case sol::type::function: - data_.reset(new FunctionHolder(luaObject)); - break; - default: - ERROR << "Unable to store object of that type: " << (int)luaObject.get_type() << std::endl; - } - } + StoredObject(sol::object) noexcept; + StoredObject(sol::stack_object) noexcept; operator bool() const noexcept; std::size_t hash() const noexcept; @@ -147,6 +67,7 @@ private: namespace std { +// For storing as key in std::unordered_map template<> struct hash { std::size_t operator()(const share_data::StoredObject &object) const noexcept { diff --git a/src/threading.cpp b/src/threading.cpp index 1e736af..1ae4250 100644 --- a/src/threading.cpp +++ b/src/threading.cpp @@ -2,7 +2,7 @@ namespace threading { -LuaThread::LuaThread(const sol::function& function, const sol::variadic_args& args) noexcept{ +LuaThread::LuaThread(const sol::function& function, const sol::variadic_args& args) noexcept { // 1. Dump function to string sol::state_view lua(function.lua_state()); str_function_ = lua["string"]["dump"](function); @@ -14,31 +14,27 @@ LuaThread::LuaThread(const sol::function& function, const sol::variadic_args& ar sol::lib::base, sol::lib::string, sol::lib::package, sol::lib::io, sol::lib::os ); - get_user_type(*p_state_); - share_data::SharedTable::get_user_type(*p_state_); + getUserType(*p_state_); + share_data::SharedTable::getUserType(*p_state_); // 3. Save parameters - store_args(args); + storeArgs(args); // 4. Run thread p_thread_.reset(new std::thread(&LuaThread::work, this)); assert(p_thread_.get() != NULL); } -void LuaThread::store_args(const sol::variadic_args& args) noexcept -{ +void LuaThread::storeArgs(const sol::variadic_args &args) noexcept { p_arguments_ = std::make_shared>(); - for(auto iter = args.begin(); iter != args.end(); iter++) - { + for(auto iter = args.begin(); iter != args.end(); iter++) { share_data::StoredObject store(iter->get()); p_arguments_->push_back(store.unpack(sol::this_state{p_state_->lua_state()})); } } -void LuaThread::join() noexcept -{ - if (p_thread_.get()) - { +void LuaThread::join() noexcept { + if (p_thread_.get()) { p_thread_->join(); p_thread_.reset(); } @@ -48,44 +44,38 @@ void LuaThread::join() noexcept p_state_.reset(); } -void LuaThread::detach() noexcept -{ +void LuaThread::detach() noexcept { p_thread_->detach(); } -void LuaThread::work() noexcept -{ - if (p_state_.get() && p_arguments_.get()) - { +void LuaThread::work() noexcept { + if (p_state_.get() && p_arguments_.get()) { std::string func_owner = std::move(str_function_); std::shared_ptr state_owner = p_state_; std::shared_ptr> arguments_owner = p_arguments_; sol::function_result func = (*state_owner)["loadstring"](func_owner); func.get()(sol::as_args(*arguments_owner)); - } - else - { + } else { throw sol::error("Internal error: invalid thread Lua state"); } } -std::string LuaThread::thread_id() noexcept -{ +std::string LuaThread::threadId() noexcept { std::stringstream ss; ss << std::this_thread::get_id(); return ss.str(); } -sol::object LuaThread::get_user_type(sol::state_view& lua) noexcept +sol::object LuaThread::getUserType(sol::state_view &lua) noexcept { static sol::usertype type( sol::call_construction(), sol::constructors>(), "join", &LuaThread::join, "detach", &LuaThread::detach, - "thread_id", &LuaThread::thread_id + "thread_id", &LuaThread::threadId ); sol::stack::push(lua, type); return sol::stack::pop(lua); } -} +} // threading diff --git a/src/threading.h b/src/threading.h index 9b1381b..c4431fc 100644 --- a/src/threading.h +++ b/src/threading.h @@ -10,20 +10,19 @@ namespace threading { -class LuaThread -{ +class LuaThread { public: LuaThread(const sol::function& function, const sol::variadic_args& args) noexcept; virtual ~LuaThread() noexcept = default; void join() noexcept; void detach() noexcept; - static std::string thread_id() noexcept; - static sol::object get_user_type(sol::state_view& lua) noexcept; + static std::string threadId() noexcept; + static sol::object getUserType(sol::state_view &lua) noexcept; private: void work() noexcept; - void store_args(const sol::variadic_args& args) noexcept; + void storeArgs(const sol::variadic_args &args) noexcept; std::string str_function_; std::shared_ptr p_state_; @@ -31,4 +30,4 @@ private: std::shared_ptr> p_arguments_; }; -} +} // threading diff --git a/src/utils.h b/src/utils.h new file mode 100644 index 0000000..ce16d3b --- /dev/null +++ b/src/utils.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +#ifdef NDEBUG +# define ERROR if(false) std::cerr +#else +# define ERROR std::cerr +#endif \ No newline at end of file diff --git a/tests/shared-table.cpp b/tests/shared-table.cpp index d2acc91..89966a7 100644 --- a/tests/shared-table.cpp +++ b/tests/shared-table.cpp @@ -2,21 +2,24 @@ #include "shared-table.h" -using namespace core; +#include + +using namespace share_data; namespace { void bootstrapState(sol::state& lua) { lua.open_libraries( sol::lib::base, - sol::lib::string + sol::lib::string, + sol::lib::table ); - SharedTable::bind(lua); + SharedTable::getUserType(lua); } -} +} // namespace -TEST(sharedTable, singleThreadSet) { +TEST(sharedTable, primitiveTypes) { SharedTable st; sol::state lua; bootstrapState(lua); @@ -30,10 +33,8 @@ st.thr = true st.del = "secret" st.del = nil )"); - if (!res1.valid()) { - FAIL() << "Set res1 failed"; - } + ASSERT_TRUE(res1.valid()) << "Set res1 failed"; ASSERT_EQ(lua["st"]["fst"], std::string("first")); ASSERT_EQ(lua["st"]["snd"], (double)2); ASSERT_EQ(lua["st"]["thr"], true); @@ -48,10 +49,8 @@ st[42] = "answer" st[42] = nil st.deleted = st[42] == nil )"); - if (!res2.valid()) { - FAIL() << "Set res2 failed"; - } + ASSERT_TRUE(res2.valid()) << "Set res2 failed"; ASSERT_EQ(lua["st"][1], 3); ASSERT_EQ(lua["st"][2], std::string("number")); ASSERT_EQ(lua["st"][-1], false); @@ -61,34 +60,13 @@ st.deleted = st[42] == nil st[true] = false st[false] = 9 )"); - if (!res3.valid()) { - FAIL() << "Set res3 failed"; - } + ASSERT_TRUE(res3.valid()) << "Set res3 failed"; ASSERT_EQ(lua["st"][true], false); ASSERT_EQ(lua["st"][false], 9); } -TEST(sharedTable, asGlobalTable) { - sol::state lua; - SharedTable st; - SharedTable::bind(lua); - - lua["st"] = &st; - - lua.script(R"( -_G = st -n = 1 -b = false -s = "Oo" -)"); - - ASSERT_EQ(lua["n"], 1); - ASSERT_EQ(lua["b"], false); - ASSERT_EQ(lua["s"], std::string("Oo")); -} - -TEST(sharedTable, severalStates) { +TEST(sharedTable, multipleStates) { sol::state lua1, lua2; bootstrapState(lua1); bootstrapState(lua2); @@ -153,7 +131,7 @@ st.thr = true)"); ASSERT_EQ(lua["st"]["thr"], true); } -TEST(sharedTable, nestedTables) { +TEST(sharedTable, playingWithSharedTables) { SharedTable recursive, st1, st2; sol::state lua; bootstrapState(lua); @@ -204,3 +182,96 @@ end ASSERT_EQ(sf2(std::string("SUCCESS")).get(), std::string("*SUCCESS*")); } + +TEST(sharedTable, playingWithTables) { + SharedTable st; + sol::state lua; + bootstrapState(lua); + + 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 +)"); + + ASSERT_TRUE(res.valid()); + ASSERT_EQ(lua["st"]["person"]["name"], std::string("John Doe")); + ASSERT_EQ(lua["st"]["person"]["age"], 25); + ASSERT_EQ(lua["st"]["pet"]["type"], std::string("cat")); + ASSERT_EQ(lua["st"]["pet"]["name"], std::string("Tomas")); + ASSERT_EQ(lua["st"]["pet"]["real"], std::string("Яша")); + ASSERT_EQ(lua["st"]["pet"]["spec"]["colour"], std::string("grey")); + ASSERT_EQ(lua["st"]["pet"]["spec"]["legs"], 4); + ASSERT_EQ(lua["st"]["recursive"]["prev"]["next"]["next"]["val"], std::string("recursive")); + + defaultPool().clear(); +} + +TEST(sharedTable, stress) { + sol::state lua; + bootstrapState(lua); + SharedTable st; + + lua["st"] = &st; + + auto res1 = lua.script(R"( +for i = 1, 1000000 do + st[i] = tostring(i) +end +)"); + + ASSERT_TRUE(res1.valid()); + ASSERT_TRUE(st.size() == 1'000'000); + + auto res2 = lua.script(R"( +for i = 1000000, 1, -1 do + st[i] = nil +end +)"); + ASSERT_TRUE(res2.valid()); + ASSERT_TRUE(st.size() == 0); +} + +TEST(sharedTable, stressWithThreads) { + SharedTable st; + + const size_t threadCount = 10; + std::vector threads; + for(size_t i = 0; i < threadCount; i++) { + threads.emplace_back([&st, thrId(i)] { + sol::state lua; + bootstrapState(lua); + lua["st"] = &st; + std::stringstream ss; + ss << "st[" << thrId << "] = 1" << std::endl; + ss << "for i = 1, 100000 do" << std::endl; + ss << " st[" << thrId << "] = " << "st[" << thrId << "] + 1" << std::endl; + ss << "end" << std::endl; + lua.script(ss.str()); + }); + } + + for(auto& thread : threads) { + thread.join(); + } + + sol::state lua; + bootstrapState(lua); + lua["st"] = &st; + for(size_t i = 0; i < threadCount; i++) { + ASSERT_TRUE(lua["st"][i] == 100'001) << (double)lua["st"][i] << std::endl; + } +}