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;
 
 var state = LuaState.Create();
-state.OpenBaseLibrary();
+state.OpenBasicLibrary();
 
 try
 {
     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
-
-        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
-}
 
-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");
 

+ 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)
     {
         var state = context.State;
+        var thread = state.CurrentThread;
 
         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,
             ChunkName = context.ChunkName ?? LuaState.DefaultChunkName,
             RootChunkName = context.RootChunkName ?? LuaState.DefaultChunkName,
@@ -20,18 +21,18 @@ public abstract partial class LuaFunction
             Function = this,
         };
 
-        state.PushCallStackFrame(frame);
+        thread.PushCallStackFrame(frame);
         try
         {
             return await InvokeAsyncCore(context, buffer, cancellationToken);
         }
         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
         {
-            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? 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)]
     public LuaValue ReadArgument(int index)
@@ -30,13 +36,14 @@ public readonly record struct LuaFunctionExecutionContext
         var arg = Arguments[index];
         if (!arg.TryRead<T>(out var argValue))
         {
+            var thread = State.CurrentThread;
             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
             {
-                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)
         {
-            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.Runtime;
 
@@ -8,33 +7,28 @@ public sealed class LuaState
 {
     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()
     {
         return new();
@@ -42,12 +36,8 @@ public sealed class 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)
@@ -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)
     {
         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)
     {
-        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()
     {
-        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;
             }
         }
 
-        var newUpValue = UpValue.Open(this, registerIndex);
-        globalState.openUpValues.Add(newUpValue);
+        var newUpValue = UpValue.Open(thread, registerIndex);
+        openUpValues.Add(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++)
         {
             var upValue = openUpValues[i];
-            if (upValue.State != this) continue;
+            if (upValue.Thread != thread) continue;
 
             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()
     {
         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.Runtime;
 
 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)
     {
-        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)
     {
         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
         {

+ 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)
     {
-        var stack = state.Stack;
+        var thread = state.CurrentThread;
+        var stack = thread.Stack;
         var chunk = closure.Proto;
 
         for (var pc = 0; pc < chunk.Instructions.Length; pc++)
@@ -498,7 +499,7 @@ public static partial class LuaVirtualMachine
                     pc += instruction.SBx;
                     if (instruction.A != 0)
                     {
-                        state.CloseUpValues(instruction.A);
+                        state.CloseUpValues(thread, instruction.A);
                     }
                     break;
                 case OpCode.Eq:
@@ -711,7 +712,7 @@ public static partial class LuaVirtualMachine
                     break;
                 case OpCode.TailCall:
                     {
-                        state.CloseUpValues(frame.Base);
+                        state.CloseUpValues(thread, frame.Base);
 
                         var va = stack.UnsafeGet(RA);
                         if (!va.TryRead<LuaFunction>(out var func))
@@ -736,7 +737,7 @@ public static partial class LuaVirtualMachine
                     }
                 case OpCode.Return:
                     {
-                        state.CloseUpValues(frame.Base);
+                        state.CloseUpValues(thread, frame.Base);
 
                         var retCount = instruction.B - 1;
 
@@ -880,7 +881,7 @@ public static partial class LuaVirtualMachine
 #endif
     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);
 
         if (isTable && t.TryGetValue(key, out var result))
@@ -931,7 +932,7 @@ public static partial class LuaVirtualMachine
 #endif
     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);
 
         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)
     {
-        var stack = state.Stack;
+        var stack = state.CurrentThread.Stack;
 
         var argumentCount = instruction.B - 1;
         if (instruction.B == 0)
@@ -991,7 +992,7 @@ public static partial class LuaVirtualMachine
         // Therefore, a call can be made without allocating new registers.
         if (isTailCall)
         {
-            var currentBase = state.GetCurrentFrame().Base;
+            var currentBase = state.CurrentThread.GetCurrentFrame().Base;
             var stackBuffer = stack.GetBuffer();
             stackBuffer.Slice(newBase, argumentCount).CopyTo(stackBuffer.Slice(currentBase, argumentCount));
             newBase = currentBase;
@@ -1031,15 +1032,15 @@ public static partial class LuaVirtualMachine
 
     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],
             ChunkName = chunk.Name,
             RootChunkName = chunk.GetRoot().Name,
         });
         var tracebacks = state.GetTracebacks();
-        state.PopCallStackFrame();
+        state.CurrentThread.PopCallStackFrame();
 
         return tracebacks;
     }

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

@@ -7,26 +7,26 @@ public sealed class UpValue
 {
     LuaValue value;
 
-    public LuaState State { get; }
+    public LuaThread Thread { get; }
     public bool IsClosed { 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
         };
     }
 
-    public static UpValue Closed(LuaState state, LuaValue value)
+    public static UpValue Closed(LuaThread thread, LuaValue value)
     {
-        return new(state)
+        return new(thread)
         {
             IsClosed = true,
             value = value
@@ -42,7 +42,7 @@ public sealed class UpValue
         }
         else
         {
-            return State.Stack.UnsafeGet(RegisterIndex);
+            return Thread.Stack.UnsafeGet(RegisterIndex);
         }
     }
 
@@ -55,7 +55,7 @@ public sealed class UpValue
         }
         else
         {
-            State.Stack.UnsafeGet(RegisterIndex) = value;
+            Thread.Stack.UnsafeGet(RegisterIndex) = value;
         }
     }
 
@@ -64,7 +64,7 @@ public sealed class UpValue
     {
         if (!IsClosed)
         {
-            value = State.Stack.UnsafeGet(RegisterIndex);
+            value = Thread.Stack.UnsafeGet(RegisterIndex);
         }
 
         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)
     {
-        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)
     {
         var thread = context.ReadArgument<LuaThread>(0);
-        buffer.Span[0] = thread.Status switch
+        buffer.Span[0] = thread.GetStatus() switch
         {
             LuaThreadStatus.Normal => "normal",
             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)
     {
-        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;
     }
 }