com.wallstop-studios.dxmessaging 2.1.2 → 2.1.4

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.
Files changed (67) hide show
  1. package/.github/workflows/dotnet-tests.yml +3 -3
  2. package/.github/workflows/prettier-autofix.yml +0 -7
  3. package/.pre-commit-config.yaml +8 -5
  4. package/AGENTS.md +12 -12
  5. package/CONTRIBUTING.md +8 -2
  6. package/Docs/Comparisons.md +5 -5
  7. package/Docs/InterceptorsAndOrdering.md +1 -1
  8. package/Docs/Performance.md +13 -13
  9. package/Docs/QuickReference.md +1 -1
  10. package/Docs/Reference.md +5 -5
  11. package/Editor/Analyzers/WallstopStudios.DxMessaging.SourceGenerators.dll +0 -0
  12. package/Editor/CustomEditors/MessagingComponentEditor.cs +3 -0
  13. package/Editor/DxMessagingEditorInitializer.cs +58 -1
  14. package/Editor/DxMessagingMenu.cs +38 -0
  15. package/Editor/DxMessagingMenu.cs.meta +11 -0
  16. package/Editor/DxMessagingSceneBuildProcessor.cs +81 -0
  17. package/Editor/DxMessagingSceneBuildProcessor.cs.meta +11 -0
  18. package/Editor/Settings/DxMessagingSettings.cs +37 -6
  19. package/Editor/Settings/DxMessagingSettingsProvider.cs +45 -7
  20. package/README.md +1 -1
  21. package/Runtime/Core/Attributes/DxOptionalParameterAttribute.cs +52 -0
  22. package/Runtime/Core/DataStructure/CyclicBuffer.cs +16 -0
  23. package/Runtime/Core/Diagnostics/MessageEmissionData.cs +1 -1
  24. package/Runtime/Core/Diagnostics/MessageRegistrationType.cs +62 -0
  25. package/Runtime/Core/DxMessagingStaticState.cs +108 -0
  26. package/Runtime/Core/DxMessagingStaticState.cs.meta +11 -0
  27. package/Runtime/Core/Extensions/IListExtensions.cs +24 -0
  28. package/Runtime/Core/Extensions/MessageBusExtensions.cs +142 -0
  29. package/Runtime/Core/Helper/MessageCache.cs +16 -0
  30. package/Runtime/Core/Helper/MessageHelperIndexer.cs +77 -0
  31. package/Runtime/Core/InstanceId.cs +86 -0
  32. package/Runtime/Core/MessageBus/DiagnosticsTarget.cs +31 -0
  33. package/Runtime/Core/MessageBus/DiagnosticsTarget.cs.meta +11 -0
  34. package/Runtime/Core/MessageBus/IMessageBus.cs +44 -16
  35. package/Runtime/Core/MessageBus/MessageBus.cs +167 -180
  36. package/Runtime/Core/MessageBus/MessageRegistrationBuilder.cs +44 -0
  37. package/Runtime/Core/MessageBus/MessagingRegistration.cs +60 -2
  38. package/Runtime/Core/MessageBus/RegistrationLog.cs +10 -0
  39. package/Runtime/Core/MessageHandler.cs +107 -6
  40. package/Runtime/Core/MessageRegistrationHandle.cs +59 -0
  41. package/Runtime/Core/MessageRegistrationToken.cs +18 -2
  42. package/Runtime/Core/Messages/ReflexiveMessage.cs +38 -0
  43. package/Runtime/Core/MessagingDebug.cs +16 -1
  44. package/Runtime/Unity/CurrentGlobalMessageBusProvider.cs +4 -0
  45. package/Runtime/Unity/DxMessagingRuntimeInitializer.cs +19 -0
  46. package/Runtime/Unity/DxMessagingRuntimeInitializer.cs.meta +11 -0
  47. package/Runtime/Unity/InitialGlobalMessageBusProvider.cs +4 -0
  48. package/Runtime/Unity/Integrations/Reflex/ReflexRegistrationInstaller.cs +17 -0
  49. package/Runtime/Unity/Integrations/VContainer/VContainerRegistrationExtensions.cs +8 -0
  50. package/Runtime/Unity/Integrations/Zenject/ZenjectRegistrationInstaller.cs +12 -0
  51. package/Runtime/Unity/MessagingComponent.cs +93 -0
  52. package/Samples~/DI/README.md +13 -13
  53. package/Samples~/Mini Combat/README.md +15 -15
  54. package/Samples~/Mini Combat/Walkthrough.md +12 -12
  55. package/Samples~/UI Buttons + Inspector/README.md +4 -4
  56. package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/DxAutoConstructorGenerator.cs +4 -0
  57. package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/DxMessageIdGenerator.cs +4 -0
  58. package/Tests/Runtime/Core/DiagnosticsTests.cs +3 -3
  59. package/Tests/Runtime/Core/DxMessagingStaticStateTests.cs +69 -0
  60. package/Tests/Runtime/Core/DxMessagingStaticStateTests.cs.meta +11 -0
  61. package/Tests/Runtime/Core/Extensions/MessageBusExtensionsTests.cs +12 -31
  62. package/Tests/Runtime/Core/OrderingManyRegistrationsTests.cs +683 -0
  63. package/Tests/Runtime/Core/OrderingManyRegistrationsTests.cs.meta +11 -0
  64. package/package.json +1 -1
  65. package/scripts/fix-eol.js +38 -3
  66. package/Tests/Runtime/WallstopStudios.DxMessaging.Tests.Runtime.csproj +0 -20
  67. package/Tests/Runtime/WallstopStudios.DxMessaging.Tests.Runtime.csproj.meta +0 -7
