Merge branch 'master' into replace-sounds-with-sound-specifier

# Conflicts:
#	Content.Server/Hands/Components/HandsComponent.cs
#	Content.Server/Light/Components/ExpendableLightComponent.cs
#	Content.Shared/Light/Component/SharedExpendableLightComponent.cs
This commit is contained in:
Galactic Chimp
2021-07-31 13:16:03 +02:00
105 changed files with 1483 additions and 1319 deletions

View File

@@ -19,7 +19,6 @@ namespace Content.Server.Power.EntitySystems
private readonly HashSet<PowerNet> _powerNetReconnectQueue = new();
private readonly HashSet<ApcNet> _apcNetReconnectQueue = new();
private int _nextId = 1;
private readonly BatteryRampPegSolver _solver = new();
public override void Initialize()
@@ -50,7 +49,7 @@ namespace Content.Server.Power.EntitySystems
private void ApcPowerReceiverShutdown(EntityUid uid, ApcPowerReceiverComponent component,
ComponentShutdown args)
{
_powerState.Loads.Remove(component.NetworkLoad.Id);
_powerState.Loads.Free(component.NetworkLoad.Id);
}
private static void ApcPowerReceiverPaused(
@@ -68,7 +67,7 @@ namespace Content.Server.Power.EntitySystems
private void BatteryShutdown(EntityUid uid, PowerNetworkBatteryComponent component, ComponentShutdown args)
{
_powerState.Batteries.Remove(component.NetworkBattery.Id);
_powerState.Batteries.Free(component.NetworkBattery.Id);
}
private static void BatteryPaused(EntityUid uid, PowerNetworkBatteryComponent component, EntityPausedEvent args)
@@ -83,7 +82,7 @@ namespace Content.Server.Power.EntitySystems
private void PowerConsumerShutdown(EntityUid uid, PowerConsumerComponent component, ComponentShutdown args)
{
_powerState.Loads.Remove(component.NetworkLoad.Id);
_powerState.Loads.Free(component.NetworkLoad.Id);
}
private static void PowerConsumerPaused(EntityUid uid, PowerConsumerComponent component, EntityPausedEvent args)
@@ -98,7 +97,7 @@ namespace Content.Server.Power.EntitySystems
private void PowerSupplierShutdown(EntityUid uid, PowerSupplierComponent component, ComponentShutdown args)
{
_powerState.Supplies.Remove(component.NetworkSupply.Id);
_powerState.Supplies.Free(component.NetworkSupply.Id);
}
private static void PowerSupplierPaused(EntityUid uid, PowerSupplierComponent component, EntityPausedEvent args)
@@ -113,7 +112,7 @@ namespace Content.Server.Power.EntitySystems
public void DestroyPowerNet(PowerNet powerNet)
{
_powerState.Networks.Remove(powerNet.NetworkNode.Id);
_powerState.Networks.Free(powerNet.NetworkNode.Id);
}
public void QueueReconnectPowerNet(PowerNet powerNet)
@@ -128,7 +127,7 @@ namespace Content.Server.Power.EntitySystems
public void DestroyApcNet(ApcNet apcNet)
{
_powerState.Networks.Remove(apcNet.NetworkNode.Id);
_powerState.Networks.Free(apcNet.NetworkNode.Id);
}
public void QueueReconnectApcNet(ApcNet apcNet)
@@ -213,26 +212,22 @@ namespace Content.Server.Power.EntitySystems
private void AllocLoad(PowerState.Load load)
{
load.Id = AllocId();
_powerState.Loads.Add(load.Id, load);
_powerState.Loads.Allocate(out load.Id) = load;
}
private void AllocSupply(PowerState.Supply supply)
{
supply.Id = AllocId();
_powerState.Supplies.Add(supply.Id, supply);
_powerState.Supplies.Allocate(out supply.Id) = supply;
}
private void AllocBattery(PowerState.Battery battery)
{
battery.Id = AllocId();
_powerState.Batteries.Add(battery.Id, battery);
_powerState.Batteries.Allocate(out battery.Id) = battery;
}
private void AllocNetwork(PowerState.Network network)
{
network.Id = AllocId();
_powerState.Networks.Add(network.Id, network);
_powerState.Networks.Allocate(out network.Id) = network;
}
private static void DoReconnectApcNet(ApcNet net)
@@ -296,11 +291,6 @@ namespace Content.Server.Power.EntitySystems
battery.NetworkBattery.LinkedNetworkDischarging = netNode.Id;
}
}
private PowerState.NodeId AllocId()
{
return new(_nextId++);
}
}
/// <summary>

