com.wallstop-studios.dxmessaging 2.0.0-rc27.3 → 2.0.0
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/Docs/Comparisons.md +194 -66
- package/Docs/GettingStarted.md +84 -17
- package/Docs/Overview.md +114 -20
- package/Docs/Patterns.md +41 -2
- package/Docs/Performance.md +9 -9
- package/Docs/VisualGuide.md +75 -10
- package/Editor/Analyzers/Microsoft.CodeAnalysis.CSharp.dll +0 -0
- package/Editor/Analyzers/Microsoft.CodeAnalysis.CSharp.dll.meta +3 -3
- package/Editor/Analyzers/Microsoft.CodeAnalysis.dll +0 -0
- package/Editor/Analyzers/Microsoft.CodeAnalysis.dll.meta +2 -2
- package/Editor/Analyzers/System.Collections.Immutable.dll +0 -0
- package/Editor/Analyzers/System.Reflection.Metadata.dll +0 -0
- package/Editor/Analyzers/System.Runtime.CompilerServices.Unsafe.dll +0 -0
- package/README.md +262 -63
- package/Runtime/Core/MessageBus/MessageBus.cs +13 -0
- package/Runtime/Core/MessageHandler.cs +27 -33
- package/Runtime/Core/MessageRegistrationToken.cs +12 -21
- package/Runtime/Unity/MessageAwareComponent.cs +6 -0
- package/Runtime/Unity/MessagingComponent.cs +24 -0
- package/Samples~/Mini Combat/README.md +81 -21
- package/Samples~/Mini Combat/Walkthrough.md +23 -1
- package/Samples~/UI Buttons + Inspector/README.md +55 -12
- package/Tests/Runtime/Core/MessagingComponentLifecycleTests.cs +89 -0
- package/Tests/Runtime/Core/MessagingComponentLifecycleTests.cs.meta +11 -0
- package/Tests/Runtime/Core/ReflexiveMessageWarningTests.cs +86 -0
- package/Tests/Runtime/Core/ReflexiveMessageWarningTests.cs.meta +11 -0
- package/Tests/Runtime/Core/SourceGeneratorNestedTests.cs +1 -1
- package/Tests/Runtime/Core/UntargetedPrefreezeTests.cs +116 -0
- package/Tests/Runtime/Core/UntargetedPrefreezeTests.cs.meta +11 -0
- package/Tests/Runtime/Scripts/Components/ReflexiveReceiverComponent.cs +14 -0
- package/Tests/Runtime/Scripts/Components/ReflexiveReceiverComponent.cs.meta +11 -0
- package/package.json +1 -1
|
@@ -1782,6 +1782,7 @@ namespace DxMessaging.Core
|
|
|
1782
1782
|
public long version;
|
|
1783
1783
|
public long lastSeenVersion = -1;
|
|
1784
1784
|
public long lastSeenEmissionId;
|
|
1785
|
+
internal int prefreezeInvocationCount;
|
|
1785
1786
|
}
|
|
1786
1787
|
|
|
1787
1788
|
/// <summary>
|
|
@@ -1895,39 +1896,16 @@ namespace DxMessaging.Core
|
|
|
1895
1896
|
/// <param name="priority">Priority at which to run the handlers.</param>
|
|
1896
1897
|
public void HandleUntargeted(ref T message, int priority, long emissionId)
|
|
1897
1898
|
{
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
)
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
&& _untargetedPostProcessingHandlers.TryGetValue(priority, out var up)
|
|
1909
|
-
)
|
|
1910
|
-
{
|
|
1911
|
-
_ = GetOrAddNewHandlerStack(up, emissionId);
|
|
1912
|
-
}
|
|
1913
|
-
// Pre-freeze post-processors for this emission/priority
|
|
1914
|
-
if (
|
|
1915
|
-
_untargetedPostProcessingFastHandlers != null
|
|
1916
|
-
&& _untargetedPostProcessingFastHandlers.TryGetValue(
|
|
1917
|
-
priority,
|
|
1918
|
-
out var fastCache
|
|
1919
|
-
)
|
|
1920
|
-
)
|
|
1921
|
-
{
|
|
1922
|
-
_ = GetOrAddNewHandlerStack(fastCache, emissionId);
|
|
1923
|
-
}
|
|
1924
|
-
if (
|
|
1925
|
-
_untargetedPostProcessingHandlers != null
|
|
1926
|
-
&& _untargetedPostProcessingHandlers.TryGetValue(priority, out var cache)
|
|
1927
|
-
)
|
|
1928
|
-
{
|
|
1929
|
-
_ = GetOrAddNewHandlerStack(cache, emissionId);
|
|
1930
|
-
}
|
|
1899
|
+
PrefreezeHandlersForEmission(
|
|
1900
|
+
_untargetedPostProcessingFastHandlers,
|
|
1901
|
+
priority,
|
|
1902
|
+
emissionId
|
|
1903
|
+
);
|
|
1904
|
+
PrefreezeHandlersForEmission(
|
|
1905
|
+
_untargetedPostProcessingHandlers,
|
|
1906
|
+
priority,
|
|
1907
|
+
emissionId
|
|
1908
|
+
);
|
|
1931
1909
|
|
|
1932
1910
|
RunFastHandlers(_untargetedFastHandlers, ref message, priority, emissionId);
|
|
1933
1911
|
RunHandlers(_untargetedHandlers, ref message, priority, emissionId);
|
|
@@ -3535,6 +3513,22 @@ namespace DxMessaging.Core
|
|
|
3535
3513
|
return actionCache.cache;
|
|
3536
3514
|
}
|
|
3537
3515
|
|
|
3516
|
+
private static void PrefreezeHandlersForEmission<THandler>(
|
|
3517
|
+
Dictionary<int, HandlerActionCache<THandler>> handlers,
|
|
3518
|
+
int priority,
|
|
3519
|
+
long emissionId
|
|
3520
|
+
)
|
|
3521
|
+
{
|
|
3522
|
+
if (
|
|
3523
|
+
handlers != null
|
|
3524
|
+
&& handlers.TryGetValue(priority, out HandlerActionCache<THandler> cache)
|
|
3525
|
+
)
|
|
3526
|
+
{
|
|
3527
|
+
cache.prefreezeInvocationCount++;
|
|
3528
|
+
_ = GetOrAddNewHandlerStack(cache, emissionId);
|
|
3529
|
+
}
|
|
3530
|
+
}
|
|
3531
|
+
|
|
3538
3532
|
private static Action AddHandler<TU>(
|
|
3539
3533
|
InstanceId context,
|
|
3540
3534
|
ref Dictionary<
|
|
@@ -59,6 +59,7 @@ namespace DxMessaging.Core
|
|
|
59
59
|
|
|
60
60
|
private readonly Dictionary<MessageRegistrationHandle, Action> _registrations = new();
|
|
61
61
|
private readonly Dictionary<MessageRegistrationHandle, Action> _deregistrations = new();
|
|
62
|
+
private readonly List<Action> _deregistrationQueue = new();
|
|
62
63
|
internal readonly Dictionary<
|
|
63
64
|
MessageRegistrationHandle,
|
|
64
65
|
MessageRegistrationMetadata
|
|
@@ -1878,13 +1879,11 @@ namespace DxMessaging.Core
|
|
|
1878
1879
|
|
|
1879
1880
|
if (_deregistrations is { Count: > 0 })
|
|
1880
1881
|
{
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
>.ValueCollection.Enumerator enumerator = _deregistrations.Values.GetEnumerator();
|
|
1885
|
-
while (enumerator.MoveNext())
|
|
1882
|
+
_deregistrationQueue.Clear();
|
|
1883
|
+
_deregistrationQueue.AddRange(_deregistrations.Values);
|
|
1884
|
+
foreach (Action deregistration in _deregistrationQueue)
|
|
1886
1885
|
{
|
|
1887
|
-
|
|
1886
|
+
deregistration?.Invoke();
|
|
1888
1887
|
}
|
|
1889
1888
|
}
|
|
1890
1889
|
|
|
@@ -1905,15 +1904,13 @@ namespace DxMessaging.Core
|
|
|
1905
1904
|
/// </example>
|
|
1906
1905
|
public void UnregisterAll()
|
|
1907
1906
|
{
|
|
1908
|
-
if (
|
|
1907
|
+
if (_deregistrations is { Count: > 0 })
|
|
1909
1908
|
{
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
>.ValueCollection.Enumerator enumerator = _deregistrations.Values.GetEnumerator();
|
|
1914
|
-
while (enumerator.MoveNext())
|
|
1909
|
+
_deregistrationQueue.Clear();
|
|
1910
|
+
_deregistrationQueue.AddRange(_deregistrations.Values);
|
|
1911
|
+
foreach (Action deregistration in _deregistrationQueue)
|
|
1915
1912
|
{
|
|
1916
|
-
|
|
1913
|
+
deregistration?.Invoke();
|
|
1917
1914
|
}
|
|
1918
1915
|
}
|
|
1919
1916
|
|
|
@@ -1934,16 +1931,10 @@ namespace DxMessaging.Core
|
|
|
1934
1931
|
/// </example>
|
|
1935
1932
|
public void RemoveRegistration(MessageRegistrationHandle handle)
|
|
1936
1933
|
{
|
|
1937
|
-
if (
|
|
1938
|
-
_deregistrations != null
|
|
1939
|
-
&& _deregistrations.TryGetValue(handle, out Action deregistrationAction)
|
|
1940
|
-
)
|
|
1934
|
+
if (_deregistrations?.Remove(handle, out Action deregistrationAction) == true)
|
|
1941
1935
|
{
|
|
1942
|
-
deregistrationAction();
|
|
1943
|
-
_ = _deregistrations.Remove(handle);
|
|
1936
|
+
deregistrationAction?.Invoke();
|
|
1944
1937
|
}
|
|
1945
|
-
|
|
1946
|
-
_ = _registrations?.Remove(handle);
|
|
1947
1938
|
}
|
|
1948
1939
|
|
|
1949
1940
|
/// <summary>
|
|
@@ -124,8 +124,14 @@ namespace DxMessaging.Unity
|
|
|
124
124
|
/// </summary>
|
|
125
125
|
protected virtual void OnDestroy()
|
|
126
126
|
{
|
|
127
|
+
if (_messagingComponent != null)
|
|
128
|
+
{
|
|
129
|
+
_messagingComponent.Release(this);
|
|
130
|
+
}
|
|
131
|
+
|
|
127
132
|
_messageRegistrationToken?.Disable();
|
|
128
133
|
_messageRegistrationToken = null;
|
|
134
|
+
_messagingComponent = null;
|
|
129
135
|
}
|
|
130
136
|
|
|
131
137
|
/// <summary>
|
|
@@ -73,6 +73,30 @@ namespace DxMessaging.Unity
|
|
|
73
73
|
return createdToken;
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
/// <summary>
|
|
77
|
+
/// Releases the registration token previously created for <paramref name="listener"/>.
|
|
78
|
+
/// </summary>
|
|
79
|
+
/// <param name="listener">Listener whose token should be released.</param>
|
|
80
|
+
/// <remarks>
|
|
81
|
+
/// Invokes <see cref="MessageRegistrationToken.Disable"/> and removes the listener from the internal cache.
|
|
82
|
+
/// Safe to call multiple times.
|
|
83
|
+
/// </remarks>
|
|
84
|
+
public bool Release(MonoBehaviour listener)
|
|
85
|
+
{
|
|
86
|
+
if (listener is null)
|
|
87
|
+
{
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (_registeredListeners.Remove(listener, out MessageRegistrationToken token))
|
|
92
|
+
{
|
|
93
|
+
token?.Disable();
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
|
|
76
100
|
/// <summary>
|
|
77
101
|
/// Ensures the underlying <see cref="Core.MessageHandler"/> exists.
|
|
78
102
|
/// </summary>
|
|
@@ -1,16 +1,79 @@
|
|
|
1
1
|
# Mini Combat Sample
|
|
2
2
|
|
|
3
|
-
> **
|
|
3
|
+
> **Perfect for:** First-time users who want to see messaging in action with a working combat example
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## What You'll Learn in 10 Minutes
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
**Stop reading documentation and start seeing it work!** This sample shows:
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
1. How Player heals without UI knowing about Player (**zero coupling**)
|
|
10
|
+
2. How Enemy broadcasts damage without knowing who cares (**observer pattern**)
|
|
11
|
+
3. How settings changes update everything automatically (**global events**)
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
**No prior messaging experience needed** - this sample walks you through everything.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Why This Sample Matters
|
|
18
|
+
|
|
19
|
+
### Before DxMessaging:
|
|
20
|
+
|
|
21
|
+
```csharp
|
|
22
|
+
public class Player {
|
|
23
|
+
[SerializeField] private UI ui; // Coupling!
|
|
24
|
+
[SerializeField] private AudioManager audio; // More coupling!
|
|
25
|
+
|
|
26
|
+
void Heal(int amount) {
|
|
27
|
+
health += amount;
|
|
28
|
+
ui.UpdateHealth(health); // Manual call
|
|
29
|
+
audio.PlayHealSound(); // Manual call
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
#### Every new system = another SerializeField + another manual call.
|
|
35
|
+
|
|
36
|
+
##### With DxMessaging:
|
|
37
|
+
|
|
38
|
+
```csharp
|
|
39
|
+
public class Player : MessageAwareComponent {
|
|
40
|
+
void Heal(int amount) {
|
|
41
|
+
health += amount;
|
|
42
|
+
new PlayerHealed(amount).EmitBroadcast(this);
|
|
43
|
+
// Done! UI, audio, analytics all react automatically.
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
###### Zero coupling. Zero manual wiring. That's the power of messaging.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Key Concepts (3 Message Types)
|
|
53
|
+
|
|
54
|
+
### 1. Untargeted - "Everyone, listen up!"
|
|
55
|
+
|
|
56
|
+
**Example:** Game settings changed
|
|
57
|
+
|
|
58
|
+
- Like a stadium announcement - everyone hears it
|
|
59
|
+
- No specific target
|
|
60
|
+
- Perfect for: settings, pause, scene transitions
|
|
61
|
+
|
|
62
|
+
### 2. Targeted - "Hey YOU, do this!"
|
|
63
|
+
|
|
64
|
+
**Example:** Heal this specific player
|
|
65
|
+
|
|
66
|
+
- Like mailing a letter to one address
|
|
67
|
+
- Only the target receives it
|
|
68
|
+
- Perfect for: commands, direct actions
|
|
69
|
+
|
|
70
|
+
### 3. Broadcast - "I did something!"
|
|
71
|
+
|
|
72
|
+
**Example:** Enemy took damage
|
|
73
|
+
|
|
74
|
+
- Like ringing a bell - anyone nearby can hear
|
|
75
|
+
- Source announces, observers react
|
|
76
|
+
- Perfect for: events, notifications, analytics
|
|
14
77
|
|
|
15
78
|
---
|
|
16
79
|
|
|
@@ -28,25 +91,22 @@ Here's what each script does:
|
|
|
28
91
|
|
|
29
92
|
---
|
|
30
93
|
|
|
31
|
-
## Quick Start Guide
|
|
94
|
+
## Quick Start Guide (2 Minutes to Working Demo)
|
|
32
95
|
|
|
33
|
-
###
|
|
96
|
+
### Import & Run (Fastest Way)
|
|
34
97
|
|
|
35
|
-
|
|
36
|
-
- Window → Package Manager
|
|
98
|
+
#### Want to see it work immediately?
|
|
37
99
|
|
|
38
|
-
1. **
|
|
39
|
-
|
|
40
|
-
|
|
100
|
+
1. **Open Package Manager**: Window → Package Manager
|
|
101
|
+
2. **Find DxMessaging** in the package list
|
|
102
|
+
3. **Scroll to Samples** section → Find "Mini Combat" → Click **Import**
|
|
103
|
+
4. **Navigate to** Assets/Samples/DxMessaging/.../Mini Combat/
|
|
104
|
+
5. **Open the scene**
|
|
105
|
+
6. **Press Play** 🎮
|
|
41
106
|
|
|
42
|
-
|
|
43
|
-
- In the Package Manager details view, scroll down to find the "Samples" section
|
|
44
|
-
- Find "Mini Combat" and click **Import**
|
|
45
|
-
- The sample files will be imported into your Assets/Samples folder
|
|
107
|
+
**That's it!** Watch the Console logs as messages flow.
|
|
46
108
|
|
|
47
|
-
|
|
48
|
-
- Navigate to Assets/Samples/DxMessaging/.../Mini Combat/
|
|
49
|
-
- Open the sample scene or create a new scene and follow Step 1 below
|
|
109
|
+
**Pro tip:** Enable diagnostics in the Inspector (MessagingComponent → Enable Diagnostics) to see message traffic in real-time.
|
|
50
110
|
|
|
51
111
|
### Method 2: Manual Setup in Your Scene
|
|
52
112
|
|
|
@@ -1,6 +1,28 @@
|
|
|
1
1
|
# Mini Combat Walkthrough
|
|
2
2
|
|
|
3
|
-
> **Deep Dive**:
|
|
3
|
+
> **Deep Dive**: Ready to understand HOW it works? This walkthrough explains every line of code and shows the complete message flow.
|
|
4
|
+
|
|
5
|
+
## Who This Is For
|
|
6
|
+
|
|
7
|
+
- ✅ **You've imported and run the sample** - now you want to understand it
|
|
8
|
+
- ✅ **You want to build your own** - need to see the patterns in action
|
|
9
|
+
- ✅ **You're debugging something** - need to understand the flow
|
|
10
|
+
|
|
11
|
+
**Haven't run the sample yet?** Go back to [the sample README](README.md) and press Play first. Come back when you've seen it work.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## What You'll Understand By The End
|
|
16
|
+
|
|
17
|
+
After reading this walkthrough, you'll know:
|
|
18
|
+
|
|
19
|
+
1. **Why each message type was chosen** - the reasoning behind Untargeted vs Targeted vs Broadcast
|
|
20
|
+
2. **How the code flows** - step-by-step from Boot.cs through every script
|
|
21
|
+
3. **Common patterns** - Observer, Broadcaster, Orchestrator, and more
|
|
22
|
+
4. **Debugging strategies** - how to find and fix issues
|
|
23
|
+
5. **How to extend it** - add your own messages and handlers
|
|
24
|
+
|
|
25
|
+
**Estimated time:** 20-30 minutes for thorough understanding
|
|
4
26
|
|
|
5
27
|
---
|
|
6
28
|
|
|
@@ -1,21 +1,64 @@
|
|
|
1
|
-
# UI Buttons + Inspector
|
|
1
|
+
# UI Buttons + Inspector Sample
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> **Perfect for:** Seeing DxMessaging work with Unity UI in 60 seconds
|
|
4
4
|
|
|
5
|
-
## What You
|
|
5
|
+
## What You'll Learn (No Code Required!)
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
- How to observe messages in the Console with a tiny listener component.
|
|
9
|
-
- How to turn on diagnostics to see message traffic while you click.
|
|
7
|
+
**Stop writing button click handlers!** This sample shows you how to:
|
|
10
8
|
|
|
11
|
-
|
|
9
|
+
1. **Wire UI Buttons to messages** - directly from the Inspector (drag & drop)
|
|
10
|
+
2. **See messages flow in real-time** - watch the Console as you click
|
|
11
|
+
3. **Enable diagnostics** - see every message with timestamps and payloads
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
1. Select `com.wallstop-studios.dxmessaging`.
|
|
15
|
-
1. In Samples, import “UI Buttons + Inspector”.
|
|
16
|
-
1. Open the sample scene (created under your project’s `Assets/Samples/...`).
|
|
13
|
+
**Why this matters:** You can add new systems (analytics, audio, achievements) that react to button clicks WITHOUT touching existing code.
|
|
17
14
|
|
|
18
|
-
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## The Power Move
|
|
18
|
+
|
|
19
|
+
### Before DxMessaging:
|
|
20
|
+
|
|
21
|
+
```csharp
|
|
22
|
+
public class PlayButton : MonoBehaviour {
|
|
23
|
+
[SerializeField] private GameManager gameManager; // Coupling
|
|
24
|
+
[SerializeField] private AudioManager audio; // Coupling
|
|
25
|
+
[SerializeField] private Analytics analytics; // Coupling
|
|
26
|
+
|
|
27
|
+
public void OnClick() {
|
|
28
|
+
gameManager.StartGame(); // Manual call
|
|
29
|
+
audio.PlayClickSound(); // Manual call
|
|
30
|
+
analytics.LogButtonClick(); // Manual call
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
#### Every new system = update PlayButton script. Exhausting.
|
|
36
|
+
|
|
37
|
+
##### With DxMessaging:
|
|
38
|
+
|
|
39
|
+
```csharp
|
|
40
|
+
public class PlayButton : MonoBehaviour {
|
|
41
|
+
public void OnClick() {
|
|
42
|
+
new ButtonClicked("Play").Emit();
|
|
43
|
+
// Done! Everything reacts automatically.
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
###### GameManager, Audio, and Analytics listen independently. Zero coupling.
|
|
49
|
+
|
|
50
|
+
## Import & Run (30 Seconds)
|
|
51
|
+
|
|
52
|
+
### Want to see it immediately?
|
|
53
|
+
|
|
54
|
+
1. **Window → Package Manager**
|
|
55
|
+
2. **Find DxMessaging** → Scroll to **Samples**
|
|
56
|
+
3. **"UI Buttons + Inspector"** → Click **Import**
|
|
57
|
+
4. **Navigate to** Assets/Samples/.../UI Buttons + Inspector/
|
|
58
|
+
5. **Open the scene** → **Press Play** 🎮
|
|
59
|
+
6. **Click the buttons** → Watch Console logs
|
|
60
|
+
|
|
61
|
+
**Done!** You're seeing DxMessaging in action.
|
|
19
62
|
|
|
20
63
|
## Click-To-Message: The Quick Path
|
|
21
64
|
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
namespace DxMessaging.Tests.Runtime.Core
|
|
2
|
+
{
|
|
3
|
+
using System.Collections;
|
|
4
|
+
using DxMessaging.Core;
|
|
5
|
+
using DxMessaging.Tests.Runtime.Scripts.Components;
|
|
6
|
+
using DxMessaging.Unity;
|
|
7
|
+
using NUnit.Framework;
|
|
8
|
+
using UnityEngine;
|
|
9
|
+
using UnityEngine.TestTools;
|
|
10
|
+
|
|
11
|
+
public sealed class MessagingComponentLifecycleTests : MessagingTestBase
|
|
12
|
+
{
|
|
13
|
+
[UnityTest]
|
|
14
|
+
public IEnumerator ReleasesListenerOnDestroy()
|
|
15
|
+
{
|
|
16
|
+
GameObject go = new(
|
|
17
|
+
"Lifecycle",
|
|
18
|
+
typeof(MessagingComponent),
|
|
19
|
+
typeof(SimpleMessageAwareComponent)
|
|
20
|
+
);
|
|
21
|
+
_spawned.Add(go);
|
|
22
|
+
|
|
23
|
+
MessagingComponent messaging = go.GetComponent<MessagingComponent>();
|
|
24
|
+
SimpleMessageAwareComponent listener = go.GetComponent<SimpleMessageAwareComponent>();
|
|
25
|
+
|
|
26
|
+
yield return null;
|
|
27
|
+
|
|
28
|
+
Assert.AreEqual(
|
|
29
|
+
1,
|
|
30
|
+
messaging._registeredListeners.Count,
|
|
31
|
+
"Expected initial listener registration."
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
Object.Destroy(listener);
|
|
35
|
+
yield return null;
|
|
36
|
+
|
|
37
|
+
Assert.AreEqual(
|
|
38
|
+
0,
|
|
39
|
+
messaging._registeredListeners.Count,
|
|
40
|
+
"Listener dictionary should be cleared after destroy."
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
SimpleMessageAwareComponent replacement =
|
|
44
|
+
go.AddComponent<SimpleMessageAwareComponent>();
|
|
45
|
+
yield return null;
|
|
46
|
+
|
|
47
|
+
Assert.AreEqual(
|
|
48
|
+
1,
|
|
49
|
+
messaging._registeredListeners.Count,
|
|
50
|
+
"Replacement listener should be tracked."
|
|
51
|
+
);
|
|
52
|
+
Assert.IsTrue(messaging._registeredListeners.ContainsKey(replacement));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
[UnityTest]
|
|
56
|
+
public IEnumerator ManualReleaseRemovesListenerAndDisablesToken()
|
|
57
|
+
{
|
|
58
|
+
GameObject go = new(
|
|
59
|
+
"ManualRelease",
|
|
60
|
+
typeof(MessagingComponent),
|
|
61
|
+
typeof(ManualListenerComponent)
|
|
62
|
+
);
|
|
63
|
+
_spawned.Add(go);
|
|
64
|
+
|
|
65
|
+
MessagingComponent messaging = go.GetComponent<MessagingComponent>();
|
|
66
|
+
ManualListenerComponent listener = go.GetComponent<ManualListenerComponent>();
|
|
67
|
+
|
|
68
|
+
MessageRegistrationToken token = listener.RequestToken(messaging);
|
|
69
|
+
Assert.AreEqual(
|
|
70
|
+
1,
|
|
71
|
+
messaging._registeredListeners.Count,
|
|
72
|
+
"Token request should register listener."
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
token.Enable();
|
|
76
|
+
Assert.IsTrue(token.Enabled, "Token should enable successfully.");
|
|
77
|
+
|
|
78
|
+
messaging.Release(listener);
|
|
79
|
+
yield return null;
|
|
80
|
+
|
|
81
|
+
Assert.AreEqual(
|
|
82
|
+
0,
|
|
83
|
+
messaging._registeredListeners.Count,
|
|
84
|
+
"Manual release should remove listener."
|
|
85
|
+
);
|
|
86
|
+
Assert.IsFalse(token.Enabled, "Released token should be disabled.");
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
namespace DxMessaging.Tests.Runtime.Core
|
|
2
|
+
{
|
|
3
|
+
using System;
|
|
4
|
+
using System.Collections;
|
|
5
|
+
using System.Collections.Generic;
|
|
6
|
+
using System.Linq;
|
|
7
|
+
using DxMessaging.Core;
|
|
8
|
+
using DxMessaging.Core.MessageBus;
|
|
9
|
+
using DxMessaging.Core.Messages;
|
|
10
|
+
using DxMessaging.Tests.Runtime.Scripts.Components;
|
|
11
|
+
using NUnit.Framework;
|
|
12
|
+
using UnityEngine;
|
|
13
|
+
using UnityEngine.TestTools;
|
|
14
|
+
|
|
15
|
+
public sealed class ReflexiveMessageWarningTests : MessagingTestBase
|
|
16
|
+
{
|
|
17
|
+
[UnityTest]
|
|
18
|
+
public IEnumerator LogsWarningOncePerBus()
|
|
19
|
+
{
|
|
20
|
+
List<(LogLevel level, string message)> logs = new();
|
|
21
|
+
var previousLogFunction = MessagingDebug.LogFunction;
|
|
22
|
+
try
|
|
23
|
+
{
|
|
24
|
+
MessagingDebug.LogFunction = (level, message) => logs.Add((level, message));
|
|
25
|
+
MessagingDebug.enabled = true;
|
|
26
|
+
logs.Clear();
|
|
27
|
+
|
|
28
|
+
GameObject go = new("ReflexiveReceiver", typeof(ReflexiveReceiverComponent));
|
|
29
|
+
_spawned.Add(go);
|
|
30
|
+
ReflexiveReceiverComponent receiver = go.GetComponent<ReflexiveReceiverComponent>();
|
|
31
|
+
|
|
32
|
+
MessageBus bus = new();
|
|
33
|
+
ReflexiveMessage message = new("OnReflexive", ReflexiveSendMode.Flat);
|
|
34
|
+
|
|
35
|
+
int warningsBefore = CountWarnings(logs);
|
|
36
|
+
InstanceId target = receiver;
|
|
37
|
+
bus.TargetedBroadcast(ref target, ref message);
|
|
38
|
+
Assert.AreEqual(1, receiver.InvocationCount);
|
|
39
|
+
int warningsAfter = CountWarnings(logs);
|
|
40
|
+
Assert.Greater(
|
|
41
|
+
warningsAfter,
|
|
42
|
+
warningsBefore,
|
|
43
|
+
"First reflexive dispatch should log a warning."
|
|
44
|
+
);
|
|
45
|
+
StringAssert.Contains("ReflexiveMessage", logs[^1].message);
|
|
46
|
+
|
|
47
|
+
warningsBefore = warningsAfter;
|
|
48
|
+
target = receiver;
|
|
49
|
+
bus.TargetedBroadcast(ref target, ref message);
|
|
50
|
+
Assert.AreEqual(2, receiver.InvocationCount);
|
|
51
|
+
warningsAfter = CountWarnings(logs);
|
|
52
|
+
Assert.AreEqual(
|
|
53
|
+
warningsBefore,
|
|
54
|
+
warningsAfter,
|
|
55
|
+
"Second dispatch on the same bus should not emit additional warnings."
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
MessageBus secondBus = new();
|
|
59
|
+
warningsBefore = warningsAfter;
|
|
60
|
+
target = receiver;
|
|
61
|
+
secondBus.TargetedBroadcast(ref target, ref message);
|
|
62
|
+
Assert.AreEqual(3, receiver.InvocationCount);
|
|
63
|
+
warningsAfter = CountWarnings(logs);
|
|
64
|
+
Assert.AreEqual(
|
|
65
|
+
warningsBefore + 1,
|
|
66
|
+
warningsAfter,
|
|
67
|
+
"A new bus should emit its own warning."
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
finally
|
|
71
|
+
{
|
|
72
|
+
MessagingDebug.LogFunction = previousLogFunction;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
yield break;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private static int CountWarnings(List<(LogLevel level, string message)> logs)
|
|
79
|
+
{
|
|
80
|
+
return logs.Count(entry =>
|
|
81
|
+
entry.level == LogLevel.Warn
|
|
82
|
+
&& entry.message.IndexOf("ReflexiveMessage dispatch", StringComparison.Ordinal) >= 0
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|