From c959e26e9205b1dca7f821db22c3e45751831dc5 Mon Sep 17 00:00:00 2001 From: Vladislav Panin Date: Wed, 27 May 2026 09:49:23 +0300 Subject: [PATCH] fix(bindable-members): eliminate shared singleton mutation in OneTime Get methods Replace the per-type static singleton pattern with fresh instance creation in Get(). The singleton's Value was mutated before being returned, so any caller that held a reference across multiple Get() calls would observe stale or incorrect values. Since OneTime bindings fire exactly once per bind cycle the allocation cost is negligible, and the hazard is completely removed. --- .../Classes/OneTimeBindableMember.cs | 18 +++++------ .../Enum/OneTimeEnumBindableMember.cs | 13 ++++---- .../Struct/OneTimeStructBindableMember.cs | 30 +++++++++---------- 3 files changed, 29 insertions(+), 32 deletions(-) diff --git a/Assets/Plugins/Aspid/MVVM/Source/ViewModels/BindableMembers/Classes/OneTimeBindableMember.cs b/Assets/Plugins/Aspid/MVVM/Source/ViewModels/BindableMembers/Classes/OneTimeBindableMember.cs index 1c67b4ce1..7be204bf7 100644 --- a/Assets/Plugins/Aspid/MVVM/Source/ViewModels/BindableMembers/Classes/OneTimeBindableMember.cs +++ b/Assets/Plugins/Aspid/MVVM/Source/ViewModels/BindableMembers/Classes/OneTimeBindableMember.cs @@ -9,19 +9,20 @@ namespace Aspid.MVVM /// The type of the value to be bound. public sealed class OneTimeBindableMember : OneTimeBindableMember, IReadOnlyValueBindableMember { - private static readonly OneTimeBindableMember _instance = new(); - /// - /// Gets or sets the current value. + /// Gets the current value. /// public T? Value { get; private set; } - + /// /// Gets the binding mode for this member. /// public BindMode Mode => BindMode.OneTime; - private OneTimeBindableMember() { } + private OneTimeBindableMember(T? value) + { + Value = value; + } /// /// @@ -60,18 +61,17 @@ private OneTimeBindableMember() { } } /// - /// Creates a reusable instance and assigns the provided value for one-time binding. + /// Creates a new instance configured with the provided value for one-time binding. /// /// The value to be provided to the binder. - /// A singleton instance of configured with the specified value. + /// A new instance configured with the specified value. public static OneTimeBindableMember Get(T value) { #if UNITY_2022_1_OR_NEWER && !ASPID_MVVM_UNITY_PROFILER_DISABLED using (GetMarker.Auto()) #endif { - _instance.Value = value; - return _instance; + return new(value); } } } diff --git a/Assets/Plugins/Aspid/MVVM/Source/ViewModels/BindableMembers/Enum/OneTimeEnumBindableMember.cs b/Assets/Plugins/Aspid/MVVM/Source/ViewModels/BindableMembers/Enum/OneTimeEnumBindableMember.cs index f918e7381..773f56d2e 100644 --- a/Assets/Plugins/Aspid/MVVM/Source/ViewModels/BindableMembers/Enum/OneTimeEnumBindableMember.cs +++ b/Assets/Plugins/Aspid/MVVM/Source/ViewModels/BindableMembers/Enum/OneTimeEnumBindableMember.cs @@ -10,23 +10,20 @@ namespace Aspid.MVVM public sealed class OneTimeEnumBindableMember : OneTimeStructBindableMember where T : struct, Enum { - private static readonly OneTimeEnumBindableMember _instance = new(); - - private OneTimeEnumBindableMember() { } - + private OneTimeEnumBindableMember(T value) : base(value) { } + /// - /// Creates a reusable instance and assigns the provided enum value for one-time binding. + /// Creates a new instance configured with the provided enum value for one-time binding. /// /// The enum value to provide to the binder. - /// A singleton instance of configured with the specified value. + /// A new instance configured with the specified value. public static OneTimeEnumBindableMember Get(T value) { #if UNITY_2022_1_OR_NEWER && !ASPID_MVVM_UNITY_PROFILER_DISABLED using (GetMarker.Auto()) #endif { - _instance.Value = value; - return _instance; + return new(value); } } } diff --git a/Assets/Plugins/Aspid/MVVM/Source/ViewModels/BindableMembers/Struct/OneTimeStructBindableMember.cs b/Assets/Plugins/Aspid/MVVM/Source/ViewModels/BindableMembers/Struct/OneTimeStructBindableMember.cs index 014e431a9..c00d22fc3 100644 --- a/Assets/Plugins/Aspid/MVVM/Source/ViewModels/BindableMembers/Struct/OneTimeStructBindableMember.cs +++ b/Assets/Plugins/Aspid/MVVM/Source/ViewModels/BindableMembers/Struct/OneTimeStructBindableMember.cs @@ -10,23 +10,20 @@ namespace Aspid.MVVM public sealed class OneTimeStructBindableMember : OneTimeStructBindableMember where T : struct { - private static readonly OneTimeStructBindableMember _instance = new(); - - private OneTimeStructBindableMember() { } - + private OneTimeStructBindableMember(T value) : base(value) { } + /// - /// Creates a reusable instance and assigns the provided value for one-time binding. + /// Creates a new instance configured with the provided value for one-time binding. /// /// The value to be provided to the binder. - /// A singleton instance of configured with the specified value. + /// A new instance configured with the specified value. public static OneTimeStructBindableMember Get(T value) { #if UNITY_2022_1_OR_NEWER && !ASPID_MVVM_UNITY_PROFILER_DISABLED using (GetMarker.Auto()) -#endif +#endif { - _instance.Value = value; - return _instance; + return new(value); } } } @@ -41,16 +38,19 @@ public abstract class OneTimeStructBindableMember : OneTimeStructBind where TBoxed : class { /// - /// Gets or sets the current value. + /// Gets the current value. /// - public T Value { get; private protected set; } - + public T Value { get; } + /// /// Gets the binding mode for this member. /// - public BindMode Mode => BindMode.OneTime; - - private protected OneTimeStructBindableMember() { } + public BindMode Mode => BindMode.OneTime; + + private protected OneTimeStructBindableMember(T value) + { + Value = value; + } /// ///