Merge pull request #17 from skkuull/ui_scripting

UI scripting
This commit is contained in:
fed 2022-03-02 15:04:15 +01:00 committed by GitHub
commit f9a24aebfc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 1747 additions and 10 deletions

View File

@ -435,7 +435,7 @@ namespace dvars
dvar_set_float_hook.create(SELECT_VALUE(0x1403C7420, 0x1404FD360), &dvar_set_float);
dvar_set_int_hook.create(SELECT_VALUE(0x1403C76C0, 0x1404FD5E0), &dvar_set_int);
dvar_set_string_hook.create(SELECT_VALUE(0x1403C7900, 0x1404FD8D0), &dvar_set_string);
dvar_set_from_string_hook.create(SELECT_VALUE(0, 0x1404FD520), &dvar_set_from_string);
dvar_set_from_string_hook.create(SELECT_VALUE(0x1403C7620, 0x1404FD520), &dvar_set_from_string);
}
};
}

View File

@ -15,11 +15,6 @@ namespace lui
public:
void post_unpack() override
{
if (!game::environment::is_mp())
{
return;
}
// Don't show create cod account popup
//utils::hook::set<uint32_t>(0x14017C957, 0); // H1(1.4)

View File

@ -0,0 +1,180 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "game/dvars.hpp"
#include "scheduler.hpp"
#include "command.hpp"
#include "ui_scripting.hpp"
#include "game/ui_scripting/lua/engine.hpp"
#include "game/ui_scripting/execution.hpp"
#include "game/ui_scripting/lua/error.hpp"
#include <utils/string.hpp>
#include <utils/hook.hpp>
namespace ui_scripting
{
namespace
{
std::unordered_map<game::hks::cclosure*, sol::protected_function> converted_functions;
utils::hook::detour hksi_lual_error_hook;
utils::hook::detour hksi_lual_error_hook2;
utils::hook::detour hks_start_hook;
utils::hook::detour hks_shutdown_hook;
utils::hook::detour hks_allocator_hook;
utils::hook::detour hks_frame_hook;
bool error_hook_enabled = false;
void hksi_lual_error_stub(game::hks::lua_State* s, const char* fmt, ...)
{
char va_buffer[2048] = {0};
va_list ap;
va_start(ap, fmt);
vsprintf_s(va_buffer, fmt, ap);
va_end(ap);
const auto formatted = std::string(va_buffer);
if (!error_hook_enabled)
{
return hksi_lual_error_hook.invoke<void>(s, formatted.data());
}
else
{
throw std::runtime_error(formatted);
}
}
void* hks_start_stub(char a1)
{
const auto _1 = gsl::finally([]()
{
ui_scripting::lua::engine::start();
});
return hks_start_hook.invoke<void*>(a1);
}
void hks_shutdown_stub()
{
ui_scripting::lua::engine::stop();
hks_shutdown_hook.invoke<void*>();
}
void* hks_allocator_stub(void* userData, void* oldMemory, unsigned __int64 oldSize, unsigned __int64 newSize)
{
const auto closure = reinterpret_cast<game::hks::cclosure*>(oldMemory);
if (converted_functions.find(closure) != converted_functions.end())
{
converted_functions.erase(closure);
}
return hks_allocator_hook.invoke<void*>(userData, oldMemory, oldSize, newSize);
}
void hks_frame_stub()
{
const auto state = *game::hks::lua_state;
if (state)
{
ui_scripting::lua::engine::run_frame();
}
}
}
int main_function_handler(game::hks::lua_State* state)
{
const auto value = state->m_apistack.base[-1];
if (value.t != game::hks::TCFUNCTION)
{
return 0;
}
const auto closure = reinterpret_cast<game::hks::cclosure*>(value.v.cClosure);
if (converted_functions.find(closure) == converted_functions.end())
{
return 0;
}
const auto function = converted_functions[closure];
const auto count = static_cast<int>(state->m_apistack.top - state->m_apistack.base);
const auto arguments = get_return_values(count);
const auto s = function.lua_state();
std::vector<sol::lua_value> converted_args;
for (const auto& argument : arguments)
{
converted_args.push_back(lua::convert(s, argument));
}
const auto results = function(sol::as_args(converted_args));
lua::handle_error(results);
for (const auto& result : results)
{
push_value(lua::convert({s, result}));
}
return results.return_count();
}
void add_converted_function(game::hks::cclosure* closure, const sol::protected_function& function)
{
converted_functions[closure] = function;
}
void clear_converted_functions()
{
converted_functions.clear();
}
void enable_error_hook()
{
error_hook_enabled = true;
}
void disable_error_hook()
{
error_hook_enabled = false;
}
class component final : public component_interface
{
public:
void post_unpack() override
{
if (game::environment::is_dedi())
{
return;
}
hks_start_hook.create(SELECT_VALUE(0x1400E4B40, 0x140176A40), hks_start_stub);
hks_shutdown_hook.create(SELECT_VALUE(0x1400DD3D0, 0x14016CA80), hks_shutdown_stub);
hksi_lual_error_hook.create(SELECT_VALUE(0x1400A5EA0, 0x14012F300), hksi_lual_error_stub);
hks_allocator_hook.create(SELECT_VALUE(0x14009B570, 0x14012BAC0), hks_allocator_stub);
hks_frame_hook.create(SELECT_VALUE(0x1400E37F0, 0x1401755B0), hks_frame_stub);
if (game::environment::is_mp())
{
hksi_lual_error_hook2.create(0x1401366B0, hksi_lual_error_stub);
}
command::add("lui_restart", []()
{
utils::hook::invoke<void>(SELECT_VALUE(0x1400DD3D0, 0x14016CA80));
utils::hook::invoke<void>(SELECT_VALUE(0x1400E6170, 0x1401780D0));
});
}
};
}
REGISTER_COMPONENT(ui_scripting::component)

View File

@ -0,0 +1,12 @@
#pragma once
#include "game/ui_scripting/lua/value_conversion.hpp"
namespace ui_scripting
{
int main_function_handler(game::hks::lua_State* state);
void add_converted_function(game::hks::cclosure* closure, const sol::protected_function& function);
void clear_converted_functions();
void enable_error_hook();
void disable_error_hook();
}

View File

@ -97,9 +97,9 @@ namespace game
WEAK symbol<int(XAssetType type)> DB_GetXAssetTypeSize{0x14019A3B0, 0x14028BE70};
WEAK symbol<void(int clientNum, const char* menu, int a3, int a4, unsigned int a5)> LUI_OpenMenu{0, 0x1404CD210};
WEAK symbol<void(int clientNum, const char* menu, int a3, int a4, unsigned int a5)> LUI_OpenMenu{0x14039D5F0, 0x1404CD210};
WEAK symbol<bool(int clientNum, const char* menu)> Menu_IsMenuOpenAndVisible{0, 0x1404C7320};
WEAK symbol<bool(int clientNum, const char* menu)> Menu_IsMenuOpenAndVisible{0x1404709C0, 0x1404C7320};
WEAK symbol<scr_string_t(const char* str)> SL_FindString{0x140314AF0, 0x14043B470};
@ -136,8 +136,8 @@ namespace game
WEAK symbol<const char* (const char*)> UI_GetMapDisplayName{0, 0x140408CC0};
WEAK symbol<const char* (const char*)> UI_GetGameTypeDisplayName{0, 0x1404086A0};
WEAK symbol<void(unsigned int localClientNum, const char** args)> UI_RunMenuScript{0, 0x1404CFE60};
WEAK symbol<int(const char* text, int maxChars, Font_s* font, float scale)> UI_TextWidth{0, 0x1404D21A0};
WEAK symbol<void(unsigned int localClientNum, const char** args)> UI_RunMenuScript{0x14039EFF0, 0x1404CFE60};
WEAK symbol<int(const char* text, int maxChars, Font_s* font, float scale)> UI_TextWidth{0x1403A0F20, 0x1404D21A0};
WEAK symbol<const char*(const char* string)> UI_SafeTranslateString{0x140350430, 0x14041C580};
@ -183,4 +183,19 @@ namespace game
{
WEAK symbol<gentity_s> g_entities{0x14550DD90, 0};
}
namespace hks
{
WEAK symbol<lua_State*> lua_state{0x141E2C2F8, 0x1426D3D08};
WEAK symbol<void(lua_State* s, const char* str, unsigned int l)> hksi_lua_pushlstring{0x14004DA90, 0x1400624F0};
WEAK symbol<HksObject*(HksObject* result, lua_State* s, const HksObject* table, const HksObject* key)> hks_obj_getfield{0x14009C0A0, 0x14012C600};
WEAK symbol<void(lua_State* s, const HksObject* tbl, const HksObject* key, const HksObject* val)> hks_obj_settable{0x14009D240, 0x14012D820};
WEAK symbol<HksObject* (HksObject* result, lua_State* s, const HksObject* table, const HksObject* key)> hks_obj_gettable{0x14009C580, 0x14012CAE0};
WEAK symbol<void(lua_State* s, int nargs, int nresults, const unsigned int* pc)> vm_call_internal{0x1400C87A0, 0x140159EB0};
WEAK symbol<HashTable*(lua_State* s, unsigned int arraySize, unsigned int hashSize)> Hashtable_Create{0x14008B3B0, 0x14011B320};
WEAK symbol<cclosure*(lua_State* s, lua_function function, int num_upvalues,
int internal_, int profilerTreatClosureAsFunc)> cclosure_Create{0x14008B5D0, 0x14011B540};
WEAK symbol<int(lua_State* s, int t)> hksi_luaL_ref{0x1400A64D0, 0x140136D30};
WEAK symbol<void(lua_State* s, int t, int ref)> hksi_luaL_unref{0x14009EF10, 0x14012F610};
}
}

