From f346a11940fd68d24fa8b2abaa27051904e17b01 Mon Sep 17 00:00:00 2001 From: TemporalOroboros Date: Sun, 28 Nov 2021 07:32:33 -0800 Subject: [PATCH] Caches reactions based on their reactants (#5589) --- .../Reaction/SharedChemicalReactionSystem.cs | 101 +++++++++++++++--- 1 file changed, 88 insertions(+), 13 deletions(-) diff --git a/Content.Shared/Chemistry/Reaction/SharedChemicalReactionSystem.cs b/Content.Shared/Chemistry/Reaction/SharedChemicalReactionSystem.cs index 1a0b0fc2e1..ac7122af1f 100644 --- a/Content.Shared/Chemistry/Reaction/SharedChemicalReactionSystem.cs +++ b/Content.Shared/Chemistry/Reaction/SharedChemicalReactionSystem.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using Content.Shared.Administration.Logs; using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Reagent; @@ -14,18 +15,85 @@ namespace Content.Shared.Chemistry.Reaction { public abstract class SharedChemicalReactionSystem : EntitySystem { - private IEnumerable _reactions = default!; + /// + /// The maximum number of reactions that may occur when a solution is changed. + /// private const int MaxReactionIterations = 20; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] protected readonly SharedAdminLogSystem _logSystem = default!; + /// + /// A cache of all existant chemical reactions indexed by one of their + /// required reactants. + /// + private IDictionary> _reactions = default!; + public override void Initialize() { base.Initialize(); - _reactions = _prototypeManager.EnumeratePrototypes(); + + InitializeReactionCache(); + _prototypeManager.PrototypesReloaded += OnPrototypesReloaded; + } + + /// + /// Handles building the reaction cache. + /// + private void InitializeReactionCache() + { + _reactions = new Dictionary>(); + + var reactions = _prototypeManager.EnumeratePrototypes(); + foreach(var reaction in reactions) + { + CacheReaction(reaction); + } + } + + /// + /// Caches a reaction by its first required reagent. + /// Used to build the reaction cache. + /// + /// A reaction prototype to cache. + private void CacheReaction(ReactionPrototype reaction) + { + var reagents = reaction.Reactants.Keys; + foreach(var reagent in reagents) + { + if(!_reactions.TryGetValue(reagent, out var cache)) + { + cache = new List(); + _reactions.Add(reagent, cache); + } + + cache.Add(reaction); + return; // Only need to cache based on the first reagent. + } + } + + /// + /// Updates the reaction cache when the prototypes are reloaded. + /// + /// The set of modified prototypes. + private void OnPrototypesReloaded(PrototypesReloadedEventArgs eventArgs) + { + if (!eventArgs.ByType.TryGetValue(typeof(ReactionPrototype), out var set)) + return; + + foreach (var (reactant, cache) in _reactions) + { + cache.RemoveAll((reaction) => set.Modified.ContainsKey(reaction.ID)); + if (cache.Count == 0) + _reactions.Remove(reactant); + } + + foreach (var prototype in set.Modified.Values) + { + CacheReaction((ReactionPrototype) prototype); + } } /// @@ -131,20 +199,25 @@ namespace Content.Shared.Chemistry.Reaction /// Removes the reactants from the solution, then returns a solution with all products. /// WARNING: Does not trigger reactions between solution and new products. /// - private Solution ProcessReactions(Solution solution, EntityUid ownerUid) + private bool ProcessReactions(Solution solution, EntityUid ownerUid, [MaybeNullWhen(false)] out Solution productSolution) { - //TODO: make a hashmap at startup and then look up reagents in the contents for a reaction - var overallProducts = new Solution(); - foreach (var reaction in _reactions) + foreach(var reactant in solution.Contents) { - if (CanReact(solution, reaction, out var unitReactions)) + if (!_reactions.TryGetValue(reactant.ReagentId, out var reactions)) + continue; + + foreach(var reaction in reactions) { - var reactionProducts = PerformReaction(solution, ownerUid, reaction, unitReactions); - overallProducts.AddSolution(reactionProducts); - break; + if (!CanReact(solution, reaction, out var unitReactions)) + continue; + + productSolution = PerformReaction(solution, ownerUid, reaction, unitReactions); + return true; } } - return overallProducts; + + productSolution = null; + return false; } /// @@ -154,7 +227,8 @@ namespace Content.Shared.Chemistry.Reaction { for (var i = 0; i < MaxReactionIterations; i++) { - var products = ProcessReactions(solution, ownerUid); + if (!ProcessReactions(solution, ownerUid, out var products)) + return; if (products.TotalVolume <= 0) return; @@ -172,7 +246,8 @@ namespace Content.Shared.Chemistry.Reaction { for (var i = 0; i < MaxReactionIterations; i++) { - var products = ProcessReactions(solution, ownerUid); + if (!ProcessReactions(solution, ownerUid, out var products)) + return; if (products.TotalVolume <= 0) return;