diff --git a/src/auto-evo/AutoEvoExploringTool.cs b/src/auto-evo/AutoEvoExploringTool.cs index b06d8ee5f8a..909d06c9aa8 100644 --- a/src/auto-evo/AutoEvoExploringTool.cs +++ b/src/auto-evo/AutoEvoExploringTool.cs @@ -761,7 +761,7 @@ private void PatchListMenuIndexChanged(int index) var selectedPatch = world.GameProperties.GameWorld.Map.Patches.Values .First(p => p.Name.ToString() == patchName); - // Get current snapshot + // Get the current snapshot var patch = new Patch(selectedPatch.Name, 0, selectedPatch.BiomeTemplate, selectedPatch.BiomeType, world.PatchHistoryList[generationDisplayed][selectedPatch.ID], selectedPatch.DynamicDataSeed) { @@ -788,10 +788,10 @@ private void PatchListMenuUpdate(Patch patch) var cache = new SimulationCache(world.WorldSettings); var globalCache = new AutoEvoGlobalCache(world.WorldSettings); - var generateMiche = new GenerateMiche(patch, cache, globalCache); + var generateMiche = new GenerateMiche(patch, globalCache); var newMiche = generateMiche.GenerateMicheTree(globalCache); - generateMiche.PopulateMiche(newMiche); + generateMiche.PopulateMiche(newMiche, cache); micheTree.SetMiche(newMiche); } diff --git a/src/auto-evo/AutoEvoRun.cs b/src/auto-evo/AutoEvoRun.cs index 98ed92bfb4b..0f013477650 100644 --- a/src/auto-evo/AutoEvoRun.cs +++ b/src/auto-evo/AutoEvoRun.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; @@ -29,6 +30,8 @@ public class AutoEvoRun private readonly List concurrentStepTasks = new(); + private readonly ConcurrentStack simulationCaches = new(); + private volatile RunStage state = RunStage.GatheringInfo; private bool started; @@ -398,11 +401,9 @@ protected virtual void GatherInfo(Queue steps) var allSpecies = new HashSet(); - var generateMicheCache = new SimulationCache(worldSettings); - foreach (var entry in map.Patches) { - steps.Enqueue(new GenerateMiche(entry.Value, generateMicheCache, globalCache)); + steps.Enqueue(new GenerateMiche(entry.Value, globalCache)); foreach (var species in entry.Value.SpeciesInPatch) { @@ -412,13 +413,12 @@ protected virtual void GatherInfo(Queue steps) foreach (var entry in map.Patches) { - steps.Enqueue(new ModifyExistingSpecies(entry.Value, new SimulationCache(worldSettings), worldSettings, - random)); + steps.Enqueue(new ModifyExistingSpecies(entry.Value, worldSettings, random)); } foreach (var species in allSpecies) { - steps.Enqueue(new MigrateSpecies(species, map, worldSettings, new SimulationCache(worldSettings), random)); + steps.Enqueue(new MigrateSpecies(species, map, worldSettings, random)); } // The new populations don't depend on the mutations but will take into account changes in the miche tree. @@ -436,7 +436,7 @@ protected virtual void GatherInfo(Queue steps) } /// - /// Adds a step that adjusts the player species population results + /// Adds a step that adjusts the player species' population results /// /// The list of steps to add the adjustment step to /// Used to get a list of patches to act on @@ -486,9 +486,9 @@ protected void AddPlayerSpeciesPopulationChangeClampStep(Queue steps, /// /// Single step run wrapper that handles checking timing if needed /// - /// Returns true when step is complete and can be discarded + /// Returns true when the step is complete and can be discarded [SuppressMessage("ReSharper", "HeuristicUnreachableCode", Justification = "False positive due to Constant bool")] - private static bool RunSingleStep(IRunStep step, RunResults results) + private static bool RunSingleStep(IRunStep step, RunResults results, SimulationCache cache) { DateTime startTime; @@ -496,7 +496,7 @@ private static bool RunSingleStep(IRunStep step, RunResults results) if (Constants.AUTO_EVO_TRACK_STEP_TIME) startTime = DateTime.Now; - var result = step.RunStep(results); + var result = step.RunStep(results, cache); if (Constants.AUTO_EVO_TRACK_STEP_TIME) { @@ -595,7 +595,7 @@ private bool Step() if (concurrentStepTasks.Count < 1) { - // No steps that can run concurrently, need to run just a normal run + // No steps that can run concurrently need to run just a normal run NormalRunPartOfNextStep(); } else @@ -627,27 +627,49 @@ private bool Step() private void NormalRunPartOfNextStep() { - if (RunSingleStep(runSteps.Peek(), results)) + var cache = GetCache(); + if (RunSingleStep(runSteps.Peek(), results, cache)) runSteps.Dequeue(); Interlocked.Increment(ref completeSteps); + + ReturnCache(cache); } private void RunSingleStepToCompletion(IRunStep step) { int steps = 0; + var cache = GetCache(); + // This condition is here to allow abandoning auto-evo runs quickly while (!Aborted) { ++steps; - if (RunSingleStep(step, results)) + if (RunSingleStep(step, results, cache)) break; } // Doing the steps counting this way is slightly faster than an increment after each step Interlocked.Add(ref completeSteps, steps); + + ReturnCache(cache); + } + + private SimulationCache GetCache() + { + if (simulationCaches.TryPop(out var cache)) + return cache; + + return new SimulationCache(Parameters.World.WorldSettings); + } + + private void ReturnCache(SimulationCache cache) + { + cache.OnAfterUse(); + + simulationCaches.Push(cache); } private void UpdateMap(bool playerCantGoExtinct) diff --git a/src/auto-evo/EditorAutoEvoRun.cs b/src/auto-evo/EditorAutoEvoRun.cs index 0c4599e15fb..48171d342a5 100644 --- a/src/auto-evo/EditorAutoEvoRun.cs +++ b/src/auto-evo/EditorAutoEvoRun.cs @@ -37,11 +37,9 @@ protected override void GatherInfo(Queue steps) var map = Parameters.World.Map; var worldSettings = Parameters.World.WorldSettings; - var generateMicheCache = new SimulationCache(worldSettings); - foreach (var entry in map.Patches) { - steps.Enqueue(new GenerateMiche(entry.Value, generateMicheCache, globalCache)); + steps.Enqueue(new GenerateMiche(entry.Value, globalCache)); } var populationCalculation = new CalculatePopulation(configuration, worldSettings, map, diff --git a/src/auto-evo/IRunStep.cs b/src/auto-evo/IRunStep.cs index e115012935d..2e9a23d0149 100644 --- a/src/auto-evo/IRunStep.cs +++ b/src/auto-evo/IRunStep.cs @@ -6,22 +6,25 @@ public interface IRunStep { /// - /// Total number of steps. As step is called this is allowed to return lower values + /// Total number of steps. As is called, this is allowed to return lower values /// than initially /// /// The total steps. public int TotalSteps { get; } /// - /// If true this step can be ran concurrently with other steps. If false all previous steps need to finish - /// before this can be ran. + /// If true, this step can be run concurrently with other steps. If false, all previous steps need to finish + /// before this can be run. /// public bool CanRunConcurrently { get; } /// /// Performs a single step. This needs to be called TotalSteps times /// - /// True once final step is complete + /// True once the final step is complete /// Results are stored here - public bool RunStep(RunResults results); + /// + /// Access to a cache where various auto-evo data can be got from efficiently. + /// + public bool RunStep(RunResults results, SimulationCache cache); } diff --git a/src/auto-evo/simulation/MichePopulation.cs b/src/auto-evo/simulation/MichePopulation.cs index 0a39d7afc83..a50998cba0a 100644 --- a/src/auto-evo/simulation/MichePopulation.cs +++ b/src/auto-evo/simulation/MichePopulation.cs @@ -12,16 +12,19 @@ /// public static class MichePopulation { - public static void Simulate(SimulationConfiguration parameters, SimulationCache? existingCache, + public static void Simulate(SimulationConfiguration parameters, ref SimulationCache? existingCache, Random randomSource) { if (existingCache?.MatchesSettings(parameters.WorldSettings) == false) throw new ArgumentException("Given cache doesn't match world settings"); - // This only seems to help a bit, so caching entirely in an auto-evo task by adding the cache parameter - // to IRunStep.RunStep might not be worth the effort at all - var cache = existingCache ?? new SimulationCache(parameters.WorldSettings); + existingCache ??= new SimulationCache(parameters.WorldSettings); + Simulate(parameters, existingCache, randomSource); + } + public static void Simulate(SimulationConfiguration parameters, SimulationCache cache, + Random randomSource) + { var insertWorkMemory = new Miche.InsertWorkingMemory(); var random = new XoShiRo256starstar(randomSource.NextInt64()); @@ -30,7 +33,7 @@ public static void Simulate(SimulationConfiguration parameters, SimulationCache? IEnumerable> patchesToSimulate = parameters.OriginalMap.Patches; - // Skip patches not configured to be simulated in order to run faster + // Skip patches that aren't configured to be simulated in order to run faster if (parameters.PatchesToRun.Count > 0) { patchesToSimulate = patchesToSimulate.Where(p => parameters.PatchesToRun.Contains(p.Value)); diff --git a/src/auto-evo/simulation/SimulationCache.cs b/src/auto-evo/simulation/SimulationCache.cs index 13bafaf6b78..b6bfb5c4ad0 100644 --- a/src/auto-evo/simulation/SimulationCache.cs +++ b/src/auto-evo/simulation/SimulationCache.cs @@ -23,12 +23,6 @@ namespace AutoEvo; /// caching is moved to a higher level in the auto-evo, that needs to be considered. /// /// -/// -/// -/// TODO: would be better to reuse instances of this class after clearing them for next use (there's now a Clear -/// method for this future usecase) -/// -/// public class SimulationCache { private readonly CompoundDefinition oxytoxy = SimulationParameters.GetCompound(Compound.Oxytoxy); @@ -1233,6 +1227,14 @@ public ResolvedMicrobeTolerances GetEnvironmentalTolerances(MicrobeSpecies speci return result; } + /// + /// Called after being used for an auto-evo step. This performs cleanup and readies this for the next use + /// + public void OnAfterUse() + { + Clear(); + } + /// /// Calculates cos of the angle between the organelle and vertical axis /// diff --git a/src/auto-evo/steps/CalculatePopulation.cs b/src/auto-evo/steps/CalculatePopulation.cs index ff55b814b39..6cc76c11a69 100644 --- a/src/auto-evo/steps/CalculatePopulation.cs +++ b/src/auto-evo/steps/CalculatePopulation.cs @@ -4,7 +4,7 @@ using Xoshiro.PRNG64; /// -/// Step that calculate the populations for all species +/// Step that calculates the populations for all species /// public class CalculatePopulation : IRunStep { @@ -34,7 +34,7 @@ public CalculatePopulation(IAutoEvoConfiguration configuration, WorldGenerationS /// public Dictionary? EnsurePatchesHaveSpecies { get; set; } - public bool RunStep(RunResults results) + public bool RunStep(RunResults results, SimulationCache cache) { // ReSharper disable RedundantArgumentDefaultValue var config = new SimulationConfiguration(configuration, map, worldSettings) @@ -58,7 +58,7 @@ public bool RunStep(RunResults results) // TODO: allow passing in a random seed - MichePopulation.Simulate(config, null, new XoShiRo256starstar()); + MichePopulation.Simulate(config, cache, new XoShiRo256starstar()); return true; } diff --git a/src/auto-evo/steps/GenerateMiche.cs b/src/auto-evo/steps/GenerateMiche.cs index 0bcddab2203..65fce5dcbf9 100644 --- a/src/auto-evo/steps/GenerateMiche.cs +++ b/src/auto-evo/steps/GenerateMiche.cs @@ -6,13 +6,11 @@ public class GenerateMiche : IRunStep { private readonly Patch patch; - private readonly SimulationCache cache; private readonly AutoEvoGlobalCache globalCache; - public GenerateMiche(Patch patch, SimulationCache cache, AutoEvoGlobalCache globalCache) + public GenerateMiche(Patch patch, AutoEvoGlobalCache globalCache) { this.patch = patch; - this.cache = cache; this.globalCache = globalCache; } @@ -20,10 +18,10 @@ public GenerateMiche(Patch patch, SimulationCache cache, AutoEvoGlobalCache glob public bool CanRunConcurrently => false; - public bool RunStep(RunResults results) + public bool RunStep(RunResults results, SimulationCache cache) { var generatedMiche = GenerateMicheTree(globalCache); - results.AddNewMicheForPatch(patch, PopulateMiche(generatedMiche)); + results.AddNewMicheForPatch(patch, PopulateMiche(generatedMiche, cache)); return true; } @@ -193,7 +191,7 @@ public Miche GenerateMicheTree(AutoEvoGlobalCache globalCache) return rootMiche; } - public Miche PopulateMiche(Miche miche) + public Miche PopulateMiche(Miche miche, SimulationCache cache) { // If no species, don't need to do anything if (patch.SpeciesInPatch.Count < 1) diff --git a/src/auto-evo/steps/LambdaStep.cs b/src/auto-evo/steps/LambdaStep.cs index 1c1d48976e3..88e50d3ed46 100644 --- a/src/auto-evo/steps/LambdaStep.cs +++ b/src/auto-evo/steps/LambdaStep.cs @@ -23,7 +23,7 @@ public LambdaStep(Action operation) /// public bool CanRunConcurrently { get; set; } - public bool RunStep(RunResults results) + public bool RunStep(RunResults results, SimulationCache cache) { operation(results); return true; diff --git a/src/auto-evo/steps/MigrateSpecies.cs b/src/auto-evo/steps/MigrateSpecies.cs index a56914154f5..72b13e0d8e0 100644 --- a/src/auto-evo/steps/MigrateSpecies.cs +++ b/src/auto-evo/steps/MigrateSpecies.cs @@ -12,15 +12,13 @@ public class MigrateSpecies : IRunStep private readonly Species species; private readonly PatchMap map; private readonly WorldGenerationSettings worldSettings; - private readonly SimulationCache cache; + private readonly Random random; private readonly Miche.InsertWorkingMemory insertWorkingMemory = new(); - public MigrateSpecies(Species species, PatchMap map, WorldGenerationSettings worldSettings, SimulationCache cache, - Random randomSource) + public MigrateSpecies(Species species, PatchMap map, WorldGenerationSettings worldSettings, Random randomSource) { this.species = species; - this.cache = cache; this.map = map; this.worldSettings = worldSettings; @@ -31,7 +29,7 @@ public MigrateSpecies(Species species, PatchMap map, WorldGenerationSettings wor public bool CanRunConcurrently => true; - public bool RunStep(RunResults results) + public bool RunStep(RunResults results, SimulationCache cache) { // Player has a separate GUI to control their migrations purposefully so auto-evo doesn't do it automatically if (species.PlayerSpecies) diff --git a/src/auto-evo/steps/ModifyExistingSpecies.cs b/src/auto-evo/steps/ModifyExistingSpecies.cs index 5ea1b530f84..8d9fc2619bc 100644 --- a/src/auto-evo/steps/ModifyExistingSpecies.cs +++ b/src/auto-evo/steps/ModifyExistingSpecies.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.CompilerServices; using Godot; using Xoshiro.PRNG64; @@ -16,7 +17,6 @@ public class ModifyExistingSpecies : IRunStep private const int TotalMutationsToTry = 20; private readonly Patch patch; - private readonly SimulationCache cache; private readonly WorldGenerationSettings worldSettings; @@ -48,7 +48,6 @@ public class ModifyExistingSpecies : IRunStep private readonly List> temporaryResultForTopMutations = new(); - private readonly MutationSorter mutationSorter; private readonly Random random; private readonly List mutationsToTry = new(); @@ -59,21 +58,19 @@ public class ModifyExistingSpecies : IRunStep private readonly List nonEmptyLeafNodes = new(); private readonly List emptyLeafNodes = new(); + private MutationSorter? mutationSorter; + private Dictionary.Enumerator speciesEnumerator; private Miche? miche; private Step step; - public ModifyExistingSpecies(Patch patch, SimulationCache cache, WorldGenerationSettings worldSettings, - Random randomSeed) + public ModifyExistingSpecies(Patch patch, WorldGenerationSettings worldSettings, Random randomSeed) { this.patch = patch; - this.cache = cache; this.worldSettings = worldSettings; - mutationSorter = new MutationSorter(patch, cache); - random = new XoShiRo256starstar(randomSeed.NextInt64()); // Patch species count is used to know how many steps there are to perform @@ -102,8 +99,13 @@ private enum Step public bool CanRunConcurrently => true; - public bool RunStep(RunResults results) + public bool RunStep(RunResults results, SimulationCache cache) { + if (mutationSorter == null || !mutationSorter.UsesCache(cache)) + { + mutationSorter = new MutationSorter(patch, cache); + } + // Setup miche data if missing if (miche == null) { @@ -129,7 +131,7 @@ public bool RunStep(RunResults results) if (species is MicrobeSpecies microbeSpecies) { - GetMutationsForSpecies(microbeSpecies); + GetMutationsForSpecies(microbeSpecies, cache); } } else @@ -153,7 +155,7 @@ public bool RunStep(RunResults results) if (species is MicrobeSpecies microbeSpecies) { - GetMutationsForSpecies(microbeSpecies); + GetMutationsForSpecies(microbeSpecies, cache); } } } @@ -287,7 +289,7 @@ private static void GetTopMutations(List> result, } } - private void GetMutationsForSpecies(MicrobeSpecies microbeSpecies) + private void GetMutationsForSpecies(MicrobeSpecies microbeSpecies, SimulationCache cache) { // The traversal end up being re-calculated quite many times, but this way we avoid quite a lot of memory // allocations @@ -307,7 +309,7 @@ private void GetMutationsForSpecies(MicrobeSpecies microbeSpecies) } var variants = GenerateMutations(microbeSpecies, - worldSettings.AutoEvoConfiguration.MutationsPerSpecies, temporaryPressures); + worldSettings.AutoEvoConfiguration.MutationsPerSpecies, temporaryPressures, cache); foreach (var variant in variants) { @@ -331,7 +333,7 @@ private void GetMutationsForSpecies(MicrobeSpecies microbeSpecies) } var variants = GenerateMutations(microbeSpecies, - worldSettings.AutoEvoConfiguration.MutationsPerSpecies, temporaryPressures); + worldSettings.AutoEvoConfiguration.MutationsPerSpecies, temporaryPressures, cache); foreach (var variant in variants) { @@ -375,8 +377,15 @@ private void PredatorsOf(List result, Miche micheTree, Species species) /// /// List of viable variants and the provided species private List GenerateMutations(MicrobeSpecies baseSpecies, int amount, - List selectionPressures) + List selectionPressures, SimulationCache cache) { +#if DEBUG + + // Check if methods are called in the wrong order + if (mutationSorter == null) + throw new InvalidOperationException("Mutation sorter is null"); +#endif + double totalMP = 100 * worldSettings.AIMutationMultiplier; temporaryMutations1.Clear(); @@ -399,7 +408,7 @@ private List GenerateMutations(MicrobeSpecies baseSpecies, int a bool lawk = worldSettings.LAWK; - mutationSorter.Setup(baseSpecies, selectionPressures); + mutationSorter!.Setup(baseSpecies, selectionPressures); tempMutationStrategies.Shuffle(random); @@ -524,5 +533,11 @@ public int Compare(Tuple? x, Tuple oldSpecies) public int TotalSteps => 1; public bool CanRunConcurrently => false; - public bool RunStep(RunResults results) + public bool RunStep(RunResults results, SimulationCache cache) { var currentSpecies = new HashSet(); foreach (var patch in world.Map.Patches) diff --git a/src/auto-evo/steps/RemoveInvalidMigrations.cs b/src/auto-evo/steps/RemoveInvalidMigrations.cs index 5c72ca2cebf..a197c611ead 100644 --- a/src/auto-evo/steps/RemoveInvalidMigrations.cs +++ b/src/auto-evo/steps/RemoveInvalidMigrations.cs @@ -14,7 +14,7 @@ public RemoveInvalidMigrations(IReadOnlyCollection speciesToCheck) public int TotalSteps => 1; public bool CanRunConcurrently => false; - public bool RunStep(RunResults results) + public bool RunStep(RunResults results, SimulationCache cache) { foreach (var species in speciesToCheck) { diff --git a/src/microbe_stage/editor/PatchMapEditorComponent.cs b/src/microbe_stage/editor/PatchMapEditorComponent.cs index 8b5f35668ac..29bd74d9ee2 100644 --- a/src/microbe_stage/editor/PatchMapEditorComponent.cs +++ b/src/microbe_stage/editor/PatchMapEditorComponent.cs @@ -421,9 +421,9 @@ private void AddExtraAISpeciesMigrationTo(Patch patch, Patch preferredSourcePatc // Create a miche tree to only find species that would actually survive in the target patch var cache = new SimulationCache(world.WorldSettings); - var generation = new GenerateMiche(patch, cache, world.AutoEvoGlobalCache); + var generation = new GenerateMiche(patch, world.AutoEvoGlobalCache); var miche = generation.GenerateMicheTree(world.AutoEvoGlobalCache); - generation.PopulateMiche(miche); + generation.PopulateMiche(miche, cache); Species? foundSpecies = null;