View File

@ -0,0 +1,161 @@
#include <std_include.hpp>
#include "execution.hpp"
#include "component/ui_scripting.hpp"
#include <utils/string.hpp>
namespace ui_scripting
{
void push_value(const script_value& value)
{
const auto state = *game::hks::lua_state;
const auto value_ = value.get_raw();
*state->m_apistack.top = value_;
state->m_apistack.top++;
}
script_value get_return_value(int offset)
{
const auto state = *game::hks::lua_state;
return state->m_apistack.top[-1 - offset];
}
arguments get_return_values(int count)
{
arguments values;
for (auto i = count - 1; i >= 0; i--)
{
values.push_back(get_return_value(i));
}
if (values.size() == 0)
{
values.push_back({});
}
return values;
}
arguments call_script_function(const function& function, const arguments& arguments)
{
const auto state = *game::hks::lua_state;
state->m_apistack.top = state->m_apistack.base;
push_value(function);
for (auto i = arguments.begin(); i != arguments.end(); ++i)
{
push_value(*i);
}
const auto _1 = gsl::finally(&disable_error_hook);
enable_error_hook();
try
{
game::hks::vm_call_internal(state, static_cast<int>(arguments.size()), -1, 0);
const auto count = static_cast<int>(state->m_apistack.top - state->m_apistack.base);
return get_return_values(count);
}
catch (const std::exception& e)
{
throw std::runtime_error(std::string("Error executing script function: ") + e.what());
}
}
script_value get_field(const userdata& self, const script_value& key)
{
const auto state = *game::hks::lua_state;
state->m_apistack.top = state->m_apistack.base;
push_value(key);
const auto _1 = gsl::finally(&disable_error_hook);
enable_error_hook();
game::hks::HksObject value{};
game::hks::HksObject userdata{};
userdata.t = game::hks::TUSERDATA;
userdata.v.ptr = self.ptr;
try
{
game::hks::hks_obj_gettable(&value, state, &userdata, &state->m_apistack.top[-1]);
return value;
}
catch (const std::exception& e)
{
throw std::runtime_error(std::string("Error getting userdata field: ") + e.what());
}
}
script_value get_field(const table& self, const script_value& key)
{
const auto state = *game::hks::lua_state;
state->m_apistack.top = state->m_apistack.base;
push_value(key);
const auto _1 = gsl::finally(&disable_error_hook);
enable_error_hook();
game::hks::HksObject value{};
game::hks::HksObject userdata{};
userdata.t = game::hks::TTABLE;
userdata.v.ptr = self.ptr;
try
{
game::hks::hks_obj_gettable(&value, state, &userdata, &state->m_apistack.top[-1]);
return value;
}
catch (const std::exception& e)
{
throw std::runtime_error(std::string("Error getting table field: ") + e.what());
}
}
void set_field(const userdata& self, const script_value& key, const script_value& value)
{
const auto state = *game::hks::lua_state;
state->m_apistack.top = state->m_apistack.base;
const auto _1 = gsl::finally(&disable_error_hook);
enable_error_hook();
game::hks::HksObject userdata{};
userdata.t = game::hks::TUSERDATA;
userdata.v.ptr = self.ptr;
try
{
game::hks::hks_obj_settable(state, &userdata, &key.get_raw(), &value.get_raw());
}
catch (const std::exception& e)
{
throw std::runtime_error(std::string("Error setting userdata field: ") + e.what());
}
}
void set_field(const table& self, const script_value& key, const script_value& value)
{
const auto state = *game::hks::lua_state;
state->m_apistack.top = state->m_apistack.base;
const auto _1 = gsl::finally(&disable_error_hook);
enable_error_hook();
game::hks::HksObject userdata{};
userdata.t = game::hks::TTABLE;
userdata.v.ptr = self.ptr;
try
{
game::hks::hks_obj_settable(state, &userdata, &key.get_raw(), &value.get_raw());
}
catch (const std::exception& e)
{
throw std::runtime_error(std::string("Error setting table field: ") + e.what());
}
}
}

