mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-14 19:29:36 +01:00
* Fix ordered subscriptions not working when targeting a parent system type * Fix missing usages of expand ordering * Extract method
233 lines
8.5 KiB
C#
233 lines
8.5 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using Robust.Shared.Collections;
|
|
using Robust.Shared.Utility;
|
|
|
|
namespace Robust.Shared.GameObjects
|
|
{
|
|
internal partial class EntityEventBus
|
|
{
|
|
private static void CollectBroadcastOrdered(
|
|
EventSource source,
|
|
EventData sub,
|
|
ref ValueList<OrderedEventDispatch> found)
|
|
{
|
|
foreach (var handler in sub.BroadcastRegistrations.Span)
|
|
{
|
|
if ((handler.Mask & source) != 0)
|
|
found.Add(new OrderedEventDispatch(handler.Handler, handler.Order));
|
|
}
|
|
}
|
|
|
|
private void RaiseLocalOrdered(
|
|
EntityUid uid,
|
|
Type eventType,
|
|
EventData subs,
|
|
ref Unit unitRef,
|
|
bool broadcast)
|
|
{
|
|
if (!subs.OrderingUpToDate)
|
|
UpdateOrderSeq(eventType, subs);
|
|
|
|
var found = new ValueList<OrderedEventDispatch>();
|
|
|
|
if (broadcast)
|
|
CollectBroadcastOrdered(EventSource.Local, subs, ref found);
|
|
|
|
EntCollectOrdered(uid, eventType, ref found);
|
|
|
|
DispatchOrderedEvents(ref unitRef, ref found);
|
|
}
|
|
|
|
private static void DispatchOrderedEvents(ref Unit eventArgs, ref ValueList<OrderedEventDispatch> found)
|
|
{
|
|
found.Sort(OrderedEventDispatchComparer.Instance);
|
|
|
|
foreach (var (handler, _) in found.Span)
|
|
{
|
|
handler(ref eventArgs);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculate sequence order for all event subscriptions, broadcast and directed.
|
|
/// </summary>
|
|
private void UpdateOrderSeq(Type eventType, EventData sub)
|
|
{
|
|
DebugTools.Assert(sub.IsOrdered);
|
|
|
|
// Collect all subscriptions, broadcast and ordered.
|
|
IEnumerable<OrderedRegistration> regs = sub.BroadcastRegistrations;
|
|
if (_eventSubsInv.TryGetValue(eventType, out var comps))
|
|
{
|
|
regs = regs.Concat(comps
|
|
.Select(c => _eventSubs[c.Value])
|
|
.Where(c => c != null)
|
|
.Select(c => c![eventType]));
|
|
}
|
|
|
|
// A hard problem in EventBus' design is that ordering is keyed on a single Type instance
|
|
// (probably just the type of the EntitySystem).
|
|
// This is problematic if a system listens to the same directed event multiple times for the same component,
|
|
// as there are now two distinct subscriptions with the same key.
|
|
// To solve this, I decided that *this is allowed*, but all ordering on the same key must be the same.
|
|
// So you can't have different Before/After on two subscriptions to an event on the same system.
|
|
//
|
|
// Group by ordering types, also filter out un-ordered ones.
|
|
|
|
var groups = regs.Where(r => r.Ordering != null).GroupBy(b => b.Ordering!.OrderType).ToArray();
|
|
|
|
// Verify that all groups of order types have the same ordering info for Before/After.
|
|
foreach (var group in groups)
|
|
{
|
|
var firstOrder = group.First().Ordering;
|
|
if (!group.All(e => e.Ordering!.Equals(firstOrder)))
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"{group.Key} uses different ordering constraints for different subscriptions to the same event {eventType}. " +
|
|
"All subscriptions to the same event from the same registrar must use the same ordering.");
|
|
}
|
|
}
|
|
|
|
// Set up topological sort.
|
|
var nodes = TopologicalSort.FromBeforeAfter(
|
|
groups.Select(g => g.ToArray()),
|
|
n => n[0].Ordering!.OrderType,
|
|
n => n,
|
|
n => n[0].Ordering!.Before,
|
|
n => n[0].Ordering!.After,
|
|
allowMissing: true);
|
|
|
|
// Start at 1, if only so events with no Ordering data at all have a distinct position.
|
|
// Doesn't really matter.
|
|
var i = 1;
|
|
foreach (var group in TopologicalSort.Sort(nodes))
|
|
{
|
|
// Assign indices to all registrations in order of the topological sort.
|
|
foreach (var registration in group)
|
|
{
|
|
registration.Order = i++;
|
|
}
|
|
}
|
|
|
|
sub.OrderingUpToDate = true;
|
|
|
|
// Sort the broadcast registrations ahead of time.
|
|
// This means ordered broadcast events have no overhead from unordered ones.
|
|
sub.BroadcastRegistrations.Sort(RegistrationOrderComparer.Instance);
|
|
|
|
// We still need Order indices on the OrderedRegistrations for directed ordered events.
|
|
// Since we sort those at submission time.
|
|
// Still, it's a single .Sort() now for those instead of the whole topological short shebang.
|
|
}
|
|
|
|
internal sealed record OrderingData(Type OrderType, Type[] Before, Type[] After)
|
|
{
|
|
public bool Equals(OrderingData? other)
|
|
{
|
|
if (other == null)
|
|
return false;
|
|
|
|
return other.OrderType == OrderType
|
|
&& Before.AsSpan().SequenceEqual(other.Before)
|
|
&& After.AsSpan().SequenceEqual(other.After);
|
|
}
|
|
|
|
public override int GetHashCode()
|
|
{
|
|
var hc = new HashCode();
|
|
hc.Add(OrderType);
|
|
hc.AddArray(Before);
|
|
hc.AddArray(After);
|
|
return hc.ToHashCode();
|
|
}
|
|
}
|
|
|
|
private sealed class RegistrationOrderComparer : IComparer<OrderedRegistration>
|
|
{
|
|
public static readonly RegistrationOrderComparer Instance = new();
|
|
|
|
public int Compare(OrderedRegistration? x, OrderedRegistration? y)
|
|
{
|
|
return x!.Order.CompareTo(y!.Order);
|
|
}
|
|
}
|
|
|
|
private record struct OrderedEventDispatch(RefEventHandler Handler, int Order);
|
|
|
|
private sealed class OrderedEventDispatchComparer : IComparer<OrderedEventDispatch>
|
|
{
|
|
public static readonly OrderedEventDispatchComparer Instance = new();
|
|
|
|
public int Compare(OrderedEventDispatch x, OrderedEventDispatch y)
|
|
{
|
|
return x.Order.CompareTo(y.Order);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Base type for directed and broadcast subscriptions. Contains ordering data.
|
|
/// </summary>
|
|
internal abstract class OrderedRegistration
|
|
{
|
|
public int Order;
|
|
public readonly OrderingData? Ordering;
|
|
|
|
protected OrderedRegistration(OrderingData? ordering)
|
|
{
|
|
Ordering = ordering;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ensure all ordered events are sorted out and verified.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Internal sorting for ordered events is normally deferred until raised
|
|
/// (since broadcast event subscriptions can be edited at any time).
|
|
///
|
|
/// Calling this gets all the sorting done ahead of time,
|
|
/// and makes sure that there are no problems with cycles and such.
|
|
/// </remarks>
|
|
public void CalcOrdering()
|
|
{
|
|
foreach (var (type, sub) in _eventData)
|
|
{
|
|
if (sub.IsOrdered && !sub.OrderingUpToDate)
|
|
{
|
|
UpdateOrderSeq(type, sub);
|
|
}
|
|
}
|
|
}
|
|
|
|
private OrderingData CreateOrderingData(Type orderType, Type[]? before, Type[]? after)
|
|
{
|
|
AddChildrenTypes(ref before);
|
|
AddChildrenTypes(ref after);
|
|
return new OrderingData(orderType, before ?? [], after ?? []);
|
|
}
|
|
|
|
private void AddChildrenTypes(ref Type[]? original)
|
|
{
|
|
if (original == null || original.Length == 0)
|
|
return;
|
|
|
|
_childrenTypesTemp.Clear();
|
|
foreach (var beforeType in original)
|
|
{
|
|
foreach (var child in _reflection.GetAllChildren(beforeType))
|
|
{
|
|
_childrenTypesTemp.Add(child);
|
|
}
|
|
}
|
|
|
|
if (_childrenTypesTemp.Count > 0)
|
|
{
|
|
Array.Resize(ref original, original.Length + _childrenTypesTemp.Count);
|
|
_childrenTypesTemp.CopyTo(original, original.Length - _childrenTypesTemp.Count);
|
|
}
|
|
}
|
|
}
|
|
}
|