Browse Source

Implemented async calls

flabbet 1 year ago
parent
commit
f5d6af4bb8

+ 10 - 2
src/PixiEditor.Extensions.Wasm/Api/Window/PopupWindow.cs

@@ -1,4 +1,5 @@
 using PixiEditor.Extensions.CommonApi.Windowing;
 using PixiEditor.Extensions.CommonApi.Windowing;
+using PixiEditor.Extensions.Wasm.Async;
 
 
 namespace PixiEditor.Extensions.Wasm.Api.Window;
 namespace PixiEditor.Extensions.Wasm.Api.Window;
 
 
@@ -27,13 +28,20 @@ public class PopupWindow : IPopupWindow
         Interop.close_window(windowHandle);
         Interop.close_window(windowHandle);
     }
     }
 
 
-    public Task<bool?> ShowDialog()
+    public AsyncCall ShowDialog()
     {
     {
-        throw new NotImplementedException();
+        int asyncHandle = Interop.show_window_async(windowHandle);
+        AsyncCall showDialogTask = Interop.AsyncHandleToTask<int>(asyncHandle);
+        return showDialogTask;
     }
     }
 
 
     public double Width { get; set; }
     public double Width { get; set; }
     public double Height { get; set; }
     public double Height { get; set; }
     public bool CanResize { get; set; }
     public bool CanResize { get; set; }
     public bool CanMinimize { get; set; }
     public bool CanMinimize { get; set; }
+    
+    Task<bool?> IPopupWindow.ShowDialog()
+    {
+        throw new PlatformNotSupportedException("Task-based ShowDialog is not supproted in Wasm. Use PopupWindows.ShowDialog() with AsyncCall return type instead.");
+    }
 }
 }

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

@@ -0,0 +1,43 @@
+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
+} 

+ 41 - 0
src/PixiEditor.Extensions.Wasm/Interop.Async.cs

@@ -0,0 +1,41 @@
+using PixiEditor.Extensions.Wasm.Async;
+
+namespace PixiEditor.Extensions.Wasm;
+
+internal partial class Interop
+{
+    private static Dictionary<int, AsyncCall> asyncCalls = new();
+    
+    [ApiExport("async_call_completed")]
+    internal static void async_call_completed(int asyncHandle, int resultValue)
+    {
+        if (!asyncCalls.ContainsKey(asyncHandle))
+        {
+            throw new InvalidOperationException($"Async call with handle {asyncHandle} does not exist.");
+        }
+        
+        asyncCalls[asyncHandle].SetResult(resultValue);
+        asyncCalls.Remove(asyncHandle);
+    }
+    
+    [ApiExport("async_call_faulted")]
+    internal static void async_call_faulted(int asyncHandle, string exceptionMessage)
+    {
+        if (!asyncCalls.ContainsKey(asyncHandle))
+        {
+            throw new InvalidOperationException($"Async call with handle {asyncHandle} does not exist.");
+        }
+        
+        asyncCalls[asyncHandle].SetException(new InvalidOperationException(exceptionMessage));
+        asyncCalls.Remove(asyncHandle);
+    }
+    
+    // TODO: More types
+    public static AsyncCall AsyncHandleToTask<T>(int asyncHandle)
+    {
+        AsyncCall asyncCall = new(asyncHandle);
+        asyncCalls[asyncHandle] = asyncCall;
+
+        return asyncCall;
+    }
+}

+ 3 - 0
src/PixiEditor.Extensions.Wasm/Interop.Windowing.cs

@@ -19,4 +19,7 @@ internal static partial class Interop
 
 
     [MethodImpl(MethodImplOptions.InternalCall)]
     [MethodImpl(MethodImplOptions.InternalCall)]
     internal static extern void close_window(int windowHandle);
     internal static extern void close_window(int windowHandle);
+
+    [MethodImpl(MethodImplOptions.InternalCall)]
+    internal static extern int show_window_async(int windowHandle);
 }
 }

+ 1 - 6
src/PixiEditor.Extensions.Wasm/Interop.cs

@@ -9,12 +9,7 @@ using PixiEditor.Extensions.Wasm.Api.FlyUI;
 
 
 namespace PixiEditor.Extensions.Wasm;
 namespace PixiEditor.Extensions.Wasm;
 
 
-internal static class Program
-{
-    internal static void Main() {
-
-    }
-}
+internal static class Program { internal static void Main() { } } // Required for compilation
 
 
 [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:Element should begin with upper-case letter", 
 [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:Element should begin with upper-case letter", 
     Justification = "Interop is a special case, it's injected to C code and follows C naming conventions.")]
     Justification = "Interop is a special case, it's injected to C code and follows C naming conventions.")]

