From bdde0e22647c15cc311df596cab691c1fdb87e40 Mon Sep 17 00:00:00 2001 From: ChebanovDD Date: Fri, 4 Aug 2023 13:43:57 +0800 Subject: [PATCH 1/8] Close #53. Fix BindingContextProvider InvalidCastException in Unity 2023. --- .../UITK/BindableUIElements/Uxmls/BindingContextProvider.Uxml.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UITK/BindableUIElements/Uxmls/BindingContextProvider.Uxml.cs b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UITK/BindableUIElements/Uxmls/BindingContextProvider.Uxml.cs index 9ac1b2d..ed5c48b 100644 --- a/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UITK/BindableUIElements/Uxmls/BindingContextProvider.Uxml.cs +++ b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UITK/BindableUIElements/Uxmls/BindingContextProvider.Uxml.cs @@ -13,6 +13,7 @@ partial class BindingContextProvider [System.Serializable] public new class UxmlSerializedData : BindingContextProvider.UxmlSerializedData { + public override object CreateInstance() => new BindingContextProvider(); } #else public new class UxmlTraits : BindingContextProvider.UxmlTraits From 0ce1e26725ee198c1ec6aa2cac6ba553ef44c944 Mon Sep 17 00:00:00 2001 From: Hellfim Date: Thu, 10 Aug 2023 15:46:14 +0300 Subject: [PATCH 2/8] Fixed typo in method name --- .../Runtime/UITK/BindableUIElements/BindableScrollView.T.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UITK/BindableUIElements/BindableScrollView.T.cs b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UITK/BindableUIElements/BindableScrollView.T.cs index d5b9091..ac115a2 100644 --- a/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UITK/BindableUIElements/BindableScrollView.T.cs +++ b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UITK/BindableUIElements/BindableScrollView.T.cs @@ -35,7 +35,7 @@ public abstract partial class BindableScrollView : public virtual void Initialize() { _itemAssets = new Dictionary(); - _itemAssetsPool = new ObjectPool(OnPoolInstantiateItem, actionOnRelease: OnPooReleaseItem, + _itemAssetsPool = new ObjectPool(OnPoolInstantiateItem, actionOnRelease: OnPoolReleaseItem, actionOnDestroy: OnPoolDestroyItem); } @@ -215,7 +215,7 @@ private VisualElement OnPoolInstantiateItem() return MakeItem(_itemTemplate); } - private void OnPooReleaseItem(VisualElement item) + private void OnPoolReleaseItem(VisualElement item) { item.RemoveFromHierarchy(); } @@ -225,4 +225,4 @@ private void OnPoolDestroyItem(VisualElement item) item.DisposeBindableElement(_objectProvider); } } -} \ No newline at end of file +} From 3b8644ef8844bddacfbf5bc029aafdddda5d2ec5 Mon Sep 17 00:00:00 2001 From: ChebanovDD Date: Tue, 15 Aug 2023 10:08:31 +0800 Subject: [PATCH 3/8] Bump package version. --- .../Assets/Plugins/UnityMvvmToolkit/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/package.json b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/package.json index 72abc63..0fbce43 100644 --- a/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/package.json +++ b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/package.json @@ -2,7 +2,7 @@ "name": "com.chebanovdd.unitymvvmtoolkit", "displayName": "Unity MVVM Toolkit", "author": { "name": "ChebanovDD", "url": "https://github.com/ChebanovDD" }, - "version": "1.1.7", + "version": "1.1.8", "unity": "2018.4", "description": "The Unity Mvvm Toolkit allows you to use data binding to establish a connection between the app UI and the data it displays. This is a simple and consistent way to achieve clean separation of business logic from UI.", "keywords": [ "mvvm", "binding", "ui", "toolkit" ], From b94d487d0fee9d51f45ad3e08d3a5fa4c4bdce4a Mon Sep 17 00:00:00 2001 From: Dima Date: Sun, 24 Sep 2023 11:31:02 +0800 Subject: [PATCH 4/8] Update readme file. --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 3fc74ba..b1e5d3a 100644 --- a/README.md +++ b/README.md @@ -1017,14 +1017,6 @@ The `BindableListView` control is the most efficient way to create lists. It use The following example demonstrates how to bind to a collection of users with `BindableListView`. -Create a main `UI Document` named `UsersView.uxml` with the following content. - -```xml - - - -``` - Create a `UI Document` named `UserItemView.uxml` for the individual items in the list. ```xml @@ -1056,7 +1048,7 @@ public class UserItemViewModel : ICollectionItem } ``` -Create a `UserListView` that inherits the `BindableListViewWrapper` abstract class. +Create a `UserListView` that inherits the `BindableListView` abstract class. ```csharp public class UserListView : BindableListView @@ -1086,7 +1078,7 @@ public class UsersViewModel : IBindableContext } ``` -Create a `UsersView` with the following content. +Create a `UsersView` as follows. ```csharp public class UsersView : DocumentView @@ -1103,6 +1095,14 @@ public class UsersView : DocumentView } ``` +Finally, create a main `UI Document` named `UsersView.uxml` with the following content. + +```xml + + + +``` + #### BindableScrollView The `BindableScrollView` has the same binding logic as the `BindableListView`. It does not use virtualization and creates VisualElements for all items regardless of visibility. From 435badc25df933ae01ec529407e702bd7424375b Mon Sep 17 00:00:00 2001 From: ChebanovDD Date: Sat, 11 Nov 2023 15:27:59 +0800 Subject: [PATCH 5/8] Add ItemTemplate selection directly in the UI Builder for Unity 2023. --- .../BindableListView.TItem.TCollection.cs | 13 ++++++++++++- .../UITK/BindableUIElements/BindableScrollView.T.cs | 13 ++++++++++++- .../BindableListView.TItem.TCollection.Uxml.cs | 11 +++++++---- .../Uxmls/BindableScrollView.T.Uxml.cs | 11 +++++++---- 4 files changed, 38 insertions(+), 10 deletions(-) diff --git a/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UITK/BindableUIElements/BindableListView.TItem.TCollection.cs b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UITK/BindableUIElements/BindableListView.TItem.TCollection.cs index 5b04477..d7d1ea2 100644 --- a/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UITK/BindableUIElements/BindableListView.TItem.TCollection.cs +++ b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UITK/BindableUIElements/BindableListView.TItem.TCollection.cs @@ -54,7 +54,7 @@ public virtual void SetBindingContext(IBindingContext context, IObjectProvider o } _itemsSourceBindingData ??= BindingItemsSourcePath.ToPropertyBindingData(); - _itemTemplate ??= objectProvider.GetCollectionItemTemplate(); + _itemTemplate ??= GetItemTemplate(objectProvider); _objectProvider = objectProvider; @@ -151,5 +151,16 @@ private void OnUnbindItem(VisualElement item, int index) UnbindItem(item, index, itemBindingContext, _objectProvider); } } + + private VisualTreeAsset GetItemTemplate(IObjectProvider objectProvider) + { +#if UNITY_2023_2_OR_NEWER + return ItemTemplate + ? ItemTemplate + : objectProvider.GetCollectionItemTemplate(); +#else + return objectProvider.GetCollectionItemTemplate(); +#endif + } } } \ No newline at end of file diff --git a/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UITK/BindableUIElements/BindableScrollView.T.cs b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UITK/BindableUIElements/BindableScrollView.T.cs index ac115a2..9cf0205 100644 --- a/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UITK/BindableUIElements/BindableScrollView.T.cs +++ b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UITK/BindableUIElements/BindableScrollView.T.cs @@ -62,7 +62,7 @@ public virtual void SetBindingContext(IBindingContext context, IObjectProvider o } _itemsSourceBindingData ??= BindingItemsSourcePath.ToPropertyBindingData(); - _itemTemplate ??= objectProvider.GetCollectionItemTemplate(); + _itemTemplate ??= GetItemTemplate(objectProvider); _objectProvider = objectProvider; @@ -224,5 +224,16 @@ private void OnPoolDestroyItem(VisualElement item) { item.DisposeBindableElement(_objectProvider); } + + private VisualTreeAsset GetItemTemplate(IObjectProvider objectProvider) + { +#if UNITY_2023_2_OR_NEWER + return ItemTemplate + ? ItemTemplate + : objectProvider.GetCollectionItemTemplate(); +#else + return objectProvider.GetCollectionItemTemplate(); +#endif + } } } diff --git a/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UITK/BindableUIElements/Uxmls/BindableListView.TItem.TCollection.Uxml.cs b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UITK/BindableUIElements/Uxmls/BindableListView.TItem.TCollection.Uxml.cs index 42807ec..15c538b 100644 --- a/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UITK/BindableUIElements/Uxmls/BindableListView.TItem.TCollection.Uxml.cs +++ b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UITK/BindableUIElements/Uxmls/BindableListView.TItem.TCollection.Uxml.cs @@ -6,23 +6,26 @@ namespace UnityMvvmToolkit.UITK.BindableUIElements partial class BindableListView { public string BindingItemsSourcePath { get; private set; } + public VisualTreeAsset ItemTemplate { get; private set; } #if UNITY_2023_2_OR_NEWER [System.Serializable] public new class UxmlSerializedData : ListView.UxmlSerializedData { - // ReSharper disable once InconsistentNaming #pragma warning disable 649 + // ReSharper disable once InconsistentNaming [UnityEngine.SerializeField] private string BindingItemsSourcePath; + // ReSharper disable once InconsistentNaming + [UnityEngine.SerializeField] private VisualTreeAsset ItemTemplate; #pragma warning restore 649 public override void Deserialize(object visualElement) { base.Deserialize(visualElement); - visualElement - .As>() - .BindingItemsSourcePath = BindingItemsSourcePath; + var bindableListView = visualElement.As>(); + bindableListView.BindingItemsSourcePath = BindingItemsSourcePath; + bindableListView.ItemTemplate = ItemTemplate; } } #else diff --git a/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UITK/BindableUIElements/Uxmls/BindableScrollView.T.Uxml.cs b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UITK/BindableUIElements/Uxmls/BindableScrollView.T.Uxml.cs index a1cdcac..03247aa 100644 --- a/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UITK/BindableUIElements/Uxmls/BindableScrollView.T.Uxml.cs +++ b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UITK/BindableUIElements/Uxmls/BindableScrollView.T.Uxml.cs @@ -6,23 +6,26 @@ namespace UnityMvvmToolkit.UITK.BindableUIElements partial class BindableScrollView { public string BindingItemsSourcePath { get; private set; } + public VisualTreeAsset ItemTemplate { get; private set; } #if UNITY_2023_2_OR_NEWER [System.Serializable] public new class UxmlSerializedData : ScrollView.UxmlSerializedData { - // ReSharper disable once InconsistentNaming #pragma warning disable 649 + // ReSharper disable once InconsistentNaming [UnityEngine.SerializeField] private string BindingItemsSourcePath; + // ReSharper disable once InconsistentNaming + [UnityEngine.SerializeField] private VisualTreeAsset ItemTemplate; #pragma warning restore 649 public override void Deserialize(object visualElement) { base.Deserialize(visualElement); - visualElement - .As>() - .BindingItemsSourcePath = BindingItemsSourcePath; + var bindableListView = visualElement.As>(); + bindableListView.BindingItemsSourcePath = BindingItemsSourcePath; + bindableListView.ItemTemplate = ItemTemplate; } } #else From a29d2ea7e9aa68cb4c96c2a65c81595db89035fb Mon Sep 17 00:00:00 2001 From: ChebanovDD Date: Sat, 11 Nov 2023 15:29:53 +0800 Subject: [PATCH 6/8] Close #60. Fix property wrappers issue. --- .../Internal/Helpers/HashCodeHelper.cs | 7 +- .../ObjectHandlers/ObjectWrapperHandler.cs | 110 +++++++++++------- .../Core/Internal/Helpers/HashCodeHelper.cs | 7 +- .../ObjectHandlers/ObjectWrapperHandler.cs | 110 +++++++++++------- .../BindingContextObjectProviderTests.cs | 37 +++++- .../TestBindingContext/MyBindingContext.cs | 4 +- .../ObjectWrapperHandlerTests.cs | 32 +++-- 7 files changed, 196 insertions(+), 111 deletions(-) diff --git a/src/UnityMvvmToolkit.Core/Internal/Helpers/HashCodeHelper.cs b/src/UnityMvvmToolkit.Core/Internal/Helpers/HashCodeHelper.cs index fa75782..e072e46 100644 --- a/src/UnityMvvmToolkit.Core/Internal/Helpers/HashCodeHelper.cs +++ b/src/UnityMvvmToolkit.Core/Internal/Helpers/HashCodeHelper.cs @@ -27,12 +27,7 @@ public static int GetMemberHashCode(Type contextType, string memberName) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int GetPropertyConverterHashCode(IPropertyValueConverter converter, string converterName = null) { - var targetTypeHash = converter.TargetType.GetHashCode(); - var sourceTypeHash = converter.SourceType.GetHashCode(); - - return string.IsNullOrWhiteSpace(converterName) - ? CombineHashCode(targetTypeHash, sourceTypeHash) - : CombineHashCode(converterName.GetHashCode(), targetTypeHash, sourceTypeHash); + return GetPropertyWrapperConverterId(converter.TargetType, converter.SourceType, converterName); } /// diff --git a/src/UnityMvvmToolkit.Core/Internal/ObjectHandlers/ObjectWrapperHandler.cs b/src/UnityMvvmToolkit.Core/Internal/ObjectHandlers/ObjectWrapperHandler.cs index d1f3650..ad1a6cc 100644 --- a/src/UnityMvvmToolkit.Core/Internal/ObjectHandlers/ObjectWrapperHandler.cs +++ b/src/UnityMvvmToolkit.Core/Internal/ObjectHandlers/ObjectWrapperHandler.cs @@ -14,6 +14,8 @@ namespace UnityMvvmToolkit.Core.Internal.ObjectHandlers { internal sealed class ObjectWrapperHandler : IDisposable { + private static readonly int ReadOnlyPropertyHashCode = typeof(IReadOnlyProperty<>).GetHashCode(); + private readonly ValueConverterHandler _valueConverterHandler; private readonly Dictionary _commandWrappers; @@ -77,22 +79,18 @@ public TProperty GetPropertyAs(IBindingContext context, M var converterId = HashCodeHelper.GetPropertyWrapperConverterId(targetType, sourceType); - if (_wrappersByConverter.TryGetValue(converterId, out var propertyWrappers)) - { - if (propertyWrappers.Count > 0) - { - return (TProperty) propertyWrappers - .Dequeue() - .AsPropertyWrapper() - .SetProperty(property); - } - } - else + var isProperty = property is IProperty; + + var wrapperId = isProperty ? converterId : GetReadOnlyWrapperId(converterId); + + if (TryGetObjectWrapper(wrapperId, out var objectWrapper)) { - _wrappersByConverter.Add(converterId, new Queue()); + return (TProperty) objectWrapper + .AsPropertyWrapper() + .SetProperty(property); } - var wrapperType = property is IProperty + var wrapperType = isProperty ? typeof(PropertyCastWrapper<,>).MakeGenericType(sourceType, targetType) : typeof(ReadOnlyPropertyCastWrapper<,>).MakeGenericType(sourceType, targetType); @@ -115,19 +113,15 @@ public TProperty GetProperty(IBindingContext context, Bin var converterId = HashCodeHelper.GetPropertyWrapperConverterId(targetType, sourceType, bindingData.ConverterName); - if (_wrappersByConverter.TryGetValue(converterId, out var propertyWrappers)) - { - if (propertyWrappers.Count > 0) - { - return (TProperty) propertyWrappers - .Dequeue() - .AsPropertyWrapper() - .SetProperty(property); - } - } - else + var isProperty = property is IProperty; + + var wrapperId = isProperty ? converterId : GetReadOnlyWrapperId(converterId); + + if (TryGetObjectWrapper(wrapperId, out var objectWrapper)) { - _wrappersByConverter.Add(converterId, new Queue()); + return (TProperty) objectWrapper + .AsPropertyWrapper() + .SetProperty(property); } if (_valueConverterHandler.TryGetValueConverterById(converterId, out var valueConverter) == false) @@ -138,7 +132,7 @@ public TProperty GetProperty(IBindingContext context, Bin var args = new object[] { valueConverter }; - var wrapperType = property is IProperty + var wrapperType = isProperty ? typeof(PropertyConvertWrapper<,>).MakeGenericType(sourceType, targetType) : typeof(ReadOnlyPropertyConvertWrapper<,>).MakeGenericType(sourceType, targetType); @@ -177,20 +171,12 @@ public ICommandWrapper GetCommandWrapper(IBindingContext context, CommandBinding var converterId = HashCodeHelper.GetCommandWrapperConverterId(commandValueType, bindingData.ConverterName); - if (_wrappersByConverter.TryGetValue(converterId, out var commandWrappers)) + if (TryGetObjectWrapper(converterId, out var objectWrapper)) { - if (commandWrappers.Count > 0) - { - return commandWrappers - .Dequeue() - .AsCommandWrapper() - .SetCommand(commandId, command) - .RegisterParameter(bindingData.ElementId, bindingData.ParameterValue); - } - } - else - { - _wrappersByConverter.Add(converterId, new Queue()); + return objectWrapper + .AsCommandWrapper() + .SetCommand(commandId, command) + .RegisterParameter(bindingData.ElementId, bindingData.ParameterValue); } if (_valueConverterHandler.TryGetValueConverterById(converterId, out var valueConverter) == false) @@ -215,7 +201,7 @@ public void ReturnProperty(IPropertyWrapper propertyWrapper) { AssureIsNotDisposed(); - ReturnWrapper(propertyWrapper); + ReturnObjectWrapper(propertyWrapper); } public void ReturnCommandWrapper(ICommandWrapper commandWrapper, int elementId) @@ -228,7 +214,8 @@ public void ReturnCommandWrapper(ICommandWrapper commandWrapper, int elementId) } _commandWrappers.Remove(commandWrapper.CommandId); - ReturnWrapper(commandWrapper); + + ReturnObjectWrapper(commandWrapper); } public void Dispose() @@ -347,10 +334,41 @@ private void CreateParameterValueConverterInstances(int converterId, IParameterV } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ReturnWrapper(IObjectWrapper wrapper) + private bool TryGetObjectWrapper(int wrapperId, out IObjectWrapper objectWrapper) + { + if (_wrappersByConverter.TryGetValue(wrapperId, out var objectWrappers)) + { + if (objectWrappers.Count > 0) + { + objectWrapper = objectWrappers.Dequeue(); + return true; + } + } + else + { + _wrappersByConverter.Add(wrapperId, new Queue()); + } + + objectWrapper = default; + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ReturnObjectWrapper(IObjectWrapper wrapper) { wrapper.Reset(); - _wrappersByConverter[wrapper.ConverterId].Enqueue(wrapper); + + switch (wrapper) + { + case IProperty: + case ICommandWrapper: + _wrappersByConverter[wrapper.ConverterId].Enqueue(wrapper); + break; + + default: + _wrappersByConverter[GetReadOnlyWrapperId(wrapper.ConverterId)].Enqueue(wrapper); + break; + } } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -361,5 +379,11 @@ private void AssureIsNotDisposed() throw new ObjectDisposedException(nameof(ObjectWrapperHandler)); } } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int GetReadOnlyWrapperId(int wrapperConverterId) + { + return HashCodeHelper.CombineHashCode(wrapperConverterId, ReadOnlyPropertyHashCode); + } } } \ No newline at end of file diff --git a/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/Core/Internal/Helpers/HashCodeHelper.cs b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/Core/Internal/Helpers/HashCodeHelper.cs index fa75782..e072e46 100644 --- a/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/Core/Internal/Helpers/HashCodeHelper.cs +++ b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/Core/Internal/Helpers/HashCodeHelper.cs @@ -27,12 +27,7 @@ public static int GetMemberHashCode(Type contextType, string memberName) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int GetPropertyConverterHashCode(IPropertyValueConverter converter, string converterName = null) { - var targetTypeHash = converter.TargetType.GetHashCode(); - var sourceTypeHash = converter.SourceType.GetHashCode(); - - return string.IsNullOrWhiteSpace(converterName) - ? CombineHashCode(targetTypeHash, sourceTypeHash) - : CombineHashCode(converterName.GetHashCode(), targetTypeHash, sourceTypeHash); + return GetPropertyWrapperConverterId(converter.TargetType, converter.SourceType, converterName); } /// diff --git a/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/Core/Internal/ObjectHandlers/ObjectWrapperHandler.cs b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/Core/Internal/ObjectHandlers/ObjectWrapperHandler.cs index d1f3650..ad1a6cc 100644 --- a/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/Core/Internal/ObjectHandlers/ObjectWrapperHandler.cs +++ b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/Core/Internal/ObjectHandlers/ObjectWrapperHandler.cs @@ -14,6 +14,8 @@ namespace UnityMvvmToolkit.Core.Internal.ObjectHandlers { internal sealed class ObjectWrapperHandler : IDisposable { + private static readonly int ReadOnlyPropertyHashCode = typeof(IReadOnlyProperty<>).GetHashCode(); + private readonly ValueConverterHandler _valueConverterHandler; private readonly Dictionary _commandWrappers; @@ -77,22 +79,18 @@ public TProperty GetPropertyAs(IBindingContext context, M var converterId = HashCodeHelper.GetPropertyWrapperConverterId(targetType, sourceType); - if (_wrappersByConverter.TryGetValue(converterId, out var propertyWrappers)) - { - if (propertyWrappers.Count > 0) - { - return (TProperty) propertyWrappers - .Dequeue() - .AsPropertyWrapper() - .SetProperty(property); - } - } - else + var isProperty = property is IProperty; + + var wrapperId = isProperty ? converterId : GetReadOnlyWrapperId(converterId); + + if (TryGetObjectWrapper(wrapperId, out var objectWrapper)) { - _wrappersByConverter.Add(converterId, new Queue()); + return (TProperty) objectWrapper + .AsPropertyWrapper() + .SetProperty(property); } - var wrapperType = property is IProperty + var wrapperType = isProperty ? typeof(PropertyCastWrapper<,>).MakeGenericType(sourceType, targetType) : typeof(ReadOnlyPropertyCastWrapper<,>).MakeGenericType(sourceType, targetType); @@ -115,19 +113,15 @@ public TProperty GetProperty(IBindingContext context, Bin var converterId = HashCodeHelper.GetPropertyWrapperConverterId(targetType, sourceType, bindingData.ConverterName); - if (_wrappersByConverter.TryGetValue(converterId, out var propertyWrappers)) - { - if (propertyWrappers.Count > 0) - { - return (TProperty) propertyWrappers - .Dequeue() - .AsPropertyWrapper() - .SetProperty(property); - } - } - else + var isProperty = property is IProperty; + + var wrapperId = isProperty ? converterId : GetReadOnlyWrapperId(converterId); + + if (TryGetObjectWrapper(wrapperId, out var objectWrapper)) { - _wrappersByConverter.Add(converterId, new Queue()); + return (TProperty) objectWrapper + .AsPropertyWrapper() + .SetProperty(property); } if (_valueConverterHandler.TryGetValueConverterById(converterId, out var valueConverter) == false) @@ -138,7 +132,7 @@ public TProperty GetProperty(IBindingContext context, Bin var args = new object[] { valueConverter }; - var wrapperType = property is IProperty + var wrapperType = isProperty ? typeof(PropertyConvertWrapper<,>).MakeGenericType(sourceType, targetType) : typeof(ReadOnlyPropertyConvertWrapper<,>).MakeGenericType(sourceType, targetType); @@ -177,20 +171,12 @@ public ICommandWrapper GetCommandWrapper(IBindingContext context, CommandBinding var converterId = HashCodeHelper.GetCommandWrapperConverterId(commandValueType, bindingData.ConverterName); - if (_wrappersByConverter.TryGetValue(converterId, out var commandWrappers)) + if (TryGetObjectWrapper(converterId, out var objectWrapper)) { - if (commandWrappers.Count > 0) - { - return commandWrappers - .Dequeue() - .AsCommandWrapper() - .SetCommand(commandId, command) - .RegisterParameter(bindingData.ElementId, bindingData.ParameterValue); - } - } - else - { - _wrappersByConverter.Add(converterId, new Queue()); + return objectWrapper + .AsCommandWrapper() + .SetCommand(commandId, command) + .RegisterParameter(bindingData.ElementId, bindingData.ParameterValue); } if (_valueConverterHandler.TryGetValueConverterById(converterId, out var valueConverter) == false) @@ -215,7 +201,7 @@ public void ReturnProperty(IPropertyWrapper propertyWrapper) { AssureIsNotDisposed(); - ReturnWrapper(propertyWrapper); + ReturnObjectWrapper(propertyWrapper); } public void ReturnCommandWrapper(ICommandWrapper commandWrapper, int elementId) @@ -228,7 +214,8 @@ public void ReturnCommandWrapper(ICommandWrapper commandWrapper, int elementId) } _commandWrappers.Remove(commandWrapper.CommandId); - ReturnWrapper(commandWrapper); + + ReturnObjectWrapper(commandWrapper); } public void Dispose() @@ -347,10 +334,41 @@ private void CreateParameterValueConverterInstances(int converterId, IParameterV } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ReturnWrapper(IObjectWrapper wrapper) + private bool TryGetObjectWrapper(int wrapperId, out IObjectWrapper objectWrapper) + { + if (_wrappersByConverter.TryGetValue(wrapperId, out var objectWrappers)) + { + if (objectWrappers.Count > 0) + { + objectWrapper = objectWrappers.Dequeue(); + return true; + } + } + else + { + _wrappersByConverter.Add(wrapperId, new Queue()); + } + + objectWrapper = default; + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ReturnObjectWrapper(IObjectWrapper wrapper) { wrapper.Reset(); - _wrappersByConverter[wrapper.ConverterId].Enqueue(wrapper); + + switch (wrapper) + { + case IProperty: + case ICommandWrapper: + _wrappersByConverter[wrapper.ConverterId].Enqueue(wrapper); + break; + + default: + _wrappersByConverter[GetReadOnlyWrapperId(wrapper.ConverterId)].Enqueue(wrapper); + break; + } } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -361,5 +379,11 @@ private void AssureIsNotDisposed() throw new ObjectDisposedException(nameof(ObjectWrapperHandler)); } } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int GetReadOnlyWrapperId(int wrapperConverterId) + { + return HashCodeHelper.CombineHashCode(wrapperConverterId, ReadOnlyPropertyHashCode); + } } } \ No newline at end of file diff --git a/tests/UnityMvvmToolkit.Test.Integration/BindingContextObjectProviderTests.cs b/tests/UnityMvvmToolkit.Test.Integration/BindingContextObjectProviderTests.cs index aed0b21..339156c 100644 --- a/tests/UnityMvvmToolkit.Test.Integration/BindingContextObjectProviderTests.cs +++ b/tests/UnityMvvmToolkit.Test.Integration/BindingContextObjectProviderTests.cs @@ -260,6 +260,41 @@ public void RentProperty_ShouldReturnProperty_WhenConverterIsSet() countProperty.Value.Should().Be(countValue.ToString()); } + [Fact] + public void RentProperty_ShouldReturnValidPropertyWrapper() + { + // Arrange + const int countValue = 69; + + var objectProvider = new BindingContextObjectProvider(new IValueConverter[] + { + new IntToStrConverter() + }); + + var bindingContext = new MyBindingContext(intValue: countValue) + { + Count = countValue + }; + + var countPropertyBindingData = nameof(MyBindingContext.Count).ToPropertyBindingData(); + var countReadOnlyPropertyBindingData = nameof(MyBindingContext.IntReadOnlyProperty).ToPropertyBindingData(); + + // Act + var countProperty = objectProvider.RentProperty(bindingContext, countPropertyBindingData); + var countReadOnlyProperty = objectProvider.RentReadOnlyProperty(bindingContext, countReadOnlyPropertyBindingData); + + objectProvider.ReturnReadOnlyProperty(countReadOnlyProperty); + objectProvider.ReturnProperty(countProperty); + + countProperty = objectProvider.RentProperty(bindingContext, countPropertyBindingData); + countReadOnlyProperty = objectProvider.RentReadOnlyProperty(bindingContext, countReadOnlyPropertyBindingData); + + // Assert + + countProperty.Value.Should().Be(countValue.ToString()); + countReadOnlyProperty.Value.Should().Be(countValue.ToString()); + } + [Fact] public void RentProperty_ShouldConvertPropertyValue_WhenSourceAndTargetTypesMatch() { @@ -303,7 +338,7 @@ public void RentProperty_ShouldReturnReadOnlyProperty_WhenPropertyInstanceIsNotR var bindingContext = new MyBindingContext(intValue: intValue); - var intPropertyBindingData = nameof(MyBindingContext.IntReadOnlyProperty).ToPropertyBindingData(); + var intPropertyBindingData = nameof(MyBindingContext.IntFakeReadOnlyProperty).ToPropertyBindingData(); // Act IReadOnlyProperty intProperty = objectProvider.RentProperty(bindingContext, intPropertyBindingData); diff --git a/tests/UnityMvvmToolkit.Test.Integration/TestBindingContext/MyBindingContext.cs b/tests/UnityMvvmToolkit.Test.Integration/TestBindingContext/MyBindingContext.cs index ac124ca..be319a6 100644 --- a/tests/UnityMvvmToolkit.Test.Integration/TestBindingContext/MyBindingContext.cs +++ b/tests/UnityMvvmToolkit.Test.Integration/TestBindingContext/MyBindingContext.cs @@ -32,7 +32,8 @@ public MyBindingContext(string title = "Title", int intValue = default) _boolCommand = new Command(value => BoolValue = value); Title = new ReadOnlyProperty(title); - IntReadOnlyProperty = new Property(intValue); + IntReadOnlyProperty = new ReadOnlyProperty(intValue); + IntFakeReadOnlyProperty = new Property(intValue); FieldCommand = new Command(default); @@ -56,6 +57,7 @@ public int Count public IReadOnlyProperty Title { get; } public IReadOnlyProperty IntReadOnlyProperty { get; } + public IReadOnlyProperty IntFakeReadOnlyProperty { get; } public ICommand FieldCommand; public ICommand IncrementCommand { get; } diff --git a/tests/UnityMvvmToolkit.Test.Unit/ObjectWrapperHandlerTests.cs b/tests/UnityMvvmToolkit.Test.Unit/ObjectWrapperHandlerTests.cs index 35ad3ab..d3bb470 100644 --- a/tests/UnityMvvmToolkit.Test.Unit/ObjectWrapperHandlerTests.cs +++ b/tests/UnityMvvmToolkit.Test.Unit/ObjectWrapperHandlerTests.cs @@ -1,5 +1,6 @@ using FluentAssertions; using NSubstitute; +using UnityMvvmToolkit.Core; using UnityMvvmToolkit.Core.Converters.ParameterValueConverters; using UnityMvvmToolkit.Core.Converters.PropertyValueConverters; using UnityMvvmToolkit.Core.Enums; @@ -7,6 +8,7 @@ using UnityMvvmToolkit.Core.Internal.Helpers; using UnityMvvmToolkit.Core.Internal.Interfaces; using UnityMvvmToolkit.Core.Internal.ObjectHandlers; +using UnityMvvmToolkit.Core.Internal.ObjectWrappers; namespace UnityMvvmToolkit.Test.Unit; @@ -35,8 +37,9 @@ public void CreateValueConverterInstances_ShouldCreatePropertyWrapperWithConvert // Arrange var converterId = HashCodeHelper.GetPropertyWrapperConverterId(_intToStrConverter); - var propertyWrapper = Substitute.For(); - propertyWrapper.ConverterId.Returns(converterId); + var propertyWrapper = new PropertyConvertWrapper(_intToStrConverter) + .SetProperty(new Property()) + .SetConverterId(converterId); var objectWrapperHandler = new ObjectWrapperHandler(_valueConverterHandler); @@ -56,8 +59,9 @@ public void CreateValueConverterInstances_ShouldCreatePropertyWrapperWithConvert // Arrange var converterId = HashCodeHelper.GetPropertyWrapperConverterId(_intToStrConverter, _intToStrConverter.Name); - var propertyWrapper = Substitute.For(); - propertyWrapper.ConverterId.Returns(converterId); + var propertyWrapper = new PropertyConvertWrapper(_intToStrConverter) + .SetProperty(new Property()) + .SetConverterId(converterId); var objectWrapperHandler = new ObjectWrapperHandler(_valueConverterHandler); @@ -80,11 +84,13 @@ public void var converterIdByName = HashCodeHelper.GetPropertyWrapperConverterId(_intToStrConverter, _intToStrConverter.Name); - var propertyWrapperByType = Substitute.For(); - propertyWrapperByType.ConverterId.Returns(converterIdByType); + var propertyWrapperByType = new PropertyConvertWrapper(_intToStrConverter) + .SetProperty(new Property()) + .SetConverterId(converterIdByType); - var propertyWrapperByName = Substitute.For(); - propertyWrapperByName.ConverterId.Returns(converterIdByName); + var propertyWrapperByName = new PropertyConvertWrapper(_intToStrConverter) + .SetProperty(new Property()) + .SetConverterId(converterIdByName); var objectWrapperHandler = new ObjectWrapperHandler(_valueConverterHandler); @@ -108,6 +114,7 @@ public void CreateValueConverterInstances_ShouldCreateCommandWrapperWithConverte { // Arrange const int elementId = 55; + var converterId = HashCodeHelper.GetCommandWrapperConverterId(_parameterToIntConverter); var commandWrapper = Substitute.For(); @@ -130,6 +137,7 @@ public void CreateValueConverterInstances_ShouldCreateCommandWrapperWithConverte { // Arrange const int elementId = 55; + var converterId = HashCodeHelper.GetCommandWrapperConverterId(_parameterToIntConverter, _parameterToIntConverter.Name); @@ -154,6 +162,7 @@ public void { // Arrange const int elementId = 55; + var converterIdByType = HashCodeHelper.GetCommandWrapperConverterId(_parameterToIntConverter); var converterIdByName = @@ -202,8 +211,9 @@ public void ReturnProperty_ShouldResetPropertyWrapper_WhenPropertyWrapperWasRetu // Arrange var converterId = HashCodeHelper.GetPropertyWrapperConverterId(_intToStrConverter); - var propertyWrapper = Substitute.For(); - propertyWrapper.ConverterId.Returns(converterId); + PropertyWrapper propertyWrapper = new PropertyConvertWrapper(_intToStrConverter); + propertyWrapper.SetProperty(new Property(69)) + .SetConverterId(converterId); var objectWrapperHandler = new ObjectWrapperHandler(_valueConverterHandler); @@ -212,7 +222,7 @@ public void ReturnProperty_ShouldResetPropertyWrapper_WhenPropertyWrapperWasRetu objectWrapperHandler.ReturnProperty(propertyWrapper); // Assert - propertyWrapper.Received(1).Reset(); + propertyWrapper.Value.Should().Be(default); } [Fact] From 3dedba4770ed403b98da6408c99fb44eeb6d74f9 Mon Sep 17 00:00:00 2001 From: Dima Date: Sat, 11 Nov 2023 16:05:32 +0800 Subject: [PATCH 7/8] Update readme file. --- README.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b1e5d3a..00e92ab 100644 --- a/README.md +++ b/README.md @@ -1078,7 +1078,7 @@ public class UsersViewModel : IBindableContext } ``` -Create a `UsersView` as follows. +Now we need to provide an item template for the `UserItemViewModel`. Create a `UsersView` as follows. ```csharp public class UsersView : DocumentView @@ -1095,6 +1095,15 @@ public class UsersView : DocumentView } ``` +Starting with Unity 2023, you can select an ItemTemplate directly in the UI Builder. + +
UI Builder Inspector +
+ +![collection-item-template](https://github.com/LibraStack/UnityMvvmToolkit/assets/28132516/2dba3a31-7ca9-45c3-a704-5f847262449c) + +
+ Finally, create a main `UI Document` named `UsersView.uxml` with the following content. ```xml From 2aef3018fdac0d16e52198f34d74caee3cea8ba8 Mon Sep 17 00:00:00 2001 From: ChebanovDD Date: Sat, 11 Nov 2023 16:06:13 +0800 Subject: [PATCH 8/8] Bump package version. --- .../Assets/Plugins/UnityMvvmToolkit/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/package.json b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/package.json index 0fbce43..3dcdba4 100644 --- a/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/package.json +++ b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/package.json @@ -2,7 +2,7 @@ "name": "com.chebanovdd.unitymvvmtoolkit", "displayName": "Unity MVVM Toolkit", "author": { "name": "ChebanovDD", "url": "https://github.com/ChebanovDD" }, - "version": "1.1.8", + "version": "1.1.9", "unity": "2018.4", "description": "The Unity Mvvm Toolkit allows you to use data binding to establish a connection between the app UI and the data it displays. This is a simple and consistent way to achieve clean separation of business logic from UI.", "keywords": [ "mvvm", "binding", "ui", "toolkit" ],