diff --git a/CHANGELOG.md b/CHANGELOG.md index 78bc11e..0239b86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,53 @@ +# 2027.0.3 + +### External Events + +- `ExternalEvent` and the asynchronous external events execute each queued raise with its own argument. Each `RaiseAsync()` task completes with the result or exception of its handler. Previously, concurrent raises overwrote the argument and left earlier tasks incomplete. +- Requests refused with `ExternalEventRequest.Denied` or `ExternalEventRequest.TimedOut` skip the handler. `Raise(argument)` returns the refusal; `RaiseAsync()` returns a task faulted with `InvalidOperationException`. +- New `RaiseAsync()` overloads accept a `CancellationToken` to cancel a pending execution. Token-aware constructors pass the token to the handler for cooperative cancellation during execution. + +### ExternalEvent Source Generator + +- A trailing `CancellationToken` parameter receives the token of `RaiseAsync()` and is excluded from the event arguments. Synchronous events pass `CancellationToken.None`. +- Generated `RaiseAsync()` extension methods include an overload with a `CancellationToken`. + +Pass a cancellation token to the generated event to cancel a pending execution or stop the handler cooperatively: + +```c# +public partial class MyRevitContext +{ + [ExternalEvent] + private void DeleteWindows(UIApplication application, ICollection elementIds, CancellationToken cancellationToken) + { + var document = application.ActiveUIDocument.Document; + using var transaction = new Transaction(document, "Delete windows"); + transaction.Start(); + + foreach (var elementId in elementIds) + { + cancellationToken.ThrowIfCancellationRequested(); + document.Delete(elementId); + } + + transaction.Commit(); + } + + private async Task DeleteWindowsAsync(ICollection elementIds) + { + using var cancellationSource = new CancellationTokenSource(TimeSpan.FromSeconds(10)); + + try + { + await DeleteWindowsAsyncEvent.RaiseAsync(elementIds, cancellationSource.Token); + } + catch (OperationCanceledException) + { + //The execution was canceled; uncommitted changes are rolled back + } + } +} +``` + # 2027.0.2 Performance optimizations for Revit 2027: @@ -7,9 +57,9 @@ Performance optimizations for Revit 2027: # 2027.0.1 -- `AsyncExternalEvent.RaiseAsync()` with `ExternalEventOptions.AllowDirectInvocation` returns a faulted task when the handler throws inside the Revit API context. The exception was previously lost and the task completed successfully. +### External Events -# 2027.0.0 +- `AsyncExternalEvent.RaiseAsync()` with `ExternalEventOptions.AllowDirectInvocation` returns a faulted task when the handler throws inside the Revit API context. The exception was previously lost and the task completed successfully. ### Compiler Tooling Fixes @@ -25,6 +75,8 @@ Performance optimizations for Revit 2027: - Generated event properties use `System.Threading.Lock` when the consumer supports C# 13 and provides the type; other targets use an object lock. - Unsupported signatures report `RVTTK0006`; conflicting generated member names report `RVTTK0007`. +# 2027.0.0 + ### External Events New family of external event types replacing legacy `ActionEventHandler`, `AsyncEventHandler`: diff --git a/README.md b/README.md index 4e08a6d..79241ce 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,7 @@ Package included by default in [Revit Templates](https://github.com/Nice3point/R * [AsyncExternalEvent\](#asyncexternaleventt) * [AsyncRequestExternalEvent\](#asyncrequestexternaleventtresult) * [AsyncRequestExternalEvent\](#asyncrequestexternaleventt-tresult) + * [Cancellation](#cancellation) * [ExternalEventOptions](#externaleventoptions) * [ExternalEvent attribute](#externalevent-attribute) * [Context](#context) @@ -200,6 +201,15 @@ so you can create them anywhere in your code and on any thread. Use `Raise()` when the caller does not need to wait, or `RaiseAsync()` when it needs completion or a return value. For generated event properties, see the [ExternalEvent attribute](#externalevent-attribute). +Several callers can share one event, such as two modeless windows, or a background service and a window. +Each `Raise(argument)` and `RaiseAsync()` call runs the handler once with its own argument, in the order of the calls. +Each `RaiseAsync()` task completes with the result or the exception of its own run. +`ExternalEvent` without an argument runs once for all waiting raises, the same way Revit runs a raised event. + +A raise that Revit refuses with `Denied` or `TimedOut` never runs. +`Raise(argument)` returns that result to the caller. +The `RaiseAsync()` task fails with an `InvalidOperationException`. + #### ExternalEvent A synchronous external event that queues the handler via the Revit external event mechanism. @@ -355,6 +365,57 @@ private async Task DeleteWindowAsync(ElementId elementId) > Await asynchronous events. Blocking a pending event with `.Wait()`, `.Result`, or `.GetAwaiter().GetResult()` on the Revit main thread prevents Revit from processing it and can deadlock. > The event handler itself remains synchronous; perform asynchronous I/O before or after raising the event. +#### Cancellation + +Every `RaiseAsync()` method has an overload that accepts a `CancellationToken`. +Pass a cancellation token to cancel a pending execution while Revit is busy with a modal dialog or a running command: + +```c# +private async Task DeleteWindowAsync(ElementId elementId) +{ + using var cancellationSource = new CancellationTokenSource(TimeSpan.FromSeconds(10)); + + try + { + await _deleteWindowAsyncEvent.RaiseAsync(elementId, cancellationSource.Token); + } + catch (OperationCanceledException) + { + //The pending execution was canceled + } +} +``` + +Cancellation before execution cancels the task without running the delegate. +Cancellation during execution does not interrupt the delegate; the task completes with its result or exception. +Other queued calls are unaffected. + +To stop a long-running delegate, accept the token as its last parameter: + +```c# +private readonly AsyncExternalEvent> _deleteWindowsAsyncEvent = new((application, elementIds, cancellationToken) => +{ + var document = application.ActiveUIDocument.Document; + using var transaction = new Transaction(document, "Delete windows"); + transaction.Start(); + + foreach (var elementId in elementIds) + { + cancellationToken.ThrowIfCancellationRequested(); + document.Delete(elementId); + } + + transaction.Commit(); +}); +``` + +When the token is canceled, `ThrowIfCancellationRequested()` stops the delegate and cancels the task. +Disposing the uncommitted transaction rolls back the changes. + +The delegate observes a cancellation requested from another thread, such as a timeout or a remote client. +A cancel button of a modeless window runs on the Revit thread. +Its cancellation takes effect only after the delegate returns. + #### ExternalEventOptions You can configure the behavior of external events using `ExternalEventOptions`. @@ -562,6 +623,11 @@ public static partial class MyViewModelExtensions { return externalEvent.RaiseAsync(new MyViewModel.CreateRoomArgs(level, coordinate)); } + + public static global::System.Threading.Tasks.Task RaiseAsync(this IAsyncRequestExternalEvent externalEvent, Level level, UV coordinate, global::System.Threading.CancellationToken cancellationToken) + { + return externalEvent.RaiseAsync(new MyViewModel.CreateRoomArgs(level, coordinate), cancellationToken); + } } ``` @@ -570,6 +636,38 @@ If initialization throws, the next access retries it. +**Methods with CancellationToken parameter** + +Declare a `CancellationToken` as the last parameter to receive the token passed to `RaiseAsync()`. +The generator excludes it from the event arguments: + +```c# +[ExternalEvent] +private void DeleteWindows(UIApplication application, ICollection elementIds, CancellationToken cancellationToken) +{ + var document = application.ActiveUIDocument.Document; + using var transaction = new Transaction(document, "Delete windows"); + transaction.Start(); + + foreach (var elementId in elementIds) + { + cancellationToken.ThrowIfCancellationRequested(); + document.Delete(elementId); + } + + transaction.Commit(); +} + +// Generates: +// IExternalEvent> DeleteWindowsEvent +// IAsyncExternalEvent> DeleteWindowsAsyncEvent +``` + +Call `await DeleteWindowsAsyncEvent.RaiseAsync(elementIds, cancellationToken)` to pass the token to the handler. +`DeleteWindowsEvent.Raise(elementIds)` passes `CancellationToken.None`. +A `CancellationToken` in any other position is a regular argument. +See [Cancellation](#cancellation) for cancellation behavior and exception handling. + **Enabling direct invocation** Set `AllowDirectInvocation` to execute the handler directly when Revit is already in API mode. diff --git a/source/Nice3point.Revit.Toolkit.Analyzers/AnalyzerReleases.Shipped.md b/source/Nice3point.Revit.Toolkit.Analyzers/AnalyzerReleases.Shipped.md index cb41149..ae466d5 100644 --- a/source/Nice3point.Revit.Toolkit.Analyzers/AnalyzerReleases.Shipped.md +++ b/source/Nice3point.Revit.Toolkit.Analyzers/AnalyzerReleases.Shipped.md @@ -1,6 +1,15 @@ ; Shipped analyzer releases ; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md +## Release 2027.0.1 + +### New Rules + + Rule ID | Category | Severity | Notes +-----------|------------------------|----------|----------------------------------------------------------------------------- + RVTTK0006 | ExternalEventGenerator | Error | Reports signatures that cannot be represented by generated external events. + RVTTK0007 | ExternalEventGenerator | Error | Reports generated member names that conflict with another declaration. + ## Release 2027.0.0 ### New Rules diff --git a/source/Nice3point.Revit.Toolkit.Analyzers/AnalyzerReleases.Unshipped.md b/source/Nice3point.Revit.Toolkit.Analyzers/AnalyzerReleases.Unshipped.md index 4675920..f2b7fad 100644 --- a/source/Nice3point.Revit.Toolkit.Analyzers/AnalyzerReleases.Unshipped.md +++ b/source/Nice3point.Revit.Toolkit.Analyzers/AnalyzerReleases.Unshipped.md @@ -1,9 +1,2 @@ ; Unshipped analyzer release ; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md - -### New Rules - - Rule ID | Category | Severity | Notes ------------|------------------------|----------|----------------------------------------------------------------------------- - RVTTK0006 | ExternalEventGenerator | Error | Reports signatures that cannot be represented by generated external events. - RVTTK0007 | ExternalEventGenerator | Error | Reports generated member names that conflict with another declaration. diff --git a/source/Nice3point.Revit.Toolkit.SourceGenerators/ExternalEvents/Analysis/ExternalEventExtensionContainer.cs b/source/Nice3point.Revit.Toolkit.SourceGenerators/ExternalEvents/Analysis/ExternalEventExtensionContainer.cs index 8c6508c..969ebc3 100644 --- a/source/Nice3point.Revit.Toolkit.SourceGenerators/ExternalEvents/Analysis/ExternalEventExtensionContainer.cs +++ b/source/Nice3point.Revit.Toolkit.SourceGenerators/ExternalEvents/Analysis/ExternalEventExtensionContainer.cs @@ -8,10 +8,7 @@ internal static class ExternalEventExtensionContainer { public static (string ClassAccessibility, string? ConflictingName) Resolve(IMethodSymbol method, CancellationToken cancellationToken) { - var hasUiApplicationParameter = method.Parameters.Length > 0 && - method.Parameters[0].Type.WithNullableAnnotation(NullableAnnotation.NotAnnotated).ToDisplayString() == ExternalEventTypeNames.UiApplication.WithoutGlobalPrefix; - - if (method.Parameters.Length - (hasUiApplicationParameter ? 1 : 0) <= 1) + if (ExternalEventParameters.GetArgumentParameters(method).Count() <= 1) { return ("public", null); } diff --git a/source/Nice3point.Revit.Toolkit.SourceGenerators/ExternalEvents/Analysis/ExternalEventExtensionSignature.cs b/source/Nice3point.Revit.Toolkit.SourceGenerators/ExternalEvents/Analysis/ExternalEventExtensionSignature.cs index e9b463b..6620cae 100644 --- a/source/Nice3point.Revit.Toolkit.SourceGenerators/ExternalEvents/Analysis/ExternalEventExtensionSignature.cs +++ b/source/Nice3point.Revit.Toolkit.SourceGenerators/ExternalEvents/Analysis/ExternalEventExtensionSignature.cs @@ -14,7 +14,7 @@ internal sealed class ExternalEventExtensionSignature private readonly Dictionary _typeParameterNames = new(SymbolEqualityComparer.Default); - public ExternalEventExtensionDefinition Create(IMethodSymbol method, bool hasUiApplicationParameter, CancellationToken cancellationToken) + public ExternalEventExtensionDefinition Create(IMethodSymbol method, CancellationToken cancellationToken) { var containingTypes = new Stack(); for (var containingType = method.ContainingType; containingType is not null; containingType = containingType.ContainingType) @@ -24,7 +24,8 @@ public ExternalEventExtensionDefinition Create(IMethodSymbol method, bool hasUiA var reservedParameterNames = new HashSet(method.Parameters.Select(static parameter => parameter.Name), StringComparer.Ordinal) { - "externalEvent" + "externalEvent", + "cancellationToken" }; var typeParameters = ImmutableArray.CreateBuilder(); @@ -54,8 +55,7 @@ public ExternalEventExtensionDefinition Create(IMethodSymbol method, bool hasUiA typeParameters.ToImmutable(), constraints.ToString(), $"{Format(method.ContainingType, TypeFormat)}.{method.Name}Args", method.ReturnsVoid ? null : Format(method.ReturnType, TypeFormat), - method.Parameters - .Skip(hasUiApplicationParameter ? 1 : 0) + ExternalEventParameters.GetArgumentParameters(method) .Select(parameter => Format(parameter.Type, TypeFormat)) .ToImmutableArray()); } diff --git a/source/Nice3point.Revit.Toolkit.SourceGenerators/ExternalEvents/Analysis/ExternalEventExtractor.cs b/source/Nice3point.Revit.Toolkit.SourceGenerators/ExternalEvents/Analysis/ExternalEventExtractor.cs index d324566..3be3634 100644 --- a/source/Nice3point.Revit.Toolkit.SourceGenerators/ExternalEvents/Analysis/ExternalEventExtractor.cs +++ b/source/Nice3point.Revit.Toolkit.SourceGenerators/ExternalEvents/Analysis/ExternalEventExtractor.cs @@ -119,16 +119,13 @@ private static bool ValidateMethod(IMethodSymbol methodSymbol, INamedTypeSymbol private static string[] GetGeneratedMemberNames(IMethodSymbol method) { - var hasUiApplicationParameter = method.Parameters.Length > 0 && - method.Parameters[0].Type.ToDisplayString(NullableFlowFormat) == ExternalEventTypeNames.UiApplication.WithoutGlobalPrefix; - var generatedMemberNames = new List { $"{method.Name}AsyncEvent" }; if (method.ReturnsVoid) { generatedMemberNames.Add($"{method.Name}Event"); } - if (method.Parameters.Length - (hasUiApplicationParameter ? 1 : 0) > 1) + if (ExternalEventParameters.GetArgumentParameters(method).Count() > 1) { generatedMemberNames.Add($"{method.Name}Args"); } @@ -140,9 +137,11 @@ private static ExternalEventDefinition CreateEventDefinition(IMethodSymbol metho { var allowDirectInvocation = attribute.GetNamedArgument("AllowDirectInvocation", fallback: false); - var (hasUiApplicationParameter, extraParameters) = ClassifyParameters(methodSymbol); + var hasUiApplicationParameter = ExternalEventParameters.HasUiApplicationParameter(methodSymbol); + var hasCancellationTokenParameter = ExternalEventParameters.HasCancellationTokenParameter(methodSymbol); + var extraParameters = ClassifyParameters(methodSymbol); var (returnsVoid, returnType) = ExtractReturnType(methodSymbol); - var delegateType = BuildDelegateType(methodSymbol, hasUiApplicationParameter, returnsVoid, returnType, extraParameters); + var delegateType = BuildDelegateType(methodSymbol, returnsVoid, returnType); var typeHierarchy = GetTypeHierarchy(methodSymbol, cancellationToken); var containingNamespace = GetNamespace(methodSymbol.ContainingType); @@ -156,10 +155,11 @@ private static ExternalEventDefinition CreateEventDefinition(IMethodSymbol metho returnType, delegateType, hasUiApplicationParameter, + hasCancellationTokenParameter, allowDirectInvocation, typeHierarchy, extraParameters, - new ExternalEventExtensionSignature().Create(methodSymbol, hasUiApplicationParameter, cancellationToken), + new ExternalEventExtensionSignature().Create(methodSymbol, cancellationToken), methodSymbol.ContainingType.GetMembers().Select(static member => member.Name).ToImmutableArray()); } @@ -252,29 +252,20 @@ private static bool HasDuplicateOverloads(IMethodSymbol methodSymbol, INamedType return false; } - private static (bool HasUiApplicationParameter, EquatableArray ExtraParameters) ClassifyParameters(IMethodSymbol methodSymbol) + private static EquatableArray ClassifyParameters(IMethodSymbol methodSymbol) { - var parameters = methodSymbol.Parameters; - var hasUiApplicationParameter = false; var extraParameters = ImmutableArray.CreateBuilder(); var recordPropertyNames = GetReservedRecordPropertyNames(methodSymbol); - for (var parameterIndex = 0; parameterIndex < parameters.Length; parameterIndex++) + foreach (var parameter in ExternalEventParameters.GetArgumentParameters(methodSymbol)) { - var parameter = parameters[parameterIndex]; - if (parameterIndex == 0 && parameter.Type.ToDisplayString(NullableFlowFormat) == ExternalEventTypeNames.UiApplication.WithoutGlobalPrefix) - { - hasUiApplicationParameter = true; - continue; - } - var propertyName = char.ToUpperInvariant(parameter.Name[0]) + parameter.Name[1..]; propertyName = CSharpIdentifier.Reserve(propertyName, recordPropertyNames); var parameterTypeFullyQualified = parameter.Type.GetFullyQualifiedNameWithNullabilityAnnotations(); extraParameters.Add(new ExternalEventParameter(parameter.Name, parameterTypeFullyQualified, propertyName)); } - return (hasUiApplicationParameter, extraParameters.ToImmutable()); + return extraParameters.ToImmutable(); } private static HashSet GetReservedRecordPropertyNames(IMethodSymbol methodSymbol) @@ -302,23 +293,11 @@ private static (bool IsVoidReturn, string? ReturnTypeFullyQualified) ExtractRetu return (false, methodSymbol.ReturnType.GetFullyQualifiedNameWithNullabilityAnnotations()); } - private static string BuildDelegateType( - IMethodSymbol methodSymbol, - bool hasUiApplicationParameter, - bool returnsVoid, - string? fullyQualifiedReturnType, - EquatableArray extraParameters) + private static string BuildDelegateType(IMethodSymbol methodSymbol, bool returnsVoid, string? fullyQualifiedReturnType) { - var parameterTypes = new List(); - if (hasUiApplicationParameter) - { - parameterTypes.Add(methodSymbol.Parameters[0].Type.GetFullyQualifiedNameWithNullabilityAnnotations()); - } - - foreach (var parameter in extraParameters) - { - parameterTypes.Add(parameter.FullyQualifiedType); - } + var parameterTypes = methodSymbol.Parameters + .Select(static parameter => parameter.Type.GetFullyQualifiedNameWithNullabilityAnnotations()) + .ToList(); if (returnsVoid) { diff --git a/source/Nice3point.Revit.Toolkit.SourceGenerators/ExternalEvents/Analysis/ExternalEventParameters.cs b/source/Nice3point.Revit.Toolkit.SourceGenerators/ExternalEvents/Analysis/ExternalEventParameters.cs new file mode 100644 index 0000000..f833075 --- /dev/null +++ b/source/Nice3point.Revit.Toolkit.SourceGenerators/ExternalEvents/Analysis/ExternalEventParameters.cs @@ -0,0 +1,50 @@ +using Microsoft.CodeAnalysis; +using Nice3point.Revit.Toolkit.SourceGenerators.CSharp; + +namespace Nice3point.Revit.Toolkit.SourceGenerators.ExternalEvents.Analysis; + +/// +/// Classifies the parameters of a method marked with [ExternalEvent]. +/// +/// +/// The event supplies a leading UIApplication parameter and a trailing CancellationToken parameter. +/// The parameters between them are the arguments the caller passes when raising the event. +/// +internal static class ExternalEventParameters +{ + private static readonly SymbolDisplayFormat TypeNameFormat = new( + SymbolDisplayGlobalNamespaceStyle.Omitted, + SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces); + + /// + /// Determines whether the first parameter of the method receives the current UIApplication. + /// + public static bool HasUiApplicationParameter(IMethodSymbol method) + { + return method.Parameters.Length > 0 && IsType(method.Parameters[0].Type, ExternalEventTypeNames.UiApplication); + } + + /// + /// Determines whether the last parameter of the method receives the cancellation token of the raise. + /// + public static bool HasCancellationTokenParameter(IMethodSymbol method) + { + var firstArgumentIndex = HasUiApplicationParameter(method) ? 1 : 0; + return method.Parameters.Length > firstArgumentIndex && IsType(method.Parameters[^1].Type, ExternalEventTypeNames.CancellationToken); + } + + /// + /// Gets the parameters the caller passes when raising the event, in declaration order. + /// + public static IEnumerable GetArgumentParameters(IMethodSymbol method) + { + var firstArgumentIndex = HasUiApplicationParameter(method) ? 1 : 0; + var argumentCount = method.Parameters.Length - firstArgumentIndex - (HasCancellationTokenParameter(method) ? 1 : 0); + return method.Parameters.Skip(firstArgumentIndex).Take(argumentCount); + } + + private static bool IsType(ITypeSymbol type, FullyQualifiedTypeName typeName) + { + return type.ToDisplayString(TypeNameFormat) == typeName.WithoutGlobalPrefix; + } +} diff --git a/source/Nice3point.Revit.Toolkit.SourceGenerators/ExternalEvents/Emission/ExternalEventWriter.cs b/source/Nice3point.Revit.Toolkit.SourceGenerators/ExternalEvents/Emission/ExternalEventWriter.cs index fe27b7c..6fb8c77 100644 --- a/source/Nice3point.Revit.Toolkit.SourceGenerators/ExternalEvents/Emission/ExternalEventWriter.cs +++ b/source/Nice3point.Revit.Toolkit.SourceGenerators/ExternalEvents/Emission/ExternalEventWriter.cs @@ -102,11 +102,13 @@ private static void WriteExtensionClass(CodeWriter writer, ExternalEventDefiniti { if (eventDefinition.ReturnsVoid) { - WriteRaiseExtensionMethod(writer, eventDefinition, qualifiedRecordType, isAsync: false); + WriteRaiseExtensionMethod(writer, eventDefinition, qualifiedRecordType, RaiseMethodKind.Raise); writer.AppendLine(); } - WriteRaiseExtensionMethod(writer, eventDefinition, qualifiedRecordType, isAsync: true); + WriteRaiseExtensionMethod(writer, eventDefinition, qualifiedRecordType, RaiseMethodKind.RaiseAsync); + writer.AppendLine(); + WriteRaiseExtensionMethod(writer, eventDefinition, qualifiedRecordType, RaiseMethodKind.CancellableRaiseAsync); } } @@ -123,17 +125,22 @@ private static void WriteEventProperty(CodeWriter writer, ExternalEventDefinitio var eventType = FormatGenericType(eventTypeName.WithGlobalPrefix, typeArguments); var interfaceType = FormatGenericType(interfaceTypeName.WithGlobalPrefix, typeArguments); - var captureHandler = eventDefinition is { IsStatic: false, ExtraParameters.Length: > 1 } && eventDefinition.TypeHierarchy[^1].Keyword is "struct" or "record struct"; + var usesHandlerDelegate = UsesHandlerDelegate(eventDefinition, propertyKind); + var captureHandler = !usesHandlerDelegate && !eventDefinition.IsStatic && eventDefinition.TypeHierarchy[^1].Keyword is "struct" or "record struct"; var reservedLocalNames = new HashSet(eventDefinition.TypeHierarchy.SelectMany(type => type.TypeParameters), StringComparer.Ordinal) { eventDefinition.MethodName }; var handlerName = captureHandler ? CSharpIdentifier.Reserve("handler", reservedLocalNames) : CSharpIdentifier.Escape(eventDefinition.MethodName); - var applicationName = CSharpIdentifier.Reserve("application", reservedLocalNames); - var argumentsName = CSharpIdentifier.Reserve("args", reservedLocalNames); + var lambdaParameterNames = new LambdaParameterNames + { + Application = CSharpIdentifier.Reserve("application", reservedLocalNames), + Arguments = CSharpIdentifier.Reserve("args", reservedLocalNames), + CancellationToken = CSharpIdentifier.Reserve("cancellationToken", reservedLocalNames) + }; - var initializer = BuildPropertyInitializer(eventDefinition, eventType, optionsArgument, handlerName, applicationName, argumentsName); + var initializer = BuildPropertyInitializer(eventDefinition, propertyKind, eventType, optionsArgument, handlerName, lambdaParameterNames); var eventProperty = new EventPropertyDefinition(staticModifier, interfaceType, propertySuffix, initializer, captureHandler ? handlerName : null); WritePropertyWithBackingField(writer, eventDefinition, useFieldKeyword, useLockType, eventProperty); @@ -196,13 +203,13 @@ private static void WritePropertyInitialization(CodeWriter writer, string assign } } - private static void WriteRaiseExtensionMethod(CodeWriter writer, ExternalEventDefinition eventDefinition, string qualifiedRecordType, bool isAsync) + private static void WriteRaiseExtensionMethod(CodeWriter writer, ExternalEventDefinition eventDefinition, string qualifiedRecordType, RaiseMethodKind methodKind) { string interfaceType; string returnType; string methodName; - if (!isAsync) + if (methodKind == RaiseMethodKind.Raise) { interfaceType = $"{ExternalEventTypeNames.ExternalEventInterface.WithGlobalPrefix}<{qualifiedRecordType}>"; returnType = ExternalEventTypeNames.ExternalEventRequest.WithGlobalPrefix; @@ -223,7 +230,7 @@ private static void WriteRaiseExtensionMethod(CodeWriter writer, ExternalEventDe } var parameters = BuildExtensionMethodParameters(eventDefinition); - var recordArguments = BuildRecordConstructorArguments(eventDefinition); + var raiseArguments = $"new {qualifiedRecordType}({BuildRecordConstructorArguments(eventDefinition)})"; var typeParameters = FormatTypeParameters(eventDefinition.Extension.TypeParameters); var genericMethodName = FormatGenericType(methodName, typeParameters); @@ -234,11 +241,18 @@ private static void WriteRaiseExtensionMethod(CodeWriter writer, ExternalEventDe var receiverName = CSharpIdentifier.Reserve("externalEvent", parameterNames); var accessibility = GetMostRestrictiveAccessibility(eventDefinition.TypeHierarchy); + if (methodKind == RaiseMethodKind.CancellableRaiseAsync) + { + var cancellationTokenName = CSharpIdentifier.Reserve("cancellationToken", parameterNames); + parameters = $"{parameters}, {ExternalEventTypeNames.CancellationToken.WithGlobalPrefix} {cancellationTokenName}"; + raiseArguments = $"{raiseArguments}, {cancellationTokenName}"; + } + WriteExcludeFromCodeCoverageAttributes(writer); WriteGeneratedCodeAttributes(writer); using (writer.BeginBlock($"{accessibility} static {returnType} {genericMethodName}(this {interfaceType} {receiverName}, {parameters}){constraints}")) { - writer.AppendLine($"return {receiverName}.{methodName}(new {qualifiedRecordType}({recordArguments}));"); + writer.AppendLine($"return {receiverName}.{methodName}({raiseArguments});"); } } @@ -270,41 +284,99 @@ private static void WriteGeneratedCodeAttributes(CodeWriter writer) return argType ?? returnType; } - private static string BuildHandlerInvocation(ExternalEventDefinition eventDefinition, string argumentsName, string handlerName, string applicationName) + private static string BuildHandlerInvocation(ExternalEventDefinition eventDefinition, EventPropertyKind propertyKind, string handlerName, LambdaParameterNames parameterNames) { var arguments = new List(); if (eventDefinition.HasUiApplicationParameter) { - arguments.Add(applicationName); + arguments.Add(parameterNames.Application); } - foreach (var parameter in eventDefinition.ExtraParameters) + if (eventDefinition.ExtraParameters.Length == 1) + { + arguments.Add(parameterNames.Arguments); + } + else { - arguments.Add($"{argumentsName}.{CSharpIdentifier.Escape(parameter.RecordPropertyName)}"); + foreach (var parameter in eventDefinition.ExtraParameters) + { + arguments.Add($"{parameterNames.Arguments}.{CSharpIdentifier.Escape(parameter.RecordPropertyName)}"); + } + } + + if (eventDefinition.HasCancellationTokenParameter) + { + arguments.Add(propertyKind == EventPropertyKind.Sync ? $"{ExternalEventTypeNames.CancellationToken.WithGlobalPrefix}.None" : parameterNames.CancellationToken); } return $"{handlerName}({string.Join(", ", arguments)})"; } + private static string BuildLambdaParameters(ExternalEventDefinition eventDefinition, EventPropertyKind propertyKind, LambdaParameterNames parameterNames) + { + var receivesCancellationToken = eventDefinition.HasCancellationTokenParameter && propertyKind != EventPropertyKind.Sync; + + var parameters = new List(); + if (eventDefinition.HasUiApplicationParameter || receivesCancellationToken) + { + parameters.Add(parameterNames.Application); + } + + if (eventDefinition.ExtraParameters.Length > 0) + { + parameters.Add(parameterNames.Arguments); + } + + if (receivesCancellationToken) + { + parameters.Add(parameterNames.CancellationToken); + } + + return string.Join(", ", parameters); + } + private static EventInitializer BuildPropertyInitializer( ExternalEventDefinition eventDefinition, + EventPropertyKind propertyKind, string eventType, string optionsArgument, string handlerName, - string applicationName, - string argumentsName) + LambdaParameterNames parameterNames) { - if (eventDefinition.ExtraParameters.Length < 2) + if (UsesHandlerDelegate(eventDefinition, propertyKind)) { return new EventInitializer($"new {eventType}(new {eventDefinition.FullyQualifiedDelegateType}({handlerName})", LambdaBody: null, optionsArgument); } - var handlerInvocation = BuildHandlerInvocation(eventDefinition, argumentsName, handlerName, applicationName); - var lambdaParameters = eventDefinition.HasUiApplicationParameter ? $"{applicationName}, {argumentsName}" : argumentsName; + var handlerInvocation = BuildHandlerInvocation(eventDefinition, propertyKind, handlerName, parameterNames); + var lambdaParameters = BuildLambdaParameters(eventDefinition, propertyKind, parameterNames); var returnKeyword = eventDefinition.ReturnsVoid ? string.Empty : "return "; return new EventInitializer($"new {eventType}(({lambdaParameters}) =>", $"{returnKeyword}{handlerInvocation};", optionsArgument); } + /// + /// Determines whether the handler delegate matches a constructor of the event as declared. + /// + /// + /// Several arguments arrive as one generated record. + /// A handler that receives a cancellation token matches only the asynchronous constructors that pass the UIApplication too. + /// Every other handler is adapted with a lambda. + /// + private static bool UsesHandlerDelegate(ExternalEventDefinition eventDefinition, EventPropertyKind propertyKind) + { + if (eventDefinition.ExtraParameters.Length > 1) + { + return false; + } + + if (!eventDefinition.HasCancellationTokenParameter) + { + return true; + } + + return propertyKind != EventPropertyKind.Sync && eventDefinition.HasUiApplicationParameter; + } + private static string BuildExtensionMethodParameters(ExternalEventDefinition eventDefinition) { var parameters = new List(); @@ -384,6 +456,20 @@ private static string FormatGenericType(string typeName, string? typeArguments) private readonly record struct EventInitializer(string Expression, string? LambdaBody, string OptionsArgument); + private readonly record struct LambdaParameterNames + { + public required string Application { get; init; } + public required string Arguments { get; init; } + public required string CancellationToken { get; init; } + } + + private enum RaiseMethodKind + { + Raise, + RaiseAsync, + CancellableRaiseAsync + } + private enum EventPropertyKind { Sync, diff --git a/source/Nice3point.Revit.Toolkit.SourceGenerators/ExternalEvents/ExternalEventDefinition.cs b/source/Nice3point.Revit.Toolkit.SourceGenerators/ExternalEvents/ExternalEventDefinition.cs index 829c4d9..8d054f1 100644 --- a/source/Nice3point.Revit.Toolkit.SourceGenerators/ExternalEvents/ExternalEventDefinition.cs +++ b/source/Nice3point.Revit.Toolkit.SourceGenerators/ExternalEvents/ExternalEventDefinition.cs @@ -15,6 +15,7 @@ internal sealed record ExternalEventDefinition( string? FullyQualifiedReturnType, string FullyQualifiedDelegateType, bool HasUiApplicationParameter, + bool HasCancellationTokenParameter, bool AllowDirectInvocation, EquatableArray TypeHierarchy, EquatableArray ExtraParameters, diff --git a/source/Nice3point.Revit.Toolkit.SourceGenerators/ExternalEvents/ExternalEventTypeNames.cs b/source/Nice3point.Revit.Toolkit.SourceGenerators/ExternalEvents/ExternalEventTypeNames.cs index 35363ad..e95d98f 100644 --- a/source/Nice3point.Revit.Toolkit.SourceGenerators/ExternalEvents/ExternalEventTypeNames.cs +++ b/source/Nice3point.Revit.Toolkit.SourceGenerators/ExternalEvents/ExternalEventTypeNames.cs @@ -22,6 +22,7 @@ internal static class ExternalEventTypeNames public static readonly FullyQualifiedTypeName ExternalEventRequest = "Autodesk.Revit.UI.ExternalEventRequest"; // System types + public static readonly FullyQualifiedTypeName CancellationToken = "System.Threading.CancellationToken"; public static readonly FullyQualifiedTypeName Task = "System.Threading.Tasks.Task"; public static readonly FullyQualifiedTypeName TaskGeneric = "System.Threading.Tasks.Task`1"; } diff --git a/source/Nice3point.Revit.Toolkit/External/Events/AsyncExternalEvent.cs b/source/Nice3point.Revit.Toolkit/External/Events/AsyncExternalEvent.cs index 4f32512..5ea18a3 100644 --- a/source/Nice3point.Revit.Toolkit/External/Events/AsyncExternalEvent.cs +++ b/source/Nice3point.Revit.Toolkit/External/Events/AsyncExternalEvent.cs @@ -5,27 +5,21 @@ namespace Nice3point.Revit.Toolkit.External; /// -/// An asynchronous external event expanding -/// with the ability to await completion of the handler execution. +/// An asynchronous external event expanding with the ability to await completion of the handler execution. /// [PublicAPI] public sealed class AsyncExternalEvent : ExternalEventHandler, IAsyncExternalEvent { - private readonly Action? _handler; + private readonly AsyncExternalEventCore _core; private readonly ExternalEventOptions _options; - private readonly Action? _uiHandler; - private TaskCompletionSource? _taskCompletionSource; /// /// Initializes a new instance of the class. /// /// The execution logic. /// Thrown if is . - public AsyncExternalEvent(Action handler) + public AsyncExternalEvent(Action handler) : this(handler, ExternalEventOptions.None) { - ArgumentNullException.ThrowIfNull(handler); - - _handler = handler; } /// @@ -38,26 +32,26 @@ public AsyncExternalEvent(Action handler, ExternalEventOptions options) { ArgumentNullException.ThrowIfNull(handler); - _handler = handler; + _core = new((_, _, _) => + { + handler(); + return default; + }, base.Raise); + _options = options; } /// - /// Initializes a new instance of the class - /// with access to the instance. + /// Initializes a new instance of the class with access to the instance. /// /// The execution logic that receives the current . /// Thrown if is . - public AsyncExternalEvent(Action handler) + public AsyncExternalEvent(Action handler) : this(handler, ExternalEventOptions.None) { - ArgumentNullException.ThrowIfNull(handler); - - _uiHandler = handler; } /// - /// Initializes a new instance of the class - /// with access to the instance. + /// Initializes a new instance of the class with access to the instance. /// /// The execution logic that receives the current . /// The options to use to configure the external event. @@ -66,7 +60,40 @@ public AsyncExternalEvent(Action handler, ExternalEventOptions op { ArgumentNullException.ThrowIfNull(handler); - _uiHandler = handler; + _core = new((uiApplication, _, _) => + { + handler(uiApplication); + return default; + }, base.Raise); + + _options = options; + } + + /// + /// Initializes a new instance of the class with access to the instance and the cancellation token of the raise. + /// + /// The execution logic that receives the current and the token passed to . + /// Thrown if is . + public AsyncExternalEvent(Action handler) : this(handler, ExternalEventOptions.None) + { + } + + /// + /// Initializes a new instance of the class with access to the instance and the cancellation token of the raise. + /// + /// The execution logic that receives the current and the token passed to . + /// The options to use to configure the external event. + /// Thrown if is . + public AsyncExternalEvent(Action handler, ExternalEventOptions options) + { + ArgumentNullException.ThrowIfNull(handler); + + _core = new((uiApplication, _, cancellationToken) => + { + handler(uiApplication, cancellationToken); + return default; + }, base.Raise); + _options = options; } @@ -76,56 +103,61 @@ public AsyncExternalEvent(Action handler, ExternalEventOptions op /// The representing the async operation being executed. /// /// Revit will wait until it is ready to process the event and then it will execute its event handler. - /// Revit processes external events only when no other commands or edit modes are currently active in Revit, - /// which is the same policy like the one that applies to evoking external commands.

