Eingestellt
Plugin-Hot-Reload
Ein Entwicklerwerkzeug, das einzelne Plugins statt des gesamten Gamemodes neu lud. Es trug vier Monate Feature-Arbeit und wurde dann eingestellt.
- Rolle
- Entwurf und Umsetzung
- Stack
- Lua (GLua), NutScript-Interna
- Kontext
- Lokaler Entwicklungs-Workflow
- Status
- Eingestellt
Das Problem
Jede Änderung an einer Gamemode-Datei lud den gesamten Gamemode neu und fror meinen lokalen Server jedes Mal etwa zwanzig Sekunden lang ein, wenn ich etwas testen wollte, selbst bei einer Änderung von einem einzigen Zeichen. Über einen Tag voller Iterationen summiert sich das zu sehr viel Warterei.
Wie es funktionierte
- Plugins wurden aus dem Gamemode-Ordner herausgenommen, wo die Engine einzelne Dateien selbstständig auffrischen kann.
- Ein Einzeiler am Anfang jeder Plugin-Datei fing diese Auffrischung ab und lud stattdessen über meinen eigenen Loader das Plugin-Set neu.
- Der Loader spiegelte den des Frameworks: Bibliotheken, Fraktionen, Klassen, Interface-Code, Entities, Waffen, Effekte, Sprachen, Sub-Plugins und Items.
- Plugin-Tabellen wurden über Reloads hinweg wiederverwendet, sodass der Zustand erhalten blieb und Event-Handler ersetzt statt dupliziert wurden.
- Nach dem Laden spielte es die Start-Events des Frameworks erneut ab, mit einer Opt-out-Liste für Plugins, die es nicht vertrugen, zweimal ausgeführt zu werden.
- Das Profiling zeigte, dass der Neuaufbau des Spawn-Menüs eine Hauptursache für Lags war, also wurde er bei Reloads unterdrückt.
-- The one line at the top of every plugin file:
--
-- if (HR and HR()) then return end
--
-- When the engine refreshes a single plugin file, this hands the reload to my
-- loader and stops the stale copy from running on its own.
local function hotreloadPlugins()
if (HR_HOTRELOADING) then return end
HR_HOTRELOADING = true
hr.plugin.loadFromDir("plugins-hr") -- mirrors the framework's own loader (omitted)
-- Rebuilding the spawn menu was the main lag source, so it's suppressed during reloads.
hook.Add("SpawnMenuEnabled", "hotreload-lagfix", function() return false end)
-- Replay the framework's start-up events, skipping plugins on the
-- opt-out list that can't handle running them twice.
local skipped = {}
for uniqueID in pairs(skipInitializedPlugins) do
local plugin = nut.plugin.list[uniqueID]
local fn = hook.GetTable()["InitializedPlugins"][plugin]
if (fn) then
hook.Remove("InitializedPlugins", plugin)
skipped[plugin] = fn
end
end
hook.Run("InitializedPlugins")
for plugin, fn in pairs(skipped) do
hook.Add("InitializedPlugins", plugin, fn)
end
hook.Remove("SpawnMenuEnabled", "hotreload-lagfix")
hook.Run("InitializedItems")
HR_HOTRELOADING = nil
end
function HR()
-- Only plugins from the hot-reload folder are handled.
local src = debug.getinfo(2, "S").source
if (src and not string.find(src, "hotreload%-plugins")) then return end
-- Re-entrancy: never start a reload while one is running, while the loader
-- is including plugin files itself, or right after a full gamemode reload.
if (HR_HOTRELOADING) then return end
if (HR_PLUGIN_LOADING or HR_PLUGINS_ALREADY_LOADED) then return end
hotreloadPlugins()
return true
end
-- A full gamemode reload loads the plugins once, then blocks the per-file
-- guard for a second so every plugin file doesn't trigger its own reload.
hook.Add("OnReloaded", "hotreload-gm-reload", function()
if (HR_PLUGINS_ALREADY_LOADED) then return end
timer.Simple(0, hotreloadPlugins)
HR_PLUGINS_ALREADY_LOADED = true
timer.Create("hotreload-gm-reload", 1, 1, function()
HR_PLUGINS_ALREADY_LOADED = false
end)
end)Was es getragen hat
Von Februar bis Juni 2025 habe ich damit die meisten neuen Features gebaut, darunter Fraktions-Events, Konflikt-Sperren, Verstecke von Kriminellen, Waffentaschen, Barrikaden, Lockpicking und die erste Version der Verbrechen.
Warum es eingestellt wurde
Seine Schwäche war der Fußabdruck. Jede hot-reloadbare Datei brauchte diesen Einzeiler, sodass eine Erleichterung für meinen eigenen Workflow Teil der Codebasis wurde, die alle anderen pflegen. Nach einem Kompatibilitätsproblem mit einem Plugin, das beim Start ungewöhnliche Dinge tat, haben der Eigentümer und ich uns darauf geeinigt, es einzustellen.
Würde ich es noch einmal bauen, würde ich es für den Code selbst unsichtbar machen: ein File-Watcher nur für die Entwicklung, der Plugins von außerhalb des Gamemodes neu lädt, ohne dass den Plugin-Dateien etwas hinzugefügt wird.