diff --git a/MSBuild/Robust.Engine.Version.props b/MSBuild/Robust.Engine.Version.props
index eb4a9d76311..3f86b934a0f 100644
--- a/MSBuild/Robust.Engine.Version.props
+++ b/MSBuild/Robust.Engine.Version.props
@@ -1,4 +1,4 @@
- 202.1.1
+ 204.0.0
diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md
index 5e57688372e..64e8362b8f7 100644
--- a/RELEASE-NOTES.md
+++ b/RELEASE-NOTES.md
@@ -54,6 +54,51 @@ END TEMPLATE-->
*None yet*
+## 204.0.0
+
+### Breaking changes
+
+* Make EntityManager abstract and make IEntityManager.EntityNetManager not nullable.
+* Make VVAccess.ReadWrite default for all Datafields instead of VVAccess.ReadOnly
+
+### New features
+
+* `TextEdit.OnTextChanged`
+* Add Pick and PickAndTake versions for System.Random for ICollections.
+
+### Bugfixes
+
+* Fix `IClipboardManager.GetText()` returning null in some cases.
+* Fix possible NRE in server-side console command completion code.
+* Fix possible NRE on DebugConsole logs.
+* Fix exception when VVing non-networked components.
+
+### Other
+
+* Remove "Do not use from content" from IComponent.
+
+
+## 203.0.0
+
+### Breaking changes
+
+* `IComponentFactory.RegisterIgnore()` no longer supports overwriting existing registrations, components should get ignored before they are registered.
+* Event bus subscriptions are now locked after `IEntityManager` has started, instead of after the first component gets added. Any event subscriptions now need to happen before startup (but after init).
+* Event bus subscriptions must now be locked before raising any events.
+* Delete FodyWeavers.xsd as it hasn't been used for a long time.
+* Remove physics sleep cancelling as it was, in hindsight, a bad idea.
+
+### New features
+
+* `RobustUnitTest` now has a `ExtraComponents` field for automatically registering additional components.
+* `IComponentFactory.RegisterIgnore()` now accepts more than one string.
+* Added `IComponentFactory.RegisterTypes` for simultaneously registering multiple components.
+
+### Bugfixes
+
+* Clamp volume calculations for audio rather than throwing.
+
+
## 202.1.1
### Bugfixes
@@ -61,7 +106,7 @@ END TEMPLATE-->
* Reverted some map/grid initialisation changes that might've been causing broadphase/physics errors.
* Fixed PVS sometimes sending entities without first sending their children.
* Fixed a container state handling bug caused by containers not removing expected entities when shutting down.
-* Fixed a `EnsureEntity` state handling bug caused by improper handling of entity deletions.
+* Fixed a `EnsureEntity` state handling bug caused by improper handling of entity deletions.
* Fixed a bad NetSyncEnabled debug assert.
@@ -77,7 +122,7 @@ END TEMPLATE-->
### Breaking changes
* Various entity manager methods now have a new `where T : IComponent` constraint.
-* The `IComponentFactory.ComponentAdded` event has been renamed to `ComponentsAdded` and now provides an array of component registrations.
+* The `IComponentFactory.ComponentAdded` event has been renamed to `ComponentsAdded` and now provides an array of component registrations.
* `IComponentFactory.RegisterIgnore()` no longer supports overwriting existing registrations, components should get ignored before they are registered.
### New features
diff --git a/Robust.Client/FodyWeavers.xsd b/Robust.Client/FodyWeavers.xsd
deleted file mode 100644
index 15adf7906a8..00000000000
--- a/Robust.Client/FodyWeavers.xsd
+++ /dev/null
@@ -1,82 +0,0 @@
-
-
-
-
-
-
-
-
-
-
- Defines if sequence points should be generated for each emitted IL instruction. Default value: Debug
-
-
-
-
-
- Insert sequence points in Debug builds only (this is the default).
-
-
-
-
- Insert sequence points in Release builds only.
-
-
-
-
- Always insert sequence points.
-
-
-
-
- Never insert sequence points.
-
-
-
-
-
-
-
- Defines how warnings should be handled. Default value: Warnings
-
-
-
-
-
- Emit build warnings (this is the default).
-
-
-
-
- Do not emit warnings.
-
-
-
-
- Treat warnings as errors.
-
-
-
-
-
-
-
-
-
-
- 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.
-
-
-
-
- A comma-separated list of error codes that can be safely ignored in assembly verification.
-
-
-
-
- 'false' to turn off automatic generation of the XML Schema file.
-
-
-
-
-
\ No newline at end of file
diff --git a/Robust.Client/Graphics/Clyde/Windowing/Glfw.Windows.cs b/Robust.Client/Graphics/Clyde/Windowing/Glfw.Windows.cs
index 2ae9430a1ef..5aa9d69497e 100644
--- a/Robust.Client/Graphics/Clyde/Windowing/Glfw.Windows.cs
+++ b/Robust.Client/Graphics/Clyde/Windowing/Glfw.Windows.cs
@@ -654,7 +654,7 @@ public Task ClipboardGetText(WindowReg mainWindow)
private static void WinThreadGetClipboard(CmdGetClipboard cmd)
{
- var clipboard = GLFW.GetClipboardString((Window*) cmd.Window);
+ var clipboard = GLFW.GetClipboardString((Window*) cmd.Window) ?? "";
// Don't have to care about synchronization I don't think so just fire this immediately.
cmd.Tcs.TrySetResult(clipboard);
}
diff --git a/Robust.Client/UserInterface/Controls/TextEdit.cs b/Robust.Client/UserInterface/Controls/TextEdit.cs
index 30d60fce687..d22575698fa 100644
--- a/Robust.Client/UserInterface/Controls/TextEdit.cs
+++ b/Robust.Client/UserInterface/Controls/TextEdit.cs
@@ -90,6 +90,8 @@ public sealed class TextEdit : Control
internal bool DebugOverlay;
private Vector2? _lastDebugMousePos;
+ public event Action? OnTextChanged;
+
public TextEdit()
{
IoCManager.InjectDependencies(this);
@@ -315,7 +317,7 @@ protected internal override void KeyBindDown(GUIBoundKeyEventArgs args)
if (changed)
{
_selectionStart = _cursorPosition;
- // OnTextChanged?.Invoke(new LineEditEventArgs(this, _text));
+ OnTextChanged?.Invoke(new TextEditEventArgs(this, _textRope));
// _updatePseudoClass();
// OnBackspace?.Invoke(new LineEditBackspaceEventArgs(oldText, _text, cursor, selectStart));
}
@@ -349,7 +351,7 @@ protected internal override void KeyBindDown(GUIBoundKeyEventArgs args)
if (changed)
{
_selectionStart = _cursorPosition;
- // OnTextChanged?.Invoke(new LineEditEventArgs(this, _text));
+ OnTextChanged?.Invoke(new TextEditEventArgs(this, _textRope));
// _updatePseudoClass();
}
@@ -382,7 +384,10 @@ protected internal override void KeyBindDown(GUIBoundKeyEventArgs args)
}
if (changed)
+ {
_selectionStart = _cursorPosition;
+ OnTextChanged?.Invoke(new TextEditEventArgs(this, _textRope));
+ }
InvalidateHorizontalCursorPos();
args.Handle();
@@ -411,7 +416,10 @@ protected internal override void KeyBindDown(GUIBoundKeyEventArgs args)
}
if (changed)
+ {
_selectionStart = _cursorPosition;
+ OnTextChanged?.Invoke(new TextEditEventArgs(this, _textRope));
+ }
InvalidateHorizontalCursorPos();
args.Handle();
@@ -748,6 +756,7 @@ protected internal override void TextEditing(GUITextEditingEventArgs args)
var startPos = _cursorPosition;
TextRope = Rope.Insert(TextRope, startPos.Index, ev.Text);
+ OnTextChanged?.Invoke(new TextEditEventArgs(this, _textRope));
_selectionStart = _cursorPosition = new CursorPos(startPos.Index + startChars, LineBreakBias.Top);
_imeData = (startPos, ev.Text.Length);
@@ -844,6 +853,7 @@ public void InsertAtCursor(string text)
var upper = SelectionUpper.Index;
TextRope = Rope.ReplaceSubstring(TextRope, lower, upper - lower, text);
+ OnTextChanged?.Invoke(new TextEditEventArgs(this, _textRope));
_selectionStart = _cursorPosition = new CursorPos(lower + text.Length, LineBreakBias.Top);
// OnTextChanged?.Invoke(new LineEditEventArgs(this, _text));
@@ -1441,6 +1451,12 @@ protected internal override void KeyboardFocusExited()
AbortIme(delete: false);
}
+ public sealed class TextEditEventArgs(TextEdit control, Rope.Node textRope) : EventArgs
+ {
+ public TextEdit Control { get; } = control;
+ public Rope.Node TextRope { get; } = textRope;
+ }
+
///
/// Specifies which line the cursor is positioned at when on a word-wrapping break.
///
diff --git a/Robust.Client/UserInterface/CustomControls/DebugConsole.xaml.cs b/Robust.Client/UserInterface/CustomControls/DebugConsole.xaml.cs
index 86fb7afb45c..59104cc0aca 100644
--- a/Robust.Client/UserInterface/CustomControls/DebugConsole.xaml.cs
+++ b/Robust.Client/UserInterface/CustomControls/DebugConsole.xaml.cs
@@ -39,7 +39,7 @@ public interface IDebugConsoleView
// And also if Update() stops firing due to an exception loop the console will still work.
// (At least from the main thread, which is what's throwing the exceptions..)
[GenerateTypedNameReferences]
- public sealed partial class DebugConsole : Control, IDebugConsoleView, IPostInjectInit
+ public sealed partial class DebugConsole : Control, IDebugConsoleView
{
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;
[Dependency] private readonly IResourceManager _resourceManager = default!;
@@ -49,7 +49,7 @@ public sealed partial class DebugConsole : Control, IDebugConsoleView, IPostInje
private static readonly ResPath HistoryPath = new("/debug_console_history.json");
private readonly ConcurrentQueue _messageQueue = new();
- private ISawmill _logger = default!;
+ private readonly ISawmill _logger;
public DebugConsole()
{
@@ -57,6 +57,8 @@ public DebugConsole()
IoCManager.InjectDependencies(this);
+ _logger = _logMan.GetSawmill("dbgconsole");
+
InitCompletions();
CommandBar.OnTextChanged += OnCommandChanged;
@@ -282,10 +284,5 @@ await Task.Run(async () =>
}
});
}
-
- void IPostInjectInit.PostInject()
- {
- _logger = _logMan.GetSawmill("dbgconsole");
- }
}
}
diff --git a/Robust.Server/Console/ServerConsoleHost.cs b/Robust.Server/Console/ServerConsoleHost.cs
index b392fc5b2eb..1c1400ef806 100644
--- a/Robust.Server/Console/ServerConsoleHost.cs
+++ b/Robust.Server/Console/ServerConsoleHost.cs
@@ -273,6 +273,9 @@ private async void HandleConCompletions(MsgConCompletion message)
}
done:
+
+ result ??= CompletionResult.Empty;
+
var msg = new MsgConCompletionResp
{
Result = result,
diff --git a/Robust.Server/ViewVariables/ViewVariablesSession.cs b/Robust.Server/ViewVariables/ViewVariablesSession.cs
index eeaa1c039bb..bbd10681e7b 100644
--- a/Robust.Server/ViewVariables/ViewVariablesSession.cs
+++ b/Robust.Server/ViewVariables/ViewVariablesSession.cs
@@ -97,7 +97,7 @@ public void Modify(object[] propertyIndex, object value)
// Auto-dirty component. Only works when modifying a field that is directly on a component,
// Does not work for nested objects.
- if (Object is Component comp)
+ if (Object is Component { NetSyncEnabled: true } comp)
EntityManager.Dirty(comp.Owner, comp);
}
diff --git a/Robust.Shared/Audio/Systems/SharedAudioSystem.cs b/Robust.Shared/Audio/Systems/SharedAudioSystem.cs
index 9be9d0bc823..4db97c30c09 100644
--- a/Robust.Shared/Audio/Systems/SharedAudioSystem.cs
+++ b/Robust.Shared/Audio/Systems/SharedAudioSystem.cs
@@ -154,7 +154,7 @@ public static float GainToVolume(float value)
{
if (value < 0f)
{
- throw new InvalidOperationException($"Tried to get volume calculation for gain of {value}.");
+ value = 0f;
}
return 10f * MathF.Log10(value);
diff --git a/Robust.Shared/GameObjects/Component.cs b/Robust.Shared/GameObjects/Component.cs
index c21445cdd38..cddbb8cea36 100644
--- a/Robust.Shared/GameObjects/Component.cs
+++ b/Robust.Shared/GameObjects/Component.cs
@@ -16,8 +16,13 @@ public abstract partial class Component : IComponent
[ViewVariables(VVAccess.ReadWrite)]
private bool _netSync { get; set; } = true;
- [Obsolete("Do not use from content")]
- public bool Networked { get; set; } = true;
+ internal bool Networked { get; set; } = true;
+
+ bool IComponent.Networked
+ {
+ get => Networked;
+ set => Networked = value;
+ }
///
public bool NetSyncEnabled
@@ -31,9 +36,14 @@ public bool NetSyncEnabled
[Obsolete("Update your API to allow accessing Owner through other means")]
public EntityUid Owner { get; set; } = EntityUid.Invalid;
- ///
[ViewVariables]
- public ComponentLifeStage LifeStage { get; [Obsolete("Do not use from content")] set; } = ComponentLifeStage.PreAdd;
+ public ComponentLifeStage LifeStage { get; internal set; } = ComponentLifeStage.PreAdd;
+
+ ComponentLifeStage IComponent.LifeStage
+ {
+ get => LifeStage;
+ set => LifeStage = value;
+ }
public virtual bool SendOnlyToOwner => false;
@@ -51,13 +61,29 @@ public bool NetSyncEnabled
[ViewVariables]
public bool Deleted => LifeStage >= ComponentLifeStage.Removing;
- ///
+ ///
+ /// This is the tick the component was created.
+ ///
[ViewVariables]
- public GameTick CreationTick { get; [Obsolete("Do not use from content")] set; }
+ public GameTick CreationTick { get; internal set; }
- ///
+ GameTick IComponent.CreationTick
+ {
+ get => CreationTick;
+ set => CreationTick = value;
+ }
+
+ ///
+ /// Marks the component as dirty so that the network will re-sync it with clients.
+ ///
[ViewVariables]
- public GameTick LastModifiedTick { get; [Obsolete("Do not use from content")] set; }
+ public GameTick LastModifiedTick { get; internal set; }
+
+ GameTick IComponent.LastModifiedTick
+ {
+ get => LastModifiedTick;
+ set => LastModifiedTick = value;
+ }
///
[Obsolete]
@@ -69,15 +95,23 @@ public void Dirty(IEntityManager? entManager = null)
// these two methods clear the LastModifiedTick/CreationTick to mark it as "not different from prototype load".
// This is used as optimization in the game state system to avoid sending redundant component data.
- [Obsolete("Do not use from content")]
- public virtual void ClearTicks()
+ void IComponent.ClearTicks()
+ {
+ ClearTicks();
+ }
+
+ private protected virtual void ClearTicks()
{
LastModifiedTick = GameTick.Zero;
ClearCreationTick();
}
- [Obsolete("Do not use from content")]
- public void ClearCreationTick()
+ void IComponent.ClearCreationTick()
+ {
+ ClearCreationTick();
+ }
+
+ private protected void ClearCreationTick()
{
CreationTick = GameTick.Zero;
}
diff --git a/Robust.Shared/GameObjects/ComponentFactory.cs b/Robust.Shared/GameObjects/ComponentFactory.cs
index c81d7365230..d0437ada609 100644
--- a/Robust.Shared/GameObjects/ComponentFactory.cs
+++ b/Robust.Shared/GameObjects/ComponentFactory.cs
@@ -388,10 +388,10 @@ public bool TryGetRegistration(IComponent component, [NotNullWhen(true)] out Com
public void DoAutoRegistrations()
{
var types = _reflectionManager.FindTypesWithAttribute().ToArray();
- RegisterClasses(types, false);
+ RegisterTypesInternal(types, false);
}
- private void RegisterClasses(Type[] types, bool overwrite)
+ private void RegisterTypesInternal(Type[] types, bool overwrite)
{
var names = _names.ToDictionary();
var lowerCaseNames = _lowerCaseNames.ToDictionary();
@@ -419,7 +419,19 @@ private void RegisterClasses(Type[] types, bool overwrite)
public void RegisterClass(bool overwrite = false)
where T : IComponent, new()
{
- RegisterClasses(new []{typeof(T)}, overwrite);
+ RegisterTypesInternal(new []{typeof(T)}, overwrite);
+ }
+
+ ///
+ public void RegisterTypes(params Type[] types)
+ {
+ foreach (var type in types)
+ {
+ if (!type.IsAssignableTo(typeof(IComponent)) || !type.HasParameterlessConstructor())
+ throw new InvalidOperationException($"Invalid type: {type}");
+ }
+
+ RegisterTypesInternal(types, false);
}
public IEnumerable GetAllRefTypes()
diff --git a/Robust.Shared/GameObjects/Components/MetaDataComponent.cs b/Robust.Shared/GameObjects/Components/MetaDataComponent.cs
index a19cf7221d1..8ebf986bca7 100644
--- a/Robust.Shared/GameObjects/Components/MetaDataComponent.cs
+++ b/Robust.Shared/GameObjects/Components/MetaDataComponent.cs
@@ -192,8 +192,7 @@ internal set
[ViewVariables]
internal PvsChunkLocation? LastPvsLocation;
- [Obsolete("Do not use from content")]
- public override void ClearTicks()
+ private protected override void ClearTicks()
{
// Do not clear modified ticks.
// MetaDataComponent is used in the game state system to carry initial data like prototype ID.
diff --git a/Robust.Shared/GameObjects/EntityEventBus.Broadcast.cs b/Robust.Shared/GameObjects/EntityEventBus.Broadcast.cs
index 837df689745..736209cd9e2 100644
--- a/Robust.Shared/GameObjects/EntityEventBus.Broadcast.cs
+++ b/Robust.Shared/GameObjects/EntityEventBus.Broadcast.cs
@@ -297,7 +297,10 @@ public void QueueEvent(EventSource source, EntityEventArgs toRaise)
private void UnsubscribeEvent(Type eventType, BroadcastRegistration tuple, IEntityEventSubscriber subscriber)
{
- if (_eventData.TryGetValue(eventType, out var subscriptions)
+ if (_subscriptionLock)
+ throw new InvalidOperationException("Subscription locked.");
+
+ if (_eventDataUnfrozen.TryGetValue(eventType, out var subscriptions)
&& subscriptions.BroadcastRegistrations.Contains(tuple))
subscriptions.BroadcastRegistrations.Remove(tuple);
@@ -307,7 +310,7 @@ private void UnsubscribeEvent(Type eventType, BroadcastRegistration tuple, IEnti
private void ProcessSingleEvent(EventSource source, ref Unit unitRef, Type eventType)
{
- if (!_eventData.TryGetValue(eventType, out var subs))
+ if (!_eventData!.TryGetValue(eventType, out var subs))
return;
if (subs.IsOrdered && !subs.OrderingUpToDate)
diff --git a/Robust.Shared/GameObjects/EntityEventBus.Common.cs b/Robust.Shared/GameObjects/EntityEventBus.Common.cs
index 0e43936e4f6..c251e42fa59 100644
--- a/Robust.Shared/GameObjects/EntityEventBus.Common.cs
+++ b/Robust.Shared/GameObjects/EntityEventBus.Common.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Frozen;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -14,7 +15,8 @@ internal sealed partial class EntityEventBus : IEventBus
private IComponentFactory _comFac;
// Data on individual events. Used to check ordering info and fire broadcast events.
- private readonly Dictionary _eventData = new();
+ private FrozenDictionary _eventData = FrozenDictionary.Empty;
+ private readonly Dictionary _eventDataUnfrozen = new();
// Inverse subscriptions to be able to unsubscribe an IEntityEventSubscriber.
private readonly Dictionary> _inverseEventSubscriptions
@@ -28,10 +30,18 @@ private readonly Dictionary _entEventTables = new();
// CompType -> EventType -> Handler
- internal Dictionary?[] _entSubscriptions =
+ internal FrozenDictionary?[] _entSubscriptions = default!;
+
+ // Variant of _entSubscriptions that omits any events with the ComponentEventAttribute
+ internal FrozenDictionary?[] _entSubscriptionsNoCompEv = default!;
+
+ // pre-freeze _entSubscriptions data
+ internal Dictionary?[] _entSubscriptionsUnfrozen =
Array.Empty?>();
// EventType -> { CompType1, ... CompType N }
+ // Only required to sort ordered subscriptions, which only happens during initialization
+ // so doesn't need to be a frozen dictionary.
private Dictionary> _entSubscriptionsInv = new();
// prevents shitcode, get your subscriptions figured out before you start spawning entities
@@ -52,7 +62,10 @@ private static ref Unit ExtractUnitRef(ref object obj, Type objType)
private void RegisterCommon(Type eventType, OrderingData? data, out EventData subs)
{
- subs = _eventData.GetOrNew(eventType);
+ if (_subscriptionLock)
+ throw new InvalidOperationException("Subscription locked.");
+
+ subs = _eventDataUnfrozen.GetOrNew(eventType);
if (data == null)
return;
diff --git a/Robust.Shared/GameObjects/EntityEventBus.Directed.cs b/Robust.Shared/GameObjects/EntityEventBus.Directed.cs
index fe38bca7d39..d8ccd62431b 100644
--- a/Robust.Shared/GameObjects/EntityEventBus.Directed.cs
+++ b/Robust.Shared/GameObjects/EntityEventBus.Directed.cs
@@ -1,6 +1,8 @@
using System;
+using System.Collections.Frozen;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
+using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Robust.Shared.Collections;
@@ -122,16 +124,7 @@ public EntityEventBus(IEntityManager entMan)
// Dynamic handling of components is only for RobustUnitTest compatibility spaghetti.
_comFac.ComponentsAdded += ComFacOnComponentsAdded;
-
- InitEntSubscriptionsArray();
- }
-
- private void InitEntSubscriptionsArray()
- {
- foreach (var reg in _comFac.GetAllRegistrations())
- {
- CompIdx.AssignArray(ref _entSubscriptions, reg.Idx, new Dictionary());
- }
+ ComFacOnComponentsAdded(_comFac.GetAllRegistrations().ToArray());
}
///
@@ -329,9 +322,12 @@ public void UnsubscribeLocalEvent()
private void ComFacOnComponentsAdded(ComponentRegistration[] regs)
{
+ if (_subscriptionLock)
+ throw new InvalidOperationException("Subscription locked.");
+
foreach (var reg in regs)
{
- CompIdx.RefArray(ref _entSubscriptions, reg.Idx) ??= new Dictionary();
+ CompIdx.RefArray(ref _entSubscriptionsUnfrozen, reg.Idx) ??= new();
}
}
@@ -346,10 +342,43 @@ public void OnEntityDeleted(EntityUid e)
}
public void OnComponentAdded(in AddedComponentEventArgs e)
+ {
+ EntAddComponent(e.BaseArgs.Owner, e.ComponentType.Idx);
+ }
+
+ internal void LockSubscriptions()
{
_subscriptionLock = true;
+ _eventData = _eventDataUnfrozen.ToFrozenDictionary();
- EntAddComponent(e.BaseArgs.Owner, e.ComponentType.Idx);
+ _entSubscriptions = _entSubscriptionsUnfrozen
+ .Select(x => x?.ToFrozenDictionary())
+ .ToArray();
+
+ _entSubscriptionsNoCompEv = _entSubscriptionsUnfrozen.Select(FreezeWithoutComponentEvent).ToArray();
+
+ CalcOrdering();
+ }
+
+ ///
+ /// Freezes a dictionary while committing events with the .
+ /// This avoids unnecessarily adding one-off events to the list of subscriptions.
+ ///
+ private FrozenDictionary? FreezeWithoutComponentEvent(
+ Dictionary? input)
+ {
+ if (input == null)
+ return null;
+
+ return input.Where(x => !IsComponentEvent(x.Key))
+ .ToFrozenDictionary();
+ }
+
+ private bool IsComponentEvent(Type t)
+ {
+ var isCompEv = _eventData[t].ComponentEvent;
+ DebugTools.Assert(isCompEv == t.HasCustomAttribute());
+ return isCompEv;
}
public void OnComponentRemoved(in RemovedComponentEventArgs e)
@@ -366,7 +395,8 @@ private void EntAddSubscription(
if (_subscriptionLock)
throw new InvalidOperationException("Subscription locked.");
- if (compType.Value >= _entSubscriptions.Length || _entSubscriptions[compType.Value] is not { } compSubs)
+ if (compType.Value >= _entSubscriptionsUnfrozen.Length
+ || _entSubscriptionsUnfrozen[compType.Value] is not { } compSubs)
{
if (IgnoreUnregisteredComponents)
return;
@@ -375,13 +405,13 @@ private void EntAddSubscription(
}
if (compSubs.ContainsKey(eventType))
+ {
throw new InvalidOperationException(
$"Duplicate Subscriptions for comp={compTypeObj}, event={eventType.Name}");
+ }
compSubs.Add(eventType, registration);
-
- var invSubs = _entSubscriptionsInv.GetOrNew(eventType);
- invSubs.Add(compType);
+ _entSubscriptionsInv.GetOrNew(eventType).Add(compType);
RegisterCommon(eventType, registration.Ordering, out var data);
data.ComponentEvent = eventType.HasCustomAttribute();
@@ -408,7 +438,8 @@ private void EntUnsubscribe(CompIdx compType, Type eventType)
if (_subscriptionLock)
throw new InvalidOperationException("Subscription locked.");
- if (compType.Value >= _entSubscriptions.Length || _entSubscriptions[compType.Value] is not { } compSubs)
+ if (compType.Value >= _entSubscriptionsUnfrozen.Length
+ || _entSubscriptionsUnfrozen[compType.Value] is not { } compSubs)
{
if (IgnoreUnregisteredComponents)
return;
@@ -435,14 +466,14 @@ private void EntRemoveEntity(EntityUid euid)
private void EntAddComponent(EntityUid euid, CompIdx compType)
{
+ DebugTools.Assert(_subscriptionLock);
+
var eventTable = _entEventTables[euid];
- var compSubs = _entSubscriptions[compType.Value]!;
+ var compSubs = _entSubscriptionsNoCompEv[compType.Value]!;
foreach (var evType in compSubs.Keys)
{
- // Skip adding this to significantly reduce memory use and GC noise on entity create.
- if (_eventData[evType].ComponentEvent)
- continue;
+ DebugTools.Assert(!_eventData[evType].ComponentEvent);
if (eventTable.Free < 0)
GrowEventTable(eventTable);
@@ -607,17 +638,16 @@ private bool EntTryGetSubscriptions(Type eventType, EntityUid euid, out Subscrip
return true;
}
- private void EntClear()
+ public void ClearSubscriptions()
{
- _entEventTables = new();
_subscriptionLock = false;
- }
-
- public void ClearEventTables()
- {
- EntClear();
-
- foreach (var sub in _entSubscriptions)
+ _eventDataUnfrozen.Clear();
+ _entEventTables.Clear();
+ _inverseEventSubscriptions.Clear();
+ _entSubscriptions = default!;
+ _entSubscriptionsNoCompEv = default!;
+ _eventData = FrozenDictionary.Empty;
+ foreach (var sub in _entSubscriptionsUnfrozen)
{
sub?.Clear();
}
@@ -632,6 +662,8 @@ public void Dispose()
_comFac = null!;
_entEventTables = null!;
_entSubscriptions = null!;
+ _entSubscriptionsNoCompEv = null!;
+ _entSubscriptionsUnfrozen = null!;
_entSubscriptionsInv = null!;
}
@@ -639,7 +671,7 @@ private struct SubscriptionsEnumerator
{
private readonly Type _eventType;
private readonly EntityUid _uid;
- private readonly Dictionary?[] _subscriptions;
+ private readonly FrozenDictionary?[] _subscriptions;
private readonly IEntityManager _entityManager;
private readonly EventTableListEntry[] _list;
private int _idx;
@@ -648,7 +680,7 @@ public SubscriptionsEnumerator(
Type eventType,
int startEntry,
EventTableListEntry[] list,
- Dictionary?[] subscriptions,
+ FrozenDictionary?[] subscriptions,
EntityUid uid,
IEntityManager entityManager)
{
diff --git a/Robust.Shared/GameObjects/EntityManager.cs b/Robust.Shared/GameObjects/EntityManager.cs
index 02c4ec88d3e..f903aa7c8f8 100644
--- a/Robust.Shared/GameObjects/EntityManager.cs
+++ b/Robust.Shared/GameObjects/EntityManager.cs
@@ -26,7 +26,7 @@ namespace Robust.Shared.GameObjects
///
[Virtual]
- public partial class EntityManager : IEntityManager
+ public abstract partial class EntityManager : IEntityManager
{
#region Dependencies
@@ -61,7 +61,7 @@ public partial class EntityManager : IEntityManager
public IEntitySystemManager EntitySysManager => _entitySystemManager;
///
- public virtual IEntityNetworkManager? EntityNetManager => null;
+ public abstract IEntityNetworkManager EntityNetManager { get; }
protected readonly Queue QueuedDeletions = new();
protected readonly HashSet QueuedDeletionsSet = new();
@@ -203,7 +203,7 @@ public virtual void Startup()
// TODO: Probably better to call this on its own given it's so infrequent.
_entitySystemManager.Initialize();
Started = true;
- _eventBus.CalcOrdering();
+ _eventBus.LockSubscriptions();
_mapSystem = System();
_xforms = System();
_containers = System();
@@ -216,7 +216,7 @@ public virtual void Shutdown()
{
ShuttingDown = true;
FlushEntities();
- _eventBus.ClearEventTables();
+ _eventBus.ClearSubscriptions();
_entitySystemManager.Shutdown();
ClearComponents();
ShuttingDown = false;
diff --git a/Robust.Shared/GameObjects/IComponent.cs b/Robust.Shared/GameObjects/IComponent.cs
index aab4e33894e..c3999910f6c 100644
--- a/Robust.Shared/GameObjects/IComponent.cs
+++ b/Robust.Shared/GameObjects/IComponent.cs
@@ -16,10 +16,9 @@ public partial interface IComponent
/// The current lifetime stage of this component. You can use this to check
/// if the component is initialized or being deleted.
///
- ComponentLifeStage LifeStage { get; [Obsolete("Do not use from content")] set; }
+ ComponentLifeStage LifeStage { get; internal set; }
- [Obsolete("Do not use from content")]
- bool Networked { get; set; }
+ internal bool Networked { get; set; }
///
/// Whether this component should be synchronized with clients when modified.
@@ -68,22 +67,21 @@ public partial interface IComponent
///
/// Marks the component as dirty so that the network will re-sync it with clients.
///
+ [Obsolete]
void Dirty(IEntityManager? entManager = null);
///
/// This is the tick the component was created.
///
- GameTick CreationTick { get; set; }
+ GameTick CreationTick { get; internal set; }
///
/// This is the last game tick Dirty() was called.
///
- GameTick LastModifiedTick { get; set; }
+ GameTick LastModifiedTick { get; internal set; }
- [Obsolete("Do not use from content")]
- void ClearTicks();
+ internal void ClearTicks();
- [Obsolete("Do not use from content")]
- void ClearCreationTick();
+ internal void ClearCreationTick();
}
}
diff --git a/Robust.Shared/GameObjects/IComponentFactory.cs b/Robust.Shared/GameObjects/IComponentFactory.cs
index f98d0f2898c..b7ade646198 100644
--- a/Robust.Shared/GameObjects/IComponentFactory.cs
+++ b/Robust.Shared/GameObjects/IComponentFactory.cs
@@ -83,6 +83,11 @@ public interface IComponentFactory
/// If the component already exists, will this replace it?
void RegisterClass(bool overwrite = false) where T : IComponent, new();
+ ///
+ /// Registers component types with the factory.
+ ///
+ void RegisterTypes(params Type[] type);
+
///
/// Registers a component name as being ignored.
///
diff --git a/Robust.Shared/GameObjects/IEntityManager.cs b/Robust.Shared/GameObjects/IEntityManager.cs
index 7884a4fac53..fc052dc8bfc 100644
--- a/Robust.Shared/GameObjects/IEntityManager.cs
+++ b/Robust.Shared/GameObjects/IEntityManager.cs
@@ -46,7 +46,7 @@ public partial interface IEntityManager
IComponentFactory ComponentFactory { get; }
IEntitySystemManager EntitySysManager { get; }
- IEntityNetworkManager? EntityNetManager { get; }
+ IEntityNetworkManager EntityNetManager { get; }
IEventBus EventBus { get; }
#region Entity Management
diff --git a/Robust.Shared/Network/Messages/MsgConCompletionResp.cs b/Robust.Shared/Network/Messages/MsgConCompletionResp.cs
index 3a64364c7c7..68bea41274e 100644
--- a/Robust.Shared/Network/Messages/MsgConCompletionResp.cs
+++ b/Robust.Shared/Network/Messages/MsgConCompletionResp.cs
@@ -4,14 +4,12 @@
namespace Robust.Shared.Network.Messages;
-#nullable disable
-
public sealed class MsgConCompletionResp : NetMessage
{
public override MsgGroups MsgGroup => MsgGroups.Command;
public int Seq { get; set; }
- public CompletionResult Result { get; set; }
+ public CompletionResult Result { get; set; } = default!;
public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer)
{
diff --git a/Robust.Shared/Physics/PhysicsWakeEvent.cs b/Robust.Shared/Physics/PhysicsWakeEvent.cs
index 549d7d7c66a..145e82776ae 100644
--- a/Robust.Shared/Physics/PhysicsWakeEvent.cs
+++ b/Robust.Shared/Physics/PhysicsWakeEvent.cs
@@ -7,11 +7,4 @@ namespace Robust.Shared.Physics;
public readonly record struct PhysicsWakeEvent(EntityUid Entity, PhysicsComponent Body);
[ByRefEvent]
-public record struct PhysicsSleepEvent(EntityUid Entity, PhysicsComponent Body)
-{
- ///
- /// Marks the entity as still being awake and cancels sleeping.
- /// This only works if is still enabled and the object is not a static object..
- ///
- public bool Cancelled;
-};
+public record struct PhysicsSleepEvent(EntityUid Entity, PhysicsComponent Body);
diff --git a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Components.cs b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Components.cs
index 4f4a8cc6ee1..303d9796b2d 100644
--- a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Components.cs
+++ b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Components.cs
@@ -401,29 +401,14 @@ public void SetAwake(Entity ent, bool value, bool updateSleepT
}
else
{
- // TODO C# event?
var ev = new PhysicsSleepEvent(uid, body);
RaiseLocalEvent(uid, ref ev, true);
-
- // Reset the sleep timer.
- if (ev.Cancelled && canWake)
- {
- body.Awake = true;
- // TODO C# event?
- var wakeEv = new PhysicsWakeEvent(uid, body);
- RaiseLocalEvent(uid, ref wakeEv, true);
-
- if (updateSleepTime)
- SetSleepTime(body, 0);
-
- return;
- }
-
ResetDynamics(body, dirty: false);
}
- // Update wake system after we are sure that the wake/sleep event wasn't cancelled.
- _wakeSystem.UpdateCanCollide(ent, checkTerminating: false, dirty: false);
+ // Update wake system last, if sleeping but still colliding.
+ if (!value && body.CanCollide)
+ _wakeSystem.UpdateCanCollide(ent, checkTerminating: false, dirty: false);
if (updateSleepTime)
SetSleepTime(body, 0);
diff --git a/Robust.Shared/Random/IRobustRandom.cs b/Robust.Shared/Random/IRobustRandom.cs
index 4c50b929233..b7a0e2bdbdd 100644
--- a/Robust.Shared/Random/IRobustRandom.cs
+++ b/Robust.Shared/Random/IRobustRandom.cs
@@ -1,9 +1,11 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Numerics;
using System.Runtime.CompilerServices;
using Robust.Shared.Collections;
using Robust.Shared.Maths;
+using Robust.Shared.Toolshed.Commands.Generic;
namespace Robust.Shared.Random;
diff --git a/Robust.Shared/Random/RandomExtensions.cs b/Robust.Shared/Random/RandomExtensions.cs
index 1ca88e1bea0..891105128ae 100644
--- a/Robust.Shared/Random/RandomExtensions.cs
+++ b/Robust.Shared/Random/RandomExtensions.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using Robust.Shared.Collections;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
@@ -47,7 +48,7 @@ public static T Pick(this IRobustRandom random, IReadOnlyCollection collec
}
}
- throw new InvalidOperationException("This should be unreachable!");
+ throw new UnreachableException("This should be unreachable!");
}
public static T PickAndTake(this IRobustRandom random, IList list)
@@ -58,6 +59,36 @@ public static T PickAndTake(this IRobustRandom random, IList list)
return element;
}
+ ///
+ /// Picks a random element from a set and returns it.
+ /// This is O(n) as it has to iterate the collection until the target index.
+ ///
+ public static T Pick(this System.Random random, ICollection collection)
+ {
+ var index = random.Next(collection.Count);
+ var i = 0;
+ foreach (var t in collection)
+ {
+ if (i++ == index)
+ {
+ return t;
+ }
+ }
+
+ throw new UnreachableException("This should be unreachable!");
+ }
+
+ ///
+ /// Picks a random from a collection then removes it and returns it.
+ /// This is O(n) as it has to iterate the collection until the target index.
+ ///
+ public static T PickAndTake(this System.Random random, ICollection set)
+ {
+ var tile = Pick(random, set);
+ set.Remove(tile);
+ return tile;
+ }
+
///
/// Generate a random number from a normal (gaussian) distribution.
///
diff --git a/Robust.Shared/ViewVariables/ViewVariablesUtility.cs b/Robust.Shared/ViewVariables/ViewVariablesUtility.cs
index 65071f44a29..827bb990d0c 100644
--- a/Robust.Shared/ViewVariables/ViewVariablesUtility.cs
+++ b/Robust.Shared/ViewVariables/ViewVariablesUtility.cs
@@ -40,7 +40,7 @@ public static bool TryGetViewVariablesAccess(MemberInfo info, [NotNullWhen(true)
if (info.HasCustomAttribute() || info.HasCustomAttribute())
{
- access = VVAccess.ReadOnly;
+ access = VVAccess.ReadWrite;
return true;
}
diff --git a/Robust.UnitTesting/RobustUnitTest.cs b/Robust.UnitTesting/RobustUnitTest.cs
index eda739c47d5..0cacf50afee 100644
--- a/Robust.UnitTesting/RobustUnitTest.cs
+++ b/Robust.UnitTesting/RobustUnitTest.cs
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
-using System.Linq;
using System.Reflection;
using NUnit.Framework;
using Robust.Client.ComponentTrees;
@@ -39,6 +38,33 @@ public enum UnitTestProject : byte
[Parallelizable]
public abstract partial class RobustUnitTest
{
+ protected virtual Type[]? ExtraComponents => null;
+ private static Type[] _components = new []
+ {
+ typeof(EyeComponent),
+ typeof(MapComponent),
+ typeof(MapGridComponent),
+ typeof(ContainerManagerComponent),
+ typeof(MetaDataComponent),
+ typeof(TransformComponent),
+ typeof(PhysicsComponent),
+ typeof(PhysicsMapComponent),
+ typeof(BroadphaseComponent),
+ typeof(FixturesComponent),
+ typeof(JointComponent),
+ typeof(GridTreeComponent),
+ typeof(MovedGridsComponent),
+ typeof(JointRelayTargetComponent),
+ typeof(OccluderComponent),
+ typeof(OccluderTreeComponent),
+ typeof(SpriteTreeComponent),
+ typeof(LightTreeComponent),
+ typeof(CollisionWakeComponent),
+ typeof(CollideOnAnchorComponent),
+ typeof(Gravity2DComponent),
+ typeof(ActorComponent)
+ };
+
public virtual UnitTestProject Project => UnitTestProject.Server;
[OneTimeSetUp]
@@ -122,6 +148,7 @@ public void BaseSetup()
systems.LoadExtraSystemType();
systems.LoadExtraSystemType();
systems.LoadExtraSystemType();
+ systems.LoadExtraSystemType();
systems.LoadExtraSystemType();
systems.LoadExtraSystemType();
systems.LoadExtraSystemType();
@@ -133,117 +160,11 @@ public void BaseSetup()
// Required components for the engine to work
// Why are we still here? Just to suffer? Why can't we just use [RegisterComponent] magic?
// TODO End Suffering.
+ // suffering has been alleviated, but still present
var compFactory = deps.Resolve();
-
- if (!compFactory.AllRegisteredTypes.Contains(typeof(EyeComponent)))
- {
- compFactory.RegisterClass();
- }
-
- if (!compFactory.AllRegisteredTypes.Contains(typeof(MapComponent)))
- {
- compFactory.RegisterClass();
- }
-
- if (!compFactory.AllRegisteredTypes.Contains(typeof(MapGridComponent)))
- {
- compFactory.RegisterClass();
- }
-
- if (!compFactory.AllRegisteredTypes.Contains(typeof(ContainerManagerComponent)))
- {
- compFactory.RegisterClass();
- }
-
- if (!compFactory.AllRegisteredTypes.Contains(typeof(MetaDataComponent)))
- {
- compFactory.RegisterClass();
- }
-
- if (!compFactory.AllRegisteredTypes.Contains(typeof(TransformComponent)))
- {
- compFactory.RegisterClass();
- }
-
- if (!compFactory.AllRegisteredTypes.Contains(typeof(PhysicsComponent)))
- {
- compFactory.RegisterClass();
- }
-
- if (!compFactory.AllRegisteredTypes.Contains(typeof(PhysicsMapComponent)))
- {
- compFactory.RegisterClass();
- }
-
- if (!compFactory.AllRegisteredTypes.Contains(typeof(BroadphaseComponent)))
- {
- compFactory.RegisterClass();
- }
-
- if (!compFactory.AllRegisteredTypes.Contains(typeof(FixturesComponent)))
- {
- compFactory.RegisterClass();
- }
-
- if (!compFactory.AllRegisteredTypes.Contains(typeof(JointComponent)))
- {
- compFactory.RegisterClass();
- }
-
- if (!compFactory.AllRegisteredTypes.Contains(typeof(GridTreeComponent)))
- {
- compFactory.RegisterClass();
- }
-
- if (!compFactory.AllRegisteredTypes.Contains(typeof(MovedGridsComponent)))
- {
- compFactory.RegisterClass();
- }
-
- if (!compFactory.AllRegisteredTypes.Contains(typeof(JointRelayTargetComponent)))
- {
- compFactory.RegisterClass();
- }
-
- if (!compFactory.AllRegisteredTypes.Contains(typeof(OccluderComponent)))
- {
- compFactory.RegisterClass();
- }
-
- if (!compFactory.AllRegisteredTypes.Contains(typeof(OccluderTreeComponent)))
- {
- compFactory.RegisterClass();
- }
-
- if (!compFactory.AllRegisteredTypes.Contains(typeof(SpriteTreeComponent)))
- {
- compFactory.RegisterClass();
- }
-
- if (!compFactory.AllRegisteredTypes.Contains(typeof(LightTreeComponent)))
- {
- compFactory.RegisterClass();
- }
-
- if (!compFactory.AllRegisteredTypes.Contains(typeof(Gravity2DComponent)))
- {
- compFactory.RegisterClass();
- }
-
- if (!compFactory.AllRegisteredTypes.Contains(typeof(CollisionWakeComponent)))
- {
- compFactory.RegisterClass();
- }
-
- if (!compFactory.AllRegisteredTypes.Contains(typeof(CollideOnAnchorComponent)))
- {
- compFactory.RegisterClass();
- }
-
- if (!compFactory.AllRegisteredTypes.Contains(typeof(ActorComponent)))
- {
- compFactory.RegisterClass();
- }
+ compFactory.RegisterTypes(_components);
+ if (ExtraComponents != null)
+ compFactory.RegisterTypes(ExtraComponents);
deps.Resolve().Initialize();
@@ -252,7 +173,7 @@ public void BaseSetup()
entMan.Initialize();
// RobustUnitTest is complete hot garbage.
// This makes EventTables ignore *all* the screwed up component abuse it causes.
- entMan.EventBus.OnlyCallOnRobustUnitTestISwearToGodPleaseSomebodyKillThisNightmare();
+ entMan.EventBus.OnlyCallOnRobustUnitTestISwearToGodPleaseSomebodyKillThisNightmare(); // The nightmare never ends
mapMan.Initialize();
systems.Initialize();
diff --git a/Robust.UnitTesting/Server/Maps/MapLoaderTest.cs b/Robust.UnitTesting/Server/Maps/MapLoaderTest.cs
index 6a22e5271c8..515e4709235 100644
--- a/Robust.UnitTesting/Server/Maps/MapLoaderTest.cs
+++ b/Robust.UnitTesting/Server/Maps/MapLoaderTest.cs
@@ -1,3 +1,4 @@
+using System;
using System.Linq;
using NUnit.Framework;
using Robust.Server.GameObjects;
@@ -52,24 +53,14 @@ public sealed partial class MapLoaderTest : RobustUnitTest
- type: MapDeserializeTest
foo: 1
bar: 2
-
";
+ protected override Type[]? ExtraComponents => new[] { typeof(MapDeserializeTestComponent), typeof(VisibilityComponent), typeof(IgnoreUIRangeComponent)};
+
[OneTimeSetUp]
public void Setup()
{
- var compFactory = IoCManager.Resolve();
- compFactory.RegisterClass();
- compFactory.RegisterClass();
- compFactory.RegisterClass();
- compFactory.GenerateNetIds();
IoCManager.Resolve().Initialize();
-
- // For some reason RobustUnitTest doesn't discover PVSSystem but this does here so ?
- var syssy = IoCManager.Resolve();
- syssy.Shutdown();
- syssy.Initialize();
-
var resourceManager = IoCManager.Resolve();
resourceManager.Initialize(null);
resourceManager.MountString("/TestMap.yml", MapData);
diff --git a/Robust.UnitTesting/Shared/GameObjects/ComponentFactory_Tests.cs b/Robust.UnitTesting/Shared/GameObjects/ComponentFactory_Tests.cs
index 7636cab7bdd..6097e79c05f 100644
--- a/Robust.UnitTesting/Shared/GameObjects/ComponentFactory_Tests.cs
+++ b/Robust.UnitTesting/Shared/GameObjects/ComponentFactory_Tests.cs
@@ -1,3 +1,4 @@
+using System;
using NUnit.Framework;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
@@ -13,12 +14,7 @@ public sealed partial class ComponentFactory_Tests : RobustUnitTest
private const string TestComponentName = "A";
private const string LowercaseTestComponentName = "a";
private const string NonexistentComponentName = "B";
-
- [OneTimeSetUp]
- public void OneTimeSetUp()
- {
- IoCManager.Resolve().RegisterClass();
- }
+ protected override Type[]? ExtraComponents => new[] {typeof(TestComponent)};
[Test]
public void GetComponentAvailabilityTest()
diff --git a/Robust.UnitTesting/Shared/GameObjects/EntityEventBusTests.ComponentEvent.cs b/Robust.UnitTesting/Shared/GameObjects/EntityEventBusTests.ComponentEvent.cs
index 406022670d4..ff42c0e1022 100644
--- a/Robust.UnitTesting/Shared/GameObjects/EntityEventBusTests.ComponentEvent.cs
+++ b/Robust.UnitTesting/Shared/GameObjects/EntityEventBusTests.ComponentEvent.cs
@@ -41,6 +41,7 @@ public void SubscribeCompEvent()
// Subscribe
int calledCount = 0;
bus.SubscribeLocalEvent(HandleTestEvent);
+ bus.LockSubscriptions();
// add a component to the system
bus.OnEntityAdded(entUid);
@@ -98,6 +99,7 @@ public void UnsubscribeCompEvent()
int calledCount = 0;
bus.SubscribeLocalEvent(HandleTestEvent);
bus.UnsubscribeLocalEvent();
+ bus.LockSubscriptions();
// add a component to the system
bus.OnEntityAdded(entUid);
@@ -153,6 +155,7 @@ public void SubscribeCompLifeEvent()
// Subscribe
int calledCount = 0;
bus.SubscribeLocalEvent(HandleTestEvent);
+ bus.LockSubscriptions();
// add a component to the system
entManMock.Raise(m => m.EntityAdded += null, entUid);
@@ -232,6 +235,7 @@ void HandlerB(EntityUid uid, Component comp, TestEvent ev)
bus.SubscribeLocalEvent(HandlerA, typeof(OrderAComponent), before: new []{typeof(OrderBComponent), typeof(OrderCComponent)});
bus.SubscribeLocalEvent(HandlerB, typeof(OrderBComponent), after: new []{typeof(OrderCComponent)});
bus.SubscribeLocalEvent(HandlerC, typeof(OrderCComponent));
+ bus.LockSubscriptions();
// add a component to the system
bus.OnEntityAdded(entUid);
diff --git a/Robust.UnitTesting/Shared/GameObjects/EntityEventBusTests.SystemEvent.cs b/Robust.UnitTesting/Shared/GameObjects/EntityEventBusTests.SystemEvent.cs
index da734c1f55f..44bb208ecc7 100644
--- a/Robust.UnitTesting/Shared/GameObjects/EntityEventBusTests.SystemEvent.cs
+++ b/Robust.UnitTesting/Shared/GameObjects/EntityEventBusTests.SystemEvent.cs
@@ -137,6 +137,7 @@ public void SubscribeEvent_MultipleSubscriptions_IndividuallyCalled()
bus.SubscribeEvent(EventSource.Local, subscriber, ev => delFooCount++);
bus.SubscribeEvent(EventSource.Local, subscriber, ev => delBarCount++);
+ bus.LockSubscriptions();
// Act & Assert
bus.RaiseEvent(EventSource.Local, new TestEventArgs());
@@ -184,6 +185,7 @@ void Handler(TestEventArgs ev) { }
bus.SubscribeEvent(EventSource.Local, subscriber, Handler);
bus.UnsubscribeEvent(EventSource.Local, subscriber);
+ bus.LockSubscriptions();
// Act
bus.UnsubscribeEvent(EventSource.Local, subscriber);
@@ -253,6 +255,7 @@ public void RaiseEvent_NoSubscriptions_Nop()
int delCalledCount = 0;
bus.SubscribeEvent(EventSource.Local, subscriber, ev => delCalledCount++);
+ bus.LockSubscriptions();
// Act
bus.RaiseEvent(EventSource.Local, new TestEventArgs());
@@ -276,6 +279,7 @@ public void RaiseEvent_Unsubscribed_Nop()
bus.SubscribeEvent(EventSource.Local, subscriber, Handler);
bus.UnsubscribeEvent(EventSource.Local, subscriber);
+ bus.LockSubscriptions();
// Act
bus.RaiseEvent(EventSource.Local, new TestEventArgs());
@@ -348,6 +352,7 @@ public void UnsubscribeEvents_UnsubscribedHandler_Nop()
bus.SubscribeEvent(EventSource.Local, subscriber, Handler);
bus.UnsubscribeEvents(subscriber);
+ bus.LockSubscriptions();
// Act
bus.RaiseEvent(EventSource.Local, new TestEventArgs());
@@ -425,6 +430,7 @@ public void ProcessQueue_EventQueued_HandlerRaised()
void Handler(TestEventArgs ev) => delCallCount++;
bus.SubscribeEvent(EventSource.Local, subscriber, Handler);
+ bus.LockSubscriptions();
bus.QueueEvent(EventSource.Local, new TestEventArgs());
// Act
@@ -464,6 +470,7 @@ void HandlerB(TestEventArgs ev)
bus.SubscribeEvent(EventSource.Local, new SubA(), HandlerA, typeof(SubA), before: new []{typeof(SubB), typeof(SubC)});
bus.SubscribeEvent(EventSource.Local, new SubB(), HandlerB, typeof(SubB), after: new []{typeof(SubC)});
bus.SubscribeEvent(EventSource.Local, new SubC(), HandlerC, typeof(SubC));
+ bus.LockSubscriptions();
// Act
bus.RaiseEvent(EventSource.Local, new TestEventArgs());
diff --git a/Robust.UnitTesting/Shared/GameObjects/Systems/AnchoredSystemTests.cs b/Robust.UnitTesting/Shared/GameObjects/Systems/AnchoredSystemTests.cs
index 3f24e68937e..fbe135b63a3 100644
--- a/Robust.UnitTesting/Shared/GameObjects/Systems/AnchoredSystemTests.cs
+++ b/Robust.UnitTesting/Shared/GameObjects/Systems/AnchoredSystemTests.cs
@@ -3,6 +3,7 @@
using NUnit.Framework;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
+using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
@@ -33,6 +34,7 @@ private static (ISimulation, EntityUid gridId) SimulationFactory()
{
var sim = RobustServerSimulation
.NewSimulation()
+ .RegisterEntitySystems(f => f.LoadExtraSystemType())
.RegisterPrototypes(f=>
{
f.LoadString(Prototypes);
@@ -69,7 +71,6 @@ public void OnAnchored_WorldPosition_TileCenter()
var (sim, gridId) = SimulationFactory();
var entMan = sim.Resolve();
var mapMan = sim.Resolve();
- var xform = entMan.System();
var coordinates = new MapCoordinates(new Vector2(7, 7), TestMapId);
@@ -77,20 +78,13 @@ public void OnAnchored_WorldPosition_TileCenter()
var grid = mapMan.GetGrid(gridId);
grid.SetTile(grid.TileIndicesFor(coordinates), new Tile(1));
- int calledCount = 0;
var ent1 = entMan.SpawnEntity(null, coordinates); // this raises MoveEvent, subscribe after
- xform.OnGlobalMoveEvent += MoveEventHandler;
// Act
+ entMan.System().ResetCounters();
entMan.GetComponent(ent1).Anchored = true;
-
Assert.That(entMan.GetComponent(ent1).WorldPosition, Is.EqualTo(new Vector2(7.5f, 7.5f))); // centered on tile
- Assert.That(calledCount, Is.EqualTo(1)); // because the ent was moved from snapping, a MoveEvent was raised.
- xform.OnGlobalMoveEvent -= MoveEventHandler;
- void MoveEventHandler(ref MoveEvent ev)
- {
- calledCount++;
- }
+ entMan.System().AssertMoved(false);
}
[ComponentProtoName("AnchorOnInit")]
@@ -105,6 +99,55 @@ public override void Initialize()
}
}
+ internal sealed class MoveEventTestSystem : EntitySystem
+ {
+ [Dependency] private readonly SharedTransformSystem _transform = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+ _transform.OnGlobalMoveEvent += OnMove;
+ SubscribeLocalEvent(OnReparent);
+ }
+
+
+ public override void Shutdown()
+ {
+ base.Shutdown();
+ _transform.OnGlobalMoveEvent -= OnMove;
+ }
+
+ public bool FailOnMove = false;
+ public int MoveCounter = 0;
+ public int ParentCounter = 0;
+
+ private void OnMove(ref MoveEvent ev)
+ {
+ MoveCounter++;
+ if (FailOnMove)
+ Assert.Fail($"Move event was raised");
+ }
+ private void OnReparent(ref EntParentChangedMessage ev)
+ {
+ ParentCounter++;
+ if (FailOnMove)
+ Assert.Fail($"Move event was raised");
+ }
+
+ public void ResetCounters()
+ {
+ ParentCounter = 0;
+ MoveCounter = 0;
+ }
+
+ public void AssertMoved(bool parentChanged = true)
+ {
+ if (parentChanged)
+ Assert.That(ParentCounter, Is.EqualTo(1));
+ Assert.That(MoveCounter, Is.EqualTo(1));
+ }
+ }
+
///
/// Ensures that if an entity gets added to lookups when anchored during init by some system.
///
@@ -159,23 +202,13 @@ public void OnAnchored_Parent_SetToGrid()
var traversal = entMan.System();
traversal.Enabled = false;
-
- var subscriber = new Subscriber();
- int calledCount = 0;
var ent1 = entMan.SpawnEntity(null, coordinates); // this raises MoveEvent, subscribe after
- entMan.EventBus.SubscribeEvent(EventSource.Local, subscriber, ParentChangedHandler);
// Act
+ entMan.System().ResetCounters();
entMan.GetComponent(ent1).Anchored = true;
-
Assert.That(entMan.GetComponent(ent1).ParentUid, Is.EqualTo(grid.Owner));
- Assert.That(calledCount, Is.EqualTo(1));
- void ParentChangedHandler(ref EntParentChangedMessage ev)
- {
- Assert.That(ev.Entity, Is.EqualTo(ent1));
- calledCount++;
-
- }
+ entMan.System().AssertMoved();
traversal.Enabled = true;
}
@@ -247,23 +280,16 @@ public void Anchored_SetPosition_Nop()
var grid = mapMan.GetGrid(gridId);
grid.SetTile(grid.TileIndicesFor(coordinates), new Tile(1));
- int calledCount = 0;
var ent1 = entMan.SpawnEntity(null, coordinates); // this raises MoveEvent, subscribe after
entMan.GetComponent(ent1).Anchored = true; // Anchoring will change parent if needed, raising MoveEvent, subscribe after
- xform.OnGlobalMoveEvent += MoveEventHandler;
+ entMan.System().FailOnMove = true;
// Act
entMan.GetComponent(ent1).WorldPosition = new Vector2(99, 99);
entMan.GetComponent(ent1).LocalPosition = new Vector2(99, 99);
Assert.That(entMan.GetComponent(ent1).MapPosition, Is.EqualTo(coordinates));
- Assert.That(calledCount, Is.EqualTo(0));
- xform.OnGlobalMoveEvent -= MoveEventHandler;
- void MoveEventHandler(ref MoveEvent ev)
- {
- Assert.Fail("MoveEvent raised when entity is anchored.");
- calledCount++;
- }
+ entMan.System().FailOnMove = false;
}
///
@@ -517,22 +543,13 @@ public void Unanchored_Unanchor_Nop()
var traversal = entMan.System();
traversal.Enabled = false;
-
- var subscriber = new Subscriber();
- int calledCount = 0;
var ent1 = entMan.SpawnEntity(null, coordinates); // this raises MoveEvent, subscribe after
- entMan.EventBus.SubscribeEvent(EventSource.Local, subscriber, ParentChangedHandler);
// Act
+ entMan.System().FailOnMove = true;
entMan.GetComponent(ent1).Anchored = false;
-
Assert.That(entMan.GetComponent(ent1).ParentUid, Is.EqualTo(mapMan.GetMapEntityId(TestMapId)));
- Assert.That(calledCount, Is.EqualTo(0));
- void ParentChangedHandler(ref EntParentChangedMessage ev)
- {
- Assert.That(ev.Entity, Is.EqualTo(ent1));
- calledCount++;
- }
+ entMan.System().FailOnMove = false;
traversal.Enabled = true;
}
diff --git a/Robust.UnitTesting/Shared/GameObjects/Systems/TransformSystemTests.cs b/Robust.UnitTesting/Shared/GameObjects/Systems/TransformSystemTests.cs
index e4cc58012b3..8af756effe8 100644
--- a/Robust.UnitTesting/Shared/GameObjects/Systems/TransformSystemTests.cs
+++ b/Robust.UnitTesting/Shared/GameObjects/Systems/TransformSystemTests.cs
@@ -3,7 +3,6 @@
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
-using Robust.Shared.Maths;
using Robust.UnitTesting.Server;
namespace Robust.UnitTesting.Shared.GameObjects.Systems
@@ -15,6 +14,7 @@ private static ISimulation SimulationFactory()
{
var sim = RobustServerSimulation
.NewSimulation()
+ .RegisterEntitySystems(f => f.LoadExtraSystemType())
.InitializeInstance();
// Adds the map with id 1, and spawns entity 1 as the map entity.
@@ -31,22 +31,11 @@ public void OnMove_LocalPosChanged_RaiseMoveEvent()
{
var sim = SimulationFactory();
var entMan = sim.Resolve();
- var xform = entMan.System();
-
- int calledCount = 0;
- xform.OnGlobalMoveEvent += MoveEventHandler;
var ent1 = entMan.SpawnEntity(null, new MapCoordinates(Vector2.Zero, new MapId(1)));
+ entMan.System().ResetCounters();
IoCManager.Resolve().GetComponent(ent1).LocalPosition = Vector2.One;
-
- Assert.That(calledCount, Is.EqualTo(1));
- xform.OnGlobalMoveEvent -= MoveEventHandler;
- void MoveEventHandler(ref MoveEvent ev)
- {
- calledCount++;
- Assert.That(ev.OldPosition, Is.EqualTo(new EntityCoordinates(EntityUid.FirstUid, Vector2.Zero)));
- Assert.That(ev.NewPosition, Is.EqualTo(new EntityCoordinates(EntityUid.FirstUid, Vector2.One)));
- }
+ entMan.System().AssertMoved(false);
}
///
diff --git a/Robust.UnitTesting/Shared/Localization/LocalizationTests.cs b/Robust.UnitTesting/Shared/Localization/LocalizationTests.cs
index 8df563bcffc..e8bd66296dd 100644
--- a/Robust.UnitTesting/Shared/Localization/LocalizationTests.cs
+++ b/Robust.UnitTesting/Shared/Localization/LocalizationTests.cs
@@ -16,15 +16,12 @@ namespace Robust.UnitTesting.Shared.Localization
[TestFixture]
internal sealed class LocalizationTests : RobustUnitTest
{
+ protected override Type[] ExtraComponents => new[] {typeof(GrammarComponent)};
[OneTimeSetUp]
public void Setup()
{
IoCManager.Resolve().Initialize();
- var componentFactory = IoCManager.Resolve();
- componentFactory.RegisterClass();
- componentFactory.GenerateNetIds();
-
var res = IoCManager.Resolve();
res.MountString("/Locale/en-US/a.ftl", FluentCode);
res.MountString("/EnginePrototypes/a.yml", YAMLCode);
diff --git a/Robust.UnitTesting/Shared/Prototypes/HotReloadTest.cs b/Robust.UnitTesting/Shared/Prototypes/HotReloadTest.cs
index 0316c415b21..481879b02f5 100644
--- a/Robust.UnitTesting/Shared/Prototypes/HotReloadTest.cs
+++ b/Robust.UnitTesting/Shared/Prototypes/HotReloadTest.cs
@@ -33,19 +33,15 @@ public sealed class HotReloadTest : RobustUnitTest
value: 10
- type: {HotReloadTestComponentTwoId}";
- private IComponentFactory _components = default!;
private PrototypeManager _prototypes = default!;
private IMapManager _maps = default!;
private IEntityManager _entities = default!;
+ protected override Type[]? ExtraComponents => new[] {typeof(HotReloadTestOneComponent), typeof(HotReloadTestTwoComponent)};
+
[OneTimeSetUp]
public void Setup()
{
- _components = IoCManager.Resolve();
- _components.RegisterClass();
- _components.RegisterClass();
- _components.GenerateNetIds();
-
IoCManager.Resolve().Initialize();
_prototypes = (PrototypeManager) IoCManager.Resolve();
_prototypes.RegisterKind(typeof(EntityPrototype));
diff --git a/Robust.UnitTesting/Shared/Prototypes/PrototypeManager_Test.cs b/Robust.UnitTesting/Shared/Prototypes/PrototypeManager_Test.cs
index bb1a8b1b46d..bcf803e0b85 100644
--- a/Robust.UnitTesting/Shared/Prototypes/PrototypeManager_Test.cs
+++ b/Robust.UnitTesting/Shared/Prototypes/PrototypeManager_Test.cs
@@ -1,3 +1,4 @@
+using System;
using System.Numerics;
using JetBrains.Annotations;
using NUnit.Framework;
@@ -21,13 +22,11 @@ public sealed class PrototypeManager_Test : RobustUnitTest
private const string LoadStringTestDummyId = "LoadStringTestDummy";
private IPrototypeManager manager = default!;
+ protected override Type[] ExtraComponents => new[] {typeof(TestBasicPrototypeComponent), typeof(PointLightComponent)};
+
[OneTimeSetUp]
public void Setup()
{
- var factory = IoCManager.Resolve();
- factory.RegisterClass();
- factory.RegisterClass();
-
IoCManager.Resolve().Initialize();
manager = IoCManager.Resolve();
manager.RegisterKind(typeof(EntityPrototype));
diff --git a/Robust.UnitTesting/Shared/Serialization/InheritanceSerializationTest.cs b/Robust.UnitTesting/Shared/Serialization/InheritanceSerializationTest.cs
index a2691cbdbab..3fc8251ac33 100644
--- a/Robust.UnitTesting/Shared/Serialization/InheritanceSerializationTest.cs
+++ b/Robust.UnitTesting/Shared/Serialization/InheritanceSerializationTest.cs
@@ -1,3 +1,4 @@
+using System;
using NUnit.Framework;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
@@ -43,16 +44,11 @@ public sealed partial class InheritanceSerializationTest : RobustUnitTest
inheritorField: {InheritorComponentFieldValue}
finalField: {FinalComponentFieldValue}";
+ protected override Type[]? ExtraComponents => new[] {typeof(TestBaseComponent), typeof(TestInheritorComponent), typeof(TestFinalComponent)};
+
[Test]
public void Test()
{
- var componentFactory = IoCManager.Resolve();
-
- componentFactory.RegisterClass();
- componentFactory.RegisterClass();
- componentFactory.RegisterClass();
- componentFactory.GenerateNetIds();
-
var serializationManager = IoCManager.Resolve();
serializationManager.Initialize();
diff --git a/Robust.UnitTesting/Shared/Serialization/TypeSerializers/ComponentRegistrySerializerTest.cs b/Robust.UnitTesting/Shared/Serialization/TypeSerializers/ComponentRegistrySerializerTest.cs
index 37e75089b8b..cdeab9532b0 100644
--- a/Robust.UnitTesting/Shared/Serialization/TypeSerializers/ComponentRegistrySerializerTest.cs
+++ b/Robust.UnitTesting/Shared/Serialization/TypeSerializers/ComponentRegistrySerializerTest.cs
@@ -1,3 +1,4 @@
+using System;
using System.IO;
using NUnit.Framework;
using Robust.Shared.GameObjects;
@@ -19,12 +20,7 @@ namespace Robust.UnitTesting.Shared.Serialization.TypeSerializers
[TestOf(typeof(ComponentRegistrySerializer))]
public sealed class ComponentRegistrySerializerTest : SerializationTest
{
- [OneTimeSetUp]
- public new void OneTimeSetup()
- {
- var componentFactory = IoCManager.Resolve();
- componentFactory.RegisterClass();
- }
+ protected override Type[]? ExtraComponents => new[] {typeof(TestComponent)};
[Test]
public void SerializationTest()