Files
tbd-station-14/Content.Benchmarks/DestructibleBenchmark.cs
Hannah Giovanna Dawson cdbe92d37d Update DamageableSystem to modern standards (#39417)
* Update DamageableSystem to modern standards

* DamageContainerId -> DamageContainerID with lint flag

* Replace strings with protoids

* Make CVar subscription declarations all consistently whitespaced

* ChangeDamage -> TryChangeDamage, cope with C# jank

* Revert event signature changes

* Restore a comment

* Re-add two queries

* Init the queries

* Use appearanceQuery in DamageChanged

* Use damageableQuery in TryChangeDamage

* Use damageableQuery in SetDamageModifierSetId

* Final cleanup, fix sandboxing

* Rectify ExplosionSystem:::ProcessEntity's call to TryChangeDamage

* Re-organize DamageableSystem

* first big fuck you breaking change.

* THATS A LOT OF DAMAGE!!!

* Fix test fails

* test fixes 2

* push it

---------

Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
2025-10-27 19:53:04 +00:00

161 lines
5.2 KiB
C#

using System.Collections.Generic;
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.Components;
using Content.Shared.Damage.Prototypes;
using Content.Shared.Damage.Systems;
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();
}
}