View File

@ -0,0 +1,18 @@
#pragma once
#include "game/game.hpp"
#include "types.hpp"
#include "script_value.hpp"
namespace ui_scripting
{
void push_value(const script_value& value);
script_value get_return_value(int offset);
arguments get_return_values(int count);
arguments call_script_function(const function& function, const arguments& arguments);
script_value get_field(const userdata& self, const script_value& key);
script_value get_field(const table& self, const script_value& key);
void set_field(const userdata& self, const script_value& key, const script_value& value);
void set_field(const table& self, const script_value& key, const script_value& value);
}

View File

@ -0,0 +1,205 @@
#include <std_include.hpp>
#include "context.hpp"
#include "error.hpp"
#include "value_conversion.hpp"
#include "../script_value.hpp"
#include "../execution.hpp"
#include "../../../component/ui_scripting.hpp"
#include "../../../component/command.hpp"
#include "component/game_console.hpp"
#include "component/scheduler.hpp"
#include <utils/string.hpp>
#include <utils/nt.hpp>
#include <utils/io.hpp>
namespace ui_scripting::lua
{
namespace
{
void setup_types(sol::state& state, scheduler& scheduler)
{
struct game
{
};
auto game_type = state.new_usertype<game>("game_");
state["game"] = game();
game_type["ontimeout"] = [&scheduler](const game&, const sol::protected_function& callback,
const long long milliseconds)
{
return scheduler.add(callback, milliseconds, true);
};
game_type["oninterval"] = [&scheduler](const game&, const sol::protected_function& callback,
const long long milliseconds)
{
return scheduler.add(callback, milliseconds, false);
};
auto userdata_type = state.new_usertype<userdata>("userdata_");
userdata_type["new"] = sol::property(
[](const userdata& userdata, const sol::this_state s)
{
return convert(s, userdata.get("new"));
},
[](const userdata& userdata, const sol::this_state s, const sol::lua_value& value)
{
userdata.set("new", convert({s, value}));
}
);
userdata_type["get"] = [](const userdata& userdata, const sol::this_state s,
const sol::lua_value& key)
{
return convert(s, userdata.get(convert({s, key})));
};
userdata_type["set"] = [](const userdata& userdata, const sol::this_state s,
const sol::lua_value& key, const sol::lua_value& value)
{
userdata.set(convert({s, key}), convert({s, value}));
};
userdata_type[sol::meta_function::index] = [](const userdata& userdata, const sol::this_state s,
const sol::lua_value& key)
{
return convert(s, userdata.get(convert({s, key})));
};
userdata_type[sol::meta_function::new_index] = [](const userdata& userdata, const sol::this_state s,
const sol::lua_value& key, const sol::lua_value& value)
{
userdata.set(convert({s, key}), convert({s, value}));
};
auto table_type = state.new_usertype<table>("table_");
table_type["new"] = sol::property(
[](const table& table, const sol::this_state s)
{
return convert(s, table.get("new"));
},
[](const table& table, const sol::this_state s, const sol::lua_value& value)
{
table.set("new", convert({s, value}));
}
);
table_type["get"] = [](const table& table, const sol::this_state s,
const sol::lua_value& key)
{
return convert(s, table.get(convert({s, key})));
};
table_type["set"] = [](const table& table, const sol::this_state s,
const sol::lua_value& key, const sol::lua_value& value)
{
table.set(convert({s, key}), convert({s, value}));
};
table_type[sol::meta_function::index] = [](const table& table, const sol::this_state s,
const sol::lua_value& key)
{
return convert(s, table.get(convert({s, key})));
};
table_type[sol::meta_function::new_index] = [](const table& table, const sol::this_state s,
const sol::lua_value& key, const sol::lua_value& value)
{
table.set(convert({s, key}), convert({s, value}));
};
auto function_type = state.new_usertype<function>("function_");
function_type[sol::meta_function::call] = [](const function& function, const sol::this_state s, sol::variadic_args va)
{
arguments arguments{};
for (auto arg : va)
{
arguments.push_back(convert({s, arg}));
}
const auto values = function.call(arguments);
std::vector<sol::lua_value> returns;
for (const auto& value : values)
{
returns.push_back(convert(s, value));
}
return sol::as_returns(returns);
};
state["luiglobals"] = table((*::game::hks::lua_state)->globals.v.table);
state["CoD"] = state["luiglobals"]["CoD"];
state["LUI"] = state["luiglobals"]["LUI"];
state["Engine"] = state["luiglobals"]["Engine"];
state["Game"] = state["luiglobals"]["Game"];
}
}
context::context(std::string folder)
: folder_(std::move(folder))
, scheduler_(state_)
{
this->state_.open_libraries(sol::lib::base,
sol::lib::package,
sol::lib::io,
sol::lib::string,
sol::lib::os,
sol::lib::math,
sol::lib::table);
this->state_["include"] = [this](const std::string& file)
{
this->load_script(file);
};
sol::function old_require = this->state_["require"];
auto base_path = utils::string::replace(this->folder_, "/", ".") + ".";
this->state_["require"] = [base_path, old_require](const std::string& path)
{
return old_require(base_path + path);
};
this->state_["scriptdir"] = [this]()
{
return this->folder_;
};
setup_types(this->state_, this->scheduler_);
printf("Loading ui script '%s'\n", this->folder_.data());
this->load_script("__init__");
}
context::~context()
{
this->state_.collect_garbage();
this->scheduler_.clear();
this->state_ = {};
}
void context::run_frame()
{
this->scheduler_.run_frame();
this->state_.collect_garbage();
}
void context::load_script(const std::string& script)
{
if (!this->loaded_scripts_.emplace(script).second)
{
return;
}
const auto file = (std::filesystem::path{this->folder_} / (script + ".lua")).generic_string();
handle_error(this->state_.safe_script_file(file, &sol::script_pass_on_error));
}
}

