Fix unix-style line endings. Oops.

This commit is contained in:
Anna Rose 2025-03-01 04:32:34 +00:00
parent 903cb95f1e
commit a986b0ac9c
4 changed files with 198 additions and 198 deletions

View File

@ -1,101 +1,101 @@
using Sandbox.ModAPI.Ingame; using Sandbox.ModAPI.Ingame;
using System.Collections.Generic; using System.Collections.Generic;
namespace IngameScript namespace IngameScript
{ {
partial class Program partial class Program
{ {
[Flags] [Flags]
public enum FailoverState public enum FailoverState
{ {
Standby = 0x1, Standby = 0x1,
Failover = 0x2, Failover = 0x2,
Active = 0x4, Active = 0x4,
} }
public class FailoverManager public class FailoverManager
{ {
private Program _program; private Program _program;
private IConsole _console; private IConsole _console;
private string _id; private string _id;
private int _rank; private int _rank;
private bool _active = false; private bool _active = false;
private SortedDictionary<int, IMyProgrammableBlock> _nodes = new SortedDictionary<int, IMyProgrammableBlock>(); private SortedDictionary<int, IMyProgrammableBlock> _nodes = new SortedDictionary<int, IMyProgrammableBlock>();
public FailoverManager(Program program, IConsole console, MyIni ini) public FailoverManager(Program program, IConsole console, MyIni ini)
{ {
_program = program; _program = program;
_console = new PrefixedConsole(console, "FailoverManager"); _console = new PrefixedConsole(console, "FailoverManager");
_ini = ini; _ini = ini;
// Read our config // Read our config
bool configExists = _ini.TryParse(_program.Me.CustomData); bool configExists = _ini.TryParse(_program.Me.CustomData);
if (!configExists) if (!configExists)
{ {
_console.Print("No failover config, assuming we are single instance."); _console.Print("No failover config, assuming we are single instance.");
_active = true; _active = true;
return; return;
} }
_id = _ini.Get("failover", "id").ToString(); _id = _ini.Get("failover", "id").ToString();
_rank = _ini.Get("failover", "rank").ToInt32(-1); _rank = _ini.Get("failover", "rank").ToInt32(-1);
if (_id == "" || _rank == -1) if (_id == "" || _rank == -1)
{ {
_console.Print("Failover config invalid. Assuming we are single instance."); _console.Print("Failover config invalid. Assuming we are single instance.");
_active = true; _active = true;
return; return;
} }
List<IMyProgrammableBlock> allPBs = new List<IMyProgrammableBlock>(); List<IMyProgrammableBlock> allPBs = new List<IMyProgrammableBlock>();
_program.GridTerminalSystem.GetBlocksOfType(allPBs, blockFilter); _program.GridTerminalSystem.GetBlocksOfType(allPBs, blockFilter);
foreach (IMyProgrammableBlock node in allPBs) foreach (IMyProgrammableBlock node in allPBs)
{ {
_ini.TryParse(node.CustomData); _ini.TryParse(node.CustomData);
string foreignId = _ini.Get("failover", "id"); string foreignId = _ini.Get("failover", "id");
if (foreignId != _id) continue; if (foreignId != _id) continue;
int foreignRank = _ini.Get("failover", "rank"); int foreignRank = _ini.Get("failover", "rank");
if (foreignRank == -1) continue; if (foreignRank == -1) continue;
_nodes[foreignRank] = node; _nodes[foreignRank] = node;
} }
} }
// returns true if we should become the active node // returns true if we should become the active node
public FailoverState ActiveCheck() public FailoverState ActiveCheck()
{ {
// Once we're the active node, we will stay active for the life of the script. // 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... // TODO: test the assumption that the script restarts from scratch on disable/enable...
// if not we may need additional logic. // if not we may need additional logic.
if (_active) return FailoverState.Active; if (_active) return FailoverState.Active;
foreach (KeyValuePair<int, IMyProgrammableBlock> kvp in _nodes) foreach (KeyValuePair<int, IMyProgrammableBlock> kvp in _nodes)
{ {
// If we have a higher priority than the nodes we've checked so far, // If we have a higher priority than the nodes we've checked so far,
// we must be the active node. // we must be the active node.
if (_rank < kvp.Key) if (_rank < kvp.Key)
{ {
_active = true; _active = true;
return FailoverState.Failover | FailoverState.Active; return FailoverState.Failover | FailoverState.Active;
} }
// We've found an active node with higher priority than us. // We've found an active node with higher priority than us.
if (!kvp.Value.Closed && kvp.Value.Enabled) return FailoverState.Standby; if (!kvp.Value.Closed && kvp.Value.Enabled) return FailoverState.Standby;
} }
// We're the last node standing. Let's hope we don't go down. // We're the last node standing. Let's hope we don't go down.
_active = true; _active = true;
return FailoverState.Failover | FailoverState.Active; return FailoverState.Failover | FailoverState.Active;
} }
private bool blockFilter(IMyProgrammableBlock block) private bool blockFilter(IMyProgrammableBlock block)
{ {
return block.IsSameConstructAs(_program.Me) && MyIni.HasSection(block.CustomData, "failover"); return block.IsSameConstructAs(_program.Me) && MyIni.HasSection(block.CustomData, "failover");
} }
} }
} }
} }

