Skip to content

Commit

Permalink
Allow handling by-value events by ref (space-wizards#4373)
Browse files Browse the repository at this point in the history
  • Loading branch information
DrSmugleaf authored Oct 19, 2023
1 parent 54529fd commit f87012e
Show file tree
Hide file tree
Showing 7 changed files with 45 additions and 147 deletions.
73 changes: 0 additions & 73 deletions Robust.Analyzers/ByRefEventAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,6 @@ public sealed class ByRefEventAnalyzer : DiagnosticAnalyzer
"Make sure that methods subscribing to a ref event have the ref keyword for the event argument."
);

private static readonly DiagnosticDescriptor ByValueEventSubscribedByRefRule = new(
Diagnostics.IdValueEventRaisedByRef,
"Value event subscribed to by-ref",
"Tried to subscribe to a value event '{0}' by-ref.",
"Usage",
DiagnosticSeverity.Error,
true,
"Make sure that methods subscribing to value events do not have the ref keyword for the event argument."
);

private static readonly DiagnosticDescriptor ByRefEventRaisedByValueRule = new(
Diagnostics.IdByRefEventRaisedByValue,
"By-ref event raised by value",
Expand All @@ -55,7 +45,6 @@ public sealed class ByRefEventAnalyzer : DiagnosticAnalyzer

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(
ByRefEventSubscribedByValueRule,
ByValueEventSubscribedByRefRule,
ByRefEventRaisedByValueRule,
ByValueEventRaisedByRefRule
);
Expand All @@ -64,71 +53,9 @@ public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
context.EnableConcurrentExecution();
context.RegisterOperationAction(CheckEventSubscription, OperationKind.Invocation);
context.RegisterOperationAction(CheckEventRaise, OperationKind.Invocation);
}

private void CheckEventSubscription(OperationAnalysisContext context)
{
if (context.Operation is not IInvocationOperation operation)
return;

var subscribeMethods = context.Compilation
.GetTypeByMetadataName("Robust.Shared.GameObjects.EntitySystem")?
.GetMembers()
.Where(m => m.Name.Contains("SubscribeLocalEvent"))
.Cast<IMethodSymbol>();

if (subscribeMethods == null)
return;

if (!subscribeMethods.Any(m => m.Equals(operation.TargetMethod.OriginalDefinition, Default)))
return;

var typeArguments = operation.TargetMethod.TypeArguments;
if (typeArguments.Length < 1 || typeArguments.Length > 2)
return;

if (operation.Arguments.First().Value is not IDelegateCreationOperation delegateCreation)
return;

if (delegateCreation.Target is not IMethodReferenceOperation methodReference)
return;

var eventParameter = methodReference.Method.Parameters.LastOrDefault();
if (eventParameter == null)
return;

ITypeSymbol eventArgument;
switch (typeArguments.Length)
{
case 1:
eventArgument = typeArguments[0];
break;
case 2:
eventArgument = typeArguments[1];
break;
default:
return;
}

var byRefAttribute = context.Compilation.GetTypeByMetadataName(ByRefAttribute);
if (byRefAttribute == null)
return;

var isByRefEventType = eventArgument
.GetAttributes()
.Any(attribute => attribute.AttributeClass?.Equals(byRefAttribute, Default) ?? false);
var parameterIsRef = eventParameter.RefKind == RefKind.Ref;

if (isByRefEventType != parameterIsRef)
{
var descriptor = isByRefEventType ? ByRefEventSubscribedByValueRule : ByValueEventSubscribedByRefRule;
var diagnostic = Diagnostic.Create(descriptor, operation.Syntax.GetLocation(), eventArgument);
context.ReportDiagnostic(diagnostic);
}
}

