Skip to content

Commit

Permalink
Try handling the global to storage rename
Browse files Browse the repository at this point in the history
  • Loading branch information
ahicks92 committed Oct 21, 2024
1 parent ec8703c commit 6e2ad14
Show file tree
Hide file tree
Showing 17 changed files with 231 additions and 235 deletions.
64 changes: 32 additions & 32 deletions control.lua
Original file line number Diff line number Diff line change
Expand Up @@ -618,11 +618,11 @@ function tile_cycle(pindex)
end
end

--Checks if the global players table has been created, and if the table entry for this player exists. Otherwise it is initialized.
--Checks if the storage players table has been created, and if the table entry for this player exists. Otherwise it is initialized.
function check_for_player(index)
if not players then
global.players = global.players or {}
players = global.players
storage.players = storage.players or {}
players = storage.players
end
if players[index] == nil then
initialize(game.get_player(index))
Expand Down Expand Up @@ -1142,15 +1142,15 @@ end
--Initialize the globally saved data tables for a specific player.
function initialize(player)
local force = player.force.index
global.forces[force] = global.forces[force] or {}
local fa_force = global.forces[force]
storage.forces[force] = storage.forces[force] or {}
local fa_force = storage.forces[force]

global.players[player.index] = global.players[player.index] or {}
local faplayer = global.players[player.index]
storage.players[player.index] = storage.players[player.index] or {}
local faplayer = storage.players[player.index]
faplayer.player = player