View File

@ -0,0 +1,36 @@
#pragma once
#pragma warning(push)
#pragma warning(disable: 4702)
#define SOL_ALL_SAFETIES_ON 1
#define SOL_PRINT_ERRORS 0
#include <sol/sol.hpp>
#include "scheduler.hpp"
namespace ui_scripting::lua
{
class context
{
public:
context(std::string folder);
~context();
context(context&&) noexcept = delete;
context& operator=(context&&) noexcept = delete;
context(const context&) = delete;
context& operator=(const context&) = delete;
void run_frame();
private:
sol::state state_{};
std::string folder_;
std::unordered_set<std::string> loaded_scripts_;
scheduler scheduler_;
void load_script(const std::string& script);
};
}

View File

@ -0,0 +1,61 @@
#include <std_include.hpp>
#include "engine.hpp"
#include "context.hpp"
#include "../../../component/ui_scripting.hpp"
#include "../../../component/game_module.hpp"
#include <utils/io.hpp>
namespace ui_scripting::lua::engine
{
namespace
{
auto& get_scripts()
{
static std::vector<std::unique_ptr<context>> scripts{};
return scripts;
}
void load_scripts(const std::string& script_dir)
{
if (!utils::io::directory_exists(script_dir))
{
return;
}
const auto scripts = utils::io::list_files(script_dir);
for (const auto& script : scripts)
{
if (std::filesystem::is_directory(script) && utils::io::file_exists(script + "/__init__.lua"))
{
get_scripts().push_back(std::make_unique<context>(script));
}
}
}
}
void start()
{
clear_converted_functions();
get_scripts().clear();
load_scripts(game_module::get_host_module().get_folder() + "/data/ui_scripts/");
load_scripts("h1-mod/ui_scripts/");
load_scripts("data/ui_scripts/");
}
void stop()
{
clear_converted_functions();
get_scripts().clear();
}
void run_frame()
{
for (auto& script : get_scripts())
{
script->run_frame();
}
}
}