BIN
src/PixiEditor.Extensions.Wasm/build/PixiEditor.Api.CGlueMSBuild.dll


+ 1 - 0
src/PixiEditor.Extensions.WasmRuntime/Api/ApiGroupHandler.cs

@@ -11,6 +11,7 @@ internal class ApiGroupHandler
     public ExtensionServices Api { get; }
     public ExtensionServices Api { get; }
     protected LayoutBuilder LayoutBuilder { get; }
     protected LayoutBuilder LayoutBuilder { get; }
     protected ObjectManager NativeObjectManager { get; }
     protected ObjectManager NativeObjectManager { get; }
+    protected AsyncCallsManager AsyncHandleManager { get; }
     protected Instance? Instance { get; }
     protected Instance? Instance { get; }
     protected WasmMemoryUtility WasmMemoryUtility { get; }
     protected WasmMemoryUtility WasmMemoryUtility { get; }
 }
 }

+ 10 - 0
src/PixiEditor.Extensions.WasmRuntime/Api/WindowingApi.cs

@@ -1,4 +1,5 @@
 using PixiEditor.Extensions.FlyUI.Elements;
 using PixiEditor.Extensions.FlyUI.Elements;
+using PixiEditor.Extensions.WasmRuntime.Utilities;
 using PixiEditor.Extensions.Windowing;
 using PixiEditor.Extensions.Windowing;
 
 
 namespace PixiEditor.Extensions.WasmRuntime.Api;
 namespace PixiEditor.Extensions.WasmRuntime.Api;
@@ -35,6 +36,15 @@ internal class WindowingApi : ApiGroupHandler
         var window = NativeObjectManager.GetObject<PopupWindow>(handle);
         var window = NativeObjectManager.GetObject<PopupWindow>(handle);
         window.Show();
         window.Show();
     }
     }
+    
+    [ApiFunction("show_window_async")]
+    public int ShowWindowAsync(int handle)
+    {
+        var window = NativeObjectManager.GetObject<PopupWindow>(handle);
+        Task<int> showDialogTask = AsyncUtility.ToResultFrom(window.ShowDialog());
+        return AsyncHandleManager.AddAsyncCall(showDialogTask);
+    }
+
 
 
     [ApiFunction("close_window")]
     [ApiFunction("close_window")]
     public void CloseWindow(int handle)
     public void CloseWindow(int handle)

+ 49 - 0
src/PixiEditor.Extensions.WasmRuntime/Management/AsyncManager.cs

@@ -0,0 +1,49 @@
+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();
+ 
+    public event AsyncCallCompleted OnAsyncCallCompleted;
+    public event AsyncCallFaulted OnAsyncCallFaulted;
+    
+    public int AddAsyncCall(Task<int> task)
+    {
+        int asyncHandle = GetNextAsyncHandle();
+        task.ContinueWith(t =>
+        {
+            if (t.IsFaulted)
+            {
+                OnAsyncCallFaulted?.Invoke(asyncHandle, t.Exception.Message);
+            }
+            else
+            {
+                OnAsyncCallCompleted?.Invoke(asyncHandle, t.Result);
+            }
+
+            RemoveAsyncCall(asyncHandle);
+        });
+        asyncCalls[asyncHandle] = task;
+        
+        return asyncHandle;
+    }
+    
+    public void RemoveAsyncCall(int asyncHandle)
+    {
+        asyncCalls.Remove(asyncHandle);
+    }
+    
+    private int GetNextAsyncHandle()
+    {
+        int asyncHandle = 0;
+        
+        while (asyncCalls.ContainsKey(asyncHandle))
+        {
+            asyncHandle++;
+        }
+        
+        return asyncHandle;
+    }
+}

+ 17 - 0
src/PixiEditor.Extensions.WasmRuntime/Utilities/AsyncUtility.cs

@@ -0,0 +1,17 @@
+namespace PixiEditor.Extensions.WasmRuntime.Utilities;
+
+public static class AsyncUtility
+{
+    public static Task<int> ToResultFrom<T>(Task<T> task)
+    {
+        return task.ContinueWith(t =>
+        {
+            if (t.Result is null)
+            {
+                return -1;
+            }
+
+            return Convert.ToInt32(t.Result);
+        });
+    }
+}

+ 15 - 0
src/PixiEditor.Extensions.WasmRuntime/WasmExtensionInstance.cs

