fix power net

This commit is contained in:
Vigers Ray 2025-06-03 17:22:17 +03:00
parent 77efc97789
commit b916af867c
6 changed files with 112 additions and 160 deletions

View file

@ -30,21 +30,14 @@ namespace Content.Server.Power.EntitySystems
private readonly PowerState _powerState = new();
private readonly HashSet<PowerNet> _powerNetReconnectQueue = new();
private readonly HashSet<ApcNet> _apcNetReconnectQueue = new();
private BatteryRampPegSolver.UpdateNetworkJob _networkJob;
private float _updateAccumulator;
private const float UpdateInterval = 0.5f;
private EntityQuery<ApcPowerReceiverBatteryComponent> _apcBatteryQuery;
private EntityQuery<BatteryComponent> _batteryQuery;
private BatteryRampPegSolver _solver = new();
public PowerNetSystem()
{
_networkJob = new BatteryRampPegSolver.UpdateNetworkJob
{
Solver = _solver
};
}
public override void Initialize()
{
base.Initialize();
@ -283,95 +276,88 @@ namespace Content.Server.Power.EntitySystems
{
base.Update(frameTime);
_updateAccumulator += frameTime;
if (_updateAccumulator < UpdateInterval)
return;
_updateAccumulator = 0;
ReconnectNetworks();
RecalculateGroupedNets();
// Synchronize batteries
RaiseLocalEvent(new NetworkBatteryPreSync());
var preSyncEvent = new NetworkBatteryPreSync();
RaiseLocalEvent(preSyncEvent);
// Run power solver.
_solver.Tick(frameTime, _powerState, _parMan);
if (_powerState.GroupedNets != null)
// Synchronize batteries, the other way around.
RaiseLocalEvent(new NetworkBatteryPostSync());
var apcPowerReceiverQuery = AllEntityQuery<ApcPowerReceiverComponent>();
while (apcPowerReceiverQuery.MoveNext(out var uid, out var apcReceiver))
{
foreach (var group in _powerState.GroupedNets)
{
if (group.Count > 100)
{
try
{
_networkJob.Networks = group;
_networkJob.State = _powerState;
_networkJob.FrameTime = frameTime;
_parMan.ProcessNow(_networkJob, group.Count);
}
catch (Exception e)
{
foreach (var network in group)
{
_solver.UpdateNetwork(network, _powerState, frameTime);
}
}
}
else
{
foreach (var network in group)
{
_solver.UpdateNetwork(network, _powerState, frameTime);
}
}
}
UpdateApcPowerReceiverSingle(uid, apcReceiver, frameTime);
}
var postSyncEvent = new NetworkBatteryPostSync();
RaiseLocalEvent(postSyncEvent);
ValidatePowerState();
UpdateComponents(frameTime);
}
private void ValidatePowerState()
{
foreach (var battery in _powerState.Batteries.Values)
var powerConsumererQuery = AllEntityQuery<PowerConsumerComponent>();
while (powerConsumererQuery.MoveNext(out var uid, out var consumer))
{
if (battery.CurrentStorage < 0 || battery.CurrentStorage > battery.Capacity)
{
battery.CurrentStorage = Math.Clamp(battery.CurrentStorage, 0, battery.Capacity);
}
UpdatePowerConsumerSingle(uid, consumer);
}
var powerNetworkBatteryQuery = AllEntityQuery<PowerNetworkBatteryComponent>();
while (powerNetworkBatteryQuery.MoveNext(out var uid, out var powerNetBattery))
{
UpdateNetworkBatterySingle(uid, powerNetBattery);
}
}
private void UpdateComponents(float frameTime)
{
var apcQuery = EntityQueryEnumerator<ApcPowerReceiverComponent>();
var consumerQuery = EntityQueryEnumerator<PowerConsumerComponent>();
var batteryQuery = EntityQueryEnumerator<PowerNetworkBatteryComponent>();
while (apcQuery.MoveNext(out var uid, out var apcReceiver))
{
UpdateApcReceiver(uid, apcReceiver, frameTime);
}
while (consumerQuery.MoveNext(out var uid, out var consumer))
{
UpdateConsumer(uid, consumer);
}
while (batteryQuery.MoveNext(out var uid, out var battery))
{
UpdateBattery(uid, battery);
}
}
private void UpdateApcReceiver(EntityUid uid, ApcPowerReceiverComponent apcReceiver, float frameTime)
private void UpdateApcPowerReceiverSingle(EntityUid uid, ApcPowerReceiverComponent apcReceiver, float frameTime)
{
var mapId = Transform(uid).MapUid;
var isAlwaysPowered = HasComp<AlwaysPoweredMapComponent>(mapId);
var powered = isAlwaysPowered || IsPoweredCalculate(apcReceiver);
MetaDataComponent? metadata = null;
if (_apcBatteryQuery.TryComp(uid, out var apcBattery) && _batteryQuery.TryComp(uid, out var battery))
{
apcReceiver.Load = apcBattery.IdleLoad;
var requireBattery = !powered && !apcReceiver.PowerDisabled;
if (requireBattery)
{
_battery.SetCharge(uid, battery.CurrentCharge - apcBattery.IdleLoad * frameTime, battery);
}
else if (powered && !_battery.IsFull(uid, battery))
{
apcReceiver.Load += apcBattery.BatteryRechargeRate * apcBattery.BatteryRechargeEfficiency;
_battery.SetCharge(uid, battery.CurrentCharge + apcBattery.BatteryRechargeRate * frameTime, battery);
}
var enableBattery = requireBattery && battery.CurrentCharge > 0;
if (apcBattery.Enabled != enableBattery)
{
apcBattery.Enabled = enableBattery;
metadata = MetaData(uid);
Dirty(uid, apcBattery, metadata);
var apcBatteryEv = new ApcPowerReceiverBatteryChangedEvent(enableBattery);
RaiseLocalEvent(uid, ref apcBatteryEv);
_appearance.SetData(uid, PowerDeviceVisuals.BatteryPowered, enableBattery);
}
powered |= enableBattery;
}
if (!apcReceiver.Recalculate && apcReceiver.Powered == powered)
return;
var metadata = MetaData(uid);
metadata ??= MetaData(uid);
if (Paused(uid, metadata))
return;
@ -383,21 +369,22 @@ namespace Content.Server.Power.EntitySystems
RaiseLocalEvent(uid, ref ev);
}
private void UpdateConsumer(EntityUid uid, PowerConsumerComponent consumer)
private void UpdatePowerConsumerSingle(EntityUid uid, PowerConsumerComponent consumer)
{
var mapId = Transform(uid).MapUid;
var isAlwaysPowered = HasComp<AlwaysPoweredMapComponent>(mapId);
var newRecv = isAlwaysPowered ? consumer.DrawRate : consumer.NetworkLoad.ReceivingPower;
if (MathHelper.CloseToPercent(consumer.LastReceived, newRecv))
ref var lastRecv = ref consumer.LastReceived;
if (MathHelper.CloseToPercent(lastRecv, newRecv))
return;
consumer.LastReceived = newRecv;
lastRecv = newRecv;
var msg = new PowerConsumerReceivedChanged(newRecv, consumer.DrawRate);
RaiseLocalEvent(uid, ref msg);
}
private void UpdateBattery(EntityUid uid, PowerNetworkBatteryComponent powerNetBattery)
private void UpdateNetworkBatterySingle(EntityUid uid, PowerNetworkBatteryComponent powerNetBattery)
{
var mapId = Transform(uid).MapUid;
var isAlwaysPowered = HasComp<AlwaysPoweredMapComponent>(mapId);
@ -440,8 +427,6 @@ namespace Content.Server.Power.EntitySystems
}
_powerNetReconnectQueue.Clear();
_powerState.GroupedNets = null;
}
private bool IsPoweredCalculate(ApcPowerReceiverComponent comp)
@ -558,53 +543,6 @@ namespace Content.Server.Power.EntitySystems
{
_solver.Validate(_powerState);
}
private void RecalculateGroupedNets()
{
if (_powerState.GroupedNets != null)
return;
var nets = _powerState.Networks.Values.ToList();
var groups = new List<List<PowerState.Network>>();
var visited = new HashSet<PowerState.Network>();
foreach (var net in nets)
{
if (visited.Contains(net))
continue;
var group = new List<PowerState.Network>();
var queue = new Queue<PowerState.Network>();
queue.Enqueue(net);
visited.Add(net);
while (queue.Count > 0)
{
var current = queue.Dequeue();
group.Add(current);
foreach (var batteryId in current.BatteryLoads.Concat(current.BatterySupplies))
{
var battery = _powerState.Batteries[batteryId];
if (battery.LinkedNetworkCharging != default && !visited.Contains(_powerState.Networks[battery.LinkedNetworkCharging]))
{
queue.Enqueue(_powerState.Networks[battery.LinkedNetworkCharging]);
visited.Add(_powerState.Networks[battery.LinkedNetworkCharging]);
}
if (battery.LinkedNetworkDischarging != default && !visited.Contains(_powerState.Networks[battery.LinkedNetworkDischarging]))
{
queue.Enqueue(_powerState.Networks[battery.LinkedNetworkDischarging]);
visited.Add(_powerState.Networks[battery.LinkedNetworkDischarging]);
}
}
}
if (group.Count > 0)
groups.Add(group);
}
_powerState.GroupedNets = groups;
}
}
/// <summary>

