Skip to content

Commit ca4f096

Browse files
authored
Merge pull request #7 from LibraStack/develop
Bug fixes.
2 parents d96f252 + da5fcde commit ca4f096

24 files changed

+398
-247
lines changed

README.md

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ A package that brings data-binding to your Unity project.
1818
- [Property](#propertyt--readonlypropertyt)
1919
- [Command](#command--commandt)
2020
- [AsyncCommand](#asynccommand--asynccommandt)
21-
- [AsyncLazyCommand](#asynclazycommand--asynclazycommandt)
2221
- [PropertyValueConverter](#propertyvalueconvertertsourcetype-ttargettype)
2322
- [ParameterValueConverter](#parametervalueconverterttargettype)
2423
- [Quick start](#watch-quick-start)
@@ -198,7 +197,6 @@ The included types are:
198197
- [Property\<T\> & ReadOnlyProperty\<T\>](#propertyt--readonlypropertyt)
199198
- [Command & Command\<T\>](#command--commandt)
200199
- [AsyncCommand & AsyncCommand\<T\>](#asynccommand--asynccommandt)
201-
- [AsyncLazyCommand & AsyncLazyCommand\<T\>](#asynclazycommand--asynclazycommandt)
202200
- [PropertyValueConverter\<TSourceType, TTargetType\>](#propertyvalueconvertertsourcetype-ttargettype)
203201
- [ParameterValueConverter\<TTargetType\>](#parametervalueconverterttargettype)
204202
- [IProperty\<T\> & IReadOnlyProperty\<T\>](#propertyt--readonlypropertyt)
@@ -425,6 +423,18 @@ public class ImageViewerViewModel : IBindingContext
425423
}
426424
```
427425

426+
To allow the same async command to be invoked concurrently multiple times, set the `AllowConcurrency` property of the `AsyncCommand` to `true`.
427+
428+
```csharp
429+
public class MainViewModel : IBindingContext
430+
{
431+
public MainViewModel()
432+
{
433+
RunConcurrentlyCommand = new AsyncCommand(RunConcurrentlyAsync) { AllowConcurrency = true };
434+
}
435+
}
436+
```
437+
428438
If you want to create an async command that supports cancellation, use the `WithCancellation` extension method.
429439

430440
```csharp
@@ -446,22 +456,13 @@ public class MyViewModel : IBindingContext
446456

447457
private void Cancel()
448458
{
449-
// If the underlying command is not running, or
450-
// if it does not support cancellation, this method will perform no action.
459+
// If the underlying command is not running, this method will perform no action.
451460
MyAsyncCommand.Cancel();
452461
}
453462
}
454463
```
455464

456-
If the command supports cancellation, previous invocations will automatically be canceled if a new one is started.
457-
458-
> **Note:** You need to import the [UniTask](https://github.com/Cysharp/UniTask) package in order to use async commands.
459-
460-
### AsyncLazyCommand & AsyncLazyCommand\<T\>
461-
462-
The `AsyncLazyCommand` and `AsyncLazyCommand<T>` are have the same functionality as the `AsyncCommand`'s, except they prevent the same async command from being invoked concurrently multiple times.
463-
464-
Let's imagine a scenario similar to the one described in the `AsyncCommand` sample, but a user clicks the `Download Image` button several times while the async operation is running. In this case, `AsyncLazyCommand` will ignore all clicks until previous async operation has completed.
465+
If a command supports cancellation and the `AllowConcurrency` property is set to `true`, all running commands will be canceled.
465466

466467
> **Note:** You need to import the [UniTask](https://github.com/Cysharp/UniTask) package in order to use async commands.
467468

src/UnityMvvmToolkit.Core/Internal/BindingContextMemberProvider.cs

Lines changed: 43 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -63,40 +63,49 @@ private static bool TryGetFieldHashCode(Type contextType, FieldInfo fieldInfo, o
6363
return TryGetHashCode(contextType, fieldInfo.Name, fieldInfo.FieldType, out hashCode);
6464
}
6565

66-
if (TryGetPropertyNameFromAttribute(fieldInfo, out var fieldName))
66+
if (HasObservableAttribute(fieldInfo, out var propertyName) == false)
6767
{
68-
return TryGetHashCode(contextType, fieldName, fieldInfo.FieldType, out hashCode);
68+
hashCode = default;
69+
return false;
6970
}
7071

71-
fieldName = fieldInfo.Name;
72+
return string.IsNullOrWhiteSpace(propertyName)
73+
? TryGetHashCode(contextType, GetFieldName(fieldInfo.Name), fieldInfo.FieldType, out hashCode)
74+
: TryGetHashCode(contextType, propertyName, fieldInfo.FieldType, out hashCode);
75+
}
7276

73-
if (fieldName.Length > 1)
77+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
78+
private static bool TryGetPropertyHashCode(Type contextType, PropertyInfo propertyInfo, out int hashCode)
79+
{
80+
if (propertyInfo.GetMethod.IsPrivate)
7481
{
75-
if (fieldName[0] == '_')
76-
{
77-
fieldName = fieldName[1..]; // TODO: Get rid of allocation.
78-
}
79-
80-
if (fieldName[0] == 'm' && fieldName[1] == '_')
81-
{
82-
fieldName = fieldName[2..]; // TODO: Get rid of allocation.
83-
}
82+
hashCode = default;
83+
return false;
8484
}
8585

86-
if (string.IsNullOrEmpty(fieldName))
86+
return TryGetHashCode(contextType, propertyInfo.Name, propertyInfo.PropertyType, out hashCode);
87+
}
88+
89+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
90+
private static bool TryGetHashCode(Type contextType, string memberName, Type memberType, out int hashCode)
91+
{
92+
if (typeof(IBaseCommand).IsAssignableFrom(memberType) ||
93+
typeof(IBaseProperty).IsAssignableFrom(memberType))
8794
{
88-
throw new InvalidOperationException($"Field name '{fieldName}' is not supported.");
95+
hashCode = HashCodeHelper.GetMemberHashCode(contextType, memberName);
96+
return true;
8997
}
9098

91-
return TryGetHashCode(contextType, fieldName, fieldInfo.FieldType, out hashCode);
99+
hashCode = default;
100+
return false;
92101
}
93102

94103
[MethodImpl(MethodImplOptions.AggressiveInlining)]
95-
private static bool TryGetPropertyNameFromAttribute(MemberInfo fieldInfo, out string propertyName)
104+
private static bool HasObservableAttribute(MemberInfo fieldInfo, out string propertyName)
96105
{
97106
var observableAttribute = fieldInfo.GetCustomAttribute<ObservableAttribute>();
98107

99-
if (observableAttribute == null || string.IsNullOrWhiteSpace(observableAttribute.PropertyName))
108+
if (observableAttribute == null)
100109
{
101110
propertyName = default;
102111
return false;
@@ -107,29 +116,29 @@ private static bool TryGetPropertyNameFromAttribute(MemberInfo fieldInfo, out st
107116
}
108117

109118
[MethodImpl(MethodImplOptions.AggressiveInlining)]
110-
private static bool TryGetPropertyHashCode(Type contextType, PropertyInfo propertyInfo, out int hashCode)
119+
private static string GetFieldName(string fieldName)
111120
{
112-
if (propertyInfo.GetMethod.IsPrivate)
121+
var resultName = fieldName;
122+
123+
if (resultName.Length > 1)
113124
{
114-
hashCode = default;
115-
return false;
116-
}
125+
if (resultName[0] == '_')
126+
{
127+
resultName = resultName[1..]; // TODO: Get rid of allocation.
128+
}
117129

118-
return TryGetHashCode(contextType, propertyInfo.Name, propertyInfo.PropertyType, out hashCode);
119-
}
130+
if (resultName[0] == 'm' && resultName[1] == '_')
131+
{
132+
resultName = resultName[2..]; // TODO: Get rid of allocation.
133+
}
134+
}
120135

121-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
122-
private static bool TryGetHashCode(Type contextType, string memberName, Type memberType, out int hashCode)
123-
{
124-
if (typeof(IBaseCommand).IsAssignableFrom(memberType) ||
125-
typeof(IBaseProperty).IsAssignableFrom(memberType))
136+
if (string.IsNullOrEmpty(resultName))
126137
{
127-
hashCode = HashCodeHelper.GetMemberHashCode(contextType, memberName);
128-
return true;
138+
throw new InvalidOperationException($"Field name '{resultName}' is not supported.");
129139
}
130140

131-
hashCode = default;
132-
return false;
141+
return resultName;
133142
}
134143
}
135144
}

src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/Core/Internal/BindingContextMemberProvider.cs

Lines changed: 43 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -63,40 +63,49 @@ private static bool TryGetFieldHashCode(Type contextType, FieldInfo fieldInfo, o
6363
return TryGetHashCode(contextType, fieldInfo.Name, fieldInfo.FieldType, out hashCode);
6464
}
6565

66-
if (TryGetPropertyNameFromAttribute(fieldInfo, out var fieldName))
66+
if (HasObservableAttribute(fieldInfo, out var propertyName) == false)
6767
{
68-
return TryGetHashCode(contextType, fieldName, fieldInfo.FieldType, out hashCode);
68+
hashCode = default;
69+
return false;
6970
}
7071

71-
fieldName = fieldInfo.Name;
72+
return string.IsNullOrWhiteSpace(propertyName)
73+
? TryGetHashCode(contextType, GetFieldName(fieldInfo.Name), fieldInfo.FieldType, out hashCode)
74+
: TryGetHashCode(contextType, propertyName, fieldInfo.FieldType, out hashCode);
75+
}
7276

73-
if (fieldName.Length > 1)
77+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
78+
private static bool TryGetPropertyHashCode(Type contextType, PropertyInfo propertyInfo, out int hashCode)
79+
{
80+
if (propertyInfo.GetMethod.IsPrivate)
7481
{
75-
if (fieldName[0] == '_')
76-
{
77-
fieldName = fieldName[1..]; // TODO: Get rid of allocation.
78-
}
79-
80-
if (fieldName[0] == 'm' && fieldName[1] == '_')
81-
{
82-
fieldName = fieldName[2..]; // TODO: Get rid of allocation.
83-
}
82+
hashCode = default;
83+
return false;
8484
}
8585

86-
if (string.IsNullOrEmpty(fieldName))
86+
return TryGetHashCode(contextType, propertyInfo.Name, propertyInfo.PropertyType, out hashCode);
87+
}
88+
89+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
90+
private static bool TryGetHashCode(Type contextType, string memberName, Type memberType, out int hashCode)
91+
{
92+
if (typeof(IBaseCommand).IsAssignableFrom(memberType) ||
93+
typeof(IBaseProperty).IsAssignableFrom(memberType))
8794
{
88-
throw new InvalidOperationException($"Field name '{fieldName}' is not supported.");
95+
hashCode = HashCodeHelper.GetMemberHashCode(contextType, memberName);
96+
return true;
8997
}
9098

91-
return TryGetHashCode(contextType, fieldName, fieldInfo.FieldType, out hashCode);
99+
hashCode = default;
100+
return false;
92101
}
93102

94103
[MethodImpl(MethodImplOptions.AggressiveInlining)]
95-
private static bool TryGetPropertyNameFromAttribute(MemberInfo fieldInfo, out string propertyName)
104+
private static bool HasObservableAttribute(MemberInfo fieldInfo, out string propertyName)
96105
{
97106
var observableAttribute = fieldInfo.GetCustomAttribute<ObservableAttribute>();
98107

99-
if (observableAttribute == null || string.IsNullOrWhiteSpace(observableAttribute.PropertyName))
108+
if (observableAttribute == null)
100109
{
101110
propertyName = default;
102111
return false;
@@ -107,29 +116,29 @@ private static bool TryGetPropertyNameFromAttribute(MemberInfo fieldInfo, out st
107116
}
108117

109118
[MethodImpl(MethodImplOptions.AggressiveInlining)]
110-
private static bool TryGetPropertyHashCode(Type contextType, PropertyInfo propertyInfo, out int hashCode)
119+
private static string GetFieldName(string fieldName)
111120
{
112-
if (propertyInfo.GetMethod.IsPrivate)
121+
var resultName = fieldName;
122+
123+
if (resultName.Length > 1)
113124
{
114-
hashCode = default;
115-
return false;
116-
}
125+
if (resultName[0] == '_')
126+
{
127+
resultName = resultName[1..]; // TODO: Get rid of allocation.
128+
}
117129

118-
return TryGetHashCode(contextType, propertyInfo.Name, propertyInfo.PropertyType, out hashCode);
119-
}
130+
if (resultName[0] == 'm' && resultName[1] == '_')
131+
{
132+
resultName = resultName[2..]; // TODO: Get rid of allocation.
133+
}
134+
}
120135

121-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
122-
private static bool TryGetHashCode(Type contextType, string memberName, Type memberType, out int hashCode)
123-
{
124-
if (typeof(IBaseCommand).IsAssignableFrom(memberType) ||
125-
typeof(IBaseProperty).IsAssignableFrom(memberType))
136+
if (string.IsNullOrEmpty(resultName))
126137
{
127-
hashCode = HashCodeHelper.GetMemberHashCode(contextType, memberName);
128-
return true;
138+
throw new InvalidOperationException($"Field name '{resultName}' is not supported.");
129139
}
130140

131-
hashCode = default;
132-
return false;
141+
return resultName;
133142
}
134143
}
135144
}

src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/External/UniTask/AsyncCommand.T.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,25 @@ public AsyncCommand(Func<T, CancellationToken, UniTask> action, Func<bool> canEx
1818

1919
public void Execute(T parameter)
2020
{
21+
if (IsCommandRunning && AllowConcurrency == false)
22+
{
23+
return;
24+
}
25+
2126
ExecuteAsync(parameter).Forget();
2227
}
2328

2429
public async UniTask ExecuteAsync(T parameter, CancellationToken cancellationToken = default)
2530
{
2631
try
2732
{
28-
IsRunning = true;
33+
SetCommandRunning(true);
34+
2935
await _action(parameter, cancellationToken);
3036
}
3137
finally
3238
{
33-
IsRunning = false;
39+
SetCommandRunning(false);
3440
}
3541
}
3642
}

src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/External/UniTask/AsyncCommand.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,25 @@ public AsyncCommand(Func<CancellationToken, UniTask> action, Func<bool> canExecu
1818

1919
public void Execute()
2020
{
21+
if (IsCommandRunning && AllowConcurrency == false)
22+
{
23+
return;
24+
}
25+
2126
ExecuteAsync().Forget();
2227
}
2328

2429
public async UniTask ExecuteAsync(CancellationToken cancellationToken = default)
2530
{
2631
try
2732
{
28-
IsRunning = true;
33+
SetCommandRunning(true);
34+
2935
await _action(cancellationToken);
3036
}
3137
finally
3238
{
33-
IsRunning = false;
39+
SetCommandRunning(false);
3440
}
3541
}
3642
}

src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/External/UniTask/AsyncLazyCommand.T.cs

Lines changed: 0 additions & 44 deletions
This file was deleted.

src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/External/UniTask/AsyncLazyCommand.T.cs.meta

Lines changed: 0 additions & 3 deletions
This file was deleted.

0 commit comments

Comments
 (0)