Files
factorio-clusterio/plugins/player_sync/module/download_inventory.lua
T
bbassieandClaude Opus 5.5 f12b016cec Invalidate instance copies when player data is imported or deleted
Generations alone could not order a repair against copies held by
instances. Deleting dropped the watermark, so a stale instance could
skip downloading the new baseline. An import could land on the same
generation as an upload that never reached the controller, and the
instance would then keep its own copy.

Imports and deletes now bump a per-player epoch, which the controller
returns on acquire and checks on upload. Instances throw away a copy
from an earlier epoch, and deleted players keep their generation and
epoch in player_sync_deleted.json. A restarted download also takes the
new generation instead of the outdated one.

Generations in player data must now be non-negative integers, so an
invalid import is refused before it reaches the datastore.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 18:52:54 +00:00

92 lines
3.2 KiB
Lua

local progress_dialog = require("modules/player_sync/gui/progress_dialog")
local ensure_character = require("modules/player_sync/ensure_character")
local restore_position = require("modules/player_sync/restore_position")
local get_script_data = require("modules/player_sync/get_script_data")
local serialize = require("modules/player_sync/serialize")
local compat = require("modules/clusterio/compat")
--- @param components string? JSON of the components the instance sent the data with, given with the first part
local function download_inventory(player_name, data, number, total, components)
local player = game.get_player(player_name)
if player == nil then
rcon.print("Player " .. player_name .. " does not exist")
return
end
local script_data = get_script_data()
local record = script_data.active_downloads[player_name]
if record == nil then
rcon.print("No active download is in progress for " .. player_name)
return
end
local player_record = script_data.players[player_name]
-- The components changed after the download was started
local sent_components = number == 1 and components and compat.json_to_table(components)
if sent_components and not serialize.same_components(record.components, sent_components) then
record.restart = true
end
if record.restart then
rcon.print("Restarting outdated download")
player_sync.initiate_inventory_download(player, player_record, record.generation, record.epoch)
return
end
if total == 0 then
-- Download was requested but the player had no data stored.
log("ERROR: Player sync failed, got empty player data for " .. player.name .. " from the controller")
player.print("ERROR: Player sync failed, got empty player data from the controller")
progress_dialog.remove(player)
-- Give the player a character and pretend that's the synced player data
if serialize.syncs(record.components, "controller") or player_record.local_controller then
ensure_character(player)
end
player_record.local_controller = nil
-- Restore player position and driving state
restore_position(player, record)
script_data.active_downloads[player_name] = nil
player_record.dirty = player.connected
player_record.sync_in_progress = false
player_record.sync = true
player_record.components = record.components
player_record.generation = record.generation
player_record.epoch = record.epoch
return
end
record.data = record.data .. data
-- Show progress in console
-- player.print("Downloaded "..(number + 1).."/"..total.." parts")
if number ~= total then
-- Show progress in GUI
progress_dialog.display(player, number, total)
-- Update activity to indicate this session is still going
record.last_active = game.ticks_played
return
end
-- Download is complete
progress_dialog.remove(player)
-- Remove download session and store finished download
script_data.active_downloads[player_name] = nil
script_data.finished_downloads[player_name] = record
local ticks = game.ticks_played - record.started
player.print("Imported player data in " .. ticks .. " ticks")
log("Imported player data for " .. player_name .. " in " .. ticks .. " ticks")
if player.connected then
player_sync.finish_download(player, record)
end
end
return download_inventory