mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-12 23:04:52 +09:00
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>
This commit is contained in:
1 parent
1d14148606
commit
f12b016cec
9 files changed
+159
-39
No files matched your search
@@ -99,6 +99,7 @@ function player_sync.serialize_player(player, player_record)
|
||||
local failed_deserialization = get_script_data().failed_deserialization[player.name] or {}
|
||||
local serialized_player = serialize.serialize_player(player, failed_deserialization, player_record.components)
|
||||
serialized_player.generation = player_record.generation
|
||||
serialized_player.epoch = player_record.epoch
|
||||
|
||||
return serialized_player
|
||||
end
|
||||
@@ -269,9 +270,15 @@ function player_sync.sync_player(acquire_response)
|
||||
end
|
||||
|
||||
local script_data = get_script_data()
|
||||
-- Epoch is bumped when an admin replaces or deletes the stored data
|
||||
local epoch = acquire_response.epoch or 0
|
||||
local finished_record = script_data.finished_downloads[acquire_response.player_name]
|
||||
if finished_record then
|
||||
if acquire_response.status == "acquired" and finished_record.generation == acquire_response.generation then
|
||||
if
|
||||
acquire_response.status == "acquired"
|
||||
and finished_record.generation == acquire_response.generation
|
||||
and (finished_record.epoch or 0) == epoch
|
||||
then
|
||||
player_sync.finish_download(player, finished_record)
|
||||
return
|
||||
end
|
||||
@@ -284,7 +291,11 @@ function player_sync.sync_player(acquire_response)
|
||||
local download_record = script_data.active_downloads[acquire_response.player_name]
|
||||
if download_record then
|
||||
-- Ignore the response if it affirms the current active download
|
||||
if acquire_response.status == "acquired" and download_record.generation == acquire_response.generation then
|
||||
if
|
||||
acquire_response.status == "acquired"
|
||||
and download_record.generation == acquire_response.generation
|
||||
and (download_record.epoch or 0) == epoch
|
||||
then
|
||||
game.print("Continuing active download")
|
||||
return
|
||||
end
|
||||
@@ -305,6 +316,8 @@ function player_sync.sync_player(acquire_response)
|
||||
-- download was started. Restart it.
|
||||
game.print("Restarting download")
|
||||
download_record.restart = true
|
||||
download_record.generation = acquire_response.generation
|
||||
download_record.epoch = epoch
|
||||
return
|
||||
end
|
||||
|
||||
@@ -333,15 +346,17 @@ function player_sync.sync_player(acquire_response)
|
||||
return
|
||||
end
|
||||
|
||||
local stale = (player_record.epoch or 0) ~= epoch
|
||||
if
|
||||
acquire_response.has_data
|
||||
and (
|
||||
not player_record.sync
|
||||
or stale
|
||||
or acquire_response.generation > player_record.generation
|
||||
or serialize.newly_synced(player_record.components, script_data.components)
|
||||
)
|
||||
then
|
||||
player_sync.initiate_inventory_download(player, player_record, acquire_response.generation)
|
||||
player_sync.initiate_inventory_download(player, player_record, acquire_response.generation, epoch)
|
||||
|
||||
else
|
||||
if acquire_response.generation == 0 then
|
||||
@@ -355,6 +370,9 @@ function player_sync.sync_player(acquire_response)
|
||||
start_sync(player_record, script_data.components)
|
||||
player_record.dirty = true
|
||||
player_record.sync_in_progress = false
|
||||
-- Deleted data keeps its generation and epoch so the next upload is accepted as the new baseline
|
||||
player_record.epoch = epoch
|
||||
player_record.generation = math.max(player_record.generation, acquire_response.generation)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -378,6 +396,7 @@ function player_sync.finish_download(player, finished_record)
|
||||
player_record.sync_in_progress = false
|
||||
start_sync(player_record, finished_record.components)
|
||||
player_record.generation = finished_record.generation
|
||||
player_record.epoch = finished_record.epoch
|
||||
end
|
||||
|
||||
-- Download inventory from controller
|
||||
@@ -478,12 +497,13 @@ function player_sync.confirm_upload(player_name, generation)
|
||||
player_record.dirty = false
|
||||
end
|
||||
|
||||
function player_sync.initiate_inventory_download(player, player_record, generation)
|
||||
function player_sync.initiate_inventory_download(player, player_record, generation, epoch)
|
||||
player.print("Initiating inventory download...")
|
||||
local record = {
|
||||
started = game.ticks_played,
|
||||
last_active = game.ticks_played,
|
||||
generation = generation,
|
||||
epoch = epoch,
|
||||
data = "",
|
||||
}
|
||||
local script_data = get_script_data()
|
||||
|
||||
Reference in new issue
Block a user