From 2f7419cf3b7fe831059969b828f9753a9abbe15e Mon Sep 17 00:00:00 2001 From: Hornwitser Date: Mon, 16 Nov 2020 19:18:17 +0100 Subject: [PATCH] Add max_concurrent_commands option Add option to set the the maximum amount of commands transmitted in parallel to the game. Increasing this value allows a greater rate of commands to be executed. --- CHANGELOG.md | 1 + docs/configuration.md | 33 ++++++++++++++++++++++++++++++ packages/lib/config/definitions.js | 6 ++++++ packages/lib/factorio/server.js | 29 +++++++++++++++++++++++++- packages/slave/slave.js | 4 ++++ 5 files changed, 72 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 049f024a..60f80bb9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,7 @@ Version 2.0.0 - Added synchronization of in-game adminlist, banlist and whitelist to Clusterio core. Previously this was handled by the playerManager plugin. - Added stripping of long paths in the Factorio server log. +- Added option to configure maximum number of commands to send in parallel. ### Changes diff --git a/docs/configuration.md b/docs/configuration.md index 2128d6b6..396d0cfe 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -342,6 +342,39 @@ banned status while the instance is running. Defaults to true +### factorio.max_concurrent_commands + +Maximum number of commands being transmitted into the game in parallel. +Since the rate at which long commands are streamed into the game is by +default limeted to 100 bytes/tick and scales down to 25 bytes/tick when +there's 20 players connected, long commands can hold up the command +interface and cause large latencies in getting data pushed into the +game. (The rates are controlled by the `segment_size` options in the +server settings.) To counteract this Clusterio sends up to this +configured value number of commands in parallel over the RCON interface. +This causes the game updates sent to clients to have this many command +streams in parallel contained in them, drastically increasing the +maxiumum size they can become. Since commands are only processed so +ofter the maximum rate of commands that can be sustained is roughly 30 +times this configured value per second, though note that large commands +will lower this value. + +The game updates sent by the server is split into roughly 500 byte +packets, and if any one of those parts are lost the entire update is +resent. This means a high number of concurrent commands can amplify +resends due to packet loss and degrade the player experience up to the +point of becomming completely unplayable. + +Prior to 0.18.7 the maximum practical size of game update sent where +arround 8 kB due to the UDP receive buffer on windows defaulting to this +value, larger updates would not get through the receive buffer and cause +the connection to drop. To stay below the 8 kB limit the maximum safe +value for this option would be around 7000 / (3 * game_speed * +maximum_segment_size), which is around 20 in normal circumstances. + +Defaults to 5 + + ### .enabled Whether to enable the given plugin on the instance. Note that plugins diff --git a/packages/lib/config/definitions.js b/packages/lib/config/definitions.js index cc273315..1cedf4fc 100644 --- a/packages/lib/config/definitions.js +++ b/packages/lib/config/definitions.js @@ -280,6 +280,12 @@ FactorioGroup.define({ type: "boolean", initial_value: true, }); +FactorioGroup.define({ + name: "max_concurrent_commands", + description: "Maximum number of RCON commands trasmitted in parallel", + type: "number", + initial_value: 5, +}); FactorioGroup.finalize(); /** diff --git a/packages/lib/factorio/server.js b/packages/lib/factorio/server.js index 49da075a..17423007 100644 --- a/packages/lib/factorio/server.js +++ b/packages/lib/factorio/server.js @@ -518,6 +518,9 @@ class FactorioServer extends events.EventEmitter { * @param {boolean} options.enableWhitelist - Turn on whitelisting. * @param {boolean} options.verboseLogging - Enable verbose logging. * @param {boolean} options.stripPaths - Strip paths in the console. + * @param {number} options.maxConcurrentCommands - + * Maximum number of RCON commands transmitted in parallel on the + * RCON connection. */ constructor(factorioDir, writeDir, options) { super(); @@ -540,6 +543,8 @@ class FactorioServer extends events.EventEmitter { this.enableWhitelist = options.enableWhitelist; /** Enable verbose logging */ this.verboseLogging = options.verboseLogging; + /** Maximum number of RCON commands transmitted in parallel on the RCON connection */ + this.maxConcurrentCommands = options.maxConcurrentCommands || 5; this._state = "new"; this._server = null; this._rconClient = null; @@ -689,7 +694,7 @@ class FactorioServer extends events.EventEmitter { port: this.rconPort, password: this.rconPassword, timeout: 200000, // 200s, should allow for commands up to 1250kB in length - maxPending: 5, + maxPending: this.maxConcurrentCommands, }; this._rconClient = new rconClient.Rcon(config); @@ -700,9 +705,31 @@ class FactorioServer extends events.EventEmitter { this._rconReady = false; this._startRcon().catch(() => { /* Ignore */ }); }); + // XXX: Workaround to suppress bogus event listener warning + if (this._rconClient.emitter instanceof events.EventEmitter) { + this._rconClient.emitter.setMaxListeners(this.maxConcurrentCommands + 5); + } await this._rconClient.connect(); } + get maxConcurrentCommands() { + return this._maxConcurrentCommands; + } + + set maxConcurrentCommands(value) { + this._maxConcurrentCommands = value; + this.setMaxListeners(value + 5); + if (this._rconClient) { + if (this._rconClient.sendQueue) { + this._rconClient.sendQueue.maxConcurrent = value; + } + // XXX: Workaround to suppress bogus event listener warning + if (this._rconClient.emitter instanceof events.EventEmitter) { + this._rconClient.emitter.setMaxListeners(value + 5); + } + } + } + /** * Initialize class instance * diff --git a/packages/slave/slave.js b/packages/slave/slave.js index 7644541c..fc20bd5d 100755 --- a/packages/slave/slave.js +++ b/packages/slave/slave.js @@ -124,6 +124,9 @@ class Instance extends libLink.Link{ } else if (group.name === "factorio" && field === "enable_whitelist") { this.updateFactorioWhitelist(group.get(field)).finally(hook); } else { + if (group.name === "factorio" && field === "max_concurrent_commands") { + this.server.maxConcurrentCommands = group.get(field); + } hook(); } }; @@ -137,6 +140,7 @@ class Instance extends libLink.Link{ enableWhitelist: this.config.get("factorio.enable_whitelist"), verboseLogging: this.config.get("factorio.verbose_logging"), stripPaths: this.config.get("factorio.strip_paths"), + maxConcurrentCommands: this.config.get("factorio.max_concurrent_commands"), }; this._status = "stopped";