Parallelize BatteryRampPegSolver (#12351)

This commit is contained in:
Leon Friedrich
2022-11-09 14:43:45 +13:00
committed by GitHub
parent 4a68db4eb2
commit eebb31493c
11 changed files with 546 additions and 250 deletions

View File

@@ -11,6 +11,8 @@ using Robust.Shared.GameObjects;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Maths; using Robust.Shared.Maths;
using Robust.Shared.Timing; using Robust.Shared.Timing;
using TerraFX.Interop.Windows;
using static Content.Server.Power.Pow3r.PowerState;
namespace Content.IntegrationTests.Tests.Power namespace Content.IntegrationTests.Tests.Power
{ {
@@ -428,6 +430,93 @@ namespace Content.IntegrationTests.Tests.Power
await pairTracker.CleanReturnAsync(); await pairTracker.CleanReturnAsync();
} }
[Test]
public async Task TestNoDemandRampdown()
{
// checks that batteries and supplies properly ramp down if the load is disconnected/disabled.
await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings { NoClient = true, ExtraPrototypes = Prototypes });
var server = pairTracker.Pair.Server;
var mapManager = server.ResolveDependency<IMapManager>();
var entityManager = server.ResolveDependency<IEntityManager>();
PowerSupplierComponent supplier = default!;
PowerNetworkBatteryComponent netBattery = default!;
BatteryComponent battery = default!;
PowerConsumerComponent consumer = default!;
var rampRate = 500;
var rampTol = 100;
var draw = 1000;
await server.WaitAssertion(() =>
{
var map = mapManager.CreateMap();
var grid = mapManager.CreateGrid(map);
// Power only works when anchored
for (var i = 0; i < 3; i++)
{
grid.SetTile(new Vector2i(0, i), new Tile(1));
entityManager.SpawnEntity("CableHV", grid.ToCoordinates(0, i));
}
var generatorEnt = entityManager.SpawnEntity("GeneratorDummy", grid.ToCoordinates());
var consumerEnt = entityManager.SpawnEntity("ConsumerDummy", grid.ToCoordinates(0, 1));
var batteryEnt = entityManager.SpawnEntity("DischargingBatteryDummy", grid.ToCoordinates(0,2));
netBattery = entityManager.GetComponent<PowerNetworkBatteryComponent>(batteryEnt);
battery = entityManager.GetComponent<BatteryComponent>(batteryEnt);
supplier = entityManager.GetComponent<PowerSupplierComponent>(generatorEnt);
consumer = entityManager.GetComponent<PowerConsumerComponent>(consumerEnt);
consumer.DrawRate = draw;
supplier.MaxSupply = draw/2;
supplier.SupplyRampRate = rampRate;
supplier.SupplyRampTolerance = rampTol;
battery.MaxCharge = 100_000;
battery.CurrentCharge = 100_000;
netBattery.MaxSupply = draw/2;
netBattery.SupplyRampRate = rampRate;
netBattery.SupplyRampTolerance = rampTol;
});
server.RunTicks(1);
await server.WaitAssertion(() =>
{
Assert.That(supplier.CurrentSupply, Is.EqualTo(rampTol).Within(0.1));
Assert.That(netBattery.CurrentSupply, Is.EqualTo(rampTol).Within(0.1));
Assert.That(consumer.ReceivedPower, Is.EqualTo(rampTol*2).Within(0.1));
});
server.RunTicks(60);
await server.WaitAssertion(() =>
{
Assert.That(supplier.CurrentSupply, Is.EqualTo(draw/2).Within(0.1));
Assert.That(supplier.SupplyRampPosition, Is.EqualTo(draw/2).Within(0.1));
Assert.That(netBattery.CurrentSupply, Is.EqualTo(draw / 2).Within(0.1));
Assert.That(netBattery.SupplyRampPosition, Is.EqualTo(draw / 2).Within(0.1));
Assert.That(consumer.ReceivedPower, Is.EqualTo(draw).Within(0.1));
});
// now we disconnect the load;
consumer.NetworkLoad.Enabled = false;
server.RunTicks(60);
await server.WaitAssertion(() =>
{
Assert.That(supplier.CurrentSupply, Is.EqualTo(0).Within(0.1));
Assert.That(supplier.SupplyRampPosition, Is.EqualTo(0).Within(0.1));
Assert.That(netBattery.CurrentSupply, Is.EqualTo(0).Within(0.1));
Assert.That(netBattery.SupplyRampPosition, Is.EqualTo(0).Within(0.1));
Assert.That(consumer.ReceivedPower, Is.EqualTo(0).Within(0.1));
});
await pairTracker.CleanReturnAsync();
}
[Test] [Test]
public async Task TestSimpleBatteryChargeDeficit() public async Task TestSimpleBatteryChargeDeficit()
@@ -712,6 +801,101 @@ namespace Content.IntegrationTests.Tests.Power
await pairTracker.CleanReturnAsync(); await pairTracker.CleanReturnAsync();
} }
/// <summary>
/// Checks that if there is insufficient supply to meet demand, generators will run at full power instead of
/// having generators and batteries sharing the load.
/// </summary>
[Test]
public async Task TestSupplyPrioritized()
{
await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings { NoClient = true, ExtraPrototypes = Prototypes });
var server = pairTracker.Pair.Server;
var mapManager = server.ResolveDependency<IMapManager>();
var entityManager = server.ResolveDependency<IEntityManager>();
var gameTiming = server.ResolveDependency<IGameTiming>();
PowerConsumerComponent consumer = default!;
PowerSupplierComponent supplier1 = default!;
PowerSupplierComponent supplier2 = default!;
PowerNetworkBatteryComponent netBattery1 = default!;
PowerNetworkBatteryComponent netBattery2 = default!;
BatteryComponent battery1 = default!;
BatteryComponent battery2 = default!;
await server.WaitAssertion(() =>
{
var map = mapManager.CreateMap();
var grid = mapManager.CreateGrid(map);
// Layout is two generators, two batteries, and one load. As to why two: because previously this test
// would fail ONLY if there were more than two batteries present, because each of them tries to supply
// the unmet load, leading to a double-battery supply attempt and ramping down of power generation from
// supplies.
// Actual layout is Battery Supply, Load, Supply, Battery
// Place cables
for (var i = -2; i <= 2; i++)
{
grid.SetTile(new Vector2i(0, i), new Tile(1));
entityManager.SpawnEntity("CableHV", grid.ToCoordinates(0, i));
}
var batteryEnt1 = entityManager.SpawnEntity("FullBatteryDummy", grid.ToCoordinates(0, 2));
var batteryEnt2 = entityManager.SpawnEntity("FullBatteryDummy", grid.ToCoordinates(0, -2));
var supplyEnt1 = entityManager.SpawnEntity("GeneratorDummy", grid.ToCoordinates(0, 1));
var supplyEnt2 = entityManager.SpawnEntity("GeneratorDummy", grid.ToCoordinates(0, -1));
var consumerEnt = entityManager.SpawnEntity("ConsumerDummy", grid.ToCoordinates(0, 0));
consumer = entityManager.GetComponent<PowerConsumerComponent>(consumerEnt);
supplier1 = entityManager.GetComponent<PowerSupplierComponent>(supplyEnt1);
supplier2 = entityManager.GetComponent<PowerSupplierComponent>(supplyEnt2);
netBattery1 = entityManager.GetComponent<PowerNetworkBatteryComponent>(batteryEnt1);
netBattery2 = entityManager.GetComponent<PowerNetworkBatteryComponent>(batteryEnt2);
battery1 = entityManager.GetComponent<BatteryComponent>(batteryEnt1);
battery2 = entityManager.GetComponent<BatteryComponent>(batteryEnt2);
// Consumer wants 2k, supplies can only provide 1k (500 each). Expectation is that batteries will only provide the necessary remaining 1k (500 each).
// Previously this failed with a 2x 333 w supplies and 2x 666 w batteries.
consumer.DrawRate = 2000;
supplier1.MaxSupply = 500;
supplier2.MaxSupply = 500;
supplier1.SupplyRampTolerance = 500;
supplier2.SupplyRampTolerance = 500;
netBattery1.MaxSupply = 1000;
netBattery2.MaxSupply = 1000;
netBattery1.SupplyRampTolerance = 1000;
netBattery2.SupplyRampTolerance = 1000;
netBattery1.SupplyRampRate = 100_000;
netBattery2.SupplyRampRate = 100_000;
battery1.MaxCharge = 100_000;
battery2.MaxCharge = 100_000;
battery1.CurrentCharge = 100_000;
battery2.CurrentCharge = 100_000;
});
// Run some ticks so everything is stable.
server.RunTicks(60);
await server.WaitAssertion(() =>
{
Assert.That(consumer.ReceivedPower, Is.EqualTo(consumer.DrawRate).Within(0.1));
Assert.That(supplier1.CurrentSupply, Is.EqualTo(supplier1.MaxSupply).Within(0.1));
Assert.That(supplier2.CurrentSupply, Is.EqualTo(supplier2.MaxSupply).Within(0.1));
Assert.That(netBattery1.CurrentSupply, Is.EqualTo(500).Within(0.1));
Assert.That(netBattery2.CurrentSupply, Is.EqualTo(500).Within(0.1));
Assert.That(netBattery2.SupplyRampPosition, Is.EqualTo(500).Within(0.1));
Assert.That(netBattery2.SupplyRampPosition, Is.EqualTo(500).Within(0.1));
});
await pairTracker.CleanReturnAsync();
}
/// <summary> /// <summary>
/// Test that power is distributed proportionally, even through batteries. /// Test that power is distributed proportionally, even through batteries.
/// </summary> /// </summary>
@@ -833,7 +1017,6 @@ namespace Content.IntegrationTests.Tests.Power
netBattery = entityManager.GetComponent<PowerNetworkBatteryComponent>(batteryEnt); netBattery = entityManager.GetComponent<PowerNetworkBatteryComponent>(batteryEnt);
var battery = entityManager.GetComponent<BatteryComponent>(batteryEnt); var battery = entityManager.GetComponent<BatteryComponent>(batteryEnt);
// Consumer needs 1000 W, supplier can only provide 800, battery fills in the remaining 200.
consumer.DrawRate = 1000; consumer.DrawRate = 1000;
supplier.MaxSupply = 1000; supplier.MaxSupply = 1000;
supplier.SupplyRampTolerance = 1000; supplier.SupplyRampTolerance = 1000;

