mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-13 15:14:52 +09:00
Add sendOrderedRcon for plugins
Add BaseInstancePlugin method for sending RCON commands in order to simplify logic in plugins that depend on commands being delivered in order.
This commit is contained in:
1 parent
e8b515c733
commit
c40362fa7f
3 files changed
+113
-3
No files matched your search
+67
-1
@@ -60,6 +60,9 @@ class BaseInstancePlugin {
|
||||
* `error`, `warn`, `audit`, `info` and `verbose`.
|
||||
*/
|
||||
this.logger = instance.logger.child({ plugin: this.info.name });
|
||||
|
||||
this._pendingRconMessages = [];
|
||||
this._sendingRconMessages = false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -207,9 +210,72 @@ class BaseInstancePlugin {
|
||||
*/
|
||||
async onPlayerEvent(event) { }
|
||||
|
||||
async sendRcon(message, expectEmpty) {
|
||||
/**
|
||||
* Send RCON message to instance
|
||||
*
|
||||
* Send a message or command to the server which can potentially be
|
||||
* executed out of order in relation to other RCON commands.
|
||||
*
|
||||
* This should not be called before onStart or after onStop. A simple
|
||||
* way to achieve this is to check that the instance status is running
|
||||
* before sending commands. Note that the instance status is set to
|
||||
* running some time after `onStart` and some time before `onStop` is
|
||||
* invoked.
|
||||
*
|
||||
* @param {string} message - message to send to server over RCON.
|
||||
* @param {boolean=} [expectEmpty=false] -
|
||||
* if true throw if the response is not empty. Useful for detecting
|
||||
* errors that might have been sent in response.
|
||||
* @returns {Promise<string>} response from server.
|
||||
*/
|
||||
async sendRcon(message, expectEmpty=false) {
|
||||
return await this.instance.sendRcon(message, expectEmpty, this.info.name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send serially ordered RCON message to instance
|
||||
*
|
||||
* Send a message or command to the server which will not be executed
|
||||
* out of order in relation to other RCON commands sent with this
|
||||
* method. The ordering applies per plugin, and two plugins sending
|
||||
* commands with this method may execute out of order in relation to
|
||||
* each other.
|
||||
*
|
||||
* This should not be called before onStart or after onStop. A simple
|
||||
* way to achieve this is to check that the instance status is running
|
||||
* before sending commands. Note that the instance status is set to
|
||||
* running some time after `onStart` and some time before `onStop` is
|
||||
* invoked.
|
||||
*
|
||||
* @param {string} message - message to send to server over RCON.
|
||||
* @param {boolean=} [expectEmpty=false] -
|
||||
* if true throw if the response is not empty. Useful for detecting
|
||||
* errors that might have been sent in response.
|
||||
* @returns {Promise<string>} response from server.
|
||||
*/
|
||||
async sendOrderedRcon(message, expectEmpty=false) {
|
||||
let promise = new Promise((resolve, reject) => {
|
||||
this._pendingRconMessages.push({resolve, reject, message, expectEmpty});
|
||||
});
|
||||
if (!this._sendingRconMessages) {
|
||||
this._sendPendingRconMessages();
|
||||
}
|
||||
return await promise;
|
||||
}
|
||||
|
||||
async _sendPendingRconMessages() {
|
||||
this._sendingRconMessages = true;
|
||||
while (this._pendingRconMessages.length) {
|
||||
let task = this._pendingRconMessages.shift();
|
||||
try {
|
||||
let result = await this.sendRcon(task.message, task.expectEmpty);
|
||||
task.resolve(result);
|
||||
} catch (err) {
|
||||
task.reject(err);
|
||||
}
|
||||
}
|
||||
this._sendingRconMessages = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+31
-1
@@ -10,7 +10,7 @@ describe("lib/plugin", function() {
|
||||
describe("class BaseInstancePlugin", function() {
|
||||
let instancePlugin;
|
||||
it("should be constructible", async function() {
|
||||
instancePlugin = new libPlugin.BaseInstancePlugin({}, { logger: new mock.MockLogger() }, {});
|
||||
instancePlugin = new libPlugin.BaseInstancePlugin({}, new mock.MockInstance(), {});
|
||||
await instancePlugin.init();
|
||||
});
|
||||
it("should define defaults for hooks", async function() {
|
||||
@@ -22,6 +22,36 @@ describe("lib/plugin", function() {
|
||||
instancePlugin.onMasterConnectionEvent("connect");
|
||||
await instancePlugin.onPrepareMasterDisconnect();
|
||||
});
|
||||
describe("sendRcon", function() {
|
||||
it("should send commands out of order", async function() {
|
||||
instancePlugin.instance.server.rconCommandResults.set("a", { time: 100, response: "a" });
|
||||
instancePlugin.instance.server.rconCommandResults.set("b", { time: 50, response: "b" });
|
||||
|
||||
let a = instancePlugin.sendRcon("a");
|
||||
let b = instancePlugin.sendRcon("b");
|
||||
let result = await Promise.race([a, b]);
|
||||
assert.equal(result, "b");
|
||||
});
|
||||
it("should propagate errors", async function() {
|
||||
instancePlugin.instance.server.rconCommandResults.set("a", new Error("ref"));
|
||||
await assert.rejects(instancePlugin.sendRcon("a"), new Error("ref"));
|
||||
});
|
||||
});
|
||||
describe("sendOrderedRcon", function() {
|
||||
it("should send commands in order", async function() {
|
||||
instancePlugin.instance.server.rconCommandResults.set("a", { time: 100, response: "a" });
|
||||
instancePlugin.instance.server.rconCommandResults.set("b", { time: 50, response: "b" });
|
||||
|
||||
let a = instancePlugin.sendOrderedRcon("a");
|
||||
let b = instancePlugin.sendOrderedRcon("b");
|
||||
let result = await Promise.race([a, b]);
|
||||
assert.equal(result, "a");
|
||||
});
|
||||
it("should propagate errors", async function() {
|
||||
instancePlugin.instance.server.rconCommandResults.set("a", new Error("ref"));
|
||||
await assert.rejects(instancePlugin.sendOrderedRcon("a"), new Error("ref"));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("class BaseMasterPlugin", function() {
|
||||
|
||||
+15
-1
@@ -3,6 +3,7 @@ const events = require("events");
|
||||
const http = require("http");
|
||||
const express = require("express");
|
||||
|
||||
const libHelpers = require("@clusterio/lib/helpers");
|
||||
const libLink = require("@clusterio/lib/link");
|
||||
const libPlugin = require("@clusterio/lib/plugin");
|
||||
const libUsers = require("@clusterio/lib/users");
|
||||
@@ -105,7 +106,20 @@ class MockServer extends events.EventEmitter {
|
||||
|
||||
async sendRcon(command) {
|
||||
this.rconCommands.push(command);
|
||||
return this.rconCommandResults.get(command) || "";
|
||||
let result = this.rconCommandResults.get(command);
|
||||
if (!result) {
|
||||
result = { response: "" };
|
||||
}
|
||||
if (result instanceof Error || typeof result === "string") {
|
||||
result = { response: result };
|
||||
}
|
||||
if (result.time) {
|
||||
await libHelpers.wait(result.time);
|
||||
}
|
||||
if (result.response instanceof Error) {
|
||||
throw result.response;
|
||||
}
|
||||
return result.response || "";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user