Completely rewritten the Lua runtime.

Somehow fixed annoying bug in coroutine
This commit is contained in:
Alessandro Proto 2023-01-25 23:06:37 +01:00
parent c270bbe8bd
commit d1d7bc1463
32 changed files with 516 additions and 592 deletions

View file

@ -1,185 +0,0 @@
using Capy64.API;
using Capy64.Core;
using Capy64.Eventing;
using Capy64.Eventing.Events;
using Capy64.LuaRuntime;
using Capy64.LuaRuntime.Libraries;
using KeraLua;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
namespace Capy64.BIOS;
public class Bios : IPlugin
{
private static IGame _game;
private readonly EventEmitter _eventEmitter;
private RuntimeInputEvents _runtimeInputEvents;
private readonly Drawing _drawing;
private static bool CloseRuntime = false;
private static bool OpenBios = false;
public Bios(IGame game)
{
_game = game;
_eventEmitter = game.EventEmitter;
_drawing = game.Drawing;
_game.EventEmitter.OnInit += OnInit;
_eventEmitter.OnTick += OnTick;
}
private void OnInit(object sender, EventArgs e)
{
RunBIOS();
}
private void OnTick(object sender, TickEvent e)
{
if (CloseRuntime)
{
_runtimeInputEvents.Unregister();
_game.LuaRuntime.Close();
CloseRuntime = false;
if (OpenBios)
{
OpenBios = false;
RunBIOS();
}
else
{
StartLuaOS();
}
}
Resume();
}
private void RunBIOS()
{
_game.LuaRuntime = new();
InitLuaPlugins();
_game.LuaRuntime.Thread.PushCFunction(L_OpenDataFolder);
_game.LuaRuntime.Thread.SetGlobal("openDataFolder");
_game.LuaRuntime.Thread.PushCFunction(L_InstallOS);
_game.LuaRuntime.Thread.SetGlobal("installOS");
_game.LuaRuntime.Thread.PushCFunction(L_Exit);
_game.LuaRuntime.Thread.SetGlobal("exit");
var status = _game.LuaRuntime.Thread.LoadFile("Assets/bios.lua");
if (status != LuaStatus.OK)
{
throw new LuaException(_game.LuaRuntime.Thread.ToString(-1));
}
_runtimeInputEvents = new RuntimeInputEvents(_eventEmitter, _game.LuaRuntime);
_runtimeInputEvents.Register();
}
private void StartLuaOS()
{
InstallOS();
try
{
_game.LuaRuntime = new Runtime();
InitLuaPlugins();
_game.LuaRuntime.Patch();
_game.LuaRuntime.Init();
_runtimeInputEvents = new(_eventEmitter, _game.LuaRuntime);
_runtimeInputEvents.Register();
}
catch (LuaException ex)
{
var panic = new PanicScreen(_game.Drawing);
_drawing.Begin();
panic.Render("Cannot load operating system!", ex.Message);
_drawing.End();
}
}
public void Resume()
{
try
{
var yielded = _game.LuaRuntime.Resume();
if (!yielded)
{
_game.Exit();
}
}
catch (LuaException e)
{
Console.WriteLine(e);
var panic = new PanicScreen(_game.Drawing);
panic.Render(e.Message);
_runtimeInputEvents.Unregister();
}
}
private static void InitLuaPlugins()
{
var allPlugins = new List<IPlugin>(_game.NativePlugins);
allPlugins.AddRange(_game.Plugins);
foreach (var plugin in allPlugins)
{
plugin.LuaInit(_game.LuaRuntime.Thread);
}
}
public static void InstallOS(bool force = false)
{
var installedFilePath = Path.Combine(Capy64.AppDataPath, ".installed");
if (!File.Exists(installedFilePath) || force)
{
FileSystem.CopyDirectory("Assets/Lua", FileSystem.DataPath, true, true);
File.Create(installedFilePath).Dispose();
}
}
public static void Shutdown()
{
_game.Exit();
}
public static void Reboot()
{
CloseRuntime = true;
OpenBios = true;
}
private static int L_OpenDataFolder(IntPtr state)
{
var path = FileSystem.DataPath;
switch (Environment.OSVersion.Platform)
{
case PlatformID.Win32NT:
Process.Start("explorer.exe", path);
break;
case PlatformID.Unix:
Process.Start("xdg-open", path);
break;
}
return 0;
}
private static int L_InstallOS(IntPtr state)
{
InstallOS(true);
return 0;
}
private static int L_Exit(IntPtr state)
{
CloseRuntime = true;
return 0;
}
}

View file