View File

@@ -246,7 +246,7 @@ namespace Content.Server.Electrocution
Node? TryNode(string? id) Node? TryNode(string? id)
{ {
if (id != null && nodeContainer.TryGetNode<Node>(id, out var tryNode) if (id != null && nodeContainer.TryGetNode<Node>(id, out var tryNode)
&& tryNode.NodeGroup is IBasePowerNet { NetworkNode: { LastAvailableSupplySum: >0 } }) && tryNode.NodeGroup is IBasePowerNet { NetworkNode: { LastCombinedSupply: >0 } })
{ {
return tryNode; return tryNode;
} }

View File

@@ -178,7 +178,7 @@ namespace Content.Server.Power.EntitySystems
return ApcExternalPowerState.None; return ApcExternalPowerState.None;
} }
var delta = netBat.CurrentReceiving - netBat.LoadingNetworkDemand; var delta = netBat.CurrentReceiving - netBat.CurrentSupply;
if (!MathHelper.CloseToPercent(delta, 0, 0.1f) && delta < 0) if (!MathHelper.CloseToPercent(delta, 0, 0.1f) && delta < 0)
{ {
return ApcExternalPowerState.Low; return ApcExternalPowerState.Low;

View File

@@ -6,6 +6,7 @@ using Content.Server.Power.Pow3r;
using JetBrains.Annotations; using JetBrains.Annotations;
using Content.Shared.Power; using Content.Shared.Power;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Shared.Threading;
namespace Content.Server.Power.EntitySystems namespace Content.Server.Power.EntitySystems
{ {
@@ -16,6 +17,7 @@ namespace Content.Server.Power.EntitySystems
public sealed class PowerNetSystem : EntitySystem public sealed class PowerNetSystem : EntitySystem
{ {
[Dependency] private readonly AppearanceSystem _appearance = default!; [Dependency] private readonly AppearanceSystem _appearance = default!;
[Dependency] private readonly IParallelManager _parMan = default!;
private readonly PowerState _powerState = new(); private readonly PowerState _powerState = new();
private readonly HashSet<PowerNet> _powerNetReconnectQueue = new(); private readonly HashSet<PowerNet> _powerNetReconnectQueue = new();
@@ -110,31 +112,37 @@ namespace Content.Server.Power.EntitySystems
public void InitPowerNet(PowerNet powerNet) public void InitPowerNet(PowerNet powerNet)
{ {
AllocNetwork(powerNet.NetworkNode); AllocNetwork(powerNet.NetworkNode);
_powerState.GroupedNets = null;
} }
public void DestroyPowerNet(PowerNet powerNet) public void DestroyPowerNet(PowerNet powerNet)
{ {
_powerState.Networks.Free(powerNet.NetworkNode.Id); _powerState.Networks.Free(powerNet.NetworkNode.Id);
_powerState.GroupedNets = null;
} }
public void QueueReconnectPowerNet(PowerNet powerNet) public void QueueReconnectPowerNet(PowerNet powerNet)
{ {
_powerNetReconnectQueue.Add(powerNet); _powerNetReconnectQueue.Add(powerNet);
_powerState.GroupedNets = null;
} }
public void InitApcNet(ApcNet apcNet) public void InitApcNet(ApcNet apcNet)
{ {
AllocNetwork(apcNet.NetworkNode); AllocNetwork(apcNet.NetworkNode);
_powerState.GroupedNets = null;
} }
public void DestroyApcNet(ApcNet apcNet) public void DestroyApcNet(ApcNet apcNet)
{ {
_powerState.Networks.Free(apcNet.NetworkNode.Id); _powerState.Networks.Free(apcNet.NetworkNode.Id);
_powerState.GroupedNets = null;
} }
public void QueueReconnectApcNet(ApcNet apcNet) public void QueueReconnectApcNet(ApcNet apcNet)
{ {
_apcNetReconnectQueue.Add(apcNet); _apcNetReconnectQueue.Add(apcNet);
_powerState.GroupedNets = null;
} }
public PowerStatistics GetStatistics() public PowerStatistics GetStatistics()
@@ -159,7 +167,7 @@ namespace Content.Server.Power.EntitySystems
// A full battery will still have the same max draw rate, // A full battery will still have the same max draw rate,
// but will likely have deliberately limited current draw rate. // but will likely have deliberately limited current draw rate.
float consumptionW = network.Loads.Sum(s => _powerState.Loads[s].DesiredPower); float consumptionW = network.Loads.Sum(s => _powerState.Loads[s].DesiredPower);
consumptionW += network.BatteriesCharging.Sum(s => _powerState.Batteries[s].CurrentReceiving); consumptionW += network.BatteryLoads.Sum(s => _powerState.Batteries[s].CurrentReceiving);
// This is interesting because LastMaxSupplySum seems to match LastAvailableSupplySum for some reason. // This is interesting because LastMaxSupplySum seems to match LastAvailableSupplySum for some reason.
// I suspect it's accounting for current supply rather than theoretical supply. // I suspect it's accounting for current supply rather than theoretical supply.
@@ -172,7 +180,7 @@ namespace Content.Server.Power.EntitySystems
float supplyBatteriesW = 0.0f; float supplyBatteriesW = 0.0f;
float storageCurrentJ = 0.0f; float storageCurrentJ = 0.0f;
float storageMaxJ = 0.0f; float storageMaxJ = 0.0f;
foreach (var discharger in network.BatteriesDischarging) foreach (var discharger in network.BatterySupplies)
{ {
var nb = _powerState.Batteries[discharger]; var nb = _powerState.Batteries[discharger];
supplyBatteriesW += nb.CurrentSupply; supplyBatteriesW += nb.CurrentSupply;
@@ -183,7 +191,7 @@ namespace Content.Server.Power.EntitySystems
// And charging // And charging
float outStorageCurrentJ = 0.0f; float outStorageCurrentJ = 0.0f;
float outStorageMaxJ = 0.0f; float outStorageMaxJ = 0.0f;
foreach (var charger in network.BatteriesCharging) foreach (var charger in network.BatteryLoads)
{ {
var nb = _powerState.Batteries[charger]; var nb = _powerState.Batteries[charger];
outStorageCurrentJ += nb.CurrentStorage; outStorageCurrentJ += nb.CurrentStorage;
@@ -191,7 +199,7 @@ namespace Content.Server.Power.EntitySystems
} }
return new() return new()
{ {
SupplyCurrent = network.LastMaxSupplySum, SupplyCurrent = network.LastCombinedMaxSupply,
SupplyBatteries = supplyBatteriesW, SupplyBatteries = supplyBatteriesW,
SupplyTheoretical = maxSupplyW, SupplyTheoretical = maxSupplyW,
Consumption = consumptionW, Consumption = consumptionW,
@@ -212,7 +220,7 @@ namespace Content.Server.Power.EntitySystems
RaiseLocalEvent(new NetworkBatteryPreSync()); RaiseLocalEvent(new NetworkBatteryPreSync());
// Run power solver. // Run power solver.
_solver.Tick(frameTime, _powerState); _solver.Tick(frameTime, _powerState, _parMan.ParallelProcessCount);
// Synchronize batteries, the other way around. // Synchronize batteries, the other way around.
RaiseLocalEvent(new NetworkBatteryPostSync()); RaiseLocalEvent(new NetworkBatteryPostSync());
@@ -332,8 +340,8 @@ namespace Content.Server.Power.EntitySystems
var netNode = net.NetworkNode; var netNode = net.NetworkNode;
netNode.Loads.Clear(); netNode.Loads.Clear();
netNode.BatteriesDischarging.Clear(); netNode.BatterySupplies.Clear();
netNode.BatteriesCharging.Clear(); netNode.BatteryLoads.Clear();
netNode.Supplies.Clear(); netNode.Supplies.Clear();
foreach (var provider in net.Providers) foreach (var provider in net.Providers)
@@ -356,7 +364,7 @@ namespace Content.Server.Power.EntitySystems
foreach (var apc in net.Apcs) foreach (var apc in net.Apcs)
{ {
var netBattery = batteryQuery.GetComponent(apc.Owner); var netBattery = batteryQuery.GetComponent(apc.Owner);
netNode.BatteriesDischarging.Add(netBattery.NetworkBattery.Id); netNode.BatterySupplies.Add(netBattery.NetworkBattery.Id);
netBattery.NetworkBattery.LinkedNetworkDischarging = netNode.Id; netBattery.NetworkBattery.LinkedNetworkDischarging = netNode.Id;
} }
} }
@@ -367,8 +375,8 @@ namespace Content.Server.Power.EntitySystems
netNode.Loads.Clear(); netNode.Loads.Clear();
netNode.Supplies.Clear(); netNode.Supplies.Clear();
netNode.BatteriesCharging.Clear(); netNode.BatteryLoads.Clear();
netNode.BatteriesDischarging.Clear(); netNode.BatterySupplies.Clear();
foreach (var consumer in net.Consumers) foreach (var consumer in net.Consumers)
{ {
@@ -387,14 +395,14 @@ namespace Content.Server.Power.EntitySystems
foreach (var charger in net.Chargers) foreach (var charger in net.Chargers)
{ {
var battery = batteryQuery.GetComponent(charger.Owner); var battery = batteryQuery.GetComponent(charger.Owner);
netNode.BatteriesCharging.Add(battery.NetworkBattery.Id); netNode.BatteryLoads.Add(battery.NetworkBattery.Id);
battery.NetworkBattery.LinkedNetworkCharging = netNode.Id; battery.NetworkBattery.LinkedNetworkCharging = netNode.Id;
} }
foreach (var discharger in net.Dischargers) foreach (var discharger in net.Dischargers)
{ {
var battery = batteryQuery.GetComponent(discharger.Owner); var battery = batteryQuery.GetComponent(discharger.Owner);
netNode.BatteriesDischarging.Add(battery.NetworkBattery.Id); netNode.BatterySupplies.Add(battery.NetworkBattery.Id);
battery.NetworkBattery.LinkedNetworkDischarging = netNode.Id; battery.NetworkBattery.LinkedNetworkDischarging = netNode.Id;
} }
} }

View File

@@ -1,10 +1,11 @@
using System.Linq;
using Content.Server.NodeContainer.NodeGroups; using Content.Server.NodeContainer.NodeGroups;
using Content.Server.NodeContainer.Nodes; using Content.Server.NodeContainer.Nodes;
using Content.Server.Power.Components; using Content.Server.Power.Components;
using Content.Server.Power.EntitySystems; using Content.Server.Power.EntitySystems;
using Content.Server.Power.Pow3r; using Content.Server.Power.Pow3r;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.Utility;
using System.Linq;
namespace Content.Server.Power.NodeGroups namespace Content.Server.Power.NodeGroups
{ {
@@ -59,6 +60,7 @@ namespace Content.Server.Power.NodeGroups
public void AddSupplier(PowerSupplierComponent supplier) public void AddSupplier(PowerSupplierComponent supplier)
{ {
DebugTools.Assert(supplier.NetworkSupply.LinkedNetwork == default);
supplier.NetworkSupply.LinkedNetwork = default; supplier.NetworkSupply.LinkedNetwork = default;
Suppliers.Add(supplier); Suppliers.Add(supplier);
_powerNetSystem?.QueueReconnectPowerNet(this); _powerNetSystem?.QueueReconnectPowerNet(this);
@@ -66,6 +68,7 @@ namespace Content.Server.Power.NodeGroups
public void RemoveSupplier(PowerSupplierComponent supplier) public void RemoveSupplier(PowerSupplierComponent supplier)
{ {
DebugTools.Assert(supplier.NetworkSupply.LinkedNetwork == NetworkNode.Id);
supplier.NetworkSupply.LinkedNetwork = default; supplier.NetworkSupply.LinkedNetwork = default;
Suppliers.Remove(supplier); Suppliers.Remove(supplier);
_powerNetSystem?.QueueReconnectPowerNet(this); _powerNetSystem?.QueueReconnectPowerNet(this);
@@ -73,6 +76,7 @@ namespace Content.Server.Power.NodeGroups
public void AddConsumer(PowerConsumerComponent consumer) public void AddConsumer(PowerConsumerComponent consumer)
{ {
DebugTools.Assert(consumer.NetworkLoad.LinkedNetwork == default);
consumer.NetworkLoad.LinkedNetwork = default; consumer.NetworkLoad.LinkedNetwork = default;
Consumers.Add(consumer); Consumers.Add(consumer);
_powerNetSystem?.QueueReconnectPowerNet(this); _powerNetSystem?.QueueReconnectPowerNet(this);
@@ -80,6 +84,7 @@ namespace Content.Server.Power.NodeGroups
public void RemoveConsumer(PowerConsumerComponent consumer) public void RemoveConsumer(PowerConsumerComponent consumer)
{ {
DebugTools.Assert(consumer.NetworkLoad.LinkedNetwork == NetworkNode.Id);
consumer.NetworkLoad.LinkedNetwork = default; consumer.NetworkLoad.LinkedNetwork = default;
Consumers.Remove(consumer); Consumers.Remove(consumer);
_powerNetSystem?.QueueReconnectPowerNet(this); _powerNetSystem?.QueueReconnectPowerNet(this);
@@ -88,7 +93,8 @@ namespace Content.Server.Power.NodeGroups
public void AddDischarger(BatteryDischargerComponent discharger) public void AddDischarger(BatteryDischargerComponent discharger)
{ {
var battery = IoCManager.Resolve<IEntityManager>().GetComponent<PowerNetworkBatteryComponent>(discharger.Owner); var battery = IoCManager.Resolve<IEntityManager>().GetComponent<PowerNetworkBatteryComponent>(discharger.Owner);
battery.NetworkBattery.LinkedNetworkCharging = default; DebugTools.Assert(battery.NetworkBattery.LinkedNetworkDischarging == default);
battery.NetworkBattery.LinkedNetworkDischarging = default;
Dischargers.Add(discharger); Dischargers.Add(discharger);
_powerNetSystem?.QueueReconnectPowerNet(this); _powerNetSystem?.QueueReconnectPowerNet(this);
} }
@@ -97,7 +103,10 @@ namespace Content.Server.Power.NodeGroups
{ {
// Can be missing if the entity is being deleted, not a big deal. // Can be missing if the entity is being deleted, not a big deal.
if (IoCManager.Resolve<IEntityManager>().TryGetComponent(discharger.Owner, out PowerNetworkBatteryComponent? battery)) if (IoCManager.Resolve<IEntityManager>().TryGetComponent(discharger.Owner, out PowerNetworkBatteryComponent? battery))
battery.NetworkBattery.LinkedNetworkCharging = default; {
DebugTools.Assert(battery.NetworkBattery.LinkedNetworkDischarging == NetworkNode.Id);
battery.NetworkBattery.LinkedNetworkDischarging = default;
}
Dischargers.Remove(discharger); Dischargers.Remove(discharger);
_powerNetSystem?.QueueReconnectPowerNet(this); _powerNetSystem?.QueueReconnectPowerNet(this);
@@ -106,6 +115,7 @@ namespace Content.Server.Power.NodeGroups
public void AddCharger(BatteryChargerComponent charger) public void AddCharger(BatteryChargerComponent charger)
{ {
var battery = IoCManager.Resolve<IEntityManager>().GetComponent<PowerNetworkBatteryComponent>(charger.Owner); var battery = IoCManager.Resolve<IEntityManager>().GetComponent<PowerNetworkBatteryComponent>(charger.Owner);
DebugTools.Assert(battery.NetworkBattery.LinkedNetworkCharging == default);
battery.NetworkBattery.LinkedNetworkCharging = default; battery.NetworkBattery.LinkedNetworkCharging = default;
Chargers.Add(charger); Chargers.Add(charger);
_powerNetSystem?.QueueReconnectPowerNet(this); _powerNetSystem?.QueueReconnectPowerNet(this);
@@ -115,7 +125,10 @@ namespace Content.Server.Power.NodeGroups
{ {
// Can be missing if the entity is being deleted, not a big deal. // Can be missing if the entity is being deleted, not a big deal.
if (IoCManager.Resolve<IEntityManager>().TryGetComponent(charger.Owner, out PowerNetworkBatteryComponent? battery)) if (IoCManager.Resolve<IEntityManager>().TryGetComponent(charger.Owner, out PowerNetworkBatteryComponent? battery))
{
DebugTools.Assert(battery.NetworkBattery.LinkedNetworkCharging == NetworkNode.Id);
battery.NetworkBattery.LinkedNetworkCharging = default; battery.NetworkBattery.LinkedNetworkCharging = default;
}
Chargers.Remove(charger); Chargers.Remove(charger);
_powerNetSystem?.QueueReconnectPowerNet(this); _powerNetSystem?.QueueReconnectPowerNet(this);

View File

@@ -1,4 +1,8 @@
using Robust.Shared.Utility; using Pidgin;
using Robust.Shared.Utility;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using static Content.Server.Power.Pow3r.PowerState; using static Content.Server.Power.Pow3r.PowerState;
namespace Content.Server.Power.Pow3r namespace Content.Server.Power.Pow3r
@@ -17,11 +21,39 @@ namespace Content.Server.Power.Pow3r
} }
} }
private readonly PriorityQueue<int, Network> _sortBuffer = new(new HeightComparer()); public void Tick(float frameTime, PowerState state, int parallel)
{
public void Tick(float frameTime, PowerState state) ClearLoadsAndSupplies(state);
state.GroupedNets ??= GroupByNetworkDepth(state);
DebugTools.Assert(state.GroupedNets.Select(x => x.Count).Sum() == state.Networks.Count);
// Each network height layer can be run in parallel without issues.
var opts = new ParallelOptions { MaxDegreeOfParallelism = parallel };
foreach (var group in state.GroupedNets)
{
// Note that many net-layers only have a handful of networks.
// E.g., the number of nets from lowest to heights for box and saltern are:
// Saltern: 1477, 11, 2, 2, 3.
// Box: 3308, 20, 1, 5.
//
// I have NFI what the overhead for a Parallel.ForEach is, and how it compares to computing differently
// sized nets. Basic benchmarking shows that this is better, but maybe the highest-tier nets should just
// be run sequentially? But then again, maybe they are 2-3 very BIG networks at the top? So maybe:
//
// TODO make GroupByNetworkDepth evaluate the TOTAL size of each layer (i.e. loads + chargers +
// suppliers + discharger) Then decide based on total layer size whether its worth parallelizing that
// layer?
Parallel.ForEach(group, opts, net => UpdateNetwork(net, state, frameTime));
}
ClearBatteries(state);
PowerSolverShared.UpdateRampPositions(frameTime, state);
}
private void ClearLoadsAndSupplies(PowerState state)
{ {
// Clear loads and supplies.
foreach (var load in state.Loads.Values) foreach (var load in state.Loads.Values)
{ {
if (load.Paused) if (load.Paused)
@@ -38,34 +70,16 @@ namespace Content.Server.Power.Pow3r
supply.CurrentSupply = 0; supply.CurrentSupply = 0;
supply.SupplyRampTarget = 0; supply.SupplyRampTarget = 0;
} }
// Run a pass to estimate network tree graph height.
// This is so that we can run networks before their children,
// to avoid draining batteries for a tick if their passing-supply gets cut off.
// It's not a big loss if this doesn't work (it won't, in some scenarios), but it's a nice-to-have.
foreach (var network in state.Networks.Values)
{
network.HeightTouched = false;
network.Height = -1;
} }
foreach (var network in state.Networks.Values) private void UpdateNetwork(Network network, PowerState state, float frameTime)
{ {
if (network.BatteriesDischarging.Count != 0) // TODO Look at SIMD.
continue; // a lot of this is performing very basic math on arrays of data objects like batteries
// this really shouldn't be hard to do.
// except for maybe the paused/enabled guff. If its mostly false, I guess they could just be 0 multipliers?
EstimateNetworkDepth(state, network); // Add up demand from loads.
}
foreach (var network in state.Networks.Values)
{
_sortBuffer.Enqueue(network.Height, network);
}
// Go over every network.
while (_sortBuffer.TryDequeue(out _, out var network))
{
// Add up demand in network.
var demand = 0f; var demand = 0f;
foreach (var loadId in network.Loads) foreach (var loadId in network.Loads)
{ {
@@ -82,8 +96,8 @@ namespace Content.Server.Power.Pow3r
// This would mean that charge rate would have no impact on throughput rate like it does currently. // This would mean that charge rate would have no impact on throughput rate like it does currently.
// Would require a second pass over the network, or something. Not sure. // Would require a second pass over the network, or something. Not sure.
// Loading batteries. // Add demand from batteries
foreach (var batteryId in network.BatteriesCharging) foreach (var batteryId in network.BatteryLoads)
{ {
var battery = state.Batteries[batteryId]; var battery = state.Batteries[batteryId];
if (!battery.Enabled || !battery.CanCharge || battery.Paused) if (!battery.Enabled || !battery.CanCharge || battery.Paused)
@@ -95,19 +109,16 @@ namespace Content.Server.Power.Pow3r
var chargeRate = battery.MaxChargeRate + battery.LoadingNetworkDemand / battery.Efficiency; var chargeRate = battery.MaxChargeRate + battery.LoadingNetworkDemand / battery.Efficiency;
var batDemand = Math.Min(chargeRate, scaledSpace); battery.DesiredPower = Math.Min(chargeRate, scaledSpace);
DebugTools.Assert(battery.DesiredPower >= 0);
DebugTools.Assert(batDemand >= 0); demand += battery.DesiredPower;
battery.DesiredPower = batDemand;
demand += batDemand;
} }
DebugTools.Assert(demand >= 0); DebugTools.Assert(demand >= 0);
// Add up supply in network. // Add up supply in network.
var availableSupplySum = 0f; var totalSupply = 0f;
var maxSupplySum = 0f; var totalMaxSupply = 0f;
foreach (var supplyId in network.Supplies) foreach (var supplyId in network.Supplies)
{ {
var supply = state.Supplies[supplyId]; var supply = state.Supplies[supplyId];
@@ -120,24 +131,26 @@ namespace Content.Server.Power.Pow3r
DebugTools.Assert(effectiveSupply >= 0); DebugTools.Assert(effectiveSupply >= 0);
DebugTools.Assert(supply.MaxSupply >= 0); DebugTools.Assert(supply.MaxSupply >= 0);
supply.EffectiveMaxSupply = effectiveSupply; supply.AvailableSupply = effectiveSupply;
availableSupplySum += effectiveSupply; totalSupply += effectiveSupply;
maxSupplySum += supply.MaxSupply; totalMaxSupply += supply.MaxSupply;
} }
var unmet = Math.Max(0, demand - availableSupplySum); var unmet = Math.Max(0, demand - totalSupply);
DebugTools.Assert(totalSupply >= 0);
DebugTools.Assert(totalMaxSupply >= 0);
DebugTools.Assert(availableSupplySum >= 0); // Supplying batteries. Batteries need to go after local supplies so that local supplies are prioritized.
DebugTools.Assert(maxSupplySum >= 0); // Also, it makes demand-pulling of batteries. Because all batteries will desire the unmet demand of their
// loading network, there will be a "rush" of input current when a network powers on, before power
// stabilizes in the network. This is fine.
// Supplying batteries. var totalBatterySupply = 0f;
// Batteries need to go after local supplies so that local supplies are prioritized. var totalMaxBatterySupply = 0f;
// Also, it makes demand-pulling of batteries if (unmet > 0)
// Because all batteries will will desire the unmet demand of their loading network, {
// there will be a "rush" of input current when a network powers on, // determine supply available from batteries
// before power stabilizes in the network. foreach (var batteryId in network.BatterySupplies)
// This is fine.
foreach (var batteryId in network.BatteriesDischarging)
{ {
var battery = state.Batteries[batteryId]; var battery = state.Batteries[batteryId];
if (!battery.Enabled || !battery.CanDischarge || battery.Paused) if (!battery.Enabled || !battery.CanDischarge || battery.Paused)
@@ -147,28 +160,25 @@ namespace Content.Server.Power.Pow3r
var supplyCap = Math.Min(battery.MaxSupply, var supplyCap = Math.Min(battery.MaxSupply,
battery.SupplyRampPosition + battery.SupplyRampTolerance); battery.SupplyRampPosition + battery.SupplyRampTolerance);
var supplyAndPassthrough = supplyCap + battery.CurrentReceiving * battery.Efficiency; var supplyAndPassthrough = supplyCap + battery.CurrentReceiving * battery.Efficiency;
var tempSupply = Math.Min(scaledSpace, supplyAndPassthrough);
// Clamp final supply to the unmet demand, so that batteries refrain from taking power away from supplies.
var clampedSupply = Math.Min(unmet, tempSupply);
DebugTools.Assert(clampedSupply >= 0);
battery.TempMaxSupply = clampedSupply;
availableSupplySum += clampedSupply;
// TODO: Calculate this properly.
maxSupplySum += clampedSupply;
battery.AvailableSupply = Math.Min(scaledSpace, supplyAndPassthrough);
battery.LoadingNetworkDemand = unmet; battery.LoadingNetworkDemand = unmet;
battery.LoadingDemandMarked = true;
battery.MaxEffectiveSupply = Math.Min(battery.CurrentStorage / frameTime, battery.MaxSupply + battery.CurrentReceiving * battery.Efficiency);
totalBatterySupply += battery.AvailableSupply;
totalMaxBatterySupply += battery.MaxEffectiveSupply;
}
} }
network.LastAvailableSupplySum = availableSupplySum; network.LastCombinedSupply = totalSupply + totalBatterySupply;
network.LastMaxSupplySum = maxSupplySum; network.LastCombinedMaxSupply = totalMaxSupply + totalMaxBatterySupply;
var met = Math.Min(demand, availableSupplySum); var met = Math.Min(demand, network.LastCombinedSupply);
if (met == 0)
return;
var supplyRatio = met / demand;
if (met != 0)
{
// Distribute supply to loads. // Distribute supply to loads.
foreach (var loadId in network.Loads) foreach (var loadId in network.Loads)
{ {
@@ -176,74 +186,81 @@ namespace Content.Server.Power.Pow3r
if (!load.Enabled || load.DesiredPower == 0 || load.Paused) if (!load.Enabled || load.DesiredPower == 0 || load.Paused)
continue; continue;
var ratio = load.DesiredPower / demand; load.ReceivingPower = load.DesiredPower * supplyRatio;
load.ReceivingPower = ratio * met;
} }
// Loading batteries // Distribute supply to batteries
foreach (var batteryId in network.BatteriesCharging) foreach (var batteryId in network.BatteryLoads)
{ {
var battery = state.Batteries[batteryId]; var battery = state.Batteries[batteryId];
if (!battery.Enabled || battery.DesiredPower == 0 || battery.Paused) if (!battery.Enabled || battery.DesiredPower == 0 || battery.Paused)
continue; continue;
var ratio = battery.DesiredPower / demand;
battery.CurrentReceiving = ratio * met;
var receivedPower = frameTime * battery.CurrentReceiving;
receivedPower *= battery.Efficiency;
battery.CurrentStorage = Math.Min(
battery.Capacity,
battery.CurrentStorage + receivedPower);
battery.LoadingMarked = true; battery.LoadingMarked = true;
battery.CurrentReceiving = battery.DesiredPower * supplyRatio;
battery.CurrentStorage += frameTime * battery.CurrentReceiving * battery.Efficiency;
DebugTools.Assert(battery.CurrentStorage <= battery.Capacity || MathHelper.CloseTo(battery.CurrentStorage, battery.Capacity));
} }
// Load to supplies // Target output capacity for supplies
var metSupply = Math.Min(demand, totalSupply);
if (metSupply > 0)
{
var relativeSupplyOutput = metSupply / totalSupply;
var targetRelativeSupplyOutput = Math.Min(demand, totalMaxSupply) / totalMaxSupply;
// Apply load to supplies
foreach (var supplyId in network.Supplies) foreach (var supplyId in network.Supplies)
{ {
var supply = state.Supplies[supplyId]; var supply = state.Supplies[supplyId];
if (!supply.Enabled || supply.EffectiveMaxSupply == 0 || supply.Paused) if (!supply.Enabled || supply.Paused)
continue; continue;
var ratio = supply.EffectiveMaxSupply / availableSupplySum; supply.CurrentSupply = supply.AvailableSupply * relativeSupplyOutput;
supply.CurrentSupply = ratio * met;
if (supply.MaxSupply != 0) // Supply ramp assumes all supplies ramp at the same rate. If some generators spin up very slowly, in
{ // principle the fast supplies should try over-shoot until they can settle back down. E.g., all supplies
var maxSupplyRatio = supply.MaxSupply / maxSupplySum; // need to reach 50% capacity, but it takes the nuclear reactor 1 hour to reach that, then our lil coal
// furnaces should run at 100% for a while. But I guess this is good enough for now.
supply.SupplyRampTarget = maxSupplyRatio * demand; supply.SupplyRampTarget = supply.MaxSupply * targetRelativeSupplyOutput;
}
else
{
supply.SupplyRampTarget = 0;
} }
} }
// Supplying batteries if (unmet <= 0 || totalBatterySupply <= 0)
foreach (var batteryId in network.BatteriesDischarging) return;
// Target output capacity for batteries
var relativeBatteryOutput = Math.Min(unmet, totalBatterySupply) / totalBatterySupply;
var relativeTargetBatteryOutput = Math.Min(unmet, totalMaxBatterySupply) / totalMaxBatterySupply;
// Apply load to supplying batteries
foreach (var batteryId in network.BatterySupplies)
{ {
var battery = state.Batteries[batteryId]; var battery = state.Batteries[batteryId];
if (!battery.Enabled || battery.TempMaxSupply == 0 || battery.Paused) if (!battery.Enabled || battery.Paused)
continue; continue;
var ratio = battery.TempMaxSupply / availableSupplySum;
battery.CurrentSupply = ratio * met;
battery.CurrentStorage = Math.Max(
0,
battery.CurrentStorage - frameTime * battery.CurrentSupply);
battery.SupplyRampTarget = battery.CurrentSupply - battery.CurrentReceiving * battery.Efficiency;
/*var maxSupplyRatio = supply.MaxSupply / maxSupplySum;
supply.SupplyRampTarget = maxSupplyRatio * demand;*/
battery.SupplyingMarked = true; battery.SupplyingMarked = true;
} battery.CurrentSupply = battery.AvailableSupply * relativeBatteryOutput;
// Note that because available supply is always greater than or equal to the current ramp target, if you
// have multiple batteries running at less than 100% output, then batteries with greater ramp tolerances
// will contribute a larger relative fraction of output power. This is because while they will both ramp
// to the same relative maximum output, the larger tolerance will mean that one will have a larger
// available supply. IMO this is undesirable, but I can't think of an easy fix ATM.
battery.CurrentStorage -= frameTime * battery.CurrentSupply;
DebugTools.Assert(battery.CurrentStorage >= 0 || MathHelper.CloseTo(battery.CurrentStorage, 0));
battery.SupplyRampTarget = battery.MaxEffectiveSupply * relativeTargetBatteryOutput - battery.CurrentReceiving * battery.Efficiency;
DebugTools.Assert(battery.SupplyRampTarget + battery.CurrentReceiving * battery.Efficiency <= battery.LoadingNetworkDemand
|| MathHelper.CloseTo(battery.SupplyRampTarget + battery.CurrentReceiving * battery.Efficiency, battery.LoadingNetworkDemand, 0.01));
} }
} }
private void ClearBatteries(PowerState state)
{
// Clear supplying/loading on any batteries that haven't been marked by usage. // Clear supplying/loading on any batteries that haven't been marked by usage.
// Because we need this data while processing ramp-pegging, we can't clear it at the start. // Because we need this data while processing ramp-pegging, we can't clear it at the start.
foreach (var battery in state.Batteries.Values) foreach (var battery in state.Batteries.Values)
@@ -252,48 +269,69 @@ namespace Content.Server.Power.Pow3r
continue; continue;
if (!battery.SupplyingMarked) if (!battery.SupplyingMarked)
{
battery.CurrentSupply = 0; battery.CurrentSupply = 0;
battery.SupplyRampTarget = 0;
battery.LoadingNetworkDemand = 0;
}
if (!battery.LoadingMarked) if (!battery.LoadingMarked)
{
battery.CurrentReceiving = 0; battery.CurrentReceiving = 0;
}
if (!battery.LoadingDemandMarked)
battery.LoadingNetworkDemand = 0;
battery.SupplyingMarked = false; battery.SupplyingMarked = false;
battery.LoadingMarked = false; battery.LoadingMarked = false;
battery.LoadingDemandMarked = false; }
} }
PowerSolverShared.UpdateRampPositions(frameTime, state); private List<List<Network>> GroupByNetworkDepth(PowerState state)
}
private static void EstimateNetworkDepth(PowerState state, Network network)
{ {
network.HeightTouched = true; List<List<Network>> groupedNetworks = new();
foreach (var network in state.Networks.Values)
if (network.BatteriesCharging.Count == 0)
{ {
network.Height = 1; network.Height = -1;
return;
} }
var max = 0; foreach (var network in state.Networks.Values)
foreach (var batteryId in network.BatteriesCharging) {
if (network.Height == -1)
RecursivelyEstimateNetworkDepth(state, network, groupedNetworks);
}
return groupedNetworks;
}
private static void RecursivelyEstimateNetworkDepth(PowerState state, Network network, List<List<Network>> groupedNetworks)
{
network.Height = -2;
var height = -1;
foreach (var batteryId in network.BatteryLoads)
{ {
var battery = state.Batteries[batteryId]; var battery = state.Batteries[batteryId];
if (battery.LinkedNetworkDischarging == default) if (battery.LinkedNetworkDischarging == default || battery.LinkedNetworkDischarging == network.Id)
continue; continue;
var subNet = state.Networks[battery.LinkedNetworkDischarging]; var subNet = state.Networks[battery.LinkedNetworkDischarging];
if (!subNet.HeightTouched) if (subNet.Height == -1)
EstimateNetworkDepth(state, subNet); RecursivelyEstimateNetworkDepth(state, subNet, groupedNetworks);
else if (subNet.Height == -2)
max = Math.Max(subNet.Height, max); {
// this network is currently computing its own height (we encountered a loop).
continue;
} }
network.Height = 1 + max; height = Math.Max(subNet.Height, height);
}
network.Height = 1 + height;
if (network.Height >= groupedNetworks.Count)
groupedNetworks.Add(new() { network });
else
groupedNetworks[network.Height].Add(network);
} }
} }
} }

View File

@@ -1,7 +1,7 @@
namespace Content.Server.Power.Pow3r namespace Content.Server.Power.Pow3r
{ {
public interface IPowerSolver public interface IPowerSolver
{ {
void Tick(float frameTime, PowerState state); void Tick(float frameTime, PowerState state, int parallel);
} }
} }

View File

@@ -1,8 +1,8 @@
namespace Content.Server.Power.Pow3r namespace Content.Server.Power.Pow3r
{ {
public sealed class NoOpSolver : IPowerSolver public sealed class NoOpSolver : IPowerSolver
{ {
public void Tick(float frameTime, PowerState state) public void Tick(float frameTime, PowerState state, int parallel)
{ {
// Literally nothing. // Literally nothing.
} }

View File

@@ -1,10 +1,11 @@
using System.Collections; using System.Collections;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using Robust.Shared.Utility; using Robust.Shared.Utility;
using static Content.Server.Power.Pow3r.PowerState;
namespace Content.Server.Power.Pow3r namespace Content.Server.Power.Pow3r
{ {
@@ -20,6 +21,7 @@ namespace Content.Server.Power.Pow3r
public GenIdStorage<Network> Networks = new(); public GenIdStorage<Network> Networks = new();
public GenIdStorage<Load> Loads = new(); public GenIdStorage<Load> Loads = new();
public GenIdStorage<Battery> Batteries = new(); public GenIdStorage<Battery> Batteries = new();
public List<List<Network>>? GroupedNets;
public readonly struct NodeId : IEquatable<NodeId> public readonly struct NodeId : IEquatable<NodeId>
{ {
@@ -168,6 +170,10 @@ namespace Content.Server.Power.Pow3r
storage.Count = cache.Length; storage.Count = cache.Length;
storage._nextFree = nextFree; storage._nextFree = nextFree;
// I think there is some issue with Pow3er's Save & Load to json leading to it constructing invalid GenIdStorages from json?
// If you get this error, clear out your data.json
DebugTools.Assert(storage.Values.Count() == storage.Count);
return storage; return storage;
} }
@@ -352,20 +358,29 @@ namespace Content.Server.Power.Pow3r
// == Runtime parameters == // == Runtime parameters ==
// Actual power supplied last network update. /// <summary>
/// Actual power supplied last network update.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)] public float CurrentSupply; [ViewVariables(VVAccess.ReadWrite)] public float CurrentSupply;
// The amount of power we WANT to be supplying to match grid load. /// <summary>
/// The amount of power we WANT to be supplying to match grid load.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)] [JsonIgnore] [ViewVariables(VVAccess.ReadWrite)] [JsonIgnore]
public float SupplyRampTarget; public float SupplyRampTarget;
// Position of the supply ramp. /// <summary>
/// Position of the supply ramp.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)] public float SupplyRampPosition; [ViewVariables(VVAccess.ReadWrite)] public float SupplyRampPosition;
[ViewVariables] [JsonIgnore] public NodeId LinkedNetwork; [ViewVariables] [JsonIgnore] public NodeId LinkedNetwork;
// In-tick max supply thanks to ramp. Used during calculations. /// <summary>
[JsonIgnore] public float EffectiveMaxSupply; /// Supply available during a tick. The actual current supply will be less than or equal to this. Used
/// during calculations.
/// </summary>
[JsonIgnore] public float AvailableSupply;
} }
public sealed class Load public sealed class Load
@@ -396,7 +411,15 @@ namespace Content.Server.Power.Pow3r
[ViewVariables(VVAccess.ReadWrite)] public float MaxChargeRate; [ViewVariables(VVAccess.ReadWrite)] public float MaxChargeRate;
[ViewVariables(VVAccess.ReadWrite)] public float MaxThroughput; // 0 = infinite cuz imgui [ViewVariables(VVAccess.ReadWrite)] public float MaxThroughput; // 0 = infinite cuz imgui
[ViewVariables(VVAccess.ReadWrite)] public float MaxSupply; [ViewVariables(VVAccess.ReadWrite)] public float MaxSupply;
/// <summary>
/// The batteries supply ramp tolerance. This is an always available supply added to the ramped supply.
/// </summary>
/// <remarks>
/// Note that this MUST BE GREATER THAN ZERO, otherwise the current battery ramping calculation will not work.
/// </remarks>
[ViewVariables(VVAccess.ReadWrite)] public float SupplyRampTolerance = 5000; [ViewVariables(VVAccess.ReadWrite)] public float SupplyRampTolerance = 5000;
[ViewVariables(VVAccess.ReadWrite)] public float SupplyRampRate = 5000; [ViewVariables(VVAccess.ReadWrite)] public float SupplyRampRate = 5000;
[ViewVariables(VVAccess.ReadWrite)] public float Efficiency = 1; [ViewVariables(VVAccess.ReadWrite)] public float Efficiency = 1;
@@ -413,11 +436,11 @@ namespace Content.Server.Power.Pow3r
[ViewVariables(VVAccess.ReadWrite)] [JsonIgnore] [ViewVariables(VVAccess.ReadWrite)] [JsonIgnore]
public bool LoadingMarked; public bool LoadingMarked;
/// <summary>
/// Amount of supply that the battery can provider this tick.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)] [JsonIgnore] [ViewVariables(VVAccess.ReadWrite)] [JsonIgnore]
public bool LoadingDemandMarked; public float AvailableSupply;
[ViewVariables(VVAccess.ReadWrite)] [JsonIgnore]
public float TempMaxSupply;
[ViewVariables(VVAccess.ReadWrite)] [JsonIgnore] [ViewVariables(VVAccess.ReadWrite)] [JsonIgnore]
public float DesiredPower; public float DesiredPower;
@@ -430,6 +453,13 @@ namespace Content.Server.Power.Pow3r
[ViewVariables(VVAccess.ReadWrite)] [JsonIgnore] [ViewVariables(VVAccess.ReadWrite)] [JsonIgnore]
public NodeId LinkedNetworkDischarging; public NodeId LinkedNetworkDischarging;
/// <summary>
/// Theoretical maximum effective supply, assuming the network providing power to this battery continues to supply it
/// at the same rate.
/// </summary>
[ViewVariables]
public float MaxEffectiveSupply;
} }
// Readonly breaks json serialization. // Readonly breaks json serialization.
@@ -438,21 +468,37 @@ namespace Content.Server.Power.Pow3r
{ {
[ViewVariables] public NodeId Id; [ViewVariables] public NodeId Id;
/// <summary>
/// Power generators
/// </summary>
[ViewVariables] public List<NodeId> Supplies = new(); [ViewVariables] public List<NodeId> Supplies = new();
/// <summary>
/// Power consumers.
/// </summary>
[ViewVariables] public List<NodeId> Loads = new(); [ViewVariables] public List<NodeId> Loads = new();
// "Loading" means the network is connected to the INPUT port of the battery. /// <summary>
[ViewVariables] public List<NodeId> BatteriesCharging = new(); /// Batteries that are draining power from this network (connected to the INPUT port of the battery).
/// </summary>
[ViewVariables] public List<NodeId> BatteryLoads = new();
// "Supplying" means the network is connected to the OUTPUT port of the battery. /// <summary>
[ViewVariables] public List<NodeId> BatteriesDischarging = new(); /// Batteries that are supplying power to this network (connected to the OUTPUT port of the battery).
/// </summary>
[ViewVariables] public List<NodeId> BatterySupplies = new();
[ViewVariables] public float LastAvailableSupplySum = 0f; /// <summary>
[ViewVariables] public float LastMaxSupplySum = 0f; /// Available supply, including both normal supplies and batteries.
/// </summary>
[ViewVariables] public float LastCombinedSupply = 0f;
/// <summary>
/// Theoretical maximum supply, including both normal supplies and batteries.
/// </summary>
[ViewVariables] public float LastCombinedMaxSupply = 0f;
[ViewVariables] [JsonIgnore] public int Height; [ViewVariables] [JsonIgnore] public int Height;
[JsonIgnore] public bool HeightTouched;
} }
} }
} }

View File

@@ -1,4 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using Content.Server.Power.Pow3r; using Content.Server.Power.Pow3r;
using static Content.Server.Power.Pow3r.PowerState; using static Content.Server.Power.Pow3r.PowerState;
@@ -45,7 +45,7 @@ namespace Pow3r
_simStopwatch.Restart(); _simStopwatch.Restart();
_tickDataIdx = (_tickDataIdx + 1) % MaxTickData; _tickDataIdx = (_tickDataIdx + 1) % MaxTickData;
_solvers[_currentSolver].Tick(frameTime, _state); _solvers[_currentSolver].Tick(frameTime, _state, 1);
// Update tick history. // Update tick history.
foreach (var load in _state.Loads.Values) foreach (var load in _state.Loads.Values)
@@ -111,13 +111,13 @@ namespace Pow3r
supply.LinkedNetwork = network.Id; supply.LinkedNetwork = network.Id;
} }
foreach (var batteryId in network.BatteriesCharging) foreach (var batteryId in network.BatteryLoads)
{ {
var battery = _state.Batteries[batteryId]; var battery = _state.Batteries[batteryId];
battery.LinkedNetworkCharging = network.Id; battery.LinkedNetworkCharging = network.Id;
} }
foreach (var batteryId in network.BatteriesDischarging) foreach (var batteryId in network.BatterySupplies)
{ {
var battery = _state.Batteries[batteryId]; var battery = _state.Batteries[batteryId];
battery.LinkedNetworkDischarging = network.Id; battery.LinkedNetworkDischarging = network.Id;

View File

@@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using ImGuiNET; using ImGuiNET;
using Robust.Shared.Maths; using Robust.Shared.Maths;
@@ -52,6 +52,7 @@ namespace Pow3r
{ {
var network = new Network(); var network = new Network();
_state.Networks.Allocate(out network.Id) = network; _state.Networks.Allocate(out network.Id) = network;
_state.GroupedNets = null;
_displayNetworks.Add(network.Id, new DisplayNetwork()); _displayNetworks.Add(network.Id, new DisplayNetwork());
} }
@@ -60,6 +61,7 @@ namespace Pow3r
var battery = new Battery(); var battery = new Battery();
_state.Batteries.Allocate(out battery.Id) = battery; _state.Batteries.Allocate(out battery.Id) = battery;
_displayBatteries.Add(battery.Id, new DisplayBattery()); _displayBatteries.Add(battery.Id, new DisplayBattery());
_state.GroupedNets = null;
} }
Checkbox("Paused", ref _paused); Checkbox("Paused", ref _paused);
@@ -270,7 +272,8 @@ namespace Pow3r
{ {
if (battery.LinkedNetworkCharging == default && Button("Link as load")) if (battery.LinkedNetworkCharging == default && Button("Link as load"))
{ {
_linking.BatteriesCharging.Add(battery.Id); _linking.BatteryLoads.Add(battery.Id);
_state.GroupedNets = null;
_linking = null; _linking = null;
RefreshLinks(); RefreshLinks();
} }
@@ -279,7 +282,8 @@ namespace Pow3r
SameLine(); SameLine();
if (battery.LinkedNetworkDischarging == default && Button("Link as supply")) if (battery.LinkedNetworkDischarging == default && Button("Link as supply"))
{ {
_linking.BatteriesDischarging.Add(battery.Id); _linking.BatterySupplies.Add(battery.Id);
_state.GroupedNets = null;
_linking = null; _linking = null;
RefreshLinks(); RefreshLinks();
} }
@@ -290,7 +294,8 @@ namespace Pow3r
if (battery.LinkedNetworkCharging != default && Button("Unlink loading")) if (battery.LinkedNetworkCharging != default && Button("Unlink loading"))
{ {
var net = _state.Networks[battery.LinkedNetworkCharging]; var net = _state.Networks[battery.LinkedNetworkCharging];
net.BatteriesCharging.Remove(battery.Id); net.BatteryLoads.Remove(battery.Id);
_state.GroupedNets = null;
battery.LinkedNetworkCharging = default; battery.LinkedNetworkCharging = default;
} }
else else
@@ -299,7 +304,8 @@ namespace Pow3r
if (battery.LinkedNetworkDischarging != default && Button("Unlink supplying")) if (battery.LinkedNetworkDischarging != default && Button("Unlink supplying"))
{ {
var net = _state.Networks[battery.LinkedNetworkDischarging]; var net = _state.Networks[battery.LinkedNetworkDischarging];
net.BatteriesDischarging.Remove(battery.Id); net.BatterySupplies.Remove(battery.Id);
_state.GroupedNets = null;
battery.LinkedNetworkDischarging = default; battery.LinkedNetworkDischarging = default;
} }
} }
@@ -331,13 +337,13 @@ namespace Pow3r
DrawArrowLine(bgDrawList, load.CurrentWindowPos, displayNet.CurrentWindowPos, Color.Red); DrawArrowLine(bgDrawList, load.CurrentWindowPos, displayNet.CurrentWindowPos, Color.Red);
} }
foreach (var batteryId in network.BatteriesCharging) foreach (var batteryId in network.BatteryLoads)
{ {
var battery = _displayBatteries[batteryId]; var battery = _displayBatteries[batteryId];
DrawArrowLine(bgDrawList, battery.CurrentWindowPos, displayNet.CurrentWindowPos, Color.Purple); DrawArrowLine(bgDrawList, battery.CurrentWindowPos, displayNet.CurrentWindowPos, Color.Purple);
} }
foreach (var batteryId in network.BatteriesDischarging) foreach (var batteryId in network.BatterySupplies)
{ {
var battery = _displayBatteries[batteryId]; var battery = _displayBatteries[batteryId];
DrawArrowLine(bgDrawList, displayNet.CurrentWindowPos, battery.CurrentWindowPos, Color.Cyan); DrawArrowLine(bgDrawList, displayNet.CurrentWindowPos, battery.CurrentWindowPos, Color.Cyan);
@@ -357,6 +363,7 @@ namespace Pow3r
case Network n: case Network n:
_state.Networks.Free(n.Id); _state.Networks.Free(n.Id);
_displayNetworks.Remove(n.Id); _displayNetworks.Remove(n.Id);
_state.GroupedNets = null;
reLink = true; reLink = true;
break; break;
@@ -374,9 +381,10 @@ namespace Pow3r
case Battery b: case Battery b:
_state.Batteries.Free(b.Id); _state.Batteries.Free(b.Id);
_state.Networks.Values.ForEach(n => n.BatteriesCharging.Remove(b.Id)); _state.Networks.Values.ForEach(n => n.BatteryLoads.Remove(b.Id));
_state.Networks.Values.ForEach(n => n.BatteriesDischarging.Remove(b.Id)); _state.Networks.Values.ForEach(n => n.BatterySupplies.Remove(b.Id));
_displayBatteries.Remove(b.Id); _displayBatteries.Remove(b.Id);
_state.GroupedNets = null;
break; break;
} }
} }