mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-11 15:04:52 +09:00
Restore recipe notifications a prefetched copy would leave cleared
A prefetched copy is sent without the save's list of cleared recipes, so its delta had only add, and a recipe cleared on the destination but not in the stored list stayed cleared where a download would give it its notification back. Without a current list the instance now sends the whole stored list marked full, and the save gives the recipes it has cleared that aren't in it their notification back, as a download's remove does. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
fe416da26c
commit
ca7caedfbe
3 files changed
+40
-10
No files matched your search
@@ -61,6 +61,8 @@ async function encodeLuaString(text: string): Promise<string> {
|
||||
export type RecipeNotificationDelta = {
|
||||
add?: string[],
|
||||
remove?: string[],
|
||||
/** add is the whole stored list, sent without the instance's: the save works out what to remove */
|
||||
full?: true,
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -87,6 +89,7 @@ export function recipeNotificationDelta(stored: string[], current: string[]): Re
|
||||
/**
|
||||
* Replace the stored recipe notifications with the difference from the
|
||||
* current state on the instance to reduce the amount of data sent to Lua.
|
||||
* Without a current state the whole stored list is sent, marked full.
|
||||
*/
|
||||
export async function applyRecipeNotificationDelta(
|
||||
playerData: IpcPlayerData,
|
||||
@@ -97,10 +100,10 @@ export async function applyRecipeNotificationDelta(
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const delta = recipeNotificationDelta(
|
||||
JSON.parse(await decodeLuaString(playerData.recipe_notifications)),
|
||||
current ? JSON.parse(await decodeLuaString(current)) : [],
|
||||
);
|
||||
const stored = JSON.parse(await decodeLuaString(playerData.recipe_notifications));
|
||||
const delta: RecipeNotificationDelta = current
|
||||
? recipeNotificationDelta(stored, JSON.parse(await decodeLuaString(current)))
|
||||
: { ...recipeNotificationDelta(stored, []), full: true };
|
||||
playerData.recipe_notifications = await encodeLuaString(JSON.stringify(delta));
|
||||
} catch (err: any) {
|
||||
logger.warn(`Dropping invalid recipe notifications for ${playerData.name}:\n${err.stack}`);
|
||||
@@ -282,7 +285,7 @@ export default async function(context: InstancePluginContext) {
|
||||
const { playerData } = event;
|
||||
const { name, generation } = playerData;
|
||||
logger.verbose(`Prefetching ${name}`);
|
||||
// This side doesn't know the save's cleared recipe list, so the whole stored list goes as the delta's add
|
||||
// This side doesn't know the save's cleared recipe list, so the whole stored list goes as a full delta
|
||||
await sendPlayerData(playerData, undefined, (chunk, number, total, extra) => (
|
||||
`/sc player_sync.prefetch_inventory('${name}',${generation},'${chunk}',${number},${total}${extra})`
|
||||
), true);
|
||||
|
||||
@@ -895,6 +895,7 @@ end
|
||||
--- @class CraftingNotificationDelta
|
||||
--- @field add string[]? Recipes cleared elsewhere that still have a notification here
|
||||
--- @field remove string[]? Recipes with a notification elsewhere that are cleared here
|
||||
--- @field full boolean? add is the whole stored list, sent without this save's (a prefetched copy)
|
||||
|
||||
--- @param player LuaPlayer
|
||||
--- @param serialized string Encoded delta against the state sent with the download request
|
||||
@@ -929,6 +930,23 @@ function serialize.deserialize_crafting_notifications(player, serialized)
|
||||
end
|
||||
end
|
||||
|
||||
-- A full delta has no remove: recipes cleared here that aren't in add get their notification back, as remove does
|
||||
if delta.full then
|
||||
local stored = {}
|
||||
for _, recipe_name in pairs(delta.add or {}) do
|
||||
stored[recipe_name] = true
|
||||
end
|
||||
local notified = {}
|
||||
for _, recipe in pairs(player.get_recipe_notifications()) do
|
||||
notified[recipe.name] = true
|
||||
end
|
||||
for recipe_name in pairs(enabled) do
|
||||
if not stored[recipe_name] and not notified[recipe_name] then
|
||||
player.add_recipe_notification(recipe_name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return next(failed) and failed or nil
|
||||
end
|
||||
|
||||
|
||||
@@ -54,10 +54,15 @@ describe("player_sync", function() {
|
||||
await apply(playerData, await encode(["a"]));
|
||||
assert.deepEqual(await decode(playerData.recipe_notifications), {});
|
||||
});
|
||||
it("should add everything without a current list", async function() {
|
||||
it("should send the whole list marked full without a current list", async function() {
|
||||
const playerData = { name: "test", recipe_notifications: await encode(["a"]) };
|
||||
await apply(playerData, undefined);
|
||||
assert.deepEqual(await decode(playerData.recipe_notifications), { add: ["a"] });
|
||||
assert.deepEqual(await decode(playerData.recipe_notifications), { add: ["a"], full: true });
|
||||
});
|
||||
it("should mark an empty list full without a current list", async function() {
|
||||
const playerData = { name: "test", recipe_notifications: await encode([]) };
|
||||
await apply(playerData, undefined);
|
||||
assert.deepEqual(await decode(playerData.recipe_notifications), { full: true });
|
||||
});
|
||||
it("should do nothing without stored notifications", async function() {
|
||||
const playerData = { name: "test" };
|
||||
@@ -118,10 +123,12 @@ describe("player_sync", function() {
|
||||
["/sc player_sync.download_inventory('test',nil,0,0)"]
|
||||
);
|
||||
});
|
||||
it("should send everything as a delta when no snapshot was given", async function() {
|
||||
it("should send everything as a full delta when no snapshot was given", async function() {
|
||||
respondWith({ generation: 1, name: "test", recipe_notifications: await encode(["a"]) });
|
||||
await download({ player_name: "test" });
|
||||
const expected = { generation: 1, name: "test", recipe_notifications: await encode({ add: ["a"] }) };
|
||||
const expected = {
|
||||
generation: 1, name: "test", recipe_notifications: await encode({ add: ["a"], full: true }),
|
||||
};
|
||||
assert.deepEqual(instance.server.rconCommands, [downloadCommand(expected)]);
|
||||
});
|
||||
it("should send a delta when a snapshot was given", async function() {
|
||||
@@ -275,7 +282,9 @@ describe("player_sync", function() {
|
||||
|
||||
it("should send prefetch_inventory chunks with the generation and the full recipe list", async function() {
|
||||
await prefetch({ generation: 3, name: "test", recipe_notifications: await encode(["a"]) });
|
||||
const expected = { generation: 3, name: "test", recipe_notifications: await encode({ add: ["a"] }) };
|
||||
const expected = {
|
||||
generation: 3, name: "test", recipe_notifications: await encode({ add: ["a"], full: true }),
|
||||
};
|
||||
assert.ok(instance.server.rconCommands.length > 1, "more than one chunk");
|
||||
assert.deepEqual(instance.server.rconCommands, prefetchCommands(expected));
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user