Browse Source

Change: redesigning Lua threads

AnnulusGames 1 year ago
parent
commit
21f4fb9610

+ 44 - 16
sandbox/ConsoleApp1/Program.cs

@@ -5,31 +5,59 @@ using Lua;
 using Lua.Standard;
 using Lua.Standard;
 
 
 var state = LuaState.Create();
 var state = LuaState.Create();
-state.OpenBaseLibrary();
+state.OpenBasicLibrary();
 
 
 try
 try
 {
 {
     var source =
     var source =
-@"
-metatable = {
-    __add = function(a, b)
-        local t = { }
-
-        for i = 1, #a do
-            t[i] = a[i] + b[i]
+"""
+-- メインコルーチンの定義
+local co_main = coroutine.create(function ()
+    print("Main coroutine starts")
+
+    -- コルーチンAの定義
+    local co_a = coroutine.create(function()
+        for i = 1, 3 do
+            print("Coroutine A, iteration "..i)
+            coroutine.yield()
         end
         end
-
-        return t
+        print("Coroutine A ends")
+    end)
+
+    --コルーチンBの定義
+    local co_b = coroutine.create(function()
+        print("Coroutine B starts")
+        coroutine.yield()-- 一時停止
+        print("Coroutine B resumes")
+    end)
+
+    -- コルーチンCの定義(コルーチンBを呼び出す)
+    local co_c = coroutine.create(function()
+        print("Coroutine C starts")
+        coroutine.resume(co_b)-- コルーチンBを実行
+        print("Coroutine C calls B and resumes")
+        coroutine.yield()-- 一時停止
+        print("Coroutine C resumes")
+    end)
+
+    -- コルーチンAとCの交互実行
+    for _ = 1, 2 do
+            coroutine.resume(co_a)
+        coroutine.resume(co_c)
     end
     end
-}
 
 
-local a = { 1, 2, 3 }
-local b = { 4, 5, 6 }
+    -- コルーチンAを再開し完了させる
+    coroutine.resume(co_a)
+
+    -- コルーチンCを再開し完了させる
+    coroutine.resume(co_c)
 
 
-setmetatable(a, metatable)
+    print("Main coroutine ends")
+end)
 
 
-return a + b
-";
+--メインコルーチンを開始
+coroutine.resume(co_main)
+""";
 
 
     var syntaxTree = LuaSyntaxTree.Parse(source, "main.lua");
     var syntaxTree = LuaSyntaxTree.Parse(source, "main.lua");
 
 

+ 217 - 0
src/Lua/LuaCoroutine.cs

@@ -0,0 +1,217 @@
+using System.Threading.Tasks.Sources;
+using Lua.Internal;
+
+namespace Lua;
+
+public sealed class LuaCoroutine : LuaThread, IValueTaskSource<LuaCoroutine.YieldContext>, IValueTaskSource<LuaCoroutine.ResumeContext>
+{
+    struct YieldContext
+    {
+    }
+
+    struct ResumeContext
+    {
+        public LuaValue[] Results;
+    }
+
+    byte status;
+    LuaState threadState;
+    ValueTask<int> functionTask;
+
+    ManualResetValueTaskSourceCore<ResumeContext> resume;
+    ManualResetValueTaskSourceCore<YieldContext> yield;
+
+    public override LuaThreadStatus GetStatus() => (LuaThreadStatus)status;
+    public bool IsProtectedMode { get; }
+    public LuaFunction Function { get; }
+
+    internal LuaCoroutine(LuaState state, LuaFunction function, bool isProtectedMode)
+    {
+        IsProtectedMode = isProtectedMode;
+        threadState = state;
+        Function = function;
+    }
+
+    public override async ValueTask<int> Resume(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken = default)
+    {
+        context.State.ThreadStack.Push(this);
+        try
+        {
+            switch ((LuaThreadStatus)Volatile.Read(ref status))
+            {
+                case LuaThreadStatus.Normal:
+                    Volatile.Write(ref status, (byte)LuaThreadStatus.Running);
+
+                    // first argument is LuaThread object
+                    for (int i = 0; i < context.ArgumentCount - 1; i++)
+                    {
+                        threadState.Push(context.Arguments[i + 1]);
+                    }
+
+                    functionTask = Function.InvokeAsync(new()
+                    {
+                        State = threadState,
+                        ArgumentCount = context.ArgumentCount - 1,
+                        ChunkName = Function.Name,
+                        RootChunkName = context.RootChunkName,
+                    }, buffer[1..], cancellationToken).Preserve();
+
+                    break;
+                case LuaThreadStatus.Suspended:
+                    Volatile.Write(ref status, (byte)LuaThreadStatus.Running);
+                    yield.SetResult(new());
+                    break;
+                case LuaThreadStatus.Running:
+                    if (IsProtectedMode)
+                    {
+                        buffer.Span[0] = false;
+                        buffer.Span[1] = "cannot resume non-suspended coroutine";
+                        return 2;
+                    }
+                    else
+                    {
+                        throw new InvalidOperationException("cannot resume non-suspended coroutine");
+                    }
+                case LuaThreadStatus.Dead:
+                    if (IsProtectedMode)
+                    {
+                        buffer.Span[0] = false;
+                        buffer.Span[1] = "cannot resume dead coroutine";
+                        return 2;
+                    }
+                    else
+                    {
+                        throw new InvalidOperationException("cannot resume dead coroutine");
+                    }
+            }
+
+            var resumeTask = new ValueTask<ResumeContext>(this, resume.Version);
+
+            CancellationTokenRegistration registration = default;
+            if (cancellationToken.CanBeCanceled)
+            {
+                registration = cancellationToken.UnsafeRegister(static x =>
+                {
+                    var coroutine = (LuaCoroutine)x!;
+                    coroutine.yield.SetException(new OperationCanceledException());
+                }, this);
+            }
+
+            try
+            {
+                (var index, var result0, var result1) = await ValueTaskEx.WhenAny(resumeTask, functionTask!);
+
+                if (index == 0)
+                {
+                    var results = result0.Results;
+
+                    buffer.Span[0] = true;
+                    for (int i = 0; i < results.Length; i++)
+                    {
+                        buffer.Span[i + 1] = results[i];
+                    }
+
+                    return results.Length + 1;
+                }
+                else
+                {
+                    Volatile.Write(ref status, (byte)LuaThreadStatus.Dead);
+                    buffer.Span[0] = true;
+                    return 1 + functionTask!.Result;
+                }
+            }
+            catch (Exception ex) when (ex is not OperationCanceledException)
+            {
+                if (IsProtectedMode)
+                {
+                    Volatile.Write(ref status, (byte)LuaThreadStatus.Dead);
+                    buffer.Span[0] = false;
+                    buffer.Span[1] = ex.Message;
+                    return 2;
+                }
+                else
+                {
+                    throw;
+                }
+            }
+            finally
+            {
+                registration.Dispose();
+                resume.Reset();
+            }
+        }
+        finally
+        {
+            context.State.ThreadStack.Pop();
+        }
+    }
+
+    public override async ValueTask Yield(LuaFunctionExecutionContext context, CancellationToken cancellationToken = default)
+    {
+        if (Volatile.Read(ref status) != (byte)LuaThreadStatus.Running)
+        {
+            throw new InvalidOperationException("cannot call yield on a coroutine that is not currently running");
+        }
+
+        resume.SetResult(new()
+        {
+            Results = context.Arguments.ToArray(),
+        });
+
+        Volatile.Write(ref status, (byte)LuaThreadStatus.Suspended);
+
+        CancellationTokenRegistration registration = default;
+        if (cancellationToken.CanBeCanceled)
+        {
+            registration = cancellationToken.UnsafeRegister(static x =>
+            {
+                var coroutine = (LuaCoroutine)x!;
+                coroutine.yield.SetException(new OperationCanceledException());
+            }, this);
+        }
+
+    RETRY:
+        try
+        {
+            await new ValueTask<YieldContext>(this, yield.Version);
+        }
+        catch (Exception ex) when (ex is not OperationCanceledException)
+        {
+            yield.Reset();
+            goto RETRY;
+        }
+
+        registration.Dispose();
+        yield.Reset();
+    }
+
+    YieldContext IValueTaskSource<YieldContext>.GetResult(short token)
+    {
+        return yield.GetResult(token);
+    }
+
+    ValueTaskSourceStatus IValueTaskSource<YieldContext>.GetStatus(short token)
+    {
+        return yield.GetStatus(token);
+    }
+
+    void IValueTaskSource<YieldContext>.OnCompleted(Action<object?> continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags)
+    {
+        yield.OnCompleted(continuation, state, token, flags);
+    }
+
+    ResumeContext IValueTaskSource<ResumeContext>.GetResult(short token)
+    {
+        return resume.GetResult(token);
+    }
+
+    ValueTaskSourceStatus IValueTaskSource<ResumeContext>.GetStatus(short token)
+    {
+        return resume.GetStatus(token);
+    }
+
+    void IValueTaskSource<ResumeContext>.OnCompleted(Action<object?> continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags)
+    {
+        resume.OnCompleted(continuation, state, token, flags);
+    }
+}

+ 5 - 4
src/Lua/LuaFunction.cs

@@ -9,10 +9,11 @@ public abstract partial class LuaFunction
     public async ValueTask<int> InvokeAsync(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
     public async ValueTask<int> InvokeAsync(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
     {
     {
         var state = context.State;
         var state = context.State;
+        var thread = state.CurrentThread;
 
 
         var frame = new CallStackFrame
         var frame = new CallStackFrame
         {
         {
-            Base = context.StackPosition == null ? state.Stack.Count - context.ArgumentCount : context.StackPosition.Value,
+            Base = context.StackPosition == null ? thread.Stack.Count - context.ArgumentCount : context.StackPosition.Value,
             CallPosition = context.SourcePosition,
             CallPosition = context.SourcePosition,
             ChunkName = context.ChunkName ?? LuaState.DefaultChunkName,
             ChunkName = context.ChunkName ?? LuaState.DefaultChunkName,
             RootChunkName = context.RootChunkName ?? LuaState.DefaultChunkName,
             RootChunkName = context.RootChunkName ?? LuaState.DefaultChunkName,
@@ -20,18 +21,18 @@ public abstract partial class LuaFunction
             Function = this,
             Function = this,
         };
         };
 
 
-        state.PushCallStackFrame(frame);
+        thread.PushCallStackFrame(frame);
         try
         try
         {
         {
             return await InvokeAsyncCore(context, buffer, cancellationToken);
             return await InvokeAsyncCore(context, buffer, cancellationToken);
         }
         }
         catch (Exception ex) when (ex is not (LuaException or OperationCanceledException))
         catch (Exception ex) when (ex is not (LuaException or OperationCanceledException))
         {
         {
-            throw new LuaRuntimeException(state.GetTracebacks(), ex.Message);
+            throw new LuaRuntimeException(thread.GetTracebacks(), ex.Message);
         }
         }
         finally
         finally
         {
         {
-            state.PopCallStackFrame();
+            thread.PopCallStackFrame();
         }
         }
     }
     }
 
 

+ 12 - 5
src/Lua/LuaFunctionExecutionContext.cs

@@ -12,8 +12,14 @@ public readonly record struct LuaFunctionExecutionContext
     public string? RootChunkName { get; init; }
     public string? RootChunkName { get; init; }
     public string? ChunkName { get; init; }
     public string? ChunkName { get; init; }
 
 
-    public int FrameBase => State.GetCurrentFrame().Base;
-    public ReadOnlySpan<LuaValue> Arguments => State.GetStackValues().Slice(FrameBase, ArgumentCount);
+    public ReadOnlySpan<LuaValue> Arguments
+    {
+        get
+        {
+            var thread = State.CurrentThread;
+            return thread.GetStackValues().Slice(thread.GetCurrentFrame().Base, ArgumentCount);
+        }
+    }
     
     
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
     public LuaValue ReadArgument(int index)
     public LuaValue ReadArgument(int index)
@@ -30,13 +36,14 @@ public readonly record struct LuaFunctionExecutionContext
         var arg = Arguments[index];
         var arg = Arguments[index];
         if (!arg.TryRead<T>(out var argValue))
         if (!arg.TryRead<T>(out var argValue))
         {
         {
+            var thread = State.CurrentThread;
             if (LuaValue.TryGetLuaValueType(typeof(T), out var type))
             if (LuaValue.TryGetLuaValueType(typeof(T), out var type))
             {
             {
-                LuaRuntimeException.BadArgument(State.GetTracebacks(), 1, State.GetCurrentFrame().Function.Name, type.ToString(), arg.Type.ToString());
+                LuaRuntimeException.BadArgument(State.GetTracebacks(), 1, thread.GetCurrentFrame().Function.Name, type.ToString(), arg.Type.ToString());
             }
             }
             else
             else
             {
             {
-                LuaRuntimeException.BadArgument(State.GetTracebacks(), 1, State.GetCurrentFrame().Function.Name, typeof(T).Name, arg.Type.ToString());
+                LuaRuntimeException.BadArgument(State.GetTracebacks(), 1, thread.GetCurrentFrame().Function.Name, typeof(T).Name, arg.Type.ToString());
             }
             }
         }
         }
 
 
@@ -47,7 +54,7 @@ public readonly record struct LuaFunctionExecutionContext
     {
     {
         if (ArgumentCount <= index)
         if (ArgumentCount <= index)
         {
         {
-            LuaRuntimeException.BadArgument(State.GetTracebacks(), index + 1, State.GetCurrentFrame().Function.Name);
+            LuaRuntimeException.BadArgument(State.GetTracebacks(), index + 1, State.CurrentThread.GetCurrentFrame().Function.Name);
         }
         }
     }
     }
 }
 }

+ 21 - 0
src/Lua/LuaMainThread.cs

@@ -0,0 +1,21 @@
+namespace Lua;
+
+public sealed class LuaMainThread : LuaThread
+{
+    public override LuaThreadStatus GetStatus()
+    {
+        return LuaThreadStatus.Running;
+    }
+
+    public override ValueTask<int> Resume(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken = default)
+    {
+        buffer.Span[0] = false;
+        buffer.Span[1] = "cannot resume non-suspended coroutine";
+        return new(2);
+    }
+
+    public override ValueTask Yield(LuaFunctionExecutionContext context, CancellationToken cancellationToken = default)
+    {
+        throw new LuaRuntimeException(context.State.GetTracebacks(), "attempt to yield from outside a coroutine");
+    }
+}

+ 29 - 85
src/Lua/LuaState.cs

@@ -1,4 +1,3 @@
-using System.Diagnostics.CodeAnalysis;
 using Lua.Internal;
 using Lua.Internal;
 using Lua.Runtime;
 using Lua.Runtime;
 
 
@@ -8,33 +7,28 @@ public sealed class LuaState
 {
 {
     public const string DefaultChunkName = "chunk";
     public const string DefaultChunkName = "chunk";
 
 
-    class GlobalState
-    {
-        public FastStackCore<LuaThread> threadStack;
-        public FastListCore<UpValue> openUpValues;
-        public readonly LuaTable environment;
-        public readonly UpValue envUpValue;
+    readonly LuaMainThread mainThread = new();
+    FastListCore<UpValue> openUpValues;
+    FastStackCore<LuaThread> threadStack;
+    readonly LuaTable environment;
+    readonly UpValue envUpValue;
+    bool isRunning;
+
+    internal UpValue EnvUpValue => envUpValue;
+    internal ref FastStackCore<LuaThread> ThreadStack => ref threadStack;
+    internal ref FastListCore<UpValue> OpenUpValues => ref openUpValues;
 
 
-        public GlobalState(LuaState state)
+    public LuaTable Environment => environment;
+    public LuaMainThread MainThread => mainThread;
+    public LuaThread CurrentThread
+    {
+        get
         {
         {
-            environment = new();
-            envUpValue = UpValue.Closed(state, environment);
+            if (threadStack.TryPeek(out var thread)) return thread;
+            return mainThread;
         }
         }
     }
     }
 
 
-    readonly GlobalState globalState;
-
-    readonly LuaStack stack = new();
-    FastStackCore<CallStackFrame> callStack;
-    bool isRunning;
-
-    internal LuaStack Stack => stack;
-    internal UpValue EnvUpValue => globalState.envUpValue;
-    internal ref FastStackCore<LuaThread> ThreadStack => ref globalState.threadStack;
-
-    public LuaTable Environment => globalState.environment;
-    public bool IsRunning => Volatile.Read(ref isRunning);
-
     public static LuaState Create()
     public static LuaState Create()
     {
     {
         return new();
         return new();
@@ -42,12 +36,8 @@ public sealed class LuaState
 
 
     LuaState()
     LuaState()
     {
     {
-        globalState = new(this);
-    }
-
-    LuaState(LuaState parent)
-    {
-        globalState = parent.globalState;
+        environment = new();
+        envUpValue = UpValue.Closed(mainThread, environment);
     }
     }
 
 
     public async ValueTask<int> RunAsync(Chunk chunk, Memory<LuaValue> buffer, CancellationToken cancellationToken = default)
     public async ValueTask<int> RunAsync(Chunk chunk, Memory<LuaValue> buffer, CancellationToken cancellationToken = default)
@@ -74,80 +64,43 @@ public sealed class LuaState
         }
         }
     }
     }
 
 
-    public ReadOnlySpan<LuaValue> GetStackValues()
-    {
-        return stack.AsSpan();
-    }
-
-    public int StackCount => stack.Count;
-
     public void Push(LuaValue value)
     public void Push(LuaValue value)
     {
     {
         ThrowIfRunning();
         ThrowIfRunning();
-        stack.Push(value);
-    }
-
-    internal void PushCallStackFrame(CallStackFrame frame)
-    {
-        callStack.Push(frame);
-    }
-
-    internal void PopCallStackFrame()
-    {
-        var frame = callStack.Pop();
-        stack.PopUntil(frame.Base);
-    }
-
-    internal ReadOnlySpan<CallStackFrame> GetCallStackSpan()
-    {
-        return callStack.AsSpan();
+        mainThread.Stack.Push(value);
     }
     }
 
 
     public LuaThread CreateThread(LuaFunction function, bool isProtectedMode = true)
     public LuaThread CreateThread(LuaFunction function, bool isProtectedMode = true)
     {
     {
-        return new LuaThread(new LuaState(this), function, isProtectedMode);
-    }
-
-    public bool TryGetCurrentThread([NotNullWhen(true)] out LuaThread? result)
-    {
-        return ThreadStack.TryPeek(out result);
-    }
-
-    public CallStackFrame GetCurrentFrame()
-    {
-        return callStack.Peek();
+        return new LuaCoroutine(this, function, isProtectedMode);
     }
     }
 
 
     public Tracebacks GetTracebacks()
     public Tracebacks GetTracebacks()
     {
     {
-        return new()
-        {
-            StackFrames = callStack.AsSpan()[1..].ToArray()
-        };
+        return MainThread.GetTracebacks();
     }
     }
 
 
-    internal UpValue GetOrAddUpValue(int registerIndex)
+    internal UpValue GetOrAddUpValue(LuaThread thread, int registerIndex)
     {
     {
-        foreach (var upValue in globalState.openUpValues.AsSpan())
+        foreach (var upValue in openUpValues.AsSpan())
         {
         {
-            if (upValue.RegisterIndex == registerIndex && upValue.State == this)
+            if (upValue.RegisterIndex == registerIndex && upValue.Thread == thread)
             {
             {
                 return upValue;
                 return upValue;
             }
             }
         }
         }
 
 
-        var newUpValue = UpValue.Open(this, registerIndex);
-        globalState.openUpValues.Add(newUpValue);
+        var newUpValue = UpValue.Open(thread, registerIndex);
+        openUpValues.Add(newUpValue);
         return newUpValue;
         return newUpValue;
     }
     }
 
 
-    internal void CloseUpValues(int frameBase)
+    internal void CloseUpValues(LuaThread thread, int frameBase)
     {
     {
-        var openUpValues = globalState.openUpValues;
         for (int i = 0; i < openUpValues.Length; i++)
         for (int i = 0; i < openUpValues.Length; i++)
         {
         {
             var upValue = openUpValues[i];
             var upValue = openUpValues[i];
-            if (upValue.State != this) continue;
+            if (upValue.Thread != thread) continue;
 
 
             if (upValue.RegisterIndex >= frameBase)
             if (upValue.RegisterIndex >= frameBase)
             {
             {
@@ -158,15 +111,6 @@ public sealed class LuaState
         }
         }
     }
     }
 
 
-    internal void DumpStackValues()
-    {
-        var span = GetStackValues();
-        for (int i = 0; i < span.Length; i++)
-        {
-            Console.WriteLine($"LuaStack [{i}]\t{span[i]}");
-        }
-    }
-
     void ThrowIfRunning()
     void ThrowIfRunning()
     {
     {
         if (Volatile.Read(ref isRunning))
         if (Volatile.Read(ref isRunning))

+ 27 - 181
src/Lua/LuaThread.cs

@@ -1,208 +1,54 @@
-using System.Threading.Tasks.Sources;
 using Lua.Internal;
 using Lua.Internal;
+using Lua.Runtime;
 
 
 namespace Lua;
 namespace Lua;
 
 
-public sealed class LuaThread : IValueTaskSource<LuaThread.YieldContext>, IValueTaskSource<LuaThread.ResumeContext>
+public abstract class LuaThread
 {
 {
-    struct YieldContext
-    {
-    }
-
-    struct ResumeContext
-    {
-        public LuaValue[] Results;
-    }
-
-    byte status;
-    LuaState threadState;
-    ValueTask<int> functionTask;
+    public abstract LuaThreadStatus GetStatus();
+    public abstract ValueTask<int> Resume(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken = default);
+    public abstract ValueTask Yield(LuaFunctionExecutionContext context, CancellationToken cancellationToken = default);
 
 
-    ManualResetValueTaskSourceCore<ResumeContext> resume;
-    ManualResetValueTaskSourceCore<YieldContext> yield;
+    LuaStack stack = new();
+    FastStackCore<CallStackFrame> callStack;
 
 
-    public LuaThreadStatus Status => (LuaThreadStatus)status;
-    public bool IsProtectedMode { get; }
-    public LuaFunction Function { get; }
+    internal LuaStack Stack => stack;
 
 
-    internal LuaThread(LuaState state, LuaFunction function, bool isProtectedMode)
+    public CallStackFrame GetCurrentFrame()
     {
     {
-        IsProtectedMode = isProtectedMode;
-        threadState = state;
-        Function = function;
+        return callStack.Peek();
     }
     }
 
 
-    public async ValueTask<int> Resume(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken = default)
+    public ReadOnlySpan<LuaValue> GetStackValues()
     {
     {
-        context.State.ThreadStack.Push(this);
-        try
-        {
-            switch ((LuaThreadStatus)Volatile.Read(ref status))
-            {
-                case LuaThreadStatus.Normal:
-                    Volatile.Write(ref status, (byte)LuaThreadStatus.Running);
-
-                    // first argument is LuaThread object
-                    for (int i = 0; i < context.ArgumentCount - 1; i++)
-                    {
-                        threadState.Push(context.Arguments[i + 1]);
-                    }
-
-                    functionTask = Function.InvokeAsync(new()
-                    {
-                        State = threadState,
-                        ArgumentCount = context.ArgumentCount - 1,
-                        ChunkName = Function.Name,
-                        RootChunkName = context.RootChunkName,
-                    }, buffer[1..], cancellationToken).Preserve();
-
-                    break;
-                case LuaThreadStatus.Suspended:
-                    Volatile.Write(ref status, (byte)LuaThreadStatus.Running);
-                    yield.SetResult(new());
-                    break;
-                case LuaThreadStatus.Running:
-                    throw new InvalidOperationException("cannot resume running coroutine");
-                case LuaThreadStatus.Dead:
-                    if (IsProtectedMode)
-                    {
-                        buffer.Span[0] = false;
-                        buffer.Span[1] = "cannot resume dead coroutine";
-                        return 2;
-                    }
-                    else
-                    {
-                        throw new InvalidOperationException("cannot resume dead coroutine");
-                    }
-            }
-
-            var resumeTask = new ValueTask<ResumeContext>(this, resume.Version);
-
-            CancellationTokenRegistration registration = default;
-            if (cancellationToken.CanBeCanceled)
-            {
-                registration = cancellationToken.UnsafeRegister(static x =>
-                {
-                    var thread = (LuaThread)x!;
-                    thread.yield.SetException(new OperationCanceledException());
-                }, this);
-            }
-
-            try
-            {
-                (var index, var result0, var result1) = await ValueTaskEx.WhenAny(resumeTask, functionTask!);
-
-                if (index == 0)
-                {
-                    var results = result0.Results;
-
-                    buffer.Span[0] = true;
-                    for (int i = 0; i < results.Length; i++)
-                    {
-                        buffer.Span[i + 1] = results[i];
-                    }
-
-                    return results.Length + 1;
-                }
-                else
-                {
-                    Volatile.Write(ref status, (byte)LuaThreadStatus.Dead);
-                    buffer.Span[0] = true;
-                    return 1 + functionTask!.Result;
-                }
-            }
-            catch (Exception ex) when (ex is not OperationCanceledException)
-            {
-                if (IsProtectedMode)
-                {
-                    Volatile.Write(ref status, (byte)LuaThreadStatus.Dead);
-                    buffer.Span[0] = false;
-                    buffer.Span[1] = ex.Message;
-                    return 2;
-                }
-                else
-                {
-                    throw;
-                }
-            }
-            finally
-            {
-                registration.Dispose();
-                resume.Reset();
-            }
-        }
-        finally
-        {
-            context.State.ThreadStack.Pop();
-        }
+        return stack.AsSpan();
     }
     }
 
 
-    public async ValueTask Yield(LuaFunctionExecutionContext context, CancellationToken cancellationToken = default)
+    internal Tracebacks GetTracebacks()
     {
     {
-        if (Volatile.Read(ref status) != (byte)LuaThreadStatus.Running)
-        {
-            throw new InvalidOperationException("cannot call yield on a coroutine that is not currently running");
-        }
-
-        resume.SetResult(new()
+        return new()
         {
         {
-            Results = context.Arguments.ToArray(),
-        });
-
-        Volatile.Write(ref status, (byte)LuaThreadStatus.Suspended);
-
-        CancellationTokenRegistration registration = default;
-        if (cancellationToken.CanBeCanceled)
-        {
-            registration = cancellationToken.UnsafeRegister(static x =>
-            {
-                var thread = (LuaThread)x!;
-                thread.yield.SetException(new OperationCanceledException());
-            }, this);
-        }
-
-    RETRY:
-        try
-        {
-            await new ValueTask<YieldContext>(this, yield.Version);
-        }
-        catch (Exception ex) when (ex is not OperationCanceledException)
-        {
-            yield.Reset();
-            goto RETRY;
-        }
-
-        registration.Dispose();
-        yield.Reset();
+            StackFrames = callStack.AsSpan()[1..].ToArray()
+        };
     }
     }
 
 
-    YieldContext IValueTaskSource<YieldContext>.GetResult(short token)
+    internal void PushCallStackFrame(CallStackFrame frame)
     {
     {
-        return yield.GetResult(token);
+        callStack.Push(frame);
     }
     }
 
 
-    ValueTaskSourceStatus IValueTaskSource<YieldContext>.GetStatus(short token)
+    internal void PopCallStackFrame()
     {
     {
-        return yield.GetStatus(token);
+        var frame = callStack.Pop();
+        stack.PopUntil(frame.Base);
     }
     }
 
 
-    void IValueTaskSource<YieldContext>.OnCompleted(Action<object?> continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags)
+    internal void DumpStackValues()
     {
     {
-        yield.OnCompleted(continuation, state, token, flags);
-    }
-
-    ResumeContext IValueTaskSource<ResumeContext>.GetResult(short token)
-    {
-        return resume.GetResult(token);
-    }
-
-    ValueTaskSourceStatus IValueTaskSource<ResumeContext>.GetStatus(short token)
-    {
-        return resume.GetStatus(token);
-    }
-
-    void IValueTaskSource<ResumeContext>.OnCompleted(Action<object?> continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags)
-    {
-        resume.OnCompleted(continuation, state, token, flags);
+        var span = GetStackValues();
+        for (int i = 0; i < span.Length; i++)
+        {
+            Console.WriteLine($"LuaStack [{i}]\t{span[i]}");
+        }
     }
     }
 }
 }

+ 3 - 2
src/Lua/Runtime/Closure.cs

@@ -27,14 +27,15 @@ public sealed class Closure : LuaFunction
 
 
     protected override ValueTask<int> InvokeAsyncCore(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
     protected override ValueTask<int> InvokeAsyncCore(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
     {
     {
-        return LuaVirtualMachine.ExecuteClosureAsync(context.State, this, context.State.GetCurrentFrame(), buffer, cancellationToken);
+        return LuaVirtualMachine.ExecuteClosureAsync(context.State, this, context.State.CurrentThread.GetCurrentFrame(), buffer, cancellationToken);
     }
     }
 
 
     static UpValue GetUpValueFromDescription(LuaState state, Chunk proto, UpValueInfo description)
     static UpValue GetUpValueFromDescription(LuaState state, Chunk proto, UpValueInfo description)
     {
     {
         if (description.IsInRegister)
         if (description.IsInRegister)
         {
         {
-            return state.GetOrAddUpValue(state.GetCurrentFrame().Base + description.Index);
+            var thread = state.CurrentThread;
+            return state.GetOrAddUpValue(thread, thread.GetCurrentFrame().Base + description.Index);
         }
         }
         else if (description.Index == -1) // -1 is global environment
         else if (description.Index == -1) // -1 is global environment
         {
         {

+ 12 - 11
src/Lua/Runtime/LuaVirtualMachine.cs

@@ -8,7 +8,8 @@ public static partial class LuaVirtualMachine
 {
 {
     internal async static ValueTask<int> ExecuteClosureAsync(LuaState state, Closure closure, CallStackFrame frame, Memory<LuaValue> buffer, CancellationToken cancellationToken)
     internal async static ValueTask<int> ExecuteClosureAsync(LuaState state, Closure closure, CallStackFrame frame, Memory<LuaValue> buffer, CancellationToken cancellationToken)
     {
     {
-        var stack = state.Stack;
+        var thread = state.CurrentThread;
+        var stack = thread.Stack;
         var chunk = closure.Proto;
         var chunk = closure.Proto;
 
 
         for (var pc = 0; pc < chunk.Instructions.Length; pc++)
         for (var pc = 0; pc < chunk.Instructions.Length; pc++)
@@ -498,7 +499,7 @@ public static partial class LuaVirtualMachine
                     pc += instruction.SBx;
                     pc += instruction.SBx;
                     if (instruction.A != 0)
                     if (instruction.A != 0)
                     {
                     {
-                        state.CloseUpValues(instruction.A);
+                        state.CloseUpValues(thread, instruction.A);
                     }
                     }
                     break;
                     break;
                 case OpCode.Eq:
                 case OpCode.Eq:
@@ -711,7 +712,7 @@ public static partial class LuaVirtualMachine
                     break;
                     break;
                 case OpCode.TailCall:
                 case OpCode.TailCall:
                     {
                     {
-                        state.CloseUpValues(frame.Base);
+                        state.CloseUpValues(thread, frame.Base);
 
 
                         var va = stack.UnsafeGet(RA);
                         var va = stack.UnsafeGet(RA);
                         if (!va.TryRead<LuaFunction>(out var func))
                         if (!va.TryRead<LuaFunction>(out var func))
@@ -736,7 +737,7 @@ public static partial class LuaVirtualMachine
                     }
                     }
                 case OpCode.Return:
                 case OpCode.Return:
                     {
                     {
-                        state.CloseUpValues(frame.Base);
+                        state.CloseUpValues(thread, frame.Base);
 
 
                         var retCount = instruction.B - 1;
                         var retCount = instruction.B - 1;
 
 
@@ -880,7 +881,7 @@ public static partial class LuaVirtualMachine
 #endif
 #endif
     static async ValueTask<LuaValue> GetTableValue(LuaState state, Chunk chunk, int pc, LuaValue table, LuaValue key, CancellationToken cancellationToken)
     static async ValueTask<LuaValue> GetTableValue(LuaState state, Chunk chunk, int pc, LuaValue table, LuaValue key, CancellationToken cancellationToken)
     {
     {
-        var stack = state.Stack;
+        var stack = state.CurrentThread.Stack;
         var isTable = table.TryRead<LuaTable>(out var t);
         var isTable = table.TryRead<LuaTable>(out var t);
 
 
         if (isTable && t.TryGetValue(key, out var result))
         if (isTable && t.TryGetValue(key, out var result))
@@ -931,7 +932,7 @@ public static partial class LuaVirtualMachine
 #endif
 #endif
     static async ValueTask SetTableValue(LuaState state, Chunk chunk, int pc, LuaValue table, LuaValue key, LuaValue value, CancellationToken cancellationToken)
     static async ValueTask SetTableValue(LuaState state, Chunk chunk, int pc, LuaValue table, LuaValue key, LuaValue value, CancellationToken cancellationToken)
     {
     {
-        var stack = state.Stack;
+        var stack = state.CurrentThread.Stack;
         var isTable = table.TryRead<LuaTable>(out var t);
         var isTable = table.TryRead<LuaTable>(out var t);
 
 
         if (isTable && t.ContainsKey(key))
         if (isTable && t.ContainsKey(key))
@@ -977,7 +978,7 @@ public static partial class LuaVirtualMachine
 
 
     static (int FrameBase, int ArgumentCount) PrepareForFunctionCall(LuaState state, LuaFunction function, Instruction instruction, int RA, bool isTailCall)
     static (int FrameBase, int ArgumentCount) PrepareForFunctionCall(LuaState state, LuaFunction function, Instruction instruction, int RA, bool isTailCall)
     {
     {
-        var stack = state.Stack;
+        var stack = state.CurrentThread.Stack;
 
 
         var argumentCount = instruction.B - 1;
         var argumentCount = instruction.B - 1;
         if (instruction.B == 0)
         if (instruction.B == 0)
@@ -991,7 +992,7 @@ public static partial class LuaVirtualMachine
         // Therefore, a call can be made without allocating new registers.
         // Therefore, a call can be made without allocating new registers.
         if (isTailCall)
         if (isTailCall)
         {
         {
-            var currentBase = state.GetCurrentFrame().Base;
+            var currentBase = state.CurrentThread.GetCurrentFrame().Base;
             var stackBuffer = stack.GetBuffer();
             var stackBuffer = stack.GetBuffer();
             stackBuffer.Slice(newBase, argumentCount).CopyTo(stackBuffer.Slice(currentBase, argumentCount));
             stackBuffer.Slice(newBase, argumentCount).CopyTo(stackBuffer.Slice(currentBase, argumentCount));
             newBase = currentBase;
             newBase = currentBase;
@@ -1031,15 +1032,15 @@ public static partial class LuaVirtualMachine
 
 
     static Tracebacks GetTracebacks(LuaState state, Chunk chunk, int pc)
     static Tracebacks GetTracebacks(LuaState state, Chunk chunk, int pc)
     {
     {
-        var frame = state.GetCurrentFrame();
-        state.PushCallStackFrame(frame with
+        var frame = state.CurrentThread.GetCurrentFrame();
+        state.CurrentThread.PushCallStackFrame(frame with
         {
         {
             CallPosition = chunk.SourcePositions[pc],
             CallPosition = chunk.SourcePositions[pc],
             ChunkName = chunk.Name,
             ChunkName = chunk.Name,
             RootChunkName = chunk.GetRoot().Name,
             RootChunkName = chunk.GetRoot().Name,
         });
         });
         var tracebacks = state.GetTracebacks();
         var tracebacks = state.GetTracebacks();
-        state.PopCallStackFrame();
+        state.CurrentThread.PopCallStackFrame();
 
 
         return tracebacks;
         return tracebacks;
     }
     }

+ 10 - 10
src/Lua/Runtime/UpValue.cs

@@ -7,26 +7,26 @@ public sealed class UpValue
 {
 {
     LuaValue value;
     LuaValue value;
 
 
-    public LuaState State { get; }
+    public LuaThread Thread { get; }
     public bool IsClosed { get; private set; }
     public bool IsClosed { get; private set; }
     public int RegisterIndex { get; private set; }
     public int RegisterIndex { get; private set; }
 
 
-    UpValue(LuaState state)
+    UpValue(LuaThread thread)
     {
     {
-        State = state;
+        Thread = thread;
     }
     }
 
 
-    public static UpValue Open(LuaState state, int registerIndex)
+    public static UpValue Open(LuaThread thread, int registerIndex)
     {
     {
-        return new(state)
+        return new(thread)
         {
         {
             RegisterIndex = registerIndex
             RegisterIndex = registerIndex
         };
         };
     }
     }
 
 
-    public static UpValue Closed(LuaState state, LuaValue value)
+    public static UpValue Closed(LuaThread thread, LuaValue value)
     {
     {
-        return new(state)
+        return new(thread)
         {
         {
             IsClosed = true,
             IsClosed = true,
             value = value
             value = value
@@ -42,7 +42,7 @@ public sealed class UpValue
         }
         }
         else
         else
         {
         {
-            return State.Stack.UnsafeGet(RegisterIndex);
+            return Thread.Stack.UnsafeGet(RegisterIndex);
         }
         }
     }
     }
 
 
@@ -55,7 +55,7 @@ public sealed class UpValue
         }
         }
         else
         else
         {
         {
-            State.Stack.UnsafeGet(RegisterIndex) = value;
+            Thread.Stack.UnsafeGet(RegisterIndex) = value;
         }
         }
     }
     }
 
 
@@ -64,7 +64,7 @@ public sealed class UpValue
     {
     {
         if (!IsClosed)
         if (!IsClosed)
         {
         {
-            value = State.Stack.UnsafeGet(RegisterIndex);
+            value = Thread.Stack.UnsafeGet(RegisterIndex);
         }
         }
 
 
         IsClosed = true;
         IsClosed = true;

+ 3 - 2
src/Lua/Standard/Coroutines/CoroutineRunningFunction.cs

@@ -9,7 +9,8 @@ public sealed class CoroutineRunningFunction : LuaFunction
 
 
     protected override ValueTask<int> InvokeAsyncCore(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
     protected override ValueTask<int> InvokeAsyncCore(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
     {
     {
-        buffer.Span[0] = context.State.TryGetCurrentThread(out _);
-        return new(1);
+        buffer.Span[0] = context.State.CurrentThread;
+        buffer.Span[1] = context.State.CurrentThread == context.State.MainThread;
+        return new(2);
     }
     }
 }
 }

+ 1 - 1
src/Lua/Standard/Coroutines/CoroutineStatusFunction.cs

@@ -10,7 +10,7 @@ public sealed class CoroutineStatusFunction : LuaFunction
     protected override ValueTask<int> InvokeAsyncCore(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
     protected override ValueTask<int> InvokeAsyncCore(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
     {
     {
         var thread = context.ReadArgument<LuaThread>(0);
         var thread = context.ReadArgument<LuaThread>(0);
-        buffer.Span[0] = thread.Status switch
+        buffer.Span[0] = thread.GetStatus() switch
         {
         {
             LuaThreadStatus.Normal => "normal",
             LuaThreadStatus.Normal => "normal",
             LuaThreadStatus.Suspended => "suspended",
             LuaThreadStatus.Suspended => "suspended",

+ 1 - 7
src/Lua/Standard/Coroutines/CoroutineYieldFunction.cs

@@ -9,13 +9,7 @@ public sealed class CoroutineYieldFunction : LuaFunction
 
 
     protected override async ValueTask<int> InvokeAsyncCore(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
     protected override async ValueTask<int> InvokeAsyncCore(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
     {
     {
-        if (!context.State.TryGetCurrentThread(out var thread))
-        {
-            throw new LuaRuntimeException(context.State.GetTracebacks(), "attempt to yield from outside a coroutine");
-        }
-
-        await thread.Yield(context, cancellationToken);
-
+        await context.State.CurrentThread.Yield(context, cancellationToken);
         return 0;
         return 0;
     }
     }
 }
 }