Refactor airlock for fewer lookups and in preparation for using a sequencer.

This commit is contained in:
Anna Rose 2025-02-09 21:51:47 -05:00
parent e82303ff00
commit 723b27298b
3 changed files with 363 additions and 284 deletions

View File

@ -5,307 +5,383 @@ using VRageMath;
namespace IngameScript namespace IngameScript
{ {
public class Airlock partial class Program
{ {
enum AirlockLightState public class Airlock
{ {
Off, enum AirlockLightState
Cycling,
Cooldown,
Error,
}
public bool DoorsClosed
{
get
{ {
return innerDoor.Status == DoorStatus.Closed && outerDoor.Status == DoorStatus.Closed; Off,
Cycling,
Cooldown,
Error,
} }
}
// TODO: this should check for depressurizing to the *reference* amount public bool DoorsClosed
public bool PressureStabilized
{
get
{ {
return ((airVent.Depressurize && airVent.GetOxygenLevel() <= targetOxygenLevel) || get
(!airVent.Depressurize && airVent.Status == VentStatus.Pressurized));
}
}
public bool OxygenBalanced
{
get { return oxygenTank.FilledRatio > 0.25; }
}
public bool Functional { get; private set; } = true;
public bool Cycling { get; private set; } = false;
private bool DoorOpened
{
get
{
return innerDoor.Status == DoorStatus.Open || outerDoor.Status == DoorStatus.Open;
}
}
// Returns false if we are in a state where we can't or don't need to balance
private bool balanceOxygen()
{
if (innerDoor.Status == DoorStatus.Closed || outerDoor.Status == DoorStatus.Open || OxygenBalanced) { return false; }
_console.Print($"{_name}: Balancing Oxygen Tank");
// Configure the vent to suck in Oxygen.
airVent.Depressurize = true;
airVent.Enabled = true;
return true;
}
private string _name;
private PrefixedConsole _console;
private MyGridProgram _p;
private float targetOxygenLevel = 0.0F;
private IMyDoor innerDoor;
private IMyDoor outerDoor;
private List<IMyLightingBlock> lights;
private IMyGasTank oxygenTank;
private IMyAirVent airVent;
private IMyAirVent airSensor;
private const int CooldownTicks = 120;
public Airlock(MyGridProgram program, Console console, string name)
{
_p = program;
_name = name;
_console = console.CreatePrefixedConsole(_name);
// Find the appropriate blocks given the airlock name
initDoors();
initLights();
initVents();
initOxygen();
}
private void initDoors()
{
List<IMyDoor> onlyDoors = new List<IMyDoor>();
_p.GridTerminalSystem.GetBlocksOfType(onlyDoors);
foreach (IMyDoor door in onlyDoors)
{
if (!door.CustomName.StartsWith(_name)) continue;
if (door.CustomName.Contains("Inner"))
{ {
innerDoor = door; return innerDoor.Status == DoorStatus.Closed && outerDoor.Status == DoorStatus.Closed;
}
else if (door.CustomName.Contains("Outer"))
{
outerDoor = door;
} }
}
if (innerDoor != null && outerDoor != null) // TODO: this should check for depressurizing to the *reference* amount
public bool PressureStabilized
{
get
{ {
return ((airVent.Depressurize && airVent.GetOxygenLevel() <= targetOxygenLevel) ||
(!airVent.Depressurize && airVent.Status == VentStatus.Pressurized));
}
}
public bool OxygenBalanced
{
get { return oxygenTank.FilledRatio > 0.25; }
}
public bool Functional
{
get
{
return (innerDoor != null && outerDoor != null && airVent != null && oxygenTank != null);
}
}
public bool Cycling { get; private set; } = false;
private bool DoorOpened
{
get
{
return innerDoor.Status == DoorStatus.Open || outerDoor.Status == DoorStatus.Open;
}
}
// Returns false if we are in a state where we can't or don't need to balance
private bool balanceOxygen()
{
if (innerDoor.Status == DoorStatus.Closed || outerDoor.Status == DoorStatus.Open || OxygenBalanced) { return false; }
_console.Print($"{_name}: Balancing Oxygen Tank");
// Configure the vent to suck in Oxygen.
airVent.Depressurize = true;
airVent.Enabled = true;
return true;
}
private string _name;
private PrefixedConsole _console;
private MyGridProgram _p;
private float targetOxygenLevel = 0.0F;
private IMyDoor innerDoor;
private IMyDoor outerDoor;
private List<IMyLightingBlock> lights;
private IMyGasTank oxygenTank;
private IMyAirVent airVent;
private IMyAirVent airSensor;
private const int CooldownTicks = 120;
public Airlock(MyGridProgram program, Console console, string name)
{
_p = program;
_name = name;
_console = console.CreatePrefixedConsole(_name);
lights = new List<IMyLightingBlock>();
// Find the appropriate blocks given the airlock name
// initDoors();
// initLights();
// initVents();
// initOxygen();
}
public void AddBlock(IMyTerminalBlock block)
{
if (block is IMyDoor)
{
addDoor(block as IMyDoor);
return; return;
} }
} if (block is IMyLightingBlock)
Functional = false;
}
private void initVents()
{
List<IMyAirVent> onlyFans = new List<IMyAirVent>();
_p.GridTerminalSystem.GetBlocksOfType(onlyFans);
foreach (IMyAirVent vent in onlyFans)
{
if (!vent.CustomName.StartsWith(_name)) continue;
if (vent.CustomName.StartsWith(_name))
{ {
if (vent.CustomName.Contains("Main")) lights.Add(block as IMyLightingBlock);
{ return;
airVent = vent; }
continue; if (block is IMyAirVent)
} {
else if (vent.CustomName.Contains("Reference")) addVent(block as IMyAirVent);
{ return;
airSensor = vent; }
continue; if (block is IMyGasTank)
} {
addOxygenTank(block as IMyGasTank);
return;
} }
// A global reference vent will be used if we don't have one specific to our airlock. _console.Print($"Tried to add invalid block '{block.CustomName}'");
// A specific vent found later will overwrite this assignment. }
if (vent.CustomName.StartsWith("Airlock Reference") && airSensor == null)
private void addDoor(IMyDoor door)
{
if (door.CustomName.Contains("Inner") && innerDoor == null)
{
innerDoor = door;
return;
}
if (door.CustomName.Contains("Outer") && outerDoor == null)
{
outerDoor = door;
return;
}
_console.Print($"Couldn't add door '{door.CustomName}'");
}
private void addVent(IMyAirVent vent)
{
if (vent.CustomName.Contains("Main") && airVent == null)
{
airVent = vent;
return;
}
if (vent.CustomName.Contains("Reference") && airSensor == null)
{ {
airSensor = vent; airSensor = vent;
return;
}
_console.Print($"Couldn't add air vent '{vent.CustomName}'");
}
private void addOxygenTank(IMyGasTank tank)
{
if (oxygenTank == null)
{
oxygenTank = tank;
return;
}
_console.Print($"Couldn't add oxygen tank '{tank.CustomName}'");
}
// private void initDoors()
// {
// List<IMyDoor> onlyDoors = new List<IMyDoor>();
// _p.GridTerminalSystem.GetBlocksOfType(onlyDoors);
// foreach (IMyDoor door in onlyDoors)
// {
// if (!door.CustomName.StartsWith(_name)) continue;
// if (door.CustomName.Contains("Inner"))
// {
// innerDoor = door;
// }
// else if (door.CustomName.Contains("Outer"))
// {
// outerDoor = door;
// }
// if (innerDoor != null && outerDoor != null)
// {
// return;
// }
// }
// Functional = false;
// }
// private void initVents()
// {
// List<IMyAirVent> onlyFans = new List<IMyAirVent>();
// _p.GridTerminalSystem.GetBlocksOfType(onlyFans);
// foreach (IMyAirVent vent in onlyFans)
// {
// if (!vent.CustomName.StartsWith(_name)) continue;
// if (vent.CustomName.StartsWith(_name))
// {
// if (vent.CustomName.Contains("Main"))
// {
// airVent = vent;
// continue;
// }
// else if (vent.CustomName.Contains("Reference"))
// {
// airSensor = vent;
// continue;
// }
// }
// // A global reference vent will be used if we don't have one specific to our airlock.
// // A specific vent found later will overwrite this assignment.
// if (vent.CustomName.StartsWith("Airlock Reference") && airSensor == null)
// {
// airSensor = vent;
// }
// }
// if (airVent == null) Functional = false;
// }
// private void initLights()
// {
// lights = new List<IMyLightingBlock>();
// List<IMyLightingBlock> allLights = new List<IMyLightingBlock>();
// _p.GridTerminalSystem.GetBlocksOfType(allLights);
// foreach (IMyLightingBlock light in allLights)
// {
// if (!light.CustomName.StartsWith(_name)) continue;
// lights.Add(light);
// }
// }
// private void initOxygen()
// {
// List<IMyGasTank> allTanks = new List<IMyGasTank>();
// _p.GridTerminalSystem.GetBlocksOfType(allTanks);
// foreach (IMyGasTank tank in allTanks)
// {
// if (!tank.CustomName.StartsWith(_name)) continue;
// oxygenTank = tank;
// return;
// }
// Functional = false;
// }
public IEnumerator<bool> CycleAirlock()
{
Cycling = true;
setLights(AirlockLightState.Cycling);
closeDoors();
while (!DoorsClosed) { yield return true; }
lockDoors();
if (!airVent.CanPressurize)
{
error("Airlock is not airtight.");
Cycling = false;
yield break;
}
pressurizeDepressurize();
while (!PressureStabilized) { yield return true; }
airVent.Enabled = false;
openDoor();
while (!DoorOpened) { yield return true; }
lockDoors();
// Balance oxygen storage
setLights(AirlockLightState.Cooldown);
if (balanceOxygen())
{
while (!OxygenBalanced) { yield return true; }
}
airVent.Enabled = false;
// Cooldown period
int cooldown = 0;
while (cooldown < CooldownTicks)
{
cooldown++;
yield return true;
}
setLights(AirlockLightState.Off);
_console.Print("Cycling Complete.");
Cycling = false;
}
private void closeDoors()
{
_console.Print("Closing Doors");
// close the doors
innerDoor.Enabled = true;
outerDoor.Enabled = true;
innerDoor.CloseDoor();
outerDoor.CloseDoor();
}
private void pressurizeDepressurize()
{
_console.Print("Cycling");
// toggle the current state
airVent.Depressurize = !airVent.Depressurize;
airVent.Enabled = true;
// When depressurizing, check the external pressure and only depressurize to that value.
// TODO: test this for floating point errors
if (airVent.Depressurize && airSensor != null)
{
targetOxygenLevel = airSensor.GetOxygenLevel();
_console.Print($"Set depressurization target to {targetOxygenLevel}");
} }
} }
if (airVent == null) Functional = false; // Open the appropriate door based on pressure state.
} private void openDoor()
private void initLights()
{
lights = new List<IMyLightingBlock>();
List<IMyLightingBlock> allLights = new List<IMyLightingBlock>();
_p.GridTerminalSystem.GetBlocksOfType(allLights);
foreach (IMyLightingBlock light in allLights)
{ {
if (!light.CustomName.StartsWith(_name)) continue; _console.Print($"Opening Door");
lights.Add(light);
}
}
private void initOxygen() if (airVent.Status == VentStatus.Pressurized)
{ {
List<IMyGasTank> allTanks = new List<IMyGasTank>(); innerDoor.Enabled = true;
_p.GridTerminalSystem.GetBlocksOfType(allTanks); innerDoor.OpenDoor();
foreach (IMyGasTank tank in allTanks) }
{ else
if (!tank.CustomName.StartsWith(_name)) continue; {
oxygenTank = tank; outerDoor.Enabled = true;
return; outerDoor.OpenDoor();
} }
Functional = false;
}
public IEnumerator<bool> CycleAirlock()
{
Cycling = true;
setLights(AirlockLightState.Cycling);
closeDoors();
while (!DoorsClosed) { yield return true; }
lockDoors();
if (!airVent.CanPressurize)
{
error("Airlock is not airtight.");
Cycling = false;
yield break;
} }
pressurizeDepressurize(); // TODO: blinkenlights are unsatisfying right now...
while (!PressureStabilized) { yield return true; } private void setLights(AirlockLightState lightState)
airVent.Enabled = false;
openDoor();
while (!DoorOpened) { yield return true; }
lockDoors();
// Balance oxygen storage
setLights(AirlockLightState.Cooldown);
if (balanceOxygen())
{ {
while (!OxygenBalanced) { yield return true; } float blinkLength = 1.0F;
} float blinkInterval = 0.0F;
airVent.Enabled = false; Color color = Color.Red;
// Cooldown period switch (lightState)
int cooldown = 0; {
while (cooldown < CooldownTicks) case AirlockLightState.Off:
{ color = Color.Green;
cooldown++; break;
yield return true; case AirlockLightState.Cycling:
blinkInterval = 1.0F;
blinkLength = 0.75F;
break;
case AirlockLightState.Cooldown:
color = Color.Yellow;
break;
case AirlockLightState.Error:
// the defaults already set this correctly
break;
}
foreach (IMyLightingBlock light in lights)
{
light.Enabled = true;
light.BlinkIntervalSeconds = blinkInterval;
light.BlinkLength = blinkLength;
light.Color = color;
}
} }
setLights(AirlockLightState.Off); private void lockDoors()
_console.Print("Cycling Complete.");
Cycling = false;
}
private void closeDoors()
{
_console.Print("Closing Doors");
// close the doors
innerDoor.Enabled = true;
outerDoor.Enabled = true;
innerDoor.CloseDoor();
outerDoor.CloseDoor();
}
private void pressurizeDepressurize()
{
_console.Print("Cycling");
// toggle the current state
airVent.Depressurize = !airVent.Depressurize;
airVent.Enabled = true;
// When depressurizing, check the external pressure and only depressurize to that value.
// TODO: test this for floating point errors
if (airVent.Depressurize && airSensor != null)
{ {
targetOxygenLevel = airSensor.GetOxygenLevel(); innerDoor.Enabled = false;
_console.Print($"Set depressurization target to {targetOxygenLevel}"); outerDoor.Enabled = false;
}
}
// Open the appropriate door based on pressure state.
private void openDoor()
{
_console.Print($"Opening Door");
if (airVent.Status == VentStatus.Pressurized)
{
innerDoor.Enabled = true;
innerDoor.OpenDoor();
}
else
{
outerDoor.Enabled = true;
outerDoor.OpenDoor();
}
}
// TODO: blinkenlights are unsatisfying right now...
private void setLights(AirlockLightState lightState)
{
float blinkLength = 1.0F;
float blinkInterval = 0.0F;
Color color = Color.Red;
switch (lightState)
{
case AirlockLightState.Off:
color = Color.Green;
break;
case AirlockLightState.Cycling:
blinkInterval = 1.0F;
blinkLength = 0.75F;
break;
case AirlockLightState.Cooldown:
color = Color.Yellow;
break;
case AirlockLightState.Error:
// the defaults already set this correctly
break;
} }
foreach (IMyLightingBlock light in lights) private void error(string error)
{ {
light.Enabled = true; _console.Print(error);
light.BlinkIntervalSeconds = blinkInterval; setLights(AirlockLightState.Error);
light.BlinkLength = blinkLength;
light.Color = color;
} }
} }
private void lockDoors()
{
innerDoor.Enabled = false;
outerDoor.Enabled = false;
}
private void error(string error)
{
_console.Print(error);
setLights(AirlockLightState.Error);
}
} }
} }

View File

@ -24,5 +24,5 @@
<AdditionalFiles Include="thumb.png" /> <AdditionalFiles Include="thumb.png" />
</ItemGroup> </ItemGroup>
<Import Project="..\Mixins\Console\Console.projitems" Label="shared" /> <Import Project="..\Mixins\Console\Console.projitems" Label="shared" />
<Import Project="..\Mixins\ConfigParser\ConfigParser.projitems" Label="shared" /> <Import Project="..\Mixins\Utils\Utils.projitems" Label="shared" />
</Project> </Project>

View File

@ -12,30 +12,33 @@ namespace IngameScript
private int _tickCount = 0; private int _tickCount = 0;
private MyCommandLine _cli; private MyCommandLine _cli;
private Console _console; private Console _console;
private MyIni _ini;
public Program() public Program()
{ {
_console = new Console(this); _ini = new MyIni();
_console = new Console(this, _ini);
_cli = new MyCommandLine(); _cli = new MyCommandLine();
_jobs = new List<IEnumerator<bool>>(); _jobs = new List<IEnumerator<bool>>();
_airlocks = new Dictionary<string, Airlock>(); _airlocks = new Dictionary<string, Airlock>();
List<IMyDoor> doors = new List<IMyDoor>(); List<IMyTerminalBlock> airlockBlocks = new List<IMyTerminalBlock>();
GridTerminalSystem.GetBlocksOfType(doors); GridTerminalSystem.GetBlocksOfType(airlockBlocks, block => block.CustomName.Contains("!Airlock"));
foreach (IMyDoor door in doors) foreach (IMyTerminalBlock block in airlockBlocks)
{ {
if (!door.CustomName.StartsWith("Airlock")) continue;
string airlockName = door.CustomName.Split(' ')[0];
if (_airlocks.ContainsKey(airlockName)) continue;
Airlock newAirlock = new Airlock(this, _console, airlockName);
if (!newAirlock.Functional)
{ {
_console.Print($"{airlockName} is missing one or more required blocks."); string airlockName = Utils.ExtractTag(block, "!Airlock");
continue; if (!_airlocks.ContainsKey(airlockName))
} {
_airlocks[airlockName] = newAirlock; _airlocks[airlockName] = new Airlock(this, _console, airlockName);
} }
_airlocks[airlockName].AddBlock(block);
}
// TODO: it would be most convenient to just delete non-functional Airlocks... but maybe they're worth
// keeping around for warnings.
}
_console.Print($"Found {_airlocks.Count} airlocks."); _console.Print($"Found {_airlocks.Count} airlocks.");
} }
@ -49,7 +52,7 @@ namespace IngameScript
if (_cli.ArgumentCount == 0) { _console.Print("You must provide an airlock ID."); } if (_cli.ArgumentCount == 0) { _console.Print("You must provide an airlock ID."); }
else else
{ {
string airlockName = $"Airlock{_cli.Argument(0)}"; string airlockName = $"!Airlock{_cli.Argument(0)}";
if (!_airlocks.ContainsKey(airlockName)) if (!_airlocks.ContainsKey(airlockName))
{ {
_console.Print($"Invalid airlock ID {_cli.Argument(0)}"); _console.Print($"Invalid airlock ID {_cli.Argument(0)}");