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 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 =
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

View file

@ -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);

View file

@ -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,13 +487,15 @@ 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);
Task.Run(() =>
{
if (_game.EngineMode == EngineMode.Classic)
{
for (int i = 0; i < data.Length; i++)
@ -518,17 +516,23 @@ public class GPU : IComponent
(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();
});
return 3;
return 1;
}
private static int L_Clear(IntPtr state)

View file

@ -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();

View file

@ -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;
}
@ -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<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);
if (obj is null)
var obj = ObjectManager.CheckObject<GPUBuffer>(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))
{
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);
}

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;
}
}