Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
<CefNativeVersion>147.0.10</CefNativeVersion>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Arch.LowLevel" Version="1.1.5" />
<PackageVersion Include="BenchmarkDotNet" Version="0.15.8" />
<PackageVersion Include="DiscordRichPresence" Version="1.2.1.24" />
<PackageVersion Include="JetBrains.Annotations" Version="2025.2.4" />
Expand Down
118 changes: 118 additions & 0 deletions Robust.Benchmarks/EntityManager/TransformComponentQueryBenchmark.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
using System.Collections.Generic;
using System.Numerics;
using System.Runtime.InteropServices;
using BenchmarkDotNet.Attributes;
using JetBrains.Annotations;
using Robust.Shared.Analyzers;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.UnitTesting.Server;

namespace Robust.Benchmarks.EntityManager;

[MemoryDiagnoser]
[Virtual]
public class TransformComponentQueryBenchmark
{
private ISimulation _simulation = default!;
private IEntityManager _entityManager = default!;

[UsedImplicitly]
[Params(1, 10, 100, 1000)]
public int N = 10000;

private List<EntityUid> Ents = default!;

[GlobalSetup]
public void GlobalSetup()
{
_simulation = RobustServerSimulation
.NewSimulation()
.InitializeInstance();

_entityManager = _simulation.Resolve<IEntityManager>();

Ents = [];
for (var i = 0; i < N; i++)
{
Ents.Add(_entityManager.SpawnEntity(null, MapCoordinates.Nullspace));
}
}

[Benchmark]
public int AllEntityQueryEnumerator()
{
var a = 0;
var query = _entityManager.AllEntityQueryEnumerator<TransformComponent>();
while (query.MoveNext(out _, out var comp))
{
a += comp.ChildCount;
}

return a;
}

[Benchmark]
public int EntityQueryEnumerator()
{
var a = 0;
var query = _entityManager.EntityQueryEnumerator<TransformComponent>();
while (query.MoveNext(out _, out var comp))
{
a += comp.ChildCount;
}

return a;
}

[Benchmark]
public int GetEntityQuery()
{
var a = 0;
var query = _entityManager.GetEntityQuery<TransformComponent>();
foreach (var uid in CollectionsMarshal.AsSpan(Ents))
{
a += query.GetComponent(uid).ChildCount;
}

return a;
}

[Benchmark]
public int GetComponent()
{
var a = 0;
foreach (var uid in CollectionsMarshal.AsSpan(Ents))
{
a += _entityManager.GetComponent<TransformComponent>(uid).ChildCount;
}

return a;
}

[Benchmark]
public int HasComponent()
{
var a = 0;
foreach (var uid in CollectionsMarshal.AsSpan(Ents))
{
if (_entityManager.HasComponent<TransformComponent>(uid))
a += 1;
}

return a;
}

[Benchmark]
public int TryGetComponent()
{
var a = 0;
foreach (var uid in CollectionsMarshal.AsSpan(Ents))
{
if (_entityManager.TryGetComponent(uid, out TransformComponent? xform))
a += xform.ChildCount;
}

return a;
}
}
21 changes: 20 additions & 1 deletion Robust.Shared/GameObjects/CompIdx.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ namespace Robust.Shared.GameObjects;
public readonly struct CompIdx : IEquatable<CompIdx>
{
internal readonly int Value;
internal readonly bool Dense;
internal readonly int DenseIndex;

internal static CompIdx Index<T>() => Store<T>.Index;

Expand Down Expand Up @@ -41,19 +43,36 @@ internal static ref T RefArray<T>(ref T[] array, CompIdx idx)
return ref array[idx.Value];
}

internal static bool GetDense<T>()
{
return Store<T>.Index.Dense;
}

internal static void SetDense<T>(bool dense, int index)
{
Store<T>.Index = new CompIdx(Store<T>.Index.Value, dense, index);
}

private static int _CompIdxMaster = -1;

private static class Store<T>
{
// ReSharper disable once StaticMemberInGenericType
public static readonly CompIdx Index = new(Interlocked.Increment(ref _CompIdxMaster));
public static CompIdx Index = new(Interlocked.Increment(ref _CompIdxMaster));
}

internal CompIdx(int value)
{
Value = value;
}

internal CompIdx(int value, bool dense, int denseIndex)
{
Value = value;
Dense = dense;
DenseIndex = denseIndex;
}

public bool Equals(CompIdx other)
{
return Value == other.Value;
Expand Down
3 changes: 2 additions & 1 deletion Robust.Shared/GameObjects/ComponentFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,9 @@ private ComponentRegistration Register(Type type,
throw new InvalidOperationException($"{lowerCaseName} is already registered, previous: {prevName}");

var unsaved = type.HasCustomAttribute<UnsavedComponentAttribute>();
var dense = type.HasCustomAttribute<DenseComponentAttribute>();

var registration = new ComponentRegistration(name, type, idx, unsaved);
var registration = new ComponentRegistration(name, type, idx, unsaved, dense);

idxToType[idx] = type;
names[name] = registration;
Expand Down
5 changes: 4 additions & 1 deletion Robust.Shared/GameObjects/ComponentRegistration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,17 @@ public sealed class ComponentRegistration

public FrozenDictionary<string, int> NetworkedFieldLookup = FrozenDictionary<string, int>.Empty;

internal bool Dense;

// Internal for sandboxing.
// Avoid content passing an instance of this to ComponentFactory to get any type they want instantiated.
internal ComponentRegistration(string name, Type type, CompIdx idx, bool unsaved = false)
internal ComponentRegistration(string name, Type type, CompIdx idx, bool unsaved = false, bool dense = false)
{
Name = name;
Type = type;
Idx = idx;
Unsaved = unsaved;
Dense = dense;
}

public override string ToString()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ public MetaDataComponentState(string? name, string? description, string? prototy
/// <summary>
/// Contains meta data about this entity that isn't component specific.
/// </summary>
[DenseComponent]
[RegisterComponent, NetworkedComponent]
public sealed partial class MetaDataComponent : Component
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ namespace Robust.Shared.GameObjects
/// and move when their parent moves cheaply.
/// </summary>
/// <seealso cref="SharedTransformSystem"/>
[DenseComponent]
[RegisterComponent, NetworkedComponent]
public sealed partial class TransformComponent : Component, IComponentDebug
{
Expand Down
19 changes: 19 additions & 0 deletions Robust.Shared/GameObjects/DenseComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System.Threading;

namespace Robust.Shared.GameObjects;

internal static class DenseComponent
{
public static int Index = -1;
}

internal static class DenseComponent<T>
{
// ReSharper disable once StaticMemberInGenericType
internal static readonly int Index = Interlocked.Increment(ref DenseComponent.Index);

static DenseComponent()
{
CompIdx.SetDense<T>(true, Index);
}
}
13 changes: 13 additions & 0 deletions Robust.Shared/GameObjects/DenseComponentAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System;
using JetBrains.Annotations;

namespace Robust.Shared.GameObjects;

/// <summary>
/// Makes component operations using this component type use an array lookup instead of a dictionary lookup
/// where possible, using more memory but improving performance for components that are used very often, such as
/// <see cref="TransformComponent"/> and <see cref="MetaDataComponent"/>
/// </summary>
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
[BaseTypeRequired(typeof(IComponent))]
public sealed class DenseComponentAttribute : Attribute;
Loading
Loading