com.wallstop-studios.dxmessaging 2.0.0-rc22 → 2.0.0-rc23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -6
- package/Runtime/Core/Extensions/EnumExtensions.cs +37 -0
- package/Runtime/Core/Extensions/EnumExtensions.cs.meta +3 -0
- package/Runtime/Core/InstanceId.cs +0 -2
- package/Runtime/Core/MessageBus/IMessageBus.cs +8 -4
- package/Runtime/Core/MessageBus/MessageBus.cs +336 -14
- package/Runtime/Core/Messages/DxReflexiveMessage.cs +132 -0
- package/Runtime/Core/Messages/DxReflexiveMessage.cs.meta +3 -0
- package/Runtime/Core/MessagingDebug.cs +1 -1
- package/Runtime/Unity/MessageAwareComponent.cs +34 -13
- package/Runtime/Unity/Messages/GlobalStringMessage.cs +15 -0
- package/Runtime/Unity/Messages/GlobalStringMessage.cs.meta +3 -0
- package/Runtime/Unity/Messages/StringMessage.cs +15 -0
- package/Runtime/Unity/Messages/StringMessage.cs.meta +3 -0
- package/Runtime/Unity/Messages.meta +3 -0
- package/Runtime/WallstopStudios.DxMessaging.asmdef +1 -1
- package/Tests/Runtime/Benchmarks/PerformanceTests.cs +141 -1
- package/Tests/Runtime/Core/MessagingTestBase.cs +3 -11
- package/Tests/Runtime/Scripts/Components/SimpleMessageAwareComponent.cs +12 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -39,12 +39,15 @@ For UntargetedMessages, DxMessaging is significantly faster (roughly 2x) than Un
|
|
|
39
39
|
|
|
40
40
|
| Message Tech | Operations / Second | Allocations? |
|
|
41
41
|
| ------------ | ------------------- | ------------ |
|
|
42
|
-
| Unity | 2,
|
|
43
|
-
| DxMessaging (GameObject) - Normal | 2,
|
|
44
|
-
| DxMessaging (Component) - Normal | 2,
|
|
45
|
-
| DxMessaging (GameObject) - No-Copy | 2,
|
|
46
|
-
| DxMessaging (Component) - No-Copy | 2,
|
|
47
|
-
| DxMessaging (Untargeted) - No-Copy | 4,
|
|
42
|
+
| Unity | 2,670,600 | Yes |
|
|
43
|
+
| DxMessaging (GameObject) - Normal | 2,722,400 | No |
|
|
44
|
+
| DxMessaging (Component) - Normal | 2,738,800 | No |
|
|
45
|
+
| DxMessaging (GameObject) - No-Copy | 2,871,800 | No |
|
|
46
|
+
| DxMessaging (Component) - No-Copy | 2,876,600 | No |
|
|
47
|
+
| DxMessaging (Untargeted) - No-Copy | 4,480,200 | No |
|
|
48
|
+
| Reflexive (One Argument) | 2,287,800 | No |
|
|
49
|
+
| Reflexive (Two Arguments) | 1,009,000 | No |
|
|
50
|
+
| Reflexive (Three Arguments) | 1,000,400 | No |
|
|
48
51
|
|
|
49
52
|
# Functionality
|
|
50
53
|
While not as fast, DxMessaging offers *additional functionality* as compared to Unity's messaging solution.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
namespace DxMessaging.Core.Extensions
|
|
2
|
+
{
|
|
3
|
+
using System;
|
|
4
|
+
using System.Runtime.CompilerServices;
|
|
5
|
+
|
|
6
|
+
public static class EnumExtensions
|
|
7
|
+
{
|
|
8
|
+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
9
|
+
public static bool HasFlagNoAlloc<T>(this T value, T flag)
|
|
10
|
+
where T : unmanaged, Enum
|
|
11
|
+
{
|
|
12
|
+
ulong valueUnderlying = GetUInt64(value);
|
|
13
|
+
ulong flagUnderlying = GetUInt64(flag);
|
|
14
|
+
return (valueUnderlying & flagUnderlying) == flagUnderlying;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
18
|
+
private static unsafe ulong GetUInt64<T>(T value)
|
|
19
|
+
where T : unmanaged
|
|
20
|
+
{
|
|
21
|
+
/*
|
|
22
|
+
Works because T is constrained to unmanaged, so it's safe to reinterpret
|
|
23
|
+
All enums are value types and have a fixed size
|
|
24
|
+
*/
|
|
25
|
+
return sizeof(T) switch
|
|
26
|
+
{
|
|
27
|
+
1 => *(byte*)&value,
|
|
28
|
+
2 => *(ushort*)&value,
|
|
29
|
+
4 => *(uint*)&value,
|
|
30
|
+
8 => *(ulong*)&value,
|
|
31
|
+
_ => throw new ArgumentException(
|
|
32
|
+
$"Unsupported enum size: {sizeof(T)} for type {typeof(T)}"
|
|
33
|
+
),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
namespace DxMessaging.Core.MessageBus
|
|
2
2
|
{
|
|
3
3
|
using System;
|
|
4
|
+
using System.Threading;
|
|
4
5
|
using Core;
|
|
5
6
|
using Messages;
|
|
6
7
|
|
|
@@ -9,7 +10,10 @@
|
|
|
9
10
|
/// </summary>
|
|
10
11
|
public interface IMessageBus
|
|
11
12
|
{
|
|
12
|
-
|
|
13
|
+
internal static int GlobalSequentialIndex = -1;
|
|
14
|
+
|
|
15
|
+
protected static int GenerateNewGlobalSequentialIndex() =>
|
|
16
|
+
Interlocked.Increment(ref GlobalSequentialIndex);
|
|
13
17
|
|
|
14
18
|
public int RegisteredGlobalSequentialIndex { get; }
|
|
15
19
|
public int RegisteredBroadcast { get; }
|
|
@@ -19,7 +23,7 @@
|
|
|
19
23
|
public int RegisteredUntargeted { get; }
|
|
20
24
|
|
|
21
25
|
/// <summary>
|
|
22
|
-
/// Given an Untargeted message, determines whether
|
|
26
|
+
/// Given an Untargeted message, determines whether it should be processed or skipped
|
|
23
27
|
/// </summary>
|
|
24
28
|
/// <typeparam name="TMessage">Specific type of message.</typeparam>
|
|
25
29
|
/// <param name="message">Message to consider.</param>
|
|
@@ -28,7 +32,7 @@
|
|
|
28
32
|
where TMessage : IUntargetedMessage;
|
|
29
33
|
|
|
30
34
|
/// <summary>
|
|
31
|
-
/// Given a Targeted message and its target, determines whether
|
|
35
|
+
/// Given a Targeted message and its target, determines whether it should be processed or skipped.
|
|
32
36
|
/// </summary>
|
|
33
37
|
/// <typeparam name="TMessage">Specific type of message.</typeparam>
|
|
34
38
|
/// <param name="target">Target of the message.</param>
|
|
@@ -41,7 +45,7 @@
|
|
|
41
45
|
where TMessage : ITargetedMessage;
|
|
42
46
|
|
|
43
47
|
/// <summary>
|
|
44
|
-
/// Given a Broadcast message and its source, determines whether
|
|
48
|
+
/// Given a Broadcast message and its source, determines whether it should be processed or skipped.
|
|
45
49
|
/// </summary>
|
|
46
50
|
/// <typeparam name="TMessage">Specific type of message.</typeparam>
|
|
47
51
|
/// <param name="source">Source of the message.</param>
|
|
@@ -2,11 +2,16 @@
|
|
|
2
2
|
{
|
|
3
3
|
using System;
|
|
4
4
|
using System.Collections.Generic;
|
|
5
|
+
using System.Linq.Expressions;
|
|
5
6
|
using System.Reflection;
|
|
6
|
-
using System.
|
|
7
|
+
using System.Runtime.CompilerServices;
|
|
8
|
+
using Extensions;
|
|
7
9
|
using Helper;
|
|
8
10
|
using Messages;
|
|
9
11
|
using static IMessageBus;
|
|
12
|
+
#if UNITY_2017_1_OR_NEWER
|
|
13
|
+
using UnityEngine;
|
|
14
|
+
#endif
|
|
10
15
|
|
|
11
16
|
/// <summary>
|
|
12
17
|
/// Instanced MessageBus for use cases where you want distinct islands of MessageBuses.
|
|
@@ -43,7 +48,7 @@
|
|
|
43
48
|
}
|
|
44
49
|
}
|
|
45
50
|
|
|
46
|
-
public int RegisteredGlobalSequentialIndex { get;
|
|
51
|
+
public int RegisteredGlobalSequentialIndex { get; } = GenerateNewGlobalSequentialIndex();
|
|
47
52
|
|
|
48
53
|
public int RegisteredBroadcast
|
|
49
54
|
{
|
|
@@ -77,9 +82,12 @@
|
|
|
77
82
|
|
|
78
83
|
// For use with re-broadcasting to generic methods
|
|
79
84
|
private static readonly object[] ReflectionMethodArgumentsCache = new object[2];
|
|
85
|
+
private static readonly List<Expression> ArgumentExpressionsCache = new();
|
|
80
86
|
|
|
81
87
|
private const BindingFlags ReflectionHelperBindingFlags =
|
|
82
88
|
BindingFlags.Static | BindingFlags.NonPublic;
|
|
89
|
+
private const BindingFlags ReflexiveMethodBindingFlags =
|
|
90
|
+
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
|
|
83
91
|
|
|
84
92
|
private delegate void FastUntargetedBroadcast<T>(ref T message)
|
|
85
93
|
where T : IUntargetedMessage;
|
|
@@ -118,6 +126,16 @@
|
|
|
118
126
|
private readonly Dictionary<Type, object> _broadcastMethodsByType = new();
|
|
119
127
|
private readonly Stack<List<object>> _innerInterceptorsStack = new();
|
|
120
128
|
|
|
129
|
+
private readonly Dictionary<
|
|
130
|
+
Type,
|
|
131
|
+
Dictionary<MethodSignatureKey, Action<MonoBehaviour, object[]>>
|
|
132
|
+
> _methodCache = new();
|
|
133
|
+
|
|
134
|
+
#if UNITY_2017_1_OR_NEWER
|
|
135
|
+
private readonly HashSet<MonoBehaviour> _recipientCache = new();
|
|
136
|
+
private readonly List<MonoBehaviour> _componentCache = new();
|
|
137
|
+
#endif
|
|
138
|
+
|
|
121
139
|
private readonly RegistrationLog _log = new();
|
|
122
140
|
|
|
123
141
|
static MessageBus()
|
|
@@ -128,11 +146,6 @@
|
|
|
128
146
|
}
|
|
129
147
|
}
|
|
130
148
|
|
|
131
|
-
public MessageBus()
|
|
132
|
-
{
|
|
133
|
-
RegisteredGlobalSequentialIndex = Interlocked.Increment(ref GlobalSequentialIndex);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
149
|
public Action RegisterUntargeted<T>(MessageHandler messageHandler, int priority = 0)
|
|
137
150
|
where T : IUntargetedMessage
|
|
138
151
|
{
|
|
@@ -708,14 +721,191 @@
|
|
|
708
721
|
}
|
|
709
722
|
|
|
710
723
|
bool foundAnyHandlers = false;
|
|
724
|
+
Dictionary<InstanceId, HandlerCache<int, HandlerCache>> targetedHandlers;
|
|
725
|
+
HandlerCache<int, HandlerCache> sortedHandlers;
|
|
726
|
+
|
|
727
|
+
if (typeof(TMessage) == typeof(DxReflexiveMessage))
|
|
728
|
+
{
|
|
729
|
+
#if UNITY_2017_1_OR_NEWER
|
|
730
|
+
ref DxReflexiveMessage reflexiveMessage = ref Unsafe.As<
|
|
731
|
+
TMessage,
|
|
732
|
+
DxReflexiveMessage
|
|
733
|
+
>(ref typedMessage);
|
|
734
|
+
|
|
735
|
+
GameObject go;
|
|
736
|
+
bool found;
|
|
737
|
+
UnityEngine.Object targetObject = target.Object;
|
|
738
|
+
switch (targetObject)
|
|
739
|
+
{
|
|
740
|
+
case GameObject gameObject:
|
|
741
|
+
{
|
|
742
|
+
found = true;
|
|
743
|
+
go = gameObject;
|
|
744
|
+
break;
|
|
745
|
+
}
|
|
746
|
+
case Component component:
|
|
747
|
+
{
|
|
748
|
+
found = true;
|
|
749
|
+
go = component.gameObject;
|
|
750
|
+
break;
|
|
751
|
+
}
|
|
752
|
+
default:
|
|
753
|
+
{
|
|
754
|
+
go = null;
|
|
755
|
+
found = false;
|
|
756
|
+
break;
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
if (found)
|
|
761
|
+
{
|
|
762
|
+
_recipientCache.Clear();
|
|
763
|
+
bool sentInADirection = false;
|
|
764
|
+
if (reflexiveMessage.sendMode.HasFlagNoAlloc(ReflexiveSendMode.Upwards))
|
|
765
|
+
{
|
|
766
|
+
sentInADirection = true;
|
|
767
|
+
if (
|
|
768
|
+
!reflexiveMessage.sendMode.HasFlagNoAlloc(ReflexiveSendMode.Downwards)
|
|
769
|
+
&& !reflexiveMessage.sendMode.HasFlagNoAlloc(ReflexiveSendMode.Flat)
|
|
770
|
+
)
|
|
771
|
+
{
|
|
772
|
+
switch (reflexiveMessage.parameters.Length)
|
|
773
|
+
{
|
|
774
|
+
case 0:
|
|
775
|
+
{
|
|
776
|
+
go.SendMessageUpwards(reflexiveMessage.method);
|
|
777
|
+
break;
|
|
778
|
+
}
|
|
779
|
+
case 1:
|
|
780
|
+
{
|
|
781
|
+
go.SendMessageUpwards(
|
|
782
|
+
reflexiveMessage.method,
|
|
783
|
+
reflexiveMessage.parameters[0]
|
|
784
|
+
);
|
|
785
|
+
break;
|
|
786
|
+
}
|
|
787
|
+
default:
|
|
788
|
+
{
|
|
789
|
+
Component[] parentComponents = go.GetComponentsInParent(
|
|
790
|
+
typeof(MonoBehaviour),
|
|
791
|
+
true
|
|
792
|
+
);
|
|
793
|
+
foreach (Component component in parentComponents)
|
|
794
|
+
{
|
|
795
|
+
if (component is MonoBehaviour script)
|
|
796
|
+
{
|
|
797
|
+
SendMessage(script, ref reflexiveMessage);
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
break;
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
else
|
|
806
|
+
{
|
|
807
|
+
Component[] parentComponents = go.GetComponentsInParent(
|
|
808
|
+
typeof(MonoBehaviour)
|
|
809
|
+
);
|
|
810
|
+
foreach (Component component in parentComponents)
|
|
811
|
+
{
|
|
812
|
+
if (component is MonoBehaviour script)
|
|
813
|
+
{
|
|
814
|
+
SendMessage(script, ref reflexiveMessage);
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
if (reflexiveMessage.sendMode.HasFlagNoAlloc(ReflexiveSendMode.Downwards))
|
|
820
|
+
{
|
|
821
|
+
sentInADirection = true;
|
|
822
|
+
if (
|
|
823
|
+
!reflexiveMessage.sendMode.HasFlagNoAlloc(ReflexiveSendMode.Upwards)
|
|
824
|
+
&& !reflexiveMessage.sendMode.HasFlagNoAlloc(ReflexiveSendMode.Flat)
|
|
825
|
+
)
|
|
826
|
+
{
|
|
827
|
+
switch (reflexiveMessage.parameters.Length)
|
|
828
|
+
{
|
|
829
|
+
case 0:
|
|
830
|
+
{
|
|
831
|
+
go.BroadcastMessage(reflexiveMessage.method);
|
|
832
|
+
break;
|
|
833
|
+
}
|
|
834
|
+
case 1:
|
|
835
|
+
{
|
|
836
|
+
go.BroadcastMessage(
|
|
837
|
+
reflexiveMessage.method,
|
|
838
|
+
reflexiveMessage.parameters[0]
|
|
839
|
+
);
|
|
840
|
+
break;
|
|
841
|
+
}
|
|
842
|
+
default:
|
|
843
|
+
{
|
|
844
|
+
_componentCache.Clear();
|
|
845
|
+
go.GetComponentsInChildren(true, _componentCache);
|
|
846
|
+
foreach (MonoBehaviour parentComponent in _componentCache)
|
|
847
|
+
{
|
|
848
|
+
SendMessage(parentComponent, ref reflexiveMessage);
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
break;
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
else
|
|
856
|
+
{
|
|
857
|
+
_componentCache.Clear();
|
|
858
|
+
go.GetComponentsInChildren(_componentCache);
|
|
859
|
+
foreach (MonoBehaviour parentComponent in _componentCache)
|
|
860
|
+
{
|
|
861
|
+
SendMessage(parentComponent, ref reflexiveMessage);
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
else if (
|
|
866
|
+
!sentInADirection
|
|
867
|
+
&& reflexiveMessage.sendMode.HasFlagNoAlloc(ReflexiveSendMode.Flat)
|
|
868
|
+
)
|
|
869
|
+
{
|
|
870
|
+
switch (reflexiveMessage.parameters.Length)
|
|
871
|
+
{
|
|
872
|
+
case 0:
|
|
873
|
+
{
|
|
874
|
+
go.SendMessage(reflexiveMessage.method);
|
|
875
|
+
break;
|
|
876
|
+
}
|
|
877
|
+
case 1:
|
|
878
|
+
{
|
|
879
|
+
go.SendMessage(
|
|
880
|
+
reflexiveMessage.method,
|
|
881
|
+
reflexiveMessage.parameters[0]
|
|
882
|
+
);
|
|
883
|
+
break;
|
|
884
|
+
}
|
|
885
|
+
default:
|
|
886
|
+
{
|
|
887
|
+
_componentCache.Clear();
|
|
888
|
+
go.GetComponents(_componentCache);
|
|
889
|
+
foreach (MonoBehaviour component in _componentCache)
|
|
890
|
+
{
|
|
891
|
+
SendMessage(component, ref reflexiveMessage);
|
|
892
|
+
}
|
|
893
|
+
break;
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
#else
|
|
899
|
+
MessagingDebug.Log(
|
|
900
|
+
LogLevel.Error,
|
|
901
|
+
"Reflexive messages are not supported in this build."
|
|
902
|
+
);
|
|
903
|
+
#endif
|
|
904
|
+
}
|
|
905
|
+
|
|
711
906
|
if (
|
|
712
|
-
_targetedSinks.TryGetValue<TMessage>(
|
|
713
|
-
|
|
714
|
-
)
|
|
715
|
-
&& targetedHandlers.TryGetValue(
|
|
716
|
-
target,
|
|
717
|
-
out HandlerCache<int, HandlerCache> sortedHandlers
|
|
718
|
-
)
|
|
907
|
+
_targetedSinks.TryGetValue<TMessage>(out targetedHandlers)
|
|
908
|
+
&& targetedHandlers.TryGetValue(target, out sortedHandlers)
|
|
719
909
|
&& 0 < sortedHandlers.handlers.Count
|
|
720
910
|
)
|
|
721
911
|
{
|
|
@@ -3160,5 +3350,137 @@
|
|
|
3160
3350
|
sourcedBroadcast(ref target, ref typedMessage);
|
|
3161
3351
|
}
|
|
3162
3352
|
}
|
|
3353
|
+
|
|
3354
|
+
#if UNITY_2017_1_OR_NEWER
|
|
3355
|
+
private static Action<MonoBehaviour, object[]> CompileMethodAction(MethodInfo methodInfo)
|
|
3356
|
+
{
|
|
3357
|
+
ParameterExpression componentParameter = Expression.Parameter(
|
|
3358
|
+
typeof(MonoBehaviour),
|
|
3359
|
+
"targetComponent"
|
|
3360
|
+
);
|
|
3361
|
+
ParameterExpression argsParameter = Expression.Parameter(typeof(object[]), "args");
|
|
3362
|
+
ParameterInfo[] methodParams = methodInfo.GetParameters();
|
|
3363
|
+
|
|
3364
|
+
ArgumentExpressionsCache.Clear();
|
|
3365
|
+
for (int i = 0; i < methodParams.Length; ++i)
|
|
3366
|
+
{
|
|
3367
|
+
Expression indexAccess = Expression.ArrayIndex(
|
|
3368
|
+
argsParameter,
|
|
3369
|
+
Expression.Constant(i)
|
|
3370
|
+
);
|
|
3371
|
+
Expression convertedArg = Expression.Convert(
|
|
3372
|
+
indexAccess,
|
|
3373
|
+
methodParams[i].ParameterType
|
|
3374
|
+
);
|
|
3375
|
+
ArgumentExpressionsCache.Add(convertedArg);
|
|
3376
|
+
}
|
|
3377
|
+
|
|
3378
|
+
// ReSharper disable once AssignNullToNotNullAttribute
|
|
3379
|
+
Expression instanceExpression = methodInfo.IsStatic
|
|
3380
|
+
? null
|
|
3381
|
+
: Expression.Convert(componentParameter, methodInfo.DeclaringType);
|
|
3382
|
+
MethodCallExpression callExpression = Expression.Call(
|
|
3383
|
+
instanceExpression,
|
|
3384
|
+
methodInfo,
|
|
3385
|
+
ArgumentExpressionsCache
|
|
3386
|
+
);
|
|
3387
|
+
Expression<Action<MonoBehaviour, object[]>> lambda = Expression.Lambda<
|
|
3388
|
+
Action<MonoBehaviour, object[]>
|
|
3389
|
+
>(callExpression, componentParameter, argsParameter);
|
|
3390
|
+
|
|
3391
|
+
return lambda.Compile();
|
|
3392
|
+
}
|
|
3393
|
+
#endif
|
|
3394
|
+
|
|
3395
|
+
private void SendMessage(MonoBehaviour recipient, ref DxReflexiveMessage message)
|
|
3396
|
+
{
|
|
3397
|
+
if (!_recipientCache.Add(recipient))
|
|
3398
|
+
{
|
|
3399
|
+
return;
|
|
3400
|
+
}
|
|
3401
|
+
|
|
3402
|
+
Type componentType = recipient.GetType();
|
|
3403
|
+
if (
|
|
3404
|
+
!_methodCache.TryGetValue(
|
|
3405
|
+
componentType,
|
|
3406
|
+
out Dictionary<MethodSignatureKey, Action<MonoBehaviour, object[]>> methodCache
|
|
3407
|
+
)
|
|
3408
|
+
)
|
|
3409
|
+
{
|
|
3410
|
+
_methodCache[componentType] = methodCache =
|
|
3411
|
+
new Dictionary<MethodSignatureKey, Action<MonoBehaviour, object[]>>();
|
|
3412
|
+
}
|
|
3413
|
+
|
|
3414
|
+
MethodSignatureKey lookupKey = message.signatureKey;
|
|
3415
|
+
if (!methodCache.TryGetValue(lookupKey, out Action<MonoBehaviour, object[]> method))
|
|
3416
|
+
{
|
|
3417
|
+
MethodInfo methodInfo = null;
|
|
3418
|
+
try
|
|
3419
|
+
{
|
|
3420
|
+
methodInfo = componentType.GetMethod(
|
|
3421
|
+
message.method,
|
|
3422
|
+
ReflexiveMethodBindingFlags,
|
|
3423
|
+
null,
|
|
3424
|
+
message.parameterTypes,
|
|
3425
|
+
null
|
|
3426
|
+
);
|
|
3427
|
+
}
|
|
3428
|
+
catch (AmbiguousMatchException)
|
|
3429
|
+
{
|
|
3430
|
+
MethodInfo[] matchingMethods = componentType.GetMethods(
|
|
3431
|
+
ReflexiveMethodBindingFlags
|
|
3432
|
+
);
|
|
3433
|
+
foreach (MethodInfo matchingMethod in matchingMethods)
|
|
3434
|
+
{
|
|
3435
|
+
if (
|
|
3436
|
+
!string.Equals(
|
|
3437
|
+
matchingMethod.Name,
|
|
3438
|
+
message.method,
|
|
3439
|
+
StringComparison.Ordinal
|
|
3440
|
+
)
|
|
3441
|
+
|| !ParameterTypesMatch(
|
|
3442
|
+
matchingMethod.GetParameters(),
|
|
3443
|
+
message.parameterTypes
|
|
3444
|
+
)
|
|
3445
|
+
)
|
|
3446
|
+
{
|
|
3447
|
+
continue;
|
|
3448
|
+
}
|
|
3449
|
+
|
|
3450
|
+
methodInfo = matchingMethod;
|
|
3451
|
+
break;
|
|
3452
|
+
}
|
|
3453
|
+
}
|
|
3454
|
+
catch
|
|
3455
|
+
{
|
|
3456
|
+
methodInfo = null;
|
|
3457
|
+
}
|
|
3458
|
+
|
|
3459
|
+
if (methodInfo != null)
|
|
3460
|
+
{
|
|
3461
|
+
method = CompileMethodAction(methodInfo);
|
|
3462
|
+
}
|
|
3463
|
+
methodCache[lookupKey] = method;
|
|
3464
|
+
}
|
|
3465
|
+
|
|
3466
|
+
method?.Invoke(recipient, message.parameters);
|
|
3467
|
+
}
|
|
3468
|
+
|
|
3469
|
+
private static bool ParameterTypesMatch(ParameterInfo[] methodParams, Type[] expectedTypes)
|
|
3470
|
+
{
|
|
3471
|
+
if (methodParams.Length != expectedTypes.Length)
|
|
3472
|
+
{
|
|
3473
|
+
return false;
|
|
3474
|
+
}
|
|
3475
|
+
|
|
3476
|
+
for (int i = 0; i < methodParams.Length; ++i)
|
|
3477
|
+
{
|
|
3478
|
+
if (methodParams[i].ParameterType != expectedTypes[i])
|
|
3479
|
+
{
|
|
3480
|
+
return false;
|
|
3481
|
+
}
|
|
3482
|
+
}
|
|
3483
|
+
return true;
|
|
3484
|
+
}
|
|
3163
3485
|
}
|
|
3164
3486
|
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
namespace DxMessaging.Core.Messages
|
|
2
|
+
{
|
|
3
|
+
using System;
|
|
4
|
+
using Attributes;
|
|
5
|
+
|
|
6
|
+
[Flags]
|
|
7
|
+
public enum ReflexiveSendMode
|
|
8
|
+
{
|
|
9
|
+
[Obsolete("Please use a valid Send Mode")]
|
|
10
|
+
None = 0,
|
|
11
|
+
Flat = 1 << 0,
|
|
12
|
+
Downwards = 1 << 1,
|
|
13
|
+
Upwards = 1 << 2,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
public readonly struct MethodSignatureKey : IEquatable<MethodSignatureKey>
|
|
17
|
+
{
|
|
18
|
+
private const int HashBase = 5556137;
|
|
19
|
+
private const int HashMultiplier = 95785853;
|
|
20
|
+
|
|
21
|
+
private readonly string _methodName;
|
|
22
|
+
private readonly Type[] _parameterTypes;
|
|
23
|
+
|
|
24
|
+
private readonly int _hashCode;
|
|
25
|
+
|
|
26
|
+
public MethodSignatureKey(string methodName, Type[] parameterTypes)
|
|
27
|
+
: this()
|
|
28
|
+
{
|
|
29
|
+
_methodName = methodName ?? throw new ArgumentNullException(nameof(methodName));
|
|
30
|
+
_parameterTypes = parameterTypes;
|
|
31
|
+
_hashCode = CalculateHashCode();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
private int CalculateHashCode()
|
|
35
|
+
{
|
|
36
|
+
int hashCode = HashBase + _methodName.GetHashCode();
|
|
37
|
+
foreach (Type type in _parameterTypes)
|
|
38
|
+
{
|
|
39
|
+
hashCode = hashCode * HashMultiplier + type.GetHashCode();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return hashCode;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
public override int GetHashCode()
|
|
46
|
+
{
|
|
47
|
+
return _hashCode;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public override bool Equals(object obj)
|
|
51
|
+
{
|
|
52
|
+
return obj is MethodSignatureKey other && Equals(other);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
public bool Equals(MethodSignatureKey other)
|
|
56
|
+
{
|
|
57
|
+
if (
|
|
58
|
+
_parameterTypes.Length != other._parameterTypes.Length
|
|
59
|
+
|| _methodName != other._methodName
|
|
60
|
+
)
|
|
61
|
+
{
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ReSharper disable once LoopCanBeConvertedToQuery
|
|
66
|
+
for (int i = 0; i < _parameterTypes.Length; ++i)
|
|
67
|
+
{
|
|
68
|
+
if (_parameterTypes[i] != other._parameterTypes[i])
|
|
69
|
+
{
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
public static bool operator ==(MethodSignatureKey left, MethodSignatureKey right)
|
|
78
|
+
{
|
|
79
|
+
return left.Equals(right);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
public static bool operator !=(MethodSignatureKey left, MethodSignatureKey right)
|
|
83
|
+
{
|
|
84
|
+
return !(left == right);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
[DxTargetedMessage]
|
|
89
|
+
public readonly partial struct DxReflexiveMessage
|
|
90
|
+
{
|
|
91
|
+
public readonly string method;
|
|
92
|
+
public readonly ReflexiveSendMode sendMode;
|
|
93
|
+
public readonly object[] parameters;
|
|
94
|
+
|
|
95
|
+
public readonly Type[] parameterTypes;
|
|
96
|
+
|
|
97
|
+
public readonly MethodSignatureKey signatureKey;
|
|
98
|
+
|
|
99
|
+
public DxReflexiveMessage(
|
|
100
|
+
string method,
|
|
101
|
+
ReflexiveSendMode sendMode,
|
|
102
|
+
params object[] parameters
|
|
103
|
+
)
|
|
104
|
+
{
|
|
105
|
+
this.method = method;
|
|
106
|
+
this.sendMode = sendMode;
|
|
107
|
+
this.parameters = parameters;
|
|
108
|
+
|
|
109
|
+
if (0 < parameters.Length)
|
|
110
|
+
{
|
|
111
|
+
int parameterCount = parameters.Length;
|
|
112
|
+
parameterTypes = new Type[parameterCount];
|
|
113
|
+
for (int i = 0; i < parameterCount; i++)
|
|
114
|
+
{
|
|
115
|
+
object parameter = parameters[i];
|
|
116
|
+
if (parameter == null)
|
|
117
|
+
{
|
|
118
|
+
throw new ArgumentNullException(
|
|
119
|
+
$"Parameter at index {i} is null, cannot resolve type!"
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
parameterTypes[i] = parameter.GetType();
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
else
|
|
126
|
+
{
|
|
127
|
+
parameterTypes = Array.Empty<Type>();
|
|
128
|
+
}
|
|
129
|
+
signatureKey = new MethodSignatureKey(method, parameterTypes);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
/// </note>
|
|
47
47
|
/// <param name="logLevel">Severity of the message.</param>
|
|
48
48
|
/// <param name="message">Format string.</param>
|
|
49
|
-
/// <param name="args">Args to populate format string with.</param>
|
|
49
|
+
/// <param name="args">Args to populate the format string with.</param>
|
|
50
50
|
public static void Log(LogLevel logLevel, string message, params object[] args)
|
|
51
51
|
{
|
|
52
52
|
if (!enabled)
|
|
@@ -1,39 +1,45 @@
|
|
|
1
1
|
namespace DxMessaging.Unity
|
|
2
2
|
{
|
|
3
3
|
using Core;
|
|
4
|
+
using Messages;
|
|
4
5
|
using UnityEngine;
|
|
5
6
|
|
|
6
7
|
[RequireComponent(typeof(MessagingComponent))]
|
|
7
8
|
public abstract class MessageAwareComponent : MonoBehaviour
|
|
8
9
|
{
|
|
10
|
+
public virtual MessageRegistrationToken Token => _messageRegistrationToken;
|
|
11
|
+
|
|
9
12
|
protected MessageRegistrationToken _messageRegistrationToken;
|
|
10
13
|
|
|
11
14
|
/// <summary>
|
|
12
|
-
/// If true, will register/
|
|
15
|
+
/// If true, will register/unregister handles when the component is enabled or disabled.
|
|
13
16
|
/// </summary>
|
|
14
17
|
protected virtual bool MessageRegistrationTiedToEnableStatus => true;
|
|
15
18
|
|
|
16
19
|
protected bool _isQuitting;
|
|
17
20
|
|
|
18
|
-
protected
|
|
19
|
-
{
|
|
20
|
-
SetupMessageHandlers();
|
|
21
|
-
}
|
|
21
|
+
protected MessagingComponent _messagingComponent;
|
|
22
22
|
|
|
23
|
-
protected void
|
|
23
|
+
protected virtual void Awake()
|
|
24
24
|
{
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
MessagingComponent messenger = GetComponent<MessagingComponent>();
|
|
28
|
-
_messageRegistrationToken = messenger.Create(this);
|
|
29
|
-
}
|
|
30
|
-
|
|
25
|
+
_messagingComponent = GetComponent<MessagingComponent>();
|
|
26
|
+
_messageRegistrationToken = _messagingComponent.Create(this);
|
|
31
27
|
RegisterMessageHandlers();
|
|
32
28
|
}
|
|
33
29
|
|
|
34
30
|
protected virtual void RegisterMessageHandlers()
|
|
35
31
|
{
|
|
36
|
-
|
|
32
|
+
_ = _messageRegistrationToken.RegisterGameObjectTargeted<StringMessage>(
|
|
33
|
+
gameObject,
|
|
34
|
+
HandleStringGameObjectMessage
|
|
35
|
+
);
|
|
36
|
+
_ = _messageRegistrationToken.RegisterComponentTargeted<StringMessage>(
|
|
37
|
+
this,
|
|
38
|
+
HandleStringComponentMessage
|
|
39
|
+
);
|
|
40
|
+
_ = _messageRegistrationToken.RegisterUntargeted<GlobalStringMessage>(
|
|
41
|
+
HandleGlobalStringMessage
|
|
42
|
+
);
|
|
37
43
|
}
|
|
38
44
|
|
|
39
45
|
protected virtual void OnEnable()
|
|
@@ -72,5 +78,20 @@
|
|
|
72
78
|
{
|
|
73
79
|
_isQuitting = true;
|
|
74
80
|
}
|
|
81
|
+
|
|
82
|
+
protected virtual void HandleStringGameObjectMessage(ref StringMessage message)
|
|
83
|
+
{
|
|
84
|
+
// No-op by default
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
protected virtual void HandleStringComponentMessage(ref StringMessage message)
|
|
88
|
+
{
|
|
89
|
+
// No-op by default
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
protected virtual void HandleGlobalStringMessage(ref GlobalStringMessage message)
|
|
93
|
+
{
|
|
94
|
+
// No-op by default
|
|
95
|
+
}
|
|
75
96
|
}
|
|
76
97
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
namespace DxMessaging.Unity.Messages
|
|
2
|
+
{
|
|
3
|
+
using Core.Attributes;
|
|
4
|
+
|
|
5
|
+
[DxUntargetedMessage]
|
|
6
|
+
public readonly partial struct GlobalStringMessage
|
|
7
|
+
{
|
|
8
|
+
public readonly string message;
|
|
9
|
+
|
|
10
|
+
public GlobalStringMessage(string message)
|
|
11
|
+
{
|
|
12
|
+
this.message = message;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
namespace DxMessaging.Unity.Messages
|
|
2
|
+
{
|
|
3
|
+
using Core.Attributes;
|
|
4
|
+
|
|
5
|
+
[DxTargetedMessage]
|
|
6
|
+
public readonly partial struct StringMessage
|
|
7
|
+
{
|
|
8
|
+
public readonly string message;
|
|
9
|
+
|
|
10
|
+
public StringMessage(string message)
|
|
11
|
+
{
|
|
12
|
+
this.message = message;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
using Core;
|
|
6
6
|
using DxMessaging.Core;
|
|
7
7
|
using DxMessaging.Core.Extensions;
|
|
8
|
+
using DxMessaging.Core.Messages;
|
|
8
9
|
using global::Unity.PerformanceTesting;
|
|
9
10
|
using NUnit.Framework;
|
|
10
11
|
using Scripts.Components;
|
|
@@ -30,9 +31,16 @@
|
|
|
30
31
|
Debug.Log("| ------------ | ------------------- | ------------ | ");
|
|
31
32
|
|
|
32
33
|
ComplexTargetedMessage message = new(Guid.NewGuid());
|
|
34
|
+
DxReflexiveMessage reflexiveMessage = new(
|
|
35
|
+
nameof(SimpleMessageAwareComponent.HandleSlowComplexTargetedMessage),
|
|
36
|
+
ReflexiveSendMode.Flat,
|
|
37
|
+
message
|
|
38
|
+
);
|
|
39
|
+
|
|
33
40
|
Stopwatch timer = Stopwatch.StartNew();
|
|
34
41
|
|
|
35
42
|
RunTest(component => Unity(timer, timeout, component.gameObject, message));
|
|
43
|
+
|
|
36
44
|
RunTest(component => NormalGameObject(timer, timeout, component, message));
|
|
37
45
|
RunTest(component => NormalComponent(timer, timeout, component, message));
|
|
38
46
|
RunTest(component => NoCopyGameObject(timer, timeout, component, message));
|
|
@@ -40,6 +48,11 @@
|
|
|
40
48
|
|
|
41
49
|
SimpleUntargetedMessage untargetedMessage = new();
|
|
42
50
|
RunTest(component => NoCopyUntargeted(timer, timeout, component, untargetedMessage));
|
|
51
|
+
RunTest(component =>
|
|
52
|
+
ReflexiveOneArgument(timer, timeout, component.gameObject, reflexiveMessage)
|
|
53
|
+
);
|
|
54
|
+
RunTest(component => ReflexiveTwoArguments(timer, timeout, component.gameObject));
|
|
55
|
+
RunTest(component => ReflexiveThreeArguments(timer, timeout, component.gameObject));
|
|
43
56
|
}
|
|
44
57
|
|
|
45
58
|
[Test]
|
|
@@ -181,7 +194,10 @@
|
|
|
181
194
|
)
|
|
182
195
|
{
|
|
183
196
|
int count = 0;
|
|
184
|
-
|
|
197
|
+
if (!target.TryGetComponent(out SimpleMessageAwareComponent component))
|
|
198
|
+
{
|
|
199
|
+
component = target.AddComponent<SimpleMessageAwareComponent>();
|
|
200
|
+
}
|
|
185
201
|
component.slowComplexTargetedHandler = () => ++count;
|
|
186
202
|
// Pre-warm
|
|
187
203
|
target.SendMessage(
|
|
@@ -221,6 +237,130 @@
|
|
|
221
237
|
DisplayCount("Unity", count, timeout, allocating);
|
|
222
238
|
}
|
|
223
239
|
|
|
240
|
+
private static void ReflexiveThreeArguments(
|
|
241
|
+
Stopwatch timer,
|
|
242
|
+
TimeSpan timeout,
|
|
243
|
+
GameObject go
|
|
244
|
+
)
|
|
245
|
+
{
|
|
246
|
+
int count = 0;
|
|
247
|
+
if (!go.TryGetComponent(out SimpleMessageAwareComponent component))
|
|
248
|
+
{
|
|
249
|
+
component = go.AddComponent<SimpleMessageAwareComponent>();
|
|
250
|
+
}
|
|
251
|
+
component.reflexiveThreeArgumentHandler = () => ++count;
|
|
252
|
+
DxReflexiveMessage message = new(
|
|
253
|
+
nameof(SimpleMessageAwareComponent.HandleReflexiveMessageThreeArguments),
|
|
254
|
+
ReflexiveSendMode.Flat,
|
|
255
|
+
1,
|
|
256
|
+
2,
|
|
257
|
+
3
|
|
258
|
+
);
|
|
259
|
+
InstanceId target = go;
|
|
260
|
+
// Pre-warm
|
|
261
|
+
message.EmitTargeted(target);
|
|
262
|
+
|
|
263
|
+
timer.Restart();
|
|
264
|
+
do
|
|
265
|
+
{
|
|
266
|
+
for (int i = 0; i < NumInvocationsPerIteration; ++i)
|
|
267
|
+
{
|
|
268
|
+
message.EmitTargeted(target);
|
|
269
|
+
}
|
|
270
|
+
} while (timer.Elapsed < timeout);
|
|
271
|
+
bool allocating;
|
|
272
|
+
try
|
|
273
|
+
{
|
|
274
|
+
Assert.That(() => message.EmitTargeted(target), Is.Not.AllocatingGCMemory());
|
|
275
|
+
allocating = false;
|
|
276
|
+
}
|
|
277
|
+
catch
|
|
278
|
+
{
|
|
279
|
+
allocating = true;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
DisplayCount("Reflexive (Three Arguments)", count, timeout, allocating);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
private static void ReflexiveTwoArguments(Stopwatch timer, TimeSpan timeout, GameObject go)
|
|
286
|
+
{
|
|
287
|
+
int count = 0;
|
|
288
|
+
if (!go.TryGetComponent(out SimpleMessageAwareComponent component))
|
|
289
|
+
{
|
|
290
|
+
component = go.AddComponent<SimpleMessageAwareComponent>();
|
|
291
|
+
}
|
|
292
|
+
component.reflexiveTwoArgumentHandler = () => ++count;
|
|
293
|
+
DxReflexiveMessage message = new(
|
|
294
|
+
nameof(SimpleMessageAwareComponent.HandleReflexiveMessageTwoArguments),
|
|
295
|
+
ReflexiveSendMode.Flat,
|
|
296
|
+
1,
|
|
297
|
+
2
|
|
298
|
+
);
|
|
299
|
+
InstanceId target = go;
|
|
300
|
+
// Pre-warm
|
|
301
|
+
message.EmitTargeted(target);
|
|
302
|
+
|
|
303
|
+
timer.Restart();
|
|
304
|
+
do
|
|
305
|
+
{
|
|
306
|
+
for (int i = 0; i < NumInvocationsPerIteration; ++i)
|
|
307
|
+
{
|
|
308
|
+
message.EmitTargeted(target);
|
|
309
|
+
}
|
|
310
|
+
} while (timer.Elapsed < timeout);
|
|
311
|
+
bool allocating;
|
|
312
|
+
try
|
|
313
|
+
{
|
|
314
|
+
Assert.That(() => message.EmitTargeted(target), Is.Not.AllocatingGCMemory());
|
|
315
|
+
allocating = false;
|
|
316
|
+
}
|
|
317
|
+
catch
|
|
318
|
+
{
|
|
319
|
+
allocating = true;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
DisplayCount("Reflexive (Two Arguments)", count, timeout, allocating);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
private static void ReflexiveOneArgument(
|
|
326
|
+
Stopwatch timer,
|
|
327
|
+
TimeSpan timeout,
|
|
328
|
+
GameObject go,
|
|
329
|
+
DxReflexiveMessage message
|
|
330
|
+
)
|
|
331
|
+
{
|
|
332
|
+
int count = 0;
|
|
333
|
+
if (!go.TryGetComponent(out SimpleMessageAwareComponent component))
|
|
334
|
+
{
|
|
335
|
+
component = go.AddComponent<SimpleMessageAwareComponent>();
|
|
336
|
+
}
|
|
337
|
+
component.slowComplexTargetedHandler = () => ++count;
|
|
338
|
+
InstanceId target = go;
|
|
339
|
+
// Pre-warm
|
|
340
|
+
message.EmitTargeted(target);
|
|
341
|
+
|
|
342
|
+
timer.Restart();
|
|
343
|
+
do
|
|
344
|
+
{
|
|
345
|
+
for (int i = 0; i < NumInvocationsPerIteration; ++i)
|
|
346
|
+
{
|
|
347
|
+
message.EmitTargeted(target);
|
|
348
|
+
}
|
|
349
|
+
} while (timer.Elapsed < timeout);
|
|
350
|
+
bool allocating;
|
|
351
|
+
try
|
|
352
|
+
{
|
|
353
|
+
Assert.That(() => message.EmitTargeted(target), Is.Not.AllocatingGCMemory());
|
|
354
|
+
allocating = false;
|
|
355
|
+
}
|
|
356
|
+
catch
|
|
357
|
+
{
|
|
358
|
+
allocating = true;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
DisplayCount("Reflexive (One Argument)", count, timeout, allocating);
|
|
362
|
+
}
|
|
363
|
+
|
|
224
364
|
private void NormalGameObject(
|
|
225
365
|
Stopwatch timer,
|
|
226
366
|
TimeSpan timeout,
|
|
@@ -154,20 +154,12 @@
|
|
|
154
154
|
}
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
-
protected MessageRegistrationToken GetToken(MessageAwareComponent component)
|
|
157
|
+
protected static MessageRegistrationToken GetToken(MessageAwareComponent component)
|
|
158
158
|
{
|
|
159
|
-
|
|
160
|
-
FieldInfo field = typeof(MessageAwareComponent).GetField(
|
|
161
|
-
"_messageRegistrationToken",
|
|
162
|
-
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic
|
|
163
|
-
);
|
|
164
|
-
Assert.IsNotNull(field);
|
|
165
|
-
MessageRegistrationToken token = field.GetValue(component) as MessageRegistrationToken;
|
|
166
|
-
Assert.IsNotNull(token);
|
|
167
|
-
return token;
|
|
159
|
+
return component.Token;
|
|
168
160
|
}
|
|
169
161
|
|
|
170
|
-
protected IEnumerator WaitUntilMessageHandlerIsFresh()
|
|
162
|
+
protected static IEnumerator WaitUntilMessageHandlerIsFresh()
|
|
171
163
|
{
|
|
172
164
|
MessageBus messageBus = MessageHandler.MessageBus;
|
|
173
165
|
Assert.IsNotNull(messageBus);
|
|
@@ -37,6 +37,8 @@
|
|
|
37
37
|
public Action componentTargetedHandler;
|
|
38
38
|
public Action complexComponentTargetedHandler;
|
|
39
39
|
public Action componentBroadcastHandler;
|
|
40
|
+
public Action reflexiveTwoArgumentHandler;
|
|
41
|
+
public Action reflexiveThreeArgumentHandler;
|
|
40
42
|
|
|
41
43
|
private bool _slowComplexTargetingEnabled = true;
|
|
42
44
|
private bool _fastComplexTargetingEnabled = true;
|
|
@@ -122,6 +124,16 @@
|
|
|
122
124
|
}
|
|
123
125
|
}
|
|
124
126
|
|
|
127
|
+
public void HandleReflexiveMessageTwoArguments(int a, int b)
|
|
128
|
+
{
|
|
129
|
+
reflexiveTwoArgumentHandler?.Invoke();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
public void HandleReflexiveMessageThreeArguments(int a, int b, int c)
|
|
133
|
+
{
|
|
134
|
+
reflexiveThreeArgumentHandler?.Invoke();
|
|
135
|
+
}
|
|
136
|
+
|
|
125
137
|
public void HandleSlowComplexTargetedMessage(ComplexTargetedMessage message)
|
|
126
138
|
{
|
|
127
139
|
slowComplexTargetedHandler?.Invoke();
|