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