mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-11 23:04:52 +09:00
Implement config notification event
Add fieldChanged event to config objects that is triggered when the value of a field in a group belonging to it is changed.
This commit is contained in:
1 parent
fc3686b865
commit
67ca8dcc4d
4 files changed
+101
-21
No files matched your search
+39
-10
@@ -1,6 +1,9 @@
|
||||
// Configuration classes
|
||||
"use strict";
|
||||
|
||||
const events = require("events");
|
||||
const util = require("util");
|
||||
|
||||
const { basicType } = require("lib/helpers");
|
||||
|
||||
|
||||
@@ -55,10 +58,20 @@ function splitOn(separator, string) {
|
||||
* Represents a collection of ConfigGroup instances that can be
|
||||
* managed as one.
|
||||
*
|
||||
* This is an EventEmitter with the following events:
|
||||
* - fieldChanged -
|
||||
* invoked after a field has been updated in one of the config groups
|
||||
* belonging to this config. Takes three parameters: group, field,
|
||||
* prev.
|
||||
*
|
||||
* @extends events.EventEmitter
|
||||
*
|
||||
* @memberof module:lib/config
|
||||
*/
|
||||
class Config {
|
||||
class Config extends events.EventEmitter {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
if (!this.constructor._finalized) {
|
||||
throw new Error(`Cannot instantiate incomplete Config class ${this.constructor.name}`)
|
||||
}
|
||||
@@ -71,7 +84,7 @@ class Config {
|
||||
for (let [name, GroupClass] of this.constructor._groups) {
|
||||
if (!this._groups.has(name)) {
|
||||
let group = new GroupClass();
|
||||
await group.init();
|
||||
await group.init(this);
|
||||
this._groups.set(name, group);
|
||||
}
|
||||
}
|
||||
@@ -98,7 +111,7 @@ class Config {
|
||||
}
|
||||
|
||||
let group = new GroupClass();
|
||||
await group.load(serializedGroup);
|
||||
await group.load(this, serializedGroup);
|
||||
this._groups.set(GroupClass.groupName, group);
|
||||
}
|
||||
|
||||
@@ -123,12 +136,12 @@ class Config {
|
||||
* Updates all groups in the config with groups in the serialized
|
||||
* config passed.
|
||||
*/
|
||||
update(serializedConfig) {
|
||||
update(serializedConfig, notify) {
|
||||
this._validate(serializedConfig);
|
||||
for (let serializedGroup of serializedConfig.groups) {
|
||||
let group = this._groups.get(serializedGroup.name);
|
||||
if (group) {
|
||||
group.update(serializedGroup);
|
||||
group.update(serializedGroup, notify);
|
||||
} else {
|
||||
this._unknownGroups.set(serializedGroup.name, serializedGroup);
|
||||
}
|
||||
@@ -226,6 +239,11 @@ class Config {
|
||||
/**
|
||||
* Collection of related config entries
|
||||
*
|
||||
* Type checks and stores a collection of related config entries. Attempt
|
||||
* to set invalid values for fields will result in an error thrown while
|
||||
* loading a config with invalid values will result in those being replaced
|
||||
* by the innitial value for those fields.
|
||||
*
|
||||
* @memberof module:lib/config
|
||||
*/
|
||||
class ConfigGroup {
|
||||
@@ -242,6 +260,7 @@ class ConfigGroup {
|
||||
|
||||
this._fields = new Map();
|
||||
this._unknownFields = new Map();
|
||||
this._config = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -250,7 +269,9 @@ class ConfigGroup {
|
||||
* Computes and assigns the initial values for all fields of the
|
||||
* config group.
|
||||
*/
|
||||
async init() {
|
||||
async init(config) {
|
||||
this._config = config;
|
||||
|
||||
for (let [name, def] of this.constructor._definitions) {
|
||||
if (this._fields.has(name)) {
|
||||
continue;
|
||||
@@ -275,9 +296,9 @@ class ConfigGroup {
|
||||
* group and then initializes any possible missing fields from it to
|
||||
* their default values.
|
||||
*/
|
||||
async load(serializedGroup) {
|
||||
this.update(serializedGroup);
|
||||
await this.init();
|
||||
async load(config, serializedGroup) {
|
||||
this.update(serializedGroup, false);
|
||||
await this.init(config);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -345,7 +366,11 @@ class ConfigGroup {
|
||||
}
|
||||
}
|
||||
|
||||
let prev = this._fields.get(name);
|
||||
this._fields.set(name, value);
|
||||
if (this._config && !util.isDeepStrictEqual(value, prev)) {
|
||||
this._config.emit("fieldChanged", this, name, prev);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -354,7 +379,7 @@ class ConfigGroup {
|
||||
* Overwrites the values of all the fields in this config group that
|
||||
* has a valid value in the passed serialized group with that value.
|
||||
*/
|
||||
update(serializedGroup) {
|
||||
update(serializedGroup, notify) {
|
||||
if (basicType(serializedGroup) !== "object") {
|
||||
throw new Error(`Expected object, not ${basicType(serializedGroup)} for ConfigGroup`);
|
||||
}
|
||||
@@ -388,7 +413,11 @@ class ConfigGroup {
|
||||
continue;
|
||||
}
|
||||
|
||||
let prev = this._fields.get(name);
|
||||
this._fields.set(name, value);
|
||||
if (notify && this._config && !util.isDeepStrictEqual(value, prev)) {
|
||||
this._config.emit("fieldChanged", this, name, prev);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -42,6 +42,19 @@ class BaseInstancePlugin {
|
||||
*/
|
||||
async init() { }
|
||||
|
||||
/**
|
||||
* Called when the value of a config field changed.
|
||||
*
|
||||
* Invoked after the value of the config field given by `field` has
|
||||
* changed.
|
||||
*
|
||||
* @param {module:lib/config.ConfigGroup} group -
|
||||
* The group who's field got changed on.
|
||||
* @param {string} field - Name of the field that changed.
|
||||
* @param prev - The previous value of the field.
|
||||
*/
|
||||
async onInstanceConfigFieldChanged(group, field, prev) { }
|
||||
|
||||
/**
|
||||
* Called before collecting Prometheus metrics
|
||||
*
|
||||
|
||||
@@ -68,6 +68,11 @@ class Instance extends link.Link{
|
||||
this.plugins = new Map();
|
||||
this.config = instanceConfig;
|
||||
|
||||
this._configFieldChanged = (group, field, prev) => {
|
||||
plugin.invokeHook(this.plugins, "onInstanceConfigFieldChanged", group, field, prev);
|
||||
};
|
||||
this.config.on("fieldChanged", this._configFieldChanged);
|
||||
|
||||
let serverOptions = {
|
||||
version: this.config.get("factorio.version"),
|
||||
gamePort: this.config.get('factorio.game_port'),
|
||||
@@ -112,6 +117,8 @@ class Instance extends link.Link{
|
||||
this._running = false;
|
||||
link.messages.instanceStopped.send(this, { instance_id: this.config.get("instance.id") });
|
||||
|
||||
this.config.off("fieldChanged", this._configFieldChanged);
|
||||
|
||||
// Clear metrics this instance is exporting
|
||||
for (let collector of prometheus.defaultRegistry.collectors) {
|
||||
if (
|
||||
@@ -756,7 +763,7 @@ class Slave extends link.Link {
|
||||
let { instance_id, serialized_config } = message.data;
|
||||
let instanceInfo = this.instanceInfos.get(instance_id);
|
||||
if (instanceInfo) {
|
||||
instanceInfo.config.update(serialized_config);
|
||||
instanceInfo.config.update(serialized_config, true);
|
||||
console.log(`Clusterio | Updated config for ${instanceInfo.path}`);
|
||||
// TODO: Notify of update if instance is running
|
||||
|
||||
|
||||
+41
-10
@@ -177,7 +177,7 @@ describe("lib/config/classes", function() {
|
||||
describe(".load()", function() {
|
||||
it("should load a basic group", async function() {
|
||||
let testInstance = new TestGroup();
|
||||
await testInstance.load({ name: "test_group", fields: {
|
||||
await testInstance.load(null, { name: "test_group", fields: {
|
||||
enum: "c",
|
||||
test: "blah",
|
||||
func: 22,
|
||||
@@ -193,7 +193,7 @@ describe("lib/config/classes", function() {
|
||||
});
|
||||
it("should load defaults for missing serialized fields", async function() {
|
||||
let testInstance = new TestGroup();
|
||||
await testInstance.load({ name: "test_group", fields: { enum: "a" } });
|
||||
await testInstance.load(null, { name: "test_group", fields: { enum: "a" } });
|
||||
|
||||
assert.equal(testInstance.get("enum"), "a");
|
||||
assert.equal(testInstance.get("test"), null);
|
||||
@@ -213,7 +213,7 @@ describe("lib/config/classes", function() {
|
||||
}
|
||||
|
||||
let testInstance = new TestGroup();
|
||||
await testInstance.load({ name: "test_group", fields: testFields });
|
||||
await testInstance.load(null, { name: "test_group", fields: testFields });
|
||||
|
||||
assert.deepEqual(testInstance.serialize().fields, testFields);
|
||||
});
|
||||
@@ -223,7 +223,7 @@ describe("lib/config/classes", function() {
|
||||
it("should skip updating invalid values", async function() {
|
||||
let testInstance = new TestGroup();
|
||||
await testInstance.init();
|
||||
testInstance.update({ name: "test_group", fields: { enum: "wrong", test: 3, func: null }});
|
||||
testInstance.update({ name: "test_group", fields: { enum: "wrong", test: 3, func: null }}, false);
|
||||
|
||||
assert.equal(testInstance.get("enum"), "b");
|
||||
assert.equal(testInstance.get("test"), null);
|
||||
@@ -237,19 +237,19 @@ describe("lib/config/classes", function() {
|
||||
new Error("Expected object, not undefined for ConfigGroup")
|
||||
);
|
||||
assert.throws(
|
||||
() => testInstance.update({}),
|
||||
() => testInstance.update({}, false),
|
||||
new Error("Expected group name test_group, not undefined")
|
||||
);
|
||||
assert.throws(
|
||||
() => testInstance.update({ name: "test_group" }),
|
||||
() => testInstance.update({ name: "test_group" }, false),
|
||||
new Error("Expected fields to be an object, not undefined")
|
||||
);
|
||||
assert.throws(
|
||||
() => testInstance.update({ name: "test_group", fields: []}),
|
||||
() => testInstance.update({ name: "test_group", fields: []}, false),
|
||||
new Error("Expected fields to be an object, not array")
|
||||
);
|
||||
assert.throws(
|
||||
() => testInstance.update({ name: "test_group", fields: null}),
|
||||
() => testInstance.update({ name: "test_group", fields: null}, false),
|
||||
new Error("Expected fields to be an object, not null")
|
||||
);
|
||||
});
|
||||
@@ -261,7 +261,7 @@ describe("lib/config/classes", function() {
|
||||
testInstance = new TestGroup();
|
||||
testInstance.update({ name: "test_group", fields: {
|
||||
enum: "a", test: "blah", func: 27,
|
||||
}});
|
||||
}}, false);
|
||||
});
|
||||
|
||||
it("should throw if field does not exist", function() {
|
||||
@@ -500,7 +500,7 @@ describe("lib/config/classes", function() {
|
||||
name: "beta",
|
||||
fields: { bar: 30 },
|
||||
}
|
||||
]});
|
||||
]}, false);
|
||||
|
||||
assert.deepEqual(testInstance.serialize(), {
|
||||
groups: [
|
||||
@@ -563,5 +563,36 @@ describe("lib/config/classes", function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe("fieldChanged event", function() {
|
||||
it("should be called when setting a field", async function() {
|
||||
let testInstance = new TestConfig();
|
||||
await testInstance.init();
|
||||
|
||||
let called = false;
|
||||
testInstance.once("fieldChanged", (group, field, prev) => {
|
||||
if (group.constructor.groupName === "alpha" && field === "foo") {
|
||||
called = true;
|
||||
}
|
||||
});
|
||||
|
||||
testInstance.set("alpha.foo", "new value");
|
||||
assert(called, "fieldChanged was not called");
|
||||
});
|
||||
|
||||
it("should be called when updating a config", async function() {
|
||||
let testInstance = new TestConfig();
|
||||
await testInstance.init();
|
||||
|
||||
let called = false;
|
||||
testInstance.once("fieldChanged", (group, field, prev) => {
|
||||
if (group.constructor.groupName === "alpha" && field === "foo") {
|
||||
called = true;
|
||||
}
|
||||
});
|
||||
|
||||
testInstance.update({ groups: [{ name: "alpha", fields: { foo: "new value" }}]}, true);
|
||||
assert(called, "fieldChanged was not called");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user