mirror of
https://github.com/PHIDIAS0303/ExpCluster.git
synced 2026-11-13 16:14:52 +09:00
Save controller plugin data on the save hook (#513)
exp_groups and exp_roles only saved their datastores on shutdown, and exp_scenario saved the player store but not the feature store on the save hook. The controller runs the save hook on its autosave interval, so a crash or kill lost every change since the controller started. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
82abc2c332
commit
20ca539af3
5 files changed
+33
-5
No files matched your search
@@ -80,11 +80,12 @@ export class ControllerPlugin {
|
||||
controller.handle(messages.RoleMappingGetRequest, plugin.handleRoleMappingGetRequest.bind(plugin));
|
||||
controller.handle(messages.RoleMappingListRequest, plugin.handleRoleMappingListRequest.bind(plugin));
|
||||
|
||||
controller.hooks.shutdown.attach(plugin.name, plugin.onShutdown.bind(plugin));
|
||||
controller.hooks.save.attach(plugin.name, plugin.onSave.bind(plugin));
|
||||
controller.hooks.shutdown.attach(plugin.name, plugin.onSave.bind(plugin));
|
||||
return plugin;
|
||||
}
|
||||
|
||||
async onShutdown() {
|
||||
async onSave() {
|
||||
await Promise.all([
|
||||
this.groups.save(),
|
||||
this.manualAssignments.save(),
|
||||
|
||||
@@ -55,13 +55,14 @@ export class ControllerPlugin {
|
||||
controller.handle(messages.AssignmentUpdateRequest, plugin.handleAssignmentUpdateRequest.bind(plugin));
|
||||
|
||||
const hooks = controller.hooks;
|
||||
hooks.shutdown.attach(plugin.name, plugin.onShutdown.bind(plugin));
|
||||
hooks.save.attach(plugin.name, plugin.onSave.bind(plugin));
|
||||
hooks.shutdown.attach(plugin.name, plugin.onSave.bind(plugin));
|
||||
hooks.controllerConfigFieldChanged.attach(plugin.name, plugin.onControllerConfigFieldChanged.bind(plugin));
|
||||
hooks.playerEvent.attach(plugin.name, plugin.onPlayerEvent.bind(plugin));
|
||||
return plugin;
|
||||
}
|
||||
|
||||
async onShutdown() {
|
||||
async onSave() {
|
||||
await this.roleMeta.save();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import t from "tap";
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import * as lib from "@clusterio/lib";
|
||||
import { Controller } from "@clusterio/controller";
|
||||
import entrypoint, { ControllerPlugin } from "../dist/node/controller.js";
|
||||
@@ -54,7 +56,7 @@ t.test("entrypoint", async t2 => {
|
||||
const controllerConfig = new lib.ControllerConfig("controller", { "controller.database_directory": t2.testdir() });
|
||||
const controller = new Controller(logger, [], controllerConfig);
|
||||
await entrypoint({ controller, logger, plugin: { name: "exp_roles" }, metrics: undefined });
|
||||
for (const hook of ["shutdown", "controllerConfigFieldChanged", "playerEvent"]) {
|
||||
for (const hook of ["save", "shutdown", "controllerConfigFieldChanged", "playerEvent"]) {
|
||||
t2.ok([...controller.hooks[hook].attached].includes("exp_roles"), `attached to ${hook}`);
|
||||
}
|
||||
await controller.hooks.shutdown.invoke();
|
||||
@@ -208,6 +210,18 @@ t.test("class ControllerPlugin", t2 => {
|
||||
t3.strictSame(plugin.roleMeta.get(5).shortHand, "Mod", "the properties are stored");
|
||||
});
|
||||
|
||||
t2.test("role properties are written on the save hook", async t3 => {
|
||||
const { plugin, controller } = await startPlugin(t3, { roles: [role(1, "Player"), role(5, "Moderator")] });
|
||||
await plugin.handleRoleMetaUpdateRequest(new messages.RoleMetaUpdateRequest(
|
||||
new messages.RoleMetaRecord(5, 2, 0, "Mod"),
|
||||
));
|
||||
await controller.hooks.save.invoke();
|
||||
|
||||
const file = path.join(controller.config.get("controller.database_directory"), "exp_roles", "role_meta.json");
|
||||
const stored = JSON.parse(await fs.readFile(file, "utf8"));
|
||||
t3.ok(stored.some(record => record.id === 5 && record.short_hand === "Mod"), "the update is on disk");
|
||||
});
|
||||
|
||||
t2.test(".handleAssignmentListRequest() returns the current assignments", async t3 => {
|
||||
const { plugin, controller } = await startPlugin(t3, {
|
||||
roles: [role(1, "Player"), role(5, "Moderator")],
|
||||
|
||||
@@ -239,6 +239,7 @@ export default async function (context: ControllerPluginContext) {
|
||||
});
|
||||
|
||||
controller.hooks.save.attach(plugin.name, async () => {
|
||||
await featureStore.save();
|
||||
await playerStore.save();
|
||||
});
|
||||
|
||||
|
||||
@@ -216,6 +216,17 @@ t.test("controller plugin", t2 => {
|
||||
);
|
||||
});
|
||||
|
||||
t2.test("features are saved on the save hook", async t3 => {
|
||||
const databaseDirectory = t3.testdir();
|
||||
const { updateFeature, save } = await startPlugin(t3, { withPlugins: false, databaseDirectory });
|
||||
await updateFeature(new messages.FeatureUpdateRequest("afk_kick", false, { afk_minutes: 5 }));
|
||||
await save();
|
||||
|
||||
const { listFeatures } = await startPlugin(t3, { withPlugins: false, databaseDirectory });
|
||||
const record = await getFeature(listFeatures, "afk_kick");
|
||||
t3.strictSame([record.enabled, record.values], [false, { afk_minutes: 5 }], "the saved record is loaded");
|
||||
});
|
||||
|
||||
t2.test("stored features are kept across restarts", async t3 => {
|
||||
const databaseDirectory = t3.testdir();
|
||||
const { updateFeature, shutdown } = await startPlugin(t3, { withPlugins: false, databaseDirectory });
|
||||
|
||||
Reference in new issue
Block a user