@@ -2,6 +2,7 @@ namespace DxMessaging.Editor.Settings
2
2
  {
3
3
  #if UNITY_EDITOR
4
4
  using System.Collections.Generic;
5
+ using Core.MessageBus;
5
6
  using UnityEditor;
6
7
  using UnityEngine;
7
8
 
@@ -21,6 +22,11 @@ namespace DxMessaging.Editor.Settings
21
22
  )
22
23
  : base(path, scope) { }
23
24
 
25
+ /// <summary>
26
+ /// Initializes the serialized settings backing store when the settings page is opened.
27
+ /// </summary>
28
+ /// <param name="searchContext">Search text provided by the Project Settings window.</param>
29
+ /// <param name="rootElement">Root visual element for UI Toolkit-based providers.</param>
24
30
  public override void OnActivate(
25
31
  string searchContext,
26
32
  UnityEngine.UIElements.VisualElement rootElement
@@ -29,28 +35,60 @@ namespace DxMessaging.Editor.Settings
29
35
  _messagingSettings = DxMessagingSettings.GetSerializedSettings();
30
36
  }
31
37
 
38
+ /// <summary>
39
+ /// Renders the DxMessaging settings UI and persists any modifications.
40
+ /// </summary>
41
+ /// <param name="searchContext">Search text provided by the Project Settings window.</param>
32
42
  public override void OnGUI(string searchContext)
