Add Task object for async functions

This commit is contained in:
Alessandro Proto 2023-03-06 23:00:21 +01:00
parent 7fff12c9ac
commit f9aeb9f793
6 changed files with 436 additions and 63 deletions

View file

@ -62,13 +62,14 @@ local function drawVendorImage()
local w, h = gpu.getSize() local w, h = gpu.getSize()
local ok, err = pcall(function() local ok, err = pcall(function()
local buffer<close>, width, height = gpu.loadImage("/boot/vendor.bmp") local task<close> = gpu.loadImageAsync("/boot/vendor.bmp")
local buffer<close> = task:await()
local x, y = local x, y =
math.ceil((w / 2) - (width / 2)), math.ceil((w / 2) - (buffer.width / 2)),
math.ceil((h / 2) - (height / 2)) math.ceil((h / 2) - (buffer.height / 2))
gpu.drawBuffer(buffer, x, y, width, height) gpu.drawBuffer(buffer, x, y)
end) end)
if not ok then if not ok then

View file

@ -73,7 +73,7 @@ public class Event : IComponent
return nargs; return nargs;
} }
private static int L_Pull(IntPtr state) public static int L_Pull(IntPtr state)
{ {
var L = Lua.FromIntPtr(state); var L = Lua.FromIntPtr(state);

View file

@ -23,6 +23,7 @@ using Microsoft.Xna.Framework.Input;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Threading.Tasks;
namespace Capy64.Runtime.Libraries; namespace Capy64.Runtime.Libraries;
@ -123,8 +124,8 @@ public class GPU : IComponent
}, },
new() new()
{ {
name = "loadImage", name = "loadImageAsync",
function = L_LoadImage, function = L_LoadImageAsync,
}, },
new() new()
{ {
@ -412,7 +413,7 @@ public class GPU : IComponent
_game.Drawing.Canvas.GetData(buffer); _game.Drawing.Canvas.GetData(buffer);
ObjectManager.PushObject(L, buffer); ObjectManager.PushObject(L, buffer);
L.SetMetaTable(GPUBuffer.ObjectType); L.SetMetaTable(GPUBufferMeta.ObjectType);
return 1; return 1;
} }
@ -421,9 +422,9 @@ public class GPU : IComponent
{ {
var L = Lua.FromIntPtr(state); 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; return 0;
} }
@ -438,7 +439,7 @@ public class GPU : IComponent
var buffer = new uint[width * height]; var buffer = new uint[width * height];
ObjectManager.PushObject(L, buffer); ObjectManager.PushObject(L, buffer);
L.SetMetaTable(GPUBuffer.ObjectType); L.SetMetaTable(GPUBufferMeta.ObjectType);
return 1; return 1;
} }
@ -447,30 +448,23 @@ public class GPU : IComponent
{ {
var L = Lua.FromIntPtr(state); 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 x = (int)L.CheckInteger(2) - 1;
var y = (int)L.CheckInteger(3) - 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) _game.Drawing.DrawBuffer(buffer.Buffer, new()
{
L.Error("width and height do not match buffer size");
}
_game.Drawing.DrawBuffer(buffer, new()
{ {
X = x, X = x,
Y = y, Y = y,
Width = w, Width = buffer.Width,
Height = h, Height = buffer.Height,
}); });
return 0; return 0;
} }
private static int L_LoadImage(IntPtr state) private static int L_LoadImageAsync(IntPtr state)
{ {
var L = Lua.FromIntPtr(state); var L = Lua.FromIntPtr(state);
@ -484,6 +478,8 @@ public class GPU : IComponent
return 0; return 0;
} }
var task = TaskMeta.Push(L, GPUBufferMeta.ObjectType);
Texture2D texture; Texture2D texture;
try try
{ {
@ -491,44 +487,52 @@ public class GPU : IComponent
} }
catch (Exception e) catch (Exception e)
{ {
L.Error(e.Message); task.Reject(e.Message);
return 0; return 1;
} }
var data = new uint[texture.Width * texture.Height]; var data = new uint[texture.Width * texture.Height];
texture.GetData(data); 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 // ABGR to RGB
value = value =
((value & 0x00_00_00_FFU) << 16) | // move R ((value & 0x00_00_00_FFU) << 16) | // move R
(value & 0x00_00_FF_00U) | // move G (value & 0x00_00_FF_00U) | // move G
((value & 0x00_FF_00_00U) >> 16); // move B ((value & 0x00_FF_00_00U) >> 16); // move B
value = ColorPalette.GetColor(value); value = ColorPalette.GetColor(value);
// RGB to ABGR // RGB to ABGR
value = value =
((value & 0x00_FF_00_00U) >> 16) | // move R ((value & 0x00_FF_00_00U) >> 16) | // move R
(value & 0x00_00_FF_00U) | // move G (value & 0x00_00_FF_00U) | // move G
((value & 0x00_00_00_FFU) << 16) | // move B ((value & 0x00_00_00_FFU) << 16) | // move B
0xFF_00_00_00U; 0xFF_00_00_00U;
data[i] = value;
}
} }
}
ObjectManager.PushObject(L, data); var buffer = new GPUBufferMeta.GPUBuffer
L.SetMetaTable(GPUBuffer.ObjectType); {
L.PushInteger(texture.Width); Buffer = data,
L.PushInteger(texture.Height); Height = texture.Height,
Width = texture.Width,
};
task.Fulfill(buffer);
texture.Dispose(); texture.Dispose();
});
return 3; return 1;
} }
private static int L_Clear(IntPtr state) private static int L_Clear(IntPtr state)

