diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..02d83f2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.idea +.work_dir +build +*.user \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..6ad9de7 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "src/sol"] + path = src/sol + url = https://github.com/ThePhD/sol2.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 97a0dc7..0d585c3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 2.8) -project(bevy) +project(woofer) find_package(Lua REQUIRED) @@ -7,20 +7,24 @@ if( "${LUA_VERSION_MAJOR}.${LUA_VERSION_MINOR}" LESS "5.2") message(FATAL_ERROR "Wrong Lua version ${LUA_VERSION_MAJOR}.${LUA_VERSION_MINOR}, use 5.2 or higher") endif() -include_directories(src/lib src/sol2/single/sol ${LUA_INCLUDE_DIR}) +include_directories(src src/sol/single/sol ${LUA_INCLUDE_DIR}) -FILE(GLOB SOURCES src/lib/*.cpp) +FILE(GLOB SOURCES src/*.cpp src/*.h) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/build) -add_library(bevy SHARED ${SOURCES}) -target_link_libraries(bevy -lpthread ${LUA_LIBRARY}) -set_target_properties(bevy PROPERTIES COMPILE_FLAGS "-Wall -Wextra -pedantic -O3 -g -std=c++14 -pthread") +add_library(woofer SHARED ${SOURCES}) +target_link_libraries(woofer -lpthread ${LUA_LIBRARY}) + +set(GENERAL "-std=c++14 -pthread") +set(ENABLE_WARNINGS "-Wall -Wextra -pedantic") +set(BUILD_FLAVOR "-O3 -UNDEBUG") +set_target_properties(woofer PROPERTIES COMPILE_FLAGS "${ENABLE_WARNINGS} ${GENERAL} ${BUILD_FLAVOR}") #---------- # INSTALL - #---------- FILE(GLOB TESTS tests/*) -FILE(GLOB LUA_SOURCES src/*.lua) -install(FILES ${TESTSz} ${LUA_SOURCES} DESTINATION ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}) \ No newline at end of file +FILE(GLOB LUA_SOURCES lua-api/*.lua) +install(FILES ${TESTS} ${LUA_SOURCES} DESTINATION ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}) \ No newline at end of file diff --git a/lua-api/woofer.lua b/lua-api/woofer.lua index 8f185a9..625fc5e 100644 --- a/lua-api/woofer.lua +++ b/lua-api/woofer.lua @@ -1,5 +1,6 @@ --local thr = require('libbevy') -require('libbevy') +package.cpath = package.cpath .. ";./?.dylib" +require('libwoofer') local thr = thread return { new = function(func) diff --git a/src/shared-table.cpp b/src/shared-table.cpp new file mode 100644 index 0000000..3d75446 --- /dev/null +++ b/src/shared-table.cpp @@ -0,0 +1,67 @@ +#include "shared-table.h" + +#include + +namespace core { + +void SharedTable::bind(sol::state_view& lua) noexcept { + lua.new_usertype("shared_table", + sol::meta_function::new_index, &SharedTable::luaSet, + sol::meta_function::index, &SharedTable::luaGet, + sol::meta_function::length, &SharedTable::size); +} + +void SharedTable::luaSet(sol::stack_object luaKey, sol::stack_object luaValue) noexcept { + assert(luaKey.valid()); + + StoredObject key(luaKey); + if (luaValue.get_type() == sol::type::nil) { + std::lock_guard g(lock_); + // in this case object is not obligatory to own data + data_.erase(key); + } else { + StoredObject value(luaValue); + std::lock_guard g(lock_); + data_.emplace(std::make_pair(std::move(key), std::move(value))); + } +} + +sol::object SharedTable::luaGet(sol::stack_object key, sol::this_state state) noexcept { + assert(key.valid()); + + StoredObject cppKey(key); + std::lock_guard g(lock_); + auto val = data_.find(cppKey); + if (val == data_.end()) { + return sol::nil; + } else { + return val->second.unpack(state); + } +} + +size_t SharedTable::size() noexcept { + std::lock_guard g(lock_); + return data_.size(); +} + +SharedTable* TablePool::getNew() noexcept { + std::lock_guard g(lock_); + data_.push_back(std::make_unique()); + return (*data_.rend()).get(); +} +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; +} + +} // core \ No newline at end of file diff --git a/src/shared-table.h b/src/shared-table.h new file mode 100644 index 0000000..e8bc3d5 --- /dev/null +++ b/src/shared-table.h @@ -0,0 +1,53 @@ +#pragma once + +#include "stored-object.h" +#include "spin-mutex.h" + +#include + +#include +#include +#include + +namespace core { + +class SharedTable { +public: + SharedTable() = default; + virtual ~SharedTable() = default; + void luaSet(sol::stack_object luaKey, sol::stack_object luaValue) noexcept; + sol::object luaGet(sol::stack_object key, sol::this_state state) noexcept; + + // Add usertype to state + static void bind(sol::state_view& lua) noexcept; + +private: // lau bindings + size_t size() noexcept; + +protected: + SpinMutex lock_; + std::unordered_map data_; + +private: + SharedTable(const SharedTable&) = delete; + SharedTable& operator=(const SharedTable&) = delete; +}; + +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; + +} // core \ No newline at end of file diff --git a/src/sol b/src/sol new file mode 160000 index 0000000..039331e --- /dev/null +++ b/src/sol @@ -0,0 +1 @@ +Subproject commit 039331e163bb3a4870426e7b8ae7b55b53ecc579 diff --git a/src/spin-mutex.h b/src/spin-mutex.h new file mode 100644 index 0000000..d497a32 --- /dev/null +++ b/src/spin-mutex.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include + +namespace core { + +class SpinMutex { +public: + void lock() noexcept { + while(lock_.test_and_set(std::memory_order_acquire)) { + std::this_thread::yield(); + } + } + + void unlock() noexcept { + lock_.clear(std::memory_order_release); + } + +private: + std::atomic_flag lock_ = ATOMIC_FLAG_INIT; +}; + +} // core diff --git a/src/stored-object.cpp b/src/stored-object.cpp new file mode 100644 index 0000000..2e695b7 --- /dev/null +++ b/src/stored-object.cpp @@ -0,0 +1,118 @@ +#include "stored-object.h" + +#include "shared-table.h" + +#include +#include +#include + +#include + +#define ERROR std::cerr + +namespace core { + +FunctionHolder::FunctionHolder(sol::stack_object luaObject) noexcept { + sol::state_view lua(luaObject.lua_state()); + sol::function dumper = lua["string"]["dump"]; + + assert(dumper.valid()); + function_ = dumper(luaObject); +} + +bool FunctionHolder::rawCompare(const BaseHolder* other) const noexcept { + return function_ == static_cast(other)->function_; +} + +bool FunctionHolder::rawLess(const BaseHolder* other) const noexcept { + return function_ < static_cast(other)->function_; +} + +std::size_t FunctionHolder::hash() const noexcept { + return std::hash()(function_); +} + +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()); + + 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); +} + +StoredObject::StoredObject(StoredObject&& init) noexcept + : data_(std::move(init.data_)) {} + +StoredObject::StoredObject(sol::stack_object luaObject) noexcept + : data_(nullptr) { + 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::operator bool() const noexcept { + return (bool)data_; +} + +StoredObject::StoredObject(SharedTable* table) noexcept + : data_(new PrimitiveHolder(table)) { +} + +std::size_t StoredObject::hash() const noexcept { + if (data_) + return data_->hash(); + else + return 0; +} + +sol::object StoredObject::unpack(sol::this_state state) const noexcept { + if (data_) + return data_->unpack(state); + else + return sol::nil; +} + +StoredObject& StoredObject::operator=(StoredObject&& o) noexcept { + data_ = std::move(o.data_); + return *this; +} + +bool StoredObject::operator==(const StoredObject& o) const noexcept { + if (data_) + return data_->compare(o.data_.get()); + else + return data_.get() == o.data_.get(); +} + +bool StoredObject::operator<(const StoredObject& o) const noexcept { + if (data_) + return data_->less(o.data_.get()); + else + return data_.get() < o.data_.get(); +} + +} // core \ No newline at end of file diff --git a/src/stored-object.h b/src/stored-object.h new file mode 100644 index 0000000..9897171 --- /dev/null +++ b/src/stored-object.h @@ -0,0 +1,120 @@ +#pragma once + +#include + +#include + +namespace core { + +class BaseHolder { +public: + BaseHolder() noexcept : type_(sol::type::nil) {} + virtual ~BaseHolder() = default; + + sol::type type() const noexcept { + return type_; + } + + bool compare(const BaseHolder* other) const noexcept { + assert(other != nullptr); + return type_ == other->type_ && rawCompare(other); + } + virtual bool less(const BaseHolder* other) const noexcept { + assert(other != nullptr); + return type_ < other->type_ && rawLess(other); + } + + virtual bool rawCompare(const BaseHolder* other) const noexcept = 0; + virtual bool rawLess(const BaseHolder* other) const noexcept = 0; + virtual std::size_t hash() const noexcept = 0; + virtual sol::object unpack(sol::this_state state) const noexcept = 0; + +protected: + sol::type type_; + +private: + BaseHolder(const BaseHolder&) = delete; + BaseHolder(BaseHolder&) = delete; +}; + +template +class PrimitiveHolder : public BaseHolder { +public: + PrimitiveHolder(sol::stack_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: + FunctionHolder(sol::stack_object luaObject) noexcept; + 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 { +public: + StoredObject() = default; + StoredObject(StoredObject&& init) noexcept; + StoredObject(sol::stack_object luaObject) noexcept; + //StoredObject(sol::object luaObject) noexcept; + StoredObject(SharedTable*) noexcept; + + operator bool() const noexcept; + std::size_t hash() const noexcept; + sol::object unpack(sol::this_state state) const noexcept; + StoredObject& operator=(StoredObject&& o) noexcept; + bool operator==(const StoredObject& o) const noexcept; + bool operator<(const StoredObject& o) const noexcept; + +private: + std::unique_ptr data_; + +private: + StoredObject(const StoredObject&) = delete; + StoredObject& operator=(const StoredObject&) = delete; +}; + +} // core + +namespace std { + +template<> +struct hash { + std::size_t operator()(const core::StoredObject &object) const noexcept { + return object.hash(); + } +}; + +} // std \ No newline at end of file diff --git a/src/threading.cpp b/src/threading.cpp index 30f7bb6..5b3e799 100644 --- a/src/threading.cpp +++ b/src/threading.cpp @@ -77,7 +77,7 @@ static std::string ThreadId() return ss.str(); } -extern "C" int luaopen_libbevy(lua_State *L) +extern "C" int luaopen_libwoofer(lua_State *L) { sol::state_view lua(L); lua.new_usertype("thread", diff --git a/tests/shared-table.cpp b/tests/shared-table.cpp new file mode 100644 index 0000000..7f85c35 --- /dev/null +++ b/tests/shared-table.cpp @@ -0,0 +1,198 @@ +#include + +#include "shared-table.h" +#include "lua-utils.h" + +#include + +using namespace core; + +BOOST_AUTO_TEST_CASE(singleThreadSet) { + sol::state lua; + bootstrapState(lua); + + SharedTable st; + + lua["st"] = &st; + + auto res1 = lua.script(R"( +st.fst = "first" +st.snd = 2 +st.thr = true +st.del = "secret" +st.del = nil +)"); + if (!res1.valid()) { + BOOST_FAIL("Set res1 failed"); + } + + BOOST_CHECK(lua["st"]["fst"] == std::string("first")); + BOOST_CHECK(lua["st"]["snd"] == (double)2); + BOOST_CHECK(lua["st"]["thr"] == true); + BOOST_CHECK(lua["st"]["del"] == sol::nil); + BOOST_CHECK(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 +)"); + if (!res2.valid()) { + BOOST_FAIL("Set res2 failed"); + } + + BOOST_CHECK(lua["st"][1] == 3); + BOOST_CHECK(lua["st"][2] == std::string("number")); + BOOST_CHECK(lua["st"][-1] == false); + BOOST_CHECK(lua["st"]["deleted"] == true); + + auto res3 = lua.script(R"( +st[true] = false +st[false] = 9 +)"); + if (!res3.valid()) { + BOOST_FAIL("Set res3 failed"); + } + + BOOST_CHECK(lua["st"][true] == false); + BOOST_CHECK(lua["st"][false] == 9); +} + +BOOST_AUTO_TEST_CASE(asGlobalTable) { + sol::state lua; + SharedTable st; + bootstrapState(lua); + + lua["st"] = &st; + + lua.script(R"( +_G = st +n = 1 +b = false +s = "Oo" +)"); + + BOOST_CHECK(lua["n"] == 1); + BOOST_CHECK(lua["b"] == false); + BOOST_CHECK(lua["s"] == std::string("Oo")); +} + +BOOST_AUTO_TEST_CASE(severalStates) { + sol::state lua1, lua2; + bootstrapState(lua1); + bootstrapState(lua2); + + auto st = std::make_unique(); + + lua1["cats"] = st.get(); + lua2["dogs"] = st.get(); + + auto res1 = lua1.script(R"( +cats.fluffy = "gav" +cats.sparky = false +cats.wow = 3 +)"); + + BOOST_CHECK(lua2["dogs"]["fluffy"] == std::string("gav")); + BOOST_CHECK(lua2["dogs"]["sparky"] == false); + BOOST_CHECK(lua2["dogs"]["wow"] == 3); +} + +BOOST_AUTO_TEST_CASE(multipleThreads) { + SharedTable st; + + std::vector threads; + + threads.emplace_back([&](){ + sol::state lua; + bootstrapState(lua); + lua["st"] = &st; + lua.script(R"( +while not st.ready do end +st.fst = true)"); + }); + + threads.emplace_back([&](){ + sol::state lua; + bootstrapState(lua); + lua["st"] = &st; + lua.script(R"( +while not st.ready do end +st.snd = true)"); + }); + + threads.emplace_back([&](){ + sol::state lua; + bootstrapState(lua); + lua["st"] = &st; + lua.script(R"( +while not st.ready do end +st.thr = true)"); + }); + + sol::state lua; + bootstrapState(lua); + lua["st"] = &st; + lua.script("st.ready = true"); + + for(auto& thread : threads) { thread.join(); } + + BOOST_CHECK(lua["st"]["fst"] == true); + BOOST_CHECK(lua["st"]["snd"] == true); + BOOST_CHECK(lua["st"]["thr"] == true); +} + +BOOST_AUTO_TEST_CASE(nestedTables) { + SharedTable recursive, st1, st2; + sol::state lua; + bootstrapState(lua); + + lua["recursive"] = &recursive; + lua["st1"] = &st1; + lua["st2"] = &st2; + + lua.script(R"( +st1.proxy = st2 +st1.proxy.value = true +recursive.next = recursive +recursive.val = "yes" +)"); + BOOST_CHECK(lua["st2"]["value"] == true); + BOOST_CHECK(lua["recursive"]["next"]["next"]["next"]["val"] == std::string("yes")); +} + +BOOST_AUTO_TEST_CASE(playingWithFunctions) { + SharedTable st; + sol::state lua; + bootstrapState(lua); + + lua["st"] = &st; + + lua.script(R"( +st.fn = function () + print "Hello C++" + return true +end +st.fn() +)"); + + sol::function sf = lua["st"]["fn"]; + BOOST_CHECK((bool)sf()); + + sol::state lua2; + bootstrapState(lua2); + + lua2["st2"] = &st; + lua2.script(R"( +st2.fn2 = function(str) + return "*" .. str .. "*" +end +)"); + + sol::function sf2 = lua["st"]["fn2"]; + + BOOST_CHECK(sf2(std::string("SUCCESS")).get() == std::string("*SUCCESS*")); +} diff --git a/tests/smoke_test.lua b/tests/smoke_test.lua index d0078c5..75fa632 100644 --- a/tests/smoke_test.lua +++ b/tests/smoke_test.lua @@ -1,7 +1,7 @@ -local thr = require('bevy') +local thr = require('woofer') t = thr.new( function() - local thr = require('bevy') + local thr = require('woofer') print(share['key1']) print(share['key2']) print(share['key3'])