@@ -21,6 +21,7 @@ public partial class WasmExtensionInstance : Extension
     private Memory memory = null!;
     private Memory memory = null!;
     private LayoutBuilder LayoutBuilder { get; set; }
     private LayoutBuilder LayoutBuilder { get; set; }
     private ObjectManager NativeObjectManager { get; set; }
     private ObjectManager NativeObjectManager { get; set; }
+    private AsyncCallsManager AsyncHandleManager { get; set; }
     private WasmMemoryUtility WasmMemoryUtility { get; set; }
     private WasmMemoryUtility WasmMemoryUtility { get; set; }
 
 
     partial void LinkApiFunctions();
     partial void LinkApiFunctions();
@@ -35,6 +36,10 @@ public partial class WasmExtensionInstance : Extension
     public void Instantiate()
     public void Instantiate()
     {
     {
         NativeObjectManager = new ObjectManager();
         NativeObjectManager = new ObjectManager();
+        AsyncHandleManager = new AsyncCallsManager();
+        AsyncHandleManager.OnAsyncCallCompleted += OnAsyncCallCompleted;
+        AsyncHandleManager.OnAsyncCallFaulted += AsyncHandleManagerOnOnAsyncCallFaulted;
+        
         LinkApiFunctions();
         LinkApiFunctions();
         Linker.DefineModule(Store, Module);
         Linker.DefineModule(Store, Module);
 
 
@@ -43,6 +48,16 @@ public partial class WasmExtensionInstance : Extension
         memory = Instance.GetMemory("memory");
         memory = Instance.GetMemory("memory");
     }
     }
 
 
+    private void OnAsyncCallCompleted(int handle, int result)
+    {
+        Instance.GetAction<int, int>("async_call_completed").Invoke(handle, result);
+    }
+    
+    private void AsyncHandleManagerOnOnAsyncCallFaulted(int handle, string exceptionMessage)
+    {
+        Instance.GetAction<int, string>("async_call_faulted").Invoke(handle, exceptionMessage);
+    }
+
     protected override void OnLoaded()
     protected override void OnLoaded()
     {
     {
         Instance.GetAction("load").Invoke();
         Instance.GetAction("load").Invoke();

+ 0 - 1
src/PixiEditor.Extensions.WasmRuntime/WasmMemoryUtility.cs

@@ -21,7 +21,6 @@ public class WasmMemoryUtility
 
 
     public string GetString(int offset, int length)
     public string GetString(int offset, int length)
     {
     {
-        //TODO: memory.ReadString is a thing
         var span = memory.GetSpan<byte>(offset, length);
         var span = memory.GetSpan<byte>(offset, length);
         return Encoding.UTF8.GetString(span);
         return Encoding.UTF8.GetString(span);
     }
     }

+ 0 - 20
src/PixiEditor.WasmApi.Gen/ApiGenerator.cs

@@ -150,26 +150,6 @@ public class ApiGenerator : IIncrementalGenerator
         return syntaxes;
         return syntaxes;
     }
     }
 
 
-    private static bool CouldBeApiImplAsync(SyntaxNode node, CancellationToken cancellation)
-    {
-        if (node is not AttributeSyntax attribute)
-            return false;
-
-        string? name = ExtractName(attribute.Name);
-
-        return name is "ApiFunction" or ApiFunctionAttributeName;
-    }
-
-    private static string? ExtractName(NameSyntax? attributeName)
-    {
-        return attributeName switch
-        {
-            SimpleNameSyntax ins => ins.Identifier.Text,
-            QualifiedNameSyntax qns => qns.Right.Identifier.Text,
-            _ => null
-        };
-    }
-
     private static (IMethodSymbol methodSymbol, SemanticModel SemanticModel)? GetApiFunctionMethodOrNull(GeneratorAttributeSyntaxContext context,
     private static (IMethodSymbol methodSymbol, SemanticModel SemanticModel)? GetApiFunctionMethodOrNull(GeneratorAttributeSyntaxContext context,
         CancellationToken cancelToken)
         CancellationToken cancelToken)
     {
     {

+ 5 - 1
src/WasmSampleExtension/SampleExtension.cs

@@ -21,7 +21,11 @@ public class SampleExtension : WasmExtension
                 )
                 )
             );
             );
 
 
-        Api.WindowProvider.CreatePopupWindow("WASM SampleExtension", layout).Show();
+        var showTask = Api.WindowProvider.CreatePopupWindow("WASM SampleExtension", layout).ShowDialog();
+        showTask.Completed += result =>
+        {
+            Api.Logger.Log($"Show task completed: {result}");
+        };
     }
     }
 }
 }