View File

@ -1,14 +1,14 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup> <PropertyGroup>
<MSBuildAllProjects Condition="'$(MSBuildVersion)' == '' Or '$(MSBuildVersion)' &lt; '16.0'">$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects> <MSBuildAllProjects Condition="'$(MSBuildVersion)' == '' Or '$(MSBuildVersion)' &lt; '16.0'">$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
<HasSharedItems>true</HasSharedItems> <HasSharedItems>true</HasSharedItems>
<SharedGUID>8a3cdcc5-4b55-4d87-a415-698a0e1ff06f</SharedGUID> <SharedGUID>8a3cdcc5-4b55-4d87-a415-698a0e1ff06f</SharedGUID>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)FailoverManager.cs" /> <Compile Include="$(MSBuildThisFileDirectory)FailoverManager.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="$(MSBuildThisFileDirectory)readme.md" /> <None Include="$(MSBuildThisFileDirectory)readme.md" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -1,20 +1,20 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Label="Globals"> <PropertyGroup Label="Globals">
<ProjectGuid>8a3cdcc5-4b55-4d87-a415-698a0e1ff06f</ProjectGuid> <ProjectGuid>8a3cdcc5-4b55-4d87-a415-698a0e1ff06f</ProjectGuid>
<MinimumVisualStudioVersion>14.0</MinimumVisualStudioVersion> <MinimumVisualStudioVersion>14.0</MinimumVisualStudioVersion>
</PropertyGroup> </PropertyGroup>
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')"/> <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')"/>
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.Default.props"/> <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.Default.props"/>
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.props"/> <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.props"/>
<PropertyGroup/> <PropertyGroup/>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<OutputPath>bin\Debug\</OutputPath> <OutputPath>bin\Debug\</OutputPath>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<OutputPath>bin\Release\</OutputPath> <OutputPath>bin\Release\</OutputPath>
</PropertyGroup> </PropertyGroup>
<Import Project="FailoverManager.projitems" Label="Shared"/> <Import Project="FailoverManager.projitems" Label="Shared"/>
<Import Project="../Console/Console.projitems" Label="Shared"/> <Import Project="../Console/Console.projitems" Label="Shared"/>
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.CSharp.targets"/> <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.CSharp.targets"/>
</Project> </Project>

View File

@ -1,66 +1,66 @@
# Failover Manager for SE scripts # 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. 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 ## Prerequisites
* This is a mixin for [MDK2](TODO). If you aren't using MDK2, see usage instructions [here](TODO). * 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). * 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. * 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 ## 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. * 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 ## Usage
This code is designed to be as drop-in as possible once you meet the prerequisites above. Usage should look something like this: 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 partial class Program : MyGridProgram
public MyIni Ini { get; } = new MyIni(); public MyIni Ini { get; } = new MyIni();
public IConsole Console { get; private set; } public IConsole Console { get; private set; }
private FailoverManager _failover; private FailoverManager _failover;
public Program() { public Program() {
Console = new MainConsole(this, "Script Name"); Console = new MainConsole(this, "Script Name");
_failover = new FailoverManager(this, Console, Ini); _failover = new FailoverManager(this, Console, Ini);
// alternate instantiation with Stub Console and fresh MyIni instance. // 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. // 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()); // _failover = new FailoverManager(this, new EchoConsole(this), new MyIni());
// The script *must* start out with periodic updates, even if it is // The script *must* start out with periodic updates, even if it is
// a script that's only triggered conditionally (though in that case), // a script that's only triggered conditionally (though in that case),
// see caveats. // see caveats.
Runtime.UpdateFrequency = UpdateFrequency.Update100; Runtime.UpdateFrequency = UpdateFrequency.Update100;
// ... script initialization happens here // ... script initialization happens here
} }
public void Main(string argument, UpdateType updateSource) { public void Main(string argument, UpdateType updateSource) {
// This basically instructs the script to "go back to sleep" // This basically instructs the script to "go back to sleep"
// if it isn't the active node. // if it isn't the active node.
if (_failover.ActiveCheck == FailoverState.Standby) return; if (_failover.ActiveCheck == FailoverState.Standby) return;
// If you need more complicated "warm-up" logic when your script // If you need more complicated "warm-up" logic when your script
// becomes active, (for example, changing the update frequency) you can use this. // 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 statement will only be true once, when the script first takes over control.
// //
// This is optional and depends on your use case. // This is optional and depends on your use case.
if (_failover.ActiveCheck.HasFlag(FailoverState.Failover)) { if (_failover.ActiveCheck.HasFlag(FailoverState.Failover)) {
// Code that should run once when this script becomes the active node goes here // Code that should run once when this script becomes the active node goes here
} }
// ... your script logic 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: Scripts that are using failover also need configuration in their Programmable Blocks' Custom Data. That should look something like this:
``` ```
[failover] [failover]
id=scriptName # This must match for all running copies of the script. 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 rank=0 # The lowest ranked copy of the script will be the primary node, and they will failover in ascending rank order
``` ```