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
This commit is contained in:
Pieter-Jan Briers
2026-09-11 06:23:02 +02:00
parent f1f3b5c64f
commit ec57c33054
20 changed files with 1056 additions and 167 deletions
+26 -4
View File
@@ -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)
@@ -0,0 +1,40 @@
#include "lua_bridge_helpers.hpp"
luabridge::TypeResult<std::reference_wrapper<luau_runtime::Vm>>
luabridge::Stack<luau_runtime::Vm&>::get(lua_State* L, int) {
auto& ref = luau_runtime::vm_from_registry(L);
return std::reference_wrapper(ref);
}
luabridge::TypeResult<std::reference_wrapper<const luau_runtime::Vm>>
luabridge::Stack<const luau_runtime::Vm&>::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;
}
}
@@ -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<T> 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<type>::push(lua_State* L, type value) { \
return Stack<std::string_view>::push(L, ::luau_runtime::lua_helpers::enum_value_to_str(L, value, names)); \
} \
\
TypeResult<type> Stack<type>::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<T> 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<type> { \
[[nodiscard]] static Result push(lua_State* L, type value); \
[[nodiscard]] static TypeResult<type> 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<luau_runtime::Vm&> {
[[nodiscard]] static TypeResult<std::reference_wrapper<luau_runtime::Vm>> 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<luau_runtime::Vm const&> {
[[nodiscard]] static TypeResult<std::reference_wrapper<luau_runtime::Vm const>> 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 <typename T, typename TLuaRef>
std::optional<T> opt(TLuaRef const& value) {
return value.template cast<std::optional<T>>().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<typename T, typename TLuaRef>
T opt_or(TLuaRef const& value, T const& default_value) {
return opt<T>(value).value_or(default_value);
}
+118
View File
@@ -0,0 +1,118 @@
#include "lua_helpers.hpp"
#include <cmath>
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<int64_t>(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;
}
}
+94
View File
@@ -0,0 +1,94 @@
#pragma once
#include "lua.h"
#include "lualib.h"
#include <string>
#include <typeinfo>
/*
* 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<typename T>
concept Enum = std::is_enum_v<T>;
/**
* 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 <Enum T>
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 <Enum T, size_t N>
T enum_str_to_value(lua_State* state, char const* str, EnumName<T> const(& options)[N]) {
std::string_view strv(str);
for (size_t i = 0; i < N; ++i) {
if (options[i].name == strv) {
return static_cast<T>(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 <Enum T, size_t N>
std::string_view enum_value_to_str(lua_State* state, T value, EnumName<T> const(& options)[N]) {
for (auto [nameValue, name] : options) {
if (nameValue == value) {
return name;
}
}
luaL_errorL(state, "Attempted to return invalid enum to Lua!");
}
}
+20 -106
View File
@@ -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 <algorithm>
#include <cmath>
@@ -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<ModContext*, std::unique_ptr<Vm>> s_vms;
void* limited_realloc(void* userData, void* pointer, size_t oldSize, size_t newSize) {
@@ -153,31 +160,6 @@ std::optional<std::string> 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<Vm*>(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<Vm*>(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<int64_t>(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>();
callback->vm = &vm;
+4 -21
View File
@@ -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
@@ -0,0 +1,7 @@
target_sources(luau_runtime PRIVATE
audio_res.cpp
bst.cpp
bst.hpp
wsys.cpp
wsys.hpp
)
@@ -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<LuaAudioWaveHandle>("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<LuaSoundTableHandle>("AudioSoundTableHandle")
.addFunction("unregister", &LuaSoundTableHandle::unregister)
.endClass();
lua_setreadonly(state, -1, true);
return 1;
}
}
@@ -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<AudioSoundTableEffectInfo> 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<uint16_t, LuaSoundTableHandle> add_sound_table_effect(
SoundEffectCategory category_id,
std::optional<AudioSoundTableEffectInfo> 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<AudioSoundTableStreamInfo> 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<uint16_t, LuaSoundTableHandle> add_sound_table_stream(
char const* file_path,
std::optional<AudioSoundTableStreamInfo> 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<SoundEffectCategory> 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<StreamPan> 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<AudioSoundTableEffectInfo> Stack<AudioSoundTableEffectInfo>::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<AudioSoundTableEffectInfo>::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<AudioSoundTableStreamInfo> Stack<AudioSoundTableStreamInfo>::get(
lua_State* L, int index) {
auto const ref = LuaRef::fromStack(L, index);
std::array<std::optional<StreamPan>, 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<AudioSoundTableStreamInfo>::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
@@ -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<AudioSoundTableEffectInfo> info,
lua_State* state,
Vm& vm);
std::tuple<uint16_t, LuaSoundTableHandle> add_sound_table_effect(
SoundEffectCategory category_id,
std::optional<AudioSoundTableEffectInfo> 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<AudioSoundTableStreamInfo> info,
lua_State* state,
Vm& vm);
std::tuple<uint16_t, LuaSoundTableHandle> add_sound_table_stream(
char const* file_path,
std::optional<AudioSoundTableStreamInfo> info,
lua_State* state,
Vm& vm);
}
namespace luabridge {
template<>
struct Stack<AudioSoundTableEffectInfo> {
[[nodiscard]] static Result push(lua_State* L, const AudioSoundTableEffectInfo& value);
[[nodiscard]] static TypeResult<AudioSoundTableEffectInfo> get(lua_State* L, int index);
};
template<>
struct Stack<AudioSoundTableStreamInfo> {
[[nodiscard]] static Result push(lua_State* L, const AudioSoundTableStreamInfo& value);
[[nodiscard]] static TypeResult<AudioSoundTableStreamInfo> get(lua_State* L, int index);
};
DECLARE_STRING_ENUM(SoundEffectCategory);
DECLARE_STRING_ENUM(StreamPan);
}
@@ -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<OwnedWaveInfo> 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<uint16_t, LuaAudioWaveHandle> add_wave(AudioWaveBank bank, char const* file, std::optional<OwnedWaveInfo> 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<AudioWaveInfo>::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<AudioRawWave>::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<AudioRawWave> Stack<AudioRawWave>::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<int16_t>(ref["sample_value_last"], 0),
.sample_value_penult = opt_or<int16_t>(ref["sample_value_penult"], 0),
};
}
using namespace luau_runtime::services::audio_res;
Result Stack<OwnedWaveInfo>::push(lua_State* L, const OwnedWaveInfo& value) {
return Stack<AudioWaveInfo>::push(L, value.info);
}
TypeResult<OwnedWaveInfo> Stack<OwnedWaveInfo>::get(lua_State* L, int index) {
auto const ref = LuaRef::fromStack(L, index);
AudioWaveInfo info {
.base_key = opt_or<uint8_t>(ref["base_key"], AUDIO_RES_DEFAULT_KEY),
.loop = opt_or<bool>(ref["loop"], false),
.loop_start_sample = opt_or<uint32_t>(ref["loop_start_sample"], 0),
.loop_end_sample = opt_or<uint32_t>(ref["loop_end_sample"], std::numeric_limits<uint32_t>::max()),
};
std::unique_ptr<AudioRawWave> raw;
if (!ref["raw_wave"].isNil()) {
raw = std::make_unique<AudioRawWave>(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<AudioWaveFormat> 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<AudioWaveBank> 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
@@ -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 <memory>
namespace luau_runtime::services::audio_res {
struct OwnedWaveInfo {
AudioWaveInfo info;
std::unique_ptr<AudioRawWave> 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<OwnedWaveInfo> info, lua_State* state, Vm& vm);
std::tuple<uint16_t, LuaAudioWaveHandle> add_wave(AudioWaveBank bank, char const* file, std::optional<OwnedWaveInfo> info, lua_State* state, Vm& vm);
}
namespace luabridge {
template<>
struct Stack<AudioWaveInfo> {
[[nodiscard]] static Result push(lua_State* L, const AudioWaveInfo& value);
};
template<>
struct Stack<AudioRawWave> {
[[nodiscard]] static Result push(lua_State* L, const AudioRawWave& value);
[[nodiscard]] static TypeResult<AudioRawWave> get(lua_State* L, int index);
};
template<>
struct Stack<luau_runtime::services::audio_res::OwnedWaveInfo> {
[[nodiscard]] static Result push(lua_State* L, const luau_runtime::services::audio_res::OwnedWaveInfo& value);
[[nodiscard]] static TypeResult<luau_runtime::services::audio_res::OwnedWaveInfo> get(lua_State* L, int index);
};
DECLARE_STRING_ENUM(AudioWaveFormat);
DECLARE_STRING_ENUM(AudioWaveBank);
}
@@ -1,12 +1,15 @@
#include "runtime.hpp"
#include "../lua_helpers.hpp"
#include "../runtime.hpp"
#include <cstring>
#include <string>
#include <vector>
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";
@@ -1,11 +1,16 @@
#include "runtime.hpp"
#include "config.hpp"
#include "../runtime.hpp"
#include <string>
#include <vector>
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";
+10
View File
@@ -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);
}
@@ -0,0 +1,32 @@
#include "services.hpp"
namespace luau_runtime::services {
namespace {
using namespace std::string_view_literals;
constexpr std::pair<std::string_view, lua_CFunction> 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;
}
}
@@ -0,0 +1,21 @@
#pragma once
#include <string_view>
#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);
}
@@ -1,13 +1,20 @@
#include "runtime.hpp"
#include "ui.hpp"
#include "../runtime.hpp"
#include <algorithm>
#include <string>
#include <utility>
#include <vector>
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<UiListItem> list_items(lua_State* state, int table, std::vector<std:
return items;
}
constexpr EnumName<UiControlKind> 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<UiStyleScope> 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<UiStyleScope>(state, scope.data(), kStyleScopeNames);
}
int pane_add_section(lua_State* state) {
+10
View File
@@ -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);
}