using System; using System.Collections.Frozen; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using JetBrains.Annotations; using Robust.Shared.Collections; using Robust.Shared.Reflection; using Robust.Shared.Utility; namespace Robust.Shared.GameObjects; internal sealed partial class EntityEventBus : IEventBus { private EntityManager _entMan; private IComponentFactory _comFac; private IReflectionManager _reflection; // Data on individual events. Used to check ordering info and fire broadcast events. private FrozenDictionary _eventData = FrozenDictionary.Empty; private readonly Dictionary _eventDataUnfrozen = new(); // Inverse subscriptions to be able to unsubscribe an IEntityEventSubscriber. private readonly Dictionary> _inverseEventSubscriptions = new(); // For queued message broadcast. private readonly Queue<(EventSource source, object args)> _eventQueue = new(); // eUid -> EventType -> { CompType1, ... CompTypeN } // See EventTable declaration for layout details internal Dictionary _entEventTables = new(); /// /// Array of component events and their handlers. The array is indexed by a component's /// , while the dictionary is indexed by the event type. This does not include events /// with the , unless is false. /// private FrozenDictionary[] _eventSubs = default!; /// /// Variant of that only includes events with the /// private FrozenDictionary[] _compEventSubs = default!; // pre-freeze event subscription data private Dictionary?[] _eventSubsUnfrozen = []; private Dictionary?[] _compEventSubsUnfrozen = []; /// /// Inverse of , mapping event types to sets of components. /// private Dictionary> _eventSubsInv = new(); // Only required to sort ordered subscriptions, which only happens during initialization // so doesn't need to be a frozen dictionary. // prevents shitcode, get your subscriptions figured out before you start spawning entities private bool _subscriptionLock; public bool IgnoreUnregisteredComponents; private readonly List _childrenTypesTemp = []; [MethodImpl(MethodImplOptions.AggressiveInlining)] private static ref Unit ExtractUnitRef(ref object obj, Type objType) { // If it's a boxed value type we have to do some trickery to return the INTERIOR reference, // not the reference to the boxed object. // Otherwise the unit points to the reference to the reference type. return ref objType.IsValueType ? ref Unsafe.As(ref obj).Value : ref Unsafe.As(ref obj); } private void RegisterCommon(Type eventType, OrderingData? data, out EventData subs) { if (_subscriptionLock) throw new InvalidOperationException("Subscription locked."); subs = _eventDataUnfrozen.GetOrNew(eventType); if (data == null) return; if (data.Before.Length > 0 || data.After.Length > 0) { subs.IsOrdered = true; subs.OrderingUpToDate = false; } } /// /// Information for a single event type handled by EventBus. Not specific to broadcast registrations. /// private sealed class EventData { public bool IsOrdered; public bool OrderingUpToDate; public ValueList BroadcastRegistrations; } // This is not a real type. Whenever you see a "ref Unit" it means it's a ref to *some* kind of other type. // It should always be cast to/from with Unsafe.As<,> internal readonly struct Unit { } [StructLayout(LayoutKind.Sequential)] private sealed class UnitBox { [UsedImplicitly] public Unit Value; } }