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.
@@ -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)
@@ -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:
@@ -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
- DxMessaging is currently roughly on-par with than Unity's built in messaging solution (when running in Unity). [Source](./Tests/Runtime/Benchmarks/PerformanceTests.cs). However, it is allocation-free and can be used in hot paths.
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,621,200 | Yes |
41
- | DxMessaging (GameObject) - Normal | 2,048,600 | No |
42
- | DxMessaging (Component) - Normal | 2,054,600 | No |
43
- | DxMessaging (GameObject) - No-Copy | 2,137,800 | No |
44
- | DxMessaging (Component) - No-Copy | 2,136,400 | No |
45
- | DxMessaging (Untargeted) - No-Copy | 3,028,400 | No |
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,3 @@
1
+ fileFormatVersion: 2
2
+ guid: 84077988aa394abb9c9608cb46690a75
3
+ timeCreated: 1745099950
@@ -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
+ }
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: 23cb3bf7f25d479c862736482ee96fa7
3
+ timeCreated: 1745102344
@@ -0,0 +1,9 @@
1
+ namespace DxMessaging.Core.Helper
2
+ {
3
+ public static class MessageHelperIndexer<TMessage>
4
+ where TMessage : IMessage
5
+ {
6
+ // ReSharper disable once StaticMemberInGenericType
7
+ internal static int SequentialId = -1;
8
+ }
9
+ }
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: 612c352c3d984a3486ac4b19bb69fa37
3
+ timeCreated: 1745101032
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: 351ee8f890884099b6c7c32e66f196a9
3
+ timeCreated: 1745100990
@@ -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 an Targeted message and its target, determines whether or not it should be processed or skipped.
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 an Broadcast message and its source, determines whether or not it should be processed or skipped.
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>