com.wallstop-studios.dxmessaging 2.0.0-rc20 → 2.0.0-rc22
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/.github/workflows/npm-publish.yml +9 -0
- package/Editor/Analyzers/System.Collections.Immutable.dll +0 -0
- package/Editor/Analyzers/System.Collections.Immutable.dll.meta +33 -0
- package/Editor/Analyzers/WallstopStudios.DxMessaging.SourceGenerators.dll +0 -0
- package/Editor/SetupCscRsp.cs +1 -0
- package/README.md +9 -7
- package/Runtime/Core/Helper/DxMessagingRuntime.cs +201 -0
- package/Runtime/Core/Helper/DxMessagingRuntime.cs.meta +3 -0
- package/Runtime/Core/Helper/MessageCache.cs +105 -0
- package/Runtime/Core/Helper/MessageCache.cs.meta +3 -0
- package/Runtime/Core/Helper/MessageHelperIndexer.cs +9 -0
- package/Runtime/Core/Helper/MessageHelperIndexer.cs.meta +3 -0
- package/Runtime/Core/Helper.meta +3 -0
- package/Runtime/Core/IMessage.cs +0 -13
- package/Runtime/Core/MessageBus/IMessageBus.cs +5 -2
- package/Runtime/Core/MessageBus/MessageBus.cs +72 -92
- package/Runtime/Core/MessageHandler.cs +51 -119
- package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/DxMessageIdGenerator.cs +255 -394
- package/package.json +1 -1
|
@@ -29,7 +29,16 @@ jobs:
|
|
|
29
29
|
run: |
|
|
30
30
|
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
|
31
31
|
echo "Manual trigger detected. Skipping version check."
|
|
32
|
+
NEW_VERSION=$(jq -r '.version' package.json)
|
|
33
|
+
echo "NEW_VERSION=$NEW_VERSION" >> $GITHUB_ENV
|
|
32
34
|
echo "should_publish=true" >> $GITHUB_ENV
|
|
35
|
+
if [[ "$NEW_VERSION" == *"rc"* ]]; then
|
|
36
|
+
echo "This is a pre-release (next tag)."
|
|
37
|
+
echo "NPM_TAG=next" >> $GITHUB_ENV
|
|
38
|
+
else
|
|
39
|
+
echo "This is a stable release (latest tag)."
|
|
40
|
+
echo "NPM_TAG=latest" >> $GITHUB_ENV
|
|
41
|
+
fi
|
|
33
42
|
else
|
|
34
43
|
PREV_VERSION=$(git show HEAD~1:package.json | jq -r '.version' || echo "0.0.0")
|
|
35
44
|
NEW_VERSION=$(jq -r '.version' package.json)
|
|
Binary file
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
fileFormatVersion: 2
|
|
2
|
+
guid: 390bb9381fa79fd458906a865ace0e80
|
|
3
|
+
PluginImporter:
|
|
4
|
+
externalObjects: {}
|
|
5
|
+
serializedVersion: 2
|
|
6
|
+
iconMap: {}
|
|
7
|
+
executionOrder: {}
|
|
8
|
+
defineConstraints: []
|
|
9
|
+
isPreloaded: 0
|
|
10
|
+
isOverridable: 1
|
|
11
|
+
isExplicitlyReferenced: 0
|
|
12
|
+
validateReferences: 1
|
|
13
|
+
platformData:
|
|
14
|
+
- first:
|
|
15
|
+
Any:
|
|
16
|
+
second:
|
|
17
|
+
enabled: 0
|
|
18
|
+
settings: {}
|
|
19
|
+
- first:
|
|
20
|
+
Editor: Editor
|
|
21
|
+
second:
|
|
22
|
+
enabled: 1
|
|
23
|
+
settings:
|
|
24
|
+
DefaultValueInitialized: true
|
|
25
|
+
- first:
|
|
26
|
+
Windows Store Apps: WindowsStoreApps
|
|
27
|
+
second:
|
|
28
|
+
enabled: 0
|
|
29
|
+
settings:
|
|
30
|
+
CPU: AnyCPU
|
|
31
|
+
userData:
|
|
32
|
+
assetBundleName:
|
|
33
|
+
assetBundleVariant:
|
|
Binary file
|
package/Editor/SetupCscRsp.cs
CHANGED
|
@@ -36,6 +36,7 @@ namespace DxMessaging.Editor
|
|
|
36
36
|
"Microsoft.CodeAnalysis.CSharp.dll",
|
|
37
37
|
"System.Reflection.Metadata.dll",
|
|
38
38
|
"System.Runtime.CompilerServices.Unsafe.dll",
|
|
39
|
+
"System.Collections.Immutable.dll",
|
|
39
40
|
};
|
|
40
41
|
|
|
41
42
|
private static readonly string LibraryArgument = $"-a:\"{LibraryPathRelative}\"";
|
package/README.md
CHANGED
|
@@ -33,16 +33,18 @@ Check out the latest [Releases](https://github.com/wallstop/DxMessaging/releases
|
|
|
33
33
|
Grab a copy of this repo (either `git clone` both [this repo](https://github.com/wallstop/DxMessaging) *and* [Unity Helpers](https://github.com/wallstop/unity-helpers) or [download a zip of the source](https://github.com/wallstop/DxMessaging/archive/refs/heads/master.zip) and [Unity Helper's source](https://github.com/wallstop/unity-helpers/archive/refs/heads/main.zip)) and copy the contents to your project's `Assets` folder.
|
|
34
34
|
|
|
35
35
|
# Benchmarks
|
|
36
|
-
|
|
36
|
+
In addition to providing a richer feature set, DxMessaging is *faster* than Unity's built in messaging solution. [Source](./Tests/Runtime/Benchmarks/PerformanceTests.cs). It is allocation-free and can be used in hot paths.
|
|
37
|
+
|
|
38
|
+
For UntargetedMessages, DxMessaging is significantly faster (roughly 2x) than Unity.
|
|
37
39
|
|
|
38
40
|
| Message Tech | Operations / Second | Allocations? |
|
|
39
41
|
| ------------ | ------------------- | ------------ |
|
|
40
|
-
| Unity | 2,
|
|
41
|
-
| DxMessaging (GameObject) - Normal | 2,
|
|
42
|
-
| DxMessaging (Component) - Normal | 2,
|
|
43
|
-
| DxMessaging (GameObject) - No-Copy | 2,
|
|
44
|
-
| DxMessaging (Component) - No-Copy | 2,
|
|
45
|
-
| DxMessaging (Untargeted) - No-Copy |
|
|
42
|
+
| Unity | 2,566,400 | Yes |
|
|
43
|
+
| DxMessaging (GameObject) - Normal | 2,726,400 | No |
|
|
44
|
+
| DxMessaging (Component) - Normal | 2,749,000 | No |
|
|
45
|
+
| DxMessaging (GameObject) - No-Copy | 2,883,800 | No |
|
|
46
|
+
| DxMessaging (Component) - No-Copy | 2,855,200 | No |
|
|
47
|
+
| DxMessaging (Untargeted) - No-Copy | 4,499,000 | No |
|
|
46
48
|
|
|
47
49
|
# Functionality
|
|
48
50
|
While not as fast, DxMessaging offers *additional functionality* as compared to Unity's messaging solution.
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
namespace DxMessaging.Core.Helper
|
|
2
|
+
{
|
|
3
|
+
using System;
|
|
4
|
+
using System.Collections.Generic;
|
|
5
|
+
using System.Reflection;
|
|
6
|
+
using Core;
|
|
7
|
+
using Messages;
|
|
8
|
+
#if UNITY_2017_1_OR_NEWER
|
|
9
|
+
using UnityEngine;
|
|
10
|
+
#endif
|
|
11
|
+
public static class DxMessagingRuntime
|
|
12
|
+
{
|
|
13
|
+
public static int TotalMessageTypes { get; private set; }
|
|
14
|
+
|
|
15
|
+
public static bool Initialized
|
|
16
|
+
{
|
|
17
|
+
get
|
|
18
|
+
{
|
|
19
|
+
lock (InitializationLock)
|
|
20
|
+
{
|
|
21
|
+
return _isInitialized;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
private static bool _isInitialized;
|
|
27
|
+
private static readonly object InitializationLock = new();
|
|
28
|
+
|
|
29
|
+
static DxMessagingRuntime()
|
|
30
|
+
{
|
|
31
|
+
Initialize();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
#if UNITY_2017_1_OR_NEWER
|
|
35
|
+
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
|
|
36
|
+
#endif
|
|
37
|
+
public static void Initialize()
|
|
38
|
+
{
|
|
39
|
+
lock (InitializationLock)
|
|
40
|
+
{
|
|
41
|
+
if (_isInitialized)
|
|
42
|
+
{
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
Log(() => "DxMessagingRuntime Initializing...", isError: false);
|
|
47
|
+
|
|
48
|
+
HashSet<Type> uniqueTypes = new();
|
|
49
|
+
List<Type> messageTypes = new();
|
|
50
|
+
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
|
|
51
|
+
|
|
52
|
+
foreach (Assembly assembly in assemblies)
|
|
53
|
+
{
|
|
54
|
+
Type[] types;
|
|
55
|
+
try
|
|
56
|
+
{
|
|
57
|
+
types = assembly.GetTypes();
|
|
58
|
+
}
|
|
59
|
+
catch (ReflectionTypeLoadException ex)
|
|
60
|
+
{
|
|
61
|
+
types = ex.Types;
|
|
62
|
+
}
|
|
63
|
+
catch
|
|
64
|
+
{
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
foreach (Type type in types)
|
|
69
|
+
{
|
|
70
|
+
try
|
|
71
|
+
{
|
|
72
|
+
if (type == null)
|
|
73
|
+
{
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (!typeof(IMessage).IsAssignableFrom(type))
|
|
78
|
+
{
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (type.IsGenericTypeDefinition)
|
|
83
|
+
{
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (uniqueTypes.Add(type))
|
|
88
|
+
{
|
|
89
|
+
messageTypes.Add(type);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch (Exception e)
|
|
93
|
+
{
|
|
94
|
+
Log(
|
|
95
|
+
() =>
|
|
96
|
+
$"Error checking if {type?.FullName} is assignable from IMessage: {e}",
|
|
97
|
+
isError: true
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (uniqueTypes.Add(typeof(IMessage)))
|
|
104
|
+
{
|
|
105
|
+
messageTypes.Add(typeof(IMessage));
|
|
106
|
+
}
|
|
107
|
+
if (uniqueTypes.Add(typeof(ITargetedMessage)))
|
|
108
|
+
{
|
|
109
|
+
messageTypes.Add(typeof(ITargetedMessage));
|
|
110
|
+
}
|
|
111
|
+
if (uniqueTypes.Add(typeof(IBroadcastMessage)))
|
|
112
|
+
{
|
|
113
|
+
messageTypes.Add(typeof(IBroadcastMessage));
|
|
114
|
+
}
|
|
115
|
+
if (uniqueTypes.Add(typeof(IUntargetedMessage)))
|
|
116
|
+
{
|
|
117
|
+
messageTypes.Add(typeof(IUntargetedMessage));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
messageTypes.Sort(
|
|
121
|
+
(a, b) =>
|
|
122
|
+
string.Compare(a.FullName, b.FullName, StringComparison.OrdinalIgnoreCase)
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
TotalMessageTypes = messageTypes.Count;
|
|
126
|
+
Type helperIndexerGenericDef = typeof(MessageHelperIndexer<>);
|
|
127
|
+
|
|
128
|
+
for (int i = 0; i < TotalMessageTypes; ++i)
|
|
129
|
+
{
|
|
130
|
+
Type messageType = messageTypes[i];
|
|
131
|
+
try
|
|
132
|
+
{
|
|
133
|
+
Type specificHelperType = helperIndexerGenericDef.MakeGenericType(
|
|
134
|
+
messageType
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
FieldInfo idField = specificHelperType.GetField(
|
|
138
|
+
"SequentialId",
|
|
139
|
+
BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic
|
|
140
|
+
);
|
|
141
|
+
if (idField != null)
|
|
142
|
+
{
|
|
143
|
+
idField.SetValue(null, i);
|
|
144
|
+
}
|
|
145
|
+
else
|
|
146
|
+
{
|
|
147
|
+
Log(
|
|
148
|
+
() =>
|
|
149
|
+
$"Error: Could not find field for SequentialId on MessageHelperIndexer<{messageType.FullName}>.",
|
|
150
|
+
isError: true
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
catch (Exception ex)
|
|
155
|
+
{
|
|
156
|
+
Log(
|
|
157
|
+
() => $"Error setting SequentialId for {messageType.FullName}: {ex}",
|
|
158
|
+
isError: true
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
_isInitialized = true;
|
|
164
|
+
Log(
|
|
165
|
+
() =>
|
|
166
|
+
$"DxMessagingRuntime Initialized. Found {TotalMessageTypes} message types.",
|
|
167
|
+
isError: false
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private static void Log(Func<string> messageProducer, bool isError)
|
|
173
|
+
{
|
|
174
|
+
try
|
|
175
|
+
{
|
|
176
|
+
string message = messageProducer();
|
|
177
|
+
#if UNITY_2017_1_OR_NEWER
|
|
178
|
+
if (isError)
|
|
179
|
+
{
|
|
180
|
+
Debug.LogError(message);
|
|
181
|
+
}
|
|
182
|
+
else
|
|
183
|
+
{
|
|
184
|
+
Debug.Log(message);
|
|
185
|
+
}
|
|
186
|
+
#else
|
|
187
|
+
Console.WriteLine(message);
|
|
188
|
+
#endif
|
|
189
|
+
}
|
|
190
|
+
catch (Exception e)
|
|
191
|
+
{
|
|
192
|
+
string errorMessage = $"Error logging message: {e}";
|
|
193
|
+
#if UNITY_2017_1_OR_NEWER
|
|
194
|
+
Debug.LogError(errorMessage);
|
|
195
|
+
#else
|
|
196
|
+
Console.WriteLine(errorMessage);
|
|
197
|
+
#endif
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
namespace DxMessaging.Core.Helper
|
|
2
|
+
{
|
|
3
|
+
using System.Collections;
|
|
4
|
+
using System.Collections.Generic;
|
|
5
|
+
|
|
6
|
+
public sealed class MessageCache<TValue> : IEnumerable<TValue>
|
|
7
|
+
where TValue : class, new()
|
|
8
|
+
{
|
|
9
|
+
public struct MessageCacheEnumerator : IEnumerator<TValue>
|
|
10
|
+
{
|
|
11
|
+
private readonly MessageCache<TValue> _cache;
|
|
12
|
+
|
|
13
|
+
private int _index;
|
|
14
|
+
private TValue _current;
|
|
15
|
+
|
|
16
|
+
internal MessageCacheEnumerator(MessageCache<TValue> cache)
|
|
17
|
+
{
|
|
18
|
+
_cache = cache;
|
|
19
|
+
_index = -1;
|
|
20
|
+
_current = default;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public bool MoveNext()
|
|
24
|
+
{
|
|
25
|
+
while (++_index < _cache._values.Length)
|
|
26
|
+
{
|
|
27
|
+
_current = _cache._values[_index];
|
|
28
|
+
if (_current != null)
|
|
29
|
+
{
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
_current = default;
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public TValue Current => _current;
|
|
39
|
+
|
|
40
|
+
object IEnumerator.Current => Current;
|
|
41
|
+
|
|
42
|
+
public void Reset()
|
|
43
|
+
{
|
|
44
|
+
_index = -1;
|
|
45
|
+
_current = default;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
public void Dispose() { }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
private readonly TValue[] _values = new TValue[DxMessagingRuntime.TotalMessageTypes];
|
|
52
|
+
|
|
53
|
+
public TValue GetOrAdd<TMessage>()
|
|
54
|
+
where TMessage : IMessage
|
|
55
|
+
{
|
|
56
|
+
int index = MessageHelperIndexer<TMessage>.SequentialId;
|
|
57
|
+
TValue value = _values[index];
|
|
58
|
+
if (value != null)
|
|
59
|
+
{
|
|
60
|
+
return value;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
value = new TValue();
|
|
64
|
+
_values[index] = value;
|
|
65
|
+
return value;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
public void Set<TMessage>(TValue value)
|
|
69
|
+
where TMessage : IMessage
|
|
70
|
+
{
|
|
71
|
+
int index = MessageHelperIndexer<TMessage>.SequentialId;
|
|
72
|
+
_values[index] = value;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
public bool TryGetValue<TMessage>(out TValue value)
|
|
76
|
+
where TMessage : IMessage
|
|
77
|
+
{
|
|
78
|
+
int index = MessageHelperIndexer<TMessage>.SequentialId;
|
|
79
|
+
value = _values[index];
|
|
80
|
+
return value != null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
public void Remove<TMessage>()
|
|
84
|
+
where TMessage : IMessage
|
|
85
|
+
{
|
|
86
|
+
int index = MessageHelperIndexer<TMessage>.SequentialId;
|
|
87
|
+
_values[index] = null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
public MessageCacheEnumerator GetEnumerator()
|
|
91
|
+
{
|
|
92
|
+
return new MessageCacheEnumerator(this);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
IEnumerator<TValue> IEnumerable<TValue>.GetEnumerator()
|
|
96
|
+
{
|
|
97
|
+
return GetEnumerator();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
IEnumerator IEnumerable.GetEnumerator()
|
|
101
|
+
{
|
|
102
|
+
return GetEnumerator();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
package/Runtime/Core/IMessage.cs
CHANGED
|
@@ -8,18 +8,5 @@
|
|
|
8
8
|
public interface IMessage
|
|
9
9
|
{
|
|
10
10
|
Type MessageType => GetType();
|
|
11
|
-
|
|
12
|
-
/// <summary>
|
|
13
|
-
/// Gets the optimized, unique integer ID for this message type, if available.
|
|
14
|
-
/// Returns null if this type uses the fallback mechanism (e.g., due to a compile-time
|
|
15
|
-
/// hash collision or manual implementation without an assigned ID).
|
|
16
|
-
/// </summary>
|
|
17
|
-
/// <remarks>
|
|
18
|
-
/// The ID is generated at compile-time for attributed types and is stable
|
|
19
|
-
/// across builds assuming the type's fully qualified name does not change.
|
|
20
|
-
/// It facilitates faster dictionary lookups compared to using System.Type directly.
|
|
21
|
-
/// Check for HasValue before using the Value.
|
|
22
|
-
/// </remarks>
|
|
23
|
-
int? OptimizedMessageId => null;
|
|
24
11
|
}
|
|
25
12
|
}
|
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
/// </summary>
|
|
10
10
|
public interface IMessageBus
|
|
11
11
|
{
|
|
12
|
+
public static int GlobalSequentialIndex = -1;
|
|
13
|
+
|
|
14
|
+
public int RegisteredGlobalSequentialIndex { get; }
|
|
12
15
|
public int RegisteredBroadcast { get; }
|
|
13
16
|
|
|
14
17
|
public int RegisteredTargeted { get; }
|
|
@@ -25,7 +28,7 @@
|
|
|
25
28
|
where TMessage : IUntargetedMessage;
|
|
26
29
|
|
|
27
30
|
/// <summary>
|
|
28
|
-
/// Given
|
|
31
|
+
/// Given a Targeted message and its target, determines whether or not it should be processed or skipped.
|
|
29
32
|
/// </summary>
|
|
30
33
|
/// <typeparam name="TMessage">Specific type of message.</typeparam>
|
|
31
34
|
/// <param name="target">Target of the message.</param>
|
|
@@ -38,7 +41,7 @@
|
|
|
38
41
|
where TMessage : ITargetedMessage;
|
|
39
42
|
|
|
40
43
|
/// <summary>
|
|
41
|
-
/// Given
|
|
44
|
+
/// Given a Broadcast message and its source, determines whether or not it should be processed or skipped.
|
|
42
45
|
/// </summary>
|
|
43
46
|
/// <typeparam name="TMessage">Specific type of message.</typeparam>
|
|
44
47
|
/// <param name="source">Source of the message.</param>
|