diff --git a/Content.Server/Power/Commands/PowerValidateCommand.cs b/Content.Server/Power/Commands/PowerValidateCommand.cs
new file mode 100644
index 0000000000..a3a6b09167
--- /dev/null
+++ b/Content.Server/Power/Commands/PowerValidateCommand.cs
@@ -0,0 +1,29 @@
+using Content.Server.Administration;
+using Content.Server.Power.EntitySystems;
+using Content.Shared.Administration;
+using Robust.Shared.Console;
+
+namespace Content.Server.Power.Commands;
+
+[AdminCommand(AdminFlags.Debug)]
+public sealed class PowerValidateCommand : LocalizedEntityCommands
+{
+ [Dependency] private readonly PowerNetSystem _powerNet = null!;
+
+ public override string Command => "power_validate";
+
+ public override void Execute(IConsoleShell shell, string argStr, string[] args)
+ {
+ try
+ {
+ _powerNet.Validate();
+ }
+ catch (Exception e)
+ {
+ shell.WriteLine(LocalizationManager.GetString("cmd-power_validate-error", ("err", e.ToString())));
+ return;
+ }
+
+ shell.WriteLine(LocalizationManager.GetString("cmd-power_validate-success"));
+ }
+}
diff --git a/Content.Server/Power/EntitySystems/PowerNetSystem.cs b/Content.Server/Power/EntitySystems/PowerNetSystem.cs
index 666d3fcbe8..ab2e27600a 100644
--- a/Content.Server/Power/EntitySystems/PowerNetSystem.cs
+++ b/Content.Server/Power/EntitySystems/PowerNetSystem.cs
@@ -519,6 +519,14 @@ namespace Content.Server.Power.EntitySystems
supplier.NetworkSupply.LinkedNetwork = netNode.Id;
}
}
+
+ ///
+ /// Validate integrity of the power state data. Throws if an error is found.
+ ///
+ public void Validate()
+ {
+ _solver.Validate(_powerState);
+ }
}
///
diff --git a/Content.Server/Power/Pow3r/BatteryRampPegSolver.cs b/Content.Server/Power/Pow3r/BatteryRampPegSolver.cs
index 599c55b6ba..bd998a0151 100644
--- a/Content.Server/Power/Pow3r/BatteryRampPegSolver.cs
+++ b/Content.Server/Power/Pow3r/BatteryRampPegSolver.cs
@@ -1,6 +1,8 @@
-using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
using Robust.Shared.Utility;
using System.Linq;
+using System.Runtime.CompilerServices;
+using JetBrains.Annotations;
using Robust.Shared.Threading;
using static Content.Server.Power.Pow3r.PowerState;
@@ -40,7 +42,9 @@ namespace Content.Server.Power.Pow3r
DebugTools.Assert(state.GroupedNets.Select(x => x.Count).Sum() == state.Networks.Count);
_networkJob.State = state;
_networkJob.FrameTime = frameTime;
+#if DEBUG
ValidateNetworkGroups(state, state.GroupedNets);
+#endif
// Each network height layer can be run in parallel without issues.
foreach (var group in state.GroupedNets)
@@ -328,16 +332,22 @@ namespace Content.Server.Power.Pow3r
RecursivelyEstimateNetworkDepth(state, network, groupedNetworks);
}
- ValidateNetworkGroups(state, groupedNetworks);
return groupedNetworks;
}
+ public void Validate(PowerState state)
+ {
+ if (state.GroupedNets == null)
+ throw new InvalidOperationException("We don't have grouped networks cached??");
+
+ ValidateNetworkGroups(state, state.GroupedNets);
+ }
+
///
/// Validate that network grouping is up to date. I.e., that it is safe to solve each networking in a given
/// group in parallel. This assumes that batteries are the only device that connects to multiple networks, and
/// is thus the only obstacle to solving everything in parallel.
///
- [Conditional("DEBUG")]
private void ValidateNetworkGroups(PowerState state, List> groupedNetworks)
{
HashSet nets = new();
@@ -362,9 +372,9 @@ namespace Content.Server.Power.Pow3r
continue;
}
- DebugTools.Assert(!nets.Contains(subNet));
- DebugTools.Assert(!netIds.Contains(subNet.Id));
- DebugTools.Assert(subNet.Height < net.Height);
+ Check(!nets.Contains(subNet), $"Net {net.Id}, battery {batteryId}");
+ Check(!netIds.Contains(subNet.Id), $"Net {net.Id}, battery {batteryId}");
+ Check(subNet.Height < net.Height, $"Net {net.Id}, battery {batteryId}");
}
foreach (var batteryId in net.BatterySupplies)
@@ -380,15 +390,32 @@ namespace Content.Server.Power.Pow3r
continue;
}
- DebugTools.Assert(!nets.Contains(parentNet));
- DebugTools.Assert(!netIds.Contains(parentNet.Id));
- DebugTools.Assert(parentNet.Height > net.Height);
+ Check(!nets.Contains(parentNet), $"Net {net.Id}, battery {batteryId}");
+ Check(!netIds.Contains(parentNet.Id), $"Net {net.Id}, battery {batteryId}");
+ Check(parentNet.Height > net.Height, $"Net {net.Id}, battery {batteryId}");
}
- DebugTools.Assert(nets.Add(net));
- DebugTools.Assert(netIds.Add(net.Id));
+ Check(nets.Add(net), $"Net {net.Id}");
+ Check(netIds.Add(net.Id), $"Net {net.Id}");
}
}
+
+ return;
+
+ // Most readable C# function def.
+ [AssertionMethod]
+ static void Check(
+ [AssertionCondition(AssertionConditionType.IS_TRUE)]
+ [DoesNotReturnIf(false)]
+ bool condition,
+ [InterpolatedStringHandlerArgument("condition")]
+ ref DebugTools.AssertInterpolatedStringHandler handler,
+ [CallerArgumentExpression(nameof(condition))]
+ string check = "")
+ {
+ if (!condition)
+ throw new DebugAssertException($"{handler.ToStringAndClear()}: failed check: {check}");
+ }
}
private static void RecursivelyEstimateNetworkDepth(PowerState state, Network network, List> groupedNetworks)
diff --git a/Content.Server/Power/Pow3r/IPowerSolver.cs b/Content.Server/Power/Pow3r/IPowerSolver.cs
index bcc33212ae..44d1a47d00 100644
--- a/Content.Server/Power/Pow3r/IPowerSolver.cs
+++ b/Content.Server/Power/Pow3r/IPowerSolver.cs
@@ -5,5 +5,6 @@ namespace Content.Server.Power.Pow3r
public interface IPowerSolver
{
void Tick(float frameTime, PowerState state, IParallelManager parallel);
+ void Validate(PowerState state);
}
}
diff --git a/Content.Server/Power/Pow3r/NoOpSolver.cs b/Content.Server/Power/Pow3r/NoOpSolver.cs
index d82de3fd57..c8829fc6f8 100644
--- a/Content.Server/Power/Pow3r/NoOpSolver.cs
+++ b/Content.Server/Power/Pow3r/NoOpSolver.cs
@@ -8,5 +8,10 @@ namespace Content.Server.Power.Pow3r
{
// Literally nothing.
}
+
+ public void Validate(PowerState state)
+ {
+ // Literally nothing.
+ }
}
}
diff --git a/Resources/Locale/en-US/power/commands.ftl b/Resources/Locale/en-US/power/commands.ftl
new file mode 100644
index 0000000000..0908af1a2b
--- /dev/null
+++ b/Resources/Locale/en-US/power/commands.ftl
@@ -0,0 +1,4 @@
+cmd-power_validate-desc = Validate power network state integrity
+cmd-power_validate-help = Usage: power_validate
+cmd-power_validate-error = Error while validating: { $err }
+cmd-power_validate-success = Validation succeeded without error