Browse Source

AsyncCall common api

flabbet 1 year ago
parent
commit
b7b0ef1ff9

+ 8 - 4
samples/Sample4_CreatePopup/CreatePopupSampleExtension.cs

@@ -1,4 +1,5 @@
-using PixiEditor.Extensions.Wasm;
+using System.Threading.Tasks;
+using PixiEditor.Extensions.Wasm;
 using PixiEditor.Extensions.Wasm.Api.FlyUI;
 
 namespace CreatePopupSample;
@@ -17,10 +18,13 @@ public class CreatePopupSampleExtension : WasmExtension
     /// <summary>
     ///     This method is called when extension is initialized. After this method is called, you can use Api property to access PixiEditor API.
     /// </summary>
-    public override async void OnInitialized()
+    public override void OnInitialized()
     {
         var popup = Api.WindowProvider.CreatePopupWindow("Hello World", new Text("Hello from popup!"));
-        await popup.ShowDialog().;
-        Api.Logger.Log("Popup closed");
+        popup.ShowDialog().Completed += (result) =>
+        {
+            string resultStr = result.HasValue ? result.Value.ToString() : "null";
+            Api.Logger.Log($"Popup closed with result: {resultStr}");
+        };
     }
 }

+ 3 - 2
src/PixiEditor.AvaloniaUI/Views/Dialogs/PixiEditorPopup.cs

@@ -5,6 +5,7 @@ using Avalonia.Controls;
 using Avalonia.Styling;
 using CommunityToolkit.Mvvm.Input;
 using PixiEditor.Extensions.CommonApi;
+using PixiEditor.Extensions.CommonApi.Async;
 using PixiEditor.Extensions.CommonApi.Windowing;
 
 namespace PixiEditor.AvaloniaUI.Views.Dialogs;
@@ -52,9 +53,9 @@ public partial class PixiEditorPopup : Window, IPopupWindow
         Show(MainWindow.Current);
     }
 
-    public async Task<bool?> ShowDialog()
+    public AsyncCall<bool?> ShowDialog()
     {
-        return await ShowDialog<bool?>(MainWindow.Current);
+        return AsyncCall<bool?>.FromTask(ShowDialog<bool?>(MainWindow.Current));
     }
 
     [RelayCommand]

+ 137 - 0
src/PixiEditor.Extensions.CommonApi/Async/AsyncCall.cs

