From ec57c3305434a31bcd827ecf9ef26b9adff3248a Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Fri, 11 Sep 2026 06:23:02 +0200 Subject: [PATCH] luau_runtime reorganization + audio_res service via Lua Primary focus was to implement audio_res in Lua. As part of this I ended up re-organizing much of the luau_runtime to be less thrown together. The audio_res service relies on the (newly fetched) LuaBridge3 library to reduce all the boiler plate. All in all, I'm quite happy with how it turned out. I did have to fork LuaBridge3 to fix some bugs though, so that tag is fetched right now: https://github.com/TwilitRealm/LuaBridge3 --- mods/luau_runtime/CMakeLists.txt | 30 ++- mods/luau_runtime/src/lua_bridge_helpers.cpp | 40 ++++ mods/luau_runtime/src/lua_bridge_helpers.hpp | 106 +++++++++ mods/luau_runtime/src/lua_helpers.cpp | 118 ++++++++++ mods/luau_runtime/src/lua_helpers.hpp | 94 ++++++++ mods/luau_runtime/src/runtime.cpp | 126 ++--------- mods/luau_runtime/src/runtime.hpp | 25 +- .../src/services/audio_res/CMakeLists.txt | 7 + .../src/services/audio_res/audio_res.cpp | 53 +++++ .../src/services/audio_res/bst.cpp | 214 ++++++++++++++++++ .../src/services/audio_res/bst.hpp | 65 ++++++ .../src/services/audio_res/wsys.cpp | 138 +++++++++++ .../src/services/audio_res/wsys.hpp | 55 +++++ .../src/{ => services}/bindings.cpp | 7 +- .../src/{ => services}/config.cpp | 9 +- mods/luau_runtime/src/services/config.hpp | 10 + mods/luau_runtime/src/services/services.cpp | 32 +++ mods/luau_runtime/src/services/services.hpp | 21 ++ mods/luau_runtime/src/{ => services}/ui.cpp | 63 +++--- mods/luau_runtime/src/services/ui.hpp | 10 + 20 files changed, 1056 insertions(+), 167 deletions(-) create mode 100644 mods/luau_runtime/src/lua_bridge_helpers.cpp create mode 100644 mods/luau_runtime/src/lua_bridge_helpers.hpp create mode 100644 mods/luau_runtime/src/lua_helpers.cpp create mode 100644 mods/luau_runtime/src/lua_helpers.hpp create mode 100644 mods/luau_runtime/src/services/audio_res/CMakeLists.txt create mode 100644 mods/luau_runtime/src/services/audio_res/audio_res.cpp create mode 100644 mods/luau_runtime/src/services/audio_res/bst.cpp create mode 100644 mods/luau_runtime/src/services/audio_res/bst.hpp create mode 100644 mods/luau_runtime/src/services/audio_res/wsys.cpp create mode 100644 mods/luau_runtime/src/services/audio_res/wsys.hpp rename mods/luau_runtime/src/{ => services}/bindings.cpp (98%) rename mods/luau_runtime/src/{ => services}/config.cpp (98%) create mode 100644 mods/luau_runtime/src/services/config.hpp create mode 100644 mods/luau_runtime/src/services/services.cpp create mode 100644 mods/luau_runtime/src/services/services.hpp rename mods/luau_runtime/src/{ => services}/ui.cpp (96%) create mode 100644 mods/luau_runtime/src/services/ui.hpp diff --git a/mods/luau_runtime/CMakeLists.txt b/mods/luau_runtime/CMakeLists.txt index fa38b64b8a..f7729d3db2 100644 --- a/mods/luau_runtime/CMakeLists.txt +++ b/mods/luau_runtime/CMakeLists.txt @@ -26,15 +26,37 @@ FetchContent_Declare(luau ) FetchContent_MakeAvailable(luau) +FetchContent_Declare(luabridge3 + URL https://github.com/TwilitRealm/LuaBridge3/archive/refs/tags/twilit-v1.zip + URL_HASH SHA256=aa65e6e3446363d03842134182db28e6206e08d366b8efb8f66d26f96a3314be + DOWNLOAD_EXTRACT_TIMESTAMP TRUE +) +FetchContent_MakeAvailable(luabridge3) + add_mod(luau_runtime SOURCES - src/bindings.cpp - src/config.cpp + src/services/bindings.cpp + src/services/config.cpp + src/services/config.hpp + src/services/services.cpp + src/services/services.hpp + src/services/ui.cpp + src/services/ui.hpp + src/lua_bridge_helpers.cpp + src/lua_bridge_helpers.hpp + src/lua_helpers.cpp + src/lua_helpers.hpp + src/runtime.hpp src/runtime.cpp - src/ui.cpp MOD_JSON mod.json RES_DIR res BUNDLE ) -target_link_libraries(luau_runtime PRIVATE Luau.VM Luau.Compiler) +target_precompile_headers(luau_runtime PRIVATE src/lua_bridge_helpers.hpp src/lua_helpers.hpp) + +# As far as I can tell, Luau already does stack checks. +target_compile_definitions(luau_runtime PRIVATE LUABRIDGE_SAFE_STACK_CHECKS=0) +add_subdirectory(src/services/audio_res) + +target_link_libraries(luau_runtime PRIVATE Luau.VM Luau.Compiler LuaBridge) diff --git a/mods/luau_runtime/src/lua_bridge_helpers.cpp b/mods/luau_runtime/src/lua_bridge_helpers.cpp new file mode 100644 index 0000000000..9895435869 --- /dev/null +++ b/mods/luau_runtime/src/lua_bridge_helpers.cpp @@ -0,0 +1,40 @@ +#include "lua_bridge_helpers.hpp" + +luabridge::TypeResult> +luabridge::Stack::get(lua_State* L, int) { + auto& ref = luau_runtime::vm_from_registry(L); + return std::reference_wrapper(ref); +} + +luabridge::TypeResult> +luabridge::Stack::get(lua_State* L, int) { + auto const& ref = luau_runtime::vm_from_registry(L); + return std::reference_wrapper(ref); +} + +namespace luau_runtime { + +BridgeScriptHandle::BridgeScriptHandle(uint64_t const handle) noexcept : handle(handle) { + +} + +BridgeScriptHandle::~BridgeScriptHandle() = default; + +void BridgeScriptHandle::unregister(lua_State* state, Vm& vm) { + if (handle == 0) { + luaL_error(state, "Stale handle"); + } + + try { + unregister_impl(state, vm); + } catch (std::exception const&) { + // If an exception occurs, disallow the Lua code from attempting to re-run unregister. + // Since we assume the object may be in an undefined state (but hopefully not?) + handle = 0; + throw; + } + + handle = 0; +} + +} diff --git a/mods/luau_runtime/src/lua_bridge_helpers.hpp b/mods/luau_runtime/src/lua_bridge_helpers.hpp new file mode 100644 index 0000000000..579b0bd9c9 --- /dev/null +++ b/mods/luau_runtime/src/lua_bridge_helpers.hpp @@ -0,0 +1,106 @@ +#pragma once + +#include "lua.h" +#include "lualib.h" +#include "LuaBridge/LuaBridge.h" +#include "runtime.hpp" + +/** + * Defines (i.e. implements) a LuaBridge3 Stack converter for an enum that is passed by string name. + * + * @param type The type name of the enum to convert. + * @param names The constant field containing the string name <-> enum value mapping. + * @see DECLARE_STRING_ENUM + */ +#define DEFINE_STRING_ENUM(type, names) \ + Result Stack::push(lua_State* L, type value) { \ + return Stack::push(L, ::luau_runtime::lua_helpers::enum_value_to_str(L, value, names)); \ + } \ + \ + TypeResult Stack::get(lua_State* L, int index) { \ + const char* str = lua_tolstring(L, index, nullptr); \ + return ::luau_runtime::lua_helpers::enum_str_to_value(L, str, names); \ + } + +/** + * Declares a LuaBridge3 Stack converter for an enum that is passed by string name. + * + * @param type The type name of the enum to convert. + * @see DEFINE_STRING_ENUM + */ +#define DECLARE_STRING_ENUM(type) \ + template<> \ + struct Stack { \ + [[nodiscard]] static Result push(lua_State* L, type value); \ + [[nodiscard]] static TypeResult get(lua_State* L, int index); \ + } + +/** + * Template specialization to allow luau_runtime::Vm& to be directly acquired + * from a LuaBridge3 function. + */ +template <> +struct luabridge::Stack { + [[nodiscard]] static TypeResult> get(lua_State* L, int); +}; + +/** + * Template specialization to allow luau_runtime::Vm const& to be directly acquired + * from a LuaBridge3 function. + */ +template<> +struct luabridge::Stack { + [[nodiscard]] static TypeResult> get(lua_State* L, int); +}; + +namespace luau_runtime { + +/** + * Base type for "handle" types using LuaBridge3. + * + * Inherit from it, then register your derived class with LuaBridge3. + * Provide the base class @ref unregister member function. + */ +class BridgeScriptHandle { +protected: + uint64_t handle; + explicit BridgeScriptHandle(uint64_t handle) noexcept; + virtual void unregister_impl(lua_State* state, Vm& vm) = 0; + +public: + virtual ~BridgeScriptHandle(); + void unregister(lua_State* state, Vm& vm); +}; + +} + +/** + * Convert a @ref luaBridge::LuaRef to an *optional* value. + * + * This uses the default LuaBridge3 handling for std::optional, + * but just makes the type specification and casting a bit less verbose. + * + * @tparam T The type contained in the resulting optional. + * @tparam TLuaRef The type of the lua ref. Let this be inferred, as table items are unnameable. + * @param value Lua ref to convert to the desired type. + */ +template +std::optional opt(TLuaRef const& value) { + return value.template cast>().value(); +} + +/** + * Convert a @ref luaBridge::LuaRef to a value, with a default fallback if nil. + * + * This uses the default LuaBridge3 handling for std::optional, + * but just makes the type specification and casting a bit less verbose. + * + * @tparam T The resulting type. + * @tparam TLuaRef The type of the lua ref. Let this be inferred, as table items are unnameable. + * @param value Lua ref to convert to the desired type. + * @param default_value The default value if the lua item is nil. + */ +template +T opt_or(TLuaRef const& value, T const& default_value) { + return opt(value).value_or(default_value); +} diff --git a/mods/luau_runtime/src/lua_helpers.cpp b/mods/luau_runtime/src/lua_helpers.cpp new file mode 100644 index 0000000000..4b6c6aa09a --- /dev/null +++ b/mods/luau_runtime/src/lua_helpers.cpp @@ -0,0 +1,118 @@ +#include "lua_helpers.hpp" + +#include + +namespace luau_runtime::lua_helpers { + +bool get_optional_bool(lua_State* state, int table, const char* field, bool fallback) { + lua_getfield(state, table, field); + const bool value = lua_isnil(state, -1) ? fallback : luaL_checkboolean(state, -1) != 0; + lua_pop(state, 1); + return value; +} + +bool to_int64(lua_State* state, int index, int64_t& outValue) { + if (lua_isinteger64(state, index)) { + outValue = lua_tointeger64(state, index, nullptr); + return true; + } + if (!lua_isnumber(state, index)) { + return false; + } + const double value = lua_tonumber(state, index); + constexpr double kMaxSafeInteger = 9007199254740991.0; + if (!std::isfinite(value) || value < -kMaxSafeInteger || value > kMaxSafeInteger || + std::trunc(value) != value) + { + return false; + } + outValue = static_cast(value); + return true; +} + +int64_t check_int64(lua_State* state, int index) { + int64_t value = 0; + if (!to_int64(state, index, value)) { + luaL_argerror(state, index, "integer value expected"); + } + return value; +} + +int64_t get_optional_int(lua_State* state, int table, const char* field, int64_t fallback) { + lua_getfield(state, table, field); + const int64_t value = lua_isnil(state, -1) ? fallback : check_int64(state, -1); + lua_pop(state, 1); + return value; +} + +double get_optional_number(lua_State* state, int table, const char* field, double fallback) { + lua_getfield(state, table, field); + const double value = lua_isnil(state, -1) ? fallback : luaL_checknumber(state, -1); + lua_pop(state, 1); + return value; +} + +double get_number(lua_State* state, int table, const char* field) { + lua_getfield(state, table, field); + const double value = luaL_checknumber(state, -1); + lua_pop(state, 1); + return value; +} + +uint32_t get_optional_uint32(lua_State* state, int table, const char* field, uint32_t fallback) { + lua_getfield(state, table, field); + const uint32_t value = lua_isnil(state, -1) ? fallback : luaL_checkunsigned(state, -1); + lua_pop(state, 1); + return value; +} + +uint32_t get_uint32(lua_State* state, int table, const char* field) { + lua_getfield(state, table, field); + const uint32_t value = luaL_checkunsigned(state, -1); + lua_pop(state, 1); + return value; +} + +int32_t get_optional_int32(lua_State* state, int table, const char* field, int32_t fallback) { + lua_getfield(state, table, field); + const int32_t value = lua_isnil(state, -1) ? fallback : luaL_checkinteger(state, -1); + lua_pop(state, 1); + return value; +} + +std::string get_optional_string( + lua_State* state, int table, const char* field, std::string_view fallback) { + std::string result; + lua_getfield(state, table, field); + if (!lua_isnil(state, -1)) { + size_t length = 0; + const char* value = luaL_checklstring(state, -1, &length); + result.assign(value, length); + } else { + result = fallback; + } + lua_pop(state, 1); + return result; +} + +int ref_optional_function(lua_State* state, int table, const char* field) { + lua_getfield(state, table, field); + if (lua_isnil(state, -1)) { + lua_pop(state, 1); + return LUA_NOREF; + } + luaL_argexpected(state, lua_isfunction(state, -1), table, "function field"); + const int ref = lua_ref(state, -1); + lua_pop(state, 1); + return ref; +} + +int ref_required_function(lua_State* state, int table, const char* field) { + const int ref = ref_optional_function(state, table, field); + if (ref == LUA_NOREF) { + luaL_error(state, "field '%s' is required", field); + } + return ref; +} + +} diff --git a/mods/luau_runtime/src/lua_helpers.hpp b/mods/luau_runtime/src/lua_helpers.hpp new file mode 100644 index 0000000000..3db3e73831 --- /dev/null +++ b/mods/luau_runtime/src/lua_helpers.hpp @@ -0,0 +1,94 @@ +#pragma once + +#include "lua.h" +#include "lualib.h" + +#include +#include + +/* + * Helper functions for API bound between Lua <-> C++ + */ + +namespace luau_runtime::lua_helpers { + +bool get_optional_bool(lua_State* state, int table, const char* field, bool fallback); +double get_optional_number(lua_State* state, int table, const char* field, double fallback); +int32_t get_optional_int32(lua_State* state, int table, const char* field, int32_t fallback); +uint32_t get_optional_uint32(lua_State* state, int table, const char* field, uint32_t fallback); +std::string get_optional_string( + lua_State* state, int table, const char* field, std::string_view fallback = {}); + +bool to_int64(lua_State* state, int index, int64_t& outValue); +int64_t check_int64(lua_State* state, int index); +int64_t get_optional_int(lua_State* state, int table, const char* field, int64_t fallback); +int ref_optional_function(lua_State* state, int table, const char* field); +int ref_required_function(lua_State* state, int table, const char* field); + +double get_number(lua_State* state, int table, const char* field); +uint32_t get_uint32(lua_State* state, int table, const char* field); + +/** + * Concept that demands things be an enum. C++ is such a cool language. + */ +template +concept Enum = std::is_enum_v; + +/** + * Specifies a name/value pair for an enum. This is used by helpers to do automatic conversions + * between the native and (string) Lua form. + * @tparam T The enum type + */ +template +struct EnumName { + T value; + std::string_view name; + + constexpr EnumName(T value, std::string_view name) : value(value), name(name) { + } +}; + +/** + * Convert an enum's string name to the actual C++ value. + * + * Raises a Lua error if the string is not mapped. + * + * @tparam T Type of the enum + * @param state The Lua state. + * @param str The string value, from Lua. + * @param options The predefined set of possible enum names. + */ +template +T enum_str_to_value(lua_State* state, char const* str, EnumName const(& options)[N]) { + std::string_view strv(str); + for (size_t i = 0; i < N; ++i) { + if (options[i].name == strv) { + return static_cast(i); + } + } + + luaL_errorL(state, "Invalid enum value for %s: '%s'", typeid(T).name(), str); +} + +/** + * Convert an enum's C++ value to the Lua-exposed string. + * + * Raises a Lua error if the string is not mapped. + * + * @tparam T Type of the enum + * @param state The Lua state. + * @param value The C++ enum value. + * @param options The predefined set of possible enum names. + */ +template +std::string_view enum_value_to_str(lua_State* state, T value, EnumName const(& options)[N]) { + for (auto [nameValue, name] : options) { + if (nameValue == value) { + return name; + } + } + + luaL_errorL(state, "Attempted to return invalid enum to Lua!"); +} + +} diff --git a/mods/luau_runtime/src/runtime.cpp b/mods/luau_runtime/src/runtime.cpp index da69541872..2583d33a10 100644 --- a/mods/luau_runtime/src/runtime.cpp +++ b/mods/luau_runtime/src/runtime.cpp @@ -1,9 +1,11 @@ #include "runtime.hpp" +#include "services/services.hpp" #include "Luau/Common.h" #include "luacode.h" #include "mods/runtime.h" #include "mods/service.hpp" +#include "mods/svc/audio_res.h" #include #include @@ -26,10 +28,15 @@ IMPORT_OPTIONAL_SERVICE(ResourceService, svc_resource); IMPORT_OPTIONAL_SERVICE(OverlayService, svc_overlay); IMPORT_OPTIONAL_SERVICE(TextureService, svc_texture); IMPORT_OPTIONAL_SERVICE(UiService, svc_ui); +IMPORT_OPTIONAL_SERVICE(AudioResService, svc_audio_res); namespace luau_runtime { namespace { +// Just here to provide a globally-unique address to use as a registry index. +// Index into the Lua registry to store the Vm*. +int vm_registry_index; + std::unordered_map> s_vms; void* limited_realloc(void* userData, void* pointer, size_t oldSize, size_t newSize) { @@ -153,31 +160,6 @@ std::optional normalize_module_path( return normalized; } -ModuleOpenFn module_factory(std::string_view name) { - if (name == "dusklight.log") { - return open_log; - } - if (name == "dusklight.host") { - return open_host; - } - if (name == "dusklight.resource") { - return open_resource; - } - if (name == "dusklight.overlay") { - return open_overlay; - } - if (name == "dusklight.texture") { - return open_texture; - } - if (name == "dusklight.config") { - return open_config; - } - if (name == "dusklight.ui") { - return open_ui; - } - return nullptr; -} - int module_require(lua_State* state); void install_module_require(lua_State* state, Vm& vm, std::string_view currentPath) { @@ -262,7 +244,7 @@ int module_require(lua_State* state) { ModuleOpenFn factory = nullptr; if (requested.starts_with("dusklight.")) { moduleName = requested; - factory = module_factory(moduleName); + factory = services::module_factory(moduleName); if (factory == nullptr) { luaL_error(state, "unknown module '%s'", moduleName.c_str()); } @@ -313,6 +295,8 @@ ModResult runtime_activate(ModContext*, ModContext* subject, ModError* outError) if (vm->state == nullptr) { return set_error(outError, MOD_ERROR, "Failed to create Luau VM"); } + lua_pushlightuserdata(vm->state, vm.get()); + lua_rawsetp(vm->state, LUA_REGISTRYINDEX, &vm_registry_index); lua_callbacks(vm->state)->userdata = vm.get(); lua_callbacks(vm->state)->interrupt = [](lua_State* state, int gc) { auto* current = static_cast(lua_callbacks(state)->userdata); @@ -405,6 +389,16 @@ Vm& vm_from_upvalue(lua_State* state) { return *vm; } +Vm& vm_from_registry(lua_State* state) { + lua_rawgetp(state, LUA_REGISTRYINDEX, &vm_registry_index); + auto* vm = static_cast(lua_tolightuserdata(state, -1)); + if (vm == nullptr) { + luaL_error(state, "missing Luau runtime context"); + } + lua_pop(state, 1); + return *vm; +} + void push_vm_closure(lua_State* state, Vm& vm, lua_CFunction function, const char* name) { lua_pushlightuserdata(state, &vm); lua_pushcclosure(state, function, name, 1); @@ -443,86 +437,6 @@ void check_result(lua_State* state, ModResult result, const char* operation) { luaL_error(state, "%s failed: %s", operation, resultName); } -bool get_optional_bool(lua_State* state, int table, const char* field, bool fallback) { - lua_getfield(state, table, field); - const bool value = lua_isnil(state, -1) ? fallback : luaL_checkboolean(state, -1) != 0; - lua_pop(state, 1); - return value; -} - -bool to_int64(lua_State* state, int index, int64_t& outValue) { - if (lua_isinteger64(state, index)) { - outValue = lua_tointeger64(state, index, nullptr); - return true; - } - if (!lua_isnumber(state, index)) { - return false; - } - const double value = lua_tonumber(state, index); - constexpr double kMaxSafeInteger = 9007199254740991.0; - if (!std::isfinite(value) || value < -kMaxSafeInteger || value > kMaxSafeInteger || - std::trunc(value) != value) - { - return false; - } - outValue = static_cast(value); - return true; -} - -int64_t check_int64(lua_State* state, int index) { - int64_t value = 0; - if (!to_int64(state, index, value)) { - luaL_argerror(state, index, "integer value expected"); - } - return value; -} - -int64_t get_optional_int(lua_State* state, int table, const char* field, int64_t fallback) { - lua_getfield(state, table, field); - const int64_t value = lua_isnil(state, -1) ? fallback : check_int64(state, -1); - lua_pop(state, 1); - return value; -} - -double get_optional_number(lua_State* state, int table, const char* field, double fallback) { - lua_getfield(state, table, field); - const double value = lua_isnil(state, -1) ? fallback : luaL_checknumber(state, -1); - lua_pop(state, 1); - return value; -} - -std::string get_optional_string( - lua_State* state, int table, const char* field, std::string fallback) { - lua_getfield(state, table, field); - if (!lua_isnil(state, -1)) { - size_t length = 0; - const char* value = luaL_checklstring(state, -1, &length); - fallback.assign(value, length); - } - lua_pop(state, 1); - return fallback; -} - -int ref_optional_function(lua_State* state, int table, const char* field) { - lua_getfield(state, table, field); - if (lua_isnil(state, -1)) { - lua_pop(state, 1); - return LUA_NOREF; - } - luaL_argexpected(state, lua_isfunction(state, -1), table, "function field"); - const int ref = lua_ref(state, -1); - lua_pop(state, 1); - return ref; -} - -int ref_required_function(lua_State* state, int table, const char* field) { - const int ref = ref_optional_function(state, table, field); - if (ref == LUA_NOREF) { - luaL_error(state, "field '%s' is required", field); - } - return ref; -} - Callback& retain_callback(Vm& vm) { auto callback = std::make_unique(); callback->vm = &vm; diff --git a/mods/luau_runtime/src/runtime.hpp b/mods/luau_runtime/src/runtime.hpp index 2916a817e5..f037875fda 100644 --- a/mods/luau_runtime/src/runtime.hpp +++ b/mods/luau_runtime/src/runtime.hpp @@ -56,6 +56,9 @@ struct Vm { unsigned callDepth = 0; bool deadlineActive = false; + Vm() = default; + Vm(Vm const&) = delete; + Vm(Vm&&) = delete; ~Vm(); }; @@ -82,22 +85,13 @@ struct ScriptHandle { using ModuleOpenFn = int (*)(lua_State* state); Vm& vm_from_upvalue(lua_State* state); +Vm& vm_from_registry(lua_State* state); void push_vm_closure(lua_State* state, Vm& vm, lua_CFunction function, const char* name); void set_function(lua_State* state, Vm& vm, const char* name, lua_CFunction function); [[noreturn]] void service_unavailable(lua_State* state, const char* name); void check_result(lua_State* state, ModResult result, const char* operation); -bool get_optional_bool(lua_State* state, int table, const char* field, bool fallback); -bool to_int64(lua_State* state, int index, int64_t& outValue); -int64_t check_int64(lua_State* state, int index); -int64_t get_optional_int(lua_State* state, int table, const char* field, int64_t fallback); -double get_optional_number(lua_State* state, int table, const char* field, double fallback); -std::string get_optional_string( - lua_State* state, int table, const char* field, std::string fallback = {}); -int ref_optional_function(lua_State* state, int table, const char* field); -int ref_required_function(lua_State* state, int table, const char* field); - Callback& retain_callback(Vm& vm); bool call_ref(Vm& vm, int ref, int argumentCount, int resultCount, std::chrono::steady_clock::duration budget, std::string& outError); @@ -110,15 +104,4 @@ ScriptHandle& check_handle(lua_State* state, int index, const char* metatable, H void push_handle(lua_State* state, Vm& vm, uint64_t value, HandleKind kind, const char* metatable, ConfigVarType configType = CONFIG_VAR_BOOL); -void push_config_value(lua_State* state, const ConfigVarValue& value); -void push_ui_handle(lua_State* state, Vm& vm, uint64_t value, HandleKind kind); - -int open_log(lua_State* state); -int open_host(lua_State* state); -int open_resource(lua_State* state); -int open_overlay(lua_State* state); -int open_texture(lua_State* state); -int open_config(lua_State* state); -int open_ui(lua_State* state); - } // namespace luau_runtime diff --git a/mods/luau_runtime/src/services/audio_res/CMakeLists.txt b/mods/luau_runtime/src/services/audio_res/CMakeLists.txt new file mode 100644 index 0000000000..e4a1029377 --- /dev/null +++ b/mods/luau_runtime/src/services/audio_res/CMakeLists.txt @@ -0,0 +1,7 @@ +target_sources(luau_runtime PRIVATE + audio_res.cpp + bst.cpp + bst.hpp + wsys.cpp + wsys.hpp +) diff --git a/mods/luau_runtime/src/services/audio_res/audio_res.cpp b/mods/luau_runtime/src/services/audio_res/audio_res.cpp new file mode 100644 index 0000000000..32db2766ec --- /dev/null +++ b/mods/luau_runtime/src/services/audio_res/audio_res.cpp @@ -0,0 +1,53 @@ +#include "../../lua_bridge_helpers.hpp" +#include "../../runtime.hpp" +#include "../services.hpp" +#include "LuaBridge/LuaBridge.h" + +#include "mods/svc/audio_res.h" + +#include "wsys.hpp" +#include "bst.hpp" + +namespace luau_runtime::services { +namespace { + +constexpr auto kServiceName = "AudioResService"; + +} + +int open_audio_res(lua_State* state) { + using namespace audio_res; + + if (svc_audio_res == nullptr) { + service_unavailable(state, kServiceName); + } + + lua_newtable(state); + + luabridge::getNamespaceFromStack(state) + // WSYS + .addVariable("DEFAULT_KEY", AUDIO_RES_DEFAULT_KEY) + .addFunction("default_wave_info", default_wave_info) + .addFunction("replace_wave", replace_wave) + .addFunction("add_wave", add_wave) + .beginClass("AudioWaveHandle") + .addFunction("unregister", &LuaAudioWaveHandle::unregister) + .endClass() + // BST + .addFunction("default_effect_info", default_effect_info) + .addFunction("replace_sound_table_effect", replace_sound_table_effect) + .addFunction("add_sound_table_effect", add_sound_table_effect) + .addFunction("default_stream_info", default_stream_info) + .addFunction("replace_sound_table_stream", replace_sound_table_stream) + .addFunction("add_sound_table_stream", add_sound_table_stream) + .beginClass("AudioSoundTableHandle") + .addFunction("unregister", &LuaSoundTableHandle::unregister) + .endClass(); + + + lua_setreadonly(state, -1, true); + return 1; +} + +} + diff --git a/mods/luau_runtime/src/services/audio_res/bst.cpp b/mods/luau_runtime/src/services/audio_res/bst.cpp new file mode 100644 index 0000000000..b41fcb37e3 --- /dev/null +++ b/mods/luau_runtime/src/services/audio_res/bst.cpp @@ -0,0 +1,214 @@ +#include "bst.hpp" + +namespace luau_runtime::services::audio_res { + +LuaSoundTableHandle::LuaSoundTableHandle(AudioSoundTableHandle handle) : BridgeScriptHandle(handle) { } + +void LuaSoundTableHandle::unregister_impl(lua_State*, Vm& vm) { + svc_audio_res->remove_sound_table(vm.subject, handle); +} + +AudioSoundTableEffectInfo const& default_effect_info() { + return *svc_audio_res->default_effect_info; +} + +LuaSoundTableHandle replace_sound_table_effect(SoundEffectCategory category_id, uint16_t effect_id, + std::optional info, lua_State* state, Vm& vm) { + AudioSoundTableEffectInfo const* effect_info = nullptr; + if (info.has_value()) { + effect_info = &*info; + } + + AudioSoundTableHandle handle; + + check_result( + state, + svc_audio_res->replace_sound_table_effect(vm.subject, category_id, effect_id, effect_info, &handle), + "audio_res.replace_sound_table_effect"); + + return LuaSoundTableHandle(handle); +} + +std::tuple add_sound_table_effect( + SoundEffectCategory category_id, + std::optional info, + lua_State* state, + Vm& vm) { + AudioSoundTableEffectInfo const* effect_info = nullptr; + if (info.has_value()) { + effect_info = &*info; + } + + uint16_t out_effect_id; + AudioSoundTableHandle handle; + + check_result( + state, + svc_audio_res->add_sound_table_effect(vm.subject, category_id, effect_info, &handle, &out_effect_id), + "audio_res.add_sound_table_effect"); + + return { out_effect_id, LuaSoundTableHandle(handle) }; +} + +AudioSoundTableStreamInfo const& default_stream_info() { + return *svc_audio_res->default_stream_info; +} + +LuaSoundTableHandle replace_sound_table_stream(uint16_t stream_id, char const* file_path, + std::optional info, lua_State* state, Vm& vm) { + AudioSoundTableStreamInfo const* stream_info = nullptr; + if (info.has_value()) { + stream_info = &*info; + } + + AudioSoundTableHandle handle; + + check_result( + state, + svc_audio_res->replace_sound_table_stream(vm.subject, stream_id, file_path, stream_info, &handle), + "audio_res.replace_sound_table_stream"); + + return LuaSoundTableHandle(handle); +} + +std::tuple add_sound_table_stream( + char const* file_path, + std::optional info, + lua_State* state, + Vm& vm) { + AudioSoundTableStreamInfo const* stream_info = nullptr; + if (info.has_value()) { + stream_info = &*info; + } + + uint16_t out_stream_id; + AudioSoundTableHandle handle; + + check_result( + state, + svc_audio_res->add_sound_table_stream(vm.subject, file_path, stream_info, &handle, &out_stream_id), + "audio_res.add_sound_table_stream"); + + return { out_stream_id, LuaSoundTableHandle(handle) }; +} + + +} // namespace luau_runtime::services::audio_res + +namespace luabridge { +namespace { + +using namespace std::string_view_literals; + +constexpr luau_runtime::lua_helpers::EnumName kSoundEffectCategoryNames[] = { + // clang-format off + { SE_CATEGORY_SYSTEM_SE, "system_se"sv }, + { SE_CATEGORY_PLAYER_VOICE, "player_voice"sv }, + { SE_CATEGORY_PLAYER_SE, "player_se"sv }, + { SE_CATEGORY_FOOTNOTE_SE, "footnote_se"sv }, + { SE_CATEGORY_COLLISION_SE, "collision_se"sv }, + { SE_CATEGORY_CHARA_VOICE, "chara_voice"sv }, + { SE_CATEGORY_CHARA_SE, "chara_se"sv }, + { SE_CATEGORY_ENEMY_SE, "enemy_se"sv }, + { SE_CATEGORY_OBJECT_SE, "object_se"sv }, + { SE_CATEGORY_ENV_SE, "env_se"sv }, + // clang-format on +}; + +constexpr luau_runtime::lua_helpers::EnumName kStreamPanNames[] = { + // clang-format off + { STREAM_PAN_CENTER, "center"sv }, + { STREAM_PAN_LEFT, "left"sv }, + { STREAM_PAN_RIGHT, "right"sv }, + // clang-format on +}; + +} // namespace + +DEFINE_STRING_ENUM(SoundEffectCategory, kSoundEffectCategoryNames); +DEFINE_STRING_ENUM(StreamPan, kStreamPanNames); + +TypeResult Stack::get( + lua_State* L, int index) { + + auto const ref = LuaRef::fromStack(L, index); + + return AudioSoundTableEffectInfo { + .priority = ref["priority"], + .volume = ref["volume"], + .pitch = ref["pitch"], + .always_max_priority = ref["always_max_priority"], + .ignore_distance_volume = ref["ignore_distance_volume"], + .ignore_distance_fx_mix = ref["ignore_distance_fx_mix"], + .ignore_pan = ref["ignore_pan"], + .ignore_dolby = ref["ignore_dolby"], + .random_volume = ref["random_volume"], + .random_pitch = ref["random_pitch"], + .doppler_power = ref["doppler_power"], + .volume_dist_class = ref["volume_dist_class"], + .clamp_min_volume = ref["clamp_min_volume"], + .cull_at_max_distance = ref["cull_at_max_distance"], + }; +} + +Result Stack::push( + lua_State* L, const AudioSoundTableEffectInfo& value) { + + LuaRef const ref = newTable(L); + + ref["priority"] = value.priority; + ref["volume"] = value.volume; + ref["pitch"] = value.pitch; + ref["always_max_priority"] = value.always_max_priority; + ref["ignore_distance_volume"] = value.ignore_distance_volume; + ref["ignore_distance_fx_mix"] = value.ignore_distance_fx_mix; + ref["ignore_pan"] = value.ignore_pan; + ref["ignore_dolby"] = value.ignore_dolby; + ref["random_volume"] = value.random_volume; + ref["random_pitch"] = value.random_pitch; + ref["doppler_power"] = value.doppler_power; + ref["volume_dist_class"] = value.volume_dist_class; + ref["clamp_min_volume"] = value.clamp_min_volume; + ref["cull_at_max_distance"] = value.cull_at_max_distance; + + ref.push(); + + return {}; +} + +TypeResult Stack::get( + lua_State* L, int index) { + + auto const ref = LuaRef::fromStack(L, index); + + std::array, STREAM_MAX_CHILDREN> const pan_parameters = ref["pan_parameters"]; + + auto info = AudioSoundTableStreamInfo { + .priority = ref["priority"], + .volume = ref["volume"], + .stop_on_scene_change = ref["stop_on_scene_change"], + }; + + for (int i = 0; i < STREAM_MAX_CHILDREN; i++) { + info.pan_parameters[i] = pan_parameters[i].value_or(STREAM_PAN_CENTER); + } + + return info; +} + +Result Stack::push( + lua_State* L, const AudioSoundTableStreamInfo& value) { + + LuaRef const ref = newTable(L); + + ref["priority"] = value.priority; + ref["volume"] = value.volume; + ref["pan_parameters"] = value.pan_parameters; + ref["stop_on_scene_change"] = value.stop_on_scene_change; + + ref.push(); + + return {}; +} + +} // namespace luabridge diff --git a/mods/luau_runtime/src/services/audio_res/bst.hpp b/mods/luau_runtime/src/services/audio_res/bst.hpp new file mode 100644 index 0000000000..62af9cffe2 --- /dev/null +++ b/mods/luau_runtime/src/services/audio_res/bst.hpp @@ -0,0 +1,65 @@ +#pragma once + +#include "mods/svc/audio_res.h" +#include "../../lua_helpers.hpp" +#include "../../lua_bridge_helpers.hpp" + +namespace luau_runtime::services::audio_res { + +class LuaSoundTableHandle final : public BridgeScriptHandle { +protected: + void unregister_impl(lua_State*, Vm& vm) override; +public: + explicit LuaSoundTableHandle(AudioSoundTableHandle handle); +}; + +AudioSoundTableEffectInfo const& default_effect_info(); + +LuaSoundTableHandle replace_sound_table_effect( + SoundEffectCategory category_id, + uint16_t effect_id, + std::optional info, + lua_State* state, + Vm& vm); + +std::tuple add_sound_table_effect( + SoundEffectCategory category_id, + std::optional info, + lua_State* state, + Vm& vm); + +AudioSoundTableStreamInfo const& default_stream_info(); + +LuaSoundTableHandle replace_sound_table_stream( + uint16_t stream_id, + char const* file_path, + std::optional info, + lua_State* state, + Vm& vm); + +std::tuple add_sound_table_stream( + char const* file_path, + std::optional info, + lua_State* state, + Vm& vm); + +} + +namespace luabridge { + +template<> +struct Stack { + [[nodiscard]] static Result push(lua_State* L, const AudioSoundTableEffectInfo& value); + [[nodiscard]] static TypeResult get(lua_State* L, int index); +}; + +template<> +struct Stack { + [[nodiscard]] static Result push(lua_State* L, const AudioSoundTableStreamInfo& value); + [[nodiscard]] static TypeResult get(lua_State* L, int index); +}; + +DECLARE_STRING_ENUM(SoundEffectCategory); +DECLARE_STRING_ENUM(StreamPan); + +} diff --git a/mods/luau_runtime/src/services/audio_res/wsys.cpp b/mods/luau_runtime/src/services/audio_res/wsys.cpp new file mode 100644 index 0000000000..eb55feae78 --- /dev/null +++ b/mods/luau_runtime/src/services/audio_res/wsys.cpp @@ -0,0 +1,138 @@ +#include "wsys.hpp" + +namespace luau_runtime::services::audio_res { + +LuaAudioWaveHandle::LuaAudioWaveHandle(AudioWaveHandle handle) : BridgeScriptHandle(handle) { } + +void LuaAudioWaveHandle::unregister_impl(lua_State*, Vm& vm) { + svc_audio_res->remove_wave(vm.subject, handle); +} + +AudioWaveInfo const& default_wave_info() { + return *svc_audio_res->default_wave_info; +} + +LuaAudioWaveHandle replace_wave(AudioWaveBank bank, uint16_t wave_id, char const* file, std::optional info, lua_State* state, Vm& vm) { + AudioWaveInfo const* wave_info = nullptr; + if (info.has_value()) { + wave_info = &info->info; + } + + AudioWaveHandle handle; + + check_result( + state, + svc_audio_res->replace_wave(vm.subject, bank, wave_id, file, wave_info, &handle), + "audio_res.replace_wave"); + + return LuaAudioWaveHandle(handle); +} + +std::tuple add_wave(AudioWaveBank bank, char const* file, std::optional info, lua_State* state, Vm& vm) { + AudioWaveInfo const* wave_info = nullptr; + if (info.has_value()) { + wave_info = &info->info; + } + + AudioWaveHandle handle; + uint16_t out_wave_id; + + check_result( + state, + svc_audio_res->add_wave(vm.subject, bank, file, wave_info, &handle, &out_wave_id), + "audio_res.replace_wave"); + + return {out_wave_id, LuaAudioWaveHandle(handle)}; +} + +} // namespace luau_runtime::services::audio_res + +namespace luabridge { + +Result Stack::push(lua_State* L, const AudioWaveInfo& value) { + LuaRef const ref = newTable(L); + + ref["base_key"] = value.base_key; + ref["loop"] = value.loop; + ref["loop_start_sample"] = value.loop_start_sample; + ref["loop_end_sample"] = value.loop_end_sample; + if (value.raw_wave) { + ref["raw_wave"] = *value.raw_wave; + } + ref.push(); + + return {}; +} + +Result Stack::push(lua_State* L, const AudioRawWave& value) { + LuaRef const ref = newTable(L); + + ref["format"] = value.format; + ref["sample_rate"] = value.sample_rate; + ref["sample_value_last"] = value.sample_value_last; + ref["sample_value_penult"] = value.sample_value_penult; + + ref.push(); + + return {}; +} + +TypeResult Stack::get(lua_State* L, int index) { + auto const ref = LuaRef::fromStack(L, index); + + return AudioRawWave { + .format = ref["format"], + .sample_rate = ref["sample_rate"], + .sample_value_last = opt_or(ref["sample_value_last"], 0), + .sample_value_penult = opt_or(ref["sample_value_penult"], 0), + }; +} + +using namespace luau_runtime::services::audio_res; + +Result Stack::push(lua_State* L, const OwnedWaveInfo& value) { + return Stack::push(L, value.info); +} + +TypeResult Stack::get(lua_State* L, int index) { + auto const ref = LuaRef::fromStack(L, index); + + AudioWaveInfo info { + .base_key = opt_or(ref["base_key"], AUDIO_RES_DEFAULT_KEY), + .loop = opt_or(ref["loop"], false), + .loop_start_sample = opt_or(ref["loop_start_sample"], 0), + .loop_end_sample = opt_or(ref["loop_end_sample"], std::numeric_limits::max()), + }; + + std::unique_ptr raw; + if (!ref["raw_wave"].isNil()) { + raw = std::make_unique(ref["raw_wave"]); + info.raw_wave = raw.get(); + } + + return OwnedWaveInfo { info, std::move(raw) }; +} + +namespace { + +using namespace std::string_view_literals; + +constexpr luau_runtime::lua_helpers::EnumName kWaveFormatNames[] = { + { AUDIO_WAVE_FORMAT_ADPCM4, "adpcm4"sv }, + { AUDIO_WAVE_FORMAT_ADPCM2, "adpcm2"sv }, + { AUDIO_WAVE_FORMAT_PCM8, "pcm8"sv }, + { AUDIO_WAVE_FORMAT_PCM16, "pcm16"sv }, +}; + +constexpr luau_runtime::lua_helpers::EnumName kWaveBankNames[] = { + { AUDIO_WAVE_BANK_SOUND_EFFECTS, "sound_effects"sv } , + { AUDIO_WAVE_BANK_MUSIC_SAMPLES, "music_samples"sv } , +}; + +} + +DEFINE_STRING_ENUM(AudioWaveFormat, kWaveFormatNames); +DEFINE_STRING_ENUM(AudioWaveBank, kWaveBankNames); + + +} // namespace luabridge diff --git a/mods/luau_runtime/src/services/audio_res/wsys.hpp b/mods/luau_runtime/src/services/audio_res/wsys.hpp new file mode 100644 index 0000000000..be7ab06a1c --- /dev/null +++ b/mods/luau_runtime/src/services/audio_res/wsys.hpp @@ -0,0 +1,55 @@ +#pragma once + +#include "lua.h" +#include "lualib.h" +#include "LuaBridge/LuaBridge.h" + +#include "../../runtime.hpp" +#include "../../lua_helpers.hpp" +#include "mods/svc/audio_res.h" + +#include + +namespace luau_runtime::services::audio_res { + +struct OwnedWaveInfo { + AudioWaveInfo info; + std::unique_ptr raw_wave; +}; + +class LuaAudioWaveHandle final : public BridgeScriptHandle { +protected: + void unregister_impl(lua_State*, Vm& vm) override; +public: + explicit LuaAudioWaveHandle(AudioWaveHandle handle); +}; + +AudioWaveInfo const& default_wave_info(); +LuaAudioWaveHandle replace_wave(AudioWaveBank bank, uint16_t wave_id, char const* file, std::optional info, lua_State* state, Vm& vm); +std::tuple add_wave(AudioWaveBank bank, char const* file, std::optional info, lua_State* state, Vm& vm); + +} + +namespace luabridge { + +template<> +struct Stack { + [[nodiscard]] static Result push(lua_State* L, const AudioWaveInfo& value); +}; + +template<> +struct Stack { + [[nodiscard]] static Result push(lua_State* L, const AudioRawWave& value); + [[nodiscard]] static TypeResult get(lua_State* L, int index); +}; + +template<> +struct Stack { + [[nodiscard]] static Result push(lua_State* L, const luau_runtime::services::audio_res::OwnedWaveInfo& value); + [[nodiscard]] static TypeResult get(lua_State* L, int index); +}; + +DECLARE_STRING_ENUM(AudioWaveFormat); +DECLARE_STRING_ENUM(AudioWaveBank); + +} diff --git a/mods/luau_runtime/src/bindings.cpp b/mods/luau_runtime/src/services/bindings.cpp similarity index 98% rename from mods/luau_runtime/src/bindings.cpp rename to mods/luau_runtime/src/services/bindings.cpp index 27f4218a83..479e8cfc63 100644 --- a/mods/luau_runtime/src/bindings.cpp +++ b/mods/luau_runtime/src/services/bindings.cpp @@ -1,12 +1,15 @@ -#include "runtime.hpp" +#include "../lua_helpers.hpp" +#include "../runtime.hpp" #include #include #include -namespace luau_runtime { +namespace luau_runtime::services { namespace { +using namespace luau_runtime::lua_helpers; + constexpr char kOverlayMetatable[] = "dusklight.overlay_handle"; constexpr char kTextureMetatable[] = "dusklight.texture_handle"; diff --git a/mods/luau_runtime/src/config.cpp b/mods/luau_runtime/src/services/config.cpp similarity index 98% rename from mods/luau_runtime/src/config.cpp rename to mods/luau_runtime/src/services/config.cpp index b198bdb6e7..ef215b1824 100644 --- a/mods/luau_runtime/src/config.cpp +++ b/mods/luau_runtime/src/services/config.cpp @@ -1,11 +1,16 @@ -#include "runtime.hpp" +#include "config.hpp" +#include "../runtime.hpp" #include #include -namespace luau_runtime { +#include "../lua_helpers.hpp" + +namespace luau_runtime::services { namespace { +using namespace luau_runtime::lua_helpers; + constexpr char kConfigVarMetatable[] = "dusklight.config_var"; constexpr char kConfigSubscriptionMetatable[] = "dusklight.config_subscription"; diff --git a/mods/luau_runtime/src/services/config.hpp b/mods/luau_runtime/src/services/config.hpp new file mode 100644 index 0000000000..1eb83e0c8f --- /dev/null +++ b/mods/luau_runtime/src/services/config.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include "lua.h" +#include "mods/svc/config.h" + +namespace luau_runtime::services { + +void push_config_value(lua_State* state, const ConfigVarValue& value); + +} diff --git a/mods/luau_runtime/src/services/services.cpp b/mods/luau_runtime/src/services/services.cpp new file mode 100644 index 0000000000..1b8425156f --- /dev/null +++ b/mods/luau_runtime/src/services/services.cpp @@ -0,0 +1,32 @@ +#include "services.hpp" + +namespace luau_runtime::services { + +namespace { + +using namespace std::string_view_literals; + +constexpr std::pair kModules[] = { + { "dusklight.log"sv, open_log }, + { "dusklight.host"sv, open_host }, + { "dusklight.resource"sv, open_resource }, + { "dusklight.overlay"sv, open_overlay }, + { "dusklight.texture"sv, open_texture }, + { "dusklight.config"sv, open_config }, + { "dusklight.ui"sv, open_ui }, + { "dusklight.audio_res"sv, open_audio_res }, +}; + +} + +ModuleOpenFn module_factory(std::string_view name) { + for (auto [ modName, ptr ] : kModules) { + if (name == modName) { + return ptr; + } + } + + return nullptr; +} + +} diff --git a/mods/luau_runtime/src/services/services.hpp b/mods/luau_runtime/src/services/services.hpp new file mode 100644 index 0000000000..e387ca2463 --- /dev/null +++ b/mods/luau_runtime/src/services/services.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include +#include "../runtime.hpp" + +#include "lua.h" + +namespace luau_runtime::services { + +int open_audio_res(lua_State* state); +int open_log(lua_State* state); +int open_host(lua_State* state); +int open_resource(lua_State* state); +int open_overlay(lua_State* state); +int open_texture(lua_State* state); +int open_config(lua_State* state); +int open_ui(lua_State* state); + +ModuleOpenFn module_factory(std::string_view name); + +} \ No newline at end of file diff --git a/mods/luau_runtime/src/ui.cpp b/mods/luau_runtime/src/services/ui.cpp similarity index 96% rename from mods/luau_runtime/src/ui.cpp rename to mods/luau_runtime/src/services/ui.cpp index 7c3acc1296..07e377d3d1 100644 --- a/mods/luau_runtime/src/ui.cpp +++ b/mods/luau_runtime/src/services/ui.cpp @@ -1,13 +1,20 @@ -#include "runtime.hpp" +#include "ui.hpp" + +#include "../runtime.hpp" #include #include #include #include -namespace luau_runtime { +#include "../lua_helpers.hpp" + +namespace luau_runtime::services { namespace { +using namespace luau_runtime::lua_helpers; +using namespace std::string_view_literals; + constexpr char kUiWindowMetatable[] = "dusklight.ui_window"; constexpr char kUiDialogMetatable[] = "dusklight.ui_dialog"; constexpr char kUiElementMetatable[] = "dusklight.ui_element"; @@ -285,40 +292,32 @@ std::vector list_items(lua_State* state, int table, std::vector kControlKindNames[] = { + { UI_CONTROL_BUTTON, "button"sv }, + { UI_CONTROL_TOGGLE, "toggle"sv }, + { UI_CONTROL_NUMBER, "number"sv }, + { UI_CONTROL_STRING, "string"sv }, + { UI_CONTROL_SELECT, "select"sv }, + { UI_CONTROL_COLOR, "color"sv }, + { UI_CONTROL_GROUP, "group"sv }, + { UI_CONTROL_FILE_PICKER, "file_picker"sv }, +}; + UiControlKind control_kind(lua_State* state, const std::string& kind) { - if (kind == "button") - return UI_CONTROL_BUTTON; - if (kind == "toggle") - return UI_CONTROL_TOGGLE; - if (kind == "number") - return UI_CONTROL_NUMBER; - if (kind == "string") - return UI_CONTROL_STRING; - if (kind == "select") - return UI_CONTROL_SELECT; - if (kind == "color") - return UI_CONTROL_COLOR; - if (kind == "group") - return UI_CONTROL_GROUP; - if (kind == "file_picker") - return UI_CONTROL_FILE_PICKER; - luaL_error(state, "unknown UI control kind '%s'", kind.c_str()); + return enum_str_to_value(state, kind.data(), kControlKindNames); } +constexpr EnumName kStyleScopeNames[] = { + { UI_SCOPE_PRELAUNCH, "prelaunch"sv }, + { UI_SCOPE_WINDOW, "window"sv }, + { UI_SCOPE_MENU_BAR, "menu_bar"sv }, + { UI_SCOPE_OVERLAY, "overlay"sv }, + { UI_SCOPE_TOUCH_CONTROLS, "touch_controls"sv }, + { UI_SCOPE_GRAPHICS_TUNER, "graphics_tuner"sv }, +}; + UiStyleScope style_scope(lua_State* state, const std::string& scope) { - if (scope == "prelaunch") - return UI_SCOPE_PRELAUNCH; - if (scope == "window") - return UI_SCOPE_WINDOW; - if (scope == "menu_bar") - return UI_SCOPE_MENU_BAR; - if (scope == "overlay") - return UI_SCOPE_OVERLAY; - if (scope == "touch_controls") - return UI_SCOPE_TOUCH_CONTROLS; - if (scope == "graphics_tuner") - return UI_SCOPE_GRAPHICS_TUNER; - luaL_error(state, "unknown UI style scope '%s'", scope.c_str()); + return enum_str_to_value(state, scope.data(), kStyleScopeNames); } int pane_add_section(lua_State* state) { diff --git a/mods/luau_runtime/src/services/ui.hpp b/mods/luau_runtime/src/services/ui.hpp new file mode 100644 index 0000000000..c1b8fb532d --- /dev/null +++ b/mods/luau_runtime/src/services/ui.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include "../runtime.hpp" +#include "lua.h" + +namespace luau_runtime::services { + +void push_ui_handle(lua_State* state, Vm& vm, uint64_t value, HandleKind kind); + +}