33
43
  {
34
- EditorGUILayout.PropertyField(
35
- _messagingSettings.FindProperty(
36
- nameof(DxMessagingSettings._enableDiagnosticsInEditor)
37
- ),
38
- new GUIContent("Global Diagnostics Mode")
44
+ SerializedProperty targetsProp = _messagingSettings.FindProperty(
45
+ nameof(DxMessagingSettings._diagnosticsTargets)
39
46
  );
47
+ DiagnosticsTarget currentTargets = (DiagnosticsTarget)targetsProp.enumValueFlag;
48
+ DiagnosticsTarget updatedTargets = (DiagnosticsTarget)
49
+ EditorGUILayout.EnumFlagsField(
50
+ new GUIContent(
51
+ "Diagnostics Targets",
52
+ "Select where global diagnostics should be enabled by default. Combine flags for multiple targets."
53
+ ),
54
+ currentTargets
55
+ );
56
+ if (updatedTargets != currentTargets)
57
+ {
58
+ targetsProp.enumValueFlag = (int)updatedTargets;
59
+ }
40
60
  EditorGUILayout.PropertyField(
41
61
  _messagingSettings.FindProperty(nameof(DxMessagingSettings._messageBufferSize)),
42
- new GUIContent("Message Buffer Size")
62
+ new GUIContent(
63
+ "Message Buffer Size",
64
+ "Number of emissions kept per bus/token when diagnostics mode is active."
65
+ )
66
+ );
67
+ EditorGUILayout.PropertyField(
68
+ _messagingSettings.FindProperty(
69
+ nameof(DxMessagingSettings._suppressDomainReloadWarning)
70
+ ),
71
+ new GUIContent(
72
+ "Suppress Domain Reload Warning",
73
+ "Disable the warning shown when Enter Play Mode Options skips domain reload; DxMessaging still resets its statics."
74
+ )
43
75
  );
44
76
 
45
77
  _messagingSettings.ApplyModifiedProperties();
46
78
  }
47
79
 
48
80
  [SettingsProvider]
81
+ /// <summary>
82
+ /// Factory used by Unity to register the DxMessaging project settings page.
83
+ /// </summary>
84
+ /// <returns>Configured settings provider instance.</returns>
49
85
  public static SettingsProvider CreateDxMessagingSettingsProvider()
50
86
  {
51
87
  DxMessagingSettingsProvider provider = new("Project/DxMessaging")
52
88
  {
53
- keywords = new HashSet<string>(new[] { "DxMessaging", "Diagnostics" }),
89
+ keywords = new HashSet<string>(
90
+ new[] { "DxMessaging", "Diagnostics", "MessageBus", "Targets" }
91
+ ),
54
92
  };
55
93
 
56
94
  return provider;
package/README.md CHANGED
@@ -52,7 +52,7 @@ Need install instructions for Git URLs, scoped registries, or tarballs? Jump to
52
52
 
53
53
  1. **Untargeted** - "Everyone listen!" (pause game, settings changed)
54
54
  1. **Targeted** - "Tell Player to heal" (commands to specific entities)
55
- 1. **Broadcast** - "Enemy took damage" (events others can observe)
55
+ 1. **Broadcast** - "I took damage" (things that happen to _you_ that others can observe)
56
56
 
57
57
  **One line:** It's the event system Unity should have shipped with - type-safe, leak-proof, and actually debuggable. 🚀
58
58
 
@@ -31,30 +31,82 @@ namespace DxMessaging.Core.Attributes
31
31
  /// Optional default value overloads. Values must be compile-time constants and
32
32
  /// will be validated by the source generator against the field type.
33
33
  /// </summary>
34
+ /// <summary>
35
+ /// Initializes the attribute with the specified default boolean value.
36
+ /// </summary>
37
+ /// <param name="value">Default value used when the constructor parameter is omitted.</param>
34
38
  public DxOptionalParameterAttribute(bool value) { }
35
39
 
40
+ /// <summary>
41
+ /// Initializes the attribute with the specified default character value.
42
+ /// </summary>
43
+ /// <param name="value">Default value used when the constructor parameter is omitted.</param>
36
44
  public DxOptionalParameterAttribute(char value) { }
37
45
 
46
+ /// <summary>
47
+ /// Initializes the attribute with the specified default string value.
48
+ /// </summary>
49
+ /// <param name="value">Default value used when the constructor parameter is omitted.</param>
38
50
  public DxOptionalParameterAttribute(string value) { }
39
51
 
52
+ /// <summary>
53
+ /// Initializes the attribute with the specified default byte value.
54
+ /// </summary>
55
+ /// <param name="value">Default value used when the constructor parameter is omitted.</param>
40
56
  public DxOptionalParameterAttribute(byte value) { }
41
57
 
58
+ /// <summary>
59
+ /// Initializes the attribute with the specified default signed byte value.
60
+ /// </summary>
61
+ /// <param name="value">Default value used when the constructor parameter is omitted.</param>
42
62
  public DxOptionalParameterAttribute(sbyte value) { }
43
63
 
64
+ /// <summary>
65
+ /// Initializes the attribute with the specified default short value.
66
+ /// </summary>
67
+ /// <param name="value">Default value used when the constructor parameter is omitted.</param>
44
68
  public DxOptionalParameterAttribute(short value) { }
45
69
 
70
+ /// <summary>
71
+ /// Initializes the attribute with the specified default unsigned short value.
72
+ /// </summary>
73
+ /// <param name="value">Default value used when the constructor parameter is omitted.</param>
46
74
  public DxOptionalParameterAttribute(ushort value) { }
47
75
 
76
+ /// <summary>
77
+ /// Initializes the attribute with the specified default integer value.
78
+ /// </summary>
79
+ /// <param name="value">Default value used when the constructor parameter is omitted.</param>
48
80
  public DxOptionalParameterAttribute(int value) { }
49
81
 
82
+ /// <summary>
83
+ /// Initializes the attribute with the specified default unsigned integer value.
84
+ /// </summary>
85
+ /// <param name="value">Default value used when the constructor parameter is omitted.</param>
50
86
  public DxOptionalParameterAttribute(uint value) { }
51
87
 
88
+ /// <summary>
89
+ /// Initializes the attribute with the specified default long value.
90
+ /// </summary>
91
+ /// <param name="value">Default value used when the constructor parameter is omitted.</param>
52
92
  public DxOptionalParameterAttribute(long value) { }
53
93
 
94
+ /// <summary>
95
+ /// Initializes the attribute with the specified default unsigned long value.
96
+ /// </summary>
97
+ /// <param name="value">Default value used when the constructor parameter is omitted.</param>
54
98
  public DxOptionalParameterAttribute(ulong value) { }
55
99
 
100
+ /// <summary>
101
+ /// Initializes the attribute with the specified default single-precision floating point value.
102
+ /// </summary>
103
+ /// <param name="value">Default value used when the constructor parameter is omitted.</param>
56
104
  public DxOptionalParameterAttribute(float value) { }
57
105
 
106
+ /// <summary>
107
+ /// Initializes the attribute with the specified default double-precision floating point value.
108
+ /// </summary>
109
+ /// <param name="value">Default value used when the constructor parameter is omitted.</param>
58
110
  public DxOptionalParameterAttribute(double value) { }
59
111
 
60
112
  /// <summary>
@@ -29,6 +29,10 @@ namespace DxMessaging.Core.DataStructure
29
29
  _current = default;
30
30
  }
31
31
 
32
+ /// <summary>
33
+ /// Advances the enumerator to the next element in chronological order.
34
+ /// </summary>
35
+ /// <returns><c>true</c> when another element is available; otherwise <c>false</c>.</returns>
32
36
  public bool MoveNext()
33
37
  {
34
38
  if (++_index < _buffer.Count)
@@ -41,16 +45,25 @@ namespace DxMessaging.Core.DataStructure
41
45
  return false;
42
46
  }
43
47
 
48
+ /// <summary>
49
+ /// Gets the element at the current enumerator position.
50
+ /// </summary>
44
51
  public T Current => _current;
45
52
 
46
53
  object IEnumerator.Current => Current;
47
54
 
55
+ /// <summary>
56
+ /// Resets the enumerator to its initial position before the first element.
57
+ /// </summary>
48
58
  public void Reset()
49
59
  {
50
60
  _index = -1;
51
61
  _current = default;
52
62
  }
53
63
 
64
+ /// <summary>
65
+ /// Releases resources held by the enumerator.
66
+ /// </summary>
54
67
  public void Dispose() { }
55
68
  }