@@ -0,0 +1,137 @@
+namespace PixiEditor.Extensions.CommonApi.Async;
+
+public delegate void AsyncCallCompleted();
+public delegate void AsyncCallCompleted<T>(T result);
+public delegate void AsyncCallFailed(Exception exception);
+    
+public class AsyncCall
+{
+    private object? _result;
+    public AsyncCallState State { get; protected set; } = AsyncCallState.Pending;
+    public bool IsCompleted => State != AsyncCallState.Pending;
+    public Exception? Exception { get; protected set; }
+
+    public object Result
+    {
+        get
+        {
+            return _result;
+        }
+        protected set
+        {
+            _result = SetResultValue(value);
+        }
+    }
+    
+    public event AsyncCallCompleted Completed;
+    public event AsyncCallFailed Failed;
+    
+    public void SetException(Exception exception)
+    {
+        if (State != AsyncCallState.Pending)
+        {
+            throw new InvalidOperationException("Cannot set exception on completed async call.");
+        }
+        
+        State = AsyncCallState.Failed;
+        Exception = exception;
+        Failed?.Invoke(exception);
+    }
+    
+    public void SetResult(object? result)
+    {
+        if (State != AsyncCallState.Pending)
+        {
+            throw new InvalidOperationException("Cannot set result on completed async call.");
+        }
+        
+        State = AsyncCallState.Completed;
+        Result = result;
+        Completed?.Invoke();
+    }
+    
+    protected virtual object SetResultValue(object? result)
+    {
+        return result;
+    }
+}
+
+public class AsyncCall<TResult> : AsyncCall
+{
+    public new TResult Result
+    {
+        get => (TResult) base.Result;
+        protected set => base.Result = value;
+    }
+    
+    public new event AsyncCallCompleted<TResult> Completed;
+    
+    public AsyncCall()
+    {
+        base.Completed += () => Completed?.Invoke(Result);
+    }
+    
+    public Task<TResult> AsTask()
+    {
+        TaskCompletionSource<TResult> tcs = new TaskCompletionSource<TResult>();
+        Completed += (result) => tcs.SetResult(result);
+        Failed += (exception) => tcs.SetException(exception);
+        return tcs.Task;
+    }
+    
+    public void SetResult(TResult result)
+    {
+        if (State != AsyncCallState.Pending)
+        {
+            throw new InvalidOperationException("Cannot set result on completed async call.");
+        }
+        
+        State = AsyncCallState.Completed;
+        Result = result;
+        Completed?.Invoke(result);
+    }
+    
+    public AsyncCall<T> ContinueWith<T>(Func<AsyncCall<TResult>, T> action)
+    {
+        AsyncCall<T> asyncCall = new AsyncCall<T>();
+        Completed += (result) => asyncCall.SetResult(action(this));
+        Failed += (exception) => asyncCall.SetException(exception);
+        return asyncCall;
+    }
+    
+    public AsyncCall ContinueWith(Action<AsyncCall<TResult>> action)
+    {
+        AsyncCall asyncCall = new AsyncCall();
+        Completed += (result) =>
+        {
+            action(this);
+            asyncCall.SetResult(null);
+        };
+        Failed += (exception) => asyncCall.SetException(exception);
+        return asyncCall;
+    }
+
+    public static AsyncCall<TResult> FromTask(Task<TResult> task)
+    {
+        AsyncCall<TResult> asyncCall = new AsyncCall<TResult>();
+        task.ContinueWith(t =>
+        {
+            if (t.IsFaulted)
+            {
+                asyncCall.SetException(t.Exception);
+            }
+            else
+            {
+                asyncCall.SetResult(t.Result);
+            }
+        });
+        return asyncCall;
+    }
+}
+
+public enum AsyncCallState
+{
+    Pending,
+    Completed,
+    Failed
+}

+ 4 - 2
src/PixiEditor.Extensions.CommonApi/Windowing/IPopupWindow.cs

@@ -1,11 +1,13 @@
-namespace PixiEditor.Extensions.CommonApi.Windowing;
+using PixiEditor.Extensions.CommonApi.Async;
+
+namespace PixiEditor.Extensions.CommonApi.Windowing;
 
 public interface IPopupWindow
 {
     public string? Title { get; set; }
     public void Show();
     public void Close();
-    public Task<bool?> ShowDialog();
+    public AsyncCall<bool?> ShowDialog(); 
     public double Width { get; set; }
     public double Height { get; set; }
     public bool CanResize { get; set; }

+ 6 - 5
src/PixiEditor.Extensions.Wasm/Api/Window/PopupWindow.cs

@@ -1,5 +1,5 @@
+using PixiEditor.Extensions.CommonApi.Async;
 using PixiEditor.Extensions.CommonApi.Windowing;
-using PixiEditor.Extensions.Wasm.Async;
 using PixiEditor.Extensions.Wasm.Bridge;
 
 namespace PixiEditor.Extensions.Wasm.Api.Window;
@@ -29,10 +29,10 @@ public class PopupWindow : IPopupWindow
         Native.close_window(windowHandle);
     }
 
-    public AsyncCall ShowDialog()
+    public AsyncCall<bool?> ShowDialog()
     {
         int asyncHandle = Native.show_window_async(windowHandle);
-        AsyncCall showDialogTask = Native.AsyncHandleToTask<int>(asyncHandle);
+        AsyncCall<bool?> showDialogTask = Native.CreateAsyncCall<bool?, int>(asyncHandle, ConvertWindowResult);
         return showDialogTask;
     }
 
