diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 1102817bfbf..3acf87395f4 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -39,7 +39,7 @@ END TEMPLATE--> ### New features -*None yet* +* Add EntityManager overloads for ComponentRegistration that's faster than the generic methods. ### Bugfixes diff --git a/Robust.Benchmarks/EntityManager/HasComponentBenchmark.cs b/Robust.Benchmarks/EntityManager/HasComponentBenchmark.cs new file mode 100644 index 00000000000..e3afb8e79d5 --- /dev/null +++ b/Robust.Benchmarks/EntityManager/HasComponentBenchmark.cs @@ -0,0 +1,96 @@ +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Engines; +using JetBrains.Annotations; +using Robust.Shared.Analyzers; +using Robust.Shared.GameObjects; +using Robust.Shared.Map; +using Robust.UnitTesting.Server; + +namespace Robust.Benchmarks.EntityManager; + +[Virtual] +public partial class HasComponentBenchmark +{ + private static readonly Consumer Consumer = new(); + + private ISimulation _simulation = default!; + private IEntityManager _entityManager = default!; + + private ComponentRegistration _compReg = default!; + + private A _dummyA = new(); + + [UsedImplicitly] + [Params(1, 10, 100, 1000)] + public int N; + + [GlobalSetup] + public void GlobalSetup() + { + _simulation = RobustServerSimulation + .NewSimulation() + .RegisterComponents(f => f.RegisterClass()) + .InitializeInstance(); + + _entityManager = _simulation.Resolve(); + var map = _simulation.CreateMap().Uid; + var coords = new EntityCoordinates(map, default); + _compReg = _entityManager.ComponentFactory.GetRegistration(typeof(A)); + + for (var i = 0; i < N; i++) + { + var uid = _entityManager.SpawnEntity(null, coords); + _entityManager.AddComponent(uid); + } + } + + [Benchmark] + public void HasComponentGeneric() + { + for (var i = 2; i <= N+1; i++) + { + var uid = new EntityUid(i); + var result = _entityManager.HasComponent(uid); + Consumer.Consume(result); + } + } + + [Benchmark] + public void HasComponentCompReg() + { + for (var i = 2; i <= N+1; i++) + { + var uid = new EntityUid(i); + var result = _entityManager.HasComponent(uid, _compReg); + Consumer.Consume(result); + } + } + + [Benchmark] + public void HasComponentType() + { + for (var i = 2; i <= N+1; i++) + { + var uid = new EntityUid(i); + var result = _entityManager.HasComponent(uid, typeof(A)); + Consumer.Consume(result); + } + } + + [Benchmark] + public void HasComponentGetType() + { + for (var i = 2; i <= N+1; i++) + { + var uid = new EntityUid(i); + var type = _dummyA.GetType(); + var result = _entityManager.HasComponent(uid, type); + Consumer.Consume(result); + } + } + + [ComponentProtoName("A")] + public sealed partial class A : Component + { + } +} diff --git a/Robust.Shared/GameObjects/EntityManager.Components.cs b/Robust.Shared/GameObjects/EntityManager.Components.cs index 95889c291c7..ec99bd4da8a 100644 --- a/Robust.Shared/GameObjects/EntityManager.Components.cs +++ b/Robust.Shared/GameObjects/EntityManager.Components.cs @@ -197,17 +197,23 @@ public void AddComponents(EntityUid target, ComponentRegistry registry, bool rem { var reg = _componentFactory.GetRegistration(name); - if (HasComponent(target, reg.Type)) + if (removeExisting) { - if (!removeExisting) + var comp = _componentFactory.GetComponent(reg); + _serManager.CopyTo(entry.Component, ref comp, notNullableOverride: true); + AddComponentInternal(target, comp, reg, overwrite: true, metadata: metadata); + } + else + { + if (HasComponent(target, reg)) + { continue; + } - RemoveComponent(target, reg.Type, metadata); + var comp = _componentFactory.GetComponent(reg); + _serManager.CopyTo(entry.Component, ref comp, notNullableOverride: true); + AddComponentInternal(target, comp, reg, overwrite: false, metadata: metadata); } - - var comp = _componentFactory.GetComponent(reg); - _serManager.CopyTo(entry.Component, ref comp, notNullableOverride: true); - AddComponent(target, comp, metadata: metadata); } } @@ -315,6 +321,22 @@ public void AddComponent(EntityUid uid, T component, bool overwrite = false, AddComponentInternal(uid, component, overwrite, false, metadata); } + private void AddComponentInternal( + EntityUid uid, + T component, + ComponentRegistration compReg, + bool overwrite = false, + MetaDataComponent? metadata = null) where T : IComponent + { + if (!MetaQuery.Resolve(uid, ref metadata, false)) + throw new ArgumentException($"Entity {uid} is not valid.", nameof(uid)); + + DebugTools.Assert(component.Owner == default); + component.Owner = uid; + + AddComponentInternal(uid, component, compReg, overwrite, skipInit: false, metadata); + } + private void AddComponentInternal(EntityUid uid, T component, bool overwrite, bool skipInit, MetaDataComponent? metadata) where T : IComponent { if (!MetaQuery.ResolveInternal(uid, ref metadata, false)) @@ -731,6 +753,14 @@ public bool HasComponent([NotNullWhen(true)] EntityUid? uid) where T : ICompo return uid.HasValue && HasComponent(uid.Value); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Pure] + public bool HasComponent(EntityUid uid, ComponentRegistration reg) + { + var dict = _entTraitArray[reg.Idx.Value]; + return dict.TryGetValue(uid, out var comp) && !comp.Deleted; + } + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] [Pure] @@ -943,6 +973,23 @@ public bool TryGetComponent([NotNullWhen(true)] EntityUid? uid, [NotNullWhen( return false; } + /// + public bool TryGetComponent(EntityUid uid, ComponentRegistration reg, [NotNullWhen(true)] out IComponent? component) + { + var dict = _entTraitArray[reg.Idx.Value]; + if (dict.TryGetValue(uid, out var comp)) + { + if (!comp.Deleted) + { + component = comp; + return true; + } + } + + component = null; + return false; + } + /// public bool TryGetComponent(EntityUid uid, Type type, [NotNullWhen(true)] out IComponent? component) { diff --git a/Robust.Shared/GameObjects/IEntityManager.Components.cs b/Robust.Shared/GameObjects/IEntityManager.Components.cs index 84b47c53f50..11937cde0fc 100644 --- a/Robust.Shared/GameObjects/IEntityManager.Components.cs +++ b/Robust.Shared/GameObjects/IEntityManager.Components.cs @@ -176,6 +176,14 @@ public partial interface IEntityManager /// True if the entity has the component type, otherwise false. bool HasComponent([NotNullWhen(true)] EntityUid? uid) where T : IComponent; + /// + /// Checks if the entity has a component type. + /// + /// Entity UID to check. + /// The component registration to check for. + /// True if the entity has the component type, otherwise false. + bool HasComponent(EntityUid uid, ComponentRegistration reg); + /// /// Checks if the entity has a component type. /// @@ -294,6 +302,15 @@ public partial interface IEntityManager /// If the component existed in the entity. bool TryGetComponent([NotNullWhen(true)] EntityUid? uid, [NotNullWhen(true)] out T? component) where T : IComponent?; + /// + /// Returns the component of a specific type. + /// + /// Entity UID to check. + /// The component registration to check for. + /// Component of the specified type (if exists). + /// If the component existed in the entity. + bool TryGetComponent(EntityUid uid, ComponentRegistration reg, [NotNullWhen(true)] out IComponent? component); + /// /// Returns the component of a specific type. ///