View file

@ -72,6 +72,11 @@ public class Machine : IComponent
name = "getClipboard", name = "getClipboard",
function = L_GetClipboard, function = L_GetClipboard,
}, },
new()
{
name = "task",
function = L_Task,
},
new(), new(),
}; };
@ -87,6 +92,18 @@ public class Machine : IComponent
return 1; 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 _) private static int L_Shutdown(IntPtr _)
{ {
RuntimeManager.Shutdown(); RuntimeManager.Shutdown();

View file

@ -20,10 +20,17 @@ using System;
namespace Capy64.Runtime.Objects; namespace Capy64.Runtime.Objects;
public class GPUBuffer : IComponent public class GPUBufferMeta : IComponent
{ {
public const string ObjectType = "GPUBuffer"; 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[] private static LuaRegister[] MetaMethods = new LuaRegister[]
{ {
new() new()
@ -61,7 +68,8 @@ public class GPUBuffer : IComponent
}; };
private static IGame _game; private static IGame _game;
public GPUBuffer(IGame game) { public GPUBufferMeta(IGame game)
{
_game = game; _game = game;
} }
@ -72,7 +80,7 @@ public class GPUBuffer : IComponent
public static uint GetColor(uint color) public static uint GetColor(uint color)
{ {
if(_game.EngineMode == EngineMode.Classic) if (_game.EngineMode == EngineMode.Classic)
return ColorPalette.GetColor(color); return ColorPalette.GetColor(color);
return color; return color;
@ -84,18 +92,18 @@ public class GPUBuffer : IComponent
L.SetFuncs(MetaMethods, 0); 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<uint[]>(L, 1, gc); return ObjectManager.ToObject<GPUBuffer>(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<uint[]>(L, 1, ObjectType, gc); var obj = ObjectManager.CheckObject<GPUBuffer>(L, 1, ObjectType, gc);
if (obj is null) if (obj.Buffer is null)
{ {
L.Error("attempt to use a closed buffer"); L.Error("attempt to use a closed buffer");
return null; return default;
} }
return obj; return obj;
} }
@ -108,19 +116,27 @@ public class GPUBuffer : IComponent
if (!L.IsInteger(2)) 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; return 1;
} }
var key = L.ToInteger(2); var key = L.ToInteger(2);
if (key < 0 || key >= buffer.Length) if (key < 0 || key >= buffer.Buffer.Length)
{ {
L.PushNil(); L.PushNil();
return 1; return 1;
} }
var value = buffer[key]; var value = buffer.Buffer[key];
// ABGR to RGB // ABGR to RGB
value = value =
@ -145,7 +161,7 @@ public class GPUBuffer : IComponent
var key = L.ToInteger(2); var key = L.ToInteger(2);
if (key < 0 || key >= buffer.Length) if (key < 0 || key >= buffer.Buffer.Length)
{ {
return 0; return 0;
} }
@ -166,7 +182,7 @@ public class GPUBuffer : IComponent
0xFF_00_00_00U; 0xFF_00_00_00U;
buffer[key] = value; buffer.Buffer[key] = value;
return 0; return 0;
} }
@ -186,7 +202,7 @@ public class GPUBuffer : IComponent
var buffer = CheckBuffer(L, false); var buffer = CheckBuffer(L, false);
L.PushInteger(buffer.LongLength); L.PushInteger(buffer.Buffer.LongLength);
return 1; return 1;
} }
@ -195,7 +211,7 @@ public class GPUBuffer : IComponent
{ {
var L = Lua.FromIntPtr(state); var L = Lua.FromIntPtr(state);
var buffer = ToBuffer(L); var buffer = ToBuffer(L);
if (buffer is not null) if (buffer.Buffer is not null)
{ {
L.PushString("GPUBuffer ({0:X})", (ulong)&buffer); L.PushString("GPUBuffer ({0:X})", (ulong)&buffer);
} }

View file

@ -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>(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<Lua> 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<RuntimeTask>(L, 1, gc);
}
private static RuntimeTask CheckTask(Lua L, bool gc = false)
{
var obj = ObjectManager.CheckObject<RuntimeTask>(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<Lua> 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;
}
}