diff --git a/Content.Benchmarks/GasReactionBenchmark.cs b/Content.Benchmarks/GasReactionBenchmark.cs new file mode 100644 index 0000000000..9ed30373d1 --- /dev/null +++ b/Content.Benchmarks/GasReactionBenchmark.cs @@ -0,0 +1,253 @@ +using System.Threading.Tasks; +using BenchmarkDotNet.Attributes; +using Content.IntegrationTests; +using Content.IntegrationTests.Pair; +using Content.Server.Atmos; +using Content.Server.Atmos.EntitySystems; +using Content.Server.Atmos.Reactions; +using Content.Shared.Atmos; +using Robust.Shared; +using Robust.Shared.Analyzers; +using Robust.Shared.GameObjects; +using Robust.Shared.Maths; + +namespace Content.Benchmarks; + +/// +/// Benchmarks the performance of different gas reactions. +/// Tests each reaction type with realistic gas mixtures to measure computational cost. +/// +[Virtual] +[GcServer(true)] +[MemoryDiagnoser] +public class GasReactionBenchmark +{ + private const int Iterations = 1000; + private TestPair _pair = default!; + private AtmosphereSystem _atmosphereSystem = default!; + + // Grid and tile for reactions that need a holder + private EntityUid _testGrid = default!; + private TileAtmosphere _testTile = default!; + // Reaction instances + private PlasmaFireReaction _plasmaFireReaction = default!; + private TritiumFireReaction _tritiumFireReaction = default!; + private FrezonProductionReaction _frezonProductionReaction = default!; + private FrezonCoolantReaction _frezonCoolantReaction = default!; + private AmmoniaOxygenReaction _ammoniaOxygenReaction = default!; + private N2ODecompositionReaction _n2oDecompositionReaction = default!; + private WaterVaporReaction _waterVaporReaction = default!; + // Gas mixtures for each reaction type + private GasMixture _plasmaFireMixture = default!; + private GasMixture _tritiumFireMixture = default!; + private GasMixture _frezonProductionMixture = default!; + private GasMixture _frezonCoolantMixture = default!; + private GasMixture _ammoniaOxygenMixture = default!; + private GasMixture _n2oDecompositionMixture = default!; + private GasMixture _waterVaporMixture = default!; + + [GlobalSetup] + public async Task SetupAsync() + { + ProgramShared.PathOffset = "../../../../"; + PoolManager.Startup(); + _pair = await PoolManager.GetServerClient(); + var server = _pair.Server; + + // Create test map and grid + var mapData = await _pair.CreateTestMap(); + _testGrid = mapData.Grid; + + await server.WaitPost(() => + { + var entMan = server.ResolveDependency(); + _atmosphereSystem = entMan.System(); + + _plasmaFireReaction = new PlasmaFireReaction(); + _tritiumFireReaction = new TritiumFireReaction(); + _frezonProductionReaction = new FrezonProductionReaction(); + _frezonCoolantReaction = new FrezonCoolantReaction(); + _ammoniaOxygenReaction = new AmmoniaOxygenReaction(); + _n2oDecompositionReaction = new N2ODecompositionReaction(); + _waterVaporReaction = new WaterVaporReaction(); + + SetupGasMixtures(); + SetupTile(); + }); + } + + private void SetupGasMixtures() + { + // Plasma Fire: Plasma + Oxygen at high temperature + // Temperature must be > PlasmaMinimumBurnTemperature for reaction to occur + _plasmaFireMixture = new GasMixture(Atmospherics.CellVolume) + { + Temperature = Atmospherics.PlasmaMinimumBurnTemperature + 100f // ~673K + }; + _plasmaFireMixture.AdjustMoles(Gas.Plasma, 20f); + _plasmaFireMixture.AdjustMoles(Gas.Oxygen, 100f); + + // Tritium Fire: Tritium + Oxygen at high temperature + // Temperature must be > FireMinimumTemperatureToExist for reaction to occur + _tritiumFireMixture = new GasMixture(Atmospherics.CellVolume) + { + Temperature = Atmospherics.FireMinimumTemperatureToExist + 100f // ~473K + }; + _tritiumFireMixture.AdjustMoles(Gas.Tritium, 20f); + _tritiumFireMixture.AdjustMoles(Gas.Oxygen, 100f); + + // Frezon Production: Oxygen + Tritium + Nitrogen catalyst + // Optimal temperature for efficiency (80% of max efficiency temp) + _frezonProductionMixture = new GasMixture(Atmospherics.CellVolume) + { + Temperature = Atmospherics.FrezonProductionMaxEfficiencyTemperature * 0.8f // ~48K + }; + _frezonProductionMixture.AdjustMoles(Gas.Oxygen, 50f); + _frezonProductionMixture.AdjustMoles(Gas.Tritium, 50f); + _frezonProductionMixture.AdjustMoles(Gas.Nitrogen, 10f); + + // Frezon Coolant: Frezon + Nitrogen + // Temperature must be > FrezonCoolLowerTemperature (23.15K) for reaction to occur + _frezonCoolantMixture = new GasMixture(Atmospherics.CellVolume) + { + Temperature = Atmospherics.T20C + 50f // ~343K + }; + _frezonCoolantMixture.AdjustMoles(Gas.Frezon, 30f); + _frezonCoolantMixture.AdjustMoles(Gas.Nitrogen, 100f); + + // Ammonia + Oxygen reaction (concentration-dependent, no temp requirement) + _ammoniaOxygenMixture = new GasMixture(Atmospherics.CellVolume) + { + Temperature = Atmospherics.T20C + 100f // ~393K + }; + _ammoniaOxygenMixture.AdjustMoles(Gas.Ammonia, 40f); + _ammoniaOxygenMixture.AdjustMoles(Gas.Oxygen, 40f); + + // N2O Decomposition (no temperature requirement, just needs N2O moles) + _n2oDecompositionMixture = new GasMixture(Atmospherics.CellVolume) + { + Temperature = Atmospherics.T20C + 100f // ~393K + }; + _n2oDecompositionMixture.AdjustMoles(Gas.NitrousOxide, 100f); + + // Water Vapor - needs water vapor to condense + _waterVaporMixture = new GasMixture(Atmospherics.CellVolume) + { + Temperature = Atmospherics.T20C + }; + _waterVaporMixture.AdjustMoles(Gas.WaterVapor, 50f); + } + + private void SetupTile() + { + // Create a tile atmosphere to use as holder for all reactions + var testIndices = new Vector2i(0, 0); + _testTile = new TileAtmosphere(_testGrid, testIndices, new GasMixture(Atmospherics.CellVolume) + { + Temperature = Atmospherics.T20C + }); + } + + private static GasMixture CloneMixture(GasMixture original) + { + return new GasMixture(original); + } + + [Benchmark] + public async Task PlasmaFireReaction() + { + await _pair.Server.WaitPost(() => + { + for (var i = 0; i < Iterations; i++) + { + var mixture = CloneMixture(_plasmaFireMixture); + _plasmaFireReaction.React(mixture, _testTile, _atmosphereSystem, 1f); + } + }); + } + + [Benchmark] + public async Task TritiumFireReaction() + { + await _pair.Server.WaitPost(() => + { + for (var i = 0; i < Iterations; i++) + { + var mixture = CloneMixture(_tritiumFireMixture); + _tritiumFireReaction.React(mixture, _testTile, _atmosphereSystem, 1f); + } + }); + } + + [Benchmark] + public async Task FrezonProductionReaction() + { + await _pair.Server.WaitPost(() => + { + for (var i = 0; i < Iterations; i++) + { + var mixture = CloneMixture(_frezonProductionMixture); + _frezonProductionReaction.React(mixture, _testTile, _atmosphereSystem, 1f); + } + }); + } + + [Benchmark] + public async Task FrezonCoolantReaction() + { + await _pair.Server.WaitPost(() => + { + for (var i = 0; i < Iterations; i++) + { + var mixture = CloneMixture(_frezonCoolantMixture); + _frezonCoolantReaction.React(mixture, _testTile, _atmosphereSystem, 1f); + } + }); + } + + [Benchmark] + public async Task AmmoniaOxygenReaction() + { + await _pair.Server.WaitPost(() => + { + for (var i = 0; i < Iterations; i++) + { + var mixture = CloneMixture(_ammoniaOxygenMixture); + _ammoniaOxygenReaction.React(mixture, _testTile, _atmosphereSystem, 1f); + } + }); + } + + [Benchmark] + public async Task N2ODecompositionReaction() + { + await _pair.Server.WaitPost(() => + { + for (var i = 0; i < Iterations; i++) + { + var mixture = CloneMixture(_n2oDecompositionMixture); + _n2oDecompositionReaction.React(mixture, _testTile, _atmosphereSystem, 1f); + } + }); + } + + [Benchmark] + public async Task WaterVaporReaction() + { + await _pair.Server.WaitPost(() => + { + for (var i = 0; i < Iterations; i++) + { + var mixture = CloneMixture(_waterVaporMixture); + _waterVaporReaction.React(mixture, _testTile, _atmosphereSystem, 1f); + } + }); + } + + [GlobalCleanup] + public async Task CleanupAsync() + { + await _pair.DisposeAsync(); + PoolManager.Shutdown(); + } +}