From a986b0ac9c368089258f1e50594be6c5f4722789 Mon Sep 17 00:00:00 2001 From: Anna Wiggins Date: Sat, 1 Mar 2025 04:32:34 +0000 Subject: [PATCH] Fix unix-style line endings. Oops. --- Mixins/FailoverManager/FailoverManager.cs | 200 +++++++++--------- .../FailoverManager/FailoverManager.projitems | 26 +-- Mixins/FailoverManager/FailoverManager.shproj | 40 ++-- Mixins/FailoverManager/readme.md | 130 ++++++------ 4 files changed, 198 insertions(+), 198 deletions(-) diff --git a/Mixins/FailoverManager/FailoverManager.cs b/Mixins/FailoverManager/FailoverManager.cs index 43e5ebc..754b437 100644 --- a/Mixins/FailoverManager/FailoverManager.cs +++ b/Mixins/FailoverManager/FailoverManager.cs @@ -1,101 +1,101 @@ -using Sandbox.ModAPI.Ingame; -using System.Collections.Generic; - -namespace IngameScript -{ - partial class Program - { - [Flags] - public enum FailoverState - { - Standby = 0x1, - Failover = 0x2, - Active = 0x4, - } - - public class FailoverManager - { - private Program _program; - private IConsole _console; - - private string _id; - private int _rank; - private bool _active = false; - private SortedDictionary _nodes = new SortedDictionary(); - - public FailoverManager(Program program, IConsole console, MyIni ini) - { - _program = program; - _console = new PrefixedConsole(console, "FailoverManager"); - _ini = ini; - - // Read our config - bool configExists = _ini.TryParse(_program.Me.CustomData); - if (!configExists) - { - _console.Print("No failover config, assuming we are single instance."); - _active = true; - return; - } - - _id = _ini.Get("failover", "id").ToString(); - _rank = _ini.Get("failover", "rank").ToInt32(-1); - - if (_id == "" || _rank == -1) - { - _console.Print("Failover config invalid. Assuming we are single instance."); - _active = true; - return; - } - - List allPBs = new List(); - _program.GridTerminalSystem.GetBlocksOfType(allPBs, blockFilter); - - foreach (IMyProgrammableBlock node in allPBs) - { - _ini.TryParse(node.CustomData); - - string foreignId = _ini.Get("failover", "id"); - if (foreignId != _id) continue; - - int foreignRank = _ini.Get("failover", "rank"); - if (foreignRank == -1) continue; - - _nodes[foreignRank] = node; - } - } - - // returns true if we should become the active node - public FailoverState ActiveCheck() - { - // Once we're the active node, we will stay active for the life of the script. - // TODO: test the assumption that the script restarts from scratch on disable/enable... - // if not we may need additional logic. - if (_active) return FailoverState.Active; - - foreach (KeyValuePair kvp in _nodes) - { - // If we have a higher priority than the nodes we've checked so far, - // we must be the active node. - if (_rank < kvp.Key) - { - _active = true; - return FailoverState.Failover | FailoverState.Active; - } - - // We've found an active node with higher priority than us. - if (!kvp.Value.Closed && kvp.Value.Enabled) return FailoverState.Standby; - } - - // We're the last node standing. Let's hope we don't go down. - _active = true; - return FailoverState.Failover | FailoverState.Active; - } - - private bool blockFilter(IMyProgrammableBlock block) - { - return block.IsSameConstructAs(_program.Me) && MyIni.HasSection(block.CustomData, "failover"); - } - } - } +using Sandbox.ModAPI.Ingame; +using System.Collections.Generic; + +namespace IngameScript +{ + partial class Program + { + [Flags] + public enum FailoverState + { + Standby = 0x1, + Failover = 0x2, + Active = 0x4, + } + + public class FailoverManager + { + private Program _program; + private IConsole _console; + + private string _id; + private int _rank; + private bool _active = false; + private SortedDictionary _nodes = new SortedDictionary(); + + public FailoverManager(Program program, IConsole console, MyIni ini) + { + _program = program; + _console = new PrefixedConsole(console, "FailoverManager"); + _ini = ini; + + // Read our config + bool configExists = _ini.TryParse(_program.Me.CustomData); + if (!configExists) + { + _console.Print("No failover config, assuming we are single instance."); + _active = true; + return; + } + + _id = _ini.Get("failover", "id").ToString(); + _rank = _ini.Get("failover", "rank").ToInt32(-1); + + if (_id == "" || _rank == -1) + { + _console.Print("Failover config invalid. Assuming we are single instance."); + _active = true; + return; + } + + List allPBs = new List(); + _program.GridTerminalSystem.GetBlocksOfType(allPBs, blockFilter); + + foreach (IMyProgrammableBlock node in allPBs) + { + _ini.TryParse(node.CustomData); + + string foreignId = _ini.Get("failover", "id"); + if (foreignId != _id) continue; + + int foreignRank = _ini.Get("failover", "rank"); + if (foreignRank == -1) continue; + + _nodes[foreignRank] = node; + } + } + + // returns true if we should become the active node + public FailoverState ActiveCheck() + { + // Once we're the active node, we will stay active for the life of the script. + // TODO: test the assumption that the script restarts from scratch on disable/enable... + // if not we may need additional logic. + if (_active) return FailoverState.Active; + + foreach (KeyValuePair kvp in _nodes) + { + // If we have a higher priority than the nodes we've checked so far, + // we must be the active node. + if (_rank < kvp.Key) + { + _active = true; + return FailoverState.Failover | FailoverState.Active; + } + + // We've found an active node with higher priority than us. + if (!kvp.Value.Closed && kvp.Value.Enabled) return FailoverState.Standby; + } + + // We're the last node standing. Let's hope we don't go down. + _active = true; + return FailoverState.Failover | FailoverState.Active; + } + + private bool blockFilter(IMyProgrammableBlock block) + { + return block.IsSameConstructAs(_program.Me) && MyIni.HasSection(block.CustomData, "failover"); + } + } + } } \ No newline at end of file diff --git a/Mixins/FailoverManager/FailoverManager.projitems b/Mixins/FailoverManager/FailoverManager.projitems index 5f8459d..23b9081 100644 --- a/Mixins/FailoverManager/FailoverManager.projitems +++ b/Mixins/FailoverManager/FailoverManager.projitems @@ -1,14 +1,14 @@ - - - - $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - true - 8a3cdcc5-4b55-4d87-a415-698a0e1ff06f - - - - - - - + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + 8a3cdcc5-4b55-4d87-a415-698a0e1ff06f + + + + + + + \ No newline at end of file diff --git a/Mixins/FailoverManager/FailoverManager.shproj b/Mixins/FailoverManager/FailoverManager.shproj index ac3455a..189b9b3 100644 --- a/Mixins/FailoverManager/FailoverManager.shproj +++ b/Mixins/FailoverManager/FailoverManager.shproj @@ -1,20 +1,20 @@ - - - - 8a3cdcc5-4b55-4d87-a415-698a0e1ff06f - 14.0 - - - - - - - bin\Debug\ - - - bin\Release\ - - - - - + + + + 8a3cdcc5-4b55-4d87-a415-698a0e1ff06f + 14.0 + + + + + + + bin\Debug\ + + + bin\Release\ + + + + + diff --git a/Mixins/FailoverManager/readme.md b/Mixins/FailoverManager/readme.md index d332124..f9aa791 100644 --- a/Mixins/FailoverManager/readme.md +++ b/Mixins/FailoverManager/readme.md @@ -1,66 +1,66 @@ -# 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](TODO). If you aren't using MDK2, see usage instructions [here](TODO). -* 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 +# 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](TODO). If you aren't using MDK2, see usage instructions [here](TODO). +* 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 ``` \ No newline at end of file