@ -1,56 +0,0 @@
using Capy64.Core;
using Capy64.LuaRuntime.Libraries;
using Microsoft.Xna.Framework;
namespace Capy64.BIOS;
public class PanicScreen
{
public Color ForegroundColor = Color.White;
public Color BackgroundColor = new Color(0, 51, 187);
private Drawing _drawing;
public PanicScreen(Drawing drawing)
{
_drawing = drawing;
}
public void Render(string error, string details = null)
{
Term.ForegroundColor = ForegroundColor;
Term.BackgroundColor = BackgroundColor;
Term.SetCursorBlink(false);
Term.SetSize(57, 23);
Term.Clear();
var title = " Capy64 ";
var halfX = (Term.Width / 2) + 1;
Term.SetCursorPosition(halfX - (title.Length / 2), 2);
Term.ForegroundColor = BackgroundColor;
Term.BackgroundColor = ForegroundColor;
Term.Write(title);
Term.ForegroundColor = ForegroundColor;
Term.BackgroundColor = BackgroundColor;
Term.SetCursorPosition(1, 4);
Print(error + '\n');
if (details is not null)
{
Print(details);
}
}
private void Print(string txt)
{
foreach (var ch in txt)
{
Term.Write(ch.ToString());
if (Term.CursorPosition.X >= Term.Width || ch == '\n')
{
Term.SetCursorPosition(1, (int)Term.CursorPosition.Y + 1);
}
}
Term.SetCursorPosition(1, (int)Term.CursorPosition.Y + 1);
}
}

View file

