From 5fc6ce608f2cccc58afcdff30767b976727a6a29 Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Tue, 7 Nov 2023 02:23:30 +0000 Subject: [PATCH] Lua: Overhaul events 1. `Events` global is replaced with `require('devilutionx.events')`. 2. Table is simplified and documented. 3. `On` removed from the event names as it was a bit redundant. 4. Functions are camelCase for consistency (e.g. `add` instead of `Add`). Example script: ```lua local events = require("devilutionx.events") local render = require("devilutionx.render") local message = require("devilutionx.message") local function greet() message("Hello from " .. _VERSION) print("Hello from ", _VERSION) end events.GameStart.add(greet) local function drawGreet() render.string("Hello from " .. _VERSION, 10, 40) end events.GameDrawComplete.add(drawGreet) ``` --- CMake/Assets.cmake | 2 +- .../assets/lua/devilutionx/events.lua | 65 +++++++++++++++++++ Packaging/resources/assets/lua/init.lua | 25 ------- .../resources/assets/lua/repl_prelude.lua | 1 + Source/diablo.cpp | 2 +- Source/engine/render/scrollrt.cpp | 2 +- Source/lua/lua.cpp | 23 +++---- 7 files changed, 81 insertions(+), 39 deletions(-) create mode 100644 Packaging/resources/assets/lua/devilutionx/events.lua delete mode 100644 Packaging/resources/assets/lua/init.lua diff --git a/CMake/Assets.cmake b/CMake/Assets.cmake index 4be07a91e..903433a5e 100644 --- a/CMake/Assets.cmake +++ b/CMake/Assets.cmake @@ -141,7 +141,7 @@ set(devilutionx_assets levels/towndata/automap.dun levels/towndata/automap.amp lua_internal/get_lua_function_signature.lua - lua/init.lua + lua/devilutionx/events.lua lua/inspect.lua lua/repl_prelude.lua nlevels/cutl5w.clx diff --git a/Packaging/resources/assets/lua/devilutionx/events.lua b/Packaging/resources/assets/lua/devilutionx/events.lua new file mode 100644 index 000000000..fd7913007 --- /dev/null +++ b/Packaging/resources/assets/lua/devilutionx/events.lua @@ -0,0 +1,65 @@ +local function CreateEvent() + local functions = {} + return { + ---Adds an event handler. + --- + ---The handler called every time an event is triggered. + ---@param func function + add = function(func) + table.insert(functions, func) + end, + + ---Removes the event handler. + ---@param func function + remove = function(func) + for i, f in ipairs(functions) do + if f == func then + table.remove(functions, i) + break + end + end + end, + + ---Triggers an event. + --- + ---The arguments are forwarded to handlers. + ---@param ... any + trigger = function(...) + if arg ~= nil then + for _, func in ipairs(functions) do + func(table.unpack(arg)) + end + else + for _, func in ipairs(functions) do + func() + end + end + end, + __sig_trigger = "(...)", + } +end + +local events = { + ---Called early on game boot. + GameBoot = CreateEvent(), + __doc_GameBoot = "Called early on game boot.", + + ---Called every time a new game is started. + GameStart = CreateEvent(), + __doc_GameStart = "Called every time a new game is started.", + + ---Called every frame at the end. + GameDrawComplete = CreateEvent(), + __doc_GameDrawComplete = "Called every frame at the end.", +} + +---Registers a custom event type with the given name. +---@param name string +function events.registerCustom(name) + events[name] = CreateEvent() +end + +events.__sig_registerCustom = "(name: string)" +events.__doc_registerCustom = "Register a custom event type." + +return events diff --git a/Packaging/resources/assets/lua/init.lua b/Packaging/resources/assets/lua/init.lua deleted file mode 100644 index f59bd0171..000000000 --- a/Packaging/resources/assets/lua/init.lua +++ /dev/null @@ -1,25 +0,0 @@ -function Events:RegisterEvent(eventName) - self[eventName] = { - Functions = {}, - Add = function(func) - table.insert(self[eventName].Functions, func) - end, - Remove = function(func) - for i, f in ipairs(self[eventName].Functions) do - if f == func then - table.remove(self[eventName].Functions, i) - break - end - end - end, - Trigger = function() - for _, func in ipairs(self[eventName].Functions) do - func() - end - end, - } -end - -Events:RegisterEvent("OnGameBoot") -Events:RegisterEvent("OnGameStart") -Events:RegisterEvent("OnGameDrawComplete") diff --git a/Packaging/resources/assets/lua/repl_prelude.lua b/Packaging/resources/assets/lua/repl_prelude.lua index c659b05e0..72e61bc22 100644 --- a/Packaging/resources/assets/lua/repl_prelude.lua +++ b/Packaging/resources/assets/lua/repl_prelude.lua @@ -1,3 +1,4 @@ +events = require('devilutionx.events') log = require('devilutionx.log') audio = require('devilutionx.audio') render = require('devilutionx.render') diff --git a/Source/diablo.cpp b/Source/diablo.cpp index e48a7b2ab..6a9def32d 100644 --- a/Source/diablo.cpp +++ b/Source/diablo.cpp @@ -805,7 +805,7 @@ void RunGameLoop(interface_mode uMsg) nthread_ignore_mutex(false); discord_manager::StartGame(); - LuaEvent("OnGameStart"); + LuaEvent("GameStart"); #ifdef GPERF_HEAP_FIRST_GAME_ITERATION unsigned run_game_iteration = 0; #endif diff --git a/Source/engine/render/scrollrt.cpp b/Source/engine/render/scrollrt.cpp index 4dc1ddec9..25d0b321a 100644 --- a/Source/engine/render/scrollrt.cpp +++ b/Source/engine/render/scrollrt.cpp @@ -1657,7 +1657,7 @@ void DrawAndBlit() DrawFPS(out); - LuaEvent("OnGameDrawComplete"); + LuaEvent("GameDrawComplete"); DrawMain(out, hgt, drawInfoBox, drawHealth, drawMana, drawBelt, drawControlButtons); diff --git a/Source/lua/lua.cpp b/Source/lua/lua.cpp index ec1a59c32..18c33b03e 100644 --- a/Source/lua/lua.cpp +++ b/Source/lua/lua.cpp @@ -32,6 +32,7 @@ struct LuaState { sol::table commonPackages = {}; std::unordered_map compiledScripts = {}; sol::environment sandbox = {}; + sol::table events = {}; }; std::optional CurrentLuaState; @@ -164,14 +165,11 @@ sol::environment CreateLuaSandbox() // Register safe built-in globals. for (const std::string_view global : { - // DevilutionX - "Events", // Built-ins: "assert", "warn", "error", "ipairs", "next", "pairs", "pcall", "select", "tonumber", "tostring", "type", "xpcall", "rawequal", "rawget", "rawset", "setmetatable", // Built-in packages: - #ifdef _DEBUG "debug", #endif @@ -213,6 +211,10 @@ void LuaInitialize() // Registering devilutionx object table SafeCallResult(lua.safe_script(RequireGenSrc), /*optional=*/false); + + // Loaded without a sandbox. + CurrentLuaState->events = RunScript(/*env=*/std::nullopt, "devilutionx.events", /*optional=*/false); + CurrentLuaState->commonPackages = lua.create_table_with( #ifdef _DEBUG "devilutionx.dev", LuaDevModule(lua), @@ -222,16 +224,16 @@ void LuaInitialize() "devilutionx.audio", LuaAudioModule(lua), "devilutionx.render", LuaRenderModule(lua), "devilutionx.message", [](std::string_view text) { EventPlrMsg(text, UiFlags::ColorRed); }, - // Load the "inspect" package without the sandbox. + // These packages are loaded without a sandbox: + "devilutionx.events", CurrentLuaState->events, "inspect", RunScript(/*env=*/std::nullopt, "inspect", /*optional=*/false)); - // This table is set up by the init script. - lua.create_named_table("Events"); + // Used by the custom require implementation. + lua["setEnvironment"] = [](const sol::environment &env, const sol::function &fn) { sol::set_environment(env, fn); }; - RunScript(CreateLuaSandbox(), "init", /*optional=*/false); RunScript(CreateLuaSandbox(), "user", /*optional=*/true); - LuaEvent("OnGameBoot"); + LuaEvent("GameBoot"); } void LuaShutdown() @@ -244,10 +246,9 @@ void LuaShutdown() void LuaEvent(std::string_view name) { - const sol::state &lua = CurrentLuaState->sol; - const auto trigger = lua.traverse_get>("Events", name, "Trigger"); + const auto trigger = CurrentLuaState->events.traverse_get>(name, "trigger"); if (!trigger.has_value() || !trigger->is()) { - LogError("Events.{}.Trigger is not a function", name); + LogError("events.{}.trigger is not a function", name); return; } const sol::protected_function fn = trigger->as();