View File

@ -0,0 +1,8 @@
#pragma once
namespace ui_scripting::lua::engine
{
void start();
void stop();
void run_frame();
}

View File

@ -0,0 +1,18 @@
#include <std_include.hpp>
#include "error.hpp"
namespace ui_scripting::lua
{
void handle_error(const sol::protected_function_result& result)
{
if (!result.valid())
{
printf("************** UI Script execution error **************\n");
const sol::error err = result;
printf("%s\n", err.what());
printf("****************************************************\n");
}
}
}

View File

@ -0,0 +1,8 @@
#pragma once
#include "context.hpp"
namespace ui_scripting::lua
{
void handle_error(const sol::protected_function_result& result);
}

View File

@ -0,0 +1,122 @@
#include "std_include.hpp"
#include "context.hpp"
#include "error.hpp"
namespace ui_scripting::lua
{
scheduler::scheduler(sol::state& state)
{
auto task_handle_type = state.new_usertype<task_handle>("task_handle");
task_handle_type["clear"] = [this](const task_handle& handle)
{
this->remove(handle);
};
}
void scheduler::run_frame()
{
callbacks_.access([&](task_list& tasks)
{
this->merge_callbacks();
for (auto i = tasks.begin(); i != tasks.end();)
{
const auto now = std::chrono::high_resolution_clock::now();
const auto diff = now - i->last_call;
if (diff < i->delay)
{
++i;
continue;
}
i->last_call = now;
if (!i->is_deleted)
{
handle_error(i->callback());
}
if (i->is_volatile || i->is_deleted)
{
i = tasks.erase(i);
}
else
{
++i;
}
}
});
}
void scheduler::clear()
{
callbacks_.access([&](task_list& tasks)
{
new_callbacks_.access([&](task_list& new_tasks)
{
new_tasks.clear();
tasks.clear();
});
});
}
task_handle scheduler::add(const sol::protected_function& callback, const long long milliseconds,
const bool is_volatile)
{
return this->add(callback, std::chrono::milliseconds(milliseconds), is_volatile);
}
task_handle scheduler::add(const sol::protected_function& callback, const std::chrono::milliseconds delay,
const bool is_volatile)
{
const uint64_t id = ++this->current_task_id_;
task task;
task.is_volatile = is_volatile;
task.callback = callback;
task.delay = delay;
task.last_call = std::chrono::steady_clock::now();
task.id = id;
task.is_deleted = false;
new_callbacks_.access([&task](task_list& tasks)
{
tasks.emplace_back(std::move(task));
});
return {id};
}
void scheduler::remove(const task_handle& handle)
{
auto mask_as_deleted = [&](task_list& tasks)
{
for (auto& task : tasks)
{
if (task.id == handle.id)
{
task.is_deleted = true;
break;
}
}
};
callbacks_.access(mask_as_deleted);
new_callbacks_.access(mask_as_deleted);
}
void scheduler::merge_callbacks()
{
callbacks_.access([&](task_list& tasks)
{
new_callbacks_.access([&](task_list& new_tasks)
{
tasks.insert(tasks.end(), std::move_iterator<task_list::iterator>(new_tasks.begin()),
std::move_iterator<task_list::iterator>(new_tasks.end()));
new_tasks = {};
});
});
}
}

View File

@ -0,0 +1,50 @@
#pragma once
#include <utils/concurrency.hpp>
namespace ui_scripting::lua
{
class context;
class task_handle
{
public:
uint64_t id = 0;
};
class task final : public task_handle
{
public:
std::chrono::steady_clock::time_point last_call{};
sol::protected_function callback{};
std::chrono::milliseconds delay{};
bool is_volatile = false;
bool is_deleted = false;
};
class scheduler final
{
public:
scheduler(sol::state& state);
scheduler(scheduler&&) noexcept = delete;
scheduler& operator=(scheduler&&) noexcept = delete;
scheduler(const scheduler&) = delete;
scheduler& operator=(const scheduler&) = delete;
void run_frame();
void clear();
task_handle add(const sol::protected_function& callback, long long milliseconds, bool is_volatile);
task_handle add(const sol::protected_function& callback, std::chrono::milliseconds delay, bool is_volatile);
private:
using task_list = std::vector<task>;
utils::concurrency::container<task_list> new_callbacks_;
utils::concurrency::container<task_list, std::recursive_mutex> callbacks_;
std::atomic_int64_t current_task_id_ = 0;
void remove(const task_handle& handle);
void merge_callbacks();
};
}

