]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Damageable/Destructible Benchmarks (#41064)
authorPrincess Cheeseballs <66055347+Princess-Cheeseballs@users.noreply.github.com>
Mon, 27 Oct 2025 02:46:27 +0000 (19:46 -0700)
committerGitHub <noreply@github.com>
Mon, 27 Oct 2025 02:46:27 +0000 (02:46 +0000)
* the fard

* oomba

* The woke swarm...

* Review

* review

* Apply suggestions from code review

---------

Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
Content.Benchmarks/DestructibleBenchmark.cs [new file with mode: 0644]
Content.Server/Destructible/DestructibleSystem.BenchmarkHelpers.cs [new file with mode: 0644]
Content.Server/Destructible/DestructibleSystem.cs
Content.Shared/Damage/Systems/DamageableSystem.BenchmarkHelpers.cs [new file with mode: 0644]
Content.Shared/Damage/Systems/DamageableSystem.cs

diff --git a/Content.Benchmarks/DestructibleBenchmark.cs b/Content.Benchmarks/DestructibleBenchmark.cs
new file mode 100644 (file)
index 0000000..b91837e
--- /dev/null
@@ -0,0 +1,159 @@
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Threading.Tasks;
+using BenchmarkDotNet.Attributes;
+using Content.IntegrationTests;
+using Content.IntegrationTests.Pair;
+using Content.Server.Destructible;
+using Content.Shared.Damage;
+using Content.Shared.Damage.Prototypes;
+using Content.Shared.FixedPoint;
+using Content.Shared.Maps;
+using Robust.Shared;
+using Robust.Shared.Analyzers;
+using Robust.Shared.GameObjects;
+using Robust.Shared.Map;
+using Robust.Shared.Maths;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Random;
+
+namespace Content.Benchmarks;
+
+[Virtual]
+[GcServer(true)]
+[MemoryDiagnoser]
+public class DestructibleBenchmark
+{
+    /// <summary>
+    /// Number of destructible entities per prototype to spawn with a <see cref="DestructibleComponent"/>.
+    /// </summary>
+    [Params(1, 10, 100, 1000, 5000)]
+    public int EntityCount;
+
+    /// <summary>
+    /// Amount of blunt damage we do to each entity.
+    /// </summary>
+    [Params(10000)]
+    public FixedPoint2 DamageAmount;
+
+    [Params("Blunt")]
+    public ProtoId<DamageTypePrototype> DamageType;
+
+    private static readonly EntProtoId WindowProtoId = "Window";
+    private static readonly EntProtoId WallProtoId = "WallReinforced";
+    private static readonly EntProtoId HumanProtoId = "MobHuman";
+
+    private static readonly ProtoId<ContentTileDefinition> TileRef = "Plating";
+
+    private readonly EntProtoId[] _prototypes = [WindowProtoId, WallProtoId, HumanProtoId];
+
+    private readonly List<Entity<DamageableComponent>> _damageables = new();
+    private readonly List<Entity<DamageableComponent, DestructibleComponent>> _destructbiles = new();
+
+    private DamageSpecifier _damage;
+
+    private TestPair _pair = default!;
+    private IEntityManager _entMan = default!;
+    private IPrototypeManager _protoMan = default!;
+    private IRobustRandom _random = default!;
+    private ITileDefinitionManager _tileDefMan = default!;
+    private DamageableSystem _damageable = default!;
+    private DestructibleSystem _destructible = default!;
+    private SharedMapSystem _map = default!;
+
+    [GlobalSetup]
+    public async Task SetupAsync()
+    {
+        ProgramShared.PathOffset = "../../../../";
+        PoolManager.Startup();
+        _pair = await PoolManager.GetServerClient();
+        var server = _pair.Server;
+
+        var mapdata = await _pair.CreateTestMap();
+
+        _entMan = server.ResolveDependency<IEntityManager>();
+        _protoMan = server.ResolveDependency<IPrototypeManager>();
+        _random = server.ResolveDependency<IRobustRandom>();
+        _tileDefMan = server.ResolveDependency<ITileDefinitionManager>();
+        _damageable = _entMan.System<DamageableSystem>();
+        _destructible = _entMan.System<DestructibleSystem>();
+        _map = _entMan.System<SharedMapSystem>();
+
+        if (!_protoMan.Resolve(DamageType, out var type))
+            return;
+
+        _damage = new DamageSpecifier(type, DamageAmount);
+
+        _random.SetSeed(69420); // Randomness needs to be deterministic for benchmarking.
+
+        var plating = _tileDefMan[TileRef].TileId;
+
+        // We make a rectangular grid of destructible entities, and then damage them all simultaneously to stress test the system.
+        // Needed for managing the performance of destructive effects and damage application.
+        await server.WaitPost(() =>
+        {
+            // Set up a thin line of tiles to place our objects on. They should be anchored for a "realistic" scenario...
+            for (var x = 0; x < EntityCount; x++)
+            {
+                for (var y = 0; y < _prototypes.Length; y++)
+                {
+                    _map.SetTile(mapdata.Grid, mapdata.Grid, new Vector2i(x, y), new Tile(plating));
+                }
+            }
+
+            for (var x = 0; x < EntityCount; x++)
+            {
+                var y = 0;
+                foreach (var protoId in _prototypes)
+                {
+                    var coords = new EntityCoordinates(mapdata.Grid, x + 0.5f, y + 0.5f);
+                    _entMan.SpawnEntity(protoId, coords);
+                    y++;
+                }
+            }
+
+            var query = _entMan.EntityQueryEnumerator<DamageableComponent, DestructibleComponent>();
+
+            while (query.MoveNext(out var uid, out var damageable, out var destructible))
+            {
+                _damageables.Add((uid, damageable));
+                _destructbiles.Add((uid, damageable, destructible));
+            }
+        });
+    }
+
+    [Benchmark]
+    public async Task PerformDealDamage()
+    {
+        await _pair.Server.WaitPost(() =>
+        {
+            _damageable.ApplyDamageToAllEntities(_damageables, _damage);
+        });
+    }
+
+    [Benchmark]
+    public async Task PerformTestTriggers()
+    {
+        await _pair.Server.WaitPost(() =>
+        {
+            _destructible.TestAllTriggers(_destructbiles);
+        });
+    }
+
+    [Benchmark]
+    public async Task PerformTestBehaviors()
+    {
+        await _pair.Server.WaitPost(() =>
+        {
+            _destructible.TestAllBehaviors(_destructbiles);
+        });
+    }
+
+
+    [GlobalCleanup]
+    public async Task CleanupAsync()
+    {
+        await _pair.DisposeAsync();
+        PoolManager.Shutdown();
+    }
+}
diff --git a/Content.Server/Destructible/DestructibleSystem.BenchmarkHelpers.cs b/Content.Server/Destructible/DestructibleSystem.BenchmarkHelpers.cs
new file mode 100644 (file)
index 0000000..ac5e370
--- /dev/null
@@ -0,0 +1,35 @@
+using Content.Shared.Damage;
+
+namespace Content.Server.Destructible;
+
+public sealed partial class DestructibleSystem
+{
+    /// <summary>
+    /// Tests all triggers in a DestructibleComponent to see how expensive it is to query them.
+    /// </summary>
+    public void TestAllTriggers(List<Entity<DamageableComponent, DestructibleComponent>> destructibles)
+    {
+        foreach (var (uid, damageable, destructible) in destructibles)
+        {
+            foreach (var threshold in destructible.Thresholds)
+            {
+                // Chances are, none of these triggers will pass!
+                Triggered(threshold, (uid, damageable));
+            }
+        }
+    }
+
+    /// <summary>
+    /// Tests all behaviours in a DestructibleComponent to see how expensive it is to query them.
+    /// </summary>
+    public void TestAllBehaviors(List<Entity<DamageableComponent, DestructibleComponent>> destructibles)
+    {
+       foreach (var (uid, damageable, destructible) in destructibles)
+       {
+           foreach (var threshold in destructible.Thresholds)
+           {
+               Execute(threshold, uid);
+           }
+       }
+    }
+}
index 682baa04ca50ced6d8cd52bc129753639b699669..847229278c3645dd713cdbcdb56c206d45b9c2bd 100644 (file)
@@ -26,7 +26,7 @@ using Robust.Shared.Random;
 namespace Content.Server.Destructible
 {
     [UsedImplicitly]
-    public sealed class DestructibleSystem : SharedDestructibleSystem
+    public sealed partial class DestructibleSystem : SharedDestructibleSystem
     {
         [Dependency] public readonly IRobustRandom Random = default!;
         public new IEntityManager EntityManager => base.EntityManager;
diff --git a/Content.Shared/Damage/Systems/DamageableSystem.BenchmarkHelpers.cs b/Content.Shared/Damage/Systems/DamageableSystem.BenchmarkHelpers.cs
new file mode 100644 (file)
index 0000000..d248d71
--- /dev/null
@@ -0,0 +1,15 @@
+namespace Content.Shared.Damage;
+
+public sealed partial class DamageableSystem
+{
+    /// <summary>
+    /// Applies damage to all entities to see how expensive it is to deal damage.
+    /// </summary>
+    public void ApplyDamageToAllEntities(List<Entity<DamageableComponent>> damageables, DamageSpecifier damage)
+    {
+        foreach (var (uid, damageable) in damageables)
+        {
+            TryChangeDamage(uid, damage, damageable: damageable);
+        }
+    }
+}
index b849227156b56633f896e8b1aa9db963bada52f5..f1e259001cffb54c023273ea00f7b285b6874de6 100644 (file)
@@ -17,7 +17,7 @@ using Robust.Shared.Utility;
 
 namespace Content.Shared.Damage
 {
-    public sealed class DamageableSystem : EntitySystem
+    public sealed partial class DamageableSystem : EntitySystem
     {
         [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
         [Dependency] private readonly SharedAppearanceSystem _appearance = default!;