Browse Source

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)
```
pull/6792/head
Gleb Mazovetskiy 2 years ago
parent
commit
5fc6ce608f
  1. 2
      CMake/Assets.cmake
  2. 65
      Packaging/resources/assets/lua/devilutionx/events.lua
  3. 25
      Packaging/resources/assets/lua/init.lua
  4. 1
      Packaging/resources/assets/lua/repl_prelude.lua
  5. 2
      Source/diablo.cpp
  6. 2
      Source/engine/render/scrollrt.cpp
  7. 23
      Source/lua/lua.cpp

2
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

65
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

25
Packaging/resources/assets/lua/init.lua

@ -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")

1
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')

2
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

2
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);

23
Source/lua/lua.cpp

@ -32,6 +32,7 @@ struct LuaState {
sol::table commonPackages = {};
std::unordered_map<std::string, sol::bytecode> compiledScripts = {};
sol::environment sandbox = {};
sol::table events = {};
};
std::optional<LuaState> 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<std::optional<sol::object>>("Events", name, "Trigger");
const auto trigger = CurrentLuaState->events.traverse_get<std::optional<sol::object>>(name, "trigger");
if (!trigger.has_value() || !trigger->is<sol::protected_function>()) {
LogError("Events.{}.Trigger is not a function", name);
LogError("events.{}.trigger is not a function", name);
return;
}
const sol::protected_function fn = trigger->as<sol::protected_function>();

Loading…
Cancel
Save