private void CheckEventRaise(OperationAnalysisContext context)
{
if (context.Operation is not IInvocationOperation operation)
Expand Down
1 change: 0 additions & 1 deletion Robust.Analyzers/Diagnostics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ public static class Diagnostics
public const string IdInvalidNotNullableFlagType = "RA0011";
public const string IdNotNullableFlagValueType = "RA0012";
public const string IdByRefEventSubscribedByValue = "RA0013";
public const string IdValueEventSubscribedByRef = "RA0014";
public const string IdByRefEventRaisedByValue = "RA0015";
public const string IdValueEventRaisedByRef = "RA0016";
public const string IdDataDefinitionPartial = "RA0017";
Expand Down
22 changes: 11 additions & 11 deletions Robust.Shared/GameObjects/EntityEventBus.Broadcast.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,13 @@ public interface IBroadcastEventBus
/// <param name="source"></param>
/// <param name="subscriber">Subscriber that owns the handler.</param>
/// <param name="eventHandler">Delegate that handles the event.</param>
/// <seealso cref="SubscribeEvent{T}(EventSource, IEntityEventSubscriber, EntityEventRefHandler{T})"/>
// [Obsolete("Subscribe to the event by ref instead (EntityEventRefHandler)")]
void SubscribeEvent<T>(EventSource source, IEntityEventSubscriber subscriber,
EntityEventHandler<T> eventHandler) where T : notnull;

/// <seealso cref="SubscribeEvent{T}(EventSource, IEntityEventSubscriber, EntityEventRefHandler{T})"/>
// [Obsolete("Subscribe to the event by ref instead (EntityEventRefHandler)")]
void SubscribeEvent<T>(
EventSource source,
IEntityEventSubscriber subscriber,
Expand Down Expand Up @@ -133,7 +137,7 @@ public void ProcessEventQueue()
var type = args.GetType();
ref var unitRef = ref ExtractUnitRef(ref args, type);

ProcessSingleEvent(source, ref unitRef, type, false);
ProcessSingleEvent(source, ref unitRef, type);
}
}

Expand Down Expand Up @@ -260,23 +264,23 @@ public void RaiseEvent(EventSource source, object toRaise)
var eventType = toRaise.GetType();
ref var unitRef = ref ExtractUnitRef(ref toRaise, eventType);

ProcessSingleEvent(source, ref unitRef, eventType, false);
ProcessSingleEvent(source, ref unitRef, eventType);
}

public void RaiseEvent<T>(EventSource source, T toRaise) where T : notnull
{
if (source == EventSource.None)
throw new ArgumentOutOfRangeException(nameof(source));

ProcessSingleEvent(source, ref Unsafe.As<T, Unit>(ref toRaise), typeof(T), false);
ProcessSingleEvent(source, ref Unsafe.As<T, Unit>(ref toRaise), typeof(T));
}

public void RaiseEvent<T>(EventSource source, ref T toRaise) where T : notnull
{
if (source == EventSource.None)
throw new ArgumentOutOfRangeException(nameof(source));

ProcessSingleEvent(source, ref Unsafe.As<T, Unit>(ref toRaise), typeof(T), true);
ProcessSingleEvent(source, ref Unsafe.As<T, Unit>(ref toRaise), typeof(T));
}

/// <inheritdoc />
Expand All @@ -301,7 +305,7 @@ private void UnsubscribeEvent(Type eventType, BroadcastRegistration tuple, IEnti
inverse.Remove(eventType);
}

private void ProcessSingleEvent(EventSource source, ref Unit unitRef, Type eventType, bool byRef)
private void ProcessSingleEvent(EventSource source, ref Unit unitRef, Type eventType)
{
if (!_eventData.TryGetValue(eventType, out var subs))
return;
Expand All @@ -314,20 +318,16 @@ private void ProcessSingleEvent(EventSource source, ref Unit unitRef, Type event
// This means ordered broadcast events have no overhead over non-ordered ones.
}

ProcessSingleEventCore(source, ref unitRef, subs, byRef);
ProcessSingleEventCore(source, ref unitRef, subs);
}

private static void ProcessSingleEventCore(
EventSource source,
ref Unit unitRef,
EventData subs,
bool byRef)
EventData subs)
{
foreach (var handler in subs.BroadcastRegistrations)
{
if (handler.ReferenceEvent != byRef)
ThrowByRefMisMatch();

if ((handler.Mask & source) != 0)
handler.Handler(ref unitRef);
}
Expand Down
4 changes: 0 additions & 4 deletions Robust.Shared/GameObjects/EntityEventBus.Common.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,6 @@ private readonly Dictionary<IEntityEventSubscriber, Dictionary<Type, BroadcastRe

public bool IgnoreUnregisteredComponents;

[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowByRefMisMatch() =>
throw new InvalidOperationException("Mismatching by-ref ness on event!");

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ref Unit ExtractUnitRef(ref object obj, Type objType)
{
Expand Down
Loading

0 comments on commit f87012e

Please sign in to comment.