From ff9ba0271fc175a8b3cc957827c4d89e300d85f3 Mon Sep 17 00:00:00 2001 From: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> Date: Fri, 13 Dec 2024 13:48:52 +0000 Subject: [PATCH] Expand compat to 0.17 (#711) --- .../templates/common/module/control.lua | 8 +- packages/host/modules/clusterio/api.lua | 6 +- packages/host/modules/clusterio/compat.lua | 178 +++++++++++------- packages/host/modules/clusterio/impl.lua | 18 +- packages/host/modules/clusterio/module.json | 5 +- .../inventory_sync/module/get_script_data.lua | 5 +- .../inventory_sync/module/inventory_sync.lua | 2 +- plugins/research_sync/module/sync.lua | 5 +- 8 files changed, 138 insertions(+), 89 deletions(-) diff --git a/packages/create/templates/common/module/control.lua b/packages/create/templates/common/module/control.lua index 06546e10..914fcfb6 100644 --- a/packages/create/templates/common/module/control.lua +++ b/packages/create/templates/common/module/control.lua @@ -12,9 +12,8 @@ local MyModule = { -- If you use any of these events and access globalData then make sure to call this function first local script_data = {} local function setup_script_data() - local _script_data = compat.script_data() - if _script_data["__plugin_name__"] == nil then - _script_data["__plugin_name__"] = { + if compat.script_data["__plugin_name__"] == nil then + compat.script_data["__plugin_name__"] = { -- starting values go here } end @@ -24,8 +23,7 @@ end --- The on_load function is called independently for each client when they first load the map -- It should be used to restore global aliases and metatables not registered with script.register_metatable function MyModule.on_load() - local _script_data = compat.script_data() - script_data = _script_data["__plugin_name__"] + script_data = compat.script_data["__plugin_name__"] end --- Public methods should be available though your top level module table diff --git a/packages/host/modules/clusterio/api.lua b/packages/host/modules/clusterio/api.lua index 11ea3aa4..49ffda06 100644 --- a/packages/host/modules/clusterio/api.lua +++ b/packages/host/modules/clusterio/api.lua @@ -10,11 +10,11 @@ api.events = { } function api.get_instance_name() - return compat.script_data().clusterio.instance_name + return compat.script_data.clusterio.instance_name end function api.get_instance_id() - return compat.script_data().clusterio.instance_id + return compat.script_data.clusterio.instance_id end function api.send_json(channel, data) @@ -33,7 +33,7 @@ function api.send_json(channel, data) if #data < 4000 then print("\f$ipc:" .. channel .. "?j" .. data) else - local script_data = compat.script_data() + local script_data = compat.script_data script_data.clusterio_file_no = (script_data.clusterio_file_no or 0) + 1 local file_name = "clst_" .. script_data.clusterio_file_no .. ".json" compat.write_file(file_name, data, false, 0) diff --git a/packages/host/modules/clusterio/compat.lua b/packages/host/modules/clusterio/compat.lua index bce3e0fc..68117d8e 100644 --- a/packages/host/modules/clusterio/compat.lua +++ b/packages/host/modules/clusterio/compat.lua @@ -1,14 +1,30 @@ --[[ -Adds various functions used to ensure compatibly between version including some polyfills +Adds various functions used to ensure compatibly between version including some polyfills. + +Implementation Guidelines +- If a function is only available at runtime in one version, then all versions must be made to error. +- Where possible, directly assign the function / table to the member of compat. +- When not possible, then it should replace itself with a direct assignment asap. +- Versions are handled in ascending order covering all supported versions and ending with an error. ]] -local json = require("json") +local lib_json = require("json") --- @diagnostic disable: deprecated --- @class LibCompat +--- @field script_data table +--- @field prototypes LuaPrototypes +--- @field active_mods table +--- @field write_file fun(filename: string, data: LocalisedString, append: boolean?, for_player: uint?) +--- @field table_to_json fun(data: table): string +--- @field json_to_table fun(json: string): AnyBasic? local compat = {} +--- @class LibCompatMt +--- @package +local compat_mt = {} + --- @alias VersionTable { [1]: number, [2]: number, [3]: number } --- Concert a version string into a version table @@ -37,12 +53,6 @@ do end end ---- Raise an error because the current version is unsupported ---- @return any -local function unsupported_version() - error("Unsupported version: " .. current_version_str, 3) -end - --- Returns true if the current version is equal to the provided version --- @param version string Version to compare against --- @return boolean # True if versions are equal @@ -86,85 +96,127 @@ function compat.version_le(version) return true end ---- The major versions of factorio we support -local major_v2 = compat.version_ge("2.0.0") -local major_v1_1 = not major_v2 and compat.version_ge("1.1.0") --- local major_v1 = not major_v1_1 and compat.version_ge("1.0.0") +--- The versions of factorio we support +local v2_0 = compat.version_ge("2.0") +local v1_1 = not v2_0 and compat.version_ge("1.1") +local v1_0 = not v1_1 and compat.version_ge("1.0") +local v0_18 = not v1_0 and compat.version_ge("0.18") +local v0_17 = not v0_18 and compat.version_ge("0.17") ---- Returns the table reference used to store script data ---- @return table script_data -function compat.script_data() - if major_v1_1 then - --- @diagnostic disable-next-line - return global - elseif major_v2 then - return storage - end - return unsupported_version() +local v2 = compat.version_ge("2.0") +local v1 = not v2 and compat.version_ge("1.0") +local v0 = not v1 and compat.version_ge("0.17") + +--- Raise an error because the current version is unsupported +local function unsupported_version() + error("Unsupported version: " .. current_version_str, 3) end ---- Returns the table reference used to store prototypes ---- @param category string Category of prototype to access, eg "item", "fluid", "recipe" ---- @return table # Table of prototype data -function compat.prototype_data(category) - if major_v1_1 then - return game[category .. "_prototypes"] - elseif major_v2 then - return prototypes[category] - end - return unsupported_version() +--- Raise an error because the function is only available at runtime +local function runtime_only() + error("To maintain compatibly, this value can only be used during runtime.", 3) end ---- Returns the table reference used to list active mods ---- @return table # Table of active mods -function compat.active_mods() - if major_v1_1 or major_v2 then +--- Keys of this table will raise an unsupported error when accessed +local unsupported_properties = {} + +--- Keys of this table can only be accessed at runtime +--- The value is a function which returns the real value +local runtime_properties = {} + +--- Handles the indexing of the tables above +function compat_mt:__index(key) + if unsupported_properties[key] then + return unsupported_version() + elseif runtime_properties[key] then + if game == nil then + return runtime_only() + end + local value = runtime_properties[key]() + if value == nil then + return unsupported_version() + end + rawset(self, key, value) + return value + end +end + +--- Prototype data contains all the definitions for the game, it is only available during runtime +function runtime_properties.prototypes() + if v0 or v1 then + --- Lazy loading of prototype data + return setmetatable({}, { + -- Prevent iteration due to lazy loading, its a really rare use case + __pairs = function() error("Pairs not supported in " .. current_version_str, 2) end, + __ipairs = function() error("IPairs not supported in " .. current_version_str, 2) end, + __index = function(self, key) + local value = game[key .. "_prototypes"] + rawset(self, key, value) + return value + end + }) + elseif v2 then + return prototypes + end +end + +--- List of all active mods +function runtime_properties.active_mods() + if v0_17 then + return game.active_mods + elseif v0 or v1 or v2 then return script.active_mods end - return unsupported_version() end ---- @param tbl table ---- @return string -function compat.table_to_json(tbl) - return unsupported_version() +--- Writes a string to file +function runtime_properties.write_file() + if v0 or v1 then + return game.write_file + elseif v2 then + return helpers.write_file + end end ---- @param json string ---- @return table -function compat.json_to_table(json) - return unsupported_version() -end - ---- @param filename string ---- @param data LocalisedString ---- @param append boolean? ---- @param for_player uint? -function compat.write_file(filename, data, append, for_player) - unsupported_version() -end - ---- Select the appropriate polyfill implementation -if major_v1_1 then +--- Convert tables to and from json, we have backported this to work before runtime +if v0 or v1 then compat.table_to_json = function(tbl) if game then compat.table_to_json = game.table_to_json return game.table_to_json(tbl) end - return json.encode(tbl) + return lib_json.encode(tbl) end compat.json_to_table = function(json) if game then compat.json_to_table = game.json_to_table return game.json_to_table(json) end - return json.decode(json) + return lib_json.decode(json) end - compat.write_file = function(filename, data, append, for_player) return game.write_file(filename, data, append, for_player) end -elseif major_v2 then +elseif v2 then compat.table_to_json = helpers.table_to_json compat.json_to_table = helpers.json_to_table - compat.write_file = helpers.write_file +else + unsupported_properties.table_to_json = true + unsupported_properties.json_to_table = true end -return compat +--- Script data refers to lua data which is persisted between loads +--- This is called immediately, during init and during load +local function set_script_data() + if v0 or v1 then + --- @diagnostic disable-next-line + compat.script_data = global + elseif v2 then + compat.script_data = storage + else + unsupported_properties.script_data = true + end +end + +set_script_data() +compat.on_init = set_script_data --- @package +compat.on_load = set_script_data --- @package + +return setmetatable(compat, compat_mt) diff --git a/packages/host/modules/clusterio/impl.lua b/packages/host/modules/clusterio/impl.lua index f7923855..5cb88e8a 100644 --- a/packages/host/modules/clusterio/impl.lua +++ b/packages/host/modules/clusterio/impl.lua @@ -4,9 +4,8 @@ local compat = require("modules/clusterio/compat") --- @class (exact) EventData.on_server_startup:EventData local function check_patch() - local script_data = compat.script_data() - if script_data.clusterio_patch_number ~= clusterio_patch_number then - script_data.clusterio_patch_number = clusterio_patch_number + if compat.script_data.clusterio_patch_number ~= clusterio_patch_number then + compat.script_data.clusterio_patch_number = clusterio_patch_number script.raise_event(api.events.on_server_startup, { name = api.events.on_server_startup, tick = game.tick }) @@ -19,9 +18,8 @@ impl.events = {} impl.events[defines.events.on_tick] = check_patch impl.events[api.events.on_server_startup] = function() - local script_data = compat.script_data() - if not script_data.clusterio then - script_data.clusterio = { + if not compat.script_data.clusterio then + compat.script_data.clusterio = { instance_id = nil, instance_name = nil, } @@ -55,8 +53,8 @@ end -- Internal API clusterio_private = {} function clusterio_private.update_instance(new_id, new_name) - local script_data = compat.script_data() check_patch() + local script_data = compat.script_data script_data.clusterio.instance_id = new_id script_data.clusterio.instance_name = new_name script.raise_event(api.events.on_instance_updated, { @@ -78,15 +76,15 @@ remote.add_interface('clusterio_api', { end, get_instance_id = function() - return compat.script_data().clusterio.instance_id + return compat.script_data.clusterio.instance_id end, get_instance_name = function() - return compat.script_data().clusterio.instance_name + return compat.script_data.clusterio.instance_name end, get_file_no = function() - local script_data = compat.script_data() + local script_data = compat.script_data script_data.clusterio_file_no = (script_data.clusterio_file_no or 0) + 1 return script_data.clusterio_file_no end, diff --git a/packages/host/modules/clusterio/module.json b/packages/host/modules/clusterio/module.json index 81dd7b1f..ca853adc 100644 --- a/packages/host/modules/clusterio/module.json +++ b/packages/host/modules/clusterio/module.json @@ -2,5 +2,8 @@ "name": "clusterio", "version": "0.0.0", "dependencies": {}, - "load": ["impl.lua"] + "load": [ + "impl.lua", + "compat.lua" + ] } diff --git a/plugins/inventory_sync/module/get_script_data.lua b/plugins/inventory_sync/module/get_script_data.lua index e9dacf3a..eb92ad16 100644 --- a/plugins/inventory_sync/module/get_script_data.lua +++ b/plugins/inventory_sync/module/get_script_data.lua @@ -2,8 +2,7 @@ local compat = require("modules/clusterio/compat") --- @param no_early_return boolean? True to always setup data return function(no_early_return) - local script_data = compat.script_data() - local inventory_sync = script_data.inventory_sync + local inventory_sync = compat.script_data.inventory_sync if inventory_sync and not no_early_return then return inventory_sync end @@ -17,6 +16,6 @@ return function(no_early_return) inventory_sync.finished_downloads = inventory_sync.finished_downloads or {} inventory_sync.active_uploads = inventory_sync.active_uploads or {} - script_data.inventory_sync = inventory_sync + compat.script_data.inventory_sync = inventory_sync return inventory_sync end diff --git a/plugins/inventory_sync/module/inventory_sync.lua b/plugins/inventory_sync/module/inventory_sync.lua index f800185f..30bf9232 100644 --- a/plugins/inventory_sync/module/inventory_sync.lua +++ b/plugins/inventory_sync/module/inventory_sync.lua @@ -360,7 +360,7 @@ end -- Upload inventory when a player leaves the game. Triggers on restart after crash if player was online during crash. inventory_sync.events[defines.events.on_pre_player_left_game] = function(event) -- for some reason, on_pre_player_left_game gets called before on_server_startup so script_data isn't ready yet - if not compat.script_data().inventory_sync then + if not compat.script_data.inventory_sync then log("ERROR: script data for inventory sync is not defined") return end diff --git a/plugins/research_sync/module/sync.lua b/plugins/research_sync/module/sync.lua index 3ddb0b79..7db1f20d 100644 --- a/plugins/research_sync/module/sync.lua +++ b/plugins/research_sync/module/sync.lua @@ -33,8 +33,7 @@ end --- @param no_early_return boolean? --- @return table local function get_script_data(no_early_return) - local script_data = compat.script_data() - local research_sync = script_data.research_sync + local research_sync = compat.script_data.research_sync if research_sync and not no_early_return then return research_sync end @@ -56,7 +55,7 @@ local function get_script_data(no_early_return) } end - script_data.research_sync = research_sync + compat.script_data.research_sync = research_sync return research_sync end