56
69
 
@@ -107,6 +120,9 @@ namespace DxMessaging.Core.DataStructure
107
120
  }
108
121
  }
109
122
 
123
+ /// <summary>
124
+ /// Creates an enumerator that iterates from the oldest element to the most recently added.
125
+ /// </summary>
110
126
  public CyclicBufferEnumerator GetEnumerator()
111
127
  {
112
128
  return new CyclicBufferEnumerator(this);
@@ -12,7 +12,7 @@ namespace DxMessaging.Core.Diagnostics
12
12
  /// Captures a snapshot of a message emission for diagnostics.
13
13
  /// </summary>
14
14
  /// <remarks>
15
- /// When diagnostics are enabled (see <see cref="MessageBus.IMessageBus.GlobalDiagnosticsMode"/>),
15
+ /// When diagnostics are enabled (see <see cref="MessageBus.IMessageBus.GlobalDiagnosticsTargets"/>),
16
16
  /// the bus and tokens record recent emissions in ring buffers along with a trimmed stack trace
17
17
  /// that excludes DxMessaging internals for easier debugging.
18
18
  ///
@@ -1,21 +1,83 @@
1
1
  namespace DxMessaging.Core.Diagnostics
2
2
  {
3
+ /// <summary>
4
+ /// Categories used when recording registrations in diagnostics logs.
5
+ /// </summary>
3
6
  public enum MessageRegistrationType
4
7
  {
8
+ /// <summary>
9
+ /// No registration type was captured.
10
+ /// </summary>
5
11
  None = 0,
12
+
13
+ /// <summary>
14
+ /// A targeted handler that listens for messages addressed to a specific <see cref="Core.InstanceId"/>.
15
+ /// </summary>
6
16
  Targeted = 1,
17
+
18
+ /// <summary>
19
+ /// A global untargeted handler that receives all messages of a given type.
20
+ /// </summary>
7
21
  Untargeted = 2,
22
+
23
+ /// <summary>
24
+ /// A broadcast handler that listens for messages emitted from a source <see cref="Core.InstanceId"/>.
25
+ /// </summary>
8
26
  Broadcast = 3,
27
+
28
+ /// <summary>
29
+ /// A broadcast post-processor that runs after broadcast handlers complete.
30
+ /// </summary>
9
31
  BroadcastPostProcessor = 4,
32
+
33
+ /// <summary>
34
+ /// A targeted post-processor that runs after targeted handlers complete.
35
+ /// </summary>
10
36
  TargetedPostProcessor = 5,
37
+
38
+ /// <summary>
39
+ /// A targeted handler that ignores the concrete target during invocation.
40
+ /// </summary>
11
41
  TargetedWithoutTargeting = 6,
42
+
43
+ /// <summary>
44
+ /// A post-processor for handlers registered without a concrete target.
45
+ /// </summary>
12
46
  TargetedWithoutTargetingPostProcessor = 7,
47
+
48
+ /// <summary>
49
+ /// A broadcast handler registered without an explicit source identity.
50
+ /// </summary>
13
51
  BroadcastWithoutSource = 8,
52
+
53
+ /// <summary>
54
+ /// A post-processor for broadcast handlers registered without an explicit source.
55
+ /// </summary>
14
56
  BroadcastWithoutSourcePostProcessor = 9,
57
+
58
+ /// <summary>
59
+ /// A post-processor that runs after untargeted handlers complete.
60
+ /// </summary>
15
61
  UntargetedPostProcessor = 10,
62
+
63
+ /// <summary>
64
+ /// A global catch-all registration that observes every message.
65
+ /// </summary>
16
66
  GlobalAcceptAll = 11,
67
+
68
+ /// <summary>
69
+ /// An untargeted interceptor that can mutate or cancel global messages.
70
+ /// </summary>
17
71
  UntargetedInterceptor = 12,
72
+
73
+ /// <summary>
74
+ /// A targeted interceptor that can mutate or cancel messages bound to a specific recipient.
75
+ /// </summary>
18
76
  TargetedInterceptor = 13,
77
+
78
+ /// <summary>
79
+ /// A broadcast interceptor that can mutate or cancel messages emitted from a source.
80
+ /// </summary>
19
81
  BroadcastInterceptor = 14,
20
82
  }
21
83
  }
@@ -0,0 +1,108 @@
1
+ namespace DxMessaging.Core
2
+ {
3
+ using System;
4
+ using Helper;
5
+ using MessageBus;
6
+
7
+ /// <summary>
8
+ /// Centralised utility for resetting DxMessaging static state when Domain Reload is disabled.
9
+ /// </summary>
10
+ public static class DxMessagingStaticState
11
+ {
12
+ private static readonly object ResetLock = new object();
13
+ private static readonly BaselineState Baseline;
14
+
15
+ static DxMessagingStaticState()
16
+ {
17
+ Baseline = CaptureBaseline();
18
+ }
19
+
20
+ /// <summary>
21
+ /// Resets all static variables in DxMessaging to their default values.
22
+ /// </summary>
23
+ public static void Reset()
24
+ {
25
+ lock (ResetLock)
26
+ {
27
+ MessagingDebug.enabled = Baseline.MessagingDebugEnabled;
28
+ MessagingDebug.LogFunction = Baseline.MessagingDebugLogFunction;
29
+
30
+ IMessageBus.GlobalDiagnosticsTargets = Baseline.GlobalDiagnosticsTargets;
31
+ IMessageBus.GlobalMessageBufferSize = Baseline.GlobalMessageBufferSize;
32
+ IMessageBus.GlobalSequentialIndex = Baseline.GlobalSequentialIndex;
33
+
34
+ MessageHelperIndexer.RestoreState(Baseline.HelperState);
35
+
36
+ MessageRegistrationHandle.SetIdSeed(Baseline.MessageRegistrationHandleSeed);
37
+ MessageRegistrationBuilder.SetSyntheticOwnerCounter(Baseline.SyntheticOwnerCounter);
38
+
39
+ MessageHandler.ResetStatics();
40
+ IMessageBus.GlobalSequentialIndex = Baseline.GlobalSequentialIndex;
41
+ }
42
+ }
43
+
44
+ private static BaselineState CaptureBaseline()
45
+ {
46
+ bool messagingDebugEnabled = MessagingDebug.enabled;
47
+ Action<LogLevel, string> messagingDebugLogFunction = MessagingDebug.LogFunction;
48
+ DiagnosticsTarget globalDiagnosticsTargets = IMessageBus.GlobalDiagnosticsTargets;
49
+ int globalMessageBufferSize = IMessageBus.GlobalMessageBufferSize;
50
+ int globalSequentialIndex = IMessageBus.GlobalSequentialIndex;
51
+ long messageRegistrationHandleSeed = MessageRegistrationHandle.GetCurrentIdSeed();
52
+ int syntheticOwnerCounter = MessageRegistrationBuilder.GetSyntheticOwnerCounter();
53
+ MessageHelperIndexer.MessageHelperIndexerState helperState =
54
+ MessageHelperIndexer.CaptureState();
55
+
56
+ return new BaselineState(
57
+ messagingDebugEnabled,
58
+ messagingDebugLogFunction,
59
+ globalDiagnosticsTargets,
60
+ globalMessageBufferSize,
61
+ globalSequentialIndex,
62
+ messageRegistrationHandleSeed,
63
+ syntheticOwnerCounter,
64
+ helperState
65
+ );
66
+ }
67
+
68
+ private sealed class BaselineState
69
+ {
70
+ internal BaselineState(
71
+ bool messagingDebugEnabled,
72
+ Action<LogLevel, string> messagingDebugLogFunction,
73
+ DiagnosticsTarget globalDiagnosticsTargets,
74
+ int globalMessageBufferSize,
75
+ int globalSequentialIndex,
76
+ long messageRegistrationHandleSeed,
77
+ int syntheticOwnerCounter,
78
+ MessageHelperIndexer.MessageHelperIndexerState helperState
79
+ )
80
+ {
81
+ MessagingDebugEnabled = messagingDebugEnabled;
82
+ MessagingDebugLogFunction = messagingDebugLogFunction;
83
+ GlobalDiagnosticsTargets = globalDiagnosticsTargets;
84
+ GlobalMessageBufferSize = globalMessageBufferSize;
85
+ GlobalSequentialIndex = globalSequentialIndex;
86
+ MessageRegistrationHandleSeed = messageRegistrationHandleSeed;
87
+ SyntheticOwnerCounter = syntheticOwnerCounter;
88
+ HelperState = helperState;
89
+ }
90
+
91
+ internal bool MessagingDebugEnabled { get; }
92
+
93
+ internal Action<LogLevel, string> MessagingDebugLogFunction { get; }
94
+
95
+ internal DiagnosticsTarget GlobalDiagnosticsTargets { get; }
96
+
97
+ internal int GlobalMessageBufferSize { get; }
98
+
99
+ internal int GlobalSequentialIndex { get; }
100
+
101
+ internal long MessageRegistrationHandleSeed { get; }
102
+
103
+ internal int SyntheticOwnerCounter { get; }
104
+
105
+ internal MessageHelperIndexer.MessageHelperIndexerState HelperState { get; }
106
+ }
107
+ }
108
+ }
@@ -0,0 +1,11 @@
1
+ fileFormatVersion: 2
2
+ guid: 2fbeee7b1ae4484f978aba9de68b3d1d
3
+ MonoImporter:
4
+ externalObjects: {}
5
+ serializedVersion: 2
6
+ defaultReferences: []
7
+ executionOrder: 0
8
+ icon: {instanceID: 0}
9
+ userData:
10
+ assetBundleName:
11
+ assetBundleVariant:
@@ -5,6 +5,22 @@ namespace DxMessaging.Core.Extensions
5
5
 
6
6
  internal static class IListExtensions
7
7
  {
8
+ /// <summary>
9
+ /// Rotates the list contents in-place by the specified offset.
10
+ /// </summary>
11
+ /// <typeparam name="T">Element type held by the list.</typeparam>
12
+ /// <param name="list">List to rotate. Must have at least two elements to perform work.</param>
13
+ /// <param name="amount">
14
+ /// Number of positions to rotate. Positive values move elements toward the end (wrapping),
15
+ /// negative values move them toward the start.
16
+ /// </param>
17
+ /// <example>
18
+ /// <code>
19
+ /// var numbers = new List&lt;int&gt; { 1, 2, 3, 4 };
20
+ /// numbers.Shift(1); // numbers becomes { 4, 1, 2, 3 }
21
+ /// numbers.Shift(-2); // numbers becomes { 2, 3, 4, 1 }
22
+ /// </code>
23
+ /// </example>
8
24
  public static void Shift<T>(this IList<T> list, int amount)
9
25
  {
10
26
  if (list is not { Count: > 1 })
@@ -26,6 +42,14 @@ namespace DxMessaging.Core.Extensions
26
42
  Reverse(list, amount, count - 1);
27
43
  }
28
44
 
45
+ /// <summary>
46
+ /// Reverses the order of elements in-place within the inclusive range.
47
+ /// </summary>
48
+ /// <typeparam name="T">Element type held by the list.</typeparam>
49
+ /// <param name="list">List whose segment should be reversed.</param>
50
+ /// <param name="start">Zero-based index of the first element in the range.</param>
51
+ /// <param name="end">Zero-based index of the last element in the range.</param>
52
+ /// <exception cref="ArgumentException">Thrown when the start or end value is outside the list bounds.</exception>
29
53
  public static void Reverse<T>(this IList<T> list, int start, int end)
30
54
  {
31
55
  if (start < 0 || list.Count <= start)