Make GuiData more lean

This commit is contained in:
Cooldude2606
2025-01-08 23:49:40 +00:00
parent 0b665bbd46
commit ee6359582c

View File

@@ -6,15 +6,20 @@ This is not limited to GUI element definitions but this is the most common use c
local ExpUtil = require("modules/exp_util") local ExpUtil = require("modules/exp_util")
local Storage = require("modules/exp_util/storage") local Storage = require("modules/exp_util/storage")
--- @type table<string, ExpGui.GuiData> --- @type table<string, ExpGui.GuiData_Raw>
local scope_data = {}
--- @type table<string, ExpGui.GuiData_Internal>
local registered_scopes = {} local registered_scopes = {}
--- @type table<uint, uint> Reg -> Player Index --- @type table<uint, uint> Reg -> Player Index
local registration_numbers = {} local registration_numbers = {}
local reg_obj = script.register_on_object_destroyed local reg_obj = script.register_on_object_destroyed
--- @type table<string, [table<uint, table<uint, any>>, table<uint, any>, table<uint, any>, table]> --- @alias DataKey LuaGuiElement | LuaPlayer | LuaForce
local scope_data = {} --- @alias DataKeys "element_data" | "player_data" | "force_data" | "global_data"
local DataKeys = ExpUtil.enum{ "element_data", "player_data", "force_data", "global_data" }
local DataKeysConcat = table.concat(DataKeys, ", ")
Storage.register({ Storage.register({
scope_data = scope_data, scope_data = scope_data,
@@ -24,10 +29,10 @@ Storage.register({
for scope, data in pairs(tbl.scope_data) do for scope, data in pairs(tbl.scope_data) do
local proxy = registered_scopes[scope] local proxy = registered_scopes[scope]
if proxy then if proxy then
proxy.element_data = data[1] rawset(proxy, "_raw", data)
proxy.player_data = data[2] for k, v in pairs(data) do
proxy.force_data = data[3] rawset(proxy, k, v)
proxy.global_data = data[4] end
end end
end end
end) end)
@@ -37,43 +42,68 @@ local GuiData = {
_scopes = registered_scopes, _scopes = registered_scopes,
} }
--- @alias DataKey LuaGuiElement | LuaPlayer | LuaForce --- @class ExpGui.GuiData_Raw
--- @field element_data table<uint, table<uint, any>?>?
--- @field player_data table<uint, any>?
--- @field force_data table<uint, any>?
--- @field global_data table?
-- This class has no prototype methods
-- Keep this in sync with DataKeys to block arbitrary strings
--- @class ExpGui.GuiData: table<DataKey, any> --- @class ExpGui.GuiData_Internal
--- @field _scope string --- @field _scope string
--- @field _owner any --- @field _raw ExpGui.GuiData_Raw
-- This class has no prototype methods
-- Do add keys to _raw without also referencing scope_data
--- @class ExpGui.GuiData: table
--- @field element_data table<uint, table<uint, any>> --- @field element_data table<uint, table<uint, any>>
--- @field player_data table<uint, any> --- @field player_data table<uint, any>
--- @field force_data table<uint, any> --- @field force_data table<uint, any>
--- @field global_data table --- @field global_data table
-- This class has no prototype methods -- This class has no prototype methods
-- Same as raw but __index ensures the values exist
GuiData._metatable = { GuiData._metatable = {
__class = "GuiData", __class = "GuiData",
} }
Storage.register_metatable(GuiData._metatable.__class, GuiData._metatable)
--- Return the index for a given key --- Return the index for a given key
--- @param self ExpGui.GuiData --- @param self ExpGui.GuiData_Internal
--- @param key DataKey --- @param key DataKeys | DataKey
--- @return any --- @return any
function GuiData._metatable.__index(self, key) function GuiData._metatable.__index(self, key)
if type(key) == "string" then
-- This is only called when the key does not exist, ie it is not in storage
-- Once created, these tables are never removed as they are likely to be used again
assert(DataKeys[key], "Valid keys are: " .. DataKeysConcat)
local value = {}
self._raw[key] = value
rawset(self, key, value)
scope_data[self._scope] = self._raw
return value
end
-- Check a given child table based on the object type
assert(type(key) == "userdata", "Index type '" .. ExpUtil.get_class_name(key) .. "' given to GuiData. Must be of type userdata.") assert(type(key) == "userdata", "Index type '" .. ExpUtil.get_class_name(key) .. "' given to GuiData. Must be of type userdata.")
local object_name = key.object_name local object_name = key.object_name
if object_name == "LuaGuiElement" then if object_name == "LuaGuiElement" then
local player_elements = self.element_data[key.player_index] local data = self._raw.element_data
local player_elements = data and data[key.player_index]
return player_elements and player_elements[key.index] return player_elements and player_elements[key.index]
elseif object_name == "LuaPlayer" then elseif object_name == "LuaPlayer" then
return self.player_data[key.index] local data = self._raw.player_data
return data and data[key.index]
elseif object_name == "LuaForce" then elseif object_name == "LuaForce" then
return self.force_data[key.index] local data = self._raw.force_data
return data and data[key.index]
else else
error("Unsupported object class '" .. object_name .. "' given as index to GuiData.") error("Unsupported object class '" .. object_name .. "' given as index to GuiData.")
end end
end end
--- Set the value index of a given key --- Set the value index of a given key
-- Internal type is not used here to allow for creation of storage
--- @param self ExpGui.GuiData --- @param self ExpGui.GuiData
--- @param key DataKey --- @param key DataKey
--- @param value unknown --- @param value unknown
@@ -81,10 +111,11 @@ function GuiData._metatable.__newindex(self, key, value)
assert(type(key) == "userdata", "Index type '" .. ExpUtil.get_class_name(key) .. "' given to GuiData. Must be of type userdata.") assert(type(key) == "userdata", "Index type '" .. ExpUtil.get_class_name(key) .. "' given to GuiData. Must be of type userdata.")
local object_name = key.object_name local object_name = key.object_name
if object_name == "LuaGuiElement" then if object_name == "LuaGuiElement" then
local player_elements = self.element_data[key.player_index] local data = self.element_data
local player_elements = data[key.player_index]
if not player_elements then if not player_elements then
player_elements = {} player_elements = {}
self.element_data[key.player_index] = player_elements data[key.player_index] = player_elements
end end
player_elements[key.index] = value player_elements[key.index] = value
registration_numbers[reg_obj(key)] = key.player_index registration_numbers[reg_obj(key)] = key.player_index
@@ -106,20 +137,11 @@ function GuiData.create(scope)
local instance = { local instance = {
_scope = scope, _scope = scope,
element_data = {}, _raw = {},
player_data = {},
force_data = {},
global_data = {},
}
scope_data[scope] = {
instance.element_data,
instance.player_data,
instance.force_data,
instance.global_data,
} }
GuiData._scopes[scope] = instance GuiData._scopes[scope] = instance
--- @cast instance ExpGui.GuiData
return setmetatable(instance, GuiData._metatable) return setmetatable(instance, GuiData._metatable)
end end
@@ -127,7 +149,7 @@ end
--- @param scope string --- @param scope string
--- @return ExpGui.GuiData --- @return ExpGui.GuiData
function GuiData.get(scope) function GuiData.get(scope)
return GuiData._scopes[scope] return GuiData._scopes[scope] --[[ @as ExpGui.GuiData ]]
end end
--- Used to clean up data from destroyed elements --- Used to clean up data from destroyed elements
@@ -140,9 +162,13 @@ local function on_object_destroyed(event)
registration_numbers[event.registration_number] = nil registration_numbers[event.registration_number] = nil
for _, scope in pairs(registered_scopes) do for _, scope in pairs(registered_scopes) do
local player_elements = scope.element_data[player_index] local data = scope._raw.element_data
local player_elements = data and data[player_index]
if player_elements then if player_elements then
player_elements[element_index] = nil player_elements[element_index] = nil
if not next(player_elements) then
data[player_index] = nil
end
end end
end end
end end
@@ -152,7 +178,10 @@ end
local function on_player_removed(event) local function on_player_removed(event)
local player_index = event.player_index local player_index = event.player_index
for _, scope in pairs(registered_scopes) do for _, scope in pairs(registered_scopes) do
scope.player_data[player_index] = nil local data = scope._raw.player_data
if data then
data[player_index] = nil
end
end end
end end
@@ -161,7 +190,10 @@ end
local function on_forces_merged(event) local function on_forces_merged(event)
local force_index = event.source_index local force_index = event.source_index
for _, scope in pairs(registered_scopes) do for _, scope in pairs(registered_scopes) do
scope.force_data[force_index] = nil local data = scope._raw.force_data
if data then
data[force_index] = nil
end
end end
end end