mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-13 07:04:53 +09:00
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>
92 lines
3.2 KiB
Lua
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
|