View File

@ -0,0 +1,144 @@
#include <std_include.hpp>
#include "value_conversion.hpp"
#include "../execution.hpp"
#include "../../../component/ui_scripting.hpp"
namespace ui_scripting::lua
{
namespace
{
table convert_table(const sol::table& t)
{
table res{};
t.for_each([res](const sol::object& key, const sol::object& value)
{
res.set(convert(key), convert(value));
});
return res;
}
script_value convert_function(const sol::protected_function& function)
{
const auto closure = game::hks::cclosure_Create(*game::hks::lua_state, main_function_handler, 0, 0, 0);
add_converted_function(closure, function);
game::hks::HksObject value{};
value.t = game::hks::TCFUNCTION;
value.v.cClosure = closure;
return value;
}
}
script_value convert(const sol::lua_value& value)
{
if (value.is<bool>())
{
return {value.as<bool>()};
}
if (value.is<int>())
{
return {value.as<int>()};
}
if (value.is<unsigned int>())
{
return {value.as<unsigned int>()};
}
if (value.is<double>())
{
return {value.as<double>()};
}
if (value.is<float>())
{
return {value.as<float>()};
}
if (value.is<std::string>())
{
return {value.as<std::string>()};
}
if (value.is<lightuserdata>())
{
return {value.as<lightuserdata>()};
}
if (value.is<userdata>())
{
return {value.as<userdata>()};
}
if (value.is<table>())
{
return {value.as<table>()};
}
if (value.is<function>())
{
return {value.as<function>()};
}
if (value.is<sol::table>())
{
return {convert_table(value.as<sol::table>())};
}
if (value.is<sol::protected_function>())
{
return {convert_function(value.as<sol::protected_function>())};
}
return {};
}
sol::lua_value convert(lua_State* state, const script_value& value)
{
if (value.is<int>())
{
return {state, value.as<int>()};
}
if (value.is<float>())
{
return {state, value.as<float>()};
}
if (value.is<bool>())
{
return {state, value.as<bool>()};
}
if (value.is<std::string>())
{
return {state, value.as<std::string>()};
}
if (value.is<lightuserdata>())
{
return {state, value.as<lightuserdata>()};
}
if (value.is<userdata>())
{
return {state, value.as<userdata>()};
}
if (value.is<table>())
{
return {state, value.as<table>()};
}
if (value.is<function>())
{
return {state, value.as<function>()};
}
return {state, sol::lua_nil};
}
}

View File

@ -0,0 +1,9 @@
#pragma once
#include "context.hpp"
#include "../script_value.hpp"
namespace ui_scripting::lua
{
script_value convert(const sol::lua_value& value);
sol::lua_value convert(lua_State* state, const script_value& value);
}

View File

