From f9aeb9f79329a863e72f2c7253875ba9724d4e27 Mon Sep 17 00:00:00 2001 From: Alessandro Proto Date: Mon, 6 Mar 2023 23:00:21 +0100 Subject: [PATCH] Add Task object for async functions --- Capy64/Assets/bios.lua | 9 +- Capy64/Runtime/Libraries/Event.cs | 2 +- Capy64/Runtime/Libraries/GPU.cs | 88 ++--- Capy64/Runtime/Libraries/Machine.cs | 17 + .../{GPUBuffer.cs => GPUBufferMeta.cs} | 48 ++- Capy64/Runtime/Objects/Task.cs | 335 ++++++++++++++++++ 6 files changed, 436 insertions(+), 63 deletions(-) rename Capy64/Runtime/Objects/{GPUBuffer.cs => GPUBufferMeta.cs} (77%) create mode 100644 Capy64/Runtime/Objects/Task.cs diff --git a/Capy64/Assets/bios.lua b/Capy64/Assets/bios.lua index 18f1349..0410836 100644 --- a/Capy64/Assets/bios.lua +++ b/Capy64/Assets/bios.lua @@ -62,13 +62,14 @@ local function drawVendorImage() local w, h = gpu.getSize() local ok, err = pcall(function() - local buffer, width, height = gpu.loadImage("/boot/vendor.bmp") + local task = gpu.loadImageAsync("/boot/vendor.bmp") + local buffer = task:await() local x, y = - math.ceil((w / 2) - (width / 2)), - math.ceil((h / 2) - (height / 2)) + math.ceil((w / 2) - (buffer.width / 2)), + math.ceil((h / 2) - (buffer.height / 2)) - gpu.drawBuffer(buffer, x, y, width, height) + gpu.drawBuffer(buffer, x, y) end) if not ok then diff --git a/Capy64/Runtime/Libraries/Event.cs b/Capy64/Runtime/Libraries/Event.cs index 02f3514..5cdc888 100644 --- a/Capy64/Runtime/Libraries/Event.cs +++ b/Capy64/Runtime/Libraries/Event.cs @@ -73,7 +73,7 @@ public class Event : IComponent return nargs; } - private static int L_Pull(IntPtr state) + public static int L_Pull(IntPtr state) { var L = Lua.FromIntPtr(state); diff --git a/Capy64/Runtime/Libraries/GPU.cs b/Capy64/Runtime/Libraries/GPU.cs index 3068555..0d6117e 100644 --- a/Capy64/Runtime/Libraries/GPU.cs +++ b/Capy64/Runtime/Libraries/GPU.cs @@ -23,6 +23,7 @@ using Microsoft.Xna.Framework.Input; using System; using System.Collections.Generic; using System.IO; +using System.Threading.Tasks; namespace Capy64.Runtime.Libraries; @@ -123,8 +124,8 @@ public class GPU : IComponent }, new() { - name = "loadImage", - function = L_LoadImage, + name = "loadImageAsync", + function = L_LoadImageAsync, }, new() { @@ -412,7 +413,7 @@ public class GPU : IComponent _game.Drawing.Canvas.GetData(buffer); ObjectManager.PushObject(L, buffer); - L.SetMetaTable(GPUBuffer.ObjectType); + L.SetMetaTable(GPUBufferMeta.ObjectType); return 1; } @@ -421,9 +422,9 @@ public class GPU : IComponent { var L = Lua.FromIntPtr(state); - var buffer = GPUBuffer.CheckBuffer(L, false); + var buffer = GPUBufferMeta.CheckBuffer(L, false); - _game.Drawing.Canvas.SetData(buffer); + _game.Drawing.Canvas.SetData(buffer.Buffer); return 0; } @@ -438,7 +439,7 @@ public class GPU : IComponent var buffer = new uint[width * height]; ObjectManager.PushObject(L, buffer); - L.SetMetaTable(GPUBuffer.ObjectType); + L.SetMetaTable(GPUBufferMeta.ObjectType); return 1; } @@ -447,30 +448,23 @@ public class GPU : IComponent { var L = Lua.FromIntPtr(state); - var buffer = GPUBuffer.CheckBuffer(L, false); + var buffer = GPUBufferMeta.CheckBuffer(L, false); var x = (int)L.CheckInteger(2) - 1; var y = (int)L.CheckInteger(3) - 1; - var w = (int)L.CheckInteger(4); - var h = (int)L.CheckInteger(5); - if (w * h != buffer.Length) - { - L.Error("width and height do not match buffer size"); - } - - _game.Drawing.DrawBuffer(buffer, new() + _game.Drawing.DrawBuffer(buffer.Buffer, new() { X = x, Y = y, - Width = w, - Height = h, + Width = buffer.Width, + Height = buffer.Height, }); return 0; } - private static int L_LoadImage(IntPtr state) + private static int L_LoadImageAsync(IntPtr state) { var L = Lua.FromIntPtr(state); @@ -484,6 +478,8 @@ public class GPU : IComponent return 0; } + var task = TaskMeta.Push(L, GPUBufferMeta.ObjectType); + Texture2D texture; try { @@ -491,44 +487,52 @@ public class GPU : IComponent } catch (Exception e) { - L.Error(e.Message); - return 0; + task.Reject(e.Message); + return 1; } var data = new uint[texture.Width * texture.Height]; texture.GetData(data); - if (_game.EngineMode == EngineMode.Classic) + Task.Run(() => { - for (int i = 0; i < data.Length; i++) + if (_game.EngineMode == EngineMode.Classic) { - var value = data[i]; + for (int i = 0; i < data.Length; i++) + { + var value = data[i]; - // ABGR to RGB - value = - ((value & 0x00_00_00_FFU) << 16) | // move R - (value & 0x00_00_FF_00U) | // move G - ((value & 0x00_FF_00_00U) >> 16); // move B + // ABGR to RGB + value = + ((value & 0x00_00_00_FFU) << 16) | // move R + (value & 0x00_00_FF_00U) | // move G + ((value & 0x00_FF_00_00U) >> 16); // move B - value = ColorPalette.GetColor(value); + value = ColorPalette.GetColor(value); - // RGB to ABGR - value = - ((value & 0x00_FF_00_00U) >> 16) | // move R - (value & 0x00_00_FF_00U) | // move G - ((value & 0x00_00_00_FFU) << 16) | // move B - 0xFF_00_00_00U; + // RGB to ABGR + value = + ((value & 0x00_FF_00_00U) >> 16) | // move R + (value & 0x00_00_FF_00U) | // move G + ((value & 0x00_00_00_FFU) << 16) | // move B + 0xFF_00_00_00U; + + data[i] = value; + } } - } - ObjectManager.PushObject(L, data); - L.SetMetaTable(GPUBuffer.ObjectType); - L.PushInteger(texture.Width); - L.PushInteger(texture.Height); + var buffer = new GPUBufferMeta.GPUBuffer + { + Buffer = data, + Height = texture.Height, + Width = texture.Width, + }; + task.Fulfill(buffer); - texture.Dispose(); + texture.Dispose(); + }); - return 3; + return 1; } private static int L_Clear(IntPtr state) diff --git a/Capy64/Runtime/Libraries/Machine.cs b/Capy64/Runtime/Libraries/Machine.cs index 6f8341e..210368c 100644 --- a/Capy64/Runtime/Libraries/Machine.cs +++ b/Capy64/Runtime/Libraries/Machine.cs @@ -72,6 +72,11 @@ public class Machine : IComponent name = "getClipboard", function = L_GetClipboard, }, + new() + { + name = "task", + function = L_Task, + }, new(), }; @@ -87,6 +92,18 @@ public class Machine : IComponent return 1; } + private static int L_Task(IntPtr state) + { + var L = Lua.FromIntPtr(state); + + var task = new Objects.TaskMeta.RuntimeTask("test"); + + ObjectManager.PushObject(L, task); + L.SetMetaTable(Objects.TaskMeta.ObjectType); + + return 1; + } + private static int L_Shutdown(IntPtr _) { RuntimeManager.Shutdown(); diff --git a/Capy64/Runtime/Objects/GPUBuffer.cs b/Capy64/Runtime/Objects/GPUBufferMeta.cs similarity index 77% rename from Capy64/Runtime/Objects/GPUBuffer.cs rename to Capy64/Runtime/Objects/GPUBufferMeta.cs index 111f50c..f4b0dd2 100644 --- a/Capy64/Runtime/Objects/GPUBuffer.cs +++ b/Capy64/Runtime/Objects/GPUBufferMeta.cs @@ -20,10 +20,17 @@ using System; namespace Capy64.Runtime.Objects; -public class GPUBuffer : IComponent +public class GPUBufferMeta : IComponent { public const string ObjectType = "GPUBuffer"; + public struct GPUBuffer + { + public uint[] Buffer { get; set; } + public int Width { get; set; } + public int Height { get; set; } + } + private static LuaRegister[] MetaMethods = new LuaRegister[] { new() @@ -61,7 +68,8 @@ public class GPUBuffer : IComponent }; private static IGame _game; - public GPUBuffer(IGame game) { + public GPUBufferMeta(IGame game) + { _game = game; } @@ -72,7 +80,7 @@ public class GPUBuffer : IComponent public static uint GetColor(uint color) { - if(_game.EngineMode == EngineMode.Classic) + if (_game.EngineMode == EngineMode.Classic) return ColorPalette.GetColor(color); return color; @@ -84,18 +92,18 @@ public class GPUBuffer : IComponent L.SetFuncs(MetaMethods, 0); } - public static uint[] ToBuffer(Lua L, bool gc = false) + public static GPUBuffer ToBuffer(Lua L, bool gc = false) { - return ObjectManager.ToObject(L, 1, gc); + return ObjectManager.ToObject(L, 1, gc); } - public static uint[] CheckBuffer(Lua L, bool gc = false) + public static GPUBuffer CheckBuffer(Lua L, bool gc = false) { - var obj = ObjectManager.CheckObject(L, 1, ObjectType, gc); - if (obj is null) + var obj = ObjectManager.CheckObject(L, 1, ObjectType, gc); + if (obj.Buffer is null) { L.Error("attempt to use a closed buffer"); - return null; + return default; } return obj; } @@ -108,19 +116,27 @@ public class GPUBuffer : IComponent if (!L.IsInteger(2)) { - L.PushNil(); + var vkey = L.ToString(2); + + if (vkey == "width") + L.PushInteger(buffer.Width); + else if (vkey == "height") + L.PushInteger(buffer.Height); + else + L.PushNil(); + return 1; } var key = L.ToInteger(2); - if (key < 0 || key >= buffer.Length) + if (key < 0 || key >= buffer.Buffer.Length) { L.PushNil(); return 1; } - var value = buffer[key]; + var value = buffer.Buffer[key]; // ABGR to RGB value = @@ -145,7 +161,7 @@ public class GPUBuffer : IComponent var key = L.ToInteger(2); - if (key < 0 || key >= buffer.Length) + if (key < 0 || key >= buffer.Buffer.Length) { return 0; } @@ -166,7 +182,7 @@ public class GPUBuffer : IComponent 0xFF_00_00_00U; - buffer[key] = value; + buffer.Buffer[key] = value; return 0; } @@ -186,7 +202,7 @@ public class GPUBuffer : IComponent var buffer = CheckBuffer(L, false); - L.PushInteger(buffer.LongLength); + L.PushInteger(buffer.Buffer.LongLength); return 1; } @@ -195,7 +211,7 @@ public class GPUBuffer : IComponent { var L = Lua.FromIntPtr(state); var buffer = ToBuffer(L); - if (buffer is not null) + if (buffer.Buffer is not null) { L.PushString("GPUBuffer ({0:X})", (ulong)&buffer); } diff --git a/Capy64/Runtime/Objects/Task.cs b/Capy64/Runtime/Objects/Task.cs new file mode 100644 index 0000000..ad87885 --- /dev/null +++ b/Capy64/Runtime/Objects/Task.cs @@ -0,0 +1,335 @@ +// This file is part of Capy64 - https://github.com/Ale32bit/Capy64 +// Copyright 2023 Alessandro "AlexDevs" Proto +// +// Licensed under the Apache License, Version 2.0 (the "License"). +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Capy64.API; +using Cyotek.Drawing.BitmapFont; +using KeraLua; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Capy64.Runtime.Objects; + +public class TaskMeta : IComponent +{ + private static IGame _game; + public TaskMeta(IGame game) + { + _game = game; + } + + public const string ObjectType = "Task"; + + public enum TaskStatus + { + Running, + Succeeded, + Failed, + } + public class RuntimeTask + { + public RuntimeTask(string typeName) + { + TypeName = typeName; + } + public Guid Guid { get; set; } = Guid.NewGuid(); + public string TypeName { get; set; } + public TaskStatus Status { get; set; } = TaskStatus.Running; + public object Result { get; private set; } + public string Error { get; private set; } + + public void Fulfill(T obj) + { + Status = TaskStatus.Succeeded; + + Result = obj; + + _game.LuaRuntime.QueueEvent("task_finish", LK => + { + LK.PushString(Guid.ToString()); + + ObjectManager.PushObject(LK, obj); + LK.SetMetaTable(TypeName); + + LK.PushNil(); + + return 3; + }); + } + + public void Fulfill(Action lk) + { + Status = TaskStatus.Succeeded; + + Result = lk; + + _game.LuaRuntime.QueueEvent("task_finish", LK => + { + LK.PushString(Guid.ToString()); + + lk(LK); + + LK.PushNil(); + + return 3; + }); + } + + public void Reject(string error, params object[] args) + { + Status = TaskStatus.Failed; + Error = string.Format(error, args); + + _game.LuaRuntime.QueueEvent("task_finish", LK => + { + LK.PushString(Guid.ToString()); + + LK.PushNil(); + + LK.PushString(Error); + + return 3; + }); + } + } + + private static LuaRegister[] Methods = new LuaRegister[] + { + new() + { + name = "await", + function = L_Await, + }, + new() + { + name = "getID", + function = L_GetID, + }, + new() + { + name = "getType", + function = L_GetType, + }, + new() + { + name = "getStatus", + function = L_GetStatus, + }, + new() + { + name = "getResult", + function = L_GetResult, + }, + new() + { + name = "getError", + function = L_GetResult, + }, + new(), + }; + + private static LuaRegister[] MetaMethods = new LuaRegister[] + { + new() + { + name = "__index", + }, + new() + { + name = "__gc", + function = LM_GC, + }, + new() + { + name = "__close", + function = LM_GC, + }, + new() + { + name = "__tostring", + function = LM_ToString, + }, + + new(), + }; + + public void LuaInit(Lua L) + { + CreateMeta(L); + } + + public static void CreateMeta(Lua L) + { + L.NewMetaTable(ObjectType); + L.SetFuncs(MetaMethods, 0); + L.NewLibTable(Methods); + L.SetFuncs(Methods, 0); + L.SetField(-2, "__index"); + L.Pop(1); + } + + public static RuntimeTask Push(Lua L, string typeName) + { + var task = new RuntimeTask(typeName); + + ObjectManager.PushObject(L, task); + L.SetMetaTable(ObjectType); + + return task; + } + + private static RuntimeTask ToTask(Lua L, bool gc = false) + { + return ObjectManager.ToObject(L, 1, gc); + } + + private static RuntimeTask CheckTask(Lua L, bool gc = false) + { + var obj = ObjectManager.CheckObject(L, 1, ObjectType, gc); + if (obj is null) + { + L.Error("attempt to use a closed file"); + return null; + } + return obj; + } + + private static void WaitForTask(Lua L) + { + L.PushCFunction(Libraries.Event.L_Pull); + L.PushString("task_finish"); + L.CallK(1, 4, 0, LK_Await); + } + + private static int L_Await(IntPtr state) + { + var L = Lua.FromIntPtr(state); + + WaitForTask(L); + + return 0; + } + + private static int LK_Await(IntPtr state, int status, nint ctx) + { + var L = Lua.FromIntPtr(state); + var task = CheckTask(L, false); + var taskId = L.CheckString(3); + + if (task.Guid.ToString() != taskId) + { + WaitForTask(L); + return 0; + } + + return 2; + } + + private static int L_GetID(IntPtr state) + { + var L = Lua.FromIntPtr(state); + + var task = CheckTask(L, false); + + L.PushString(task.Guid.ToString()); + + return 1; + } + + private static int L_GetType(IntPtr state) + { + var L = Lua.FromIntPtr(state); + + var task = CheckTask(L, false); + + L.PushString(task.TypeName); + + return 1; + } + + private static int L_GetStatus(IntPtr state) + { + var L = Lua.FromIntPtr(state); + + var task = CheckTask(L, false); + + L.PushString(task.Status.ToString().ToLower()); + + return 1; + } + + private static int L_GetResult(IntPtr state) + { + var L = Lua.FromIntPtr(state); + + var task = CheckTask(L, false); + + if (task.Status == TaskStatus.Succeeded) + { + if (task.Result is Action lk) + { + // todo: make use of first-generated data + lk(L); + } + else + { + ObjectManager.PushObject(L, task.Result); + L.SetMetaTable(task.TypeName); + } + } + else + { + L.PushNil(); + } + + return 1; + } + + private static int L_GetError(IntPtr state) + { + var L = Lua.FromIntPtr(state); + + var task = CheckTask(L, false); + + if (task.Status == TaskStatus.Failed) + { + L.PushString(task.Error); + } + else + { + L.PushNil(); + } + + return 1; + } + + private static int LM_GC(IntPtr state) + { + return 0; + } + + private static int LM_ToString(IntPtr state) + { + var L = Lua.FromIntPtr(state); + var task = ToTask(L); + L.PushString("Task<{0}>: {1} ({2})", task?.TypeName, task?.Guid, task?.Status); + + return 1; + } +}