@@ -60,8 +60,9 @@ public class PopupWindow : IPopupWindow
         set => Native.set_window_minimizable(windowHandle, value);
     }
     
-    Task<bool?> IPopupWindow.ShowDialog()
+    private bool? ConvertWindowResult(int result)
     {
-        throw new PlatformNotSupportedException("Task-based ShowDialog is not supproted in Wasm. Use PopupWindows.ShowDialog() with AsyncCall return type instead.");
+        if(result == -1) return null;
+        return result == 1;
     }
 }

+ 0 - 43
src/PixiEditor.Extensions.Wasm/Async/AsyncCall.cs

@@ -1,43 +0,0 @@
-namespace PixiEditor.Extensions.Wasm.Async;
-
-public class AsyncCall
-{
-    public int AsyncHandle { get; }
-    private AsyncCallStatus AsyncState { get; set; } = AsyncCallStatus.NotStarted;
-    public bool IsCompleted => AsyncState switch
-    {
-        AsyncCallStatus.Completed => true,
-        AsyncCallStatus.Faulted => true,
-        _ => false
-    };
-    private TaskCompletionSource<int> TaskCompletionSource { get; } = new TaskCompletionSource<int>();
-
-    public event Action<int> Completed;
-    public event Action<Exception> Faulted;
-    
-    public AsyncCall(int asyncHandle)
-    {
-        AsyncHandle = asyncHandle;
-    }
-    
-    public void SetResult(int result)
-    {
-        TaskCompletionSource.SetResult(result);
-        Completed?.Invoke(result);
-    }
-
-    public void SetException(Exception exception)
-    {
-        TaskCompletionSource.SetException(exception);
-        Faulted?.Invoke(exception);
-    }
-}
-
-public enum AsyncCallStatus
-{
-    NotStarted,
-    Started,
-    Running,
-    Completed,
-    Faulted
-} 

+ 27 - 0
src/PixiEditor.Extensions.Wasm/Async/ConvertableAsyncCall.cs

@@ -0,0 +1,27 @@
+using PixiEditor.Extensions.CommonApi.Async;
+
+namespace PixiEditor.Extensions.Wasm.Async;
+
+public class ConvertableAsyncCall<TResult, TInput> : AsyncCall<TResult>
+{
+    public Func<TInput, TResult> Converter { get; }
+    
+    public ConvertableAsyncCall(Func<TInput, TResult> converter)
+    {
+        Converter = converter;
+    }
+
+    protected override object SetResultValue(object? result)
+    {
+        if(result is null)
+        {
+            return default(TResult);
+        }
+        if (result is TInput input)
+        {
+            return Converter(input);
+        }
+
+        throw new InvalidCastException($"Cannot convert {result.GetType().Name} to {typeof(TInput).Name}.");
+    }
+}

+ 12 - 4
src/PixiEditor.Extensions.Wasm/Bridge/Native.Async.cs

@@ -1,4 +1,5 @@
-using PixiEditor.Extensions.Wasm.Async;
+using PixiEditor.Extensions.CommonApi.Async;
+using PixiEditor.Extensions.Wasm.Async;
 
 namespace PixiEditor.Extensions.Wasm.Bridge;
 
@@ -30,10 +31,17 @@ internal partial class Native
         asyncCalls.Remove(asyncHandle);
     }
     