@ -0,0 +1,274 @@
#include <std_include.hpp>
#include "execution.hpp"
#include "types.hpp"
#include "script_value.hpp"
namespace ui_scripting
{
/***************************************************************
* Constructors
**************************************************************/
script_value::script_value(const game::hks::HksObject& value)
: value_(value)
{
}
script_value::script_value(const int value)
{
game::hks::HksObject obj{};
obj.t = game::hks::TNUMBER;
obj.v.number = static_cast<float>(value);
this->value_ = obj;
}
script_value::script_value(const unsigned int value)
{
game::hks::HksObject obj{};
obj.t = game::hks::TNUMBER;
obj.v.number = static_cast<float>(value);
this->value_ = obj;
}
script_value::script_value(const bool value)
{
game::hks::HksObject obj{};
obj.t = game::hks::TBOOLEAN;
obj.v.boolean = value;
this->value_ = obj;
}
script_value::script_value(const float value)
{
game::hks::HksObject obj{};
obj.t = game::hks::TNUMBER;
obj.v.number = static_cast<float>(value);
this->value_ = obj;
}
script_value::script_value(const double value)
: script_value(static_cast<float>(value))
{
}
script_value::script_value(const char* value)
{
game::hks::HksObject obj{};
const auto state = *game::hks::lua_state;
state->m_apistack.top = state->m_apistack.base;
game::hks::hksi_lua_pushlstring(state, value, (unsigned int)strlen(value));
obj = state->m_apistack.top[-1];
this->value_ = obj;
}
script_value::script_value(const std::string& value)
: script_value(value.data())
{
}
script_value::script_value(const lightuserdata& value)
{
this->value_.t = game::hks::TLIGHTUSERDATA;
this->value_.v.ptr = value.ptr;
}
script_value::script_value(const userdata& value)
{
this->value_.t = game::hks::TUSERDATA;
this->value_.v.ptr = value.ptr;
}
script_value::script_value(const table& value)
{
this->value_.t = game::hks::TTABLE;
this->value_.v.ptr = value.ptr;
}
script_value::script_value(const function& value)
{
this->value_.t = value.type;
this->value_.v.ptr = value.ptr;
}
/***************************************************************
* Integer
**************************************************************/
template <>
bool script_value::is<int>() const
{
const auto number = this->get_raw().v.number;
return this->get_raw().t == game::hks::TNUMBER && static_cast<int>(number) == number;
}
template <>
bool script_value::is<unsigned int>() const
{
return this->is<int>();
}
template <>
int script_value::get() const
{
return static_cast<int>(this->get_raw().v.number);
}
template <>
unsigned int script_value::get() const
{
return static_cast<unsigned int>(this->get_raw().v.number);
}
/***************************************************************
* Boolean
**************************************************************/
template <>
bool script_value::is<bool>() const
{
return this->get_raw().t == game::hks::TBOOLEAN;
}
template <>
bool script_value::get() const
{
return this->get_raw().v.boolean;
}
/***************************************************************
* Float
**************************************************************/
template <>
bool script_value::is<float>() const
{
return this->get_raw().t == game::hks::TNUMBER;
}
template <>
bool script_value::is<double>() const
{
return this->is<float>();
}
template <>
float script_value::get() const
{
return this->get_raw().v.number;
}
template <>
double script_value::get() const
{
return static_cast<double>(this->get_raw().v.number);
}
/***************************************************************
* String
**************************************************************/
template <>
bool script_value::is<const char*>() const
{
return this->get_raw().t == game::hks::TSTRING;
}
template <>
bool script_value::is<std::string>() const
{
return this->is<const char*>();
}
template <>
const char* script_value::get() const
{
return this->get_raw().v.str->m_data;
}
template <>
std::string script_value::get() const
{
return this->get<const char*>();
}
/***************************************************************
* Lightuserdata
**************************************************************/
template <>
bool script_value::is<lightuserdata>() const
{
return this->get_raw().t == game::hks::TLIGHTUSERDATA;
}
template <>
lightuserdata script_value::get() const
{
return this->get_raw().v.ptr;
}
/***************************************************************
* Userdata
**************************************************************/
template <>
bool script_value::is<userdata>() const
{
return this->get_raw().t == game::hks::TUSERDATA;
}
template <>
userdata script_value::get() const
{
return this->get_raw().v.ptr;
}
/***************************************************************
* Table
**************************************************************/
template <>
bool script_value::is<table>() const
{
return this->get_raw().t == game::hks::TTABLE;
}
template <>
table script_value::get() const
{
return this->get_raw().v.table;
}
/***************************************************************
* Function
**************************************************************/
template <>
bool script_value::is<function>() const
{
return this->get_raw().t == game::hks::TIFUNCTION
|| this->get_raw().t == game::hks::TCFUNCTION;
}
template <>
function script_value::get() const
{
return { this->get_raw().v.cClosure, this->get_raw().t };
}
/***************************************************************
*
**************************************************************/
const game::hks::HksObject& script_value::get_raw() const
{
return this->value_;
}
}

View File

@ -0,0 +1,56 @@
#pragma once
#include "game/game.hpp"
namespace ui_scripting
{
class lightuserdata;
class userdata;
class table;
class function;
class script_value
{
public:
script_value() = default;
script_value(const game::hks::HksObject& value);
script_value(int value);
script_value(unsigned int value);
script_value(bool value);
script_value(float value);
script_value(double value);
script_value(const char* value);
script_value(const std::string& value);
script_value(const lightuserdata& value);
script_value(const userdata& value);
script_value(const table& value);
script_value(const function& value);
template <typename T>
bool is() const;
template <typename T>
T as() const
{
if (!this->is<T>())
{
throw std::runtime_error("Invalid type");
}
return get<T>();
}
const game::hks::HksObject& get_raw() const;
private:
template <typename T>
T get() const;
game::hks::HksObject value_{};
};
using arguments = std::vector<script_value>;
}

View File

