effil/src/cpp/threading.cpp
2017-02-08 00:06:26 +03:00

264 lines
8.2 KiB
C++

#include "threading.h"
#include "stored-object.h"
#include "utils.h"
namespace effil {
class LuaHookStopException : public std::exception {};
std::string threadId() noexcept
{
std::stringstream ss;
ss << std::this_thread::get_id();
return ss.str();
}
void yield() noexcept
{
std::this_thread::yield();
}
void sleep(int64_t time, sol::optional<std::string> period) noexcept
{
std::string metric = period ? period.value() : "s";
if (metric == "ms")
std::this_thread::sleep_for(std::chrono::milliseconds(time));
else if (metric == "s")
std::this_thread::sleep_for(std::chrono::seconds(time));
else if (metric == "m")
std::this_thread::sleep_for(std::chrono::minutes(time));
else
throw sol::error("invalid time identificator: " + metric);
}
thread_local LuaThread::ThreadData* LuaThread::pThreadLocalData = NULL;
// class LuaThread
LuaThread::LuaThread(std::shared_ptr<ThreadData> threadData, const std::string& function, const sol::variadic_args& args) {
pThreadData_ = threadData;
ASSERT(pThreadData_.get());
pThreadData_->command = ThreadCommand::Nothing;
pThreadData_->status = ThreadStatus::Running;
std::vector<sol::object> arguments;
for(const auto& iter: args) {
StoredObject store(iter.get<sol::object>());
arguments.push_back(store.unpack(sol::this_state{pThreadData_->luaState}));
}
pThread_.reset(new std::thread(&LuaThread::work, pThreadData_, function, std::move(arguments)));
ASSERT(pThread_.get() != nullptr);
pThread_->detach();
}
void LuaThread::luaHook(lua_State*, lua_Debug*)
{
if (pThreadLocalData)
{
switch (pThreadLocalData->command)
{
case ThreadCommand::Pause:
{
pThreadLocalData->status = ThreadStatus::Paused;
ThreadCommand cmd = pThreadLocalData->command;
while (cmd == ThreadCommand::Pause) {
std::this_thread::yield();
cmd = pThreadLocalData->command;
}
assert(cmd != ThreadCommand::Nothing);
if (cmd == ThreadCommand::Resume)
{
pThreadLocalData->status = ThreadStatus::Running;
break; // Just go out of the function
}
else { /* HOOK_STOP - do nothing and go to the next case */}
}
case ThreadCommand::Cancel:
throw LuaHookStopException();
default:
case ThreadCommand::Nothing:
break;
}
}
}
void LuaThread::work(std::shared_ptr<ThreadData> threadData, const std::string strFunction, std::vector<sol::object>&& arguments) noexcept {
try {
pThreadLocalData = threadData.get();
ASSERT(threadData.get()) << "invalid internal thread state\n";
const sol::object& stringLoader = threadData->luaState["loadstring"];
ASSERT(stringLoader.valid() && stringLoader.get_type() == sol::type::function);
sol::function userFuncObj = static_cast<const sol::function&>(stringLoader)(strFunction);
sol::function_result results = userFuncObj(sol::as_args(arguments));
(void)results; // TODO: try to avoid use of useless sol::function_result here
sol::variadic_args args(threadData->luaState, -lua_gettop(threadData->luaState));
for(const auto& iter: args) {
StoredObject store(iter.get<sol::object>());
threadData->results.emplace_back(std::move(store));
}
threadData->status = ThreadStatus::Completed;
}
catch (const LuaHookStopException&) {
threadData->status = ThreadStatus::Canceled;
}
catch (const sol::error& err) {
threadData->status = ThreadStatus::Failed;
sol::stack::push(threadData->luaState, err.what());
StoredObject store(sol::stack::pop<sol::object>(threadData->luaState));
threadData->results.emplace_back(std::move(store));
}
}
void LuaThread::cancel() noexcept
{
pThreadData_->command = ThreadCommand::Cancel;
}
void LuaThread::pause() noexcept
{
pThreadData_->command = ThreadCommand::Pause;
}
void LuaThread::resume() noexcept
{
pThreadData_->command = ThreadCommand::Resume;
}
std::tuple<sol::object, sol::table> LuaThread::wait(sol::this_state state) const noexcept
{
ThreadStatus stat = pThreadData_->status;
while (stat == ThreadStatus::Running) {
std::this_thread::yield();
stat = pThreadData_->status;
}
sol::table returns = sol::state_view(state).create_table();
if (stat == ThreadStatus::Completed)
{
for (const StoredObject& obj: pThreadData_->results)
{
returns.add(obj.unpack(state));
}
}
return std::make_tuple(sol::make_object(state, threadStatusToString(stat)), std::move(returns));
}
std::string LuaThread::threadStatusToString(ThreadStatus stat) const noexcept
{
switch(stat)
{
case ThreadStatus::Running: return "running";
case ThreadStatus::Paused: return "paused";
case ThreadStatus::Canceled: return "canceled";
case ThreadStatus::Completed: return "completed";
case ThreadStatus::Failed: return "failed";
}
assert(false);
return "unknown";
}
std::string LuaThread::status() const noexcept
{
return threadStatusToString(pThreadData_->status);
}
sol::object LuaThread::getUserType(sol::state_view &lua) noexcept
{
static sol::usertype<LuaThread> type(
"new", sol::no_constructor,
"cancel", &LuaThread::cancel,
"pause", &LuaThread::pause,
"resume", &LuaThread::resume,
"status", &LuaThread::status,
"wait", &LuaThread::wait
);
sol::stack::push(lua, type);
return sol::stack::pop<sol::object>(lua);
}
// class ThreadFactory
ThreadFactory::ThreadFactory(const sol::function& func) : stepwise_(false), step_(100U) {
sol::state_view lua(func.lua_state());
const sol::object& dumper = lua["string"]["dump"];
ASSERT(dumper.valid() && dumper.get_type() == sol::type::function)
<< "Unable to get string.dump()";
strFunction_ = static_cast<const sol::function&>(dumper)(func);
// Inherit all pathes from parent state by default
packagePath_ = lua["package"]["path"].get<std::string>();
packageCPath_ = lua["package"]["cpath"].get<std::string>();
}
std::unique_ptr<LuaThread> ThreadFactory::runThread(const sol::variadic_args& args) {
std::shared_ptr<LuaThread::ThreadData> threadData(new LuaThread::ThreadData);
ASSERT(threadData.get());
threadData->luaState.open_libraries(
sol::lib::base, sol::lib::string,
sol::lib::package, sol::lib::io, sol::lib::os
);
if (stepwise_)
lua_sethook(threadData->luaState, LuaThread::luaHook, LUA_MASKCOUNT, step_);
threadData->luaState["package"]["path"] = packagePath_;
threadData->luaState["package"]["cpath"] = packageCPath_;
// Inherit all pathes from parent state
effil::LuaThread::getUserType(threadData->luaState);
effil::ThreadFactory::getUserType(threadData->luaState);
effil::SharedTable::getUserType(threadData->luaState);
return std::make_unique<LuaThread>(threadData, strFunction_, args);
}
bool ThreadFactory::stepwise(const sol::optional<bool>& value)
{
bool ret = stepwise_ ;
if (value)
stepwise_ = value.value();
return ret;
}
unsigned int ThreadFactory::step(const sol::optional<unsigned int>& value)
{
bool ret = step_;
if (value)
step_ = value.value();
return ret;
}
std::string ThreadFactory::packagePath(const sol::optional<std::string>& value)
{
std::string& ret = packagePath_;
if (value)
packagePath_ = value.value();
return ret;
}
std::string ThreadFactory::packageCPath(const sol::optional<std::string>& value)
{
std::string& ret = packageCPath_;
if (value)
packageCPath_ = value.value();
return ret;
}
sol::object ThreadFactory::getUserType(sol::state_view &lua) noexcept
{
static sol::usertype<ThreadFactory> type(
"new", sol::no_constructor,
sol::meta_function::call, &ThreadFactory::runThread,
"stepwise", &ThreadFactory::stepwise,
"step", &ThreadFactory::step,
"package_path", &ThreadFactory::packagePath,
"package_cpath", &ThreadFactory::packageCPath
);
sol::stack::push(lua, type);
return sol::stack::pop<sol::object>(lua);
}
} // effil