From 6685146a1e3e188eac1fb2502920225c56cc08e1 Mon Sep 17 00:00:00 2001 From: Jezithyr Date: Mon, 6 May 2024 17:27:36 -0700 Subject: [PATCH] Moved Serverside solution container code to shared (yes that includes ensureSolution!) (#27478) * Added warning to tryGetSolution, moved SolutionContainer code to shared - Added an optional warning (false by default) to print an error if a solution is missing when using tryGetSolution methods - Moved ensuring solution containers to shared, left the old method stubs for compatability and marked them as obsolete. * Update SharedSolutionContainerSystem.cs * Update SharedSolutionContainerSystem.cs * Update SolutionContainerSystem.cs * Update SharedSolutionContainerSystem.cs * Fixing ensuring chem solutions always returning false on client - ensuring chem solutions will only return false on the client if it is waiting for a server solutionEntity to be synced * Added concentration helpers * fix whitespace --- .../EntitySystems/SolutionContainerSystem.cs | 175 +--------- .../SharedSolutionContainerSystem.cs | 326 +++++++++++++++++- 2 files changed, 328 insertions(+), 173 deletions(-) diff --git a/Content.Server/Chemistry/Containers/EntitySystems/SolutionContainerSystem.cs b/Content.Server/Chemistry/Containers/EntitySystems/SolutionContainerSystem.cs index 755312554c..3d99db1129 100644 --- a/Content.Server/Chemistry/Containers/EntitySystems/SolutionContainerSystem.cs +++ b/Content.Server/Chemistry/Containers/EntitySystems/SolutionContainerSystem.cs @@ -9,180 +9,37 @@ using System.Numerics; namespace Content.Server.Chemistry.Containers.EntitySystems; +[Obsolete("This is being depreciated. Use SharedSolutionContainerSystem instead!")] public sealed partial class SolutionContainerSystem : SharedSolutionContainerSystem { - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnMapInit); - SubscribeLocalEvent(OnComponentShutdown); - SubscribeLocalEvent(OnComponentShutdown); - } - - + [Obsolete("This is being depreciated. Use the ensure methods in SharedSolutionContainerSystem instead!")] public Solution EnsureSolution(Entity entity, string name) => EnsureSolution(entity, name, out _); + [Obsolete("This is being depreciated. Use the ensure methods in SharedSolutionContainerSystem instead!")] public Solution EnsureSolution(Entity entity, string name, out bool existed) => EnsureSolution(entity, name, FixedPoint2.Zero, out existed); + [Obsolete("This is being depreciated. Use the ensure methods in SharedSolutionContainerSystem instead!")] public Solution EnsureSolution(Entity entity, string name, FixedPoint2 maxVol, out bool existed) => EnsureSolution(entity, name, maxVol, null, out existed); + [Obsolete("This is being depreciated. Use the ensure methods in SharedSolutionContainerSystem instead!")] public Solution EnsureSolution(Entity entity, string name, FixedPoint2 maxVol, Solution? prototype, out bool existed) { - var (uid, meta) = entity; - if (!Resolve(uid, ref meta)) - throw new InvalidOperationException("Attempted to ensure solution on invalid entity."); - - var manager = EnsureComp(uid); - if (meta.EntityLifeStage >= EntityLifeStage.MapInitialized) - return EnsureSolutionEntity((uid, manager), name, maxVol, prototype, out existed).Comp.Solution; - else - return EnsureSolutionPrototype((uid, manager), name, maxVol, prototype, out existed); - } - - public void EnsureAllSolutions(Entity entity) - { - if (entity.Comp.Solutions is not { } prototypes) - return; - - foreach (var (name, prototype) in prototypes) - { - EnsureSolutionEntity((entity.Owner, entity.Comp), name, prototype.MaxVolume, prototype, out _); - } - - entity.Comp.Solutions = null; - Dirty(entity); - } - - public Entity EnsureSolutionEntity(Entity entity, string name, FixedPoint2 maxVol, Solution? prototype, out bool existed) - { - existed = true; - - var (uid, container) = entity; - - var solutionSlot = ContainerSystem.EnsureContainer(uid, $"solution@{name}", out existed); - if (!Resolve(uid, ref container, logMissing: false)) - { - existed = false; - container = AddComp(uid); - container.Containers.Add(name); - } - else if (!existed) - { - container.Containers.Add(name); - Dirty(uid, container); - } - - var needsInit = false; - SolutionComponent solutionComp; - if (solutionSlot.ContainedEntity is not { } solutionId) - { - prototype ??= new() { MaxVolume = maxVol }; - prototype.Name = name; - (solutionId, solutionComp, _) = SpawnSolutionUninitialized(solutionSlot, name, maxVol, prototype); - existed = false; - needsInit = true; - Dirty(uid, container); - } - else - { - solutionComp = Comp(solutionId); - DebugTools.Assert(TryComp(solutionId, out ContainedSolutionComponent? relation) && relation.Container == uid && relation.ContainerName == name); - DebugTools.Assert(solutionComp.Solution.Name == name); - - var solution = solutionComp.Solution; - solution.MaxVolume = FixedPoint2.Max(solution.MaxVolume, maxVol); - - // Depending on MapInitEvent order some systems can ensure solution empty solutions and conflict with the prototype solutions. - // We want the reagents from the prototype to exist even if something else already created the solution. - if (prototype is { Volume.Value: > 0 }) - solution.AddSolution(prototype, PrototypeManager); - - Dirty(solutionId, solutionComp); - } - - if (needsInit) - EntityManager.InitializeAndStartEntity(solutionId, Transform(solutionId).MapID); - - return (solutionId, solutionComp); - } - - private Solution EnsureSolutionPrototype(Entity entity, string name, FixedPoint2 maxVol, Solution? prototype, out bool existed) - { - existed = true; - - var (uid, container) = entity; - if (!Resolve(uid, ref container, logMissing: false)) - { - container = AddComp(uid); - existed = false; - } - - if (container.Solutions is null) - container.Solutions = new(SolutionContainerManagerComponent.DefaultCapacity); - - if (!container.Solutions.TryGetValue(name, out var solution)) - { - solution = prototype ?? new() { Name = name, MaxVolume = maxVol }; - container.Solutions.Add(name, solution); - existed = false; - } - else - solution.MaxVolume = FixedPoint2.Max(solution.MaxVolume, maxVol); - - Dirty(uid, container); - return solution; + EnsureSolution(entity, name, maxVol, prototype, out existed, out var solution); + return solution!;//solution is only ever null on the client, so we can suppress this } - - private Entity SpawnSolutionUninitialized(ContainerSlot container, string name, FixedPoint2 maxVol, Solution prototype) + [Obsolete("This is being depreciated. Use the ensure methods in SharedSolutionContainerSystem instead!")] + public Entity EnsureSolutionEntity( + Entity entity, + string name, + FixedPoint2 maxVol, + Solution? prototype, + out bool existed) { - var coords = new EntityCoordinates(container.Owner, Vector2.Zero); - var uid = EntityManager.CreateEntityUninitialized(null, coords, null); - - var solution = new SolutionComponent() { Solution = prototype }; - AddComp(uid, solution); - - var relation = new ContainedSolutionComponent() { Container = container.Owner, ContainerName = name }; - AddComp(uid, relation); - - MetaData.SetEntityName(uid, $"solution - {name}"); - ContainerSystem.Insert(uid, container, force: true); - - return (uid, solution, relation); + EnsureSolutionEntity(entity, name, out existed, out var solEnt, maxVol, prototype); + return solEnt!.Value;//solEnt is only ever null on the client, so we can suppress this } - - #region Event Handlers - - private void OnMapInit(Entity entity, ref MapInitEvent args) - { - EnsureAllSolutions(entity); - } - - private void OnComponentShutdown(Entity entity, ref ComponentShutdown args) - { - foreach (var name in entity.Comp.Containers) - { - if (ContainerSystem.TryGetContainer(entity, $"solution@{name}", out var solutionContainer)) - ContainerSystem.ShutdownContainer(solutionContainer); - } - entity.Comp.Containers.Clear(); - } - - private void OnComponentShutdown(Entity entity, ref ComponentShutdown args) - { - if (TryComp(entity.Comp.Container, out SolutionContainerManagerComponent? container)) - { - container.Containers.Remove(entity.Comp.ContainerName); - Dirty(entity.Comp.Container, container); - } - - if (ContainerSystem.TryGetContainer(entity, $"solution@{entity.Comp.ContainerName}", out var solutionContainer)) - ContainerSystem.ShutdownContainer(solutionContainer); - } - - #endregion Event Handlers } diff --git a/Content.Shared/Chemistry/EntitySystems/SharedSolutionContainerSystem.cs b/Content.Shared/Chemistry/EntitySystems/SharedSolutionContainerSystem.cs index 4b91007085..fdb2f550f9 100644 --- a/Content.Shared/Chemistry/EntitySystems/SharedSolutionContainerSystem.cs +++ b/Content.Shared/Chemistry/EntitySystems/SharedSolutionContainerSystem.cs @@ -11,10 +11,13 @@ using Robust.Shared.Prototypes; using Robust.Shared.Utility; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Numerics; using System.Runtime.CompilerServices; using System.Text; using Content.Shared.Hands.Components; using Content.Shared.Hands.EntitySystems; +using Robust.Shared.Map; +using Robust.Shared.Network; using Dependency = Robust.Shared.IoC.DependencyAttribute; namespace Content.Shared.Chemistry.EntitySystems; @@ -58,6 +61,7 @@ public abstract partial class SharedSolutionContainerSystem : EntitySystem [Dependency] protected readonly SharedHandsSystem Hands = default!; [Dependency] protected readonly SharedContainerSystem ContainerSystem = default!; [Dependency] protected readonly MetaDataSystem MetaData = default!; + [Dependency] protected readonly INetManager NetManager = default!; public override void Initialize() { @@ -66,13 +70,18 @@ public abstract partial class SharedSolutionContainerSystem : EntitySystem InitializeRelays(); SubscribeLocalEvent(OnComponentInit); - SubscribeLocalEvent(OnComponentStartup); - SubscribeLocalEvent(OnComponentShutdown); - - SubscribeLocalEvent(OnComponentInit); - + SubscribeLocalEvent(OnSolutionStartup); + SubscribeLocalEvent(OnSolutionShutdown); + SubscribeLocalEvent(OnContainerManagerInit); SubscribeLocalEvent(OnExamineSolution); SubscribeLocalEvent>(OnSolutionExaminableVerb); + SubscribeLocalEvent(OnMapInit); + + if (NetManager.IsServer) + { + SubscribeLocalEvent(OnContainerManagerShutdown); + SubscribeLocalEvent(OnContainedSolutionShutdown); + } } @@ -121,8 +130,14 @@ public abstract partial class SharedSolutionContainerSystem : EntitySystem /// The name of the solution entity to fetch. /// Returns the solution entity that was fetched. /// Returns the solution state of the solution entity that was fetched. + /// /// Should we print an error if the solution specified by name is missing /// - public bool TryGetSolution(Entity container, string? name, [NotNullWhen(true)] out Entity? entity, [NotNullWhen(true)] out Solution? solution) + public bool TryGetSolution( + Entity container, + string? name, + [NotNullWhen(true)] out Entity? entity, + [NotNullWhen(true)] out Solution? solution, + bool errorOnMissing = false) { if (!TryGetSolution(container, name, out entity)) { @@ -135,7 +150,11 @@ public abstract partial class SharedSolutionContainerSystem : EntitySystem } /// - public bool TryGetSolution(Entity container, string? name, [NotNullWhen(true)] out Entity? entity) + public bool TryGetSolution( + Entity container, + string? name, + [NotNullWhen(true)] out Entity? entity, + bool errorOnMissing = false) { if (TryComp(container, out BlockSolutionAccessComponent? blocker)) { @@ -155,12 +174,18 @@ public abstract partial class SharedSolutionContainerSystem : EntitySystem else { entity = null; + if (!errorOnMissing) + return false; + Log.Error($"{ToPrettyString(container)} does not have a solution with ID: {name}"); return false; } if (!TryComp(uid, out SolutionComponent? comp)) { entity = null; + if (!errorOnMissing) + return false; + Log.Error($"{ToPrettyString(container)} does not have a solution with ID: {name}"); return false; } @@ -171,13 +196,18 @@ public abstract partial class SharedSolutionContainerSystem : EntitySystem /// /// Version of TryGetSolution that doesn't take or return an entity. /// Used for prototypes and with old code parity. - public bool TryGetSolution(SolutionContainerManagerComponent container, string name, [NotNullWhen(true)] out Solution? solution) + public bool TryGetSolution(SolutionContainerManagerComponent container, + string name, + [NotNullWhen(true)] out Solution? solution, + bool errorOnMissing = false) { solution = null; - if (container.Solutions == null) + if (container.Solutions != null) + return container.Solutions.TryGetValue(name, out solution); + if (!errorOnMissing) return false; - - return container.Solutions.TryGetValue(name, out solution); + Log.Error($"{container} does not have a solution with ID: {name}"); + return false; } public IEnumerable<(string? Name, Entity Solution)> EnumerateSolutions(Entity container, bool includeSelf = true) @@ -703,17 +733,17 @@ public abstract partial class SharedSolutionContainerSystem : EntitySystem entity.Comp.Solution.ValidateSolution(); } - private void OnComponentStartup(Entity entity, ref ComponentStartup args) + private void OnSolutionStartup(Entity entity, ref ComponentStartup args) { UpdateChemicals(entity); } - private void OnComponentShutdown(Entity entity, ref ComponentShutdown args) + private void OnSolutionShutdown(Entity entity, ref ComponentShutdown args) { RemoveAllSolution(entity); } - private void OnComponentInit(Entity entity, ref ComponentInit args) + private void OnContainerManagerInit(Entity entity, ref ComponentInit args) { if (entity.Comp.Containers is not { Count: > 0 } containers) return; @@ -904,5 +934,273 @@ public abstract partial class SharedSolutionContainerSystem : EntitySystem return true; } + private void OnMapInit(Entity entity, ref MapInitEvent args) + { + EnsureAllSolutions(entity); + } + + private void OnContainerManagerShutdown(Entity entity, ref ComponentShutdown args) + { + foreach (var name in entity.Comp.Containers) + { + if (ContainerSystem.TryGetContainer(entity, $"solution@{name}", out var solutionContainer)) + ContainerSystem.ShutdownContainer(solutionContainer); + } + entity.Comp.Containers.Clear(); + } + + private void OnContainedSolutionShutdown(Entity entity, ref ComponentShutdown args) + { + if (TryComp(entity.Comp.Container, out SolutionContainerManagerComponent? container)) + { + container.Containers.Remove(entity.Comp.ContainerName); + Dirty(entity.Comp.Container, container); + } + + if (ContainerSystem.TryGetContainer(entity, $"solution@{entity.Comp.ContainerName}", out var solutionContainer)) + ContainerSystem.ShutdownContainer(solutionContainer); + } + #endregion Event Handlers + + public bool EnsureSolution( + Entity entity, + string name, + [NotNullWhen(true)]out Solution? solution, + FixedPoint2 maxVol = default) + { + return EnsureSolution(entity, name, maxVol, null, out _, out solution); + } + + public bool EnsureSolution( + Entity entity, + string name, + out bool existed, + [NotNullWhen(true)]out Solution? solution, + FixedPoint2 maxVol = default) + { + return EnsureSolution(entity, name, maxVol, null, out existed, out solution); + } + + public bool EnsureSolution( + Entity entity, + string name, + FixedPoint2 maxVol, + Solution? prototype, + out bool existed, + [NotNullWhen(true)] out Solution? solution) + { + solution = null; + existed = false; + + var (uid, meta) = entity; + if (!Resolve(uid, ref meta)) + throw new InvalidOperationException("Attempted to ensure solution on invalid entity."); + var manager = EnsureComp(uid); + if (meta.EntityLifeStage >= EntityLifeStage.MapInitialized) + { + EnsureSolutionEntity((uid, manager), name, out existed, + out var solEnt, maxVol, prototype); + solution = solEnt!.Value.Comp.Solution; + return true; + } + solution = EnsureSolutionPrototype((uid, manager), name, maxVol, prototype, out existed); + return true; + } + + public void EnsureAllSolutions(Entity entity) + { + if (NetManager.IsClient) + return; + + if (entity.Comp.Solutions is not { } prototypes) + return; + + foreach (var (name, prototype) in prototypes) + { + EnsureSolutionEntity((entity.Owner, entity.Comp), name, out _, out _, prototype.MaxVolume, prototype); + } + + entity.Comp.Solutions = null; + Dirty(entity); + } + + public bool EnsureSolutionEntity( + Entity entity, + string name, + [NotNullWhen(true)] out Entity? solutionEntity, + FixedPoint2 maxVol = default) => + EnsureSolutionEntity(entity, name, out _, out solutionEntity, maxVol); + + public bool EnsureSolutionEntity( + Entity entity, + string name, + out bool existed, + [NotNullWhen(true)] out Entity? solutionEntity, + FixedPoint2 maxVol = default, + Solution? prototype = null + ) + { + existed = true; + solutionEntity = null; + + var (uid, container) = entity; + + var solutionSlot = ContainerSystem.EnsureContainer(uid, $"solution@{name}", out existed); + if (!Resolve(uid, ref container, logMissing: false)) + { + existed = false; + container = AddComp(uid); + container.Containers.Add(name); + if (NetManager.IsClient) + return false; + } + else if (!existed) + { + container.Containers.Add(name); + Dirty(uid, container); + } + + var needsInit = false; + SolutionComponent solutionComp; + if (solutionSlot.ContainedEntity is not { } solutionId) + { + if (NetManager.IsClient) + return false; + prototype ??= new() { MaxVolume = maxVol }; + prototype.Name = name; + (solutionId, solutionComp, _) = SpawnSolutionUninitialized(solutionSlot, name, maxVol, prototype); + existed = false; + needsInit = true; + Dirty(uid, container); + } + else + { + solutionComp = Comp(solutionId); + DebugTools.Assert(TryComp(solutionId, out ContainedSolutionComponent? relation) && relation.Container == uid && relation.ContainerName == name); + DebugTools.Assert(solutionComp.Solution.Name == name); + + var solution = solutionComp.Solution; + solution.MaxVolume = FixedPoint2.Max(solution.MaxVolume, maxVol); + + // Depending on MapInitEvent order some systems can ensure solution empty solutions and conflict with the prototype solutions. + // We want the reagents from the prototype to exist even if something else already created the solution. + if (prototype is { Volume.Value: > 0 }) + solution.AddSolution(prototype, PrototypeManager); + + Dirty(solutionId, solutionComp); + } + + if (needsInit) + EntityManager.InitializeAndStartEntity(solutionId, Transform(solutionId).MapID); + solutionEntity = (solutionId, solutionComp); + return true; + } + + private Solution EnsureSolutionPrototype(Entity entity, string name, FixedPoint2 maxVol, Solution? prototype, out bool existed) + { + existed = true; + + var (uid, container) = entity; + if (!Resolve(uid, ref container, logMissing: false)) + { + container = AddComp(uid); + existed = false; + } + + if (container.Solutions is null) + container.Solutions = new(SolutionContainerManagerComponent.DefaultCapacity); + + if (!container.Solutions.TryGetValue(name, out var solution)) + { + solution = prototype ?? new() { Name = name, MaxVolume = maxVol }; + container.Solutions.Add(name, solution); + existed = false; + } + else + solution.MaxVolume = FixedPoint2.Max(solution.MaxVolume, maxVol); + + Dirty(uid, container); + return solution; + } + + private Entity SpawnSolutionUninitialized(ContainerSlot container, string name, FixedPoint2 maxVol, Solution prototype) + { + var coords = new EntityCoordinates(container.Owner, Vector2.Zero); + var uid = EntityManager.CreateEntityUninitialized(null, coords, null); + + var solution = new SolutionComponent() { Solution = prototype }; + AddComp(uid, solution); + + var relation = new ContainedSolutionComponent() { Container = container.Owner, ContainerName = name }; + AddComp(uid, relation); + + MetaData.SetEntityName(uid, $"solution - {name}"); + ContainerSystem.Insert(uid, container, force: true); + + return (uid, solution, relation); + } + + public void AdjustDissolvedReagent( + Entity dissolvedSolution, + FixedPoint2 volume, + ReagentId reagent, + float concentrationChange) + { + if (concentrationChange == 0) + return; + var dissolvedSol = dissolvedSolution.Comp.Solution; + var amtChange = + GetReagentQuantityFromConcentration(dissolvedSolution, volume, MathF.Abs(concentrationChange)); + if (concentrationChange > 0) + { + dissolvedSol.AddReagent(reagent, amtChange); + } + else + { + dissolvedSol.RemoveReagent(reagent,amtChange); + } + UpdateChemicals(dissolvedSolution); + } + + public FixedPoint2 GetReagentQuantityFromConcentration(Entity dissolvedSolution, + FixedPoint2 volume,float concentration) + { + var dissolvedSol = dissolvedSolution.Comp.Solution; + if (volume == 0 + || dissolvedSol.Volume == 0) + return 0; + return concentration * volume; + } + + public float GetReagentConcentration(Entity dissolvedSolution, + FixedPoint2 volume, ReagentId dissolvedReagent) + { + var dissolvedSol = dissolvedSolution.Comp.Solution; + if (volume == 0 + || dissolvedSol.Volume == 0 + || !dissolvedSol.TryGetReagentQuantity(dissolvedReagent, out var dissolvedVol)) + return 0; + return (float)dissolvedVol / volume.Float(); + } + + public FixedPoint2 ClampReagentAmountByConcentration( + Entity dissolvedSolution, + FixedPoint2 volume, + ReagentId dissolvedReagent, + FixedPoint2 dissolvedReagentAmount, + float maxConcentration = 1f) + { + var dissolvedSol = dissolvedSolution.Comp.Solution; + if (volume == 0 + || dissolvedSol.Volume == 0 + || !dissolvedSol.TryGetReagentQuantity(dissolvedReagent, out var dissolvedVol)) + return 0; + volume *= maxConcentration; + dissolvedVol += dissolvedReagentAmount; + var overflow = volume - dissolvedVol; + if (overflow < 0) + dissolvedReagentAmount += overflow; + return dissolvedReagentAmount; + } } -- 2.52.0