- /// When is specified and Revit is in API mode, - /// the handler is executed directly on the calling thread instead of being queued. + /// Revit processes external events only when no other commands or edit modes are currently active in Revit, which is the same policy like the one that applies to evoking external commands.
+ /// Each call executes the handler once, in the order of the calls. + /// The returned task completes with the outcome of that execution. + /// A call that Revit refuses with or fails its task with an without executing the handler.
+ /// When is specified and Revit is in API mode, the handler is executed directly on the calling thread instead of being queued. + /// This overload passes to . ///
public Task RaiseAsync() + { + return RaiseAsync(CancellationToken.None); + } + + /// + /// Raises the external event asynchronously, instructing Revit to execute the handler within the Revit API context. + /// + /// The token to monitor for cancellation requests. + /// The representing the async operation being executed. + /// + /// Revit will wait until it is ready to process the event and then it will execute its event handler. + /// Revit processes external events only when no other commands or edit modes are currently active in Revit, which is the same policy like the one that applies to evoking external commands.
+ /// Each call executes the handler once, in the order of the calls. + /// The returned task completes with the outcome of that execution. + /// A call that Revit refuses with or fails its task with an without executing the handler.
+ /// A cancellation requested before Revit executes the handler cancels the task at once without executing the handler. + /// A cancellation requested while the handler executes doesn't interrupt it. + /// An that the handler throws for the token cancels the task.
+ /// When is specified and Revit is in API mode, the handler is executed directly on the calling thread instead of being queued. + /// The token is checked once, before the handler executes. + ///
+ public Task RaiseAsync(CancellationToken cancellationToken) { if ((_options & ExternalEventOptions.AllowDirectInvocation) != 0 && RevitContext.IsRevitInApiMode) { - try - { - ExecuteHandler(RevitContext.UiApplication); - return Task.CompletedTask; - } - catch (Exception exception) - { - return Task.FromException(exception); - } + return _core.Invoke(RevitContext.UiApplication, default, cancellationToken); } - _taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - Raise(); - - return _taskCompletionSource.Task; + return _core.RaiseAsync(default, cancellationToken); } - /// Callback invoked by Revit. Not intended to be called in user code. - [EditorBrowsable(EditorBrowsableState.Never)] - public override void Execute(UIApplication uiApplication) + /// + /// + /// Each call queues a separate execution without waiting for completion. + /// The handler receives . + /// Revit catches an exception of the handler. + /// + public override ExternalEventRequest Raise() { - try - { - ExecuteHandler(uiApplication); - _taskCompletionSource?.SetResult(); - } - catch (Exception exception) - { - _taskCompletionSource?.SetException(exception); - } + return _core.Raise(default); } - private void ExecuteHandler(UIApplication uiApplication) + /// Callback invoked by Revit. + /// Not intended to be called in user code. + [EditorBrowsable(EditorBrowsableState.Never)] + public override void Execute(UIApplication uiApplication) { - if (_uiHandler is not null) - { - _uiHandler.Invoke(uiApplication); - } - else - { - _handler!.Invoke(); - } + _core.Execute(uiApplication); } } diff --git a/source/Nice3point.Revit.Toolkit/External/Events/AsyncExternalEventCore{TArgument,TResult}.cs b/source/Nice3point.Revit.Toolkit/External/Events/AsyncExternalEventCore{TArgument,TResult}.cs new file mode 100644 index 0000000..2426799 --- /dev/null +++ b/source/Nice3point.Revit.Toolkit/External/Events/AsyncExternalEventCore{TArgument,TResult}.cs @@ -0,0 +1,214 @@ +using System.Collections.Concurrent; +using System.Diagnostics.CodeAnalysis; +using Autodesk.Revit.UI; + +// ReSharper disable once CheckNamespace +namespace Nice3point.Revit.Toolkit.External; + +/// +/// Provides the queue of raises of an asynchronous external event, the execution of each raise, and the completion of its task. +/// +/// The type of the argument passed to the handler. +/// The type of the result produced by the handler. +/// The execution logic of the external event. +/// The function that raises the Revit external event. +/// +/// Revit runs a pending external event once however often it is raised. +/// Each execution runs the oldest raise and raises the event again for the rest. +/// When Revit refuses that raise, the rest runs in the same execution.
+/// The execution, the cancellation, and the refusal of a raise compete for the reservation of its completion. +/// Only the one that reserves it completes the task. +///
+internal sealed class AsyncExternalEventCore(Func handler, Func raise) +{ + private readonly ConcurrentQueue _operations = new(); + + /// + /// Adds a raise to the end of the queue without a completion task and raises the Revit external event. + /// + /// The argument to pass to the handler. + /// The result of the request to raise the Revit external event. + /// An exception of the handler propagates from . + public ExternalEventRequest Raise(TArgument argument) + { + return Enqueue(new Operation(argument)); + } + + /// + /// Executes the handler on the calling thread. + /// + /// The current instance. + /// The argument to pass to the handler. + /// The token to pass to the handler. + /// A completed task that contains the outcome of the handler, or a canceled task when is canceled before the handler executes. + public Task Invoke(UIApplication uiApplication, TArgument argument, CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + + try + { + return Task.FromResult(handler(uiApplication, argument, cancellationToken)); + } + catch (OperationCanceledException exception) when (IsCanceledBy(exception, cancellationToken)) + { + return Task.FromCanceled(cancellationToken); + } + catch (Exception exception) + { + return Task.FromException(exception); + } + } + + /// + /// Adds a raise to the end of the queue and raises the Revit external event. + /// + /// The argument to pass to the handler. + /// The token that withdraws the raise before its execution, passed to the handler. + /// A task that completes with the outcome of the handler, or a task canceled at the cancellation of before the execution. + /// A raise that Revit refuses fails its task with an without executing. + public Task RaiseAsync(TArgument argument, CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + + var operation = new Operation(argument, cancellationToken); + Enqueue(operation); + + return operation.Task!; + } + + /// + /// Executes the oldest raise in the queue that is not canceled and raises the Revit external event again for the rest. + /// + /// The current instance. + public void Execute(UIApplication uiApplication) + { + while (TryDequeueAndReserveCompletion(out var operation)) + { + var executesNextRaise = !_operations.IsEmpty && raise().IsRefused; + + try + { + operation.DangerousSetResult(handler(uiApplication, operation.Argument, operation.CancellationToken)); + } + catch (Exception exception) when (operation.Task is not null) + { + operation.DangerousSetException(exception); + } + + if (!executesNextRaise) + { + return; + } + } + } + + private ExternalEventRequest Enqueue(Operation operation) + { + _operations.Enqueue(operation); + + var request = raise(); + if (request.IsRefused) + { + operation.TrySetException(new InvalidOperationException($"Revit did not accept the external event request and returned the {request} result. Raise the external event again.")); + } + + return request; + } + + private bool TryDequeueAndReserveCompletion([NotNullWhen(true)] out Operation? operation) + { + while (_operations.TryDequeue(out operation)) + { + if (operation.TryReserveCompletion()) + { + return true; + } + } + + return false; + } + + private static bool IsCanceledBy(OperationCanceledException exception, CancellationToken cancellationToken) + { + return cancellationToken.IsCancellationRequested && exception.CancellationToken == cancellationToken; + } + + private sealed class Operation + { + private readonly TaskCompletionSource? _completionSource; + private CancellationTokenRegistration _cancellationRegistration; + private int _completionReserved; + + public Operation(TArgument argument) + { + Argument = argument; + } + + public Operation(TArgument argument, CancellationToken cancellationToken) : this(argument) + { + _completionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + CancellationToken = cancellationToken; + _cancellationRegistration = cancellationToken.Register(static state => ((Operation)state!).TrySetCanceled(), this); + } + + public TArgument Argument { get; private set; } + public CancellationToken CancellationToken { get; } + public Task? Task => _completionSource?.Task; + + public bool TryReserveCompletion() + { + if (Interlocked.Exchange(ref _completionReserved, 1) != 0) + { + return false; + } + + _cancellationRegistration.Dispose(); + return true; + } + + public bool TrySetException(Exception exception) + { + if (!TryReserveCompletion()) + { + return false; + } + + Argument = default!; + _completionSource?.TrySetException(exception); + return true; + } + + public void DangerousSetResult(TResult result) + { + _completionSource?.TrySetResult(result); + } + + public void DangerousSetException(Exception exception) + { + if (exception is OperationCanceledException canceledException && IsCanceledBy(canceledException, CancellationToken)) + { + _completionSource!.TrySetCanceled(CancellationToken); + return; + } + + _completionSource!.TrySetException(exception); + } + + private void TrySetCanceled() + { + if (!TryReserveCompletion()) + { + return; + } + + Argument = default!; + _completionSource!.TrySetCanceled(CancellationToken); + } + } +} diff --git a/source/Nice3point.Revit.Toolkit/External/Events/AsyncExternalEvent{T}.cs b/source/Nice3point.Revit.Toolkit/External/Events/AsyncExternalEvent{T}.cs index de2534e..8aef493 100644 --- a/source/Nice3point.Revit.Toolkit/External/Events/AsyncExternalEvent{T}.cs +++ b/source/Nice3point.Revit.Toolkit/External/Events/AsyncExternalEvent{T}.cs @@ -5,29 +5,24 @@ namespace Nice3point.Revit.Toolkit.External; /// -/// A generic asynchronous external event expanding -/// that accepts an argument of type . +/// A generic asynchronous external event expanding that accepts an argument of type . /// /// The type of the argument passed to the handler. [PublicAPI] public sealed class AsyncExternalEvent : ExternalEventHandler, IAsyncExternalEvent { - private readonly Action? _handler; + private readonly AsyncExternalEventCore _core; private readonly ExternalEventOptions _options; - private readonly Action? _uiHandler; + private readonly Lock _argumentLock = new(); private T _argument = default!; - private TaskCompletionSource? _taskCompletionSource; /// /// Initializes a new instance of the class. /// /// The execution logic that receives an argument of type . /// Thrown if is . - public AsyncExternalEvent(Action handler) + public AsyncExternalEvent(Action handler) : this(handler, ExternalEventOptions.None) { - ArgumentNullException.ThrowIfNull(handler); - - _handler = handler; } /// @@ -40,26 +35,26 @@ public AsyncExternalEvent(Action handler, ExternalEventOptions options) { ArgumentNullException.ThrowIfNull(handler); - _handler = handler; + _core = new((_, argument, _) => + { + handler(argument); + return default; + }, base.Raise); + _options = options; } /// - /// Initializes a new instance of the class - /// with access to the instance. + /// Initializes a new instance of the class with access to the instance. /// /// The execution logic that receives the current and an argument of type . /// Thrown if is . - public AsyncExternalEvent(Action handler) + public AsyncExternalEvent(Action handler) : this(handler, ExternalEventOptions.None) { - ArgumentNullException.ThrowIfNull(handler); - - _uiHandler = handler; } /// - /// Initializes a new instance of the class - /// with access to the instance. + /// Initializes a new instance of the class with access to the instance. /// /// The execution logic that receives the current and an argument of type . /// The options to use to configure the external event. @@ -68,70 +63,117 @@ public AsyncExternalEvent(Action handler, ExternalEventOptions { ArgumentNullException.ThrowIfNull(handler); - _uiHandler = handler; + _core = new((uiApplication, argument, _) => + { + handler(uiApplication, argument); + return default; + }, base.Raise); + + _options = options; + } + + /// + /// Initializes a new instance of the class with access to the instance and the cancellation token of the raise. + /// + /// The execution logic that receives the current , an argument of type , and the token passed to . + /// Thrown if is . + public AsyncExternalEvent(Action handler) : this(handler, ExternalEventOptions.None) + { + } + + /// + /// Initializes a new instance of the class with access to the instance and the cancellation token of the raise. + /// + /// The execution logic that receives the current , an argument of type , and the token passed to . + /// The options to use to configure the external event. + /// Thrown if is . + public AsyncExternalEvent(Action handler, ExternalEventOptions options) + { + ArgumentNullException.ThrowIfNull(handler); + + _core = new((uiApplication, argument, cancellationToken) => + { + handler(uiApplication, argument, cancellationToken); + return default; + }, base.Raise); + _options = options; } /// - /// Raises the external event asynchronously with the specified argument, - /// instructing Revit to execute the handler within the Revit API context. + /// Raises the external event asynchronously with the specified argument, instructing Revit to execute the handler within the Revit API context. /// /// The argument to pass to the handler. /// The representing the async operation being executed. /// /// Revit will wait until it is ready to process the event and then it will execute its event handler. - /// Revit processes external events only when no other commands or edit modes are currently active in Revit, - /// which is the same policy like the one that applies to evoking external commands.

- /// When is specified and Revit is in API mode, - /// the handler is executed directly on the calling thread instead of being queued. + /// Revit processes external events only when no other commands or edit modes are currently active in Revit, which is the same policy like the one that applies to evoking external commands.
+ /// Each call executes the handler once with its own argument, in the order of the calls. + /// The returned task completes with the outcome of that execution. + /// A call that Revit refuses with or fails its task with an without executing the handler.
+ /// When is specified and Revit is in API mode, the handler is executed directly on the calling thread instead of being queued. + /// This overload passes to . ///
public Task RaiseAsync(T argument) { - _argument = argument; + return RaiseAsync(argument, CancellationToken.None); + } - if ((_options & ExternalEventOptions.AllowDirectInvocation) != 0 && RevitContext.IsRevitInApiMode) + /// + /// Raises the external event asynchronously with the specified argument, instructing Revit to execute the handler within the Revit API context. + /// + /// The argument to pass to the handler. + /// The token to monitor for cancellation requests. + /// The representing the async operation being executed. + /// + /// Revit will wait until it is ready to process the event and then it will execute its event handler. + /// Revit processes external events only when no other commands or edit modes are currently active in Revit, which is the same policy like the one that applies to evoking external commands.
+ /// Each call executes the handler once with its own argument, in the order of the calls. + /// The returned task completes with the outcome of that execution. + /// A call that Revit refuses with or fails its task with an without executing the handler.
+ /// A cancellation requested before Revit executes the handler cancels the task at once without executing the handler. + /// A cancellation requested while the handler executes doesn't interrupt it. + /// An that the handler throws for the token cancels the task.
+ /// When is specified and Revit is in API mode, the handler is executed directly on the calling thread instead of being queued. + /// The token is checked once, before the handler executes. + ///
+ public Task RaiseAsync(T argument, CancellationToken cancellationToken) + { + lock (_argumentLock) { - try - { - ExecuteHandler(RevitContext.UiApplication); - return Task.CompletedTask; - } - catch (Exception exception) - { - return Task.FromException(exception); - } + _argument = argument; } - _taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - Raise(); + if ((_options & ExternalEventOptions.AllowDirectInvocation) != 0 && RevitContext.IsRevitInApiMode) + { + return _core.Invoke(RevitContext.UiApplication, argument, cancellationToken); + } - return _taskCompletionSource.Task; + return _core.RaiseAsync(argument, cancellationToken); } - /// Callback invoked by Revit. Not intended to be called in user code. - [EditorBrowsable(EditorBrowsableState.Never)] - public override void Execute(UIApplication uiApplication) + /// + /// + /// Each call queues a separate execution with the last argument passed to , or the default value of before the first call. + /// The handler receives . + /// Revit catches an exception of the handler. + /// + public override ExternalEventRequest Raise() { - try + T argument; + lock (_argumentLock) { - ExecuteHandler(uiApplication); - _taskCompletionSource?.SetResult(); - } - catch (Exception exception) - { - _taskCompletionSource?.SetException(exception); + argument = _argument; } + + return _core.Raise(argument); } - private void ExecuteHandler(UIApplication uiApplication) + /// Callback invoked by Revit. + /// Not intended to be called in user code. + [EditorBrowsable(EditorBrowsableState.Never)] + public override void Execute(UIApplication uiApplication) { - if (_uiHandler is not null) - { - _uiHandler.Invoke(uiApplication, _argument); - } - else - { - _handler!.Invoke(_argument); - } + _core.Execute(uiApplication); } } diff --git a/source/Nice3point.Revit.Toolkit/External/Events/AsyncRequestExternalEvent{T,TResult}.cs b/source/Nice3point.Revit.Toolkit/External/Events/AsyncRequestExternalEvent{T,TResult}.cs index 1c2ea3a..e6c2484 100644 --- a/source/Nice3point.Revit.Toolkit/External/Events/AsyncRequestExternalEvent{T,TResult}.cs +++ b/source/Nice3point.Revit.Toolkit/External/Events/AsyncRequestExternalEvent{T,TResult}.cs @@ -5,31 +5,25 @@ namespace Nice3point.Revit.Toolkit.External; /// -/// A generic asynchronous external event expanding -/// that accepts an argument of type -/// and returns a result of type via . +/// A generic asynchronous external event expanding that accepts an argument of type and returns a result of type via . /// /// The type of the argument passed to the handler. /// The type of the result produced by the handler. [PublicAPI] public sealed class AsyncRequestExternalEvent : ExternalEventHandler, IAsyncRequestExternalEvent { - private readonly Func? _handler; + private readonly AsyncExternalEventCore _core; private readonly ExternalEventOptions _options; - private readonly Func? _uiHandler; + private readonly Lock _argumentLock = new(); private T _argument = default!; - private TaskCompletionSource? _taskCompletionSource; /// /// Initializes a new instance of the class. /// /// The execution logic that receives an argument of type and returns a result of type . /// Thrown if is . - public AsyncRequestExternalEvent(Func handler) + public AsyncRequestExternalEvent(Func handler) : this(handler, ExternalEventOptions.None) { - ArgumentNullException.ThrowIfNull(handler); - - _handler = handler; } /// @@ -42,26 +36,21 @@ public AsyncRequestExternalEvent(Func handler, ExternalEventOptions { ArgumentNullException.ThrowIfNull(handler); - _handler = handler; + _core = new((_, argument, _) => handler(argument), base.Raise); _options = options; } /// - /// Initializes a new instance of the class - /// with access to the instance. + /// Initializes a new instance of the class with access to the instance. /// /// The execution logic that receives the current and an argument of type , returning a result of type . /// Thrown if is . - public AsyncRequestExternalEvent(Func handler) + public AsyncRequestExternalEvent(Func handler) : this(handler, ExternalEventOptions.None) { - ArgumentNullException.ThrowIfNull(handler); - - _uiHandler = handler; } /// - /// Initializes a new instance of the class - /// with access to the instance. + /// Initializes a new instance of the class with access to the instance. /// /// The execution logic that receives the current and an argument of type , returning a result of type . /// The options to use to configure the external event. @@ -70,73 +59,107 @@ public AsyncRequestExternalEvent(Func handler, Extern { ArgumentNullException.ThrowIfNull(handler); - _uiHandler = handler; + _core = new((uiApplication, argument, _) => handler(uiApplication, argument), base.Raise); + _options = options; + } + + /// + /// Initializes a new instance of the class with access to the instance and the cancellation token of the raise. + /// + /// The execution logic that receives the current , an argument of type , and the token passed to , returning a result of type . + /// Thrown if is . + public AsyncRequestExternalEvent(Func handler) : this(handler, ExternalEventOptions.None) + { + } + + /// + /// Initializes a new instance of the class with access to the instance and the cancellation token of the raise. + /// + /// The execution logic that receives the current , an argument of type , and the token passed to , returning a result of type . + /// The options to use to configure the external event. + /// Thrown if is . + public AsyncRequestExternalEvent(Func handler, ExternalEventOptions options) + { + ArgumentNullException.ThrowIfNull(handler); + + _core = new(handler, base.Raise); _options = options; } /// - /// Raises the external event asynchronously with the specified argument, - /// instructing Revit to execute the handler within the Revit API context. + /// Raises the external event asynchronously with the specified argument, instructing Revit to execute the handler within the Revit API context. /// /// The argument to pass to the handler. /// The representing the async operation being executed, containing the result of type . /// /// Revit will wait until it is ready to process the event and then it will execute its event handler. - /// Revit processes external events only when no other commands or edit modes are currently active in Revit, - /// which is the same policy like the one that applies to evoking external commands.

- /// When is specified and Revit is in API mode, - /// the handler is executed directly on the calling thread instead of being queued. + /// Revit processes external events only when no other commands or edit modes are currently active in Revit, which is the same policy like the one that applies to evoking external commands.
+ /// Each call executes the handler once with its own argument, in the order of the calls. + /// The returned task completes with the outcome of that execution. + /// A call that Revit refuses with or fails its task with an without executing the handler.
+ /// When is specified and Revit is in API mode, the handler is executed directly on the calling thread instead of being queued. + /// This overload passes to . ///
public Task RaiseAsync(T argument) { - _argument = argument; - - if ((_options & ExternalEventOptions.AllowDirectInvocation) != 0 && RevitContext.IsRevitInApiMode) - { - try - { - var result = ExecuteHandler(RevitContext.UiApplication); - return Task.FromResult(result); - } - catch (Exception exception) - { - return Task.FromException(exception); - } - } - - _taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - Raise(); - - return _taskCompletionSource.Task; + return RaiseAsync(argument, CancellationToken.None); } - /// Callback invoked by Revit. Not intended to be called in user code. - [EditorBrowsable(EditorBrowsableState.Never)] - public override void Execute(UIApplication uiApplication) + /// + /// Raises the external event asynchronously with the specified argument, instructing Revit to execute the handler within the Revit API context. + /// + /// The argument to pass to the handler. + /// The token to monitor for cancellation requests. + /// The representing the async operation being executed, containing the result of type . + /// + /// Revit will wait until it is ready to process the event and then it will execute its event handler. + /// Revit processes external events only when no other commands or edit modes are currently active in Revit, which is the same policy like the one that applies to evoking external commands.
+ /// Each call executes the handler once with its own argument, in the order of the calls. + /// The returned task completes with the outcome of that execution. + /// A call that Revit refuses with or fails its task with an without executing the handler.
+ /// A cancellation requested before Revit executes the handler cancels the task at once without executing the handler. + /// A cancellation requested while the handler executes doesn't interrupt it. + /// An that the handler throws for the token cancels the task.
+ /// When is specified and Revit is in API mode, the handler is executed directly on the calling thread instead of being queued. + /// The token is checked once, before the handler executes. + ///
+ public Task RaiseAsync(T argument, CancellationToken cancellationToken) { - try + lock (_argumentLock) { - var result = ExecuteHandler(uiApplication); - _taskCompletionSource?.SetResult(result); + _argument = argument; } - catch (Exception exception) + + if ((_options & ExternalEventOptions.AllowDirectInvocation) != 0 && RevitContext.IsRevitInApiMode) { - _taskCompletionSource?.SetException(exception); + return _core.Invoke(RevitContext.UiApplication, argument, cancellationToken); } + + return _core.RaiseAsync(argument, cancellationToken); } - private TResult ExecuteHandler(UIApplication uiApplication) + /// + /// + /// Each call queues a separate execution with the last argument passed to , or the default value of before the first call. + /// The handler receives ; its result is discarded. + /// Revit catches an exception of the handler. + /// + public override ExternalEventRequest Raise() { - TResult result; - if (_uiHandler is not null) - { - result = _uiHandler.Invoke(uiApplication, _argument); - } - else + T argument; + lock (_argumentLock) { - result = _handler!.Invoke(_argument); + argument = _argument; } - return result; + return _core.Raise(argument); + } + + /// Callback invoked by Revit. + /// Not intended to be called in user code. + [EditorBrowsable(EditorBrowsableState.Never)] + public override void Execute(UIApplication uiApplication) + { + _core.Execute(uiApplication); } } diff --git a/source/Nice3point.Revit.Toolkit/External/Events/AsyncRequestExternalEvent{TResult}.cs b/source/Nice3point.Revit.Toolkit/External/Events/AsyncRequestExternalEvent{TResult}.cs index c4f6008..356d89c 100644 --- a/source/Nice3point.Revit.Toolkit/External/Events/AsyncRequestExternalEvent{TResult}.cs +++ b/source/Nice3point.Revit.Toolkit/External/Events/AsyncRequestExternalEvent{TResult}.cs @@ -5,28 +5,22 @@ namespace Nice3point.Revit.Toolkit.External; /// -/// A generic asynchronous external event expanding -/// to return a result of type via . +/// A generic asynchronous external event expanding to return a result of type via . /// /// The type of the result produced by the handler. [PublicAPI] public sealed class AsyncRequestExternalEvent : ExternalEventHandler, IAsyncRequestExternalEvent { - private readonly Func? _handler; + private readonly AsyncExternalEventCore _core; private readonly ExternalEventOptions _options; - private readonly Func? _uiHandler; - private TaskCompletionSource? _taskCompletionSource; /// /// Initializes a new instance of the class. /// /// The execution logic that returns a result of type . /// Thrown if is . - public AsyncRequestExternalEvent(Func handler) + public AsyncRequestExternalEvent(Func handler) : this(handler, ExternalEventOptions.None) { - ArgumentNullException.ThrowIfNull(handler); - - _handler = handler; } /// @@ -39,26 +33,21 @@ public AsyncRequestExternalEvent(Func handler, ExternalEventOptions opt { ArgumentNullException.ThrowIfNull(handler); - _handler = handler; + _core = new((_, _, _) => handler(), base.Raise); _options = options; } /// - /// Initializes a new instance of the class - /// with access to the instance. + /// Initializes a new instance of the class with access to the instance. /// /// The execution logic that receives the current and returns a result of type . /// Thrown if is . - public AsyncRequestExternalEvent(Func handler) + public AsyncRequestExternalEvent(Func handler) : this(handler, ExternalEventOptions.None) { - ArgumentNullException.ThrowIfNull(handler); - - _uiHandler = handler; } /// - /// Initializes a new instance of the class - /// with access to the instance. + /// Initializes a new instance of the class with access to the instance. /// /// The execution logic that receives the current and returns a result of type . /// The options to use to configure the external event. @@ -67,7 +56,30 @@ public AsyncRequestExternalEvent(Func handler, ExternalE { ArgumentNullException.ThrowIfNull(handler); - _uiHandler = handler; + _core = new((uiApplication, _, _) => handler(uiApplication), base.Raise); + _options = options; + } + + /// + /// Initializes a new instance of the class with access to the instance and the cancellation token of the raise. + /// + /// The execution logic that receives the current and the token passed to , returning a result of type . + /// Thrown if is . + public AsyncRequestExternalEvent(Func handler) : this(handler, ExternalEventOptions.None) + { + } + + /// + /// Initializes a new instance of the class with access to the instance and the cancellation token of the raise. + /// + /// The execution logic that receives the current and the token passed to , returning a result of type . + /// The options to use to configure the external event. + /// Thrown if is . + public AsyncRequestExternalEvent(Func handler, ExternalEventOptions options) + { + ArgumentNullException.ThrowIfNull(handler); + + _core = new((uiApplication, _, cancellationToken) => handler(uiApplication, cancellationToken), base.Raise); _options = options; } @@ -77,59 +89,61 @@ public AsyncRequestExternalEvent(Func handler, ExternalE /// The representing the async operation being executed, containing the result of type . /// /// Revit will wait until it is ready to process the event and then it will execute its event handler. - /// Revit processes external events only when no other commands or edit modes are currently active in Revit, - /// which is the same policy like the one that applies to evoking external commands.

- /// When is specified and Revit is in API mode, - /// the handler is executed directly on the calling thread instead of being queued. + /// Revit processes external events only when no other commands or edit modes are currently active in Revit, which is the same policy like the one that applies to evoking external commands.
+ /// Each call executes the handler once, in the order of the calls. + /// The returned task completes with the outcome of that execution. + /// A call that Revit refuses with or fails its task with an without executing the handler.
+ /// When is specified and Revit is in API mode, the handler is executed directly on the calling thread instead of being queued. + /// This overload passes to . ///
public Task RaiseAsync() + { + return RaiseAsync(CancellationToken.None); + } + + /// + /// Raises the external event asynchronously, instructing Revit to execute the handler within the Revit API context. + /// + /// The token to monitor for cancellation requests. + /// The representing the async operation being executed, containing the result of type . + /// + /// Revit will wait until it is ready to process the event and then it will execute its event handler. + /// Revit processes external events only when no other commands or edit modes are currently active in Revit, which is the same policy like the one that applies to evoking external commands.
+ /// Each call executes the handler once, in the order of the calls. + /// The returned task completes with the outcome of that execution. + /// A call that Revit refuses with or fails its task with an without executing the handler.
+ /// A cancellation requested before Revit executes the handler cancels the task at once without executing the handler. + /// A cancellation requested while the handler executes doesn't interrupt it. + /// An that the handler throws for the token cancels the task.
+ /// When is specified and Revit is in API mode, the handler is executed directly on the calling thread instead of being queued. + /// The token is checked once, before the handler executes. + ///
+ public Task RaiseAsync(CancellationToken cancellationToken) { if ((_options & ExternalEventOptions.AllowDirectInvocation) != 0 && RevitContext.IsRevitInApiMode) { - try - { - var result = ExecuteHandler(RevitContext.UiApplication); - return Task.FromResult(result); - } - catch (Exception exception) - { - return Task.FromException(exception); - } + return _core.Invoke(RevitContext.UiApplication, default, cancellationToken); } - _taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - Raise(); - - return _taskCompletionSource.Task; + return _core.RaiseAsync(default, cancellationToken); } - /// Callback invoked by Revit. Not intended to be called in user code. - [EditorBrowsable(EditorBrowsableState.Never)] - public override void Execute(UIApplication uiApplication) + /// + /// + /// Each call queues a separate execution without waiting for completion. + /// The handler receives ; its result is discarded. + /// Revit catches an exception of the handler. + /// + public override ExternalEventRequest Raise() { - try - { - var result = ExecuteHandler(uiApplication); - _taskCompletionSource?.SetResult(result); - } - catch (Exception exception) - { - _taskCompletionSource?.SetException(exception); - } + return _core.Raise(default); } - private TResult ExecuteHandler(UIApplication uiApplication) + /// Callback invoked by Revit. + /// Not intended to be called in user code. + [EditorBrowsable(EditorBrowsableState.Never)] + public override void Execute(UIApplication uiApplication) { - TResult result; - if (_uiHandler is not null) - { - result = _uiHandler.Invoke(uiApplication); - } - else - { - result = _handler!.Invoke(); - } - - return result; + _core.Execute(uiApplication); } } diff --git a/source/Nice3point.Revit.Toolkit/External/Events/Attributes/ExternalEventAttribute.cs b/source/Nice3point.Revit.Toolkit/External/Events/Attributes/ExternalEventAttribute.cs index b94838f..4a472ca 100644 --- a/source/Nice3point.Revit.Toolkit/External/Events/Attributes/ExternalEventAttribute.cs +++ b/source/Nice3point.Revit.Toolkit/External/Events/Attributes/ExternalEventAttribute.cs @@ -75,8 +75,21 @@ namespace Nice3point.Revit.Toolkit.External; /// // Extension methods: /// public static ExternalEventRequest Raise(this IExternalEvent<DeleteInstancesArgs> externalEvent, BuiltInCategory category, int count); /// public static Task RaiseAsync(this IAsyncExternalEvent<DeleteInstancesArgs> externalEvent, BuiltInCategory category, int count); +/// public static Task RaiseAsync(this IAsyncExternalEvent<DeleteInstancesArgs> externalEvent, BuiltInCategory category, int count, CancellationToken cancellationToken); /// /// +/// +/// Methods with a cancellation token. +/// A method can declare a as its last parameter. +/// The token is not an extra parameter: +/// +/// void Method(ElementId, CancellationToken); +/// +/// Will generate an property and an property with an ElementId argument. +/// The asynchronous property passes the token of RaiseAsync to the method. +/// The synchronous property passes . +/// A in any other position is an extra parameter. +/// ///
[PublicAPI] [AttributeUsage(AttributeTargets.Method, Inherited = false)] diff --git a/source/Nice3point.Revit.Toolkit/External/Events/ExternalEvent.cs b/source/Nice3point.Revit.Toolkit/External/Events/ExternalEvent.cs index 836c96f..a8f82d6 100644 --- a/source/Nice3point.Revit.Toolkit/External/Events/ExternalEvent.cs +++ b/source/Nice3point.Revit.Toolkit/External/Events/ExternalEvent.cs @@ -5,10 +5,9 @@ namespace Nice3point.Revit.Toolkit.External; /// -/// An external event whose sole purpose is to relay its functionality to other -/// objects by invoking delegates. The default behavior queues the handler -/// via the Revit external event mechanism. This type allows you to accept -/// a parameter in the callback overload. +/// An external event whose sole purpose is to relay its functionality to other objects by invoking delegates. +/// The default behavior queues the handler via the Revit external event mechanism. +/// The constructor passes the current to the handler. /// [PublicAPI] public class ExternalEvent : ExternalEventHandler, IExternalEvent @@ -44,8 +43,7 @@ public ExternalEvent(Action handler, ExternalEventOptions options) } /// - /// Initializes a new instance of the class - /// with access to the instance. + /// Initializes a new instance of the class with access to the instance. /// /// The execution logic that receives the current . /// Thrown if is . @@ -57,8 +55,7 @@ public ExternalEvent(Action handler) } /// - /// Initializes a new instance of the class - /// with access to the instance. + /// Initializes a new instance of the class with access to the instance. /// /// The execution logic that receives the current . /// The options to use to configure the external event. @@ -75,16 +72,13 @@ public ExternalEvent(Action handler, ExternalEventOptions options /// Raises (signals) the external event, instructing Revit to execute the handler within the Revit API context. ///
/// - /// The result of event raising request. If the request is 'Accepted', - /// the event would be added to the event queue and its handler will - /// be executed in the next event-processing cycle. + /// The result of event raising request. + /// If the request is 'Accepted', the event would be added to the event queue and its handler will be executed in the next event-processing cycle. /// /// /// Revit will wait until it is ready to process the event and then it will execute its event handler. - /// Revit processes external events only when no other commands or edit modes are currently active in Revit, - /// which is the same policy like the one that applies to evoking external commands.

- /// When is specified and Revit is in API mode, - /// the handler is executed directly on the calling thread instead of being queued. + /// Revit processes external events only when no other commands or edit modes are currently active in Revit, which is the same policy like the one that applies to evoking external commands.
+ /// When is specified and Revit is in API mode, the handler is executed directly on the calling thread instead of being queued. ///
public override ExternalEventRequest Raise() { @@ -97,7 +91,8 @@ public override ExternalEventRequest Raise() return base.Raise(); } - /// Callback invoked by Revit. Not intended to be called in user code. + /// Callback invoked by Revit. + /// Not intended to be called in user code. [EditorBrowsable(EditorBrowsableState.Never)] public override void Execute(UIApplication uiApplication) { diff --git a/source/Nice3point.Revit.Toolkit/External/Events/ExternalEventCore{TArgument}.cs b/source/Nice3point.Revit.Toolkit/External/Events/ExternalEventCore{TArgument}.cs new file mode 100644 index 0000000..b9cb020 --- /dev/null +++ b/source/Nice3point.Revit.Toolkit/External/Events/ExternalEventCore{TArgument}.cs @@ -0,0 +1,98 @@ +using System.Collections.Concurrent; +using System.Diagnostics.CodeAnalysis; +using Autodesk.Revit.UI; + +// ReSharper disable once CheckNamespace +namespace Nice3point.Revit.Toolkit.External; + +/// +/// Provides the queue of raises of an external event with an argument and the execution of each raise. +/// +/// The type of the argument passed to the handler. +/// The execution logic of the external event. +/// The function that raises the Revit external event. +/// +/// Revit runs a pending external event once however often it is raised. +/// Each execution runs the oldest raise and raises the event again for the rest. +/// When Revit refuses that raise, the rest runs in the same execution.
+/// The execution and the refusal of a raise compete for its reservation. +/// A raise that Revit refuses never executes. +///
+internal sealed class ExternalEventCore(Action handler, Func raise) +{ + private readonly ConcurrentQueue _operations = new(); + + /// + /// Executes the handler on the calling thread. + /// + /// The current instance. + /// The argument to pass to the handler. + public void Invoke(UIApplication uiApplication, TArgument argument) + { + handler(uiApplication, argument); + } + + /// + /// Adds a raise to the end of the queue and raises the Revit external event. + /// + /// The argument to pass to the handler. + /// The result of the request to raise the Revit external event. + /// A raise that Revit refuses leaves the queue unexecuted. + public ExternalEventRequest Raise(TArgument argument) + { + var operation = new Operation(argument); + _operations.Enqueue(operation); + + var request = raise(); + if (request.IsRefused) + { + operation.TryReserveExecution(); + } + + return request; + } + + /// + /// Executes the oldest raise in the queue and raises the Revit external event again for the rest. + /// + /// The current instance. + /// An exception of the handler propagates to the caller. + public void Execute(UIApplication uiApplication) + { + while (TryDequeueAndReserveExecution(out var operation)) + { + var executesNextRaise = !_operations.IsEmpty && raise().IsRefused; + handler(uiApplication, operation.Argument); + + if (!executesNextRaise) + { + return; + } + } + } + + private bool TryDequeueAndReserveExecution([NotNullWhen(true)] out Operation? operation) + { + while (_operations.TryDequeue(out operation)) + { + if (operation.TryReserveExecution()) + { + return true; + } + } + + return false; + } + + private sealed class Operation(TArgument argument) + { + private int _executionReserved; + + public TArgument Argument { get; } = argument; + + public bool TryReserveExecution() + { + return Interlocked.Exchange(ref _executionReserved, 1) == 0; + } + } +} diff --git a/source/Nice3point.Revit.Toolkit/External/Events/ExternalEventRequestExtensions.cs b/source/Nice3point.Revit.Toolkit/External/Events/ExternalEventRequestExtensions.cs new file mode 100644 index 0000000..0a91a60 --- /dev/null +++ b/source/Nice3point.Revit.Toolkit/External/Events/ExternalEventRequestExtensions.cs @@ -0,0 +1,22 @@ +using Autodesk.Revit.UI; + +// ReSharper disable once CheckNamespace +namespace Nice3point.Revit.Toolkit.External; + +/// +/// Contains extension members for . +/// +internal static class ExternalEventRequestExtensions +{ + /// + /// Defines extensions for . + /// + /// The result of a request to raise an external event. + extension(ExternalEventRequest request) + { + /// + /// Gets a value indicating whether Revit refused to raise the external event. + /// + public bool IsRefused => request is ExternalEventRequest.Denied or ExternalEventRequest.TimedOut; + } +} diff --git a/source/Nice3point.Revit.Toolkit/External/Events/ExternalEvent{T}.cs b/source/Nice3point.Revit.Toolkit/External/Events/ExternalEvent{T}.cs index 0005873..68b48c5 100644 --- a/source/Nice3point.Revit.Toolkit/External/Events/ExternalEvent{T}.cs +++ b/source/Nice3point.Revit.Toolkit/External/Events/ExternalEvent{T}.cs @@ -5,17 +5,15 @@ namespace Nice3point.Revit.Toolkit.External; /// -/// A generic synchronous external event expanding -/// that accepts an argument of type -/// and relays its functionality by invoking delegates within the Revit API context. +/// A generic synchronous external event expanding that accepts an argument of type and relays its functionality by invoking delegates within the Revit API context. /// /// The type of the argument passed to the handler. [PublicAPI] public class ExternalEvent : ExternalEventHandler, IExternalEvent { - private readonly Action? _handler; + private readonly ExternalEventCore _core; private readonly ExternalEventOptions _options; - private readonly Action? _uiHandler; + private readonly Lock _argumentLock = new(); private T _argument = default!; /// @@ -23,11 +21,8 @@ public class ExternalEvent : ExternalEventHandler, IExternalEvent /// /// The execution logic that receives an argument of type . /// Thrown if is . - public ExternalEvent(Action handler) + public ExternalEvent(Action handler) : this(handler, ExternalEventOptions.None) { - ArgumentNullException.ThrowIfNull(handler); - - _handler = handler; } /// @@ -40,26 +35,21 @@ public ExternalEvent(Action handler, ExternalEventOptions options) { ArgumentNullException.ThrowIfNull(handler); - _handler = handler; + _core = new((_, argument) => handler(argument), base.Raise); _options = options; } /// - /// Initializes a new instance of the class - /// with access to the instance. + /// Initializes a new instance of the class with access to the instance. /// /// The execution logic that receives the current and an argument of type . /// Thrown if is . - public ExternalEvent(Action handler) + public ExternalEvent(Action handler) : this(handler, ExternalEventOptions.None) { - ArgumentNullException.ThrowIfNull(handler); - - _uiHandler = handler; } /// - /// Initializes a new instance of the class - /// with access to the instance. + /// Initializes a new instance of the class with access to the instance. /// /// The execution logic that receives the current and an argument of type . /// The options to use to configure the external event. @@ -68,51 +58,61 @@ public ExternalEvent(Action handler, ExternalEventOptions opti { ArgumentNullException.ThrowIfNull(handler); - _uiHandler = handler; + _core = new(handler, base.Raise); _options = options; } /// - /// Raises (signals) the external event with the specified argument, - /// instructing Revit to execute the handler within the Revit API context. + /// Raises (signals) the external event with the specified argument, instructing Revit to execute the handler within the Revit API context. /// /// The argument to pass to the handler. /// - /// The result of event raising request. If the request is 'Accepted', - /// the event would be added to the event queue and its handler will - /// be executed in the next event-processing cycle. + /// The result of event raising request. + /// If the request is 'Accepted', the event would be added to the event queue and its handler will be executed in the next event-processing cycle. /// /// /// Revit will wait until it is ready to process the event and then it will execute its event handler. - /// Revit processes external events only when no other commands or edit modes are currently active in Revit, - /// which is the same policy like the one that applies to evoking external commands.

- /// When is specified and Revit is in API mode, - /// the handler is executed directly on the calling thread instead of being queued. + /// Revit processes external events only when no other commands or edit modes are currently active in Revit, which is the same policy like the one that applies to evoking external commands.
+ /// Each call executes the handler once with its own argument, in the order of the calls. + /// A call that returns or never executes the handler.
+ /// When is specified and Revit is in API mode, the handler is executed directly on the calling thread instead of being queued. ///
public ExternalEventRequest Raise(T argument) { - _argument = argument; + lock (_argumentLock) + { + _argument = argument; + } if ((_options & ExternalEventOptions.AllowDirectInvocation) != 0 && RevitContext.IsRevitInApiMode) { - Execute(RevitContext.UiApplication); + _core.Invoke(RevitContext.UiApplication, argument); return ExternalEventRequest.Accepted; } - return base.Raise(); + return _core.Raise(argument); } - /// Callback invoked by Revit. Not intended to be called in user code. - [EditorBrowsable(EditorBrowsableState.Never)] - public override void Execute(UIApplication uiApplication) + /// + /// + /// Each call queues a separate execution with the last argument passed to , or the default value of before the first call. + /// + public override ExternalEventRequest Raise() { - if (_uiHandler is not null) - { - _uiHandler.Invoke(uiApplication, _argument); - } - else + T argument; + lock (_argumentLock) { - _handler!.Invoke(_argument); + argument = _argument; } + + return _core.Raise(argument); + } + + /// Callback invoked by Revit. + /// Not intended to be called in user code. + [EditorBrowsable(EditorBrowsableState.Never)] + public override void Execute(UIApplication uiApplication) + { + _core.Execute(uiApplication); } } diff --git a/source/Nice3point.Revit.Toolkit/External/Events/Interfaces/IAsyncExternalEvent.cs b/source/Nice3point.Revit.Toolkit/External/Events/Interfaces/IAsyncExternalEvent.cs index 3f5341b..eac5567 100644 --- a/source/Nice3point.Revit.Toolkit/External/Events/Interfaces/IAsyncExternalEvent.cs +++ b/source/Nice3point.Revit.Toolkit/External/Events/Interfaces/IAsyncExternalEvent.cs @@ -13,4 +13,15 @@ public interface IAsyncExternalEvent ///
/// The representing the async operation being executed. Task RaiseAsync(); + + /// + /// Raises (signals) the external event, instructing Revit to execute the handler within the Revit API context and wait for completion asynchronously. + /// + /// The token to monitor for cancellation requests. + /// The representing the async operation being executed. + /// + /// A cancellation requested before Revit executes the handler cancels the task at once without executing the handler. + /// A cancellation requested while the handler executes doesn't interrupt it. + /// + Task RaiseAsync(CancellationToken cancellationToken); } diff --git a/source/Nice3point.Revit.Toolkit/External/Events/Interfaces/IAsyncExternalEvent{T}.cs b/source/Nice3point.Revit.Toolkit/External/Events/Interfaces/IAsyncExternalEvent{T}.cs index bd0c40c..e00ea7d 100644 --- a/source/Nice3point.Revit.Toolkit/External/Events/Interfaces/IAsyncExternalEvent{T}.cs +++ b/source/Nice3point.Revit.Toolkit/External/Events/Interfaces/IAsyncExternalEvent{T}.cs @@ -15,4 +15,16 @@ public interface IAsyncExternalEvent /// The argument to pass to the handler. /// The representing the async operation being executed. Task RaiseAsync(T argument); + + /// + /// Raises (signals) the external event with the specified argument, instructing Revit to execute the handler within the Revit API context and wait for completion asynchronously. + /// + /// The argument to pass to the handler. + /// The token to monitor for cancellation requests. + /// The representing the async operation being executed. + /// + /// A cancellation requested before Revit executes the handler cancels the task at once without executing the handler. + /// A cancellation requested while the handler executes doesn't interrupt it. + /// + Task RaiseAsync(T argument, CancellationToken cancellationToken); } diff --git a/source/Nice3point.Revit.Toolkit/External/Events/Interfaces/IAsyncRequestExternalEvent{T,TResult}.cs b/source/Nice3point.Revit.Toolkit/External/Events/Interfaces/IAsyncRequestExternalEvent{T,TResult}.cs index 5a777ca..9369dea 100644 --- a/source/Nice3point.Revit.Toolkit/External/Events/Interfaces/IAsyncRequestExternalEvent{T,TResult}.cs +++ b/source/Nice3point.Revit.Toolkit/External/Events/Interfaces/IAsyncRequestExternalEvent{T,TResult}.cs @@ -17,4 +17,16 @@ public interface IAsyncRequestExternalEvent /// The argument to pass to the handler. /// The representing the async operation being executed, containing the result of type . Task RaiseAsync(T argument); + + /// + /// Raises (signals) the external event with the specified argument, instructing Revit to execute the handler within the Revit API context and wait for completion asynchronously. + /// + /// The argument to pass to the handler. + /// The token to monitor for cancellation requests. + /// The representing the async operation being executed, containing the result of type . + /// + /// A cancellation requested before Revit executes the handler cancels the task at once without executing the handler. + /// A cancellation requested while the handler executes doesn't interrupt it. + /// + Task RaiseAsync(T argument, CancellationToken cancellationToken); } diff --git a/source/Nice3point.Revit.Toolkit/External/Events/Interfaces/IAsyncRequestExternalEvent{TResult}.cs b/source/Nice3point.Revit.Toolkit/External/Events/Interfaces/IAsyncRequestExternalEvent{TResult}.cs index 87fc058..d3c4ab8 100644 --- a/source/Nice3point.Revit.Toolkit/External/Events/Interfaces/IAsyncRequestExternalEvent{TResult}.cs +++ b/source/Nice3point.Revit.Toolkit/External/Events/Interfaces/IAsyncRequestExternalEvent{TResult}.cs @@ -15,4 +15,15 @@ public interface IAsyncRequestExternalEvent ///
/// The representing the async operation being executed, containing the result of type . Task RaiseAsync(); + + /// + /// Raises (signals) the external event, instructing Revit to execute the handler within the Revit API context and wait for completion asynchronously. + /// + /// The token to monitor for cancellation requests. + /// The representing the async operation being executed, containing the result of type . + /// + /// A cancellation requested before Revit executes the handler cancels the task at once without executing the handler. + /// A cancellation requested while the handler executes doesn't interrupt it. + /// + Task RaiseAsync(CancellationToken cancellationToken); } diff --git a/source/Nice3point.Revit.Toolkit/External/Events/VoidResult.cs b/source/Nice3point.Revit.Toolkit/External/Events/VoidResult.cs new file mode 100644 index 0000000..f19e9cd --- /dev/null +++ b/source/Nice3point.Revit.Toolkit/External/Events/VoidResult.cs @@ -0,0 +1,8 @@ +// ReSharper disable once CheckNamespace + +namespace Nice3point.Revit.Toolkit.External; + +/// +/// Represents the absent argument or result of an asynchronous external event. +/// +internal readonly struct VoidResult; diff --git a/source/Nice3point.Revit.Toolkit/Nice3point.Revit.Toolkit.csproj b/source/Nice3point.Revit.Toolkit/Nice3point.Revit.Toolkit.csproj index 7d56518..901a560 100644 --- a/source/Nice3point.Revit.Toolkit/Nice3point.Revit.Toolkit.csproj +++ b/source/Nice3point.Revit.Toolkit/Nice3point.Revit.Toolkit.csproj @@ -38,6 +38,10 @@ + + + + diff --git a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/ExtensionContainerTests.cs b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/ExtensionContainerTests.cs index 4abda90..ea7de7d 100644 --- a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/ExtensionContainerTests.cs +++ b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/ExtensionContainerTests.cs @@ -58,7 +58,7 @@ [ExternalEvent] private void Run(int first, int second) { } var generatedContainer = output.GetTypeByMetadataName("Consumer.ModelExtensions")!; await Assert.That(generatedContainer.DeclaredAccessibility).IsEqualTo(containerAccessibility == "public" ? Accessibility.Public : Accessibility.Internal); await Assert.That(generatedContainer.GetMembers("Raise")).Count().IsEqualTo(1); - await Assert.That(generatedContainer.GetMembers("RaiseAsync")).Count().IsEqualTo(1); + await Assert.That(generatedContainer.GetMembers("RaiseAsync")).Count().IsEqualTo(2); } [Test] diff --git a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/ExternalEventGeneratorExecutionTests.cs b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/ExternalEventGeneratorExecutionTests.cs index 369f1cf..809e5c0 100644 --- a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/ExternalEventGeneratorExecutionTests.cs +++ b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/ExternalEventGeneratorExecutionTests.cs @@ -1,6 +1,7 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Testing; using Nice3point.Revit.Toolkit.SourceGenerators.Tests.Helpers; +using TUnit.Assertions.Enums; namespace Nice3point.Revit.Toolkit.SourceGenerators.Tests; @@ -175,4 +176,48 @@ await second.RunAsyncEvent.RaiseAsync(2), await Assert.That(result[1]).IsEqualTo(22); await Assert.That(result[2]).IsZero(); } + + [Test] + public async Task CancellationTokenHandler_ReceivesTheTokenOfTheRaiseAsync() + { + const string source = """ + using System.Threading; + using System.Threading.Tasks; + using Nice3point.Revit.Toolkit.External; + + namespace Consumer; + + public partial class Model + { + public CancellationToken ReceivedToken; + + [ExternalEvent] + private bool Run(int quantity, string label, CancellationToken cancellationToken) => cancellationToken.CanBeCanceled; + + [ExternalEvent] + private void Notify(CancellationToken cancellationToken) => ReceivedToken = cancellationToken; + } + + public static class Probe + { + public static async Task RunAsync() + { + using var cancellationSource = new CancellationTokenSource(); + var model = new Model(); + var extensionToken = await model.RunAsyncEvent.RaiseAsync(42, "Walls", cancellationSource.Token); + var recordToken = await model.RunAsyncEvent.RaiseAsync(new Model.RunArgs(42, "Walls"), cancellationSource.Token); + var omittedToken = await model.RunAsyncEvent.RaiseAsync(42, "Walls"); + model.NotifyEvent.Raise(); + var synchronousToken = model.ReceivedToken.CanBeCanceled; + await model.NotifyAsyncEvent.RaiseAsync(cancellationSource.Token); + return new[] { extensionToken, recordToken, omittedToken, synchronousToken, model.ReceivedToken == cancellationSource.Token }; + } + } + """; + + var (driver, input) = await GeneratorTest.RunAsync(source); + var result = await GeneratorTest.ExecuteAsync(driver, input); + + await Assert.That(result).IsEquivalentTo([true, true, false, false, true], CollectionOrdering.Matching); + } } diff --git a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/ExternalEventGeneratorNameBindingTests.cs b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/ExternalEventGeneratorNameBindingTests.cs index cb7a6d4..960abb1 100644 --- a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/ExternalEventGeneratorNameBindingTests.cs +++ b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/ExternalEventGeneratorNameBindingTests.cs @@ -38,6 +38,36 @@ public void UseGeneratedMembers() await Verify(driver.GetRunResult()).UseParameters(methodName); } + [Test] + [Arguments("Method", "cancellationToken", "System.Threading.CancellationToken token", "")] + [Arguments("Argument", "Run", "int cancellationToken, int count, System.Threading.CancellationToken token", "2, 5")] + public async Task CancellationTokenName_KeepsDistinctBindingsAsync(string scenario, string methodName, string parameters, string arguments) + { + var cancellableArguments = arguments.Length == 0 ? "System.Threading.CancellationToken.None" : $"{arguments}, System.Threading.CancellationToken.None"; + var source = $$""" + using Nice3point.Revit.Toolkit.External; + + namespace Consumer; + + public partial class Model + { + [ExternalEvent] + private void {{methodName}}({{parameters}}) { } + + public void UseGeneratedMembers() + { + _ = {{methodName}}Event.Raise({{arguments}}); + _ = {{methodName}}AsyncEvent.RaiseAsync({{cancellableArguments}}); + } + } + """; + + var (driver, _) = await GeneratorTest.RunAsync(source); + + await Assert.That(driver.GetRunResult().GeneratedTrees.Length).IsEqualTo(1); + await Verify(driver.GetRunResult()).UseParameters(scenario); + } + [Test] [Arguments("clone")] [Arguments("equals")] diff --git a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/ExternalEventGeneratorRegressionTests.cs b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/ExternalEventGeneratorRegressionTests.cs index a320c14..b77137e 100644 --- a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/ExternalEventGeneratorRegressionTests.cs +++ b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/ExternalEventGeneratorRegressionTests.cs @@ -201,6 +201,37 @@ private void Run(int quantity, string label) { } await Verify(driver.GetRunResult()).UseParameters(declaration, languageVersion); } + [Test] + [Arguments("struct", LanguageVersion.CSharp13)] + [Arguments("struct", LanguageVersion.CSharp14)] + [Arguments("record struct", LanguageVersion.CSharp14)] + public async Task StructInstanceMethod_WithCancellationTokenGeneratesCompilableEventsAsync(string declaration, LanguageVersion languageVersion) + { + var source = $$""" + using System.Threading; + using Nice3point.Revit.Toolkit.External; + + namespace Consumer; + + public partial {{declaration}} Model + { + [ExternalEvent] + private void Run(int quantity, CancellationToken cancellationToken) { } + + public void UseGeneratedMembers() + { + _ = RunEvent.Raise(42); + _ = RunAsyncEvent.RaiseAsync(42, CancellationToken.None); + } + } + """; + + var (driver, _) = await GeneratorTest.RunAsync(source, languageVersion: languageVersion); + + await Assert.That(driver.GetRunResult().GeneratedTrees).Count().IsEqualTo(1); + await Verify(driver.GetRunResult()).UseParameters(declaration, languageVersion); + } + [Test] [Arguments("Capitalization", "int value, int Value")] [Arguments("ExtensionReceiver", "int externalEvent, int quantity")] diff --git a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/ExternalEventGeneratorTests.cs b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/ExternalEventGeneratorTests.cs index 7f77492..90b50f9 100644 --- a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/ExternalEventGeneratorTests.cs +++ b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/ExternalEventGeneratorTests.cs @@ -8,19 +8,31 @@ namespace Nice3point.Revit.Toolkit.SourceGenerators.Tests; public sealed class ExternalEventGeneratorTests { [Test] - [Arguments("Void", false, false, 0)] - [Arguments("VoidApplication", false, true, 0)] - [Arguments("VoidArgument", false, false, 1)] - [Arguments("VoidApplicationArgument", false, true, 1)] - [Arguments("VoidArguments", false, false, 2)] - [Arguments("VoidApplicationArguments", false, true, 2)] - [Arguments("Result", true, false, 0)] - [Arguments("ResultApplication", true, true, 0)] - [Arguments("ResultArgument", true, false, 1)] - [Arguments("ResultApplicationArgument", true, true, 1)] - [Arguments("ResultArguments", true, false, 2)] - [Arguments("ResultApplicationArguments", true, true, 2)] - public async Task MethodSignature_GeneratesCompilableEventsAsync(string scenario, bool returnsValue, bool hasApplication, int argumentCount) + [Arguments("Void", false, false, 0, false)] + [Arguments("VoidApplication", false, true, 0, false)] + [Arguments("VoidArgument", false, false, 1, false)] + [Arguments("VoidApplicationArgument", false, true, 1, false)] + [Arguments("VoidArguments", false, false, 2, false)] + [Arguments("VoidApplicationArguments", false, true, 2, false)] + [Arguments("Result", true, false, 0, false)] + [Arguments("ResultApplication", true, true, 0, false)] + [Arguments("ResultArgument", true, false, 1, false)] + [Arguments("ResultApplicationArgument", true, true, 1, false)] + [Arguments("ResultArguments", true, false, 2, false)] + [Arguments("ResultApplicationArguments", true, true, 2, false)] + [Arguments("VoidToken", false, false, 0, true)] + [Arguments("VoidApplicationToken", false, true, 0, true)] + [Arguments("VoidArgumentToken", false, false, 1, true)] + [Arguments("VoidApplicationArgumentToken", false, true, 1, true)] + [Arguments("VoidArgumentsToken", false, false, 2, true)] + [Arguments("VoidApplicationArgumentsToken", false, true, 2, true)] + [Arguments("ResultToken", true, false, 0, true)] + [Arguments("ResultApplicationToken", true, true, 0, true)] + [Arguments("ResultArgumentToken", true, false, 1, true)] + [Arguments("ResultApplicationArgumentToken", true, true, 1, true)] + [Arguments("ResultArgumentsToken", true, false, 2, true)] + [Arguments("ResultApplicationArgumentsToken", true, true, 2, true)] + public async Task MethodSignature_GeneratesCompilableEventsAsync(string scenario, bool returnsValue, bool hasApplication, int argumentCount, bool hasCancellationToken) { var parameters = new List(); if (hasApplication) @@ -38,6 +50,11 @@ public async Task MethodSignature_GeneratesCompilableEventsAsync(string scenario parameters.Add("string label"); } + if (hasCancellationToken) + { + parameters.Add("System.Threading.CancellationToken cancellationToken"); + } + var arguments = argumentCount switch { 0 => string.Empty, @@ -45,6 +62,7 @@ public async Task MethodSignature_GeneratesCompilableEventsAsync(string scenario _ => "42, \"Walls\"" }; + var cancellableArguments = argumentCount == 0 ? "System.Threading.CancellationToken.None" : $"{arguments}, System.Threading.CancellationToken.None"; var returnType = returnsValue ? "int" : "void"; var body = returnsValue ? "=> 42;" : "{ }"; var synchronousCall = returnsValue ? string.Empty : $"_ = RunEvent.Raise({arguments});"; @@ -62,6 +80,7 @@ public void UseGeneratedMembers() { {{synchronousCall}} _ = RunAsyncEvent.RaiseAsync({{arguments}}); + _ = RunAsyncEvent.RaiseAsync({{cancellableArguments}}); } } """; @@ -357,6 +376,35 @@ public void UseGeneratedMembers(UIApplication application) await Verify(driver.GetRunResult()); } + [Test] + [Arguments("BeforeLastParameter", "System.Threading.CancellationToken token, int quantity", "System.Threading.CancellationToken.None, 42")] + [Arguments("NullableLastParameter", "int quantity, System.Threading.CancellationToken? token", "42, null")] + public async Task CancellationTokenOutsideTheLastParameter_RemainsUserArgumentAsync(string scenario, string parameters, string arguments) + { + var source = $$""" + using Nice3point.Revit.Toolkit.External; + + namespace Consumer; + + public partial class Model + { + [ExternalEvent] + private void Run({{parameters}}) { } + + public void UseGeneratedMembers() + { + _ = RunEvent.Raise({{arguments}}); + _ = RunAsyncEvent.RaiseAsync({{arguments}}, System.Threading.CancellationToken.None); + } + } + """; + + var (driver, _) = await GeneratorTest.RunAsync(source); + + await Assert.That(driver.GetRunResult().GeneratedTrees).Count().IsEqualTo(1); + await Verify(driver.GetRunResult()).UseParameters(scenario); + } + [Test] public async Task UnrelatedEdit_ReusesGeneratedOutputAsync() { diff --git a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Fixtures/ExternalEvents.cs b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Fixtures/ExternalEvents.cs index b7b83d0..5adf8cc 100644 --- a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Fixtures/ExternalEvents.cs +++ b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Fixtures/ExternalEvents.cs @@ -1,4 +1,5 @@ using System; +using System.Threading; using System.Threading.Tasks; namespace Autodesk.Revit.UI @@ -31,10 +32,10 @@ public enum ExternalEventOptions { None = 0, AllowDirectInvocation = 1 } public interface IExternalEvent { Autodesk.Revit.UI.ExternalEventRequest Raise(); } public interface IExternalEvent { Autodesk.Revit.UI.ExternalEventRequest Raise(T args); } - public interface IAsyncExternalEvent { Task RaiseAsync(); } - public interface IAsyncExternalEvent { Task RaiseAsync(T args); } - public interface IAsyncRequestExternalEvent { Task RaiseAsync(); } - public interface IAsyncRequestExternalEvent { Task RaiseAsync(T args); } + public interface IAsyncExternalEvent { Task RaiseAsync(); Task RaiseAsync(CancellationToken cancellationToken); } + public interface IAsyncExternalEvent { Task RaiseAsync(T args); Task RaiseAsync(T args, CancellationToken cancellationToken); } + public interface IAsyncRequestExternalEvent { Task RaiseAsync(); Task RaiseAsync(CancellationToken cancellationToken); } + public interface IAsyncRequestExternalEvent { Task RaiseAsync(T args); Task RaiseAsync(T args, CancellationToken cancellationToken); } public class ExternalEvent : IExternalEvent { @@ -56,37 +57,65 @@ public ExternalEvent(Action handler, Externa public class AsyncExternalEvent : IAsyncExternalEvent { - private readonly Action _handler; - public AsyncExternalEvent(Action handler, ExternalEventOptions options = default) { _handler = handler; EventConstruction.Observer?.Invoke(); } + private readonly Action _handler; + public AsyncExternalEvent(Action handler, ExternalEventOptions options = default) + : this((application, cancellationToken) => handler(), options) { } public AsyncExternalEvent(Action handler, ExternalEventOptions options = default) - : this(() => handler(new Autodesk.Revit.UI.UIApplication()), options) { } - public Task RaiseAsync() { _handler(); return Task.CompletedTask; } + : this((application, cancellationToken) => handler(application), options) { } + public AsyncExternalEvent(Action handler, ExternalEventOptions options = default) + { + _handler = cancellationToken => handler(new Autodesk.Revit.UI.UIApplication(), cancellationToken); + EventConstruction.Observer?.Invoke(); + } + public Task RaiseAsync() => RaiseAsync(CancellationToken.None); + public Task RaiseAsync(CancellationToken cancellationToken) { _handler(cancellationToken); return Task.CompletedTask; } } public class AsyncExternalEvent : IAsyncExternalEvent { - private readonly Action _handler; - public AsyncExternalEvent(Action handler, ExternalEventOptions options = default) { _handler = handler; EventConstruction.Observer?.Invoke(); } + private readonly Action _handler; + public AsyncExternalEvent(Action handler, ExternalEventOptions options = default) + : this((application, args, cancellationToken) => handler(args), options) { } public AsyncExternalEvent(Action handler, ExternalEventOptions options = default) - : this(args => handler(new Autodesk.Revit.UI.UIApplication(), args), options) { } - public Task RaiseAsync(T args) { _handler(args); return Task.CompletedTask; } + : this((application, args, cancellationToken) => handler(application, args), options) { } + public AsyncExternalEvent(Action handler, ExternalEventOptions options = default) + { + _handler = (args, cancellationToken) => handler(new Autodesk.Revit.UI.UIApplication(), args, cancellationToken); + EventConstruction.Observer?.Invoke(); + } + public Task RaiseAsync(T args) => RaiseAsync(args, CancellationToken.None); + public Task RaiseAsync(T args, CancellationToken cancellationToken) { _handler(args, cancellationToken); return Task.CompletedTask; } } public class AsyncRequestExternalEvent : IAsyncRequestExternalEvent { - private readonly Func _handler; - public AsyncRequestExternalEvent(Func handler, ExternalEventOptions options = default) { _handler = handler; EventConstruction.Observer?.Invoke(); } + private readonly Func _handler; + public AsyncRequestExternalEvent(Func handler, ExternalEventOptions options = default) + : this((application, cancellationToken) => handler(), options) { } public AsyncRequestExternalEvent(Func handler, ExternalEventOptions options = default) - : this(() => handler(new Autodesk.Revit.UI.UIApplication()), options) { } - public Task RaiseAsync() => Task.FromResult(_handler()); + : this((application, cancellationToken) => handler(application), options) { } + public AsyncRequestExternalEvent(Func handler, ExternalEventOptions options = default) + { + _handler = cancellationToken => handler(new Autodesk.Revit.UI.UIApplication(), cancellationToken); + EventConstruction.Observer?.Invoke(); + } + public Task RaiseAsync() => RaiseAsync(CancellationToken.None); + public Task RaiseAsync(CancellationToken cancellationToken) => Task.FromResult(_handler(cancellationToken)); } public class AsyncRequestExternalEvent : IAsyncRequestExternalEvent { - private readonly Func _handler; - public AsyncRequestExternalEvent(Func handler, ExternalEventOptions options = default) { _handler = handler; EventConstruction.Observer?.Invoke(); } + private readonly Func _handler; + public AsyncRequestExternalEvent(Func handler, ExternalEventOptions options = default) + : this((application, args, cancellationToken) => handler(args), options) { } public AsyncRequestExternalEvent(Func handler, ExternalEventOptions options = default) - : this(args => handler(new Autodesk.Revit.UI.UIApplication(), args), options) { } - public Task RaiseAsync(T args) => Task.FromResult(_handler(args)); + : this((application, args, cancellationToken) => handler(application, args), options) { } + public AsyncRequestExternalEvent(Func handler, ExternalEventOptions options = default) + { + _handler = (args, cancellationToken) => handler(new Autodesk.Revit.UI.UIApplication(), args, cancellationToken); + EventConstruction.Observer?.Invoke(); + } + public Task RaiseAsync(T args) => RaiseAsync(args, CancellationToken.None); + public Task RaiseAsync(T args, CancellationToken cancellationToken) => Task.FromResult(_handler(args, cancellationToken)); } } diff --git a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.CancellationTokenName_KeepsDistinctBindingsAsync_scenario=Argument#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.CancellationTokenName_KeepsDistinctBindingsAsync_scenario=Argument#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs new file mode 100644 index 0000000..26460b1 --- /dev/null +++ b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.CancellationTokenName_KeepsDistinctBindingsAsync_scenario=Argument#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs @@ -0,0 +1,74 @@ +//HintName: Consumer.Model.Run.C0F8FDCA0BFA30FC.cs +// +#pragma warning disable +#nullable enable + +namespace Consumer +{ + public partial class Model + { + private object? _RunEventLock; + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public global::Nice3point.Revit.Toolkit.External.IExternalEvent RunEvent + { + get + { + lock (global::System.Threading.LazyInitializer.EnsureInitialized(ref _RunEventLock)) + { + return field ??= new global::Nice3point.Revit.Toolkit.External.ExternalEvent((args) => + { + Run(args.CancellationToken, args.Count, global::System.Threading.CancellationToken.None); + }); + } + } + } + + private object? _RunAsyncEventLock; + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public global::Nice3point.Revit.Toolkit.External.IAsyncExternalEvent RunAsyncEvent + { + get + { + lock (global::System.Threading.LazyInitializer.EnsureInitialized(ref _RunAsyncEventLock)) + { + return field ??= new global::Nice3point.Revit.Toolkit.External.AsyncExternalEvent((application, args, cancellationToken) => + { + Run(args.CancellationToken, args.Count, cancellationToken); + }); + } + } + } + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public sealed record RunArgs(int CancellationToken, int Count); + } + + public static partial class ModelExtensions + { + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public static global::Autodesk.Revit.UI.ExternalEventRequest Raise(this global::Nice3point.Revit.Toolkit.External.IExternalEvent externalEvent, int cancellationToken, int count) + { + return externalEvent.Raise(new global::Consumer.Model.RunArgs(cancellationToken, count)); + } + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public static global::System.Threading.Tasks.Task RaiseAsync(this global::Nice3point.Revit.Toolkit.External.IAsyncExternalEvent externalEvent, int cancellationToken, int count) + { + return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(cancellationToken, count)); + } + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public static global::System.Threading.Tasks.Task RaiseAsync(this global::Nice3point.Revit.Toolkit.External.IAsyncExternalEvent externalEvent, int cancellationToken, int count, global::System.Threading.CancellationToken cancellationToken1) + { + return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(cancellationToken, count), cancellationToken1); + } + } +} diff --git a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.CancellationTokenName_KeepsDistinctBindingsAsync_scenario=Method#Consumer.Model.cancellationToken.2CA1BB3A517766C8.verified.cs b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.CancellationTokenName_KeepsDistinctBindingsAsync_scenario=Method#Consumer.Model.cancellationToken.2CA1BB3A517766C8.verified.cs new file mode 100644 index 0000000..d593329 --- /dev/null +++ b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.CancellationTokenName_KeepsDistinctBindingsAsync_scenario=Method#Consumer.Model.cancellationToken.2CA1BB3A517766C8.verified.cs @@ -0,0 +1,46 @@ +//HintName: Consumer.Model.cancellationToken.2CA1BB3A517766C8.cs +// +#pragma warning disable +#nullable enable + +namespace Consumer +{ + public partial class Model + { + private object? _cancellationTokenEventLock; + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public global::Nice3point.Revit.Toolkit.External.IExternalEvent cancellationTokenEvent + { + get + { + lock (global::System.Threading.LazyInitializer.EnsureInitialized(ref _cancellationTokenEventLock)) + { + return field ??= new global::Nice3point.Revit.Toolkit.External.ExternalEvent(() => + { + cancellationToken(global::System.Threading.CancellationToken.None); + }); + } + } + } + + private object? _cancellationTokenAsyncEventLock; + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public global::Nice3point.Revit.Toolkit.External.IAsyncExternalEvent cancellationTokenAsyncEvent + { + get + { + lock (global::System.Threading.LazyInitializer.EnsureInitialized(ref _cancellationTokenAsyncEventLock)) + { + return field ??= new global::Nice3point.Revit.Toolkit.External.AsyncExternalEvent((application, cancellationToken1) => + { + cancellationToken(cancellationToken1); + }); + } + } + } + } +} diff --git a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.DifferentGenericArities_PreserveIndependentExtensionAccessibilityAsync_ordinaryAccessibility=internal_genericAccessibility=public#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.DifferentGenericArities_PreserveIndependentExtensionAccessibilityAsync_ordinaryAccessibility=internal_genericAccessibility=public#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs index c5cda72..aac9c12 100644 --- a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.DifferentGenericArities_PreserveIndependentExtensionAccessibilityAsync_ordinaryAccessibility=internal_genericAccessibility=public#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs +++ b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.DifferentGenericArities_PreserveIndependentExtensionAccessibilityAsync_ordinaryAccessibility=internal_genericAccessibility=public#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs @@ -63,5 +63,12 @@ public static partial class ModelExtensions { return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(value, count)); } + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + internal static global::System.Threading.Tasks.Task RaiseAsync(this global::Nice3point.Revit.Toolkit.External.IAsyncExternalEvent externalEvent, int value, int count, global::System.Threading.CancellationToken cancellationToken) + { + return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(value, count), cancellationToken); + } } } diff --git a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.DifferentGenericArities_PreserveIndependentExtensionAccessibilityAsync_ordinaryAccessibility=internal_genericAccessibility=public#Consumer.Model`1.Run.BFAFF1DAC26910EF.verified.cs b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.DifferentGenericArities_PreserveIndependentExtensionAccessibilityAsync_ordinaryAccessibility=internal_genericAccessibility=public#Consumer.Model`1.Run.BFAFF1DAC26910EF.verified.cs index c88f45f..eb4269a 100644 --- a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.DifferentGenericArities_PreserveIndependentExtensionAccessibilityAsync_ordinaryAccessibility=internal_genericAccessibility=public#Consumer.Model`1.Run.BFAFF1DAC26910EF.verified.cs +++ b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.DifferentGenericArities_PreserveIndependentExtensionAccessibilityAsync_ordinaryAccessibility=internal_genericAccessibility=public#Consumer.Model`1.Run.BFAFF1DAC26910EF.verified.cs @@ -63,5 +63,12 @@ public static partial class ModelExtensions { return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(value, count)); } + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public static global::System.Threading.Tasks.Task RaiseAsync(this global::Nice3point.Revit.Toolkit.External.IAsyncExternalEvent.RunArgs> externalEvent, TValue value, int count, global::System.Threading.CancellationToken cancellationToken) + { + return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(value, count), cancellationToken); + } } } diff --git a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.DifferentGenericArities_PreserveIndependentExtensionAccessibilityAsync_ordinaryAccessibility=public_genericAccessibility=internal#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.DifferentGenericArities_PreserveIndependentExtensionAccessibilityAsync_ordinaryAccessibility=public_genericAccessibility=internal#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs index 1f6380b..87bc08f 100644 --- a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.DifferentGenericArities_PreserveIndependentExtensionAccessibilityAsync_ordinaryAccessibility=public_genericAccessibility=internal#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs +++ b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.DifferentGenericArities_PreserveIndependentExtensionAccessibilityAsync_ordinaryAccessibility=public_genericAccessibility=internal#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs @@ -63,5 +63,12 @@ public static partial class ModelExtensions { return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(value, count)); } + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public static global::System.Threading.Tasks.Task RaiseAsync(this global::Nice3point.Revit.Toolkit.External.IAsyncExternalEvent externalEvent, int value, int count, global::System.Threading.CancellationToken cancellationToken) + { + return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(value, count), cancellationToken); + } } } diff --git a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.DifferentGenericArities_PreserveIndependentExtensionAccessibilityAsync_ordinaryAccessibility=public_genericAccessibility=internal#Consumer.Model`1.Run.BFAFF1DAC26910EF.verified.cs b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.DifferentGenericArities_PreserveIndependentExtensionAccessibilityAsync_ordinaryAccessibility=public_genericAccessibility=internal#Consumer.Model`1.Run.BFAFF1DAC26910EF.verified.cs index 3d90e28..bc222e9 100644 --- a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.DifferentGenericArities_PreserveIndependentExtensionAccessibilityAsync_ordinaryAccessibility=public_genericAccessibility=internal#Consumer.Model`1.Run.BFAFF1DAC26910EF.verified.cs +++ b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.DifferentGenericArities_PreserveIndependentExtensionAccessibilityAsync_ordinaryAccessibility=public_genericAccessibility=internal#Consumer.Model`1.Run.BFAFF1DAC26910EF.verified.cs @@ -63,5 +63,12 @@ public static partial class ModelExtensions { return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(value, count)); } + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + internal static global::System.Threading.Tasks.Task RaiseAsync(this global::Nice3point.Revit.Toolkit.External.IAsyncExternalEvent.RunArgs> externalEvent, TValue value, int count, global::System.Threading.CancellationToken cancellationToken) + { + return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(value, count), cancellationToken); + } } } diff --git a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.FieldContextualKeyword_PreservesNamespaceAndTypeBindingAsync_namespaceName=Consumer_typeName=field#Consumer.field.Run.6145C3C7A0D4FA0F.verified.cs b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.FieldContextualKeyword_PreservesNamespaceAndTypeBindingAsync_namespaceName=Consumer_typeName=field#Consumer.field.Run.6145C3C7A0D4FA0F.verified.cs index e93a076..0e09a0f 100644 --- a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.FieldContextualKeyword_PreservesNamespaceAndTypeBindingAsync_namespaceName=Consumer_typeName=field#Consumer.field.Run.6145C3C7A0D4FA0F.verified.cs +++ b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.FieldContextualKeyword_PreservesNamespaceAndTypeBindingAsync_namespaceName=Consumer_typeName=field#Consumer.field.Run.6145C3C7A0D4FA0F.verified.cs @@ -63,5 +63,12 @@ public static partial class @fieldExtensions { return externalEvent.RaiseAsync(new global::Consumer.field.RunArgs(value, count)); } + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public static global::System.Threading.Tasks.Task RaiseAsync(this global::Nice3point.Revit.Toolkit.External.IAsyncExternalEvent externalEvent, int value, int count, global::System.Threading.CancellationToken cancellationToken) + { + return externalEvent.RaiseAsync(new global::Consumer.field.RunArgs(value, count), cancellationToken); + } } } diff --git a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.FieldContextualKeyword_PreservesNamespaceAndTypeBindingAsync_namespaceName=field_typeName=Model#field.Model.Run.8F529F91CA4A8D2A.verified.cs b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.FieldContextualKeyword_PreservesNamespaceAndTypeBindingAsync_namespaceName=field_typeName=Model#field.Model.Run.8F529F91CA4A8D2A.verified.cs index f12981b..26b6785 100644 --- a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.FieldContextualKeyword_PreservesNamespaceAndTypeBindingAsync_namespaceName=field_typeName=Model#field.Model.Run.8F529F91CA4A8D2A.verified.cs +++ b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.FieldContextualKeyword_PreservesNamespaceAndTypeBindingAsync_namespaceName=field_typeName=Model#field.Model.Run.8F529F91CA4A8D2A.verified.cs @@ -63,5 +63,12 @@ public static partial class ModelExtensions { return externalEvent.RaiseAsync(new global::field.Model.RunArgs(value, count)); } + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public static global::System.Threading.Tasks.Task RaiseAsync(this global::Nice3point.Revit.Toolkit.External.IAsyncExternalEvent externalEvent, int value, int count, global::System.Threading.CancellationToken cancellationToken) + { + return externalEvent.RaiseAsync(new global::field.Model.RunArgs(value, count), cancellationToken); + } } } diff --git a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.MethodName_MatchesGeneratedLocalNameWithoutShadowingAsync_methodName=application#Consumer.Model.application.3E69A3B91C661309.verified.cs b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.MethodName_MatchesGeneratedLocalNameWithoutShadowingAsync_methodName=application#Consumer.Model.application.3E69A3B91C661309.verified.cs index 45c7f5c..2d97e7d 100644 --- a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.MethodName_MatchesGeneratedLocalNameWithoutShadowingAsync_methodName=application#Consumer.Model.application.3E69A3B91C661309.verified.cs +++ b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.MethodName_MatchesGeneratedLocalNameWithoutShadowingAsync_methodName=application#Consumer.Model.application.3E69A3B91C661309.verified.cs @@ -63,5 +63,12 @@ public static partial class ModelExtensions { return externalEvent.RaiseAsync(new global::Consumer.Model.applicationArgs(first, second)); } + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public static global::System.Threading.Tasks.Task RaiseAsync(this global::Nice3point.Revit.Toolkit.External.IAsyncExternalEvent externalEvent, int first, int second, global::System.Threading.CancellationToken cancellationToken) + { + return externalEvent.RaiseAsync(new global::Consumer.Model.applicationArgs(first, second), cancellationToken); + } } } diff --git a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.MethodName_MatchesGeneratedLocalNameWithoutShadowingAsync_methodName=args#Consumer.Model.args.2DAA7B354C035418.verified.cs b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.MethodName_MatchesGeneratedLocalNameWithoutShadowingAsync_methodName=args#Consumer.Model.args.2DAA7B354C035418.verified.cs index 9922726..ee35fcb 100644 --- a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.MethodName_MatchesGeneratedLocalNameWithoutShadowingAsync_methodName=args#Consumer.Model.args.2DAA7B354C035418.verified.cs +++ b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.MethodName_MatchesGeneratedLocalNameWithoutShadowingAsync_methodName=args#Consumer.Model.args.2DAA7B354C035418.verified.cs @@ -63,5 +63,12 @@ public static partial class ModelExtensions { return externalEvent.RaiseAsync(new global::Consumer.Model.argsArgs(first, second)); } + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public static global::System.Threading.Tasks.Task RaiseAsync(this global::Nice3point.Revit.Toolkit.External.IAsyncExternalEvent externalEvent, int first, int second, global::System.Threading.CancellationToken cancellationToken) + { + return externalEvent.RaiseAsync(new global::Consumer.Model.argsArgs(first, second), cancellationToken); + } } } diff --git a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.MethodName_MatchesGeneratedLocalNameWithoutShadowingAsync_methodName=handler#Consumer.Model.handler.3D61EBDA00260524.verified.cs b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.MethodName_MatchesGeneratedLocalNameWithoutShadowingAsync_methodName=handler#Consumer.Model.handler.3D61EBDA00260524.verified.cs index 36ccf02..c7119a2 100644 --- a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.MethodName_MatchesGeneratedLocalNameWithoutShadowingAsync_methodName=handler#Consumer.Model.handler.3D61EBDA00260524.verified.cs +++ b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.MethodName_MatchesGeneratedLocalNameWithoutShadowingAsync_methodName=handler#Consumer.Model.handler.3D61EBDA00260524.verified.cs @@ -75,5 +75,12 @@ public static partial class ModelExtensions { return externalEvent.RaiseAsync(new global::Consumer.Model.handlerArgs(first, second)); } + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public static global::System.Threading.Tasks.Task RaiseAsync(this global::Nice3point.Revit.Toolkit.External.IAsyncExternalEvent externalEvent, int first, int second, global::System.Threading.CancellationToken cancellationToken) + { + return externalEvent.RaiseAsync(new global::Consumer.Model.handlerArgs(first, second), cancellationToken); + } } } diff --git a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.RecordArgumentName_DoesNotCollideWithSynthesizedMembersAsync_parameterName=clone#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.RecordArgumentName_DoesNotCollideWithSynthesizedMembersAsync_parameterName=clone#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs index 3c8aeca..3dadbe2 100644 --- a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.RecordArgumentName_DoesNotCollideWithSynthesizedMembersAsync_parameterName=clone#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs +++ b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.RecordArgumentName_DoesNotCollideWithSynthesizedMembersAsync_parameterName=clone#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs @@ -63,5 +63,12 @@ public static partial class ModelExtensions { return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(clone, count)); } + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public static global::System.Threading.Tasks.Task RaiseAsync(this global::Nice3point.Revit.Toolkit.External.IAsyncExternalEvent externalEvent, int clone, int count, global::System.Threading.CancellationToken cancellationToken) + { + return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(clone, count), cancellationToken); + } } } diff --git a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.RecordArgumentName_DoesNotCollideWithSynthesizedMembersAsync_parameterName=deconstruct#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.RecordArgumentName_DoesNotCollideWithSynthesizedMembersAsync_parameterName=deconstruct#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs index b22458d..e20cfd0 100644 --- a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.RecordArgumentName_DoesNotCollideWithSynthesizedMembersAsync_parameterName=deconstruct#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs +++ b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.RecordArgumentName_DoesNotCollideWithSynthesizedMembersAsync_parameterName=deconstruct#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs @@ -63,5 +63,12 @@ public static partial class ModelExtensions { return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(deconstruct, count)); } + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public static global::System.Threading.Tasks.Task RaiseAsync(this global::Nice3point.Revit.Toolkit.External.IAsyncExternalEvent externalEvent, int deconstruct, int count, global::System.Threading.CancellationToken cancellationToken) + { + return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(deconstruct, count), cancellationToken); + } } } diff --git a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.RecordArgumentName_DoesNotCollideWithSynthesizedMembersAsync_parameterName=equalityContract#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.RecordArgumentName_DoesNotCollideWithSynthesizedMembersAsync_parameterName=equalityContract#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs index bcc9780..c4775df 100644 --- a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.RecordArgumentName_DoesNotCollideWithSynthesizedMembersAsync_parameterName=equalityContract#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs +++ b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.RecordArgumentName_DoesNotCollideWithSynthesizedMembersAsync_parameterName=equalityContract#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs @@ -63,5 +63,12 @@ public static partial class ModelExtensions { return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(equalityContract, count)); } + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public static global::System.Threading.Tasks.Task RaiseAsync(this global::Nice3point.Revit.Toolkit.External.IAsyncExternalEvent externalEvent, int equalityContract, int count, global::System.Threading.CancellationToken cancellationToken) + { + return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(equalityContract, count), cancellationToken); + } } } diff --git a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.RecordArgumentName_DoesNotCollideWithSynthesizedMembersAsync_parameterName=equals#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.RecordArgumentName_DoesNotCollideWithSynthesizedMembersAsync_parameterName=equals#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs index af1721a..72ab724 100644 --- a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.RecordArgumentName_DoesNotCollideWithSynthesizedMembersAsync_parameterName=equals#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs +++ b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.RecordArgumentName_DoesNotCollideWithSynthesizedMembersAsync_parameterName=equals#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs @@ -63,5 +63,12 @@ public static partial class ModelExtensions { return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(@equals, count)); } + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public static global::System.Threading.Tasks.Task RaiseAsync(this global::Nice3point.Revit.Toolkit.External.IAsyncExternalEvent externalEvent, int @equals, int count, global::System.Threading.CancellationToken cancellationToken) + { + return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(@equals, count), cancellationToken); + } } } diff --git a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.RecordArgumentName_DoesNotCollideWithSynthesizedMembersAsync_parameterName=getHashCode#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.RecordArgumentName_DoesNotCollideWithSynthesizedMembersAsync_parameterName=getHashCode#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs index 5728630..57be0f0 100644 --- a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.RecordArgumentName_DoesNotCollideWithSynthesizedMembersAsync_parameterName=getHashCode#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs +++ b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.RecordArgumentName_DoesNotCollideWithSynthesizedMembersAsync_parameterName=getHashCode#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs @@ -63,5 +63,12 @@ public static partial class ModelExtensions { return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(getHashCode, count)); } + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public static global::System.Threading.Tasks.Task RaiseAsync(this global::Nice3point.Revit.Toolkit.External.IAsyncExternalEvent externalEvent, int getHashCode, int count, global::System.Threading.CancellationToken cancellationToken) + { + return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(getHashCode, count), cancellationToken); + } } } diff --git a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.RecordArgumentName_DoesNotCollideWithSynthesizedMembersAsync_parameterName=printMembers#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.RecordArgumentName_DoesNotCollideWithSynthesizedMembersAsync_parameterName=printMembers#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs index c3baca6..30ca409 100644 --- a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.RecordArgumentName_DoesNotCollideWithSynthesizedMembersAsync_parameterName=printMembers#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs +++ b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.RecordArgumentName_DoesNotCollideWithSynthesizedMembersAsync_parameterName=printMembers#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs @@ -63,5 +63,12 @@ public static partial class ModelExtensions { return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(printMembers, count)); } + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public static global::System.Threading.Tasks.Task RaiseAsync(this global::Nice3point.Revit.Toolkit.External.IAsyncExternalEvent externalEvent, int printMembers, int count, global::System.Threading.CancellationToken cancellationToken) + { + return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(printMembers, count), cancellationToken); + } } } diff --git a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.RecordArgumentName_DoesNotCollideWithSynthesizedMembersAsync_parameterName=toString#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.RecordArgumentName_DoesNotCollideWithSynthesizedMembersAsync_parameterName=toString#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs index 3f4197b..d618dfd 100644 --- a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.RecordArgumentName_DoesNotCollideWithSynthesizedMembersAsync_parameterName=toString#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs +++ b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.RecordArgumentName_DoesNotCollideWithSynthesizedMembersAsync_parameterName=toString#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs @@ -63,5 +63,12 @@ public static partial class ModelExtensions { return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(toString, count)); } + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public static global::System.Threading.Tasks.Task RaiseAsync(this global::Nice3point.Revit.Toolkit.External.IAsyncExternalEvent externalEvent, int toString, int count, global::System.Threading.CancellationToken cancellationToken) + { + return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(toString, count), cancellationToken); + } } } diff --git a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.ShadowedGenericParameter_PreservesDistinctExtensionTypeArgumentsAsync#Consumer.Outer`1.Inner`1.Run.4DB1B2D9FFC22275.verified.cs b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.ShadowedGenericParameter_PreservesDistinctExtensionTypeArgumentsAsync#Consumer.Outer`1.Inner`1.Run.4DB1B2D9FFC22275.verified.cs index d220622..ffbbd06 100644 --- a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.ShadowedGenericParameter_PreservesDistinctExtensionTypeArgumentsAsync#Consumer.Outer`1.Inner`1.Run.4DB1B2D9FFC22275.verified.cs +++ b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorNameBindingTests.ShadowedGenericParameter_PreservesDistinctExtensionTypeArgumentsAsync#Consumer.Outer`1.Inner`1.Run.4DB1B2D9FFC22275.verified.cs @@ -66,5 +66,12 @@ public static partial class OuterExtensions { return externalEvent.RaiseAsync(new global::Consumer.Outer.Inner.RunArgs(value, count)); } + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public static global::System.Threading.Tasks.Task RaiseAsync(this global::Nice3point.Revit.Toolkit.External.IAsyncExternalEvent.Inner.RunArgs> externalEvent, TValue1 value, int count, global::System.Threading.CancellationToken cancellationToken) where TValue : class where TValue1 : struct + { + return externalEvent.RaiseAsync(new global::Consumer.Outer.Inner.RunArgs(value, count), cancellationToken); + } } } diff --git a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorRegressionTests.EscapedIdentifiers_GenerateCompilableEventsAsync_scenario=Method#Consumer.Model.event.D890DA5C45B99396.verified.cs b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorRegressionTests.EscapedIdentifiers_GenerateCompilableEventsAsync_scenario=Method#Consumer.Model.event.D890DA5C45B99396.verified.cs index 3f9631c..fa38fa2 100644 --- a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorRegressionTests.EscapedIdentifiers_GenerateCompilableEventsAsync_scenario=Method#Consumer.Model.event.D890DA5C45B99396.verified.cs +++ b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorRegressionTests.EscapedIdentifiers_GenerateCompilableEventsAsync_scenario=Method#Consumer.Model.event.D890DA5C45B99396.verified.cs @@ -63,5 +63,12 @@ public static partial class ModelExtensions { return externalEvent.RaiseAsync(new global::Consumer.Model.eventArgs(quantity, label)); } + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public static global::System.Threading.Tasks.Task RaiseAsync(this global::Nice3point.Revit.Toolkit.External.IAsyncExternalEvent externalEvent, int quantity, string label, global::System.Threading.CancellationToken cancellationToken) + { + return externalEvent.RaiseAsync(new global::Consumer.Model.eventArgs(quantity, label), cancellationToken); + } } } diff --git a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorRegressionTests.EscapedIdentifiers_GenerateCompilableEventsAsync_scenario=Parameter#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorRegressionTests.EscapedIdentifiers_GenerateCompilableEventsAsync_scenario=Parameter#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs index 2e3d52d..1479537 100644 --- a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorRegressionTests.EscapedIdentifiers_GenerateCompilableEventsAsync_scenario=Parameter#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs +++ b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorRegressionTests.EscapedIdentifiers_GenerateCompilableEventsAsync_scenario=Parameter#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs @@ -63,5 +63,12 @@ public static partial class ModelExtensions { return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(@event, label)); } + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public static global::System.Threading.Tasks.Task RaiseAsync(this global::Nice3point.Revit.Toolkit.External.IAsyncExternalEvent externalEvent, int @event, string label, global::System.Threading.CancellationToken cancellationToken) + { + return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(@event, label), cancellationToken); + } } } diff --git a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorRegressionTests.EscapedIdentifiers_GenerateCompilableEventsAsync_scenario=Type#Consumer.class.Run.6B49BE1219B268DF.verified.cs b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorRegressionTests.EscapedIdentifiers_GenerateCompilableEventsAsync_scenario=Type#Consumer.class.Run.6B49BE1219B268DF.verified.cs index 39f6ed3..0f13f50 100644 --- a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorRegressionTests.EscapedIdentifiers_GenerateCompilableEventsAsync_scenario=Type#Consumer.class.Run.6B49BE1219B268DF.verified.cs +++ b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorRegressionTests.EscapedIdentifiers_GenerateCompilableEventsAsync_scenario=Type#Consumer.class.Run.6B49BE1219B268DF.verified.cs @@ -63,5 +63,12 @@ public static partial class @classExtensions { return externalEvent.RaiseAsync(new global::Consumer.@class.RunArgs(quantity, label)); } + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public static global::System.Threading.Tasks.Task RaiseAsync(this global::Nice3point.Revit.Toolkit.External.IAsyncExternalEvent externalEvent, int quantity, string label, global::System.Threading.CancellationToken cancellationToken) + { + return externalEvent.RaiseAsync(new global::Consumer.@class.RunArgs(quantity, label), cancellationToken); + } } } diff --git a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorRegressionTests.GenericArguments_PreserveExtensionConstraintsAsync#Consumer.Model`1.Run.BFAFF1DAC26910EF.verified.cs b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorRegressionTests.GenericArguments_PreserveExtensionConstraintsAsync#Consumer.Model`1.Run.BFAFF1DAC26910EF.verified.cs index d96b34c..6bdff6e 100644 --- a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorRegressionTests.GenericArguments_PreserveExtensionConstraintsAsync#Consumer.Model`1.Run.BFAFF1DAC26910EF.verified.cs +++ b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorRegressionTests.GenericArguments_PreserveExtensionConstraintsAsync#Consumer.Model`1.Run.BFAFF1DAC26910EF.verified.cs @@ -63,5 +63,12 @@ public static partial class ModelExtensions { return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(value, quantity)); } + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public static global::System.Threading.Tasks.Task RaiseAsync(this global::Nice3point.Revit.Toolkit.External.IAsyncExternalEvent.RunArgs> externalEvent, TValue value, int quantity, global::System.Threading.CancellationToken cancellationToken) where TValue : class, global::System.IDisposable, new() + { + return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(value, quantity), cancellationToken); + } } } diff --git a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorRegressionTests.NestedGenericType_PreservesHierarchyAsync#Consumer.Outer`1.Model`1.Run.463E66F10AD83318.verified.cs b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorRegressionTests.NestedGenericType_PreservesHierarchyAsync#Consumer.Outer`1.Model`1.Run.463E66F10AD83318.verified.cs index 332a67b..a2dc99b 100644 --- a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorRegressionTests.NestedGenericType_PreservesHierarchyAsync#Consumer.Outer`1.Model`1.Run.463E66F10AD83318.verified.cs +++ b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorRegressionTests.NestedGenericType_PreservesHierarchyAsync#Consumer.Outer`1.Model`1.Run.463E66F10AD83318.verified.cs @@ -41,5 +41,12 @@ public static partial class OuterExtensions { return externalEvent.RaiseAsync(new global::Consumer.Outer.Model.RunArgs(value, result)); } + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public static global::System.Threading.Tasks.Task RaiseAsync(this global::Nice3point.Revit.Toolkit.External.IAsyncRequestExternalEvent.Model.RunArgs, TResult> externalEvent, TValue value, TResult result, global::System.Threading.CancellationToken cancellationToken) where TValue : class where TResult : struct + { + return externalEvent.RaiseAsync(new global::Consumer.Outer.Model.RunArgs(value, result), cancellationToken); + } } } diff --git a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorRegressionTests.ParameterNames_PreserveDistinctBindingsAsync_scenario=Capitalization#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorRegressionTests.ParameterNames_PreserveDistinctBindingsAsync_scenario=Capitalization#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs index 19936e0..22f62e8 100644 --- a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorRegressionTests.ParameterNames_PreserveDistinctBindingsAsync_scenario=Capitalization#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs +++ b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorRegressionTests.ParameterNames_PreserveDistinctBindingsAsync_scenario=Capitalization#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs @@ -63,5 +63,12 @@ public static partial class ModelExtensions { return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(value, Value)); } + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public static global::System.Threading.Tasks.Task RaiseAsync(this global::Nice3point.Revit.Toolkit.External.IAsyncExternalEvent externalEvent, int value, int Value, global::System.Threading.CancellationToken cancellationToken) + { + return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(value, Value), cancellationToken); + } } } diff --git a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorRegressionTests.ParameterNames_PreserveDistinctBindingsAsync_scenario=ExtensionReceiver#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorRegressionTests.ParameterNames_PreserveDistinctBindingsAsync_scenario=ExtensionReceiver#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs index 902a87b..833643b 100644 --- a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorRegressionTests.ParameterNames_PreserveDistinctBindingsAsync_scenario=ExtensionReceiver#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs +++ b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorRegressionTests.ParameterNames_PreserveDistinctBindingsAsync_scenario=ExtensionReceiver#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs @@ -63,5 +63,12 @@ public static partial class ModelExtensions { return externalEvent1.RaiseAsync(new global::Consumer.Model.RunArgs(externalEvent, quantity)); } + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public static global::System.Threading.Tasks.Task RaiseAsync(this global::Nice3point.Revit.Toolkit.External.IAsyncExternalEvent externalEvent1, int externalEvent, int quantity, global::System.Threading.CancellationToken cancellationToken) + { + return externalEvent1.RaiseAsync(new global::Consumer.Model.RunArgs(externalEvent, quantity), cancellationToken); + } } } diff --git a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorRegressionTests.StructInstanceMethod_WithCancellationTokenGeneratesCompilableEventsAsync_declaration=record struct_languageVersion=CSharp14#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorRegressionTests.StructInstanceMethod_WithCancellationTokenGeneratesCompilableEventsAsync_declaration=record struct_languageVersion=CSharp14#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs new file mode 100644 index 0000000..f17ff8a --- /dev/null +++ b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorRegressionTests.StructInstanceMethod_WithCancellationTokenGeneratesCompilableEventsAsync_declaration=record struct_languageVersion=CSharp14#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs @@ -0,0 +1,58 @@ +//HintName: Consumer.Model.Run.C0F8FDCA0BFA30FC.cs +// +#pragma warning disable +#nullable enable + +namespace Consumer +{ + public partial record struct Model + { + private object? _RunEventLock; + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public global::Nice3point.Revit.Toolkit.External.IExternalEvent RunEvent + { + get + { + lock (global::System.Threading.LazyInitializer.EnsureInitialized(ref _RunEventLock)) + { + if (field is not null) + { + return field; + } + + var handler = new global::System.Action(Run); + return field = new global::Nice3point.Revit.Toolkit.External.ExternalEvent((args) => + { + handler(args, global::System.Threading.CancellationToken.None); + }); + } + } + } + + private object? _RunAsyncEventLock; + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public global::Nice3point.Revit.Toolkit.External.IAsyncExternalEvent RunAsyncEvent + { + get + { + lock (global::System.Threading.LazyInitializer.EnsureInitialized(ref _RunAsyncEventLock)) + { + if (field is not null) + { + return field; + } + + var handler = new global::System.Action(Run); + return field = new global::Nice3point.Revit.Toolkit.External.AsyncExternalEvent((application, args, cancellationToken) => + { + handler(args, cancellationToken); + }); + } + } + } + } +} diff --git a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorRegressionTests.StructInstanceMethod_WithCancellationTokenGeneratesCompilableEventsAsync_declaration=struct_languageVersion=CSharp13#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorRegressionTests.StructInstanceMethod_WithCancellationTokenGeneratesCompilableEventsAsync_declaration=struct_languageVersion=CSharp13#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs new file mode 100644 index 0000000..dbc645a --- /dev/null +++ b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorRegressionTests.StructInstanceMethod_WithCancellationTokenGeneratesCompilableEventsAsync_declaration=struct_languageVersion=CSharp13#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs @@ -0,0 +1,60 @@ +//HintName: Consumer.Model.Run.C0F8FDCA0BFA30FC.cs +// +#pragma warning disable +#nullable enable + +namespace Consumer +{ + public partial struct Model + { + private object? _RunEventLock; + private global::Nice3point.Revit.Toolkit.External.IExternalEvent? _RunEventBackingField; + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public global::Nice3point.Revit.Toolkit.External.IExternalEvent RunEvent + { + get + { + lock (global::System.Threading.LazyInitializer.EnsureInitialized(ref _RunEventLock)) + { + if (_RunEventBackingField is not null) + { + return _RunEventBackingField; + } + + var handler = new global::System.Action(Run); + return _RunEventBackingField = new global::Nice3point.Revit.Toolkit.External.ExternalEvent((args) => + { + handler(args, global::System.Threading.CancellationToken.None); + }); + } + } + } + + private object? _RunAsyncEventLock; + private global::Nice3point.Revit.Toolkit.External.IAsyncExternalEvent? _RunAsyncEventBackingField; + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public global::Nice3point.Revit.Toolkit.External.IAsyncExternalEvent RunAsyncEvent + { + get + { + lock (global::System.Threading.LazyInitializer.EnsureInitialized(ref _RunAsyncEventLock)) + { + if (_RunAsyncEventBackingField is not null) + { + return _RunAsyncEventBackingField; + } + + var handler = new global::System.Action(Run); + return _RunAsyncEventBackingField = new global::Nice3point.Revit.Toolkit.External.AsyncExternalEvent((application, args, cancellationToken) => + { + handler(args, cancellationToken); + }); + } + } + } + } +} diff --git a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorRegressionTests.StructInstanceMethod_WithCancellationTokenGeneratesCompilableEventsAsync_declaration=struct_languageVersion=CSharp14#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorRegressionTests.StructInstanceMethod_WithCancellationTokenGeneratesCompilableEventsAsync_declaration=struct_languageVersion=CSharp14#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs new file mode 100644 index 0000000..1accf15 --- /dev/null +++ b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorRegressionTests.StructInstanceMethod_WithCancellationTokenGeneratesCompilableEventsAsync_declaration=struct_languageVersion=CSharp14#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs @@ -0,0 +1,58 @@ +//HintName: Consumer.Model.Run.C0F8FDCA0BFA30FC.cs +// +#pragma warning disable +#nullable enable + +namespace Consumer +{ + public partial struct Model + { + private object? _RunEventLock; + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public global::Nice3point.Revit.Toolkit.External.IExternalEvent RunEvent + { + get + { + lock (global::System.Threading.LazyInitializer.EnsureInitialized(ref _RunEventLock)) + { + if (field is not null) + { + return field; + } + + var handler = new global::System.Action(Run); + return field = new global::Nice3point.Revit.Toolkit.External.ExternalEvent((args) => + { + handler(args, global::System.Threading.CancellationToken.None); + }); + } + } + } + + private object? _RunAsyncEventLock; + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public global::Nice3point.Revit.Toolkit.External.IAsyncExternalEvent RunAsyncEvent + { + get + { + lock (global::System.Threading.LazyInitializer.EnsureInitialized(ref _RunAsyncEventLock)) + { + if (field is not null) + { + return field; + } + + var handler = new global::System.Action(Run); + return field = new global::Nice3point.Revit.Toolkit.External.AsyncExternalEvent((application, args, cancellationToken) => + { + handler(args, cancellationToken); + }); + } + } + } + } +} diff --git a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorRegressionTests.StructInstanceMethod_WithMultipleArgumentsGeneratesCompilableEventsAsync_declaration=record struct_languageVersion=CSharp13#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorRegressionTests.StructInstanceMethod_WithMultipleArgumentsGeneratesCompilableEventsAsync_declaration=record struct_languageVersion=CSharp13#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs index 9b02f2d..58052b1 100644 --- a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorRegressionTests.StructInstanceMethod_WithMultipleArgumentsGeneratesCompilableEventsAsync_declaration=record struct_languageVersion=CSharp13#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs +++ b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorRegressionTests.StructInstanceMethod_WithMultipleArgumentsGeneratesCompilableEventsAsync_declaration=record struct_languageVersion=CSharp13#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs @@ -77,5 +77,12 @@ public static partial class ModelExtensions { return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(quantity, label)); } + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public static global::System.Threading.Tasks.Task RaiseAsync(this global::Nice3point.Revit.Toolkit.External.IAsyncExternalEvent externalEvent, int quantity, string label, global::System.Threading.CancellationToken cancellationToken) + { + return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(quantity, label), cancellationToken); + } } } diff --git a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorRegressionTests.StructInstanceMethod_WithMultipleArgumentsGeneratesCompilableEventsAsync_declaration=record struct_languageVersion=CSharp14#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorRegressionTests.StructInstanceMethod_WithMultipleArgumentsGeneratesCompilableEventsAsync_declaration=record struct_languageVersion=CSharp14#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs index d9ce893..94f978e 100644 --- a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorRegressionTests.StructInstanceMethod_WithMultipleArgumentsGeneratesCompilableEventsAsync_declaration=record struct_languageVersion=CSharp14#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs +++ b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorRegressionTests.StructInstanceMethod_WithMultipleArgumentsGeneratesCompilableEventsAsync_declaration=record struct_languageVersion=CSharp14#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs @@ -75,5 +75,12 @@ public static partial class ModelExtensions { return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(quantity, label)); } + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public static global::System.Threading.Tasks.Task RaiseAsync(this global::Nice3point.Revit.Toolkit.External.IAsyncExternalEvent externalEvent, int quantity, string label, global::System.Threading.CancellationToken cancellationToken) + { + return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(quantity, label), cancellationToken); + } } } diff --git a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorRegressionTests.StructInstanceMethod_WithMultipleArgumentsGeneratesCompilableEventsAsync_declaration=struct_languageVersion=CSharp13#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorRegressionTests.StructInstanceMethod_WithMultipleArgumentsGeneratesCompilableEventsAsync_declaration=struct_languageVersion=CSharp13#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs index 324b978..519964b 100644 --- a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorRegressionTests.StructInstanceMethod_WithMultipleArgumentsGeneratesCompilableEventsAsync_declaration=struct_languageVersion=CSharp13#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs +++ b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorRegressionTests.StructInstanceMethod_WithMultipleArgumentsGeneratesCompilableEventsAsync_declaration=struct_languageVersion=CSharp13#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs @@ -77,5 +77,12 @@ public static partial class ModelExtensions { return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(quantity, label)); } + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public static global::System.Threading.Tasks.Task RaiseAsync(this global::Nice3point.Revit.Toolkit.External.IAsyncExternalEvent externalEvent, int quantity, string label, global::System.Threading.CancellationToken cancellationToken) + { + return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(quantity, label), cancellationToken); + } } } diff --git a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorRegressionTests.StructInstanceMethod_WithMultipleArgumentsGeneratesCompilableEventsAsync_declaration=struct_languageVersion=CSharp14#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorRegressionTests.StructInstanceMethod_WithMultipleArgumentsGeneratesCompilableEventsAsync_declaration=struct_languageVersion=CSharp14#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs index 7d9bee5..e7a4d70 100644 --- a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorRegressionTests.StructInstanceMethod_WithMultipleArgumentsGeneratesCompilableEventsAsync_declaration=struct_languageVersion=CSharp14#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs +++ b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorRegressionTests.StructInstanceMethod_WithMultipleArgumentsGeneratesCompilableEventsAsync_declaration=struct_languageVersion=CSharp14#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs @@ -75,5 +75,12 @@ public static partial class ModelExtensions { return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(quantity, label)); } + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public static global::System.Threading.Tasks.Task RaiseAsync(this global::Nice3point.Revit.Toolkit.External.IAsyncExternalEvent externalEvent, int quantity, string label, global::System.Threading.CancellationToken cancellationToken) + { + return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(quantity, label), cancellationToken); + } } } diff --git a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.AllowDirectInvocation_PreservesOptionsAsync_scenario=RecordDisabled#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.AllowDirectInvocation_PreservesOptionsAsync_scenario=RecordDisabled#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs index 125d2ba..c9fedc7 100644 --- a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.AllowDirectInvocation_PreservesOptionsAsync_scenario=RecordDisabled#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs +++ b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.AllowDirectInvocation_PreservesOptionsAsync_scenario=RecordDisabled#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs @@ -63,5 +63,12 @@ public static partial class ModelExtensions { return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(quantity, label)); } + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public static global::System.Threading.Tasks.Task RaiseAsync(this global::Nice3point.Revit.Toolkit.External.IAsyncExternalEvent externalEvent, int quantity, string label, global::System.Threading.CancellationToken cancellationToken) + { + return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(quantity, label), cancellationToken); + } } } diff --git a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.AllowDirectInvocation_PreservesOptionsAsync_scenario=RecordEnabled#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.AllowDirectInvocation_PreservesOptionsAsync_scenario=RecordEnabled#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs index 70a5ef9..f1e5e9e 100644 --- a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.AllowDirectInvocation_PreservesOptionsAsync_scenario=RecordEnabled#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs +++ b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.AllowDirectInvocation_PreservesOptionsAsync_scenario=RecordEnabled#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs @@ -63,5 +63,12 @@ public static partial class ModelExtensions { return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(quantity, label)); } + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public static global::System.Threading.Tasks.Task RaiseAsync(this global::Nice3point.Revit.Toolkit.External.IAsyncExternalEvent externalEvent, int quantity, string label, global::System.Threading.CancellationToken cancellationToken) + { + return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(quantity, label), cancellationToken); + } } } diff --git a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.CancellationTokenOutsideTheLastParameter_RemainsUserArgumentAsync_scenario=BeforeLastParameter#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.CancellationTokenOutsideTheLastParameter_RemainsUserArgumentAsync_scenario=BeforeLastParameter#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs new file mode 100644 index 0000000..b401a80 --- /dev/null +++ b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.CancellationTokenOutsideTheLastParameter_RemainsUserArgumentAsync_scenario=BeforeLastParameter#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs @@ -0,0 +1,74 @@ +//HintName: Consumer.Model.Run.C0F8FDCA0BFA30FC.cs +// +#pragma warning disable +#nullable enable + +namespace Consumer +{ + public partial class Model + { + private object? _RunEventLock; + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public global::Nice3point.Revit.Toolkit.External.IExternalEvent RunEvent + { + get + { + lock (global::System.Threading.LazyInitializer.EnsureInitialized(ref _RunEventLock)) + { + return field ??= new global::Nice3point.Revit.Toolkit.External.ExternalEvent((args) => + { + Run(args.Token, args.Quantity); + }); + } + } + } + + private object? _RunAsyncEventLock; + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public global::Nice3point.Revit.Toolkit.External.IAsyncExternalEvent RunAsyncEvent + { + get + { + lock (global::System.Threading.LazyInitializer.EnsureInitialized(ref _RunAsyncEventLock)) + { + return field ??= new global::Nice3point.Revit.Toolkit.External.AsyncExternalEvent((args) => + { + Run(args.Token, args.Quantity); + }); + } + } + } + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public sealed record RunArgs(global::System.Threading.CancellationToken Token, int Quantity); + } + + public static partial class ModelExtensions + { + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public static global::Autodesk.Revit.UI.ExternalEventRequest Raise(this global::Nice3point.Revit.Toolkit.External.IExternalEvent externalEvent, global::System.Threading.CancellationToken token, int quantity) + { + return externalEvent.Raise(new global::Consumer.Model.RunArgs(token, quantity)); + } + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public static global::System.Threading.Tasks.Task RaiseAsync(this global::Nice3point.Revit.Toolkit.External.IAsyncExternalEvent externalEvent, global::System.Threading.CancellationToken token, int quantity) + { + return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(token, quantity)); + } + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public static global::System.Threading.Tasks.Task RaiseAsync(this global::Nice3point.Revit.Toolkit.External.IAsyncExternalEvent externalEvent, global::System.Threading.CancellationToken token, int quantity, global::System.Threading.CancellationToken cancellationToken) + { + return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(token, quantity), cancellationToken); + } + } +} diff --git a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.CancellationTokenOutsideTheLastParameter_RemainsUserArgumentAsync_scenario=NullableLastParameter#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.CancellationTokenOutsideTheLastParameter_RemainsUserArgumentAsync_scenario=NullableLastParameter#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs new file mode 100644 index 0000000..da3b8f2 --- /dev/null +++ b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.CancellationTokenOutsideTheLastParameter_RemainsUserArgumentAsync_scenario=NullableLastParameter#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs @@ -0,0 +1,74 @@ +//HintName: Consumer.Model.Run.C0F8FDCA0BFA30FC.cs +// +#pragma warning disable +#nullable enable + +namespace Consumer +{ + public partial class Model + { + private object? _RunEventLock; + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public global::Nice3point.Revit.Toolkit.External.IExternalEvent RunEvent + { + get + { + lock (global::System.Threading.LazyInitializer.EnsureInitialized(ref _RunEventLock)) + { + return field ??= new global::Nice3point.Revit.Toolkit.External.ExternalEvent((args) => + { + Run(args.Quantity, args.Token); + }); + } + } + } + + private object? _RunAsyncEventLock; + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public global::Nice3point.Revit.Toolkit.External.IAsyncExternalEvent RunAsyncEvent + { + get + { + lock (global::System.Threading.LazyInitializer.EnsureInitialized(ref _RunAsyncEventLock)) + { + return field ??= new global::Nice3point.Revit.Toolkit.External.AsyncExternalEvent((args) => + { + Run(args.Quantity, args.Token); + }); + } + } + } + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public sealed record RunArgs(int Quantity, global::System.Threading.CancellationToken? Token); + } + + public static partial class ModelExtensions + { + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public static global::Autodesk.Revit.UI.ExternalEventRequest Raise(this global::Nice3point.Revit.Toolkit.External.IExternalEvent externalEvent, int quantity, global::System.Threading.CancellationToken? token) + { + return externalEvent.Raise(new global::Consumer.Model.RunArgs(quantity, token)); + } + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public static global::System.Threading.Tasks.Task RaiseAsync(this global::Nice3point.Revit.Toolkit.External.IAsyncExternalEvent externalEvent, int quantity, global::System.Threading.CancellationToken? token) + { + return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(quantity, token)); + } + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public static global::System.Threading.Tasks.Task RaiseAsync(this global::Nice3point.Revit.Toolkit.External.IAsyncExternalEvent externalEvent, int quantity, global::System.Threading.CancellationToken? token, global::System.Threading.CancellationToken cancellationToken) + { + return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(quantity, token), cancellationToken); + } + } +} diff --git a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.MethodSignature_GeneratesCompilableEventsAsync_scenario=ResultApplicationArgumentToken#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.MethodSignature_GeneratesCompilableEventsAsync_scenario=ResultApplicationArgumentToken#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs new file mode 100644 index 0000000..fb5e120 --- /dev/null +++ b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.MethodSignature_GeneratesCompilableEventsAsync_scenario=ResultApplicationArgumentToken#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs @@ -0,0 +1,25 @@ +//HintName: Consumer.Model.Run.C0F8FDCA0BFA30FC.cs +// +#pragma warning disable +#nullable enable + +namespace Consumer +{ + public partial class Model + { + private object? _RunAsyncEventLock; + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public global::Nice3point.Revit.Toolkit.External.IAsyncRequestExternalEvent RunAsyncEvent + { + get + { + lock (global::System.Threading.LazyInitializer.EnsureInitialized(ref _RunAsyncEventLock)) + { + return field ??= new global::Nice3point.Revit.Toolkit.External.AsyncRequestExternalEvent(new global::System.Func(Run)); + } + } + } + } +} diff --git a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.MethodSignature_GeneratesCompilableEventsAsync_scenario=ResultApplicationArguments#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.MethodSignature_GeneratesCompilableEventsAsync_scenario=ResultApplicationArguments#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs index cf4b4d6..c4aeb20 100644 --- a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.MethodSignature_GeneratesCompilableEventsAsync_scenario=ResultApplicationArguments#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs +++ b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.MethodSignature_GeneratesCompilableEventsAsync_scenario=ResultApplicationArguments#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs @@ -38,5 +38,12 @@ public static partial class ModelExtensions { return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(quantity, label)); } + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public static global::System.Threading.Tasks.Task RaiseAsync(this global::Nice3point.Revit.Toolkit.External.IAsyncRequestExternalEvent externalEvent, int quantity, string label, global::System.Threading.CancellationToken cancellationToken) + { + return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(quantity, label), cancellationToken); + } } } diff --git a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.MethodSignature_GeneratesCompilableEventsAsync_scenario=ResultApplicationArgumentsToken#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.MethodSignature_GeneratesCompilableEventsAsync_scenario=ResultApplicationArgumentsToken#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs new file mode 100644 index 0000000..e1a1e2f --- /dev/null +++ b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.MethodSignature_GeneratesCompilableEventsAsync_scenario=ResultApplicationArgumentsToken#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs @@ -0,0 +1,49 @@ +//HintName: Consumer.Model.Run.C0F8FDCA0BFA30FC.cs +// +#pragma warning disable +#nullable enable + +namespace Consumer +{ + public partial class Model + { + private object? _RunAsyncEventLock; + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public global::Nice3point.Revit.Toolkit.External.IAsyncRequestExternalEvent RunAsyncEvent + { + get + { + lock (global::System.Threading.LazyInitializer.EnsureInitialized(ref _RunAsyncEventLock)) + { + return field ??= new global::Nice3point.Revit.Toolkit.External.AsyncRequestExternalEvent((application, args, cancellationToken) => + { + return Run(application, args.Quantity, args.Label, cancellationToken); + }); + } + } + } + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public sealed record RunArgs(int Quantity, string Label); + } + + public static partial class ModelExtensions + { + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public static global::System.Threading.Tasks.Task RaiseAsync(this global::Nice3point.Revit.Toolkit.External.IAsyncRequestExternalEvent externalEvent, int quantity, string label) + { + return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(quantity, label)); + } + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public static global::System.Threading.Tasks.Task RaiseAsync(this global::Nice3point.Revit.Toolkit.External.IAsyncRequestExternalEvent externalEvent, int quantity, string label, global::System.Threading.CancellationToken cancellationToken) + { + return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(quantity, label), cancellationToken); + } + } +} diff --git a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.MethodSignature_GeneratesCompilableEventsAsync_scenario=ResultApplicationToken#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.MethodSignature_GeneratesCompilableEventsAsync_scenario=ResultApplicationToken#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs new file mode 100644 index 0000000..142086a --- /dev/null +++ b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.MethodSignature_GeneratesCompilableEventsAsync_scenario=ResultApplicationToken#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs @@ -0,0 +1,25 @@ +//HintName: Consumer.Model.Run.C0F8FDCA0BFA30FC.cs +// +#pragma warning disable +#nullable enable + +namespace Consumer +{ + public partial class Model + { + private object? _RunAsyncEventLock; + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public global::Nice3point.Revit.Toolkit.External.IAsyncRequestExternalEvent RunAsyncEvent + { + get + { + lock (global::System.Threading.LazyInitializer.EnsureInitialized(ref _RunAsyncEventLock)) + { + return field ??= new global::Nice3point.Revit.Toolkit.External.AsyncRequestExternalEvent(new global::System.Func(Run)); + } + } + } + } +} diff --git a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.MethodSignature_GeneratesCompilableEventsAsync_scenario=ResultArgumentToken#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.MethodSignature_GeneratesCompilableEventsAsync_scenario=ResultArgumentToken#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs new file mode 100644 index 0000000..54ee371 --- /dev/null +++ b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.MethodSignature_GeneratesCompilableEventsAsync_scenario=ResultArgumentToken#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs @@ -0,0 +1,28 @@ +//HintName: Consumer.Model.Run.C0F8FDCA0BFA30FC.cs +// +#pragma warning disable +#nullable enable + +namespace Consumer +{ + public partial class Model + { + private object? _RunAsyncEventLock; + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public global::Nice3point.Revit.Toolkit.External.IAsyncRequestExternalEvent RunAsyncEvent + { + get + { + lock (global::System.Threading.LazyInitializer.EnsureInitialized(ref _RunAsyncEventLock)) + { + return field ??= new global::Nice3point.Revit.Toolkit.External.AsyncRequestExternalEvent((application, args, cancellationToken) => + { + return Run(args, cancellationToken); + }); + } + } + } + } +} diff --git a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.MethodSignature_GeneratesCompilableEventsAsync_scenario=ResultArguments#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.MethodSignature_GeneratesCompilableEventsAsync_scenario=ResultArguments#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs index 3d3385a..7090380 100644 --- a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.MethodSignature_GeneratesCompilableEventsAsync_scenario=ResultArguments#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs +++ b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.MethodSignature_GeneratesCompilableEventsAsync_scenario=ResultArguments#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs @@ -38,5 +38,12 @@ public static partial class ModelExtensions { return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(quantity, label)); } + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public static global::System.Threading.Tasks.Task RaiseAsync(this global::Nice3point.Revit.Toolkit.External.IAsyncRequestExternalEvent externalEvent, int quantity, string label, global::System.Threading.CancellationToken cancellationToken) + { + return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(quantity, label), cancellationToken); + } } } diff --git a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.MethodSignature_GeneratesCompilableEventsAsync_scenario=ResultArgumentsToken#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.MethodSignature_GeneratesCompilableEventsAsync_scenario=ResultArgumentsToken#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs new file mode 100644 index 0000000..57e36b2 --- /dev/null +++ b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.MethodSignature_GeneratesCompilableEventsAsync_scenario=ResultArgumentsToken#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs @@ -0,0 +1,49 @@ +//HintName: Consumer.Model.Run.C0F8FDCA0BFA30FC.cs +// +#pragma warning disable +#nullable enable + +namespace Consumer +{ + public partial class Model + { + private object? _RunAsyncEventLock; + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public global::Nice3point.Revit.Toolkit.External.IAsyncRequestExternalEvent RunAsyncEvent + { + get + { + lock (global::System.Threading.LazyInitializer.EnsureInitialized(ref _RunAsyncEventLock)) + { + return field ??= new global::Nice3point.Revit.Toolkit.External.AsyncRequestExternalEvent((application, args, cancellationToken) => + { + return Run(args.Quantity, args.Label, cancellationToken); + }); + } + } + } + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public sealed record RunArgs(int Quantity, string Label); + } + + public static partial class ModelExtensions + { + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public static global::System.Threading.Tasks.Task RaiseAsync(this global::Nice3point.Revit.Toolkit.External.IAsyncRequestExternalEvent externalEvent, int quantity, string label) + { + return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(quantity, label)); + } + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public static global::System.Threading.Tasks.Task RaiseAsync(this global::Nice3point.Revit.Toolkit.External.IAsyncRequestExternalEvent externalEvent, int quantity, string label, global::System.Threading.CancellationToken cancellationToken) + { + return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(quantity, label), cancellationToken); + } + } +} diff --git a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.MethodSignature_GeneratesCompilableEventsAsync_scenario=ResultToken#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.MethodSignature_GeneratesCompilableEventsAsync_scenario=ResultToken#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs new file mode 100644 index 0000000..fb54bf2 --- /dev/null +++ b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.MethodSignature_GeneratesCompilableEventsAsync_scenario=ResultToken#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs @@ -0,0 +1,28 @@ +//HintName: Consumer.Model.Run.C0F8FDCA0BFA30FC.cs +// +#pragma warning disable +#nullable enable + +namespace Consumer +{ + public partial class Model + { + private object? _RunAsyncEventLock; + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public global::Nice3point.Revit.Toolkit.External.IAsyncRequestExternalEvent RunAsyncEvent + { + get + { + lock (global::System.Threading.LazyInitializer.EnsureInitialized(ref _RunAsyncEventLock)) + { + return field ??= new global::Nice3point.Revit.Toolkit.External.AsyncRequestExternalEvent((application, cancellationToken) => + { + return Run(cancellationToken); + }); + } + } + } + } +} diff --git a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.MethodSignature_GeneratesCompilableEventsAsync_scenario=VoidApplicationArgumentToken#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.MethodSignature_GeneratesCompilableEventsAsync_scenario=VoidApplicationArgumentToken#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs new file mode 100644 index 0000000..f3700db --- /dev/null +++ b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.MethodSignature_GeneratesCompilableEventsAsync_scenario=VoidApplicationArgumentToken#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs @@ -0,0 +1,43 @@ +//HintName: Consumer.Model.Run.C0F8FDCA0BFA30FC.cs +// +#pragma warning disable +#nullable enable + +namespace Consumer +{ + public partial class Model + { + private object? _RunEventLock; + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public global::Nice3point.Revit.Toolkit.External.IExternalEvent RunEvent + { + get + { + lock (global::System.Threading.LazyInitializer.EnsureInitialized(ref _RunEventLock)) + { + return field ??= new global::Nice3point.Revit.Toolkit.External.ExternalEvent((application, args) => + { + Run(application, args, global::System.Threading.CancellationToken.None); + }); + } + } + } + + private object? _RunAsyncEventLock; + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public global::Nice3point.Revit.Toolkit.External.IAsyncExternalEvent RunAsyncEvent + { + get + { + lock (global::System.Threading.LazyInitializer.EnsureInitialized(ref _RunAsyncEventLock)) + { + return field ??= new global::Nice3point.Revit.Toolkit.External.AsyncExternalEvent(new global::System.Action(Run)); + } + } + } + } +} diff --git a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.MethodSignature_GeneratesCompilableEventsAsync_scenario=VoidApplicationArguments#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.MethodSignature_GeneratesCompilableEventsAsync_scenario=VoidApplicationArguments#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs index 184540b..409b80f 100644 --- a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.MethodSignature_GeneratesCompilableEventsAsync_scenario=VoidApplicationArguments#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs +++ b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.MethodSignature_GeneratesCompilableEventsAsync_scenario=VoidApplicationArguments#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs @@ -63,5 +63,12 @@ public static partial class ModelExtensions { return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(quantity, label)); } + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public static global::System.Threading.Tasks.Task RaiseAsync(this global::Nice3point.Revit.Toolkit.External.IAsyncExternalEvent externalEvent, int quantity, string label, global::System.Threading.CancellationToken cancellationToken) + { + return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(quantity, label), cancellationToken); + } } } diff --git a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.MethodSignature_GeneratesCompilableEventsAsync_scenario=VoidApplicationArgumentsToken#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.MethodSignature_GeneratesCompilableEventsAsync_scenario=VoidApplicationArgumentsToken#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs new file mode 100644 index 0000000..f034b76 --- /dev/null +++ b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.MethodSignature_GeneratesCompilableEventsAsync_scenario=VoidApplicationArgumentsToken#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs @@ -0,0 +1,74 @@ +//HintName: Consumer.Model.Run.C0F8FDCA0BFA30FC.cs +// +#pragma warning disable +#nullable enable + +namespace Consumer +{ + public partial class Model + { + private object? _RunEventLock; + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public global::Nice3point.Revit.Toolkit.External.IExternalEvent RunEvent + { + get + { + lock (global::System.Threading.LazyInitializer.EnsureInitialized(ref _RunEventLock)) + { + return field ??= new global::Nice3point.Revit.Toolkit.External.ExternalEvent((application, args) => + { + Run(application, args.Quantity, args.Label, global::System.Threading.CancellationToken.None); + }); + } + } + } + + private object? _RunAsyncEventLock; + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public global::Nice3point.Revit.Toolkit.External.IAsyncExternalEvent RunAsyncEvent + { + get + { + lock (global::System.Threading.LazyInitializer.EnsureInitialized(ref _RunAsyncEventLock)) + { + return field ??= new global::Nice3point.Revit.Toolkit.External.AsyncExternalEvent((application, args, cancellationToken) => + { + Run(application, args.Quantity, args.Label, cancellationToken); + }); + } + } + } + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public sealed record RunArgs(int Quantity, string Label); + } + + public static partial class ModelExtensions + { + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public static global::Autodesk.Revit.UI.ExternalEventRequest Raise(this global::Nice3point.Revit.Toolkit.External.IExternalEvent externalEvent, int quantity, string label) + { + return externalEvent.Raise(new global::Consumer.Model.RunArgs(quantity, label)); + } + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public static global::System.Threading.Tasks.Task RaiseAsync(this global::Nice3point.Revit.Toolkit.External.IAsyncExternalEvent externalEvent, int quantity, string label) + { + return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(quantity, label)); + } + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public static global::System.Threading.Tasks.Task RaiseAsync(this global::Nice3point.Revit.Toolkit.External.IAsyncExternalEvent externalEvent, int quantity, string label, global::System.Threading.CancellationToken cancellationToken) + { + return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(quantity, label), cancellationToken); + } + } +} diff --git a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.MethodSignature_GeneratesCompilableEventsAsync_scenario=VoidApplicationToken#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.MethodSignature_GeneratesCompilableEventsAsync_scenario=VoidApplicationToken#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs new file mode 100644 index 0000000..a16621e --- /dev/null +++ b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.MethodSignature_GeneratesCompilableEventsAsync_scenario=VoidApplicationToken#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs @@ -0,0 +1,43 @@ +//HintName: Consumer.Model.Run.C0F8FDCA0BFA30FC.cs +// +#pragma warning disable +#nullable enable + +namespace Consumer +{ + public partial class Model + { + private object? _RunEventLock; + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public global::Nice3point.Revit.Toolkit.External.IExternalEvent RunEvent + { + get + { + lock (global::System.Threading.LazyInitializer.EnsureInitialized(ref _RunEventLock)) + { + return field ??= new global::Nice3point.Revit.Toolkit.External.ExternalEvent((application) => + { + Run(application, global::System.Threading.CancellationToken.None); + }); + } + } + } + + private object? _RunAsyncEventLock; + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public global::Nice3point.Revit.Toolkit.External.IAsyncExternalEvent RunAsyncEvent + { + get + { + lock (global::System.Threading.LazyInitializer.EnsureInitialized(ref _RunAsyncEventLock)) + { + return field ??= new global::Nice3point.Revit.Toolkit.External.AsyncExternalEvent(new global::System.Action(Run)); + } + } + } + } +} diff --git a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.MethodSignature_GeneratesCompilableEventsAsync_scenario=VoidArgumentToken#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.MethodSignature_GeneratesCompilableEventsAsync_scenario=VoidArgumentToken#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs new file mode 100644 index 0000000..9ce14d9 --- /dev/null +++ b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.MethodSignature_GeneratesCompilableEventsAsync_scenario=VoidArgumentToken#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs @@ -0,0 +1,46 @@ +//HintName: Consumer.Model.Run.C0F8FDCA0BFA30FC.cs +// +#pragma warning disable +#nullable enable + +namespace Consumer +{ + public partial class Model + { + private object? _RunEventLock; + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public global::Nice3point.Revit.Toolkit.External.IExternalEvent RunEvent + { + get + { + lock (global::System.Threading.LazyInitializer.EnsureInitialized(ref _RunEventLock)) + { + return field ??= new global::Nice3point.Revit.Toolkit.External.ExternalEvent((args) => + { + Run(args, global::System.Threading.CancellationToken.None); + }); + } + } + } + + private object? _RunAsyncEventLock; + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public global::Nice3point.Revit.Toolkit.External.IAsyncExternalEvent RunAsyncEvent + { + get + { + lock (global::System.Threading.LazyInitializer.EnsureInitialized(ref _RunAsyncEventLock)) + { + return field ??= new global::Nice3point.Revit.Toolkit.External.AsyncExternalEvent((application, args, cancellationToken) => + { + Run(args, cancellationToken); + }); + } + } + } + } +} diff --git a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.MethodSignature_GeneratesCompilableEventsAsync_scenario=VoidArguments#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.MethodSignature_GeneratesCompilableEventsAsync_scenario=VoidArguments#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs index 125d2ba..c9fedc7 100644 --- a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.MethodSignature_GeneratesCompilableEventsAsync_scenario=VoidArguments#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs +++ b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.MethodSignature_GeneratesCompilableEventsAsync_scenario=VoidArguments#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs @@ -63,5 +63,12 @@ public static partial class ModelExtensions { return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(quantity, label)); } + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public static global::System.Threading.Tasks.Task RaiseAsync(this global::Nice3point.Revit.Toolkit.External.IAsyncExternalEvent externalEvent, int quantity, string label, global::System.Threading.CancellationToken cancellationToken) + { + return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(quantity, label), cancellationToken); + } } } diff --git a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.MethodSignature_GeneratesCompilableEventsAsync_scenario=VoidArgumentsToken#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.MethodSignature_GeneratesCompilableEventsAsync_scenario=VoidArgumentsToken#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs new file mode 100644 index 0000000..967aa3b --- /dev/null +++ b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.MethodSignature_GeneratesCompilableEventsAsync_scenario=VoidArgumentsToken#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs @@ -0,0 +1,74 @@ +//HintName: Consumer.Model.Run.C0F8FDCA0BFA30FC.cs +// +#pragma warning disable +#nullable enable + +namespace Consumer +{ + public partial class Model + { + private object? _RunEventLock; + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public global::Nice3point.Revit.Toolkit.External.IExternalEvent RunEvent + { + get + { + lock (global::System.Threading.LazyInitializer.EnsureInitialized(ref _RunEventLock)) + { + return field ??= new global::Nice3point.Revit.Toolkit.External.ExternalEvent((args) => + { + Run(args.Quantity, args.Label, global::System.Threading.CancellationToken.None); + }); + } + } + } + + private object? _RunAsyncEventLock; + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public global::Nice3point.Revit.Toolkit.External.IAsyncExternalEvent RunAsyncEvent + { + get + { + lock (global::System.Threading.LazyInitializer.EnsureInitialized(ref _RunAsyncEventLock)) + { + return field ??= new global::Nice3point.Revit.Toolkit.External.AsyncExternalEvent((application, args, cancellationToken) => + { + Run(args.Quantity, args.Label, cancellationToken); + }); + } + } + } + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public sealed record RunArgs(int Quantity, string Label); + } + + public static partial class ModelExtensions + { + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public static global::Autodesk.Revit.UI.ExternalEventRequest Raise(this global::Nice3point.Revit.Toolkit.External.IExternalEvent externalEvent, int quantity, string label) + { + return externalEvent.Raise(new global::Consumer.Model.RunArgs(quantity, label)); + } + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public static global::System.Threading.Tasks.Task RaiseAsync(this global::Nice3point.Revit.Toolkit.External.IAsyncExternalEvent externalEvent, int quantity, string label) + { + return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(quantity, label)); + } + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public static global::System.Threading.Tasks.Task RaiseAsync(this global::Nice3point.Revit.Toolkit.External.IAsyncExternalEvent externalEvent, int quantity, string label, global::System.Threading.CancellationToken cancellationToken) + { + return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(quantity, label), cancellationToken); + } + } +} diff --git a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.MethodSignature_GeneratesCompilableEventsAsync_scenario=VoidToken#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.MethodSignature_GeneratesCompilableEventsAsync_scenario=VoidToken#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs new file mode 100644 index 0000000..a7a9834 --- /dev/null +++ b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.MethodSignature_GeneratesCompilableEventsAsync_scenario=VoidToken#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs @@ -0,0 +1,46 @@ +//HintName: Consumer.Model.Run.C0F8FDCA0BFA30FC.cs +// +#pragma warning disable +#nullable enable + +namespace Consumer +{ + public partial class Model + { + private object? _RunEventLock; + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public global::Nice3point.Revit.Toolkit.External.IExternalEvent RunEvent + { + get + { + lock (global::System.Threading.LazyInitializer.EnsureInitialized(ref _RunEventLock)) + { + return field ??= new global::Nice3point.Revit.Toolkit.External.ExternalEvent(() => + { + Run(global::System.Threading.CancellationToken.None); + }); + } + } + } + + private object? _RunAsyncEventLock; + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public global::Nice3point.Revit.Toolkit.External.IAsyncExternalEvent RunAsyncEvent + { + get + { + lock (global::System.Threading.LazyInitializer.EnsureInitialized(ref _RunAsyncEventLock)) + { + return field ??= new global::Nice3point.Revit.Toolkit.External.AsyncExternalEvent((application, cancellationToken) => + { + Run(cancellationToken); + }); + } + } + } + } +} diff --git a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.NullableSignature_PreservesAnnotationsAsync#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.NullableSignature_PreservesAnnotationsAsync#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs index 7d5d392..451855d 100644 --- a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.NullableSignature_PreservesAnnotationsAsync#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs +++ b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.NullableSignature_PreservesAnnotationsAsync#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs @@ -38,5 +38,12 @@ public static partial class ModelExtensions { return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(label, quantity)); } + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public static global::System.Threading.Tasks.Task RaiseAsync(this global::Nice3point.Revit.Toolkit.External.IAsyncRequestExternalEvent externalEvent, string? label, int? quantity, global::System.Threading.CancellationToken cancellationToken) + { + return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(label, quantity), cancellationToken); + } } } diff --git a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.UIApplicationAfterFirstParameter_RemainsUserArgumentAsync#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.UIApplicationAfterFirstParameter_RemainsUserArgumentAsync#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs index 30846e4..ed7a1b6 100644 --- a/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.UIApplicationAfterFirstParameter_RemainsUserArgumentAsync#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs +++ b/tests/Nice3point.Revit.Toolkit.SourceGenerators.Tests/Snapshots/ExternalEventGeneratorTests.UIApplicationAfterFirstParameter_RemainsUserArgumentAsync#Consumer.Model.Run.C0F8FDCA0BFA30FC.verified.cs @@ -63,5 +63,12 @@ public static partial class ModelExtensions { return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(quantity, application)); } + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCode("Nice3point.Revit.Toolkit.SourceGenerators.ExternalEventGenerator", "1.0.0.0")] + public static global::System.Threading.Tasks.Task RaiseAsync(this global::Nice3point.Revit.Toolkit.External.IAsyncExternalEvent externalEvent, int quantity, global::Autodesk.Revit.UI.UIApplication application, global::System.Threading.CancellationToken cancellationToken) + { + return externalEvent.RaiseAsync(new global::Consumer.Model.RunArgs(quantity, application), cancellationToken); + } } } diff --git a/tests/Nice3point.Revit.Toolkit.Tests/AsyncExternalEventCoreTests.cs b/tests/Nice3point.Revit.Toolkit.Tests/AsyncExternalEventCoreTests.cs new file mode 100644 index 0000000..6f9a7e2 --- /dev/null +++ b/tests/Nice3point.Revit.Toolkit.Tests/AsyncExternalEventCoreTests.cs @@ -0,0 +1,157 @@ +using Autodesk.Revit.UI; +using Nice3point.Revit.Toolkit.External; +using Nice3point.TUnit.Revit; +using TUnit.Assertions.Enums; + +namespace Nice3point.Revit.Toolkit.Tests; + +public sealed class AsyncExternalEventCoreTests : RevitApiUiTest +{ + [Test] + [Arguments(nameof(ExternalEventRequest.Denied))] + [Arguments(nameof(ExternalEventRequest.TimedOut))] + public async Task RaiseAsync_RefusedRequest_FaultsTheTaskWithoutExecution(string refusalName) + { + // Arrange + var refusal = (ExternalEventRequest)Enum.Parse(typeof(ExternalEventRequest), refusalName); + var executionCount = 0; + var core = new AsyncExternalEventCore((_, argument, _) => argument + executionCount++, () => refusal); + + // Act + var raiseTask = core.RaiseAsync(42, CancellationToken.None); + core.Execute(null!); + + // Assert + using (Assert.Multiple()) + { + await Assert.That(() => raiseTask).Throws(); + await Assert.That(executionCount).IsZero(); + } + } + + [Test] + public async Task Execute_RefusedRaiseForTheRest_ExecutesTheRestInTheSameExecution() + { + // Arrange + var raiseResults = new Queue([ExternalEventRequest.Accepted, ExternalEventRequest.Pending, ExternalEventRequest.Pending, ExternalEventRequest.Denied, ExternalEventRequest.Denied]); + var core = new AsyncExternalEventCore((_, argument, _) => argument, raiseResults.Dequeue); + var firstTask = core.RaiseAsync(1, CancellationToken.None); + var secondTask = core.RaiseAsync(2, CancellationToken.None); + var thirdTask = core.RaiseAsync(3, CancellationToken.None); + + // Act + core.Execute(null!); + + // Assert + var results = await Task.WhenAll(firstTask, secondTask, thirdTask); + await Assert.That(results).IsEquivalentTo([1, 2, 3], CollectionOrdering.Matching); + } + + [Test] + public async Task Execute_AcceptedRaiseForTheRest_ExecutesTheOldestRaise() + { + // Arrange + var raiseCount = 0; + var core = new AsyncExternalEventCore((_, argument, _) => argument, () => + { + raiseCount++; + return ExternalEventRequest.Accepted; + }); + + var firstTask = core.RaiseAsync(1, CancellationToken.None); + var secondTask = core.RaiseAsync(2, CancellationToken.None); + + // Act + core.Execute(null!); + + // Assert + using (Assert.Multiple()) + { + await Assert.That(firstTask.Status).IsEqualTo(TaskStatus.RanToCompletion); + await Assert.That(secondTask.IsCompleted).IsFalse(); + await Assert.That(raiseCount).IsEqualTo(3); + } + } + + [Test] + [Arguments(nameof(ExternalEventRequest.Denied))] + [Arguments(nameof(ExternalEventRequest.TimedOut))] + public async Task Raise_RefusedRequest_ReturnsTheRefusalWithoutExecution(string refusalName) + { + // Arrange + var refusal = (ExternalEventRequest)Enum.Parse(typeof(ExternalEventRequest), refusalName); + var executionCount = 0; + var core = new AsyncExternalEventCore((_, argument, _) => argument + executionCount++, () => refusal); + + // Act + var request = core.Raise(42); + core.Execute(null!); + + // Assert + using (Assert.Multiple()) + { + await Assert.That(request).IsEqualTo(refusal); + await Assert.That(executionCount).IsZero(); + } + } + + [Test] + public async Task Raise_BetweenAsyncRaises_PreservesTheirResultsAndExecutionOrder() + { + // Arrange + var receivedArguments = new List(); + var receivedTokens = new List(); + using var cancellationSource = new CancellationTokenSource(); + var core = new AsyncExternalEventCore((_, argument, token) => + { + receivedArguments.Add(argument); + receivedTokens.Add(token); + return argument; + }, () => ExternalEventRequest.Accepted); + + // Act + var firstTask = core.RaiseAsync(1, cancellationSource.Token); + var request = core.Raise(2); + var lastTask = core.RaiseAsync(3, cancellationSource.Token); + core.Execute(null!); + core.Execute(null!); + var isLastTaskCompletedAfterSecondExecution = lastTask.IsCompleted; + core.Execute(null!); + + // Assert + using (Assert.Multiple()) + { + await Assert.That(request).IsEqualTo(ExternalEventRequest.Accepted); + await Assert.That(await firstTask).IsEqualTo(1); + await Assert.That(await lastTask).IsEqualTo(3); + await Assert.That(isLastTaskCompletedAfterSecondExecution).IsFalse(); + await Assert.That(receivedArguments).IsEquivalentTo([1, 2, 3], CollectionOrdering.Matching); + await Assert.That(receivedTokens).IsEquivalentTo([cancellationSource.Token, CancellationToken.None, cancellationSource.Token], CollectionOrdering.Matching); + } + } + + [Test] + public async Task Execute_RaiseHandlerThrows_PropagatesTheExceptionAndSchedulesTheFollowingRaise() + { + // Arrange + var failure = new InvalidOperationException("Handler failure"); + var raiseCount = 0; + var core = new AsyncExternalEventCore((_, argument, _) => argument == 1 ? throw failure : argument, () => + { + raiseCount++; + return ExternalEventRequest.Accepted; + }); + + core.Raise(1); + var followingTask = core.RaiseAsync(2, CancellationToken.None); + + // Act & Assert + var exception = await Assert.That(() => core.Execute(null!)).Throws(); + await Assert.That(exception).IsSameReferenceAs(failure); + await Assert.That(raiseCount).IsEqualTo(3); + await Assert.That(followingTask.IsCompleted).IsFalse(); + + core.Execute(null!); + await Assert.That(await followingTask).IsEqualTo(2); + } +} diff --git a/tests/Nice3point.Revit.Toolkit.Tests/AsyncExternalEventTests.cs b/tests/Nice3point.Revit.Toolkit.Tests/AsyncExternalEventTests.cs index 7d90566..f86136f 100644 --- a/tests/Nice3point.Revit.Toolkit.Tests/AsyncExternalEventTests.cs +++ b/tests/Nice3point.Revit.Toolkit.Tests/AsyncExternalEventTests.cs @@ -1,6 +1,7 @@ using Autodesk.Revit.UI; using Nice3point.Revit.Toolkit.External; using Nice3point.TUnit.Revit; +using TUnit.Assertions.Enums; namespace Nice3point.Revit.Toolkit.Tests; @@ -178,4 +179,267 @@ public async Task RaiseAsync_SequentialRaises_ExecutesEveryRaise(CancellationTok // Assert await Assert.That(executionCount).IsEqualTo(3); } + + [Test] + [Timeout(10_000)] + public async Task RaiseAsync_RaisesBeforeTheEventCycle_ExecutesTheHandlerForEveryRaise(CancellationToken cancellationToken) + { + // Arrange + var executionCount = 0; + var externalEvent = new AsyncExternalEvent(() => executionCount++); + + // Act + var firstTask = externalEvent.RaiseAsync(); + var secondTask = externalEvent.RaiseAsync(); + await Task.WhenAll(firstTask, secondTask).WaitAsync(cancellationToken); + + // Assert + await Assert.That(executionCount).IsEqualTo(2); + } + + [Test] + [Timeout(10_000)] + public async Task RaiseAsync_GenericRaisesBeforeTheEventCycle_ExecutesTheHandlerForEachArgumentInOrder(CancellationToken cancellationToken) + { + // Arrange + var receivedArguments = new List(); + var externalEvent = new AsyncExternalEvent(receivedArguments.Add); + + // Act + var firstTask = externalEvent.RaiseAsync(1); + var secondTask = externalEvent.RaiseAsync(2); + var thirdTask = externalEvent.RaiseAsync(3); + await Task.WhenAll(firstTask, secondTask, thirdTask).WaitAsync(cancellationToken); + + // Assert + await Assert.That(receivedArguments).IsEquivalentTo([1, 2, 3], CollectionOrdering.Matching); + } + + [Test] + [Timeout(10_000)] + public async Task RaiseAsync_GenericFirstRaiseThrows_CompletesTheFollowingRaise(CancellationToken cancellationToken) + { + // Arrange + var receivedArguments = new List(); + var externalEvent = new AsyncExternalEvent(argument => + { + receivedArguments.Add(argument); + if (argument == 1) + { + throw new InvalidOperationException("Handler failure"); + } + }); + + // Act + var failingTask = externalEvent.RaiseAsync(1); + var followingTask = externalEvent.RaiseAsync(2); + await followingTask.WaitAsync(cancellationToken); + + // Assert + using (Assert.Multiple()) + { + await Assert.That(() => failingTask.WaitAsync(cancellationToken)).Throws(); + await Assert.That(followingTask.Status).IsEqualTo(TaskStatus.RanToCompletion); + await Assert.That(receivedArguments).IsEquivalentTo([1, 2], CollectionOrdering.Matching); + } + } + + [Test] + public async Task RaiseAsync_CanceledToken_ReturnsCanceledTask() + { + // Arrange + var externalEvent = new AsyncExternalEvent(() => { }); + using var cancellationSource = new CancellationTokenSource(); + cancellationSource.Cancel(); + + // Act + var raiseTask = externalEvent.RaiseAsync(cancellationSource.Token); + + // Assert + await Assert.That(raiseTask.IsCanceled).IsTrue(); + } + + [Test] + [Timeout(10_000)] + public async Task RaiseAsync_TokenCanceledBeforeTheEventCycle_CancelsTheTaskWithoutExecution(CancellationToken cancellationToken) + { + // Arrange + var executionCount = 0; + var externalEvent = new AsyncExternalEvent(() => executionCount++); + using var cancellationSource = new CancellationTokenSource(); + + // Act + var canceledTask = externalEvent.RaiseAsync(cancellationSource.Token); + var followingTask = externalEvent.RaiseAsync(); + cancellationSource.Cancel(); + await followingTask.WaitAsync(cancellationToken); + + // Assert + using (Assert.Multiple()) + { + await Assert.That(canceledTask.IsCanceled).IsTrue(); + await Assert.That(executionCount).IsEqualTo(1); + } + } + + [Test] + [Timeout(10_000)] + public async Task RaiseAsync_TokenUiApplicationHandler_ReceivesTheUiApplicationAndToken(CancellationToken cancellationToken) + { + // Arrange + UIApplication? receivedApplication = null; + CancellationToken receivedToken = default; + using var cancellationSource = new CancellationTokenSource(); + var externalEvent = new AsyncExternalEvent((uiApplication, raiseToken) => + { + receivedApplication = uiApplication; + receivedToken = raiseToken; + }); + + // Act + await externalEvent.RaiseAsync(cancellationSource.Token).WaitAsync(cancellationToken); + + // Assert + using (Assert.Multiple()) + { + await Assert.That(receivedApplication!.MainWindowHandle).IsEqualTo(UiApplication.MainWindowHandle); + await Assert.That(receivedToken).IsEqualTo(cancellationSource.Token); + } + } + + [Test] + [Timeout(10_000)] + public async Task RaiseAsync_GenericTokenCanceledBeforeTheEventCycle_ExecutesOnlyTheFollowingRaise(CancellationToken cancellationToken) + { + // Arrange + var receivedArguments = new List(); + var externalEvent = new AsyncExternalEvent(receivedArguments.Add); + using var cancellationSource = new CancellationTokenSource(); + + // Act + var canceledTask = externalEvent.RaiseAsync(1, cancellationSource.Token); + var followingTask = externalEvent.RaiseAsync(2); + cancellationSource.Cancel(); + await followingTask.WaitAsync(cancellationToken); + + // Assert + using (Assert.Multiple()) + { + await Assert.That(canceledTask.IsCanceled).IsTrue(); + await Assert.That(receivedArguments).IsEquivalentTo([2]); + } + } + + [Test] + [Timeout(10_000)] + public async Task RaiseAsync_GenericTokenHandler_ReceivesTheArgumentAndToken(CancellationToken cancellationToken) + { + // Arrange + var receivedArgument = 0; + CancellationToken receivedToken = default; + using var cancellationSource = new CancellationTokenSource(); + var externalEvent = new AsyncExternalEvent((_, argument, raiseToken) => + { + receivedArgument = argument; + receivedToken = raiseToken; + }); + + // Act + await externalEvent.RaiseAsync(42, cancellationSource.Token).WaitAsync(cancellationToken); + + // Assert + using (Assert.Multiple()) + { + await Assert.That(receivedArgument).IsEqualTo(42); + await Assert.That(receivedToken).IsEqualTo(cancellationSource.Token); + } + } + + [Test] + [Timeout(10_000)] + public async Task Raise_AllowDirectInvocation_QueuesEveryCallWithoutWaiting(CancellationToken cancellationToken) + { + // Arrange + var receivedTokens = new List(); + var execution = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + ExternalEventHandler externalEvent = new AsyncExternalEvent((_, token) => + { + receivedTokens.Add(token); + if (receivedTokens.Count == 2) + { + execution.SetResult(); + } + }, ExternalEventOptions.AllowDirectInvocation); + + // Act + var firstRequest = externalEvent.Raise(); + var secondRequest = externalEvent.Raise(); + var executionCountOnRaise = receivedTokens.Count; + await execution.Task.WaitAsync(cancellationToken); + + // Assert + using (Assert.Multiple()) + { + await Assert.That(firstRequest).IsEqualTo(ExternalEventRequest.Accepted); + await Assert.That(secondRequest).IsEqualTo(ExternalEventRequest.Pending); + await Assert.That(executionCountOnRaise).IsZero(); + await Assert.That(receivedTokens).IsEquivalentTo([CancellationToken.None, CancellationToken.None], CollectionOrdering.Matching); + } + } + + [Test] + [Timeout(10_000)] + public async Task Raise_GenericMixedRaises_QueuesTheLastArgumentWithoutChangingAsyncCompletion(CancellationToken cancellationToken) + { + // Arrange + var receivedArguments = new List(); + var externalEvent = new AsyncExternalEvent(receivedArguments.Add); + + // Act + var firstRequest = externalEvent.Raise(); + var firstTask = externalEvent.RaiseAsync(1); + externalEvent.Raise(); + var secondTask = externalEvent.RaiseAsync(2); + externalEvent.Raise(); + var lastTask = externalEvent.RaiseAsync(3); + await Task.WhenAll(firstTask, secondTask, lastTask).WaitAsync(cancellationToken); + + // Assert + using (Assert.Multiple()) + { + await Assert.That(firstRequest).IsEqualTo(ExternalEventRequest.Accepted); + await Assert.That(receivedArguments).IsEquivalentTo([0, 1, 1, 2, 2, 3], CollectionOrdering.Matching); + } + } + + [Test] + [Timeout(10_000)] + public async Task Raise_GenericAfterDirectInvocation_QueuesTheLastArgument(CancellationToken cancellationToken) + { + // Arrange + var receivedArguments = new List(); + var execution = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var externalEvent = new AsyncExternalEvent(argument => + { + receivedArguments.Add(argument); + if (receivedArguments.Count == 2) + { + execution.SetResult(); + } + }, ExternalEventOptions.AllowDirectInvocation); + + await externalEvent.RaiseAsync(42).WaitAsync(cancellationToken); + + // Act + externalEvent.Raise(); + var executionCountOnRaise = receivedArguments.Count; + await execution.Task.WaitAsync(cancellationToken); + + // Assert + using (Assert.Multiple()) + { + await Assert.That(executionCountOnRaise).IsEqualTo(1); + await Assert.That(receivedArguments).IsEquivalentTo([42, 42], CollectionOrdering.Matching); + } + } } diff --git a/tests/Nice3point.Revit.Toolkit.Tests/AsyncRequestExternalEventTests.cs b/tests/Nice3point.Revit.Toolkit.Tests/AsyncRequestExternalEventTests.cs index 678df9b..2533853 100644 --- a/tests/Nice3point.Revit.Toolkit.Tests/AsyncRequestExternalEventTests.cs +++ b/tests/Nice3point.Revit.Toolkit.Tests/AsyncRequestExternalEventTests.cs @@ -1,5 +1,6 @@ using Nice3point.Revit.Toolkit.External; using Nice3point.TUnit.Revit; +using TUnit.Assertions.Enums; namespace Nice3point.Revit.Toolkit.Tests; @@ -147,4 +148,415 @@ public async Task RaiseAsync_ArgumentAllowDirectInvocation_CompletesOnRaiseWithT await Assert.That(await raiseTask).IsEqualTo(42); } } + + [Test] + [Timeout(10_000)] + public async Task RaiseAsync_RaisesBeforeTheEventCycle_CompletesEachTaskWithItsOwnResult(CancellationToken cancellationToken) + { + // Arrange + var executionCount = 0; + var externalEvent = new AsyncRequestExternalEvent(() => ++executionCount); + + // Act + var firstTask = externalEvent.RaiseAsync(); + var secondTask = externalEvent.RaiseAsync(); + var results = await Task.WhenAll(firstTask, secondTask).WaitAsync(cancellationToken); + + // Assert + await Assert.That(results).IsEquivalentTo([1, 2], CollectionOrdering.Matching); + } + + [Test] + [Timeout(10_000)] + public async Task RaiseAsync_ArgumentRaisesBeforeTheEventCycle_CompletesEachTaskWithTheResultOfItsArgument(CancellationToken cancellationToken) + { + // Arrange + var receivedArguments = new List(); + var externalEvent = new AsyncRequestExternalEvent(argument => + { + receivedArguments.Add(argument); + return argument; + }); + + // Act + var firstTask = externalEvent.RaiseAsync(1); + var secondTask = externalEvent.RaiseAsync(2); + var results = await Task.WhenAll(firstTask, secondTask).WaitAsync(cancellationToken); + + // Assert + using (Assert.Multiple()) + { + await Assert.That(results).IsEquivalentTo([1, 2], CollectionOrdering.Matching); + await Assert.That(receivedArguments).IsEquivalentTo([1, 2], CollectionOrdering.Matching); + } + } + + [Test] + [Timeout(10_000)] + public async Task RaiseAsync_ArgumentFirstRaiseThrows_CompletesTheFollowingRaiseWithItsResult(CancellationToken cancellationToken) + { + // Arrange + var externalEvent = new AsyncRequestExternalEvent(argument => argument == 1 ? throw new InvalidOperationException("Handler failure") : argument); + + // Act + var failingTask = externalEvent.RaiseAsync(1); + var followingTask = externalEvent.RaiseAsync(2); + var followingResult = await followingTask.WaitAsync(cancellationToken); + + // Assert + using (Assert.Multiple()) + { + await Assert.That(() => failingTask.WaitAsync(cancellationToken)).Throws(); + await Assert.That(followingResult).IsEqualTo(2); + } + } + + [Test] + [Timeout(10_000)] + public async Task RaiseAsync_CanceledToken_ReturnsCanceledTaskWithoutExecution(CancellationToken cancellationToken) + { + // Arrange + var receivedArguments = new List(); + var externalEvent = new AsyncRequestExternalEvent(argument => + { + receivedArguments.Add(argument); + return argument; + }); + + using var cancellationSource = new CancellationTokenSource(); + cancellationSource.Cancel(); + + // Act + var canceledTask = externalEvent.RaiseAsync(1, cancellationSource.Token); + var followingResult = await externalEvent.RaiseAsync(2).WaitAsync(cancellationToken); + + // Assert + using (Assert.Multiple()) + { + await Assert.That(canceledTask.IsCanceled).IsTrue(); + await Assert.That(followingResult).IsEqualTo(2); + await Assert.That(receivedArguments).IsEquivalentTo([2]); + } + } + + [Test] + [Timeout(10_000)] + public async Task RaiseAsync_TokenCanceledBeforeTheEventCycle_CancelsTheTaskAtOnceAndExecutesTheFollowingRaise(CancellationToken cancellationToken) + { + // Arrange + var receivedArguments = new List(); + var externalEvent = new AsyncRequestExternalEvent(argument => + { + receivedArguments.Add(argument); + return argument; + }); + + using var cancellationSource = new CancellationTokenSource(); + + // Act + var canceledTask = externalEvent.RaiseAsync(1, cancellationSource.Token); + var followingTask = externalEvent.RaiseAsync(2); + cancellationSource.Cancel(); + var isCanceledOnCancel = canceledTask.IsCanceled; + var followingResult = await followingTask.WaitAsync(cancellationToken); + + // Assert + using (Assert.Multiple()) + { + await Assert.That(isCanceledOnCancel).IsTrue(); + await Assert.That(followingResult).IsEqualTo(2); + await Assert.That(receivedArguments).IsEquivalentTo([2]); + } + } + + [Test] + public async Task RaiseAsync_TokenCanceledBeforeTheEventCycle_CancelsTheTaskWithThatToken() + { + // Arrange + var externalEvent = new AsyncRequestExternalEvent(argument => argument); + using var cancellationSource = new CancellationTokenSource(); + + // Act + var canceledTask = externalEvent.RaiseAsync(1, cancellationSource.Token); + cancellationSource.Cancel(); + + // Assert + var exception = await Assert.That(() => canceledTask).Throws(); + await Assert.That(exception!.CancellationToken).IsEqualTo(cancellationSource.Token); + } + + [Test] + [Timeout(10_000)] + public async Task RaiseAsync_TokenCanceledWhileTheHandlerExecutes_CompletesWithTheHandlerResult(CancellationToken cancellationToken) + { + // Arrange + using var cancellationSource = new CancellationTokenSource(); + var externalEvent = new AsyncRequestExternalEvent(argument => + { + cancellationSource.Cancel(); + return argument; + }); + + // Act + var result = await externalEvent.RaiseAsync(42, cancellationSource.Token).WaitAsync(cancellationToken); + + // Assert + await Assert.That(result).IsEqualTo(42); + } + + [Test] + [Timeout(10_000)] + public async Task RaiseAsync_TokenHandler_ReceivesTheTokenOfTheRaise(CancellationToken cancellationToken) + { + // Arrange + using var cancellationSource = new CancellationTokenSource(); + var externalEvent = new AsyncRequestExternalEvent((_, _, raiseToken) => raiseToken); + + // Act + var receivedToken = await externalEvent.RaiseAsync(42, cancellationSource.Token).WaitAsync(cancellationToken); + + // Assert + await Assert.That(receivedToken).IsEqualTo(cancellationSource.Token); + } + + [Test] + [Timeout(10_000)] + public async Task RaiseAsync_TokenHandlerThrowsForItsToken_CancelsTheTask(CancellationToken cancellationToken) + { + // Arrange + using var cancellationSource = new CancellationTokenSource(); + var externalEvent = new AsyncRequestExternalEvent((_, argument, raiseToken) => + { + cancellationSource.Cancel(); + raiseToken.ThrowIfCancellationRequested(); + return argument; + }); + + // Act + var raiseTask = externalEvent.RaiseAsync(42, cancellationSource.Token); + + // Assert + var exception = await Assert.That(() => raiseTask.WaitAsync(cancellationToken)).Throws(); + await Assert.That(exception!.CancellationToken).IsEqualTo(cancellationSource.Token); + } + + [Test] + [Timeout(10_000)] + public async Task RaiseAsync_HandlerThrowsForAnotherToken_FaultsTheTask(CancellationToken cancellationToken) + { + // Arrange + using var raiseCancellationSource = new CancellationTokenSource(); + using var otherCancellationSource = new CancellationTokenSource(); + otherCancellationSource.Cancel(); + var externalEvent = new AsyncRequestExternalEvent(_ => throw new OperationCanceledException(otherCancellationSource.Token)); + + // Act + var raiseTask = externalEvent.RaiseAsync(42, raiseCancellationSource.Token); + + // Assert + await Assert.That(() => raiseTask.WaitAsync(cancellationToken)).Throws(); + await Assert.That(raiseTask.IsFaulted).IsTrue(); + } + + [Test] + public async Task RaiseAsync_AllowDirectInvocationCanceledToken_ReturnsCanceledTaskWithoutExecution() + { + // Arrange + var executionCount = 0; + var externalEvent = new AsyncRequestExternalEvent(argument => argument + executionCount++, ExternalEventOptions.AllowDirectInvocation); + using var cancellationSource = new CancellationTokenSource(); + cancellationSource.Cancel(); + + // Act + var raiseTask = externalEvent.RaiseAsync(42, cancellationSource.Token); + + // Assert + using (Assert.Multiple()) + { + await Assert.That(raiseTask.IsCanceled).IsTrue(); + await Assert.That(executionCount).IsZero(); + } + } + + [Test] + public async Task RaiseAsync_AllowDirectInvocationTokenCanceledWhileTheHandlerExecutes_CompletesWithTheHandlerResult() + { + // Arrange + using var cancellationSource = new CancellationTokenSource(); + var externalEvent = new AsyncRequestExternalEvent(argument => + { + cancellationSource.Cancel(); + return argument; + }, ExternalEventOptions.AllowDirectInvocation); + + // Act + var raiseTask = externalEvent.RaiseAsync(42, cancellationSource.Token); + + // Assert + using (Assert.Multiple()) + { + await Assert.That(raiseTask.Status).IsEqualTo(TaskStatus.RanToCompletion); + await Assert.That(await raiseTask).IsEqualTo(42); + } + } + + [Test] + public async Task RaiseAsync_AllowDirectInvocationTokenHandlerThrowsForItsToken_ReturnsCanceledTask() + { + // Arrange + using var cancellationSource = new CancellationTokenSource(); + var externalEvent = new AsyncRequestExternalEvent((_, argument, raiseToken) => + { + cancellationSource.Cancel(); + raiseToken.ThrowIfCancellationRequested(); + return argument; + }, ExternalEventOptions.AllowDirectInvocation); + + // Act + var raiseTask = externalEvent.RaiseAsync(42, cancellationSource.Token); + + // Assert + await Assert.That(raiseTask.IsCanceled).IsTrue(); + } + + [Test] + [Timeout(10_000)] + public async Task RaiseAsync_TokenUiApplicationHandler_ReceivesTheUiApplicationAndToken(CancellationToken cancellationToken) + { + // Arrange + using var cancellationSource = new CancellationTokenSource(); + var externalEvent = new AsyncRequestExternalEvent<(IntPtr MainWindowHandle, CancellationToken Token)>((uiApplication, raiseToken) => (uiApplication.MainWindowHandle, raiseToken)); + + // Act + var (mainWindowHandle, receivedToken) = await externalEvent.RaiseAsync(cancellationSource.Token).WaitAsync(cancellationToken); + + // Assert + using (Assert.Multiple()) + { + await Assert.That(mainWindowHandle).IsEqualTo(UiApplication.MainWindowHandle); + await Assert.That(receivedToken).IsEqualTo(cancellationSource.Token); + } + } + + [Test] + [Timeout(10_000)] + public async Task RaiseAsync_TokenCanceledBeforeTheEventCycle_CancelsTheTaskWithoutExecution(CancellationToken cancellationToken) + { + // Arrange + var executionCount = 0; + var externalEvent = new AsyncRequestExternalEvent(() => ++executionCount); + using var cancellationSource = new CancellationTokenSource(); + + // Act + var canceledTask = externalEvent.RaiseAsync(cancellationSource.Token); + var followingTask = externalEvent.RaiseAsync(); + cancellationSource.Cancel(); + var followingResult = await followingTask.WaitAsync(cancellationToken); + + // Assert + using (Assert.Multiple()) + { + await Assert.That(canceledTask.IsCanceled).IsTrue(); + await Assert.That(followingResult).IsEqualTo(1); + } + } + + [Test] + [Timeout(10_000)] + public async Task Raise_AllowDirectInvocation_QueuesEveryCallWithoutWaiting(CancellationToken cancellationToken) + { + // Arrange + var receivedTokens = new List(); + var execution = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + ExternalEventHandler externalEvent = new AsyncRequestExternalEvent((_, token) => + { + receivedTokens.Add(token); + if (receivedTokens.Count == 2) + { + execution.SetResult(); + } + + return receivedTokens.Count; + }, ExternalEventOptions.AllowDirectInvocation); + + // Act + var firstRequest = externalEvent.Raise(); + var secondRequest = externalEvent.Raise(); + var executionCountOnRaise = receivedTokens.Count; + await execution.Task.WaitAsync(cancellationToken); + + // Assert + using (Assert.Multiple()) + { + await Assert.That(firstRequest).IsEqualTo(Autodesk.Revit.UI.ExternalEventRequest.Accepted); + await Assert.That(secondRequest).IsEqualTo(Autodesk.Revit.UI.ExternalEventRequest.Pending); + await Assert.That(executionCountOnRaise).IsZero(); + await Assert.That(receivedTokens).IsEquivalentTo([CancellationToken.None, CancellationToken.None], CollectionOrdering.Matching); + } + } + + [Test] + [Timeout(10_000)] + public async Task Raise_GenericMixedRaises_QueuesTheLastArgumentWithoutChangingAsyncCompletion(CancellationToken cancellationToken) + { + // Arrange + var receivedArguments = new List(); + var externalEvent = new AsyncRequestExternalEvent(argument => + { + receivedArguments.Add(argument); + return argument; + }); + + // Act + var firstRequest = externalEvent.Raise(); + var firstTask = externalEvent.RaiseAsync(1); + externalEvent.Raise(); + var secondTask = externalEvent.RaiseAsync(2); + externalEvent.Raise(); + var lastTask = externalEvent.RaiseAsync(3); + await Task.WhenAll(firstTask, secondTask, lastTask).WaitAsync(cancellationToken); + + // Assert + using (Assert.Multiple()) + { + await Assert.That(firstRequest).IsEqualTo(Autodesk.Revit.UI.ExternalEventRequest.Accepted); + await Assert.That(await firstTask).IsEqualTo(1); + await Assert.That(await secondTask).IsEqualTo(2); + await Assert.That(await lastTask).IsEqualTo(3); + await Assert.That(receivedArguments).IsEquivalentTo([0, 1, 1, 2, 2, 3], CollectionOrdering.Matching); + } + } + + [Test] + [Timeout(10_000)] + public async Task Raise_GenericAfterDirectInvocation_QueuesTheLastArgument(CancellationToken cancellationToken) + { + // Arrange + var receivedArguments = new List(); + var execution = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var externalEvent = new AsyncRequestExternalEvent(argument => + { + receivedArguments.Add(argument); + if (receivedArguments.Count == 2) + { + execution.SetResult(); + } + + return argument; + }, ExternalEventOptions.AllowDirectInvocation); + + await externalEvent.RaiseAsync(42).WaitAsync(cancellationToken); + + // Act + externalEvent.Raise(); + var executionCountOnRaise = receivedArguments.Count; + await execution.Task.WaitAsync(cancellationToken); + + // Assert + using (Assert.Multiple()) + { + await Assert.That(executionCountOnRaise).IsEqualTo(1); + await Assert.That(receivedArguments).IsEquivalentTo([42, 42], CollectionOrdering.Matching); + } + } } diff --git a/tests/Nice3point.Revit.Toolkit.Tests/ExternalEventCoreTests.cs b/tests/Nice3point.Revit.Toolkit.Tests/ExternalEventCoreTests.cs new file mode 100644 index 0000000..154228e --- /dev/null +++ b/tests/Nice3point.Revit.Toolkit.Tests/ExternalEventCoreTests.cs @@ -0,0 +1,67 @@ +using Autodesk.Revit.UI; +using Nice3point.Revit.Toolkit.External; +using Nice3point.TUnit.Revit; +using TUnit.Assertions.Enums; + +namespace Nice3point.Revit.Toolkit.Tests; + +public sealed class ExternalEventCoreTests : RevitApiUiTest +{ + [Test] + [Arguments(nameof(ExternalEventRequest.Denied))] + [Arguments(nameof(ExternalEventRequest.TimedOut))] + public async Task Raise_RefusedRequest_NeverExecutesTheArgument(string refusalName) + { + // Arrange + var refusal = (ExternalEventRequest)Enum.Parse(typeof(ExternalEventRequest), refusalName); + var raiseResults = new Queue([refusal, ExternalEventRequest.Accepted]); + var receivedArguments = new List(); + var core = new ExternalEventCore((_, argument) => receivedArguments.Add(argument), raiseResults.Dequeue); + + // Act + var refusedRequest = core.Raise(1); + var acceptedRequest = core.Raise(2); + core.Execute(null!); + + // Assert + using (Assert.Multiple()) + { + await Assert.That(refusedRequest).IsEqualTo(refusal); + await Assert.That(acceptedRequest).IsEqualTo(ExternalEventRequest.Accepted); + await Assert.That(receivedArguments).IsEquivalentTo([2]); + } + } + + [Test] + public async Task Execute_RefusedRaiseForTheRest_ExecutesTheRestInTheSameExecution() + { + // Arrange + var raiseResults = new Queue([ExternalEventRequest.Accepted, ExternalEventRequest.Pending, ExternalEventRequest.TimedOut]); + var receivedArguments = new List(); + var core = new ExternalEventCore((_, argument) => receivedArguments.Add(argument), raiseResults.Dequeue); + core.Raise(1); + core.Raise(2); + + // Act + core.Execute(null!); + + // Assert + await Assert.That(receivedArguments).IsEquivalentTo([1, 2], CollectionOrdering.Matching); + } + + [Test] + public async Task Execute_AcceptedRaiseForTheRest_ExecutesTheOldestRaise() + { + // Arrange + var receivedArguments = new List(); + var core = new ExternalEventCore((_, argument) => receivedArguments.Add(argument), () => ExternalEventRequest.Accepted); + core.Raise(1); + core.Raise(2); + + // Act + core.Execute(null!); + + // Assert + await Assert.That(receivedArguments).IsEquivalentTo([1]); + } +} diff --git a/tests/Nice3point.Revit.Toolkit.Tests/ExternalEventTests.cs b/tests/Nice3point.Revit.Toolkit.Tests/ExternalEventTests.cs index e25f081..05a1a09 100644 --- a/tests/Nice3point.Revit.Toolkit.Tests/ExternalEventTests.cs +++ b/tests/Nice3point.Revit.Toolkit.Tests/ExternalEventTests.cs @@ -1,6 +1,7 @@ using Autodesk.Revit.UI; using Nice3point.Revit.Toolkit.External; using Nice3point.TUnit.Revit; +using TUnit.Assertions.Enums; using ExternalEvent = Nice3point.Revit.Toolkit.External.ExternalEvent; namespace Nice3point.Revit.Toolkit.Tests; @@ -127,6 +128,51 @@ public async Task Raise_GenericAllowDirectInvocation_ExecutesOnRaiseWithTheArgum await Assert.That(receivedArguments).IsEquivalentTo([1, 2]); } + [Test] + [Timeout(10_000)] + public async Task Raise_RaisesBeforeTheEventCycle_ExecutesTheHandlerOnce(CancellationToken cancellationToken) + { + // Arrange + var executionCount = 0; + var externalEvent = new ExternalEvent(() => executionCount++); + var eventCycle = new AsyncExternalEvent(() => { }); + + // Act + externalEvent.Raise(); + externalEvent.Raise(); + await eventCycle.RaiseAsync().WaitAsync(cancellationToken); + await eventCycle.RaiseAsync().WaitAsync(cancellationToken); + + // Assert + await Assert.That(executionCount).IsEqualTo(1); + } + + [Test] + [Timeout(10_000)] + public async Task Raise_GenericRaisesBeforeTheEventCycle_ExecutesTheHandlerForEachArgumentInOrder(CancellationToken cancellationToken) + { + // Arrange + var receivedArguments = new List(); + var execution = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var externalEvent = new ExternalEvent(argument => + { + receivedArguments.Add(argument); + if (receivedArguments.Count == 3) + { + execution.SetResult(); + } + }); + + // Act + externalEvent.Raise(1); + externalEvent.Raise(2); + externalEvent.Raise(3); + await execution.Task.WaitAsync(cancellationToken); + + // Assert + await Assert.That(receivedArguments).IsEquivalentTo([1, 2, 3], CollectionOrdering.Matching); + } + [Test] public async Task GetName_DefaultIdentifier_ReturnsTheTypeName() { @@ -146,4 +192,40 @@ public async Task Constructor_NullHandler_ThrowsArgumentNullException() // Act & Assert await Assert.That(() => new ExternalEvent((Action)null!)).Throws(); } + + [Test] + [Arguments(false)] + [Arguments(true)] + [Timeout(10_000)] + public async Task Raise_GenericWithoutArgument_QueuesTheLastArgument(bool allowDirectInvocation, CancellationToken cancellationToken) + { + // Arrange + var receivedArguments = new List(); + var execution = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var options = allowDirectInvocation ? ExternalEventOptions.AllowDirectInvocation : ExternalEventOptions.None; + int[] expectedArguments = allowDirectInvocation ? [42, 0, 42] : [0, 42, 42]; + var externalEvent = new ExternalEvent(argument => + { + receivedArguments.Add(argument); + if (receivedArguments.Count == 3) + { + execution.SetResult(); + } + }, options); + + // Act + var firstRequest = externalEvent.Raise(); + externalEvent.Raise(42); + externalEvent.Raise(); + var executionCountOnRaise = receivedArguments.Count; + await execution.Task.WaitAsync(cancellationToken); + + // Assert + using (Assert.Multiple()) + { + await Assert.That(firstRequest).IsEqualTo(ExternalEventRequest.Accepted); + await Assert.That(executionCountOnRaise).IsEqualTo(allowDirectInvocation ? 1 : 0); + await Assert.That(receivedArguments).IsEquivalentTo(expectedArguments, CollectionOrdering.Matching); + } + } }