committed by
GitHub
parent
ea60a81fdf
commit
103bc19508
305
Content.Server/Power/Pow3r/BatteryRampPegSolver.cs
Normal file
305
Content.Server/Power/Pow3r/BatteryRampPegSolver.cs
Normal file
@@ -0,0 +1,305 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.Utility;
|
||||
using static Content.Server.Power.Pow3r.PowerState;
|
||||
|
||||
namespace Content.Server.Power.Pow3r
|
||||
{
|
||||
public sealed class BatteryRampPegSolver : IPowerSolver
|
||||
{
|
||||
private sealed class HeightComparer : IComparer<Network>
|
||||
{
|
||||
public static HeightComparer Instance { get; } = new();
|
||||
|
||||
public int Compare(Network? x, Network? y)
|
||||
{
|
||||
if (ReferenceEquals(x, y)) return 0;
|
||||
if (ReferenceEquals(null, y)) return 1;
|
||||
if (ReferenceEquals(null, x)) return -1;
|
||||
return x.Height.CompareTo(y.Height);
|
||||
}
|
||||
}
|
||||
|
||||
private Network[] _sortBuffer = Array.Empty<Network>();
|
||||
|
||||
public void Tick(float frameTime, PowerState state)
|
||||
{
|
||||
// Clear loads and supplies.
|
||||
foreach (var load in state.Loads.Values)
|
||||
{
|
||||
if (load.Paused)
|
||||
continue;
|
||||
|
||||
load.ReceivingPower = 0;
|
||||
}
|
||||
|
||||
foreach (var supply in state.Supplies.Values)
|
||||
{
|
||||
if (supply.Paused)
|
||||
continue;
|
||||
|
||||
supply.CurrentSupply = 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)
|
||||
{
|
||||
if (network.BatteriesDischarging.Count != 0)
|
||||
continue;
|
||||
|
||||
EstimateNetworkDepth(state, network);
|
||||
}
|
||||
|
||||
if (_sortBuffer.Length != state.Networks.Count)
|
||||
_sortBuffer = new Network[state.Networks.Count];
|
||||
|
||||
var i = 0;
|
||||
foreach (var network in state.Networks.Values)
|
||||
{
|
||||
_sortBuffer[i++] = network;
|
||||
}
|
||||
|
||||
Array.Sort(_sortBuffer, HeightComparer.Instance);
|
||||
|
||||
// Go over every network.
|
||||
foreach (var network in _sortBuffer)
|
||||
{
|
||||
// Add up demand in network.
|
||||
var demand = 0f;
|
||||
foreach (var loadId in network.Loads)
|
||||
{
|
||||
var load = state.Loads[loadId];
|
||||
|
||||
if (!load.Enabled || load.Paused)
|
||||
continue;
|
||||
|
||||
DebugTools.Assert(load.DesiredPower >= 0);
|
||||
demand += load.DesiredPower;
|
||||
}
|
||||
|
||||
// TODO: Consider having battery charge loads be processed "after" pass-through loads.
|
||||
// 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.
|
||||
|
||||
// Loading batteries.
|
||||
foreach (var batteryId in network.BatteriesCharging)
|
||||
{
|
||||
var battery = state.Batteries[batteryId];
|
||||
if (!battery.Enabled || !battery.CanCharge || battery.Paused)
|
||||
continue;
|
||||
|
||||
var batterySpace = (battery.Capacity - battery.CurrentStorage) * (1 / battery.Efficiency);
|
||||
batterySpace = Math.Max(0, batterySpace);
|
||||
var scaledSpace = batterySpace / frameTime;
|
||||
|
||||
var chargeRate = battery.MaxChargeRate + battery.LoadingNetworkDemand / battery.Efficiency;
|
||||
|
||||
var batDemand = Math.Min(chargeRate, scaledSpace);
|
||||
|
||||
DebugTools.Assert(batDemand >= 0);
|
||||
|
||||
battery.DesiredPower = batDemand;
|
||||
demand += batDemand;
|
||||
}
|
||||
|
||||
DebugTools.Assert(demand >= 0);
|
||||
|
||||
// Add up supply in network.
|
||||
var availableSupplySum = 0f;
|
||||
var maxSupplySum = 0f;
|
||||
foreach (var supplyId in network.Supplies)
|
||||
{
|
||||
var supply = state.Supplies[supplyId];
|
||||
if (!supply.Enabled || supply.Paused)
|
||||
continue;
|
||||
|
||||
var rampMax = supply.SupplyRampPosition + supply.SupplyRampTolerance;
|
||||
var effectiveSupply = Math.Min(rampMax, supply.MaxSupply);
|
||||
|
||||
DebugTools.Assert(effectiveSupply >= 0);
|
||||
DebugTools.Assert(supply.MaxSupply >= 0);
|
||||
|
||||
supply.EffectiveMaxSupply = effectiveSupply;
|
||||
availableSupplySum += effectiveSupply;
|
||||
maxSupplySum += supply.MaxSupply;
|
||||
}
|
||||
|
||||
var unmet = Math.Max(0, demand - availableSupplySum);
|
||||
|
||||
DebugTools.Assert(availableSupplySum >= 0);
|
||||
DebugTools.Assert(maxSupplySum >= 0);
|
||||
|
||||
// Supplying batteries.
|
||||
// Batteries need to go after local supplies so that local supplies are prioritized.
|
||||
// Also, it makes demand-pulling of batteries
|
||||
// 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,
|
||||
// before power stabilizes in the network.
|
||||
// This is fine.
|
||||
foreach (var batteryId in network.BatteriesDischarging)
|
||||
{
|
||||
var battery = state.Batteries[batteryId];
|
||||
if (!battery.Enabled || !battery.CanDischarge || battery.Paused)
|
||||
continue;
|
||||
|
||||
var scaledSpace = battery.CurrentStorage / frameTime;
|
||||
var supplyCap = Math.Min(battery.MaxSupply,
|
||||
battery.SupplyRampPosition + battery.SupplyRampTolerance);
|
||||
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.LoadingNetworkDemand = unmet;
|
||||
battery.LoadingDemandMarked = true;
|
||||
}
|
||||
|
||||
var met = Math.Min(demand, availableSupplySum);
|
||||
|
||||
if (met != 0)
|
||||
{
|
||||
// Distribute supply to loads.
|
||||
foreach (var loadId in network.Loads)
|
||||
{
|
||||
var load = state.Loads[loadId];
|
||||
if (!load.Enabled || load.DesiredPower == 0 || load.Paused)
|
||||
continue;
|
||||
|
||||
var ratio = load.DesiredPower / demand;
|
||||
load.ReceivingPower = ratio * met;
|
||||
}
|
||||
|
||||
// Loading batteries
|
||||
foreach (var batteryId in network.BatteriesCharging)
|
||||
{
|
||||
var battery = state.Batteries[batteryId];
|
||||
|
||||
if (!battery.Enabled || battery.DesiredPower == 0 || battery.Paused)
|
||||
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;
|
||||
}
|
||||
|
||||
// Load to supplies
|
||||
foreach (var supplyId in network.Supplies)
|
||||
{
|
||||
var supply = state.Supplies[supplyId];
|
||||
if (!supply.Enabled || supply.EffectiveMaxSupply == 0 || supply.Paused)
|
||||
continue;
|
||||
|
||||
var ratio = supply.EffectiveMaxSupply / availableSupplySum;
|
||||
supply.CurrentSupply = ratio * met;
|
||||
|
||||
if (supply.MaxSupply != 0)
|
||||
{
|
||||
var maxSupplyRatio = supply.MaxSupply / maxSupplySum;
|
||||
|
||||
supply.SupplyRampTarget = maxSupplyRatio * demand;
|
||||
}
|
||||
else
|
||||
{
|
||||
supply.SupplyRampTarget = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Supplying batteries
|
||||
foreach (var batteryId in network.BatteriesDischarging)
|
||||
{
|
||||
var battery = state.Batteries[batteryId];
|
||||
if (!battery.Enabled || battery.TempMaxSupply == 0 || battery.Paused)
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
foreach (var battery in state.Batteries.Values)
|
||||
{
|
||||
if (battery.Paused)
|
||||
continue;
|
||||
|
||||
if (!battery.SupplyingMarked)
|
||||
battery.CurrentSupply = 0;
|
||||
|
||||
if (!battery.LoadingMarked)
|
||||
battery.CurrentReceiving = 0;
|
||||
|
||||
if (!battery.LoadingDemandMarked)
|
||||
battery.LoadingNetworkDemand = 0;
|
||||
|
||||
battery.SupplyingMarked = false;
|
||||
battery.LoadingMarked = false;
|
||||
battery.LoadingDemandMarked = false;
|
||||
}
|
||||
|
||||
PowerSolverShared.UpdateRampPositions(frameTime, state);
|
||||
}
|
||||
|
||||
private static void EstimateNetworkDepth(PowerState state, Network network)
|
||||
{
|
||||
network.HeightTouched = true;
|
||||
|
||||
if (network.BatteriesCharging.Count == 0)
|
||||
{
|
||||
network.Height = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
var max = 0;
|
||||
foreach (var batteryId in network.BatteriesCharging)
|
||||
{
|
||||
var battery = state.Batteries[batteryId];
|
||||
|
||||
if (battery.LinkedNetworkDischarging == default)
|
||||
continue;
|
||||
|
||||
var subNet = state.Networks[battery.LinkedNetworkDischarging];
|
||||
if (!subNet.HeightTouched)
|
||||
EstimateNetworkDepth(state, subNet);
|
||||
|
||||
max = Math.Max(subNet.Height, max);
|
||||
}
|
||||
|
||||
network.Height = 1 + max;
|
||||
}
|
||||
}
|
||||
}
|
||||
181
Content.Server/Power/Pow3r/GraphWalkSolver.cs
Normal file
181
Content.Server/Power/Pow3r/GraphWalkSolver.cs
Normal file
@@ -0,0 +1,181 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using static Content.Server.Power.Pow3r.PowerState;
|
||||
|
||||
namespace Content.Server.Power.Pow3r
|
||||
{
|
||||
/// <summary>
|
||||
/// Partial implementation of full-graph-walking power solving under pow3r.
|
||||
/// Concept described at https://hackmd.io/@ss14/lowpower
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Many features like batteries, cycle detection, join handling, etc... are not implemented at all.
|
||||
/// Seriously, this implementation barely works. Ah well.
|
||||
/// <see cref="BatteryRampPegSolver"/> is better.
|
||||
/// </remarks>
|
||||
public class GraphWalkSolver : IPowerSolver
|
||||
{
|
||||
public void Tick(float frameTime, PowerState state)
|
||||
{
|
||||
foreach (var load in state.Loads.Values)
|
||||
{
|
||||
load.ReceivingPower = 0;
|
||||
}
|
||||
|
||||
foreach (var supply in state.Supplies.Values)
|
||||
{
|
||||
supply.CurrentSupply = 0;
|
||||
}
|
||||
|
||||
foreach (var network in state.Networks.Values)
|
||||
{
|
||||
// Clear some stuff.
|
||||
network.LocalDemandMet = 0;
|
||||
|
||||
// Add up demands in network.
|
||||
network.LocalDemandTotal = network.Loads
|
||||
.Select(l => state.Loads[l])
|
||||
.Where(c => c.Enabled)
|
||||
.Sum(c => c.DesiredPower);
|
||||
|
||||
// Add up supplies in network.
|
||||
var availableSupplySum = 0f;
|
||||
var maxSupplySum = 0f;
|
||||
foreach (var supplyId in network.Supplies)
|
||||
{
|
||||
var supply = state.Supplies[supplyId];
|
||||
if (!supply.Enabled)
|
||||
continue;
|
||||
|
||||
var rampMax = supply.SupplyRampPosition + supply.SupplyRampTolerance;
|
||||
var effectiveSupply = Math.Min(rampMax, supply.MaxSupply);
|
||||
supply.EffectiveMaxSupply = effectiveSupply;
|
||||
availableSupplySum += effectiveSupply;
|
||||
maxSupplySum += supply.MaxSupply;
|
||||
}
|
||||
|
||||
network.AvailableSupplyTotal = availableSupplySum;
|
||||
network.TheoreticalSupplyTotal = maxSupplySum;
|
||||
}
|
||||
|
||||
// Sort networks by tree height so that suppliers that have less possible loads go FIRST.
|
||||
// Idea being that a backup generator on a small subnet should do more work
|
||||
// so that a larger generator that covers more networks can put its power elsewhere.
|
||||
var sortedByHeight = state.Networks.Values.OrderBy(v => TotalSubLoadCount(state, v)).ToArray();
|
||||
|
||||
// Go over every network with supply to send power.
|
||||
foreach (var network in sortedByHeight)
|
||||
{
|
||||
// Find all loads recursively, and sum them up.
|
||||
var subNets = new List<Network>();
|
||||
var totalDemand = 0f;
|
||||
GetLoadingNetworksRecursively(state, network, subNets, ref totalDemand);
|
||||
|
||||
if (totalDemand == 0)
|
||||
continue;
|
||||
|
||||
// Calculate power delivered.
|
||||
var power = Math.Min(totalDemand, network.AvailableSupplyTotal);
|
||||
|
||||
// Distribute load across supplies in network.
|
||||
foreach (var supplyId in network.Supplies)
|
||||
{
|
||||
var supply = state.Supplies[supplyId];
|
||||
if (!supply.Enabled)
|
||||
continue;
|
||||
|
||||
if (supply.EffectiveMaxSupply != 0)
|
||||
{
|
||||
var ratio = supply.EffectiveMaxSupply / network.AvailableSupplyTotal;
|
||||
|
||||
supply.CurrentSupply = ratio * power;
|
||||
}
|
||||
else
|
||||
{
|
||||
supply.CurrentSupply = 0;
|
||||
}
|
||||
|
||||
if (supply.MaxSupply != 0)
|
||||
{
|
||||
var ratio = supply.MaxSupply / network.TheoreticalSupplyTotal;
|
||||
|
||||
supply.SupplyRampTarget = ratio * totalDemand;
|
||||
}
|
||||
else
|
||||
{
|
||||
supply.SupplyRampTarget = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Distribute supply across subnet loads.
|
||||
foreach (var subNet in subNets)
|
||||
{
|
||||
var rem = subNet.RemainingDemand;
|
||||
var ratio = rem / totalDemand;
|
||||
|
||||
subNet.LocalDemandMet += ratio * power;
|
||||
}
|
||||
}
|
||||
|
||||
// Distribute power across loads in networks.
|
||||
foreach (var network in state.Networks.Values)
|
||||
{
|
||||
if (network.LocalDemandMet == 0)
|
||||
continue;
|
||||
|
||||
foreach (var loadId in network.Loads)
|
||||
{
|
||||
var load = state.Loads[loadId];
|
||||
if (!load.Enabled)
|
||||
continue;
|
||||
|
||||
var ratio = load.DesiredPower / network.LocalDemandTotal;
|
||||
load.ReceivingPower = ratio * network.LocalDemandMet;
|
||||
}
|
||||
}
|
||||
|
||||
PowerSolverShared.UpdateRampPositions(frameTime, state);
|
||||
}
|
||||
|
||||
private int TotalSubLoadCount(PowerState state, Network network)
|
||||
{
|
||||
// TODO: Cycle detection.
|
||||
var height = network.Loads.Count;
|
||||
|
||||
foreach (var batteryId in network.BatteriesCharging)
|
||||
{
|
||||
var battery = state.Batteries[batteryId];
|
||||
if (battery.LinkedNetworkDischarging != default)
|
||||
{
|
||||
height += TotalSubLoadCount(state, state.Networks[battery.LinkedNetworkDischarging]);
|
||||
}
|
||||
}
|
||||
|
||||
return height;
|
||||
}
|
||||
|
||||
private void GetLoadingNetworksRecursively(
|
||||
PowerState state,
|
||||
Network network,
|
||||
List<Network> networks,
|
||||
ref float totalDemand)
|
||||
{
|
||||
networks.Add(network);
|
||||
totalDemand += network.LocalDemandTotal - network.LocalDemandMet;
|
||||
|
||||
foreach (var batteryId in network.BatteriesCharging)
|
||||
{
|
||||
var battery = state.Batteries[batteryId];
|
||||
if (battery.LinkedNetworkDischarging != default)
|
||||
{
|
||||
GetLoadingNetworksRecursively(
|
||||
state,
|
||||
state.Networks[battery.LinkedNetworkDischarging],
|
||||
networks,
|
||||
ref totalDemand);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
7
Content.Server/Power/Pow3r/IPowerSolver.cs
Normal file
7
Content.Server/Power/Pow3r/IPowerSolver.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Content.Server.Power.Pow3r
|
||||
{
|
||||
public interface IPowerSolver
|
||||
{
|
||||
void Tick(float frameTime, PowerState state);
|
||||
}
|
||||
}
|
||||
10
Content.Server/Power/Pow3r/NoOpSolver.cs
Normal file
10
Content.Server/Power/Pow3r/NoOpSolver.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace Content.Server.Power.Pow3r
|
||||
{
|
||||
public sealed class NoOpSolver : IPowerSolver
|
||||
{
|
||||
public void Tick(float frameTime, PowerState state)
|
||||
{
|
||||
// Literally nothing.
|
||||
}
|
||||
}
|
||||
}
|
||||
90
Content.Server/Power/Pow3r/PowerSolverShared.cs
Normal file
90
Content.Server/Power/Pow3r/PowerSolverShared.cs
Normal file
@@ -0,0 +1,90 @@
|
||||
using System;
|
||||
|
||||
namespace Content.Server.Power.Pow3r
|
||||
{
|
||||
public static class PowerSolverShared
|
||||
{
|
||||
public static void UpdateRampPositions(float frameTime, PowerState state)
|
||||
{
|
||||
// Update supplies to move their ramp position towards target, if necessary.
|
||||
foreach (var supply in state.Supplies.Values)
|
||||
{
|
||||
if (supply.Paused)
|
||||
continue;
|
||||
|
||||
if (!supply.Enabled)
|
||||
{
|
||||
// If disabled, set ramp to 0.
|
||||
supply.SupplyRampPosition = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
var rampDev = supply.SupplyRampTarget - supply.SupplyRampPosition;
|
||||
if (Math.Abs(rampDev) > 0.001f)
|
||||
{
|
||||
float newPos;
|
||||
if (rampDev > 0)
|
||||
{
|
||||
// Position below target, go up.
|
||||
newPos = Math.Min(
|
||||
supply.SupplyRampTarget,
|
||||
supply.SupplyRampPosition + supply.SupplyRampRate * frameTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Other way around, go down
|
||||
newPos = Math.Max(
|
||||
supply.SupplyRampTarget,
|
||||
supply.SupplyRampPosition - supply.SupplyRampRate * frameTime);
|
||||
}
|
||||
|
||||
supply.SupplyRampPosition = Math.Clamp(newPos, 0, supply.MaxSupply);
|
||||
}
|
||||
else
|
||||
{
|
||||
supply.SupplyRampPosition = supply.SupplyRampTarget;
|
||||
}
|
||||
}
|
||||
|
||||
// Batteries too.
|
||||
foreach (var battery in state.Batteries.Values)
|
||||
{
|
||||
if (battery.Paused)
|
||||
continue;
|
||||
|
||||
if (!battery.Enabled)
|
||||
{
|
||||
// If disabled, set ramp to 0.
|
||||
battery.SupplyRampPosition = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
var rampDev = battery.SupplyRampTarget - battery.SupplyRampPosition;
|
||||
if (Math.Abs(rampDev) > 0.001f)
|
||||
{
|
||||
float newPos;
|
||||
if (rampDev > 0)
|
||||
{
|
||||
// Position below target, go up.
|
||||
newPos = Math.Min(
|
||||
battery.SupplyRampTarget,
|
||||
battery.SupplyRampPosition + battery.SupplyRampRate * frameTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Other way around, go down
|
||||
newPos = Math.Max(
|
||||
battery.SupplyRampTarget,
|
||||
battery.SupplyRampPosition - battery.SupplyRampRate * frameTime);
|
||||
}
|
||||
|
||||
battery.SupplyRampPosition = Math.Clamp(newPos, 0, battery.MaxSupply);
|
||||
}
|
||||
else
|
||||
{
|
||||
battery.SupplyRampPosition = battery.SupplyRampTarget;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
207
Content.Server/Power/Pow3r/PowerState.cs
Normal file
207
Content.Server/Power/Pow3r/PowerState.cs
Normal file
@@ -0,0 +1,207 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Numerics;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.Power.Pow3r
|
||||
{
|
||||
public sealed class PowerState
|
||||
{
|
||||
public const int MaxTickData = 180;
|
||||
|
||||
public static readonly JsonSerializerOptions SerializerOptions = new()
|
||||
{
|
||||
IncludeFields = true,
|
||||
Converters = {new NodeIdJsonConverter()}
|
||||
};
|
||||
|
||||
public Dictionary<NodeId, Supply> Supplies = new();
|
||||
public Dictionary<NodeId, Network> Networks = new();
|
||||
public Dictionary<NodeId, Load> Loads = new();
|
||||
public Dictionary<NodeId, Battery> Batteries = new();
|
||||
|
||||
public readonly struct NodeId : IEquatable<NodeId>
|
||||
{
|
||||
public readonly int Id;
|
||||
|
||||
public NodeId(int id)
|
||||
{
|
||||
Id = id;
|
||||
}
|
||||
|
||||
public bool Equals(NodeId other)
|
||||
{
|
||||
return Id == other.Id;
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return obj is NodeId other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Id;
|
||||
}
|
||||
|
||||
public static bool operator ==(NodeId left, NodeId right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator !=(NodeId left, NodeId right)
|
||||
{
|
||||
return !left.Equals(right);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Id.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class NodeIdJsonConverter : JsonConverter<NodeId>
|
||||
{
|
||||
public override NodeId Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
return new(reader.GetInt32());
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, NodeId value, JsonSerializerOptions options)
|
||||
{
|
||||
writer.WriteNumberValue(value.Id);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class Supply
|
||||
{
|
||||
[ViewVariables] public NodeId Id;
|
||||
|
||||
// == Static parameters ==
|
||||
[ViewVariables(VVAccess.ReadWrite)] public bool Enabled = true;
|
||||
[ViewVariables(VVAccess.ReadWrite)] public bool Paused;
|
||||
[ViewVariables(VVAccess.ReadWrite)] public float MaxSupply;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)] public float SupplyRampRate;
|
||||
[ViewVariables(VVAccess.ReadWrite)] public float SupplyRampTolerance;
|
||||
|
||||
// == Runtime parameters ==
|
||||
|
||||
// Actual power supplied last network update.
|
||||
[ViewVariables(VVAccess.ReadWrite)] public float CurrentSupply;
|
||||
|
||||
// The amount of power we WANT to be supplying to match grid load.
|
||||
[ViewVariables(VVAccess.ReadWrite)] [JsonIgnore]
|
||||
public float SupplyRampTarget;
|
||||
|
||||
// Position of the supply ramp.
|
||||
[ViewVariables(VVAccess.ReadWrite)] public float SupplyRampPosition;
|
||||
|
||||
[ViewVariables] [JsonIgnore] public NodeId LinkedNetwork;
|
||||
|
||||
// In-tick max supply thanks to ramp. Used during calculations.
|
||||
[JsonIgnore] public float EffectiveMaxSupply;
|
||||
}
|
||||
|
||||
public sealed class Load
|
||||
{
|
||||
[ViewVariables] public NodeId Id;
|
||||
|
||||
// == Static parameters ==
|
||||
[ViewVariables(VVAccess.ReadWrite)] public bool Enabled = true;
|
||||
[ViewVariables(VVAccess.ReadWrite)] public bool Paused;
|
||||
[ViewVariables(VVAccess.ReadWrite)] public float DesiredPower;
|
||||
|
||||
// == Runtime parameters ==
|
||||
[ViewVariables(VVAccess.ReadWrite)] public float ReceivingPower;
|
||||
|
||||
[ViewVariables] [JsonIgnore] public NodeId LinkedNetwork;
|
||||
}
|
||||
|
||||
public sealed class Battery
|
||||
{
|
||||
[ViewVariables] public NodeId Id;
|
||||
|
||||
// == Static parameters ==
|
||||
[ViewVariables(VVAccess.ReadWrite)] public bool Enabled = true;
|
||||
[ViewVariables(VVAccess.ReadWrite)] public bool Paused;
|
||||
[ViewVariables(VVAccess.ReadWrite)] public bool CanDischarge = true;
|
||||
[ViewVariables(VVAccess.ReadWrite)] public bool CanCharge = true;
|
||||
[ViewVariables(VVAccess.ReadWrite)] public float Capacity;
|
||||
[ViewVariables(VVAccess.ReadWrite)] public float MaxChargeRate;
|
||||
[ViewVariables(VVAccess.ReadWrite)] public float MaxThroughput; // 0 = infinite cuz imgui
|
||||
[ViewVariables(VVAccess.ReadWrite)] public float MaxSupply;
|
||||
[ViewVariables(VVAccess.ReadWrite)] public float SupplyRampTolerance;
|
||||
[ViewVariables(VVAccess.ReadWrite)] public float SupplyRampRate;
|
||||
[ViewVariables(VVAccess.ReadWrite)] public float Efficiency = 1;
|
||||
|
||||
// == Runtime parameters ==
|
||||
[ViewVariables(VVAccess.ReadWrite)] public float SupplyRampPosition;
|
||||
[ViewVariables(VVAccess.ReadWrite)] public float CurrentSupply;
|
||||
[ViewVariables(VVAccess.ReadWrite)] public float CurrentStorage;
|
||||
[ViewVariables(VVAccess.ReadWrite)] public float CurrentReceiving;
|
||||
[ViewVariables(VVAccess.ReadWrite)] public float LoadingNetworkDemand;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)] [JsonIgnore]
|
||||
public bool SupplyingMarked;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)] [JsonIgnore]
|
||||
public bool LoadingMarked;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)] [JsonIgnore]
|
||||
public bool LoadingDemandMarked;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)] [JsonIgnore]
|
||||
public float TempMaxSupply;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)] [JsonIgnore]
|
||||
public float DesiredPower;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)] [JsonIgnore]
|
||||
public float SupplyRampTarget;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)] [JsonIgnore]
|
||||
public NodeId LinkedNetworkCharging;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)] [JsonIgnore]
|
||||
public NodeId LinkedNetworkDischarging;
|
||||
}
|
||||
|
||||
// Readonly breaks json serialization.
|
||||
[SuppressMessage("ReSharper", "FieldCanBeMadeReadOnly.Local")]
|
||||
public sealed class Network
|
||||
{
|
||||
[ViewVariables] public NodeId Id;
|
||||
|
||||
[ViewVariables] public List<NodeId> Supplies = new();
|
||||
|
||||
[ViewVariables] public List<NodeId> Loads = new();
|
||||
|
||||
// "Loading" means the network is connected to the INPUT port of the battery.
|
||||
[ViewVariables] public List<NodeId> BatteriesCharging = new();
|
||||
|
||||
// "Supplying" means the network is connected to the OUTPUT port of the battery.
|
||||
[ViewVariables] public List<NodeId> BatteriesDischarging = new();
|
||||
|
||||
// Calculation parameters used by GraphWalkSolver.
|
||||
// Unused by BatteryRampPegSolver.
|
||||
[JsonIgnore] public float LocalDemandTotal;
|
||||
[JsonIgnore] public float LocalDemandMet;
|
||||
[JsonIgnore] public float GroupDemandTotal;
|
||||
[JsonIgnore] public float GroupDemandMet;
|
||||
|
||||
[ViewVariables] [JsonIgnore] public int Height;
|
||||
[JsonIgnore] public bool HeightTouched;
|
||||
|
||||
// Supply available this tick.
|
||||
[JsonIgnore] public float AvailableSupplyTotal;
|
||||
|
||||
// Max theoretical supply assuming max ramp.
|
||||
[JsonIgnore] public float TheoreticalSupplyTotal;
|
||||
public float RemainingDemand => LocalDemandTotal - LocalDemandMet;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user