Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
69b67ba
Pt. 1
moonheart08 Mar 9, 2026
98dbe6f
Fixups and mandatory IComponentFactory.
moonheart08 Mar 10, 2026
eed0eca
Filters.
moonheart08 Mar 10, 2026
d17290d
ComponentFilterQuery.
moonheart08 Mar 10, 2026
bb3b58a
fixes
moonheart08 Mar 10, 2026
2b0b778
Tests pt 1.
moonheart08 Mar 10, 2026
27f03b1
test fixes
moonheart08 Mar 10, 2026
2cd3ccc
Final tests.
moonheart08 Mar 10, 2026
67b079a
obsolete
moonheart08 Mar 10, 2026
05b8689
fix warning
moonheart08 Mar 10, 2026
167d096
Pause handling.
moonheart08 Mar 10, 2026
614cb0e
Proxy methods
moonheart08 Mar 10, 2026
abddc2d
Proxy marks + xmldoc.
moonheart08 Mar 10, 2026
d530e17
Fix more obsoletes in engine.
moonheart08 Mar 10, 2026
5d5d82c
more warning fixes.
moonheart08 Mar 10, 2026
2a34a7c
Assertions in ComponentRegistry.
moonheart08 Mar 10, 2026
f53f7ae
Better RemoveComponents API. AddComponents is covered by FillMissesWi…
moonheart08 Mar 10, 2026
ff0e497
test docs.
moonheart08 Mar 10, 2026
7eaff61
docs
moonheart08 Mar 10, 2026
9915510
AnyMatchingComponent
moonheart08 Mar 10, 2026
33ac93f
Snip some bad API design.
moonheart08 Mar 10, 2026
0747af2
Release notes.
moonheart08 Mar 11, 2026
86267c0
Revert "Release notes.", the notes are for another PR.
moonheart08 Mar 11, 2026
3fa6786
Release notes.
moonheart08 Mar 11, 2026
82aa5f3
Add an efficient enumerator impl to EntityQuery.
moonheart08 Mar 10, 2026
1a775fe
Mark the legacy entity queries as obsolete.
moonheart08 Mar 10, 2026
96be0d6
Explicit ToList
moonheart08 Mar 10, 2026
4d0cf9e
Stop inlining everything or so help me.
moonheart08 Mar 10, 2026
a9c17d4
docs
moonheart08 Mar 10, 2026
6b697bc
Dynamic entity query. For internal use first.
moonheart08 Mar 10, 2026
b7347a4
paused checks.
moonheart08 Mar 10, 2026
5b7d9f4
smal refactor
moonheart08 Mar 10, 2026
9069fac
Extra EntityQuerys for 2-4 components.
moonheart08 Mar 10, 2026
f3053dd
my beloved Obsolete.
moonheart08 Mar 10, 2026
cae52c9
Tests.
moonheart08 Mar 10, 2026
19c9be4
more test.
moonheart08 Mar 10, 2026
8bdebf5
Add new EntityQuery types to IoC.
moonheart08 Mar 10, 2026
cf97f19
Release notes.
moonheart08 Mar 11, 2026
319055c
Poke
moonheart08 Mar 11, 2026
ecd2140
review.
moonheart08 Mar 12, 2026
e73f274
cleanup + docs + proxies + obsoletions.
moonheart08 Mar 12, 2026
98bc593
Add a net to catch misuse of DynamicEntityQuery.
moonheart08 Mar 12, 2026
fdb1c6a
justification
moonheart08 Mar 12, 2026
cdf842f
Friendlier exception + catch edgecase.
moonheart08 Mar 12, 2026
8b56561
Poke
moonheart08 Mar 12, 2026
e2d1c4f
Merge branch '10-03-2026-enumerable-entity-query' into staging
moonheart08 Mar 17, 2026
98b9b18
Start on CommandBuffer.
moonheart08 Mar 15, 2026
0f1a867
scaffolding.
moonheart08 Mar 15, 2026
0cadbf8
save more work.
moonheart08 Mar 16, 2026
ae024e1
Save more work.
moonheart08 Mar 16, 2026
e4ede9a
First tests, it works!!
moonheart08 Mar 16, 2026
181a24c
Comment on order.
moonheart08 Mar 16, 2026
88b2aa9
More tests.
moonheart08 Mar 16, 2026
251e07c
found a bug, thanks tests.
moonheart08 Mar 16, 2026
26fb67e
one more test case.
moonheart08 Mar 16, 2026
2ddd494
awr
moonheart08 Mar 16, 2026
3550438
what do you mean we have tests relying on hardcoded entity ids.
moonheart08 Mar 17, 2026
21c414f
Caught a possible threading bug.
moonheart08 Mar 17, 2026
447addd
Fixes for content-side testing.
moonheart08 Mar 17, 2026
d8cf836
More intuitive names for the EntityBuilder spawn methods.
moonheart08 Mar 17, 2026
028f511
Add a new, failing test.
moonheart08 Mar 18, 2026
6836963
It was grid traversal.
moonheart08 Mar 18, 2026
86f7a58
More stuff.
moonheart08 Mar 18, 2026
9813ab2
Fix various IoCManager invocations that could explode on other threads.
moonheart08 Mar 18, 2026
2d17704
Start smoke testing EntityBuilder in Spawn(), tests fail rn.
moonheart08 Mar 18, 2026
e9a685d
Pass engine tests again.
moonheart08 Mar 18, 2026
5a41233
comments.
moonheart08 Mar 18, 2026
cb72886
start on fixing spritecomponent.
moonheart08 Mar 18, 2026
23e852f
Work on fixing this mess.
moonheart08 Mar 18, 2026
91dc893
Add a warning to Resolve for some obvious misuse of Resolve, make Cop…
moonheart08 Mar 18, 2026
edf0c33
sigh
moonheart08 Mar 18, 2026
769b3d6
Change out where we're using EntityBuilder by default, server only now.
moonheart08 Mar 25, 2026
ce41fac
awawa
moonheart08 Mar 25, 2026
e03b09e
Merge branch 'master' into 15-03-2026-command-buffers
moonheart08 Mar 25, 2026
3cb35bd
fix.
moonheart08 Mar 25, 2026
9459245
Un "fix" spritecomponent. It's just too much for now.
moonheart08 Mar 25, 2026
fd8eff5
Change back some init logic we don't use in EntityBuilder.
moonheart08 Mar 25, 2026
c6c46ca
Tweak CommandBuffers.
moonheart08 Mar 26, 2026
13ae6f6
Cleanup.
moonheart08 Mar 26, 2026
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
42 changes: 38 additions & 4 deletions RELEASE-NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,23 +35,57 @@ END TEMPLATE-->

