diff --git a/exp_gui/module/data.lua b/exp_gui/module/data.lua index 2e5604c8..113cff58 100644 --- a/exp_gui/module/data.lua +++ b/exp_gui/module/data.lua @@ -6,28 +6,33 @@ This is not limited to GUI element definitions but this is the most common use c local ExpUtil = require("modules/exp_util") local Storage = require("modules/exp_util/storage") ---- @type table +--- @type table +local scope_data = {} + +--- @type table local registered_scopes = {} --- @type table Reg -> Player Index local registration_numbers = {} local reg_obj = script.register_on_object_destroyed ---- @type table>, table, table, table]> -local scope_data = {} +--- @alias DataKey LuaGuiElement | LuaPlayer | LuaForce +--- @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({ - scope_data = scope_data, + scope_data = scope_data, registration_numbers = registration_numbers, }, function(tbl) registration_numbers = tbl.registration_numbers for scope, data in pairs(tbl.scope_data) do local proxy = registered_scopes[scope] if proxy then - proxy.element_data = data[1] - proxy.player_data = data[2] - proxy.force_data = data[3] - proxy.global_data = data[4] + rawset(proxy, "_raw", data) + for k, v in pairs(data) do + rawset(proxy, k, v) + end end end end) @@ -37,43 +42,68 @@ local GuiData = { _scopes = registered_scopes, } ---- @alias DataKey LuaGuiElement | LuaPlayer | LuaForce +--- @class ExpGui.GuiData_Raw +--- @field element_data table?>? +--- @field player_data table? +--- @field force_data table? +--- @field global_data table? +-- This class has no prototype methods +-- Keep this in sync with DataKeys to block arbitrary strings ---- @class ExpGui.GuiData: table +--- @class ExpGui.GuiData_Internal --- @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> --- @field player_data table --- @field force_data table --- @field global_data table -- This class has no prototype methods +-- Same as raw but __index ensures the values exist GuiData._metatable = { __class = "GuiData", } -Storage.register_metatable(GuiData._metatable.__class, GuiData._metatable) - --- Return the index for a given key ---- @param self ExpGui.GuiData ---- @param key DataKey +--- @param self ExpGui.GuiData_Internal +--- @param key DataKeys | DataKey --- @return any 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.") local object_name = key.object_name 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] 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 - return self.force_data[key.index] + local data = self._raw.force_data + return data and data[key.index] else error("Unsupported object class '" .. object_name .. "' given as index to GuiData.") end end --- 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 key DataKey --- @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.") local object_name = key.object_name 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 player_elements = {} - self.element_data[key.player_index] = player_elements + data[key.player_index] = player_elements end player_elements[key.index] = value registration_numbers[reg_obj(key)] = key.player_index @@ -106,20 +137,11 @@ function GuiData.create(scope) local instance = { _scope = scope, - element_data = {}, - player_data = {}, - force_data = {}, - global_data = {}, - } - - scope_data[scope] = { - instance.element_data, - instance.player_data, - instance.force_data, - instance.global_data, + _raw = {}, } GuiData._scopes[scope] = instance + --- @cast instance ExpGui.GuiData return setmetatable(instance, GuiData._metatable) end @@ -127,7 +149,7 @@ end --- @param scope string --- @return ExpGui.GuiData function GuiData.get(scope) - return GuiData._scopes[scope] + return GuiData._scopes[scope] --[[ @as ExpGui.GuiData ]] end --- Used to clean up data from destroyed elements @@ -140,9 +162,13 @@ local function on_object_destroyed(event) registration_numbers[event.registration_number] = nil 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 player_elements[element_index] = nil + if not next(player_elements) then + data[player_index] = nil + end end end end @@ -152,7 +178,10 @@ end local function on_player_removed(event) local player_index = event.player_index 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 @@ -161,7 +190,10 @@ end local function on_forces_merged(event) local force_index = event.source_index 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