-    // TODO: More types
-    public static AsyncCall AsyncHandleToTask<T>(int asyncHandle)
+    public static AsyncCall<T> CreateAsyncCall<T>(int asyncHandle)
     {
-        AsyncCall asyncCall = new(asyncHandle);
+        AsyncCall<T> asyncCall = new();
+        asyncCalls[asyncHandle] = asyncCall;
+
+        return asyncCall;
+    }
+    
+    public static AsyncCall<TResult> CreateAsyncCall<TResult, TConversionInput>(int asyncHandle, Func<TConversionInput, TResult> conversion)
+    {
+        ConvertableAsyncCall<TResult, TConversionInput> asyncCall = new(conversion);
         asyncCalls[asyncHandle] = asyncCall;
 
         return asyncCall;

+ 2 - 1
src/PixiEditor.Extensions.WasmRuntime/Api/WindowingApi.cs

@@ -1,4 +1,5 @@
 using PixiEditor.Extensions.Common.Localization;
+using PixiEditor.Extensions.CommonApi.Async;
 using PixiEditor.Extensions.FlyUI.Elements;
 using PixiEditor.Extensions.WasmRuntime.Utilities;
 using PixiEditor.Extensions.Windowing;
@@ -42,7 +43,7 @@ internal class WindowingApi : ApiGroupHandler
     public int ShowWindowAsync(int handle)
     {
         var window = NativeObjectManager.GetObject<PopupWindow>(handle);
-        Task<int> showDialogTask = AsyncUtility.ToResultFrom(window.ShowDialog());
+        var showDialogTask = AsyncUtility.ToIntResultFrom(window.ShowDialog());
         return AsyncHandleManager.AddAsyncCall(showDialogTask);
     }
 

+ 6 - 4
src/PixiEditor.Extensions.WasmRuntime/Management/AsyncManager.cs

@@ -1,20 +1,22 @@
-namespace PixiEditor.Extensions.WasmRuntime.Management;
+using PixiEditor.Extensions.CommonApi.Async;
+
+namespace PixiEditor.Extensions.WasmRuntime.Management;
 
 internal delegate void AsyncCallCompleted(int asyncHandle, int resultValue); 
 internal delegate void AsyncCallFaulted(int asyncHandle, string exceptionMessage); 
 internal class AsyncCallsManager 
 {
-    private Dictionary<int, Task> asyncCalls = new();
+    private Dictionary<int, AsyncCall> asyncCalls = new();
  
     public event AsyncCallCompleted OnAsyncCallCompleted;
     public event AsyncCallFaulted OnAsyncCallFaulted;
     
-    public int AddAsyncCall(Task<int> task)
+    public int AddAsyncCall(AsyncCall<int> task)
     {
         int asyncHandle = GetNextAsyncHandle();
         task.ContinueWith(t =>
         {
-            if (t.IsFaulted)
+            if (t.Exception != null)
             {
                 OnAsyncCallFaulted?.Invoke(asyncHandle, t.Exception.Message);
             }

+ 4 - 2
src/PixiEditor.Extensions.WasmRuntime/Utilities/AsyncUtility.cs

@@ -1,8 +1,10 @@
-namespace PixiEditor.Extensions.WasmRuntime.Utilities;
+using PixiEditor.Extensions.CommonApi.Async;
+
+namespace PixiEditor.Extensions.WasmRuntime.Utilities;
 
 public static class AsyncUtility
 {
-    public static Task<int> ToResultFrom<T>(Task<T> task)
+    public static AsyncCall<int> ToIntResultFrom<T>(AsyncCall<T> task)
     {
         return task.ContinueWith(t =>
         {

+ 3 - 2
src/PixiEditor.Extensions/Windowing/PopupWindow.cs

@@ -1,4 +1,5 @@
-using PixiEditor.Extensions.CommonApi.Windowing;
+using PixiEditor.Extensions.CommonApi.Async;
+using PixiEditor.Extensions.CommonApi.Windowing;
 
 namespace PixiEditor.Extensions.Windowing;
 
@@ -20,7 +21,7 @@ public class PopupWindow : IPopupWindow
     public void Show() => _underlyingWindow.Show();
     public void Close() => _underlyingWindow.Close();
 
-    public Task<bool?> ShowDialog() => _underlyingWindow.ShowDialog();
+    public AsyncCall<bool?> ShowDialog() => _underlyingWindow.ShowDialog();
     public double Width
     {
         get => _underlyingWindow.Width;