diff --git a/src/cpp/garbage-collector.cpp b/src/cpp/garbage-collector.cpp new file mode 100644 index 0000000..f6e654a --- /dev/null +++ b/src/cpp/garbage-collector.cpp @@ -0,0 +1,89 @@ +#include "garbage-collector.h" + +#include "utils.h" + +#include +#include + +namespace effil { + +GarbageCollector::GarbageCollector() noexcept + : state_(GCState::Idle), + lastCleanup_(0), + step_(200) {} + +GCObject* GarbageCollector::get(GCObjectHandle handle) noexcept { + std::lock_guard g(lock_); + auto it = objects_.find(handle); + if (it == objects_.end()) { + DEBUG << "Null handle " << handle << std::endl; + return nullptr; + } + return it->second.get(); +} + +bool GarbageCollector::has(GCObjectHandle handle) const noexcept { + std::lock_guard g(lock_); + return objects_.find(handle) != objects_.end(); +} + +// Here is the naive tri-color marking +// garbage collecting algorithm implementation. +void GarbageCollector::cleanup() { + std::lock_guard g(lock_); + + if (state_ == GCState::Stopped) return; + assert(state_ != GCState::Running); + state_ = GCState::Running; + + std::vector grey; + std::map> black; + + for(const auto& handleAndObject : objects_) + if (handleAndObject.second->instances() > 1) + grey.push_back(handleAndObject.first); + + while(!grey.empty()) { + GCObjectHandle handle = grey.back(); + grey.pop_back(); + + auto object = objects_[handle]; + black[handle] = object; + for(GCObjectHandle refHandle : object->refers()) + if (black.find(refHandle) == black.end()) + grey.push_back(refHandle); + } + + DEBUG << "Removing " << (objects_.size() - black.size()) + << " out of " << objects_.size() << std::endl; + // Sweep phase + objects_ = std::move(black); + + state_ = GCState::Idle; + lastCleanup_.store(0); +} + +size_t GarbageCollector::size() const noexcept { + std::lock_guard g(lock_); + return objects_.size(); +} + +void GarbageCollector::stop() noexcept { + std::lock_guard g(lock_); + assert(state_ == GCState::Idle || state_ == GCState::Stopped); + state_ = GCState::Stopped; +} + +void GarbageCollector::resume() noexcept { + std::lock_guard g(lock_); + assert(state_ == GCState::Idle || state_ == GCState::Stopped); + state_ = GCState::Idle; +} + + +GarbageCollector& getGC() noexcept { + static GarbageCollector pool; + return pool; +} + +} // effil \ No newline at end of file diff --git a/src/cpp/garbage-collector.h b/src/cpp/garbage-collector.h new file mode 100644 index 0000000..bde6df3 --- /dev/null +++ b/src/cpp/garbage-collector.h @@ -0,0 +1,79 @@ +#pragma once + +#include "spin-mutex.h" + +#include +#include +#include + +namespace effil { + +// Unique handle for all objects spawned from one object. +typedef void* GCObjectHandle; + +// All effil objects that owned in lua code have to inherit this class. +// This type o object can persist in multiple threads and in multiple lua states. +// Child has to care about storing data and concurrent access. +class GCObject { +public: + GCObject() noexcept : refs_(new std::set) {} + GCObject(const GCObject& init) = default; + GCObject(GCObject&& init) = default; + virtual ~GCObject() = default; + + GCObjectHandle handle() const noexcept { return reinterpret_cast(refs_.get()); } + size_t instances() const noexcept { return refs_.use_count(); } + const std::set& refers() const { return *refs_; } + +protected: + std::shared_ptr> refs_; +}; + +enum class GCState { + Idle, + Running, + Stopped +}; + +class GarbageCollector { +public: + GarbageCollector() noexcept; + ~GarbageCollector() = default; + + // This method is used to create all managed objects. + template + ObjectType create(Args&&... args) { + if (lastCleanup_.fetch_add(1) == step_) cleanup(); + auto object = std::make_shared(std::forward(args)...); + + std::lock_guard g(lock_); + objects_[object->handle()] = object; + return *object; + } + + GCObject* get(GCObjectHandle handle) noexcept; + bool has(GCObjectHandle handle) const noexcept; + void cleanup(); + size_t size() const noexcept; + void stop() noexcept; + void resume() noexcept; + size_t step() const noexcept { return step_; } + void step(size_t newStep) noexcept { step_ = newStep; } + GCState state() const noexcept { return state_; } + +private: + mutable std::mutex lock_; + GCState state_; + std::atomic lastCleanup_; + size_t step_; + std::map> objects_; + +private: + GarbageCollector(GarbageCollector&&) = delete; + GarbageCollector(const GarbageCollector&) = delete; +}; + + +GarbageCollector& getGC() noexcept; + +} // effil \ No newline at end of file diff --git a/src/cpp/lua-module.cpp b/src/cpp/lua-module.cpp index 7ae4648..64739bd 100644 --- a/src/cpp/lua-module.cpp +++ b/src/cpp/lua-module.cpp @@ -1,16 +1,19 @@ #include "threading.h" #include "shared-table.h" +#include "garbage-collector.h" #include +using namespace effil; + namespace { sol::object createThreadFactory(sol::this_state lua, const sol::function& func) { - return sol::make_object(lua, std::make_unique(func)); + return sol::make_object(lua, std::make_unique(func)); } sol::object createShare(sol::this_state lua) { - return sol::make_object(lua, std::make_unique()); + return sol::make_object(lua, getGC().create()); } } // namespace @@ -18,13 +21,13 @@ sol::object createShare(sol::this_state lua) { extern "C" int luaopen_libeffil(lua_State *L) { sol::state_view lua(L); effil::LuaThread::getUserType(lua); - effil::SharedTable::getUserType(lua); - effil::ThreadFactory::getUserType(lua); + SharedTable::getUserType(lua); + ThreadFactory::getUserType(lua); sol::table public_api = lua.create_table_with( "thread", createThreadFactory, - "thread_id", effil::threadId, - "sleep", effil::sleep, - "yield", effil::yield, + "thread_id", threadId, + "sleep", sleep, + "yield", yield, "share", createShare ); sol::stack::push(lua, public_api); diff --git a/src/cpp/shared-table.cpp b/src/cpp/shared-table.cpp index bb154e2..9765a91 100644 --- a/src/cpp/shared-table.cpp +++ b/src/cpp/shared-table.cpp @@ -1,10 +1,24 @@ #include "shared-table.h" -#include +#include "utils.h" + #include +#include namespace effil { +SharedTable::SharedTable() noexcept + : GCObject(), + lock_(new SpinMutex()), + data_(new std::unordered_map()){ +} + +SharedTable::SharedTable(const SharedTable& init) noexcept + : GCObject(init), + lock_(init.lock_), + data_(init.data_) { +} + sol::object SharedTable::getUserType(sol::state_view &lua) noexcept { static sol::usertype type( "new", sol::no_constructor, @@ -17,8 +31,12 @@ sol::object SharedTable::getUserType(sol::state_view &lua) noexcept { } void SharedTable::set(StoredObject&& key, StoredObject&& value) noexcept { - std::lock_guard g(lock_); - data_[std::move(key)] = std::move(value); + std::lock_guard g(*lock_); + + if (key.isGCObject()) refs_->insert(key.gcHandle()); + if (value.isGCObject()) refs_->insert(value.gcHandle()); + + (*data_)[std::move(key)] = std::move(value); } void SharedTable::luaSet(const sol::stack_object& luaKey, const sol::stack_object& luaValue) { @@ -26,9 +44,16 @@ void SharedTable::luaSet(const sol::stack_object& luaKey, const sol::stack_objec StoredObject key(luaKey); if (luaValue.get_type() == sol::type::nil) { - std::lock_guard g(lock_); + std::lock_guard g(*lock_); + // in this case object is not obligatory to own data - data_.erase(key); + auto it = (*data_).find(key); + if (it != (*data_).end()) { + if (it->first.isGCObject()) refs_->erase(it->first.gcHandle()); + if (it->second.isGCObject()) refs_->erase(it->second.gcHandle()); + (*data_).erase(it); + } + } else { set(std::move(key), StoredObject(luaValue)); } @@ -38,9 +63,9 @@ sol::object SharedTable::luaGet(const sol::stack_object& key, const sol::this_st ASSERT(key.valid()); StoredObject cppKey(key); - std::lock_guard g(lock_); - auto val = data_.find(cppKey); - if (val == data_.end()) { + std::lock_guard g(*lock_); + auto val = (*data_).find(cppKey); + if (val == (*data_).end()) { return sol::nil; } else { return val->second.unpack(state); @@ -48,29 +73,8 @@ sol::object SharedTable::luaGet(const sol::stack_object& key, const sol::this_st } 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_.emplace_back(ptr); - return ptr; -} -std::size_t TablePool::size() const noexcept { - std::lock_guard g(lock_); - return data_.size(); -} - -void TablePool::clear() noexcept { - std::lock_guard g(lock_); - data_.clear(); -} - -TablePool& defaultPool() noexcept { - static TablePool pool; - return pool; + std::lock_guard g(*lock_); + return data_->size(); } } // effil diff --git a/src/cpp/shared-table.h b/src/cpp/shared-table.h index a04f769..d674ff1 100644 --- a/src/cpp/shared-table.h +++ b/src/cpp/shared-table.h @@ -1,5 +1,6 @@ #pragma once +#include "garbage-collector.h" #include "stored-object.h" #include "spin-mutex.h" @@ -7,47 +8,28 @@ #include #include -#include namespace effil { -class SharedTable { +class SharedTable : public GCObject { public: - SharedTable() = default; + SharedTable() noexcept; + SharedTable(SharedTable&&) = default; + SharedTable(const SharedTable& init) noexcept; 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 + // 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; - -protected: - mutable SpinMutex lock_; - std::unordered_map data_; + size_t size() const noexcept; private: - SharedTable(const SharedTable&) = delete; - SharedTable& operator=(const SharedTable&) = delete; + mutable std::shared_ptr lock_; + std::shared_ptr> data_; }; -class TablePool { -public: - TablePool() = default; - SharedTable* getNew() noexcept; - std::size_t size() const noexcept; - void clear() noexcept; - -private: - mutable SpinMutex lock_; - std::vector> data_; - -private: - TablePool(const TablePool&) = delete; -}; - -TablePool& defaultPool() noexcept; - } // effil + diff --git a/src/cpp/stored-object.cpp b/src/cpp/stored-object.cpp index 54060c9..d3d7341 100644 --- a/src/cpp/stored-object.cpp +++ b/src/cpp/stored-object.cpp @@ -72,26 +72,55 @@ private: std::string function_; }; +class TableHolder : public BaseHolder { +public: + template + TableHolder(const SolType& luaObject) noexcept { + assert(luaObject.template is()); + handle_ = luaObject.template as().handle(); + assert(getGC().has(handle_)); + } + + TableHolder(GCObjectHandle handle) noexcept + : handle_(handle) {} + + bool compare(const BaseHolder *other) const noexcept final { + return BaseHolder::compare(other) && static_cast(other)->handle_ == handle_; + } + + std::size_t hash() const noexcept final { + return std::hash()(handle_); + } + + sol::object unpack(sol::this_state state) const noexcept final { + return sol::make_object(state, *static_cast(getGC().get(handle_))); + } + + GCObjectHandle handle() const noexcept { return handle_; } +private: + GCObjectHandle handle_; +}; + // 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; +typedef std::vector> SolTableToShared; void dumpTable(SharedTable* target, sol::table luaTable, SolTableToShared& visited); StoredObject makeStoredObject(sol::object luaObject, SolTableToShared& visited) { if (luaObject.get_type() == sol::type::table) { sol::table luaTable = luaObject; - auto comparator = [&luaTable](const std::pair& element){ + 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); + SharedTable table = getGC().create(); + visited.emplace_back(std::make_pair(luaTable, table.handle())); + dumpTable(&table, luaTable, visited); + return StoredObject(table.handle()); } else { return StoredObject(st->second); } @@ -118,7 +147,7 @@ std::unique_ptr fromSolObject(const SolObject& luaObject) { case sol::type::string: return std::make_unique>(luaObject); case sol::type::userdata: - return std::make_unique>(luaObject); + return std::make_unique(luaObject); case sol::type::function: return std::make_unique(luaObject); case sol::type::table: @@ -126,14 +155,14 @@ std::unique_ptr fromSolObject(const SolObject& luaObject) { 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}}; + SharedTable table = getGC().create(); + SolTableToShared visited{{luaTable, table.handle()}}; // 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); + dumpTable(&table, luaTable, visited); + return std::make_unique(table.handle()); } default: ERROR << "Unable to store object of that type: " << (int)luaObject.get_type() << "\n"; @@ -146,8 +175,8 @@ std::unique_ptr fromSolObject(const SolObject& luaObject) { StoredObject::StoredObject(StoredObject&& init) noexcept : data_(std::move(init.data_)) {} -StoredObject::StoredObject(SharedTable* table) noexcept - : data_(new PrimitiveHolder(table)) { +StoredObject::StoredObject(GCObjectHandle handle) noexcept + : data_(new TableHolder(handle)) { } StoredObject::StoredObject(const sol::object& object) @@ -176,6 +205,14 @@ sol::object StoredObject::unpack(sol::this_state state) const { return sol::nil; } +bool StoredObject::isGCObject() const noexcept { + return data_->type() == typeid(TableHolder); +} + +GCObjectHandle StoredObject::gcHandle() const noexcept { + return ((TableHolder*)data_.get())->handle(); +} + StoredObject& StoredObject::operator=(StoredObject&& o) noexcept { data_ = std::move(o.data_); return *this; diff --git a/src/cpp/stored-object.h b/src/cpp/stored-object.h index 276afdf..ecd0c32 100644 --- a/src/cpp/stored-object.h +++ b/src/cpp/stored-object.h @@ -1,9 +1,8 @@ #pragma once -#include +#include "garbage-collector.h" -#include -#include +#include namespace effil { @@ -12,9 +11,8 @@ public: BaseHolder() = default; virtual ~BaseHolder() = default; - virtual bool compare(const BaseHolder* other) const noexcept { - return typeid(*this) == typeid(*other); - } + virtual bool compare(const BaseHolder* other) const noexcept { return typeid(*this) == typeid(*other); } + virtual const std::type_info& type() { return typeid(*this); } virtual std::size_t hash() const noexcept = 0; virtual sol::object unpack(sol::this_state state) const = 0; @@ -24,19 +22,22 @@ private: BaseHolder(BaseHolder&) = delete; }; -class SharedTable; - class StoredObject { public: StoredObject() = default; StoredObject(StoredObject&& init) noexcept; - StoredObject(SharedTable*) noexcept; + StoredObject(GCObjectHandle) noexcept; StoredObject(const sol::object&); StoredObject(const sol::stack_object&); + operator bool() const noexcept; std::size_t hash() const noexcept; sol::object unpack(sol::this_state state) const; + + bool isGCObject() const noexcept; + GCObjectHandle gcHandle() const noexcept; + StoredObject& operator=(StoredObject&& o) noexcept; bool operator==(const StoredObject& o) const noexcept; diff --git a/src/cpp/threading.cpp b/src/cpp/threading.cpp index c4f92b1..4b207f7 100644 --- a/src/cpp/threading.cpp +++ b/src/cpp/threading.cpp @@ -1,6 +1,8 @@ #include "threading.h" #include "stored-object.h" +#include "utils.h" + namespace effil { class LuaHookStopException : public std::exception {}; diff --git a/src/cpp/utils.h b/src/cpp/utils.h index 07cd984..1674861 100644 --- a/src/cpp/utils.h +++ b/src/cpp/utils.h @@ -6,7 +6,6 @@ #include namespace effil { -namespace utils { class Exception : public sol::error { public: @@ -28,8 +27,13 @@ private: std::string message_; }; -} // utils } // effil -#define ERROR throw effil::utils::Exception() << __FILE__ << ":" << __LINE__ +#define ERROR throw effil::Exception() << __FILE__ << ":" << __LINE__ #define ASSERT(cond) if (!(cond)) ERROR << "In condition '" << #cond << "': " + +#ifdef NDEBUG +# define DEBUG if (false) std::cout +#else +# define DEBUG std::cout +#endif diff --git a/tests/cpp/garbage-collector.cpp b/tests/cpp/garbage-collector.cpp new file mode 100644 index 0000000..d8c52be --- /dev/null +++ b/tests/cpp/garbage-collector.cpp @@ -0,0 +1,162 @@ +#include + +#include "test-utils.h" +#include "garbage-collector.h" + +#include +#include + +using namespace effil; + +TEST(gc, GCObject) { + GCObject o1; + EXPECT_EQ(o1.instances(), (size_t)1); + + GCObject o2 = getGC().create(); + EXPECT_EQ(o2.instances(), (size_t)2); + + GCObject o3 = getGC().create(); + GCObject o4(o3); + GCObject o5(o4); + EXPECT_EQ(o5.instances(), o3.instances()); + EXPECT_EQ(o5.instances(), (size_t)4); + EXPECT_EQ(o5.handle(), o3.handle()); + EXPECT_NE(o1.handle(), o5.handle()); +} + +TEST(gc, collect) { + getGC().cleanup(); + ASSERT_EQ(getGC().size(), (size_t)0); + + { + GCObject o1 = getGC().create();; + GCObject o2 = getGC().create();; + } + EXPECT_EQ(getGC().size(), (size_t)2); + getGC().cleanup(); + EXPECT_EQ(getGC().size(), (size_t)0); +} + +namespace { + +struct Dummy : public GCObject { + void add(GCObjectHandle ref) { refs_->insert(ref); } +}; + +} + +TEST(gc, withRefs) { + getGC().cleanup(); + { + Dummy root = getGC().create(); + + { + Dummy orphan = getGC().create(); + for(size_t i = 0; i < 3; i++) { + Dummy child = getGC().create(); + root.add(child.handle()); + } + } + EXPECT_EQ(getGC().size(), (size_t) 5); + getGC().cleanup(); + EXPECT_EQ(getGC().size(), (size_t) 4); + } + getGC().cleanup(); + EXPECT_EQ(getGC().size(), (size_t)0); +} + +TEST(gc, autoCleanup) { + std::vector threads; + size_t objectsPerThread = 1000; + + for(size_t i = 0; i < 5; i++) + threads.emplace_back([=]{ + for(size_t i = 0; i < objectsPerThread; i++) + getGC().create(); + }); + + for(auto& thread : threads) thread.join(); + + EXPECT_LT(getGC().size(), getGC().step()); +} + +TEST(gc, gcInLuaState) { + sol::state lua; + bootstrapState(lua); + + lua["st"] = getGC().create(); + lua.script(R"( +for i=1,1000 do +st[i] = {"Wow"} +end +)"); + EXPECT_EQ(getGC().size(), (size_t)1001); + +lua.script(R"( +for i=1,1000 do +st[i] = nil +end +)"); + getGC().cleanup(); + EXPECT_EQ(getGC().size(), (size_t)1); +} + +TEST(gc, cycles) { + { + sol::state lua; + bootstrapState(lua); + getGC().cleanup(); + + lua["st"] = getGC().create(); + lua.script(R"( +st.parent = {} +st.parent.child = { ref = st.parent } +st[4] = { one = 1 } +st[5] = { flag = true } +)"); + EXPECT_EQ(getGC().size(), (size_t)5); + + lua.script("st.parent = nil"); + + lua.collect_garbage(); + getGC().cleanup(); + EXPECT_EQ(getGC().size(), (size_t)3); + } + getGC().cleanup(); + EXPECT_EQ(getGC().size(), (size_t)0); +} + +TEST(gc, multipleStates) { + sol::state lua1; + sol::state lua2; + bootstrapState(lua1); + bootstrapState(lua2); + + { + SharedTable st = getGC().create(); + lua1["st"] = st; + lua2["st"] = st; + } + getGC().cleanup(); + EXPECT_EQ(getGC().size(), (size_t)1); + + lua1.script(R"( +st.men = { name = "John", age = 22 } +st.car = { name = "Lada", model = 12 } +st.cat = { name = "Tomas" } +st.fish = { name = "Herbert" } + +st.men.car = st.car +st.men.cat = st.cat +st.men.fish = st.fish +)"); + getGC().cleanup(); + EXPECT_EQ(getGC().size(), (size_t)5); + + lua2.script("copy = { st.men } st = nil"); + lua1.script("st = nil"); + lua1.collect_garbage(); + lua2.collect_garbage(); + getGC().cleanup(); + EXPECT_EQ(getGC().size(), (size_t)4); +} diff --git a/tests/cpp/shared-table.cpp b/tests/cpp/shared-table.cpp index 2054ab5..eb9cb5a 100644 --- a/tests/cpp/shared-table.cpp +++ b/tests/cpp/shared-table.cpp @@ -1,30 +1,18 @@ #include +#include "test-utils.h" #include "shared-table.h" +#include "garbage-collector.h" #include using namespace effil; -namespace { - -void bootstrapState(sol::state& lua) { - lua.open_libraries( - sol::lib::base, - sol::lib::string, - sol::lib::table - ); - SharedTable::getUserType(lua); -} - -} // namespace - TEST(sharedTable, primitiveTypes) { - SharedTable st; sol::state lua; bootstrapState(lua); - lua["st"] = &st; + lua["st"] = SharedTable(); auto res1 = lua.script(R"( st.fst = "first" @@ -92,28 +80,28 @@ TEST(sharedTable, multipleThreads) { std::vector threads; - threads.emplace_back([&](){ + threads.emplace_back([=](){ sol::state lua; bootstrapState(lua);; - lua["st"] = &st; + lua["st"] = st; lua.script(R"( while not st.ready do end st.fst = true)"); }); - threads.emplace_back([&](){ + threads.emplace_back([=](){ sol::state lua; bootstrapState(lua); - lua["st"] = &st; + lua["st"] = st; lua.script(R"( while not st.ready do end st.snd = true)"); }); - threads.emplace_back([&](){ + threads.emplace_back([=](){ sol::state lua; bootstrapState(lua); - lua["st"] = &st; + lua["st"] = st; lua.script(R"( while not st.ready do end st.thr = true)"); @@ -121,7 +109,7 @@ st.thr = true)"); sol::state lua; bootstrapState(lua); - lua["st"] = &st; + lua["st"] = st; lua.script("st.ready = true"); for(auto& thread : threads) { thread.join(); } @@ -132,13 +120,12 @@ st.thr = true)"); } TEST(sharedTable, playingWithSharedTables) { - SharedTable recursive, st1, st2; sol::state lua; bootstrapState(lua); - lua["recursive"] = &recursive; - lua["st1"] = &st1; - lua["st2"] = &st2; + lua["recursive"] = getGC().create(); + lua["st1"] = getGC().create(); + lua["st2"] = getGC().create(); lua.script(R"( st1.proxy = st2 @@ -155,7 +142,7 @@ TEST(sharedTable, playingWithFunctions) { sol::state lua; bootstrapState(lua); - lua["st"] = &st; + lua["st"] = st; lua.script(R"( st.fn = function () @@ -171,7 +158,7 @@ st.fn() sol::state lua2; bootstrapState(lua2); - lua2["st2"] = &st; + lua2["st2"] = st; lua2.script(R"( st2.fn2 = function(str) return "*" .. str .. "*" @@ -188,7 +175,7 @@ TEST(sharedTable, playingWithTables) { sol::state lua; bootstrapState(lua); - lua["st"] = &st; + lua["st"] = st; auto res = lua.script(R"( st.works = "fine" st.person = {name = 'John Doe', age = 25} @@ -216,8 +203,6 @@ st.recursive = recursive EXPECT_EQ(lua["st"]["pet"]["spec"]["colour"], std::string("grey")); EXPECT_EQ(lua["st"]["pet"]["spec"]["legs"], 4); EXPECT_EQ(lua["st"]["recursive"]["prev"]["next"]["next"]["val"], std::string("recursive")); - - defaultPool().clear(); } TEST(sharedTable, stress) { @@ -225,7 +210,7 @@ TEST(sharedTable, stress) { bootstrapState(lua); SharedTable st; - lua["st"] = &st; + lua["st"] = st; auto res1 = lua.script(R"( for i = 1, 1000000 do @@ -251,14 +236,14 @@ TEST(sharedTable, stressWithThreads) { const size_t threadCount = 10; std::vector threads; for(size_t i = 0; i < threadCount; i++) { - threads.emplace_back([&st, thrId(i)] { + threads.emplace_back([=] { sol::state lua; bootstrapState(lua); - lua["st"] = &st; + lua["st"] = st; std::stringstream ss; - ss << "st[" << thrId << "] = 1" << std::endl; + ss << "st[" << i << "] = 1" << std::endl; ss << "for i = 1, 100000 do" << std::endl; - ss << " st[" << thrId << "] = " << "st[" << thrId << "] + 1" << std::endl; + ss << " st[" << i << "] = " << "st[" << i << "] + 1" << std::endl; ss << "end" << std::endl; lua.script(ss.str()); }); @@ -270,7 +255,7 @@ TEST(sharedTable, stressWithThreads) { sol::state lua; bootstrapState(lua); - lua["st"] = &st; + lua["st"] = st; for(size_t i = 0; i < threadCount; i++) { EXPECT_TRUE(lua["st"][i] == 100'001) << (double)lua["st"][i] << std::endl; } diff --git a/tests/cpp/test_main.cpp b/tests/cpp/test-main.cpp similarity index 100% rename from tests/cpp/test_main.cpp rename to tests/cpp/test-main.cpp diff --git a/tests/cpp/test-utils.h b/tests/cpp/test-utils.h new file mode 100644 index 0000000..637611b --- /dev/null +++ b/tests/cpp/test-utils.h @@ -0,0 +1,17 @@ +#pragma once + +#include "shared-table.h" +#include + +namespace effil { + +inline void bootstrapState(sol::state& lua) { + lua.open_libraries( + sol::lib::base, + sol::lib::string, + sol::lib::table + ); + SharedTable::getUserType(lua); +} + +} // namespace