@ -2,7 +2,7 @@
using Capy64.Core; using Capy64.Core;
using Capy64.Eventing; using Capy64.Eventing;
using Capy64.Extensions; using Capy64.Extensions;
using Capy64.LuaRuntime; using Capy64.Runtime;
using Capy64.PluginManager; using Capy64.PluginManager;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
@ -23,6 +23,7 @@ public class Capy64 : Game, IGame
Environment.SpecialFolder.ApplicationData, Environment.SpecialFolder.ApplicationData,
Environment.SpecialFolderOption.Create), Environment.SpecialFolderOption.Create),
"Capy64"); "Capy64");
public static Capy64 Instance;
public Capy64 Game => this; public Capy64 Game => this;
public IList<IPlugin> NativePlugins { get; private set; } public IList<IPlugin> NativePlugins { get; private set; }
public IList<IPlugin> Plugins { get; private set; } public IList<IPlugin> Plugins { get; private set; }
@ -30,7 +31,7 @@ public class Capy64 : Game, IGame
public int Height { get; set; } = 300; public int Height { get; set; } = 300;
public float Scale { get; set; } = 2f; public float Scale { get; set; } = 2f;
public Drawing Drawing { get; private set; } public Drawing Drawing { get; private set; }
public Runtime LuaRuntime { get; set; } public LuaState LuaRuntime { get; set; }
public EventEmitter EventEmitter { get; private set; } public EventEmitter EventEmitter { get; private set; }
public Borders Borders = new() public Borders Borders = new()
{ {
@ -50,6 +51,8 @@ public class Capy64 : Game, IGame
public Capy64() public Capy64()
{ {
Instance = this;
_graphics = new GraphicsDeviceManager(this); _graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content"; Content.RootDirectory = "Content";
IsMouseVisible = true; IsMouseVisible = true;

View file

@ -1,7 +1,7 @@
using Capy64.API; using Capy64.API;
using Capy64.Core; using Capy64.Core;
using Capy64.Eventing; using Capy64.Eventing;
using Capy64.LuaRuntime; using Capy64.Runtime;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -15,7 +15,7 @@ public interface IGame
IList<IPlugin> Plugins { get; } IList<IPlugin> Plugins { get; }
GameWindow Window { get; } GameWindow Window { get; }
Drawing Drawing { get; } Drawing Drawing { get; }
Runtime LuaRuntime { get; set; } LuaState LuaRuntime { get; set; }
EventEmitter EventEmitter { get; } EventEmitter EventEmitter { get; }
void ConfigureServices(IServiceProvider serviceProvider); void ConfigureServices(IServiceProvider serviceProvider);

View file

@ -1,7 +0,0 @@
namespace Capy64.LuaRuntime;
public interface ILuaEvent
{
public string Name { get; set; }
public bool BypassFilter { get; set; }
}

View file

@ -1,11 +0,0 @@
using KeraLua;
using System;
namespace Capy64.LuaRuntime;
public class LuaDelegateEvent : ILuaEvent
{
public string Name { get; set; }
public Func<Lua, int> Handler { get; set; }
public bool BypassFilter { get; set; } = false;
}

View file

@ -1,8 +0,0 @@
namespace Capy64.LuaRuntime;
public class LuaEvent : ILuaEvent
{
public string Name { get; set; }
public object[] Parameters { get; set; }
public bool BypassFilter { get; set; } = false;
}

View file

@ -1,201 +0,0 @@
using Capy64.LuaRuntime.Extensions;
using Capy64.LuaRuntime.Libraries;
using KeraLua;
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Linq;
using System.Text;
using System.Timers;
namespace Capy64.LuaRuntime;
public class Runtime
{
private readonly ConcurrentQueue<ILuaEvent> eventQueue = new(new LuaEvent[]
{
new()
{
Name = "init",
Parameters = Array.Empty<object>()
}
});
private readonly Lua Parent;
public Lua Thread { get; private set; }
private string[] filters = Array.Empty<string>();
private bool _disposing = false;
private static System.Timers.Timer yieldTimeoutTimer = new(TimeSpan.FromSeconds(3));
private static bool yieldTimedOut = false;
public Runtime()
{
Parent = new(false)
{
Encoding = Encoding.UTF8,
};
Sandbox.OpenLibraries(Parent);
Thread = Parent.NewThread();
Thread.SetHook(LH_YieldTimeout, LuaHookMask.Count, 7000);
yieldTimeoutTimer.Elapsed += (sender, ev) =>
{
yieldTimedOut = true;
};
}
private static void LH_YieldTimeout(IntPtr state, IntPtr ar)
{
var L = Lua.FromIntPtr(state);
if (yieldTimedOut)
{
L.Error("no yield timeout");
Console.WriteLine("tick");
}
}
public void Patch()
{
Sandbox.Patch(Parent);
}
public void Init()
{
Parent.SetTop(0);
var initContent = File.ReadAllText(Path.Combine(FileSystem.DataPath, "init.lua"));
var status = Thread.LoadString(initContent, "=init.lua");
if (status != LuaStatus.OK)
{
throw new LuaException(Thread.ToString(-1));
}
}
public void PushEvent(LuaEvent ev)
{
eventQueue.Enqueue(ev);
}
public void PushEvent(string name, params object[] pars)
{
eventQueue.Enqueue(new LuaEvent() { Name = name, Parameters = pars });
}
public void PushEvent(LuaDelegateEvent ev)
{
eventQueue.Enqueue(ev);
}
/// <summary>
/// Push a new event to the event queue.
/// </summary>
/// <param name="name">Event name</param>
/// <param name="handler">Event handler. Push any needed parameter from here. Return n as amount of parameters pushed, minus event name.
/// For example: I push an event with 3 parameters, then I return 3.
/// </param>
public void PushEvent(string name, Func<Lua, int> handler)
{
eventQueue.Enqueue(new LuaDelegateEvent
{
Name = name,
Handler = handler
});
}
/// <summary>
/// Resume the Lua thread
/// </summary>
/// <returns>Whether it yielded</returns>
public bool Resume()
{
while (eventQueue.TryDequeue(out ILuaEvent ev))
{
if (!ResumeThread(ev))
return false;
}
return true;
}
private bool ResumeThread(ILuaEvent ev)
{
if (filters.Length > 0 && !filters.Contains(ev.Name))
{
if (!ev.BypassFilter)
{
return true;
}
}
filters = Array.Empty<string>();
var evpars = PushEventToStack(ev);
if (_disposing)
return false;
yieldTimeoutTimer.Start();
yieldTimedOut = false;
var status = Thread.Resume(null, evpars, out int pars);
yieldTimeoutTimer.Stop();
if (status is LuaStatus.OK or LuaStatus.Yield)
{
if (_disposing)
return false;
if (status == LuaStatus.Yield)
{
filters = new string[pars];
for (int i = 0; i < pars; i++)
{
filters[i] = Thread.OptString(i + 1, null);
}
}
Thread.Pop(pars);
return status == LuaStatus.Yield;
}
var error = Thread.OptString(-1, "Unknown exception");
Thread.Traceback(Thread);
var stacktrace = Thread.OptString(-1, "");
throw new LuaException($"Top thread exception:\n{error}\n{stacktrace}");
}
private int PushEventToStack(ILuaEvent ev)
{
Thread.PushString(ev.Name);
switch (ev)
{
case LuaEvent e:
if (e.Parameters != null)
{
foreach (var par in e.Parameters)
{
Thread.PushValue(par);
}
}
return (e.Parameters?.Length ?? 0) + 1;
case LuaDelegateEvent e:
int n = e.Handler(Thread);
return n + 1;
default:
throw new NotImplementedException();
}
}
public void Close()
{
_disposing = true;
Parent.Close();
}
}

View file

@ -1,4 +1,4 @@
namespace Capy64.LuaRuntime; namespace Capy64.Runtime;
public class Constants public class Constants
{ {

View file

@ -1,6 +1,6 @@
using KeraLua; using KeraLua;
namespace Capy64.LuaRuntime.Extensions namespace Capy64.Runtime.Extensions
{ {
static class LuaExtensions static class LuaExtensions
{ {

View file

@ -1,7 +1,7 @@
using System; using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace Capy64.LuaRuntime.Extensions namespace Capy64.Runtime.Extensions
{ {
internal static partial class NativeLibraries internal static partial class NativeLibraries
{ {

View file

@ -4,7 +4,7 @@ using System.Collections;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
namespace Capy64.LuaRuntime.Extensions; namespace Capy64.Runtime.Extensions;
public static class Utils public static class Utils
{ {
public static void PushArray(this Lua L, object obj) public static void PushArray(this Lua L, object obj)

View file

@ -1,21 +1,22 @@
using Capy64.Core; using Capy64.Core;
using Capy64.Eventing;
using Capy64.Eventing.Events; using Capy64.Eventing.Events;
using Capy64.LuaRuntime; using Capy64.Eventing;
using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Input;
using System;
using System.Linq; using System.Linq;
using static Capy64.Eventing.InputManager; using static Capy64.Eventing.InputManager;
using Capy64.Runtime.Extensions;
namespace Capy64.BIOS; namespace Capy64.Runtime;
internal class RuntimeInputEvents internal class InputEmitter
{ {
private EventEmitter _eventEmitter; private EventEmitter _eventEmitter;
private Runtime _runtime; private LuaState _runtime;
private const int rebootDelay = 30; private const int rebootDelay = 30;
private int heldReboot = 0; private int heldReboot = 0;
public RuntimeInputEvents(EventEmitter eventEmitter, Runtime runtime) public InputEmitter(EventEmitter eventEmitter, LuaState runtime)
{ {
_eventEmitter = eventEmitter; _eventEmitter = eventEmitter;
_runtime = runtime; _runtime = runtime;
@ -74,66 +75,84 @@ internal class RuntimeInputEvents
if (heldReboot >= rebootDelay) if (heldReboot >= rebootDelay)
{ {
heldReboot = 0; heldReboot = 0;
Bios.Reboot(); RuntimeManager.Reboot();
} }
} }
private void OnMouseUp(object sender, MouseButtonEvent e) private void OnMouseUp(object sender, MouseButtonEvent e)
{ {
_runtime.PushEvent("mouse_up", new object[] _runtime.QueueEvent("mouse_up", LK =>
{ {
(int)e.Button, LK.PushInteger((int)e.Button);
e.Position.X, LK.PushInteger(e.Position.X);
e.Position.Y, LK.PushInteger(e.Position.Y);
return 3;
}); });
} }
private void OnMouseDown(object sender, MouseButtonEvent e) private void OnMouseDown(object sender, MouseButtonEvent e)
{ {
_runtime.PushEvent("mouse_down", new object[] _runtime.QueueEvent("mouse_down", LK =>
{ {
(int)e.Button, LK.PushInteger((int)e.Button);
e.Position.X, LK.PushInteger(e.Position.X);
e.Position.Y, LK.PushInteger(e.Position.Y);
return 3;
}); });
} }
private void OnMouseMove(object sender, MouseMoveEvent e) private void OnMouseMove(object sender, MouseMoveEvent e)
{ {
_runtime.PushEvent("mouse_move", new object[] _runtime.QueueEvent("mouse_move", LK =>
{ {
e.PressedButtons, LK.NewTable();
e.Position.X, for (int i = 1; i <= e.PressedButtons.Length; i++)
e.Position.Y, {
LK.PushInteger(e.PressedButtons[i - 1]);
LK.SetInteger(-2, i);
}
LK.PushInteger(e.Position.X);
LK.PushInteger(e.Position.Y);
return 3;
}); });
} }
private void OnMouseWheel(object sender, MouseWheelEvent e) private void OnMouseWheel(object sender, MouseWheelEvent e)
{ {
_runtime.PushEvent("mouse_scroll", new object[] _runtime.QueueEvent("mouse_scroll", LK =>
{ {
e.Position.X, LK.PushInteger(e.Position.X);
e.Position.Y, LK.PushInteger(e.Position.Y);
e.VerticalValue, LK.PushInteger(e.VerticalValue);
e.HorizontalValue, LK.PushInteger(e.HorizontalValue);
return 4;
}); });
} }
private void OnKeyUp(object sender, KeyEvent e) private void OnKeyUp(object sender, KeyEvent e)
{ {
_runtime.PushEvent("key_up", new object[] _runtime.QueueEvent("key_up", LK =>
{ {
e.KeyCode, LK.PushInteger(e.KeyCode);
e.KeyName, LK.PushString(e.KeyName);
(int)e.Mods LK.PushInteger((int)e.Mods);
return 3;
}); });
} }
private void OnKeyDown(object sender, KeyEvent e) private void OnKeyDown(object sender, KeyEvent e)
{ {
_runtime.PushEvent("key_down", new object[] _runtime.QueueEvent("key_down", LK =>
{ {
e.KeyCode, LK.PushInteger(e.KeyCode);
e.KeyName, LK.PushString(e.KeyName);
(int)e.Mods, LK.PushInteger((int)e.Mods);
e.IsHeld, LK.PushBoolean(e.IsHeld);
return 4;
}); });
if ((e.Mods & Modifiers.Ctrl) != Modifiers.None && !e.IsHeld) if ((e.Mods & Modifiers.Ctrl) != Modifiers.None && !e.IsHeld)
@ -141,26 +160,17 @@ internal class RuntimeInputEvents
if ((e.Mods & Modifiers.Alt) != Modifiers.None) if ((e.Mods & Modifiers.Alt) != Modifiers.None)
{ {
if (e.Key == Keys.C) if (e.Key == Keys.C)
{ _runtime.QueueEvent("interrupt", LK => 0);
_runtime.PushEvent(new LuaEvent()
{
Name = "interrupt",
Parameters = { },
BypassFilter = true,
});
}
} }
else if (e.Key == Keys.V) else if (e.Key == Keys.V)
{ {
if (SDL.HasClipboardText()) if (SDL.HasClipboardText())
{ {
var text = SDL.GetClipboardText(); var text = SDL.GetClipboardText();
_runtime.PushEvent(new LuaEvent() _runtime.QueueEvent("paste", LK => {
{ LK.PushString(text);
Name = "paste",
Parameters = new[] { return 1;
text,
},
}); });
} }
} }
@ -169,9 +179,10 @@ internal class RuntimeInputEvents
private void OnChar(object sender, CharEvent e) private void OnChar(object sender, CharEvent e)
{ {
_runtime.PushEvent("char", new object[] _runtime.QueueEvent("char", LK => {
{ LK.PushString(e.Character.ToString());
e.Character,
return 1;
}); });
} }
} }

View file

@ -6,7 +6,7 @@ using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Capy64.LuaRuntime.Libraries; namespace Capy64.Runtime.Libraries;
public class Event : IPlugin public class Event : IPlugin
{ {
@ -52,7 +52,7 @@ public class Event : IPlugin
{ {
var L = Lua.FromIntPtr(state); var L = Lua.FromIntPtr(state);
if(L.ToString(1) == "interrupt") if (L.ToString(1) == "interrupt")
{ {
L.Error("interrupt"); L.Error("interrupt");
} }
@ -67,7 +67,7 @@ public class Event : IPlugin
var L = Lua.FromIntPtr(state); var L = Lua.FromIntPtr(state);
var nargs = L.GetTop(); var nargs = L.GetTop();
for(int i = 1; i <= nargs; i++) for (int i = 1; i <= nargs; i++)
{ {
L.CheckString(i); L.CheckString(i);
} }
@ -100,18 +100,14 @@ public class Event : IPlugin
var nargs = L.GetTop(); var nargs = L.GetTop();
var evParsState = L.NewThread(); _game.LuaRuntime.QueueEvent(eventName, LK =>
for(int i = 2; i <= nargs; i++)
{ {
L.PushCopy(i); for (int i = 2; i <= nargs; i++)
} {
L.PushCopy(i);
}
L.XMove(evParsState, nargs - 1); L.XMove(LK, nargs - 1);
_game.LuaRuntime.PushEvent(eventName, LK =>
{
evParsState.XMove(LK, nargs - 1);
return nargs - 1; return nargs - 1;
}); });

View file

@ -1,13 +1,13 @@
using Capy64.API; using Capy64.API;
using Capy64.LuaRuntime.Extensions; using Capy64.Runtime.Objects.Handlers;
using Capy64.LuaRuntime.Objects.Handlers; using Capy64.Runtime.Extensions;
using KeraLua; using KeraLua;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
namespace Capy64.LuaRuntime.Libraries; namespace Capy64.Runtime.Libraries;
public class FileSystem : IPlugin public class FileSystem : IPlugin
{ {

View file

@ -1,11 +1,11 @@
using Capy64.API; using Capy64.API;
using Capy64.LuaRuntime.Objects; using Capy64.Runtime.Objects;
using KeraLua; using KeraLua;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace Capy64.LuaRuntime.Libraries; namespace Capy64.Runtime.Libraries;
public class GPU : IPlugin public class GPU : IPlugin
{ {

View file

@ -1,6 +1,6 @@
using Capy64.API; using Capy64.API;
using Capy64.LuaRuntime.Extensions; using Capy64.Runtime.Extensions;
using Capy64.LuaRuntime.Objects.Handlers; using Capy64.Runtime.Objects.Handlers;
using KeraLua; using KeraLua;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using System; using System;
@ -11,7 +11,7 @@ using System.Net.WebSockets;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
namespace Capy64.LuaRuntime.Libraries; namespace Capy64.Runtime.Libraries;
#nullable enable #nullable enable
public class HTTP : IPlugin public class HTTP : IPlugin
{ {
@ -189,7 +189,13 @@ public class HTTP : IPlugin
if (task.IsFaulted || task.IsCanceled) if (task.IsFaulted || task.IsCanceled)
{ {
_game.LuaRuntime.PushEvent("http_failure", requestId, task.Exception?.Message); _game.LuaRuntime.QueueEvent("http_failure", LK =>
{
LK.PushInteger(requestId);
LK.PushString(task.Exception?.Message);
return 2;
});
return; return;
} }
@ -203,41 +209,42 @@ public class HTTP : IPlugin
else else
handler = new ReadHandle(stream); handler = new ReadHandle(stream);
_game.LuaRuntime.PushEvent("http_response", L => _game.LuaRuntime.QueueEvent("http_response", LK =>
{ {
// arg 1, request id // arg 1, request id
L.PushInteger(requestId); LK.PushInteger(requestId);
// arg 2, response data // arg 2, response data
L.NewTable(); LK.NewTable();
L.PushString("success"); LK.PushString("success");
L.PushBoolean(response.IsSuccessStatusCode); LK.PushBoolean(response.IsSuccessStatusCode);
L.SetTable(-3); LK.SetTable(-3);
L.PushString("statusCode"); LK.PushString("statusCode");
L.PushNumber((int)response.StatusCode); LK.PushNumber((int)response.StatusCode);
L.SetTable(-3); LK.SetTable(-3);
L.PushString("reasonPhrase"); LK.PushString("reasonPhrase");
L.PushString(response.ReasonPhrase); LK.PushString(response.ReasonPhrase);
L.SetTable(-3); LK.SetTable(-3);
L.PushString("headers"); LK.PushString("headers");
L.NewTable(); LK.NewTable();
foreach (var header in response.Headers) foreach (var header in response.Headers)
{ {
L.PushString(header.Key); LK.PushString(header.Key);
L.PushArray(header.Value.ToArray()); LK.PushArray(header.Value.ToArray());
L.SetTable(-3); LK.SetTable(-3);
} }
L.SetTable(-3); LK.SetTable(-3);
handler.Push(L, false); handler.Push(LK, false);
return 2; return 2;
}); });
//_game.LuaRuntime.PushEvent("http_response", requestId, response.IsSuccessStatusCode, content, (int)response.StatusCode, response.ReasonPhrase); //_game.LuaRuntime.PushEvent("http_response", requestId, response.IsSuccessStatusCode, content, (int)response.StatusCode, response.ReasonPhrase);
@ -316,7 +323,13 @@ public class HTTP : IPlugin
{ {
if (task.IsFaulted || task.IsCanceled) if (task.IsFaulted || task.IsCanceled)
{ {
_game.LuaRuntime.PushEvent("websocket_failure", requestId, task.Exception?.Message); _game.LuaRuntime.QueueEvent("websocket_failure", LK =>
{
LK.PushInteger(requestId);
LK.PushString(task.Exception?.Message);
return 2;
});
return; return;
} }
@ -325,11 +338,11 @@ public class HTTP : IPlugin
var handle = new WebSocketHandle(wsClient, requestId, _game); var handle = new WebSocketHandle(wsClient, requestId, _game);
WebSocketConnections.Add(handle); WebSocketConnections.Add(handle);
_game.LuaRuntime.PushEvent("websocket_connect", L => _game.LuaRuntime.QueueEvent("websocket_connect", LK =>
{ {
L.PushInteger(requestId); LK.PushInteger(requestId);
handle.Push(L, true); handle.Push(LK, true);
return 2; return 2;
}); });
@ -342,7 +355,12 @@ public class HTTP : IPlugin
if (result.MessageType == WebSocketMessageType.Close) if (result.MessageType == WebSocketMessageType.Close)
{ {
await wsClient.CloseAsync(WebSocketCloseStatus.NormalClosure, null, CancellationToken.None); await wsClient.CloseAsync(WebSocketCloseStatus.NormalClosure, null, CancellationToken.None);
_game.LuaRuntime.PushEvent("websocket_close", requestId); _game.LuaRuntime.QueueEvent("websocket_close", LK =>
{
LK.PushInteger(requestId);
return 1;
});
return; return;
} }
else else
@ -353,7 +371,13 @@ public class HTTP : IPlugin
if (result.EndOfMessage) if (result.EndOfMessage)
{ {
_game.LuaRuntime.PushEvent("websocket_message", requestId, builder.ToString()); _game.LuaRuntime.QueueEvent("websocket_message", LK =>
{
LK.PushInteger(requestId);
LK.PushString(builder.ToString());
return 2;
});
builder.Clear(); builder.Clear();
} }
} }

View file

@ -2,7 +2,7 @@
using KeraLua; using KeraLua;
using System; using System;
namespace Capy64.LuaRuntime.Libraries; namespace Capy64.Runtime.Libraries;
public class OS : IPlugin public class OS : IPlugin
{ {
@ -47,11 +47,11 @@ public class OS : IPlugin
if (doReboot) if (doReboot)
{ {
BIOS.Bios.Reboot(); RuntimeManager.Reboot();
} }
else else
{ {
BIOS.Bios.Shutdown(); RuntimeManager.Shutdown();
} }
return 0; return 0;

View file

@ -8,7 +8,7 @@ using System;
using static Capy64.Utils; using static Capy64.Utils;
using static System.Formats.Asn1.AsnWriter; using static System.Formats.Asn1.AsnWriter;
namespace Capy64.LuaRuntime.Libraries; namespace Capy64.Runtime.Libraries;
internal class Term : IPlugin internal class Term : IPlugin
{ {

View file

@ -2,7 +2,7 @@
using KeraLua; using KeraLua;
using System; using System;
namespace Capy64.LuaRuntime.Libraries; namespace Capy64.Runtime.Libraries;
class Timer : IPlugin class Timer : IPlugin
{ {
@ -54,7 +54,12 @@ class Timer : IPlugin
timer.Elapsed += (o, e) => timer.Elapsed += (o, e) =>
{ {
_game.LuaRuntime.PushEvent("timer", timerId); _game.LuaRuntime.QueueEvent("timer", LK =>
{
LK.PushInteger(timerId);
return 1;
});
}; };
L.PushInteger(timerId); L.PushInteger(timerId);

View file

@ -0,0 +1,20 @@
using KeraLua;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Capy64.Runtime;
public class LuaEvent
{
public LuaEvent(string name, Func<Lua, int> handler)
{
Name = name;
Handler = handler;
}
public string Name { get; set; }
public Func<Lua, int> Handler { get; set; }
}

View file

@ -1,7 +1,7 @@
using System; using System;
using System.Runtime.Serialization; using System.Runtime.Serialization;
namespace Capy64.LuaRuntime; namespace Capy64.Runtime;
public class LuaException : Exception public class LuaException : Exception
{ {

148
Capy64/Runtime/LuaState.cs Normal file
View file

@ -0,0 +1,148 @@
using Capy64.API;
using KeraLua;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace Capy64.Runtime;
public class LuaState : IDisposable
{
public Lua Thread;
private Lua _parent;
private Queue<LuaEvent> _queue = new();
private string[] _eventFilters = Array.Empty<string>();
public LuaState()
{
_parent = new Lua(false)
{
Encoding = Encoding.UTF8,
};
Sandbox.OpenLibraries(_parent);
Sandbox.Patch(_parent);
Thread = _parent.NewThread();
InitPlugins();
Thread.SetTop(0);
}
private void InitPlugins()
{
var allPlugins = new List<IPlugin>(Capy64.Instance.NativePlugins);
allPlugins.AddRange(Capy64.Instance.Plugins);
foreach (var plugin in allPlugins)
{
plugin.LuaInit(Thread);
}
}
public void QueueEvent(string eventName, Func<Lua, int> handler)
{
_queue.Enqueue(new(eventName, handler));
}
/// <summary>
/// Process all events in the queue.
/// </summary>
/// <returns>Whether the thread can still be resumed and is not finished</returns>
public bool ProcessQueue()
{
while (Dequeue(out var npars))
{
if (!Resume(npars))
{
return false;
}
}
return true;
}
/// <summary>
/// Dequeue the state with event parameters at head of queue
/// and push values to thread
/// </summary>
/// <param name="npars">Amount of parameters of event</param>
/// <returns>If an event is dequeued</returns>
private bool Dequeue(out int npars)
{
npars = 0;
if (_queue.Count == 0)
return false;
var ev = _queue.Dequeue();
Thread.PushString(ev.Name);
npars = 1;
npars += ev.Handler(Thread);
return true;
}
/// <summary>
/// Resume the Lua thread
/// </summary>
/// <returns>If yieldable</returns>
/// <exception cref="LuaException"></exception>
private bool Resume(int npars)
{
//Sandbox.DumpStack(Thread);
var eventName = Thread.ToString(1);
if (_eventFilters.Length != 0 && !_eventFilters.Contains(eventName) && eventName != "interrupt")
{
Thread.Pop(npars);
return true;
}
var status = Thread.Resume(null, npars, out var nresults);
// the Lua script is finished, there's nothing else to resume
if (status == LuaStatus.OK)
return false;
if (status == LuaStatus.Yield)
{
_eventFilters = new string[nresults];
for (var i = 1; i <= nresults; i++)
{
_eventFilters[i - 1] = Thread.ToString(i);
}
Thread.Pop(nresults);
return true;
}
// something bad happened
var error = Thread.ToString(-1);
Thread.Traceback(Thread);
string stacktrace = Thread.OptString(-1, null);
var builder = new StringBuilder();
builder.AppendLine(error);
if (!string.IsNullOrWhiteSpace(stacktrace))
{
builder.Append(stacktrace);
}
throw new LuaException(builder.ToString());
}
public void Dispose()
{
_queue.Clear();
Thread.Close();
_parent.Close();
}
}

View file

@ -1,7 +1,7 @@
using KeraLua; using KeraLua;
using System; using System;
namespace Capy64.LuaRuntime.Objects; namespace Capy64.Runtime.Objects;
public class GPUBuffer public class GPUBuffer
{ {

View file

@ -4,7 +4,7 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text; using System.Text;
namespace Capy64.LuaRuntime.Objects.Handlers; namespace Capy64.Runtime.Objects.Handlers;
public class BinaryReadHandle : IHandle public class BinaryReadHandle : IHandle
{ {

View file

@ -4,7 +4,7 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text; using System.Text;
namespace Capy64.LuaRuntime.Objects.Handlers; namespace Capy64.Runtime.Objects.Handlers;
public class BinaryWriteHandle : IHandle public class BinaryWriteHandle : IHandle
{ {

View file

@ -1,6 +1,6 @@
using KeraLua; using KeraLua;
namespace Capy64.LuaRuntime.Objects.Handlers; namespace Capy64.Runtime.Objects.Handlers;
public interface IHandle public interface IHandle
{ {

View file

@ -3,7 +3,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
namespace Capy64.LuaRuntime.Objects.Handlers; namespace Capy64.Runtime.Objects.Handlers;
public class ReadHandle : IHandle public class ReadHandle : IHandle
{ {

View file

@ -1,4 +1,4 @@
using Capy64.LuaRuntime.Libraries; using Capy64.Runtime.Libraries;
using KeraLua; using KeraLua;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -8,7 +8,7 @@ using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Capy64.LuaRuntime.Objects.Handlers; namespace Capy64.Runtime.Objects.Handlers;
public class WebSocketHandle : IHandle public class WebSocketHandle : IHandle
{ {
@ -92,7 +92,12 @@ public class WebSocketHandle : IHandle
.ContinueWith(async task => .ContinueWith(async task =>
{ {
await task; await task;
_game.LuaRuntime.PushEvent("websocket_close", h._requestId); _game.LuaRuntime.QueueEvent("websocket_close", LK =>
{
LK.PushInteger(h._requestId);
return 1;
});
}); });
HTTP.WebSocketConnections.Remove(h); HTTP.WebSocketConnections.Remove(h);

View file

@ -3,7 +3,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
namespace Capy64.LuaRuntime.Objects.Handlers; namespace Capy64.Runtime.Objects.Handlers;
public class WriteHandle : IHandle public class WriteHandle : IHandle
{ {

View file

@ -0,0 +1,180 @@
using Capy64.API;
using Capy64.Eventing.Events;
using Capy64.Runtime.Libraries;
using KeraLua;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Capy64.Runtime;
internal class RuntimeManager : IPlugin
{
private LuaState luaState;
private InputEmitter emitter;
private int step = 0;
private static bool close = false;
private static IGame _game;
public RuntimeManager(IGame game)
{
_game = game;
_game.EventEmitter.OnInit += OnInit;
_game.EventEmitter.OnTick += OnTick;
}
/// <summary>
/// Start Capy64 state.
/// First BIOS, then OS
/// </summary>
private void Start()
{
emitter?.Unregister();
luaState?.Dispose();
luaState = null;
close = false;
switch (step++)
{
case 0:
InitBIOS();
break;
case 1:
InitOS();
break;
default:
step = 0;
Start();
break;
}
}
private void Resume()
{
if (close || !luaState.ProcessQueue())
{
Start();
}
}
private void InitBIOS()
{
luaState = new LuaState();
_game.LuaRuntime = luaState;
emitter = new(_game.EventEmitter, luaState);
luaState.QueueEvent("init", LK =>
{
LK.PushInteger(step);
return 1;
});
emitter.Register();
luaState.Thread.PushCFunction(L_OpenDataFolder);
luaState.Thread.SetGlobal("openDataFolder");
luaState.Thread.PushCFunction(L_InstallOS);
luaState.Thread.SetGlobal("installOS");
luaState.Thread.PushCFunction(L_Exit);
luaState.Thread.SetGlobal("exit");
var status = luaState.Thread.LoadFile("Assets/bios.lua");
if (status != LuaStatus.OK)
{
throw new LuaException(luaState.Thread.ToString(-1));
}
}
private void InitOS()
{
luaState = new LuaState();
_game.LuaRuntime = luaState;
emitter = new(_game.EventEmitter, luaState);
luaState.QueueEvent("init", LK =>
{
LK.PushInteger(step);
return 1;
});
emitter.Register();
var initContent = File.ReadAllText(Path.Combine(FileSystem.DataPath, "init.lua"));
var status = luaState.Thread.LoadString(initContent, "=init.lua");
if (status != LuaStatus.OK)
{
throw new LuaException(luaState.Thread.ToString(-1));
}
}
public static void Reboot()
{
close = true;
}
public static void Shutdown()
{
close = true;
_game.Exit();
}
public static void InstallOS(bool force = false)
{
var installedFilePath = Path.Combine(Capy64.AppDataPath, ".installed");
if (!File.Exists(installedFilePath) || force)
{
FileSystem.CopyDirectory("Assets/Lua", FileSystem.DataPath, true, true);
File.Create(installedFilePath).Dispose();
}
}
private static int L_OpenDataFolder(IntPtr state)
{
var path = FileSystem.DataPath;
switch (Environment.OSVersion.Platform)
{
case PlatformID.Win32NT:
Process.Start("explorer.exe", path);
break;
case PlatformID.Unix:
Process.Start("xdg-open", path);
break;
}
return 0;
}
private static int L_InstallOS(IntPtr state)
{
InstallOS(true);
return 0;
}
private static int L_Exit(IntPtr state)
{
close = true;
return 0;
}
private void OnInit(object sender, EventArgs e)
{
Start();
}
private void OnTick(object sender, TickEvent e)
{
Resume();
}
}

View file

@ -1,12 +1,12 @@
using Capy64.LuaRuntime.Extensions; using Capy64.Runtime.Extensions;
using Capy64.LuaRuntime.Libraries; using Capy64.Runtime.Libraries;
using KeraLua; using KeraLua;
using System; using System;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
namespace Capy64.LuaRuntime; namespace Capy64.Runtime;
internal class Sandbox internal class Sandbox
{ {