@ -0,0 +1,276 @@
#include <std_include.hpp>
#include "types.hpp"
#include "execution.hpp"
namespace ui_scripting
{
/***************************************************************
* Lightuserdata
**************************************************************/
lightuserdata::lightuserdata(void* ptr_)
: ptr(ptr_)
{
}
/***************************************************************
* Userdata
**************************************************************/
userdata::userdata(void* ptr_)
: ptr(ptr_)
{
this->add();
}
userdata::userdata(const userdata& other)
{
this->operator=(other);
}
userdata::userdata(userdata&& other) noexcept
{
this->ptr = other.ptr;
this->ref = other.ref;
other.ref = 0;
}
userdata::~userdata()
{
this->release();
}
userdata& userdata::operator=(const userdata& other)
{
if (&other != this)
{
this->release();
this->ptr = other.ptr;
this->ref = other.ref;
this->add();
}
return *this;
}
userdata& userdata::operator=(userdata&& other) noexcept
{
if (&other != this)
{
this->release();
this->ptr = other.ptr;
this->ref = other.ref;
other.ref = 0;
}
return *this;
}
void userdata::add()
{
game::hks::HksObject value{};
value.v.ptr = this->ptr;
value.t = game::hks::TUSERDATA;
const auto state = *game::hks::lua_state;
state->m_apistack.top = state->m_apistack.base;
push_value(value);
this->ref = game::hks::hksi_luaL_ref(*game::hks::lua_state, -10000);
}
void userdata::release()
{
if (this->ref)
{
game::hks::hksi_luaL_unref(*game::hks::lua_state, -10000, this->ref);
}
}
void userdata::set(const script_value& key, const script_value& value) const
{
set_field(*this, key, value);
}
script_value userdata::get(const script_value& key) const
{
return get_field(*this, key);
}
/***************************************************************
* Table
**************************************************************/
table::table()
{
const auto state = *game::hks::lua_state;
this->ptr = game::hks::Hashtable_Create(state, 0, 0);
this->add();
}
table::table(game::hks::HashTable* ptr_)
: ptr(ptr_)
{
this->add();
}
table::table(const table& other)
{
this->operator=(other);
}
table::table(table&& other) noexcept
{
this->ptr = other.ptr;
this->ref = other.ref;
other.ref = 0;
}
table::~table()
{
this->release();
}
table& table::operator=(const table& other)
{
if (&other != this)
{
this->release();
this->ptr = other.ptr;
this->ref = other.ref;
this->add();
}
return *this;
}
table& table::operator=(table&& other) noexcept
{
if (&other != this)
{
this->release();
this->ptr = other.ptr;
this->ref = other.ref;
other.ref = 0;
}
return *this;
}
void table::add()
{
game::hks::HksObject value{};
value.v.table = this->ptr;
value.t = game::hks::TTABLE;
const auto state = *game::hks::lua_state;
state->m_apistack.top = state->m_apistack.base;
push_value(value);
this->ref = game::hks::hksi_luaL_ref(*game::hks::lua_state, -10000);
}
void table::release()
{
if (this->ref)
{
game::hks::hksi_luaL_unref(*game::hks::lua_state, -10000, this->ref);
}
}
void table::set(const script_value& key, const script_value& value) const
{
set_field(*this, key, value);
}
script_value table::get(const script_value& key) const
{
return get_field(*this, key);
}
/***************************************************************
* Function
**************************************************************/
function::function(game::hks::cclosure* ptr_, game::hks::HksObjectType type_)
: ptr(ptr_)
, type(type_)
{
this->add();
}
function::function(const function& other)
{
this->operator=(other);
}
function::function(function&& other) noexcept
{
this->ptr = other.ptr;
this->type = other.type;
this->ref = other.ref;
other.ref = 0;
}
function::~function()
{
this->release();
}
function& function::operator=(const function& other)
{
if (&other != this)
{
this->release();
this->ptr = other.ptr;
this->type = other.type;
this->ref = other.ref;
this->add();
}
return *this;
}
function& function::operator=(function&& other) noexcept
{
if (&other != this)
{
this->release();
this->ptr = other.ptr;
this->type = other.type;
this->ref = other.ref;
other.ref = 0;
}
return *this;
}
void function::add()
{
game::hks::HksObject value{};
value.v.cClosure = this->ptr;
value.t = this->type;
const auto state = *game::hks::lua_state;
state->m_apistack.top = state->m_apistack.base;
push_value(value);
this->ref = game::hks::hksi_luaL_ref(*game::hks::lua_state, -10000);
}
void function::release()
{
if (this->ref)
{
game::hks::hksi_luaL_unref(*game::hks::lua_state, -10000, this->ref);
}
}
arguments function::call(const arguments& arguments) const
{
return call_script_function(*this, arguments);
}
}

View File

@ -0,0 +1,89 @@
#pragma once
#include "game/game.hpp"
#include "script_value.hpp"
namespace ui_scripting
{
class lightuserdata
{
public:
lightuserdata(void*);
void* ptr;
};
class userdata
{
public:
userdata(void*);
userdata(const userdata& other);
userdata(userdata&& other) noexcept;
~userdata();
userdata& operator=(const userdata& other);
userdata& operator=(userdata&& other) noexcept;
script_value get(const script_value& key) const;
void set(const script_value& key, const script_value& value) const;
void* ptr;
private:
void add();
void release();
int ref{};
};
class table
{
public:
table();
table(game::hks::HashTable* ptr_);
table(const table& other);
table(table&& other) noexcept;
~table();
table& operator=(const table& other);
table& operator=(table&& other) noexcept;
script_value get(const script_value& key) const;
void set(const script_value& key, const script_value& value) const;
game::hks::HashTable* ptr;
private:
void add();
void release();
int ref{};
};
class function
{
public:
function(game::hks::cclosure*, game::hks::HksObjectType);
function(const function& other);
function(function&& other) noexcept;
~function();
function& operator=(const function& other);
function& operator=(function&& other) noexcept;
arguments call(const arguments& arguments) const;
game::hks::cclosure* ptr;
game::hks::HksObjectType type;
private:
void add();
void release();
int ref{};
};
}