Initial SharedTable support

This commit is contained in:
Ilia Udalov 2017-01-18 00:21:52 +03:00
parent 8aa635f8c1
commit 74b4e61a36
13 changed files with 605 additions and 12 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
.idea
.work_dir
build
*.user

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "src/sol"]
path = src/sol
url = https://github.com/ThePhD/sol2.git

View File

@ -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})
FILE(GLOB LUA_SOURCES lua-api/*.lua)
install(FILES ${TESTS} ${LUA_SOURCES} DESTINATION ${CMAKE_LIBRARY_OUTPUT_DIRECTORY})

View File

@ -1,5 +1,6 @@
--local thr = require('libbevy')
require('libbevy')
package.cpath = package.cpath .. ";./?.dylib"
require('libwoofer')
local thr = thread
return {
new = function(func)

67
src/shared-table.cpp Normal file
View File

@ -0,0 +1,67 @@
#include "shared-table.h"
#include <cassert>
namespace core {
void SharedTable::bind(sol::state_view& lua) noexcept {
lua.new_usertype<SharedTable>("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<SpinMutex> g(lock_);
// in this case object is not obligatory to own data
data_.erase(key);
} else {
StoredObject value(luaValue);
std::lock_guard<SpinMutex> 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<SpinMutex> 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<SpinMutex> g(lock_);
return data_.size();
}
SharedTable* TablePool::getNew() noexcept {
std::lock_guard<SpinMutex> g(lock_);
data_.push_back(std::make_unique<SharedTable>());
return (*data_.rend()).get();
}
std::size_t TablePool::size() const noexcept {
std::lock_guard<SpinMutex> g(lock_);
return data_.size();
}
void TablePool::clear() noexcept {
std::lock_guard<SpinMutex> g(lock_);
data_.clear();
}
TablePool& defaultPool() noexcept {
static TablePool pool;
return pool;
}
} // core

53
src/shared-table.h Normal file
View File

@ -0,0 +1,53 @@
#pragma once
#include "stored-object.h"
#include "spin-mutex.h"
#include <sol.hpp>
#include <unordered_map>
#include <memory>
#include <vector>
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<StoredObject, StoredObject> 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<std::unique_ptr<SharedTable>> data_;
private:
TablePool(const TablePool&) = delete;
};
TablePool& defaultPool() noexcept;
} // core

1
src/sol Submodule

@ -0,0 +1 @@
Subproject commit 039331e163bb3a4870426e7b8ae7b55b53ecc579

24
src/spin-mutex.h Normal file
View File

@ -0,0 +1,24 @@
#pragma once
#include <atomic>
#include <thread>
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

118
src/stored-object.cpp Normal file
View File

@ -0,0 +1,118 @@
#include "stored-object.h"
#include "shared-table.h"
#include <map>
#include <vector>
#include <iostream>
#include <cassert>
#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<const FunctionHolder*>(other)->function_;
}
bool FunctionHolder::rawLess(const BaseHolder* other) const noexcept {
return function_ < static_cast<const FunctionHolder*>(other)->function_;
}
std::size_t FunctionHolder::hash() const noexcept {
return std::hash<std::string>()(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<bool>(luaObject));
break;
case sol::type::number:
data_.reset(new PrimitiveHolder<double>(luaObject));
break;
case sol::type::string:
data_.reset(new PrimitiveHolder<std::string>(luaObject));
break;
case sol::type::userdata:
data_.reset(new PrimitiveHolder<SharedTable*>(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<SharedTable*>(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

120
src/stored-object.h Normal file
View File

@ -0,0 +1,120 @@
#pragma once
#include <sol.hpp>
#include <utility>
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<typename StoredType>
class PrimitiveHolder : public BaseHolder {
public:
PrimitiveHolder(sol::stack_object luaObject) noexcept
: data_(luaObject.as<StoredType>()) {
}
PrimitiveHolder(const StoredType& init) noexcept
: data_(init) {}
bool rawCompare(const BaseHolder* other) const noexcept final {
assert(type_ == other->type());
return static_cast<const PrimitiveHolder<StoredType>*>(other)->data_ == data_;
}
bool rawLess(const BaseHolder* other) const noexcept final {
assert(type_ == other->type());
return data_ < static_cast<const PrimitiveHolder<StoredType>*>(other)->data_;
}
std::size_t hash() const noexcept final {
return std::hash<StoredType>()(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<BaseHolder> data_;
private:
StoredObject(const StoredObject&) = delete;
StoredObject& operator=(const StoredObject&) = delete;
};
} // core
namespace std {
template<>
struct hash<core::StoredObject> {
std::size_t operator()(const core::StoredObject &object) const noexcept {
return object.hash();
}
};
} // std

View File

@ -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<LuaThread>("thread",

198
tests/shared-table.cpp Normal file
View File

@ -0,0 +1,198 @@
#include <boost/test/unit_test.hpp>
#include "shared-table.h"
#include "lua-utils.h"
#include <logging.h>
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<SharedTable>();
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<std::thread> 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>() == std::string("*SUCCESS*"));
}

View File

@ -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'])