View File

@@ -1,181 +0,0 @@
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);
}
}
}
}
}

View File

@@ -1,40 +1,51 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text.Json;
using System.Text.Json.Serialization;
using Robust.Shared.Utility;
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 GenIdStorage<Supply> Supplies = new();
public GenIdStorage<Network> Networks = new();
public GenIdStorage<Load> Loads = new();
public GenIdStorage<Battery> Batteries = new();
public readonly struct NodeId : IEquatable<NodeId>
{
public readonly int Id;
public readonly int Index;
public readonly int Generation;
public NodeId(int id)
public long Combined => (uint) Index | ((long) Generation << 32);
public NodeId(int index, int generation)
{
Id = id;
Index = index;
Generation = generation;
}
public NodeId(long combined)
{
Index = (int) combined;
Generation = (int) (combined >> 32);
}
public bool Equals(NodeId other)
{
return Id == other.Id;
return Index == other.Index && Generation == other.Generation;
}
public override bool Equals(object? obj)
@@ -44,7 +55,7 @@ namespace Content.Server.Power.Pow3r
public override int GetHashCode()
{
return Id;
return HashCode.Combine(Index, Generation);
}
public static bool operator ==(NodeId left, NodeId right)
@@ -59,7 +70,261 @@ namespace Content.Server.Power.Pow3r
public override string ToString()
{
return Id.ToString();
return $"{Index} (G{Generation})";
}
}
public static class GenIdStorage
{
public static GenIdStorage<T> FromEnumerable<T>(IEnumerable<(NodeId, T)> enumerable)
{
return GenIdStorage<T>.FromEnumerable(enumerable);
}
}
public sealed class GenIdStorage<T>
{
// This is an implementation of "generational index" storage.
//
// The advantage of this storage method is extremely fast, O(1) lookup (way faster than Dictionary).
// Resolving a value in the storage is a single array load and generation compare. Extremely fast.
// Indices can also be cached into temporary
// Disadvantages are that storage cannot be shrunk, and sparse storage is inefficient space wise.
// Also this implementation does not have optimizations necessary to make sparse iteration efficient.
//
// The idea here is that the index type (NodeId in this case) has both an index and a generation.
// The index is an integer index into the storage array, the generation is used to avoid use-after-free.
//
// Empty slots in the array form a linked list of free slots.
// When we allocate a new slot, we pop one link off this linked list and hand out its index + generation.
//
// When we free a node, we bump the generation of the slot and make it the head of the linked list.
// The generation being bumped means that any IDs to this slot will fail to resolve (generation mismatch).
//
// Index of the next free slot to use when allocating a new one.
// If this is int.MaxValue,
// it basically means "no slot available" and the next allocation call should resize the array storage.
private int _nextFree = int.MaxValue;
private Slot[] _storage;
public int Count { get; private set; }
public ref T this[NodeId id]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
ref var slot = ref _storage[id.Index];
if (slot.Generation != id.Generation)
ThrowKeyNotFound();
return ref slot.Value;
}
}
public GenIdStorage()
{
_storage = Array.Empty<Slot>();
}
public static GenIdStorage<T> FromEnumerable(IEnumerable<(NodeId, T)> enumerable)
{
var storage = new GenIdStorage<T>();
// Cache enumerable to array to do double enumeration.
var cache = enumerable.ToArray();
if (cache.Length == 0)
return storage;
// Figure out max size necessary and set storage size to that.
var maxSize = cache.Max(tup => tup.Item1.Index) + 1;
storage._storage = new Slot[maxSize];
// Fill in slots.
foreach (var (id, value) in cache)
{
DebugTools.Assert(id.Generation != 0, "Generation cannot be 0");
ref var slot = ref storage._storage[id.Index];
DebugTools.Assert(slot.Generation == 0, "Duplicate key index!");
slot.Generation = id.Generation;
slot.Value = value;
}
// Go through empty slots and build the free chain.
var nextFree = int.MaxValue;
for (var i = 0; i < storage._storage.Length; i++)
{
ref var slot = ref storage._storage[i];
if (slot.Generation != 0)
// Slot in use.
continue;
slot.NextSlot = nextFree;
nextFree = i;
}
storage.Count = cache.Length;
storage._nextFree = nextFree;
return storage;
}
public ref T Allocate(out NodeId id)
{
if (_nextFree == int.MaxValue)
ReAllocate();
var idx = _nextFree;
ref var slot = ref _storage[idx];
Count += 1;
_nextFree = slot.NextSlot;
// NextSlot = -1 indicates filled.
slot.NextSlot = -1;
id = new NodeId(idx, slot.Generation);
return ref slot.Value;
}
public void Free(NodeId id)
{
var idx = id.Index;
ref var slot = ref _storage[idx];
if (slot.Generation != id.Generation)
ThrowKeyNotFound();
if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
slot.Value = default!;
Count -= 1;
slot.Generation += 1;
slot.NextSlot = _nextFree;
_nextFree = idx;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private void ReAllocate()
{
var oldLength = _storage.Length;
var newLength = Math.Max(oldLength, 2) * 2;
ReAllocateTo(newLength);
}
private void ReAllocateTo(int newSize)
{
var oldLength = _storage.Length;
DebugTools.Assert(newSize >= oldLength, "Cannot shrink GenIdStorage");
Array.Resize(ref _storage, newSize);
for (var i = oldLength; i < newSize - 1; i++)
{
// Build linked list chain for newly allocated segment.
ref var slot = ref _storage[i];
slot.NextSlot = i + 1;
// Every slot starts at generation 1.
slot.Generation = 1;
}
_storage[^1].NextSlot = _nextFree;
_nextFree = oldLength;
}
public ValuesCollection Values => new(this);
private struct Slot
{
// Next link on the free list. if int.MaxValue then this is the tail.
// If negative, this slot is occupied.
public int NextSlot;
// Generation of this slot.
public int Generation;
public T Value;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowKeyNotFound()
{
throw new KeyNotFoundException();
}
public readonly struct ValuesCollection : IReadOnlyCollection<T>
{
private readonly GenIdStorage<T> _owner;
public ValuesCollection(GenIdStorage<T> owner)
{
_owner = owner;
}
public Enumerator GetEnumerator()
{
return new Enumerator(_owner);
}
public int Count => _owner.Count;
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
return GetEnumerator();
}
public struct Enumerator : IEnumerator<T>
{
// Save the array in the enumerator here to avoid a few pointer dereferences.
private readonly Slot[] _owner;
private int _index;
public Enumerator(GenIdStorage<T> owner)
{
_owner = owner._storage;
Current = default!;
_index = -1;
}
public bool MoveNext()
{
while (true)
{
_index += 1;
if (_index >= _owner.Length)
return false;
ref var slot = ref _owner[_index];
if (slot.NextSlot < 0)
{
Current = slot.Value;
return true;
}
}
}
public void Reset()
{
_index = -1;
}
object IEnumerator.Current => Current!;
public T Current { get; private set; }
public void Dispose()
{
}
}
}
}
@@ -67,12 +332,12 @@ namespace Content.Server.Power.Pow3r
{
public override NodeId Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return new(reader.GetInt32());
return new NodeId(reader.GetInt64());
}
public override void Write(Utf8JsonWriter writer, NodeId value, JsonSerializerOptions options)
{
writer.WriteNumberValue(value.Id);
writer.WriteNumberValue(value.Combined);
}
}
@@ -186,22 +451,8 @@ namespace Content.Server.Power.Pow3r
// "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;
}
}
}