View file

@ -114,6 +114,7 @@ namespace Content.Server.Power.Pow3r
if (!load.Enabled || load.Paused)
continue;
DebugTools.Assert(load.DesiredPower >= 0);
demand += load.DesiredPower;
}
@ -188,8 +189,6 @@ namespace Content.Server.Power.Pow3r
battery.AvailableSupply = Math.Min(scaledSpace, supplyAndPassthrough);
battery.LoadingNetworkDemand = unmet;
battery.MaxEffectiveSupply = Math.Min(battery.CurrentStorage / frameTime,
battery.MaxSupply + battery.CurrentReceiving * battery.Efficiency);
battery.MaxEffectiveSupply = Math.Min(battery.CurrentStorage / frameTime, battery.MaxSupply + battery.CurrentReceiving * battery.Efficiency);
totalBatterySupply += battery.AvailableSupply;

View file

@ -127,24 +127,6 @@ namespace Content.Server.Power.Pow3r
_storage = Array.Empty<Slot>();
}
public GenIdStorage(int initialCapacity)
{
if (initialCapacity <= 0)
throw new ArgumentException("Initial capacity must be greater than 0", nameof(initialCapacity));
_storage = new Slot[initialCapacity];
for (var i = 0; i < initialCapacity - 1; i++)
{
_storage[i].NextSlot = i + 1;
_storage[i].Generation = 1;
}
_storage[initialCapacity - 1].NextSlot = int.MaxValue;
_storage[initialCapacity - 1].Generation = 1;
_nextFree = 0;
}
public static GenIdStorage<T> FromEnumerable(IEnumerable<(NodeId, T)> enumerable)
{
var storage = new GenIdStorage<T>();

View file

@ -36,5 +36,5 @@ public sealed partial class CCVars : CVars
/// Set to true to disable parallel processing in the pow3r solver.
/// </summary>
public static readonly CVarDef<bool> DebugPow3rDisableParallel =
CVarDef.Create("debug.pow3r_disable_parallel", true, CVar.SERVERONLY);
CVarDef.Create("debug.pow3r_disable_parallel", false, CVar.SERVERONLY);
}

