Fix unix-style line endings. Oops.
This commit is contained in:
parent
903cb95f1e
commit
a986b0ac9c
|
@ -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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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)' < '16.0'">$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
|
<MSBuildAllProjects Condition="'$(MSBuildVersion)' == '' Or '$(MSBuildVersion)' < '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>
|
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
```
|
```
|
Loading…
Reference in New Issue
Block a user