SharedTable able to store lua tables && related tests.

Fix codestyle.
This commit is contained in:
Ilia Udalov 2017-01-21 22:39:58 +03:00
parent 7e35ab3a76
commit 5407a33947
11 changed files with 328 additions and 198 deletions

View File

@ -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()

View File

@ -3,24 +3,25 @@
#include <lua.hpp>
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<threading::LuaThread>(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<share_data::SharedTable>());
}
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;

View File

@ -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<share_data::SharedTable> 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<sol::object>(lua);
}
void SharedTable::set(StoredObject key, StoredObject value) noexcept {
std::lock_guard<SpinMutex> 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<SpinMutex> g(lock_);
return data_.size();
}
SharedTable* TablePool::getNew() noexcept {
SharedTable* ptr = new SharedTable();
std::lock_guard<SpinMutex> g(lock_);
data_.push_back(std::make_unique<SharedTable>());
return (*data_.rend()).get();
data_.emplace_back(ptr);
return ptr;
}
std::size_t TablePool::size() const noexcept {
std::lock_guard<SpinMutex> g(lock_);

View File

@ -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<StoredObject, StoredObject> data_;
private:
@ -49,4 +50,4 @@ private:
TablePool& defaultPool() noexcept;
} // core
} // share_data

View File

@ -21,4 +21,4 @@ private:
std::atomic_flag lock_ = ATOMIC_FLAG_INIT;
};
} // core
} // share_data

View File

@ -1,51 +1,183 @@
#include "stored-object.h"
#include "shared-table.h"
#include "utils.h"
#include <map>
#include <vector>
#include <algorithm>
#include <cassert>
namespace share_data {
bool FunctionHolder::rawCompare(const BaseHolder* other) const noexcept {
return function_ == static_cast<const FunctionHolder*>(other)->function_;
}
namespace {
bool FunctionHolder::rawLess(const BaseHolder* other) const noexcept {
return function_ < static_cast<const FunctionHolder*>(other)->function_;
}
template<typename StoredType>
class PrimitiveHolder : public BaseHolder {
public:
PrimitiveHolder(sol::stack_object luaObject) noexcept
: data_(luaObject.as<StoredType>()) {}
std::size_t FunctionHolder::hash() const noexcept {
return std::hash<std::string>()(function_);
}
PrimitiveHolder(sol::object luaObject) noexcept
: data_(luaObject.as<StoredType>()) {}
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<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:
template<typename SolObject>
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<const FunctionHolder*>(other)->function_;
}
bool rawLess(const BaseHolder* other) const noexcept final {
return function_ < static_cast<const FunctionHolder*>(other)->function_;
}
std::size_t hash() const noexcept final {
return std::hash<std::string>()(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<std::pair<sol::object, SharedTable*>> 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<sol::table, SharedTable*>& 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<typename SolObject>
std::unique_ptr<BaseHolder> fromSolObject(SolObject luaObject) {
switch(luaObject.get_type()) {
case sol::type::nil:
break;
case sol::type::boolean:
return std::make_unique<PrimitiveHolder<bool>>(luaObject);
case sol::type::number:
return std::make_unique<PrimitiveHolder<double>>(luaObject);
case sol::type::string:
return std::make_unique<PrimitiveHolder<std::string>>(luaObject);
case sol::type::userdata:
return std::make_unique<PrimitiveHolder<SharedTable*>>(luaObject);
case sol::type::function:
return std::make_unique<FunctionHolder>(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<PrimitiveHolder<SharedTable*>>(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<SharedTable*>(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

View File

@ -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<typename StoredType>
class PrimitiveHolder : public BaseHolder {
public:
PrimitiveHolder(sol::stack_object luaObject) noexcept
: data_(luaObject.as<StoredType>()) {}
PrimitiveHolder(sol::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:
template<typename SolObject>
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<typename SolObject>
StoredObject(SolObject luaObject)
{
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(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<share_data::StoredObject> {
std::size_t operator()(const share_data::StoredObject &object) const noexcept {

View File

@ -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<std::vector<sol::object>>();
for(auto iter = args.begin(); iter != args.end(); iter++)
{
for(auto iter = args.begin(); iter != args.end(); iter++) {
share_data::StoredObject store(iter->get<sol::object>());
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<sol::state> state_owner = p_state_;
std::shared_ptr<std::vector<sol::object>> arguments_owner = p_arguments_;
sol::function_result func = (*state_owner)["loadstring"](func_owner);
func.get<sol::function>()(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<LuaThread> type(
sol::call_construction(), sol::constructors<sol::types<sol::function, sol::variadic_args>>(),
"join", &LuaThread::join,
"detach", &LuaThread::detach,
"thread_id", &LuaThread::thread_id
"thread_id", &LuaThread::threadId
);
sol::stack::push(lua, type);
return sol::stack::pop<sol::object>(lua);
}
}
} // threading

View File

@ -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<sol::state> p_state_;
@ -31,4 +30,4 @@ private:
std::shared_ptr<std::vector<sol::object>> p_arguments_;
};
}
} // threading

9
src/utils.h Normal file
View File

@ -0,0 +1,9 @@
#pragma once
#include <iostream>
#ifdef NDEBUG
# define ERROR if(false) std::cerr
#else
# define ERROR std::cerr
#endif

View File

@ -2,21 +2,24 @@
#include "shared-table.h"
using namespace core;
#include <thread>
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>(), 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<std::thread> 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;
}
}