space_engineers/Mixins/FailoverManager/readme.md

3.6 KiB

Failover Manager for SE scripts

This mixin allows multiple Programmable Blocks to coordinate with each other so only one copy of the script is running at a time; all but one block act as backup nodes that will be automatically activated (in priority order) in the event the active node goes offline.

Prerequisites

  • This is a mixin for MDK2. If you aren't using MDK2, see usage instructions here.
  • This requires my Console mixin as well. If you don't want to use that mixin's full functionality, you can use an EchoConsole (example below).
  • If you aren't using MyIni to configure your script's blocks, you can also just pass new MyIni() and avoid instantiating an entire property.

Caveats

  • Scripts that are manually invoked via buttons or action bar actions (such as docking sequences, airlocks, scripts that activate mechanical sequences, etc) are typically tied to a specific Programmable Block. You can get around this by using a Timer Block to execute the script on all nodes; the executions will get ignored on the standby nodes. However, since this just introduces a new single point of failure, its utility is somewhat case-specific. This may still be advantageous for an airlock script (with a separate timer block per airlock) but not as helpful for a docking script invoked from a cockpit. Whether or not that's better than just running a separate script per airlock is left as an exercise for the reader.

Usage

This code is designed to be as drop-in as possible once you meet the prerequisites above. Usage should look something like this:

    public partial class Program : MyGridProgram
        public MyIni Ini { get; } = new MyIni();
        public IConsole Console { get; private set; }

        private FailoverManager _failover;

        public Program() {
            Console = new MainConsole(this, "Script Name");
            _failover = new FailoverManager(this, Console, Ini);

            // alternate instantiation with Stub Console and fresh MyIni instance.
            // If you do it this way, you don't need the `Ini` or `Console` properties above.
            // _failover = new FailoverManager(this, new EchoConsole(this), new MyIni());

            // The script *must* start out with periodic updates, even if it is
            // a script that's only triggered conditionally (though in that case),
            // see caveats.
            Runtime.UpdateFrequency = UpdateFrequency.Update100;

            // ... script initialization happens here
        }

        public void Main(string argument, UpdateType updateSource) {
            // This basically instructs the script to "go back to sleep"
            // if it isn't the active node.
            if (_failover.ActiveCheck == FailoverState.Standby) return;

            // If you need more complicated "warm-up" logic when your script
            // becomes active, (for example, changing the update frequency) you can use this.
            // This statement will only be true once, when the script first takes over control.
            //
            // This is optional and depends on your use case.
            if (_failover.ActiveCheck.HasFlag(FailoverState.Failover)) {
                // Code that should run once when this script becomes the active node goes here
            }

            // ... your script logic goes here
        }

Scripts that are using failover also need configuration in their Programmable Blocks' Custom Data. That should look something like this:

[failover]
id=scriptName # This must match for all running copies of the script.
rank=0 # The lowest ranked copy of the script will be the primary node, and they will failover in ascending rank order