mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-14 07:24:52 +09:00
Convert inventory_sync to entrypoint functions
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b2705b8788
commit
1ca0c658b9
4 files changed
+215
-225
No files matched your search
+110
-109
@@ -1,7 +1,7 @@
|
||||
import util from "util";
|
||||
import zlib from "zlib";
|
||||
import * as lib from "@clusterio/lib";
|
||||
import { BaseInstancePlugin } from "@clusterio/host";
|
||||
import type { InstancePluginContext } from "@clusterio/host";
|
||||
import {
|
||||
AcquireRequest, AcquireResponse, ReleaseRequest, UploadRequest, DownloadRequest, DownloadResponse, IpcPlayerData,
|
||||
} from "./messages.js";
|
||||
@@ -76,67 +76,36 @@ export function recipeNotificationDelta(stored: string[], current: string[]): Re
|
||||
return delta;
|
||||
}
|
||||
|
||||
export class InstancePlugin extends BaseInstancePlugin {
|
||||
playersToRelease!: Set<string>;
|
||||
disconnecting!: boolean;
|
||||
|
||||
async init() {
|
||||
this.playersToRelease = new Set();
|
||||
this.disconnecting = false;
|
||||
|
||||
// Handle IPC from scenario script
|
||||
this.instance.server.on(
|
||||
"ipc-inventory_sync_acquire",
|
||||
(request: IpcPlayerName) => this.handleAcquire(request).catch(
|
||||
err => this.logger.error(`Error handling ipc-inventory_sync_acquire:\n${err.stack}`)
|
||||
),
|
||||
);
|
||||
this.instance.server.on(
|
||||
"ipc-inventory_sync_release",
|
||||
(request: IpcPlayerName) => this.handleRelease(request).catch(
|
||||
err => this.logger.error(`Error handling ipc-inventory_sync_release:\n${err.stack}`)
|
||||
),
|
||||
);
|
||||
this.instance.server.on(
|
||||
"ipc-inventory_sync_upload",
|
||||
(player_data: IpcPlayerData) => this.handleUpload(player_data).catch(
|
||||
err => this.logger.error(`Error handling ipc-inventory_sync_upload:\n${err.stack}`)
|
||||
),
|
||||
);
|
||||
this.instance.server.on(
|
||||
"ipc-inventory_sync_download",
|
||||
(request: IpcDownloadRequest) => this.handleDownload(request).catch(
|
||||
err => this.logger.error(`Error handling ipc-inventory_sync_download:\n${err.stack}`)
|
||||
),
|
||||
);
|
||||
/**
|
||||
* Replace the stored recipe notifications with the difference from the
|
||||
* current state on the instance to reduce the amount of data sent to Lua.
|
||||
*/
|
||||
export async function applyRecipeNotificationDelta(
|
||||
playerData: IpcPlayerData,
|
||||
current: string | undefined,
|
||||
logger: lib.Logger,
|
||||
) {
|
||||
if (!playerData.recipe_notifications) {
|
||||
return;
|
||||
}
|
||||
|
||||
async onPrepareControllerDisconnect() {
|
||||
this.disconnecting = true;
|
||||
try {
|
||||
const delta = recipeNotificationDelta(
|
||||
JSON.parse(await decodeLuaString(playerData.recipe_notifications)),
|
||||
current ? JSON.parse(await decodeLuaString(current)) : [],
|
||||
);
|
||||
playerData.recipe_notifications = await encodeLuaString(JSON.stringify(delta));
|
||||
} catch (err: any) {
|
||||
logger.warn(`Dropping invalid recipe notifications for ${playerData.name}:\n${err.stack}`);
|
||||
delete playerData.recipe_notifications;
|
||||
}
|
||||
}
|
||||
|
||||
export default async function(context: InstancePluginContext) {
|
||||
const { instance, host, logger, plugin } = context;
|
||||
const playersToRelease = new Set<string>();
|
||||
let disconnecting = false;
|
||||
|
||||
onControllerConnectionEvent(event: "connect" | "drop" | "resume" | "close") {
|
||||
if (event === "connect") {
|
||||
this.disconnecting = false;
|
||||
(async () => {
|
||||
for (let player_name of this.playersToRelease) {
|
||||
if (!this.host.connector.connected || this.disconnecting) {
|
||||
return;
|
||||
}
|
||||
this.playersToRelease.delete(player_name);
|
||||
await this.instance.sendTo(
|
||||
"controller",
|
||||
new ReleaseRequest(this.instance.id, player_name)
|
||||
);
|
||||
}
|
||||
})().catch(
|
||||
err => this.logger.error(`Unpexpected error releasing queued up players:\n${err.stack}`)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async handleAcquire(request: IpcPlayerName) {
|
||||
async function handleAcquire(request: IpcPlayerName) {
|
||||
let response: IpcAcquireResponse = {
|
||||
player_name: request.player_name,
|
||||
status: "error",
|
||||
@@ -145,11 +114,11 @@ export class InstancePlugin extends BaseInstancePlugin {
|
||||
generation: undefined,
|
||||
};
|
||||
|
||||
if (this.host.connector.connected && !this.disconnecting) {
|
||||
if (host.connector.connected && !disconnecting) {
|
||||
try {
|
||||
let acquireResponse: AcquireResponse = await this.instance.sendTo(
|
||||
let acquireResponse: AcquireResponse = await instance.sendTo(
|
||||
"controller",
|
||||
new AcquireRequest(this.instance.id, request.player_name),
|
||||
new AcquireRequest(instance.id, request.player_name),
|
||||
);
|
||||
response = {
|
||||
player_name: request.player_name,
|
||||
@@ -160,105 +129,137 @@ export class InstancePlugin extends BaseInstancePlugin {
|
||||
};
|
||||
} catch (err: any) {
|
||||
if (!(err instanceof lib.SessionLost)) {
|
||||
this.logger.error(`Unexpected error sending aquire request:\n${err.stack}`);
|
||||
logger.error(`Unexpected error sending aquire request:\n${err.stack}`);
|
||||
response.message = err.message;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let json = lib.escapeString(JSON.stringify(response));
|
||||
await this.sendRcon(`/sc inventory_sync.acquire_response("${json}")`, true);
|
||||
await instance.sendRcon(`/sc inventory_sync.acquire_response("${json}")`, true, plugin.name);
|
||||
}
|
||||
|
||||
async handleRelease(request: IpcPlayerName) {
|
||||
if (!this.host.connector.connected) {
|
||||
this.playersToRelease.add(request.player_name);
|
||||
async function handleRelease(request: IpcPlayerName) {
|
||||
if (!host.connector.connected) {
|
||||
playersToRelease.add(request.player_name);
|
||||
}
|
||||
|
||||
try {
|
||||
await this.instance.sendTo(
|
||||
await instance.sendTo(
|
||||
"controller",
|
||||
new ReleaseRequest(this.instance.id, request.player_name)
|
||||
new ReleaseRequest(instance.id, request.player_name)
|
||||
);
|
||||
} catch (err: any) {
|
||||
if (err instanceof lib.SessionLost) {
|
||||
this.playersToRelease.add(request.player_name);
|
||||
playersToRelease.add(request.player_name);
|
||||
} else {
|
||||
this.logger.error(`Unexpected error releasing player ${request.player_name}:\n${err.stack}`);
|
||||
logger.error(`Unexpected error releasing player ${request.player_name}:\n${err.stack}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async handleUpload(player_data: IpcPlayerData) {
|
||||
if (!this.host.connector.connected || this.disconnecting) {
|
||||
async function handleUpload(player_data: IpcPlayerData) {
|
||||
if (!host.connector.connected || disconnecting) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.verbose(`Uploading ${player_data.name} (${JSON.stringify(player_data).length / 1000}kB)`);
|
||||
logger.verbose(`Uploading ${player_data.name} (${JSON.stringify(player_data).length / 1000}kB)`);
|
||||
try {
|
||||
await this.instance.sendTo(
|
||||
await instance.sendTo(
|
||||
"controller",
|
||||
new UploadRequest(this.instance.id, player_data.name, player_data),
|
||||
new UploadRequest(instance.id, player_data.name, player_data),
|
||||
);
|
||||
|
||||
} catch (err: any) {
|
||||
if (!(err instanceof lib.SessionLost)) {
|
||||
this.logger.error(`Unexpected error uploading inventory for ${player_data.name}:\n${err.stack}`);
|
||||
logger.error(`Unexpected error uploading inventory for ${player_data.name}:\n${err.stack}`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
await this.sendRcon(
|
||||
`/sc inventory_sync.confirm_upload("${player_data.name}", ${player_data.generation})`, true
|
||||
await instance.sendRcon(
|
||||
`/sc inventory_sync.confirm_upload("${player_data.name}", ${player_data.generation})`, true, plugin.name
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the stored recipe notifications with the difference from the
|
||||
* current state on the instance to reduce the amount of data sent to Lua.
|
||||
*/
|
||||
async applyRecipeNotificationDelta(playerData: IpcPlayerData, current?: string) {
|
||||
if (!playerData.recipe_notifications) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const delta = recipeNotificationDelta(
|
||||
JSON.parse(await decodeLuaString(playerData.recipe_notifications)),
|
||||
current ? JSON.parse(await decodeLuaString(current)) : [],
|
||||
);
|
||||
playerData.recipe_notifications = await encodeLuaString(JSON.stringify(delta));
|
||||
} catch (err: any) {
|
||||
this.logger.warn(`Dropping invalid recipe notifications for ${playerData.name}:\n${err.stack}`);
|
||||
delete playerData.recipe_notifications;
|
||||
}
|
||||
}
|
||||
|
||||
async handleDownload(request: IpcDownloadRequest) {
|
||||
async function handleDownload(request: IpcDownloadRequest) {
|
||||
const playerName = request.player_name;
|
||||
this.logger.verbose(`Downloading ${playerName}`);
|
||||
logger.verbose(`Downloading ${playerName}`);
|
||||
|
||||
let response: DownloadResponse = await this.instance.sendTo(
|
||||
let response: DownloadResponse = await instance.sendTo(
|
||||
"controller",
|
||||
new DownloadRequest(this.instance.id, playerName)
|
||||
new DownloadRequest(instance.id, playerName)
|
||||
);
|
||||
|
||||
if (!response.playerData) {
|
||||
await this.sendRcon(`/sc inventory_sync.download_inventory('${playerName}',nil,0,0)`, true);
|
||||
await instance.sendRcon(
|
||||
`/sc inventory_sync.download_inventory('${playerName}',nil,0,0)`, true, plugin.name
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
await this.applyRecipeNotificationDelta(response.playerData, request.recipe_notifications);
|
||||
await applyRecipeNotificationDelta(response.playerData, request.recipe_notifications, logger);
|
||||
|
||||
const chunkSize = this.instance.config.get("inventory_sync.rcon_chunk_size");
|
||||
const chunkSize = instance.config.get("inventory_sync.rcon_chunk_size");
|
||||
const chunks = chunkify(chunkSize, JSON.stringify(response.playerData));
|
||||
this.logger.verbose(`Sending inventory for ${playerName} in ${chunks.length} chunks`);
|
||||
logger.verbose(`Sending inventory for ${playerName} in ${chunks.length} chunks`);
|
||||
for (let i = 0; i < chunks.length; i++) {
|
||||
// this.logger.verbose(`Sending chunk ${i+1} of ${chunks.length}`)
|
||||
const chunk = lib.escapeString(chunks[i]);
|
||||
await this.sendRcon(
|
||||
await instance.sendRcon(
|
||||
`/sc inventory_sync.download_inventory('${playerName}','${chunk}',${i + 1},${chunks.length})`,
|
||||
true
|
||||
true,
|
||||
plugin.name,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle IPC from scenario script
|
||||
instance.server.on(
|
||||
"ipc-inventory_sync_acquire",
|
||||
(request: IpcPlayerName) => handleAcquire(request).catch(
|
||||
err => logger.error(`Error handling ipc-inventory_sync_acquire:\n${err.stack}`)
|
||||
),
|
||||
);
|
||||
instance.server.on(
|
||||
"ipc-inventory_sync_release",
|
||||
(request: IpcPlayerName) => handleRelease(request).catch(
|
||||
err => logger.error(`Error handling ipc-inventory_sync_release:\n${err.stack}`)
|
||||
),
|
||||
);
|
||||
instance.server.on(
|
||||
"ipc-inventory_sync_upload",
|
||||
(player_data: IpcPlayerData) => handleUpload(player_data).catch(
|
||||
err => logger.error(`Error handling ipc-inventory_sync_upload:\n${err.stack}`)
|
||||
),
|
||||
);
|
||||
instance.server.on(
|
||||
"ipc-inventory_sync_download",
|
||||
(request: IpcDownloadRequest) => handleDownload(request).catch(
|
||||
err => logger.error(`Error handling ipc-inventory_sync_download:\n${err.stack}`)
|
||||
),
|
||||
);
|
||||
|
||||
instance.hooks.prepareControllerDisconnect.attach(plugin.name, async () => {
|
||||
disconnecting = true;
|
||||
});
|
||||
|
||||
instance.hooks.controllerConnectionEvent.attach(plugin.name, async (event) => {
|
||||
if (event === "connect") {
|
||||
disconnecting = false;
|
||||
(async () => {
|
||||
for (let player_name of playersToRelease) {
|
||||
if (!host.connector.connected || disconnecting) {
|
||||
return;
|
||||
}
|
||||
playersToRelease.delete(player_name);
|
||||
await instance.sendTo(
|
||||
"controller",
|
||||
new ReleaseRequest(instance.id, player_name)
|
||||
);
|
||||
}
|
||||
})().catch(
|
||||
err => logger.error(`Unpexpected error releasing queued up players:\n${err.stack}`)
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user