1
0

PowerState.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509
  1. using System.Collections;
  2. using System.Diagnostics.CodeAnalysis;
  3. using System.Linq;
  4. using System.Runtime.CompilerServices;
  5. using System.Text.Json;
  6. using System.Text.Json.Serialization;
  7. using Robust.Shared.Utility;
  8. using static Content.Server.Power.Pow3r.PowerState;
  9. namespace Content.Server.Power.Pow3r
  10. {
  11. public sealed class PowerState
  12. {
  13. public static readonly JsonSerializerOptions SerializerOptions = new()
  14. {
  15. IncludeFields = true,
  16. Converters = {new NodeIdJsonConverter()}
  17. };
  18. public GenIdStorage<Supply> Supplies = new();
  19. public GenIdStorage<Network> Networks = new();
  20. public GenIdStorage<Load> Loads = new();
  21. public GenIdStorage<Battery> Batteries = new();
  22. public List<List<Network>>? GroupedNets;
  23. public readonly struct NodeId : IEquatable<NodeId>
  24. {
  25. public readonly int Index;
  26. public readonly int Generation;
  27. public long Combined => (uint) Index | ((long) Generation << 32);
  28. public NodeId(int index, int generation)
  29. {
  30. Index = index;
  31. Generation = generation;
  32. }
  33. public NodeId(long combined)
  34. {
  35. Index = (int) combined;
  36. Generation = (int) (combined >> 32);
  37. }
  38. public bool Equals(NodeId other)
  39. {
  40. return Index == other.Index && Generation == other.Generation;
  41. }
  42. public override bool Equals(object? obj)
  43. {
  44. return obj is NodeId other && Equals(other);
  45. }
  46. public override int GetHashCode()
  47. {
  48. return HashCode.Combine(Index, Generation);
  49. }
  50. public static bool operator ==(NodeId left, NodeId right)
  51. {
  52. return left.Equals(right);
  53. }
  54. public static bool operator !=(NodeId left, NodeId right)
  55. {
  56. return !left.Equals(right);
  57. }
  58. public override string ToString()
  59. {
  60. return $"{Index} (G{Generation})";
  61. }
  62. }
  63. public static class GenIdStorage
  64. {
  65. public static GenIdStorage<T> FromEnumerable<T>(IEnumerable<(NodeId, T)> enumerable)
  66. {
  67. return GenIdStorage<T>.FromEnumerable(enumerable);
  68. }
  69. }
  70. public sealed class GenIdStorage<T>
  71. {
  72. // This is an implementation of "generational index" storage.
  73. //
  74. // The advantage of this storage method is extremely fast, O(1) lookup (way faster than Dictionary).
  75. // Resolving a value in the storage is a single array load and generation compare. Extremely fast.
  76. // Indices can also be cached into temporary
  77. // Disadvantages are that storage cannot be shrunk, and sparse storage is inefficient space wise.
  78. // Also this implementation does not have optimizations necessary to make sparse iteration efficient.
  79. //
  80. // The idea here is that the index type (NodeId in this case) has both an index and a generation.
  81. // The index is an integer index into the storage array, the generation is used to avoid use-after-free.
  82. //
  83. // Empty slots in the array form a linked list of free slots.
  84. // When we allocate a new slot, we pop one link off this linked list and hand out its index + generation.
  85. //
  86. // When we free a node, we bump the generation of the slot and make it the head of the linked list.
  87. // The generation being bumped means that any IDs to this slot will fail to resolve (generation mismatch).
  88. //
  89. // Index of the next free slot to use when allocating a new one.
  90. // If this is int.MaxValue,
  91. // it basically means "no slot available" and the next allocation call should resize the array storage.
  92. private int _nextFree = int.MaxValue;
  93. private Slot[] _storage;
  94. public int Count { get; private set; }
  95. public ref T this[NodeId id]
  96. {
  97. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  98. get
  99. {
  100. ref var slot = ref _storage[id.Index];
  101. if (slot.Generation != id.Generation)
  102. ThrowKeyNotFound();
  103. return ref slot.Value;
  104. }
  105. }
  106. public GenIdStorage()
  107. {
  108. _storage = Array.Empty<Slot>();
  109. }
  110. public static GenIdStorage<T> FromEnumerable(IEnumerable<(NodeId, T)> enumerable)
  111. {
  112. var storage = new GenIdStorage<T>();
  113. // Cache enumerable to array to do double enumeration.
  114. var cache = enumerable.ToArray();
  115. if (cache.Length == 0)
  116. return storage;
  117. // Figure out max size necessary and set storage size to that.
  118. var maxSize = cache.Max(tup => tup.Item1.Index) + 1;
  119. storage._storage = new Slot[maxSize];
  120. // Fill in slots.
  121. foreach (var (id, value) in cache)
  122. {
  123. DebugTools.Assert(id.Generation != 0, "Generation cannot be 0");
  124. ref var slot = ref storage._storage[id.Index];
  125. DebugTools.Assert(slot.Generation == 0, "Duplicate key index!");
  126. slot.Generation = id.Generation;
  127. slot.Value = value;
  128. slot.NextSlot = -1;
  129. }
  130. // Go through empty slots and build the free chain.
  131. var nextFree = int.MaxValue;
  132. for (var i = 0; i < storage._storage.Length; i++)
  133. {
  134. ref var slot = ref storage._storage[i];
  135. if (slot.NextSlot == -1)
  136. // Slot in use.
  137. continue;
  138. slot.NextSlot = nextFree;
  139. nextFree = i;
  140. }
  141. storage.Count = cache.Length;
  142. storage._nextFree = nextFree;
  143. // Sanity check for a former bug with save/load.
  144. DebugTools.Assert(storage.Values.Count() == storage.Count);
  145. return storage;
  146. }
  147. public ref T Allocate(out NodeId id)
  148. {
  149. if (_nextFree == int.MaxValue)
  150. ReAllocate();
  151. var idx = _nextFree;
  152. ref var slot = ref _storage[idx];
  153. Count += 1;
  154. _nextFree = slot.NextSlot;
  155. // NextSlot = -1 indicates filled.
  156. slot.NextSlot = -1;
  157. id = new NodeId(idx, slot.Generation);
  158. return ref slot.Value;
  159. }
  160. public void Free(NodeId id)
  161. {
  162. var idx = id.Index;
  163. ref var slot = ref _storage[idx];
  164. if (slot.Generation != id.Generation)
  165. ThrowKeyNotFound();
  166. if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
  167. slot.Value = default!;
  168. Count -= 1;
  169. slot.Generation += 1;
  170. slot.NextSlot = _nextFree;
  171. _nextFree = idx;
  172. }
  173. [MethodImpl(MethodImplOptions.NoInlining)]
  174. private void ReAllocate()
  175. {
  176. var oldLength = _storage.Length;
  177. var newLength = Math.Max(oldLength, 2) * 2;
  178. ReAllocateTo(newLength);
  179. }
  180. private void ReAllocateTo(int newSize)
  181. {
  182. var oldLength = _storage.Length;
  183. DebugTools.Assert(newSize >= oldLength, "Cannot shrink GenIdStorage");
  184. Array.Resize(ref _storage, newSize);
  185. for (var i = oldLength; i < newSize - 1; i++)
  186. {
  187. // Build linked list chain for newly allocated segment.
  188. ref var slot = ref _storage[i];
  189. slot.NextSlot = i + 1;
  190. // Every slot starts at generation 1.
  191. slot.Generation = 1;
  192. }
  193. _storage[^1].NextSlot = _nextFree;
  194. _nextFree = oldLength;
  195. }
  196. public ValuesCollection Values => new(this);
  197. private struct Slot
  198. {
  199. // Next link on the free list. if int.MaxValue then this is the tail.
  200. // If negative, this slot is occupied.
  201. public int NextSlot;
  202. // Generation of this slot.
  203. public int Generation;
  204. public T Value;
  205. }
  206. [MethodImpl(MethodImplOptions.NoInlining)]
  207. private static void ThrowKeyNotFound()
  208. {
  209. throw new KeyNotFoundException();
  210. }
  211. public readonly struct ValuesCollection : IReadOnlyCollection<T>
  212. {
  213. private readonly GenIdStorage<T> _owner;
  214. public ValuesCollection(GenIdStorage<T> owner)
  215. {
  216. _owner = owner;
  217. }
  218. public Enumerator GetEnumerator()
  219. {
  220. return new Enumerator(_owner);
  221. }
  222. public int Count => _owner.Count;
  223. IEnumerator IEnumerable.GetEnumerator()
  224. {
  225. return GetEnumerator();
  226. }
  227. IEnumerator<T> IEnumerable<T>.GetEnumerator()
  228. {
  229. return GetEnumerator();
  230. }
  231. public struct Enumerator : IEnumerator<T>
  232. {
  233. // Save the array in the enumerator here to avoid a few pointer dereferences.
  234. private readonly Slot[] _owner;
  235. private int _index;
  236. public Enumerator(GenIdStorage<T> owner)
  237. {
  238. _owner = owner._storage;
  239. Current = default!;
  240. _index = -1;
  241. }
  242. public bool MoveNext()
  243. {
  244. while (true)
  245. {
  246. _index += 1;
  247. if (_index >= _owner.Length)
  248. return false;
  249. ref var slot = ref _owner[_index];
  250. if (slot.NextSlot < 0)
  251. {
  252. Current = slot.Value;
  253. return true;
  254. }
  255. }
  256. }
  257. public void Reset()
  258. {
  259. _index = -1;
  260. }
  261. object IEnumerator.Current => Current!;
  262. public T Current { get; private set; }
  263. public void Dispose()
  264. {
  265. }
  266. }
  267. }
  268. }
  269. public sealed class NodeIdJsonConverter : JsonConverter<NodeId>
  270. {
  271. public override NodeId Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
  272. {
  273. return new NodeId(reader.GetInt64());
  274. }
  275. public override void Write(Utf8JsonWriter writer, NodeId value, JsonSerializerOptions options)
  276. {
  277. writer.WriteNumberValue(value.Combined);
  278. }
  279. }
  280. public sealed class Supply
  281. {
  282. [ViewVariables] public NodeId Id;
  283. // == Static parameters ==
  284. [ViewVariables(VVAccess.ReadWrite)] public bool Enabled = true;
  285. [ViewVariables(VVAccess.ReadWrite)] public bool Paused;
  286. [ViewVariables(VVAccess.ReadWrite)] public float MaxSupply;
  287. [ViewVariables(VVAccess.ReadWrite)] public float SupplyRampRate = 5000;
  288. [ViewVariables(VVAccess.ReadWrite)] public float SupplyRampTolerance = 5000;
  289. // == Runtime parameters ==
  290. /// <summary>
  291. /// Actual power supplied last network update.
  292. /// </summary>
  293. [ViewVariables(VVAccess.ReadWrite)] public float CurrentSupply;
  294. /// <summary>
  295. /// The amount of power we WANT to be supplying to match grid load.
  296. /// </summary>
  297. [ViewVariables(VVAccess.ReadWrite)] [JsonIgnore]
  298. public float SupplyRampTarget;
  299. /// <summary>
  300. /// Position of the supply ramp.
  301. /// </summary>
  302. [ViewVariables(VVAccess.ReadWrite)] public float SupplyRampPosition;
  303. [ViewVariables] [JsonIgnore] public NodeId LinkedNetwork;
  304. /// <summary>
  305. /// Supply available during a tick. The actual current supply will be less than or equal to this. Used
  306. /// during calculations.
  307. /// </summary>
  308. [JsonIgnore] public float AvailableSupply;
  309. }
  310. public sealed class Load
  311. {
  312. [ViewVariables] public NodeId Id;
  313. // == Static parameters ==
  314. [ViewVariables(VVAccess.ReadWrite)] public bool Enabled = true;
  315. [ViewVariables(VVAccess.ReadWrite)] public bool Paused;
  316. [ViewVariables(VVAccess.ReadWrite)] public float DesiredPower;
  317. // == Runtime parameters ==
  318. [ViewVariables(VVAccess.ReadWrite)] public float ReceivingPower;
  319. [ViewVariables] [JsonIgnore] public NodeId LinkedNetwork;
  320. }
  321. public sealed class Battery
  322. {
  323. [ViewVariables] public NodeId Id;
  324. // == Static parameters ==
  325. [ViewVariables(VVAccess.ReadWrite)] public bool Enabled = true;
  326. [ViewVariables(VVAccess.ReadWrite)] public bool Paused;
  327. [ViewVariables(VVAccess.ReadWrite)] public bool CanDischarge = true;
  328. [ViewVariables(VVAccess.ReadWrite)] public bool CanCharge = true;
  329. [ViewVariables(VVAccess.ReadWrite)] public float Capacity;
  330. [ViewVariables(VVAccess.ReadWrite)] public float MaxChargeRate;
  331. [ViewVariables(VVAccess.ReadWrite)] public float MaxThroughput; // 0 = infinite cuz imgui
  332. [ViewVariables(VVAccess.ReadWrite)] public float MaxSupply;
  333. /// <summary>
  334. /// The batteries supply ramp tolerance. This is an always available supply added to the ramped supply.
  335. /// </summary>
  336. /// <remarks>
  337. /// Note that this MUST BE GREATER THAN ZERO, otherwise the current battery ramping calculation will not work.
  338. /// </remarks>
  339. [ViewVariables(VVAccess.ReadWrite)] public float SupplyRampTolerance = 5000;
  340. [ViewVariables(VVAccess.ReadWrite)] public float SupplyRampRate = 5000;
  341. [ViewVariables(VVAccess.ReadWrite)] public float Efficiency = 1;
  342. // == Runtime parameters ==
  343. [ViewVariables(VVAccess.ReadWrite)] public float SupplyRampPosition;
  344. [ViewVariables(VVAccess.ReadWrite)] public float CurrentSupply;
  345. [ViewVariables(VVAccess.ReadWrite)] public float CurrentStorage;
  346. [ViewVariables(VVAccess.ReadWrite)] public float CurrentReceiving;
  347. [ViewVariables(VVAccess.ReadWrite)] public float LoadingNetworkDemand;
  348. [ViewVariables(VVAccess.ReadWrite)] [JsonIgnore]
  349. public bool SupplyingMarked;
  350. [ViewVariables(VVAccess.ReadWrite)] [JsonIgnore]
  351. public bool LoadingMarked;
  352. /// <summary>
  353. /// Amount of supply that the battery can provide this tick.
  354. /// </summary>
  355. [ViewVariables(VVAccess.ReadWrite)] [JsonIgnore]
  356. public float AvailableSupply;
  357. [ViewVariables(VVAccess.ReadWrite)] [JsonIgnore]
  358. public float DesiredPower;
  359. [ViewVariables(VVAccess.ReadWrite)] [JsonIgnore]
  360. public float SupplyRampTarget;
  361. [ViewVariables(VVAccess.ReadWrite)] [JsonIgnore]
  362. public NodeId LinkedNetworkCharging;
  363. [ViewVariables(VVAccess.ReadWrite)] [JsonIgnore]
  364. public NodeId LinkedNetworkDischarging;
  365. /// <summary>
  366. /// Theoretical maximum effective supply, assuming the network providing power to this battery continues to supply it
  367. /// at the same rate.
  368. /// </summary>
  369. [ViewVariables]
  370. public float MaxEffectiveSupply;
  371. }
  372. // Readonly breaks json serialization.
  373. [SuppressMessage("ReSharper", "FieldCanBeMadeReadOnly.Local")]
  374. public sealed class Network
  375. {
  376. [ViewVariables] public NodeId Id;
  377. /// <summary>
  378. /// Power generators
  379. /// </summary>
  380. [ViewVariables] public List<NodeId> Supplies = new();
  381. /// <summary>
  382. /// Power consumers.
  383. /// </summary>
  384. [ViewVariables] public List<NodeId> Loads = new();
  385. /// <summary>
  386. /// Batteries that are draining power from this network (connected to the INPUT port of the battery).
  387. /// </summary>
  388. [ViewVariables] public List<NodeId> BatteryLoads = new();
  389. /// <summary>
  390. /// Batteries that are supplying power to this network (connected to the OUTPUT port of the battery).
  391. /// </summary>
  392. [ViewVariables] public List<NodeId> BatterySupplies = new();
  393. /// <summary>
  394. /// The total load on the power network as of last tick.
  395. /// </summary>
  396. [ViewVariables] public float LastCombinedLoad = 0f;
  397. /// <summary>
  398. /// Available supply, including both normal supplies and batteries.
  399. /// </summary>
  400. [ViewVariables] public float LastCombinedSupply = 0f;
  401. /// <summary>
  402. /// Theoretical maximum supply, including both normal supplies and batteries.
  403. /// </summary>
  404. [ViewVariables] public float LastCombinedMaxSupply = 0f;
  405. [ViewVariables] [JsonIgnore] public int Height;
  406. }
  407. }
  408. }