View file

@ -25,12 +25,23 @@ zzzz-fmt-power-watts =
# Therefore 1 kilowatt-hour is equal to 3,600,000 joules (3.6MJ)
zzzz-fmt-power-joules =
{ TOSTRING($divided, "F1") } { $places ->
[0] Дж
[1] кДж
[2] МДж
[3] ГДж
[4] ТДж
*[5] ???
}
[0] Дж
[1] кДж
[2] МДж
[3] ГДж
[4] ТДж
*[5] ???
}
# Used internally by the ENERGYWATTHOURS() function.
zzzz-fmt-energy-watt-hours = { TOSTRING($divided, "F1") } { $places ->
[0] Wh
[1] kWh
[2] MWh
[3] GWh
[4] TWh
*[5] ???
}
# Used internally by the PLAYTIME() function.
zzzz-fmt-playtime = { $hours }Ч { $minutes }М

View file

@ -0,0 +1,22 @@
## Strings for the battery (SMES/substation) menu
battery-menu-footer-left = Danger: high voltage
battery-menu-footer-right = 7.2 REV 6
battery-menu-out = OUT
battery-menu-in = IN
battery-menu-charge-header = Charge Circuit
battery-menu-discharge-header = Discharge Circuit
battery-menu-storage-header = Storage Cells
battery-menu-passthrough = Passthrough
battery-menu-max = Max:
battery-menu-current = Current:
battery-menu-stored = Stored:
battery-menu-energy = Energy:
battery-menu-eta-full = ETA (full):
battery-menu-eta-empty = ETA (empty):
battery-menu-eta-value = ~{ $minutes } min
battery-menu-eta-value-max = >{ $minutes } min
battery-menu-eta-value-na = N/A
battery-menu-power-value = { POWERWATTS($value) }
battery-menu-stored-percent-value = { TOSTRING($value, "P1") }
battery-menu-stored-energy-value = { ENERGYWATTHOURS($value) }