### Breaking changes

*None yet*
- Engine has a new `ComponentFilter` type, which name conflicts with the one currently in content.
Content maintainers should apply [this patch](https://github.com/space-wizards/space-station-14/pull/43168).
- `ComponentRegistry` no longer inherits directly from Dictionary, and only provides a subset of the dictionary API
that was necessary to get the Space Station 14 Wizard's Den content package compiling. All Dictionary methods are
additionally obsolete, and you should consult `ComponentRegistry`'s documentation and method list for replacements.
- `IEntityLoadContext` has new required methods that take an `IComponentFactory`.

### New features

*None yet*
- Introduces `ComponentFilter`, a set of components that can be used with various filter methods on `IEntityManager`.
Please consult the documentation for the type, alongside `IEntityManager.Filters.cs`, for the full set of new features.
- `ComponentRegistry` now provides an improved API that does not require users manually insert components into the
dictionary. Consult the documentation for the type for the full feature list.
- `EntityQuery<TComp1>` now implements `IEnumerable<Entity<TComp1>>` and can be used with `foreach` performantly.
- `EntityQuery<TComp1, TComp2>`, `EntityQuery<TComp1, TComp2, TComp3>`, and `EntityQuery<TComp1, TComp2, TComp3, TComp4>`
have been added and implement `IEnumerable`.
<br/><br/>
They're direct counterparts to `EntityQuery<TComp1>` with similar methods and constructors.
Additionally, like `EntityQuery<TComp1>`, all of these types can be resolved with a `[Dependency]` in systems.
- `DynamicEntityQuery` has been added. It implements component queries for an unbounded set of components, with the
ability to mark the presence of specific components as optional (may be null) or excluded (query doesn't match
entities with that component).
<br/><br/>
It is currently primarily an implementation detail of `EntityQuery<>` but can be used directly and will lead to
expanded functionality in the future.

### Bugfixes

*None yet*

### Other

*None yet*
- `void AddComponents(EntityUid target, EntityPrototype prototype, bool removeExisting = true)` and
`void RemoveComponents(EntityUid target, EntityPrototype prototype)` were marked obsolete and will be removed without
replacement. Using EntityPrototype as a filter and registry is not supported. Additionally,
the former API has never functioned correctly with `removeExisting: true` and that functionality is not supported.
- `void RemoveComponents(EntityUid target, ComponentRegistry registry)` is now obsolete and a ComponentFilter solution
should be used instead.
- `CompRegistryEntityEnumerator` is now obsolete in favor of `ComponentFilterQuery`.
- `EntityQueryEnumerator<TComp1>`, `EntityQueryEnumerator<TComp1, TComp2>`, `EntityQueryEnumerator<TComp1, TComp2, TComp3>`,
`EntityQueryEnumerator<TComp1, TComp2, TComp3, TComp4>`, `AllEntityQueryEnumerator<TComp1>`, `AllEntityQueryEnumerator<TComp1, TComp2>`,
`AllEntityQueryEnumerator<TComp1, TComp2, TComp3>`, `AllEntityQueryEnumerator<TComp1, TComp2, TComp3, TComp4>`,
and `ComponentQueryEnumerator` are now obsolete.
- The EntityManager methods `EntityQuery<TComp1>`, `EntityQuery<TComp1, TComp2>`, `EntityQuery<TComp1, TComp2, TComp3>`,
and `EntityQuery<TComp1, TComp2, TComp3, TComp4>`, `EntityQueryEnumerator`, `AllEntityQueryEnumerator`,
`ComponentQueryEnumerator`, `AllComponentsList`, `AllEntityUids`, `AllEntityUids`, `AllEntities`, and `AllComponents`
are now obsolete.

### Internal

*None yet*
- `ComponentRegistry`-dependant surfaces in the engine have been rewritten to use the new methods wherever possible.


## 274.0.1
Expand Down
1 change: 1 addition & 0 deletions Robust.Client/ClientIoC.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ public static void RegisterIoC(GameController.DisplayMode mode, IDependencyColle
deps.Register<IMapManagerInternal, NetworkedMapManager>();
deps.Register<INetworkedMapManager, NetworkedMapManager>();
deps.Register<IEntityManager, ClientEntityManager>();
deps.Register<IRemoteEntityManager, ClientEntityManager>();
deps.Register<FontTagHijackHolder>();
deps.Register<IReflectionManager, ClientReflectionManager>();
deps.Register<IConsoleHost, ClientConsoleHost>();
Expand Down
3 changes: 2 additions & 1 deletion Robust.Client/GameObjects/ClientEntityManager.Network.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Threading;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Utility;
Expand All @@ -8,7 +9,7 @@ namespace Robust.Client.GameObjects;

public sealed partial class ClientEntityManager
{
protected override NetEntity GenerateNetEntity() => new(NextNetworkId++ | NetEntity.ClientEntity);
protected override NetEntity GenerateNetEntity() => new(Interlocked.Increment(ref NextNetworkId) | NetEntity.ClientEntity);

/// <summary>
/// If the client fails to resolve a NetEntity then during component state handling or the likes we
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -243,12 +243,13 @@ public ShaderInstance? PostShader
public ISpriteLayer this[object layerKey] => this[LayerMap[layerKey]];
public IEnumerable<ISpriteLayer> AllLayers => Layers;

void ISerializationHooks.AfterDeserialization()
void ISerializationHooks.AfterDeserialization(IDependencyCollection collection)
{
// Please somebody burn this to the ground. There is so much spaghetti.
// Why has no one answered my prayers.
// Workin' on it --kaylie.

IoCManager.InjectDependencies(this);
collection.InjectDependencies(this);
if (!string.IsNullOrWhiteSpace(rsi))
{
var rsiPath = TextureRoot / rsi;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
using System;
using System.Collections.Generic;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Utility;

namespace Robust.Client.GameObjects;

public sealed partial class SpriteSystem
{
[Dependency] private readonly ISerializationManager _serMan = default!;

/// <summary>
/// Resets the sprite's animated layers to align with a given time (in seconds).
/// </summary>
Expand Down Expand Up @@ -54,6 +58,15 @@ public void CopySprite(Entity<SpriteComponent?> source, Entity<SpriteComponent?>
if (!Resolve(target.Owner, ref target.Comp))
return;

// Some code in content decided it'd be cool to copy from uninitialized SpriteComponents.
// Because this no longer works, we copy some extra data to ensure it does anyway.
if (source.Comp.LifeStage == ComponentLifeStage.PreAdd)
{
Log.Info($"Hit a bodge case in CopySprite, please change your usage to use ISerializationManager.CopyTo instead: {Environment.StackTrace}");
_serMan.CopyTo<SpriteComponent>(source.Comp, ref target.Comp, notNullableOverride: true);
return;
}

target.Comp._baseRsi = source.Comp._baseRsi;
target.Comp._bounds = source.Comp._bounds;
target.Comp._visible = source.Comp._visible;
Expand Down
7 changes: 4 additions & 3 deletions Robust.Client/Graphics/Shaders/ShaderPrototype.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,9 @@ public ShaderInstance InstanceUnique()
[DataField("blend_mode")]
private string? _rawBlendMode;

void ISerializationHooks.AfterDeserialization()
void ISerializationHooks.AfterDeserialization(IDependencyCollection collection)
{
// TODO: This isn't threadsafe and can't be! WGPU when.
switch (_rawKind)
{
case "source":
Expand All @@ -118,7 +119,7 @@ void ISerializationHooks.AfterDeserialization()
if (_path == null)
throw new InvalidOperationException("Source shaders must specify a source file.");

_source = IoCManager.Resolve<IResourceCache>().GetResource<ShaderSourceResource>(_path.Value);
_source = collection.Resolve<IResourceCache>().GetResource<ShaderSourceResource>(_path.Value);

if (_paramMapping != null)
{
Expand All @@ -140,7 +141,7 @@ void ISerializationHooks.AfterDeserialization()

case "canvas":
Kind = ShaderKind.Canvas;
_source = IoCManager.Resolve<IResourceCache>().GetResource<ShaderSourceResource>("/Shaders/Internal/default-sprite.swsl");
_source = collection.Resolve<IResourceCache>().GetResource<ShaderSourceResource>("/Shaders/Internal/default-sprite.swsl");
break;

default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,10 @@ public void Test(string prototypeName)
_sim.Resolve<IEntityManager>().System<SharedMapSystem>().CreateMap(out var map);

Assert.That(() => entMan.SpawnEntity(prototypeName, new MapCoordinates(0, 0, map)),
Throws.TypeOf<EntityCreationException>());
// Throws an aggregate containing only creation exceptions.
Throws.TypeOf<AggregateException>()
.And.Property("InnerExceptions")
.All.TypeOf<EntityCreationException2>());

Assert.That(entMan.GetEntities().Where(p => entMan.GetComponent<MetaDataComponent>(p).EntityPrototype?.ID == prototypeName), Is.Empty);
}
Expand Down
1 change: 1 addition & 0 deletions Robust.Server.Testing/RobustServerSimulation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,7 @@ public ISimulation InitializeInstance()
//Tier 2: Simulation
container.RegisterInstance<IConsoleHost>(new Mock<IConsoleHost>().Object); //Console is technically a frontend, we want to run headless
container.Register<IEntityManager, ServerEntityManager>();
container.Register<IRemoteEntityManager, ServerEntityManager>();
container.Register<IServerEntityNetworkManager, ServerEntityManager>();
container.Register<EntityManager, ServerEntityManager>();
container.Register<IMapManager, NetworkedMapManager>();
Expand Down
54 changes: 54 additions & 0 deletions Robust.Server/GameObjects/ServerEntityManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
using Robust.Shared.Player;
Expand Down Expand Up @@ -136,6 +138,58 @@ internal override void SetLifeStage(MetaDataComponent meta, EntityLifeStage stag

public override IEntityNetworkManager EntityNetManager => this;

public override EntityUid Spawn(
string? protoName,
MapCoordinates coordinates,
ComponentRegistry? overrides = null,
Angle rotation = default)
{
// Start building the entity as described...
var builder = EntityBuilder(protoName)
.LocatedAt(coordinates, rotation);

// If we got overrides, apply them here.
if (overrides is not null)
builder.ApplyRegistry(overrides);

// Spawn it into the simulation and return the id.
return Spawn(builder);
}

public override EntityUid SpawnAttachedTo(
string? protoName,
EntityCoordinates coordinates,
ComponentRegistry? overrides = null,
Angle rotation = default)
{
if (!coordinates.IsValid(this))
throw new InvalidOperationException($"Tried to spawn entity {protoName} on invalid coordinates {coordinates}.");

// Start building the entity as described...
var builder = EntityBuilder(protoName)
.ChildOf(coordinates, rotation);

// If we got overrides, apply them here.
if (overrides is not null)
builder.ApplyRegistry(overrides);

// Spawn it into the simulation and return the id.
return Spawn(builder);
}

public override EntityUid Spawn(string? protoName = null, ComponentRegistry? overrides = null, bool doMapInit = true)
{
// Start building the entity as described...
var builder = EntityBuilder(protoName);

// If we got overrides, apply them here.
if (overrides is not null)
builder.ApplyRegistry(overrides);

// Spawn it into the simulation and return the id.
return Spawn(builder, doMapInit);
}

/// <inheritdoc />
public event EventHandler<object>? ReceivedSystemMessage;

Expand Down
1 change: 1 addition & 0 deletions Robust.Server/ServerIoC.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ internal static void RegisterIoC(IDependencyCollection deps)
deps.Register<IMapManagerInternal, NetworkedMapManager>();
deps.Register<INetworkedMapManager, NetworkedMapManager>();
deps.Register<IEntityManager, ServerEntityManager>();
deps.Register<IRemoteEntityManager, ServerEntityManager>();
deps.Register<IEntityNetworkManager, ServerEntityManager>();
deps.Register<IServerEntityNetworkManager, ServerEntityManager>();
deps.Register<IPlacementManager, PlacementManager>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ void AssertPaused(bool expected, params EntityUid[] uids)
{
foreach (var uid in uids)
{
Assert.That(entMan.GetComponent<MetaDataComponent>(uid).EntityPaused, Is.EqualTo(expected));
Assert.That(entMan.GetComponent<MetaDataComponent>(uid).EntityPaused, Is.EqualTo(expected), $"Entity {entMan.ToPrettyString(uid)} was not paused.");
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using NUnit.Framework;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.CommandBuffers;

namespace Robust.UnitTesting.Shared.GameObjects.CommandBuffers;

public sealed class CommandConstructionTests
{
[Test]
public void ConstructQueuedActionT()
{
CommandBufferEntry.QueuedActionT(ctx =>
{
Assert.That(ctx, NUnit.Framework.Is.Not.Null);
Assert.Pass();
},
this,
out var entry);

entry.InvokeQueuedActionT();

Assert.Fail("Invocation did nothing?");
}

[Test]
public void ConstructQueuedActionTEnt()
{
CommandBufferEntry.QueuedActionTEnt(static (ctx, ent) =>
{
Assert.That(ctx, NUnit.Framework.Is.Not.Null);
Assert.That(ent, NUnit.Framework.Is.EqualTo(EntityUid.FirstUid));
Assert.Pass();
},
this,
EntityUid.FirstUid,
out var entry);

entry.InvokeQueuedActionTEnt();

Assert.Fail("Invocation did nothing?");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using NUnit.Framework;
using Robust.Client.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Serialization.Manager;

namespace Robust.UnitTesting.Shared.GameObjects.EntityBuilder;

internal sealed class ClientEntityBuilderTests : OurRobustUnitTest
{
private IEntityManager _entMan = default!;

public override UnitTestProject Project => UnitTestProject.Client;

[OneTimeSetUp]
public void Setup()
{
IoCManager.Resolve<ISerializationManager>().Initialize();

_entMan = IoCManager.Resolve<IEntityManager>();
}

[Test]
[Description("Ensure that adding SpriteComponent to a builder doesn't immediately explode.")]
public void CreateWithSprite()
{
Robust.Shared.GameObjects.EntityBuilders.EntityBuilder b = null!;
// NUnit [RequiresThread] is old and crusty and doesn't work for us.
// So we need to isolate IoC this way instead.
var t = new Thread(() =>
{
b = _entMan.EntityBuilder()
.AddComp<SpriteComponent>();
});

t.Start();
t.Join();

_entMan.Spawn(b);
}
}
Loading
Loading