if not fa_force.resources then
for pi, p in pairs(global.players) do
for pi, p in pairs(storage.players) do
if p.player.valid and p.player.force.index == force and p.resources and p.mapped then
fa_force.resources = p.resources
fa_force.mapped = p.mapped
Expand Down Expand Up @@ -2359,14 +2359,14 @@ function schedule(ticks_in_the_future, func_to_call, data_to_pass_1, data_to_pas
return
end
local tick = game.tick + ticks_in_the_future
local schedule = global.scheduled_events
local schedule = storage.scheduled_events
schedule[tick] = schedule[tick] or {}
table.insert(schedule[tick], { func_to_call, data_to_pass_1, data_to_pass_2, data_to_pass_3 })
end

--Handles a player joining into a game session.
function on_player_join(pindex)
players = players or global.players
players = players or storage.players
schedule(3, "call_to_fix_zoom", pindex)
schedule(4, "call_to_sync_graphics", pindex)
fa_localising.check_player(pindex)
Expand Down Expand Up @@ -2408,11 +2408,11 @@ end
function on_tick(event)
ScannerEntrypoint.on_tick()

if global.scheduled_events[event.tick] then
for _, to_call in pairs(global.scheduled_events[event.tick]) do
if storage.scheduled_events[event.tick] then
for _, to_call in pairs(storage.scheduled_events[event.tick]) do
_G[to_call[1]](to_call[2], to_call[3], to_call[4])
end
global.scheduled_events[event.tick] = nil
storage.scheduled_events[event.tick] = nil
end
move_characters(event)

Expand Down Expand Up @@ -6379,16 +6379,16 @@ script.on_event(defines.events.on_player_mined_item, function(event)
game.get_player(pindex).play_sound({ path = "Close-Inventory-Sound", volume_modifier = 1 })
end)

function ensure_global_structures_are_up_to_date()
global.forces = global.forces or {}
global.players = global.players or {}
players = global.players
function ensure_storage_structures_are_up_to_date()
storage.forces = storage.forces or {}
storage.players = storage.players or {}
players = storage.players
for pindex, player in pairs(game.players) do
initialize(player)
end

global.entity_types = {}
entity_types = global.entity_types
storage.entity_types = {}
entity_types = storage.entity_types

local types = {}
for _, ent in pairs(game.entity_prototypes) do
Expand All @@ -6410,8 +6410,8 @@ function ensure_global_structures_are_up_to_date()
end
table.insert(entity_types, "container")

global.production_types = {}
production_types = global.production_types
storage.production_types = {}
production_types = storage.production_types

local ents = game.entity_prototypes
local types = {}
Expand All @@ -6437,8 +6437,8 @@ function ensure_global_structures_are_up_to_date()
table.insert(production_types, "transport-belt")
table.insert(production_types, "container")

global.building_types = {}
building_types = global.building_types
storage.building_types = {}
building_types = storage.building_types

local ents = game.entity_prototypes
local types = {}
Expand All @@ -6451,24 +6451,24 @@ function ensure_global_structures_are_up_to_date()
end
table.insert(building_types, "character")

global.scheduled_events = global.scheduled_events or {}
storage.scheduled_events = storage.scheduled_events or {}
end

script.on_load(function()
players = global.players
entity_types = global.entity_types
production_types = global.production_types
building_types = global.building_types
players = storage.players
entity_types = storage.entity_types
production_types = storage.production_types
building_types = storage.building_types
end)

script.on_configuration_changed(ensure_global_structures_are_up_to_date)
script.on_configuration_changed(ensure_storage_structures_are_up_to_date)

script.on_init(function()
---@type any
local skip_intro_message = remote.interfaces["freeplay"]
skip_intro_message = skip_intro_message and skip_intro_message["set_skip_intro"]
if skip_intro_message then remote.call("freeplay", "set_skip_intro", true) end
ensure_global_structures_are_up_to_date()
ensure_storage_structures_are_up_to_date()
end)

script.on_event(defines.events.on_cutscene_cancelled, function(event)
Expand Down Expand Up @@ -6961,12 +6961,12 @@ script.on_event(defines.events.on_gui_confirmed, function(event)
if players[pindex].travel.creating then
--Create new point
players[pindex].travel.creating = false
table.insert(global.players[pindex].travel, {
table.insert(storage.players[pindex].travel, {
name = result,
position = fa_utils.center_of_tile(players[pindex].position),
description = "No description",
})
table.sort(global.players[pindex].travel, function(k1, k2)
table.sort(storage.players[pindex].travel, function(k1, k2)
return k1.name < k2.name
end)
printout(
Expand Down
12 changes: 5 additions & 7 deletions devdocs/scanner.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
Last reviewed: 2024-10-18 (update this if reviewing this doc)

NOTE: Current content is before Update 2.0. Primarily this means that we refer to "global". 2.0 renames this to "storage". If you are here after that rename and see references to global, this is what they mean.
Last reviewed: 2024-10-21 (update this if reviewing this doc)

# Introduction

Expand Down Expand Up @@ -31,7 +29,7 @@ If you need to do something more complicated, copy and modify an existing backen

To add a new scanner category open scanner-consts.lua and add it to `CATEGORIES`. Below, also add it to `CATEGORY_ORDER` so that the scanner knows to iterate it and where. Then make sure a localised string of the form `scanner-categoryname` e.g. `scanner-category-production` is present.

Finally you probably need to go to surface-scanner.lua and find the declaration of global-manager. Increment `ephemeral_state_version` by 1. This will wipe out and rebuild scanner information in saves as necessary. This is not always required, but if you are unsure whether it is or not then this is always safe to do.
Finally you probably need to go to surface-scanner.lua and find the declaration of storage-manager. Increment `ephemeral_state_version` by 1. This will wipe out and rebuild scanner information in saves as necessary. This is not always required, but if you are unsure whether it is or not then this is always safe to do.

Scanner ignores any prototype type not in these tables. E.g. we leave out beams and explosions.

Expand All @@ -45,13 +43,13 @@ We must account for all of this in an efficient way. We have under 16 milliseco

# The Big Idea

Let's step back and pretend that this isn't Factorio or Lua. We are in some programming language which has all the features we might want and without the constraints of needing to be safe to store in global. The obvious solution in that situation is to get some sort of list of new entities or tiles, and feed them into classes. And indeed, that's what we do. But with some Lua-specific and Factorio-specific tricks. This allows pushing all of the complexity to one spot, and makes extension and modification of the scanner possible without having to understand the rest. To see this, read any of the backends-- can you implement a callback that handles one entity? Yes.
Let's step back and pretend that this isn't Factorio or Lua. We are in some programming language which has all the features we might want and without the constraints of needing to be safe to store in storage. The obvious solution in that situation is to get some sort of list of new entities or tiles, and feed them into classes. And indeed, that's what we do. But with some Lua-specific and Factorio-specific tricks. This allows pushing all of the complexity to one spot, and makes extension and modification of the scanner possible without having to understand the rest. To see this, read any of the backends-- can you implement a callback that handles one entity? Yes.

But. This is Lua. So instead of classes we have closures and metatables. And we don't get closures because we have to store in global. Our only hammer is metatables, and as they say if all you have is a hammer... The way that Lua does OOP is via metatable tricks. See [Programmihng in Lua](https://www.lua.org/pil/16.html) for some information. This is Lua 5.1, but nothing changed about metatables in Lua 5.2.
But. This is Lua. So instead of classes we have closures and metatables. And we don't get closures because we have to store in storage. Our only hammer is metatables, and as they say if all you have is a hammer... The way that Lua does OOP is via metatable tricks. See [Programmihng in Lua](https://www.lua.org/pil/16.html) for some information. This is Lua 5.1, but nothing changed about metatables in Lua 5.2.

Factorio allows registering metatables by name with some boilerplate ceremony. We already use this in e.g. rulers. One invents a name which must never change, and then registers it at the top level. This makes a number of things complicated, most notably inheritance. For the sake of simplicity therefore we just drop empty methods in when a backend doesn't need them. For the most part that means exactly one: `on_new_entity` or `on_new_chunk`.

The elephant in the room of course is that single-entity.lua does in fact appear to be using callgbacks. This is true, but that too is a trick. In table-helpers.lua, a helper function called `nested_indexer` exists which knows how to chain metatables together. By using this, we can write the function at the bottom of backends/simple.lua which is able to hide away the complexity of packing callbacks and other things that can't safely store into global behind metatable tricks. The restriction is that one must do all of this at the top level of the code so that it loads when control.lua does, not after. This is fine for scanner, and indeed as can be seen in single-entity.lua works quite well.
The elephant in the room of course is that single-entity.lua does in fact appear to be using callgbacks. This is true, but that too is a trick. In table-helpers.lua, a helper function called `nested_indexer` exists which knows how to chain metatables together. By using this, we can write the function at the bottom of backends/simple.lua which is able to hide away the complexity of packing callbacks and other things that can't safely store into storage behind metatable tricks. The restriction is that one must do all of this at the top level of the code so that it loads when control.lua does, not after. This is fine for scanner, and indeed as can be seen in single-entity.lua works quite well.

The result of all of this is that one needs to know barely anything to add to it. While Lua OOP isn't really friendly to new Lua users, copying code around and changing some strings is enough.

Expand Down
Loading

0 comments on commit 6e2ad14

Please sign in to comment.