Optimise the puddle system to reach an equilibrium quickly. (#23776)

* Optimise the puddle system to reach an equilibrium quickly.

* Remove use of Linq
Try to be more efficient with Tuples

* review

---------

Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
This commit is contained in:
mr-bo-jangles
2024-01-11 13:22:56 +00:00
committed by GitHub
parent 1886941da6
commit 064d52db41

View File

@@ -24,6 +24,7 @@ using Content.Shared.Slippery;
using Content.Shared.StepTrigger.Components; using Content.Shared.StepTrigger.Components;
using Content.Shared.StepTrigger.Systems; using Content.Shared.StepTrigger.Systems;
using Robust.Server.Audio; using Robust.Server.Audio;
using Robust.Shared.Collections;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Map.Components; using Robust.Shared.Map.Components;
using Robust.Shared.Player; using Robust.Shared.Player;
@@ -73,6 +74,8 @@ public sealed partial class PuddleSystem : SharedPuddleSystem
// loses & then gains reagents in a single tick. // loses & then gains reagents in a single tick.
private HashSet<EntityUid> _deletionQueue = new(); private HashSet<EntityUid> _deletionQueue = new();
private EntityQuery<PuddleComponent> _puddleQuery;
/* /*
* TODO: Need some sort of way to do blood slash / vomit solution spill on its own * TODO: Need some sort of way to do blood slash / vomit solution spill on its own
* This would then evaporate into the puddle tile below * This would then evaporate into the puddle tile below
@@ -83,6 +86,8 @@ public sealed partial class PuddleSystem : SharedPuddleSystem
{ {
base.Initialize(); base.Initialize();
_puddleQuery = GetEntityQuery<PuddleComponent>();
// Shouldn't need re-anchoring. // Shouldn't need re-anchoring.
SubscribeLocalEvent<PuddleComponent, AnchorStateChangedEvent>(OnAnchorChanged); SubscribeLocalEvent<PuddleComponent, AnchorStateChangedEvent>(OnAnchorChanged);
SubscribeLocalEvent<PuddleComponent, ExaminedEvent>(HandlePuddleExamined); SubscribeLocalEvent<PuddleComponent, ExaminedEvent>(HandlePuddleExamined);
@@ -99,6 +104,7 @@ public sealed partial class PuddleSystem : SharedPuddleSystem
private void OnPuddleSpread(Entity<PuddleComponent> entity, ref SpreadNeighborsEvent args) private void OnPuddleSpread(Entity<PuddleComponent> entity, ref SpreadNeighborsEvent args)
{ {
// Overflow is the source of the overflowing liquid. This contains the excess fluid above overflow limit (20u)
var overflow = GetOverflowSolution(entity.Owner, entity.Comp); var overflow = GetOverflowSolution(entity.Owner, entity.Comp);
if (overflow.Volume == FixedPoint2.Zero) if (overflow.Volume == FixedPoint2.Zero)
@@ -107,50 +113,9 @@ public sealed partial class PuddleSystem : SharedPuddleSystem
return; return;
} }
var puddleQuery = GetEntityQuery<PuddleComponent>();
// For overflows, we never go to a fully evaporative tile just to avoid continuously having to mop it. // For overflows, we never go to a fully evaporative tile just to avoid continuously having to mop it.
// First we overflow to neighbors with overflow capacity // First we go to free tiles.
if (args.Neighbors.Count > 0)
{
_random.Shuffle(args.Neighbors);
// Overflow to neighbors with remaining space.
foreach (var neighbor in args.Neighbors)
{
if (!puddleQuery.TryGetComponent(neighbor, out var puddle) ||
!_solutionContainerSystem.ResolveSolution(neighbor, puddle.SolutionName, ref puddle.Solution, out var neighborSolution) ||
CanFullyEvaporate(neighborSolution))
{
continue;
}
var remaining = puddle.OverflowVolume - neighborSolution.Volume;
if (remaining <= FixedPoint2.Zero)
continue;
var split = overflow.SplitSolution(remaining);
if (!_solutionContainerSystem.TryAddSolution(puddle.Solution.Value, split))
continue;
args.Updates--;
EnsureComp<ActiveEdgeSpreaderComponent>(neighbor);
if (args.Updates <= 0)
break;
}
if (overflow.Volume == FixedPoint2.Zero)
{
RemCompDeferred<ActiveEdgeSpreaderComponent>(entity);
return;
}
}
// Then we go to free tiles.
// Need to go even if we have a little remainder to avoid solution sploshing around internally // Need to go even if we have a little remainder to avoid solution sploshing around internally
// for ages. // for ages.
if (args.NeighborFreeTiles.Count > 0 && args.Updates > 0) if (args.NeighborFreeTiles.Count > 0 && args.Updates > 0)
@@ -172,26 +137,142 @@ public sealed partial class PuddleSystem : SharedPuddleSystem
return; return;
} }
// Then we go to anything else. // Then we overflow to neighbors with overflow capacity
if (overflow.Volume > FixedPoint2.Zero && args.Neighbors.Count > 0 && args.Updates > 0) if (args.Neighbors.Count > 0)
{ {
var spillPerNeighbor = overflow.Volume / args.Neighbors.Count; var resolvedNeighbourSolutions = new ValueList<(Solution neighborSolution, PuddleComponent puddle, EntityUid neighbor)>();
// Resolve all our neighbours first, so we can use their properties to decide who to operate on first.
foreach (var neighbor in args.Neighbors) foreach (var neighbor in args.Neighbors)
{ {
// Overflow to neighbours (unless it's pure water) if (!_puddleQuery.TryGetComponent(neighbor, out var puddle) ||
if (!puddleQuery.TryGetComponent(neighbor, out var puddle) || !_solutionContainerSystem.ResolveSolution(neighbor, puddle.SolutionName, ref puddle.Solution,
!_solutionContainerSystem.ResolveSolution(neighbor, puddle.SolutionName, ref puddle.Solution, out var neighborSolution) || out var neighborSolution) ||
CanFullyEvaporate(neighborSolution)) CanFullyEvaporate(neighborSolution))
{ {
continue; continue;
} }
var split = overflow.SplitSolution(spillPerNeighbor); resolvedNeighbourSolutions.Add(
(neighborSolution, puddle, neighbor)
);
}
if (!_solutionContainerSystem.TryAddSolution(puddle.Solution.Value, split)) // We want to deal with our neighbours by lowest current volume to highest, as this allows us to fill up our low points quickly.
resolvedNeighbourSolutions.Sort(
(x, y) =>
x.neighborSolution.Volume.CompareTo(y.neighborSolution.Volume));
// Overflow to neighbors with remaining space.
foreach (var (neighborSolution, puddle, neighbor) in resolvedNeighbourSolutions)
{
// Water doesn't flow uphill
if (neighborSolution.Volume >= (overflow.Volume + puddle.OverflowVolume))
{
continue;
}
// Work out how much we could send into this neighbour without overflowing it, and send up to that much
var remaining = puddle.OverflowVolume - neighborSolution.Volume;
// If we can't send anything, then skip this neighbour
if (remaining <= FixedPoint2.Zero)
continue; continue;
// We don't want to spill over to make high points either.
if (neighborSolution.Volume + remaining >= (overflow.Volume + puddle.OverflowVolume))
{
continue;
}
var split = overflow.SplitSolution(remaining);
if (puddle.Solution != null && !_solutionContainerSystem.TryAddSolution(puddle.Solution.Value, split))
continue;
args.Updates--;
EnsureComp<ActiveEdgeSpreaderComponent>(neighbor);
if (args.Updates <= 0)
break;
}
// If there is nothing left to overflow from our tile, then we'll stop this tile being a active spreader
if (overflow.Volume == FixedPoint2.Zero)
{
RemCompDeferred<ActiveEdgeSpreaderComponent>(entity);
return;
}
}
// Then we go to anything else.
if (overflow.Volume > FixedPoint2.Zero && args.Neighbors.Count > 0 && args.Updates > 0)
{
var resolvedNeighbourSolutions =
new ValueList<(Solution neighborSolution, PuddleComponent puddle, EntityUid neighbor)>();
// Keep track of the total volume in the area
FixedPoint2 totalVolume = 0;
// Resolve all our neighbours so that we can use their properties to decide who to act on first
foreach (var neighbor in args.Neighbors)
{
if (!_puddleQuery.TryGetComponent(neighbor, out var puddle) ||
!_solutionContainerSystem.ResolveSolution(neighbor, puddle.SolutionName, ref puddle.Solution,
out var neighborSolution) ||
CanFullyEvaporate(neighborSolution))
{
continue;
}
resolvedNeighbourSolutions.Add((neighborSolution, puddle, neighbor));
totalVolume += neighborSolution.Volume;
}
// We should act on neighbours by their total volume.
resolvedNeighbourSolutions.Sort(
(x, y) =>
x.neighborSolution.Volume.CompareTo(y.neighborSolution.Volume)
);
// Overflow to neighbors with remaining total allowed space (1000u) above the overflow volume (20u).
foreach (var (neighborSolution, puddle, neighbor) in resolvedNeighbourSolutions)
{
// What the source tiles current volume is.
var sourceCurrentVolume = overflow.Volume + puddle.OverflowVolume;
// Water doesn't flow uphill
if (neighborSolution.Volume >= sourceCurrentVolume)
{
continue;
}
// We're in the low point in this area, let the neighbour tiles have a chance to spread to us first.
var idealAverageVolume =
(totalVolume + overflow.Volume + puddle.OverflowVolume) / (args.Neighbors.Count + 1);
if (idealAverageVolume > sourceCurrentVolume)
{
continue;
}
// Work our how far off the ideal average this neighbour is.
var spillThisNeighbor = idealAverageVolume - neighborSolution.Volume;
// Skip if we want to spill negative amounts of fluid to this neighbour
if (spillThisNeighbor < FixedPoint2.Zero)
{
continue;
}
// Try to send them as much towards the average ideal as we can
var split = overflow.SplitSolution(spillThisNeighbor);
// If we can't do it, move on.
if (puddle.Solution != null && !_solutionContainerSystem.TryAddSolution(puddle.Solution.Value, split))
continue;
// If we succeed, then ensure that this neighbour is also able to spread it's overflow onwards
EnsureComp<ActiveEdgeSpreaderComponent>(neighbor); EnsureComp<ActiveEdgeSpreaderComponent>(neighbor);
args.Updates--; args.Updates--;
@@ -219,7 +300,8 @@ public sealed partial class PuddleSystem : SharedPuddleSystem
if (!_random.Prob(0.5f)) if (!_random.Prob(0.5f))
return; return;
if (!_solutionContainerSystem.ResolveSolution(entity.Owner, entity.Comp.SolutionName, ref entity.Comp.Solution, out var solution)) if (!_solutionContainerSystem.ResolveSolution(entity.Owner, entity.Comp.SolutionName, ref entity.Comp.Solution,
out var solution))
return; return;
_popups.PopupEntity(Loc.GetString("puddle-component-slipped-touch-reaction", ("puddle", entity.Owner)), _popups.PopupEntity(Loc.GetString("puddle-component-slipped-touch-reaction", ("puddle", entity.Owner)),
@@ -238,6 +320,7 @@ public sealed partial class PuddleSystem : SharedPuddleSystem
{ {
Del(ent); Del(ent);
} }
_deletionQueue.Clear(); _deletionQueue.Clear();
TickEvaporation(); TickEvaporation();
@@ -245,7 +328,8 @@ public sealed partial class PuddleSystem : SharedPuddleSystem
private void OnPuddleInit(Entity<PuddleComponent> entity, ref ComponentInit args) private void OnPuddleInit(Entity<PuddleComponent> entity, ref ComponentInit args)
{ {
_solutionContainerSystem.EnsureSolution(entity.Owner, entity.Comp.SolutionName, FixedPoint2.New(PuddleVolume), out _); _solutionContainerSystem.EnsureSolution(entity.Owner, entity.Comp.SolutionName, FixedPoint2.New(PuddleVolume),
out _);
} }
private void OnSolutionUpdate(Entity<PuddleComponent> entity, ref SolutionContainerChangedEvent args) private void OnSolutionUpdate(Entity<PuddleComponent> entity, ref SolutionContainerChangedEvent args)
@@ -266,7 +350,8 @@ public sealed partial class PuddleSystem : SharedPuddleSystem
UpdateAppearance(entity, entity.Comp); UpdateAppearance(entity, entity.Comp);
} }
private void UpdateAppearance(EntityUid uid, PuddleComponent? puddleComponent = null, AppearanceComponent? appearance = null) private void UpdateAppearance(EntityUid uid, PuddleComponent? puddleComponent = null,
AppearanceComponent? appearance = null)
{ {
if (!Resolve(uid, ref puddleComponent, ref appearance, false)) if (!Resolve(uid, ref puddleComponent, ref appearance, false))
{ {
@@ -276,7 +361,8 @@ public sealed partial class PuddleSystem : SharedPuddleSystem
var volume = FixedPoint2.Zero; var volume = FixedPoint2.Zero;
Color color = Color.White; Color color = Color.White;
if (_solutionContainerSystem.ResolveSolution(uid, puddleComponent.SolutionName, ref puddleComponent.Solution, out var solution)) if (_solutionContainerSystem.ResolveSolution(uid, puddleComponent.SolutionName, ref puddleComponent.Solution,
out var solution))
{ {
volume = solution.Volume / puddleComponent.OverflowVolume; volume = solution.Volume / puddleComponent.OverflowVolume;
@@ -294,7 +380,8 @@ public sealed partial class PuddleSystem : SharedPuddleSystem
continue; continue;
var interpolateValue = quantity.Float() / solution.Volume.Float(); var interpolateValue = quantity.Float() / solution.Volume.Float();
color = Color.InterpolateBetween(color, _prototypeManager.Index<ReagentPrototype>(standout).SubstanceColor, interpolateValue); color = Color.InterpolateBetween(color,
_prototypeManager.Index<ReagentPrototype>(standout).SubstanceColor, interpolateValue);
} }
} }
@@ -347,6 +434,7 @@ public sealed partial class PuddleSystem : SharedPuddleSystem
var reagentProto = _prototypeManager.Index<ReagentPrototype>(reagent.Prototype); var reagentProto = _prototypeManager.Index<ReagentPrototype>(reagent.Prototype);
maxViscosity = Math.Max(maxViscosity, reagentProto.Viscosity); maxViscosity = Math.Max(maxViscosity, reagentProto.Viscosity);
} }
if (maxViscosity > 0) if (maxViscosity > 0)
{ {
var comp = EnsureComp<SlowContactsComponent>(uid); var comp = EnsureComp<SlowContactsComponent>(uid);
@@ -398,7 +486,8 @@ public sealed partial class PuddleSystem : SharedPuddleSystem
if (!Resolve(uid, ref puddleComponent)) if (!Resolve(uid, ref puddleComponent))
return FixedPoint2.Zero; return FixedPoint2.Zero;
return _solutionContainerSystem.ResolveSolution(uid, puddleComponent.SolutionName, ref puddleComponent.Solution, out var solution) return _solutionContainerSystem.ResolveSolution(uid, puddleComponent.SolutionName, ref puddleComponent.Solution,
out var solution)
? solution.Volume ? solution.Volume
: FixedPoint2.Zero; : FixedPoint2.Zero;
} }
@@ -422,7 +511,8 @@ public sealed partial class PuddleSystem : SharedPuddleSystem
return false; return false;
if (addedSolution.Volume == 0 || if (addedSolution.Volume == 0 ||
!_solutionContainerSystem.ResolveSolution(puddleUid, puddleComponent.SolutionName, ref puddleComponent.Solution)) !_solutionContainerSystem.ResolveSolution(puddleUid, puddleComponent.SolutionName,
ref puddleComponent.Solution))
{ {
return false; return false;
} }
@@ -470,14 +560,16 @@ public sealed partial class PuddleSystem : SharedPuddleSystem
/// </summary> /// </summary>
public Solution GetOverflowSolution(EntityUid uid, PuddleComponent? puddle = null) public Solution GetOverflowSolution(EntityUid uid, PuddleComponent? puddle = null)
{ {
if (!Resolve(uid, ref puddle) || !_solutionContainerSystem.ResolveSolution(uid, puddle.SolutionName, ref puddle.Solution)) if (!Resolve(uid, ref puddle) ||
!_solutionContainerSystem.ResolveSolution(uid, puddle.SolutionName, ref puddle.Solution))
{ {
return new Solution(0); return new Solution(0);
} }
// TODO: This is going to fail with struct solutions. // TODO: This is going to fail with struct solutions.
var remaining = puddle.OverflowVolume; var remaining = puddle.OverflowVolume;
var split = _solutionContainerSystem.SplitSolution(puddle.Solution.Value, CurrentVolume(uid, puddle) - remaining); var split = _solutionContainerSystem.SplitSolution(puddle.Solution.Value,
CurrentVolume(uid, puddle) - remaining);
return split; return split;
} }
@@ -521,10 +613,13 @@ public sealed partial class PuddleSystem : SharedPuddleSystem
targets.Add(owner); targets.Add(owner);
_reactive.DoEntityReaction(owner, splitSolution, ReactionMethod.Touch); _reactive.DoEntityReaction(owner, splitSolution, ReactionMethod.Touch);
_popups.PopupEntity(Loc.GetString("spill-land-spilled-on-other", ("spillable", uid), ("target", Identity.Entity(owner, EntityManager))), owner, PopupType.SmallCaution); _popups.PopupEntity(
Loc.GetString("spill-land-spilled-on-other", ("spillable", uid),
("target", Identity.Entity(owner, EntityManager))), owner, PopupType.SmallCaution);
} }
_color.RaiseEffect(solution.GetColor(_prototypeManager), targets, Filter.Pvs(uid, entityManager: EntityManager)); _color.RaiseEffect(solution.GetColor(_prototypeManager), targets,
Filter.Pvs(uid, entityManager: EntityManager));
return TrySpillAt(coordinates, solution, out puddleUid, sound); return TrySpillAt(coordinates, solution, out puddleUid, sound);
} }
@@ -553,7 +648,8 @@ public sealed partial class PuddleSystem : SharedPuddleSystem
/// <summary> /// <summary>
/// <see cref="TrySpillAt(Robust.Shared.Map.EntityCoordinates,Content.Shared.Chemistry.Components.Solution,out Robust.Shared.GameObjects.EntityUid,bool)"/> /// <see cref="TrySpillAt(Robust.Shared.Map.EntityCoordinates,Content.Shared.Chemistry.Components.Solution,out Robust.Shared.GameObjects.EntityUid,bool)"/>
/// </summary> /// </summary>
public bool TrySpillAt(EntityUid uid, Solution solution, out EntityUid puddleUid, bool sound = true, TransformComponent? transformComponent = null) public bool TrySpillAt(EntityUid uid, Solution solution, out EntityUid puddleUid, bool sound = true,
TransformComponent? transformComponent = null)
{ {
if (!Resolve(uid, ref transformComponent, false)) if (!Resolve(uid, ref transformComponent, false))
{ {
@@ -567,7 +663,8 @@ public sealed partial class PuddleSystem : SharedPuddleSystem
/// <summary> /// <summary>
/// <see cref="TrySpillAt(Robust.Shared.Map.EntityCoordinates,Content.Shared.Chemistry.Components.Solution,out Robust.Shared.GameObjects.EntityUid,bool)"/> /// <see cref="TrySpillAt(Robust.Shared.Map.EntityCoordinates,Content.Shared.Chemistry.Components.Solution,out Robust.Shared.GameObjects.EntityUid,bool)"/>
/// </summary> /// </summary>
public bool TrySpillAt(TileRef tileRef, Solution solution, out EntityUid puddleUid, bool sound = true, bool tileReact = true) public bool TrySpillAt(TileRef tileRef, Solution solution, out EntityUid puddleUid, bool sound = true,
bool tileReact = true)
{ {
if (solution.Volume <= 0) if (solution.Volume <= 0)
{ {
@@ -637,6 +734,7 @@ public sealed partial class PuddleSystem : SharedPuddleSystem
{ {
EnsureComp<ActiveEdgeSpreaderComponent>(puddleUid); EnsureComp<ActiveEdgeSpreaderComponent>(puddleUid);
} }
return true; return true;
} }
@@ -646,7 +744,6 @@ public sealed partial class PuddleSystem : SharedPuddleSystem
{ {
for (var i = solution.Contents.Count - 1; i >= 0; i--) for (var i = solution.Contents.Count - 1; i >= 0; i--)
{ {
var (reagent, quantity) = solution.Contents[i]; var (reagent, quantity) = solution.Contents[i];
var proto = _prototypeManager.Index<ReagentPrototype>(reagent.Prototype); var proto = _prototypeManager.Index<ReagentPrototype>(reagent.Prototype);
var removed = proto.ReactionTile(tileRef, quantity); var removed = proto.ReactionTile(tileRef, quantity);