Browse Source

make apis LuaThread Centered

Akeit0 7 months ago
parent
commit
168994dc18
47 changed files with 1210 additions and 708 deletions
  1. 7 0
      Lua.sln
  2. 479 0
      sanbox/ConsoleApp2/.gitignore
  3. 16 0
      sanbox/ConsoleApp2/ConsoleApp2.csproj
  4. 62 0
      sanbox/ConsoleApp2/Program.cs
  5. 1 1
      sandbox/Benchmark/InterpreterSteps.cs
  6. 17 17
      sandbox/Benchmark/NBodyBenchmark.cs
  7. 3 3
      src/Lua.SourceGenerator/LuaObjectGenerator.Emit.cs
  8. 7 13
      src/Lua/CodeAnalysis/Compilation/Declarements.cs
  9. 2 2
      src/Lua/CodeAnalysis/Compilation/Dump.cs
  10. 2 1
      src/Lua/CodeAnalysis/Compilation/Scanner.cs
  11. 2 2
      src/Lua/Exceptions.cs
  12. 1 1
      src/Lua/Internal/Constants.cs
  13. 5 5
      src/Lua/Internal/LuaDebug.cs
  14. 168 21
      src/Lua/LuaCoroutine.cs
  15. 4 3
      src/Lua/LuaFunction.cs
  16. 14 14
      src/Lua/LuaFunctionExecutionContext.cs
  17. 21 21
      src/Lua/LuaFunctionExtensions.cs
  18. 5 14
      src/Lua/LuaMainThread.cs
  19. 16 64
      src/Lua/LuaState.cs
  20. 5 5
      src/Lua/LuaStateExtensions.cs
  21. 67 66
      src/Lua/LuaThread.cs
  22. 15 32
      src/Lua/LuaThreadExtensions.cs
  23. 34 0
      src/Lua/LuaUserThread.cs
  24. 2 2
      src/Lua/LuaValue.cs
  25. 43 199
      src/Lua/Runtime/Instruction.cs
  26. 22 0
      src/Lua/Runtime/Lease.cs
  27. 6 6
      src/Lua/Runtime/LuaClosure.cs
  28. 12 2
      src/Lua/Runtime/LuaStack.cs
  29. 6 40
      src/Lua/Runtime/LuaVirtualMachine.Debug.cs
  30. 40 45
      src/Lua/Runtime/LuaVirtualMachine.cs
  31. 4 3
      src/Lua/Runtime/Prototype.cs
  32. 2 4
      src/Lua/Runtime/Tracebacks.cs
  33. 12 12
      src/Lua/Standard/BasicLibrary.cs
  34. 28 28
      src/Lua/Standard/BitwiseLibrary.cs
  35. 3 3
      src/Lua/Standard/CoroutineLibrary.cs
  36. 6 12
      src/Lua/Standard/DebugLibrary.cs
  37. 3 3
      src/Lua/Standard/FileHandle.cs
  38. 5 5
      src/Lua/Standard/IOLibrary.cs
  39. 4 4
      src/Lua/Standard/Internal/Bit32Helper.cs
  40. 12 12
      src/Lua/Standard/Internal/DateTimeHelper.cs
  41. 10 9
      src/Lua/Standard/Internal/IOHelper.cs
  42. 3 3
      src/Lua/Standard/OperatingSystemLibrary.cs
  43. 23 23
      src/Lua/Standard/StringLibrary.cs
  44. 8 7
      src/Lua/Standard/TableLibrary.cs
  45. 1 0
      tests/Lua.Tests/LuaObjectTests.cs
  46. 2 0
      tests/Lua.Tests/MetatableTests.cs
  47. 0 1
      tests/Lua.Tests/StringTests.cs

+ 7 - 0
Lua.sln

@@ -19,6 +19,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Benchmark", "sandbox\Benchm
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lua.SourceGenerator", "src\Lua.SourceGenerator\Lua.SourceGenerator.csproj", "{C4BB264C-4D37-4E2D-99FD-4918CE22D7E4}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleApp2", "sanbox\ConsoleApp2\ConsoleApp2.csproj", "{EAEC81EE-2443-4209-AD43-767875280BA1}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -48,6 +50,10 @@ Global
 		{C4BB264C-4D37-4E2D-99FD-4918CE22D7E4}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{C4BB264C-4D37-4E2D-99FD-4918CE22D7E4}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{C4BB264C-4D37-4E2D-99FD-4918CE22D7E4}.Release|Any CPU.Build.0 = Release|Any CPU
+		{EAEC81EE-2443-4209-AD43-767875280BA1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{EAEC81EE-2443-4209-AD43-767875280BA1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{EAEC81EE-2443-4209-AD43-767875280BA1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{EAEC81EE-2443-4209-AD43-767875280BA1}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(NestedProjects) = preSolution
 		{6E33BFBC-E51F-493E-9AF0-30C1100F5B5D} = {18A64E25-9557-457B-80AE-A6EFE853118D}
@@ -55,5 +61,6 @@ Global
 		{718A361C-AAF3-45A4-84D4-8C4FB6BB374E} = {33883F28-679F-48AD-8E64-3515C7BDAF5A}
 		{FC157C29-8AAE-49C8-9536-208E3F0698DA} = {33883F28-679F-48AD-8E64-3515C7BDAF5A}
 		{C4BB264C-4D37-4E2D-99FD-4918CE22D7E4} = {18A64E25-9557-457B-80AE-A6EFE853118D}
+		{EAEC81EE-2443-4209-AD43-767875280BA1} = {33883F28-679F-48AD-8E64-3515C7BDAF5A}
 	EndGlobalSection
 EndGlobal

+ 479 - 0
sanbox/ConsoleApp2/.gitignore

@@ -0,0 +1,479 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from `dotnet new gitignore`
+
+# dotenv files
+.env
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Mono auto generated files
+mono_crash.*
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Ww][Ii][Nn]32/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+[Ll]ogs/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUnit
+*.VisualState.xml
+TestResult.xml
+nunit-*.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# Tye
+.tye/
+
+# ASP.NET Scaffolding
+ScaffoldingReadMe.txt
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.log
+*.tlog
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Coverlet is a free, cross platform Code Coverage Tool
+coverage*.json
+coverage*.xml
+coverage*.info
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# NuGet Symbol Packages
+*.snupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+*.appxbundle
+*.appxupload
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio 6 auto-generated project file (contains which files were open etc.)
+*.vbp
+
+# Visual Studio 6 workspace and project file (working project files containing files to include in project)
+*.dsw
+*.dsp
+
+# Visual Studio 6 technical files
+*.ncb
+*.aps
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# Visual Studio History (VSHistory) files
+.vshistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
+# Ionide (cross platform F# VS Code tools) working folder
+.ionide/
+
+# Fody - auto-generated XML schema
+FodyWeavers.xsd
+
+# VS Code files for those working on multiple tools
+.vscode/
+
+# Local History for Visual Studio Code
+.history/
+
+# Windows Installer files from build outputs
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# JetBrains Rider
+*.sln.iml
+.idea
+
+##
+## Visual studio for Mac
+##
+
+
+# globs
+Makefile.in
+*.userprefs
+*.usertasks
+config.make
+config.status
+aclocal.m4
+install-sh
+autom4te.cache/
+*.tar.gz
+tarballs/
+test-results/
+
+# Mac bundle stuff
+*.dmg
+*.app
+
+# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
+# General
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
+# Windows thumbnail cache files
+Thumbs.db
+ehthumbs.db
+ehthumbs_vista.db
+
+# Dump file
+*.stackdump
+
+# Folder config file
+[Dd]esktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
+
+# Vim temporary swap files
+*.swp

+ 16 - 0
sanbox/ConsoleApp2/ConsoleApp2.csproj

@@ -0,0 +1,16 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+    <PropertyGroup>
+        <OutputType>Exe</OutputType>
+        <TargetFramework>net9.0</TargetFramework>
+        <LangVersion>13</LangVersion>
+        <ImplicitUsings>enable</ImplicitUsings>
+        <Nullable>enable</Nullable>
+    </PropertyGroup>
+
+    <ItemGroup>
+      <ProjectReference Include="..\..\src\Lua.SourceGenerator\Lua.SourceGenerator.csproj" />
+      <ProjectReference Include="..\..\src\Lua\Lua.csproj" />
+    </ItemGroup>
+
+</Project>

+ 62 - 0
sanbox/ConsoleApp2/Program.cs

@@ -0,0 +1,62 @@
+using Lua.Runtime;
+using Lua;
+using Lua.Standard;
+
+var state = LuaState.Create();
+state.OpenStandardLibraries();
+{
+    var closure = state.Compile("return function (a,b,...)  print('a : '..a..' b :'..'args : ',...) end", "simple");
+    using var threadLease = state.MainThread.RentUseThread();
+    var thread = threadLease.Thread;
+    {
+        var results = await thread.RunAsync(closure, 0);
+        for (int i = 0; i < results.Length; i++)
+        {
+            Console.WriteLine(results[i]);
+        }
+
+        var f = results[0].Read<LuaClosure>();
+        results.Dispose();
+        thread.Push("hello", "world", 1, 2, 3);
+        var result2 = await thread.RunAsync(f, 5);
+        for (int i = 0; i < result2.Length; i++)
+        {
+            Console.WriteLine(result2[i]);
+        }
+
+        result2.Dispose();
+    }
+}
+
+{
+    var results = await state.DoStringAsync(
+        """
+        return function (...)  
+            local args = {...}
+            for i = 1, #args do
+                local v = args[i]
+                print('To Lua:\t' .. coroutine.yield('from C# ' .. i ..' '..v))
+            end
+        end
+        """, "coroutine");
+    var f = results[0].Read<LuaClosure>();
+    using var coroutineLease = state.MainThread.RentCoroutine(f);
+    var coroutine = coroutineLease.Thread;
+    {
+        coroutine.Push("a", "b", "c", "d", "e");
+
+        for (int i = 0; coroutine.CanResume; i++)
+        {
+            if (i != 0) coroutine.Push($"from C# {i}");
+            using var resumeResult = await coroutine.ResumeAsync();
+            Console.Write("To C#:\t");
+            for (int j = 0; j < resumeResult.Length; j++)
+            {
+                Console.Write(resumeResult[j]);
+                Console.Write('\t');
+            }
+
+            Console.WriteLine();
+        }
+    }
+}

+ 1 - 1
sandbox/Benchmark/InterpreterSteps.cs

@@ -17,7 +17,7 @@ public class InterpreterSteps
         sourceText = File.ReadAllText(filePath);
         state = LuaState.Create();
         state.OpenStandardLibraries();
-        closure = state.Compile(sourceText,sourceText);
+        closure = state.Compile(sourceText, sourceText);
     }
 
     [IterationSetup]

+ 17 - 17
sandbox/Benchmark/NBodyBenchmark.cs

@@ -31,23 +31,23 @@ public class NBodyBenchmark
         return core.MoonSharpState.DoString(core.SourceText);
     }
 
-    [Benchmark(Description = "MoonSharp (RunFile)")]
-    public DynValue Benchmark_MoonSharp_File()
-    {
-        return core.MoonSharpState.DoFile(core.FilePath);
-    }
+    // [Benchmark(Description = "MoonSharp (RunFile)")]
+    // public DynValue Benchmark_MoonSharp_File()
+    // {
+    //     return core.MoonSharpState.DoFile(core.FilePath);
+    // }
 
-    [Benchmark(Description = "NLua (DoString)")]
+    [Benchmark(Description = "NLua (DoString)", Baseline = true)]
     public object[] Benchmark_NLua_String()
     {
         return core.NLuaState.DoString(core.SourceText);
     }
 
-    [Benchmark(Description = "NLua (DoFile)")]
-    public object[] Benchmark_NLua_File()
-    {
-        return core.NLuaState.DoFile(core.FilePath);
-    }
+    // [Benchmark(Description = "NLua (DoFile)")]
+    // public object[] Benchmark_NLua_File()
+    // {
+    //     return core.NLuaState.DoFile(core.FilePath);
+    // }
 
     [Benchmark(Description = "Lua-CSharp (DoString)")]
     public async Task<LuaValue> Benchmark_LuaCSharp_String()
@@ -56,10 +56,10 @@ public class NBodyBenchmark
         return buffer[0];
     }
 
-    [Benchmark(Description = "Lua-CSharp (DoFileAsync)")]
-    public async Task<LuaValue> Benchmark_LuaCSharp_File()
-    {
-        await core.LuaCSharpState.DoFileAsync(core.FilePath, buffer);
-        return buffer[0];
-    }
+    // [Benchmark(Description = "Lua-CSharp (DoFileAsync)")]
+    // public async Task<LuaValue> Benchmark_LuaCSharp_File()
+    // {
+    //     await core.LuaCSharpState.DoFileAsync(core.FilePath, buffer);
+    //     return buffer[0];
+    // }
 }

+ 3 - 3
src/Lua.SourceGenerator/LuaObjectGenerator.Emit.cs

@@ -262,7 +262,7 @@ partial class LuaObjectGenerator
                     {
                         if (propertyMetadata.IsReadOnly)
                         {
-                            builder.AppendLine($@"throw new global::Lua.LuaRuntimeException(context.State.GetTraceback(), $""'{{key}}' cannot overwrite."");");
+                            builder.AppendLine($@"throw new global::Lua.LuaRuntimeException(context.Thread.GetTraceback(), $""'{{key}}' cannot overwrite."");");
                         }
                         else if (propertyMetadata.IsStatic)
                         {
@@ -284,7 +284,7 @@ partial class LuaObjectGenerator
 
                     using (builder.BeginIndentScope())
                     {
-                        builder.AppendLine($@"throw new global::Lua.LuaRuntimeException(context.State.GetTraceback(), $""'{{key}}' cannot overwrite."");");
+                        builder.AppendLine($@"throw new global::Lua.LuaRuntimeException(context.Thread.GetTraceback(), $""'{{key}}' cannot overwrite."");");
                     }
                 }
 
@@ -292,7 +292,7 @@ partial class LuaObjectGenerator
 
                 using (builder.BeginIndentScope())
                 {
-                    builder.AppendLine(@$"throw new global::Lua.LuaRuntimeException(context.State.GetTraceback(), $""'{{key}}' not found."");");
+                    builder.AppendLine(@$"throw new global::Lua.LuaRuntimeException(context.Thread.GetTraceback(), $""'{{key}}' not found."");");
                 }
             }
 

+ 7 - 13
src/Lua/CodeAnalysis/Compilation/Declarements.cs

@@ -3,8 +3,7 @@ using System.Runtime.CompilerServices;
 
 namespace Lua.CodeAnalysis.Compilation;
 
-
-unsafe struct TextReader (char* Ptr, int Length)
+unsafe struct TextReader(char* Ptr, int Length)
 {
     public int Position;
 
@@ -29,10 +28,10 @@ unsafe struct TextReader (char* Ptr, int Length)
     public char Current => Ptr[Position];
 }
 
-internal  unsafe struct AssignmentTarget (ref AssignmentTarget previous, ExprDesc exprDesc)
+internal unsafe struct AssignmentTarget(ref AssignmentTarget previous, ExprDesc exprDesc)
 {
     public readonly AssignmentTarget* Previous = (AssignmentTarget*)Unsafe.AsPointer(ref previous);
-    public  ExprDesc Description = exprDesc;
+    public ExprDesc Description = exprDesc;
 }
 
 internal struct Label
@@ -42,14 +41,13 @@ internal struct Label
     public int ActiveVariableCount;
 }
 
-
 internal class Block : IPoolNode<Block>
 {
     public Block? Previous;
     public int FirstLabel, FirstGoto;
     public int ActiveVariableCount;
     public bool HasUpValue, IsLoop;
-    Block(){}
+    Block() { }
     ref Block? IPoolNode<Block>.NextNode => ref Previous;
 
     static LinkedPool<Block> pool;
@@ -67,7 +65,7 @@ internal class Block : IPoolNode<Block>
         block.ActiveVariableCount = activeVariableCount;
         block.HasUpValue = hasUpValue;
         block.IsLoop = isLoop;
-        
+
 
         return block;
     }
@@ -79,8 +77,7 @@ internal class Block : IPoolNode<Block>
     }
 }
 
-
-internal  struct ExprDesc
+internal struct ExprDesc
 {
     public Kind Kind;
     public int Index;
@@ -98,8 +95,6 @@ internal  struct ExprDesc
     public readonly bool HasMultipleReturns() => Kind == Kind.Call || Kind == Kind.VarArg;
 }
 
-
-
 internal enum Kind
 {
     Void = 0,
@@ -116,5 +111,4 @@ internal enum Kind
     Relocatable = 11,
     Call = 12,
     VarArg = 13
-}
-
+}

+ 2 - 2
src/Lua/CodeAnalysis/Compilation/Dump.cs

@@ -267,7 +267,7 @@ internal unsafe ref struct DumpState(IBufferWriter<byte> writer, bool reversedEn
     }
 }
 
-internal unsafe ref struct UnDumpState(ReadOnlySpan<byte> span,ReadOnlySpan<char> name)
+internal unsafe ref struct UnDumpState(ReadOnlySpan<byte> span, ReadOnlySpan<char> name)
 {
     public ReadOnlySpan<byte> Unread = span;
     bool otherEndian;
@@ -320,7 +320,7 @@ internal unsafe ref struct UnDumpState(ReadOnlySpan<byte> span,ReadOnlySpan<char
         Read(span);
 
         if (otherEndian) i = BinaryPrimitives.ReverseEndianness(i);
-        
+
         return i;
     }
 

+ 2 - 1
src/Lua/CodeAnalysis/Compilation/Scanner.cs

@@ -214,7 +214,7 @@ internal struct Scanner
                         SaveAndAdvance();
                         if (!comment)
                         {
-                            var s = Buffer.ToString(2 + sep, Buffer.Length - (4 + 2*sep));
+                            var s = Buffer.ToString(2 + sep, Buffer.Length - (4 + 2 * sep));
                             Buffer.Clear();
                             return s;
                         }
@@ -424,6 +424,7 @@ internal struct Scanner
 
             Save(r);
         }
+
         Save('\'');
         Token.S = Buffer.ToString();
         Buffer.Clear();

+ 2 - 2
src/Lua/Exceptions.cs

@@ -71,11 +71,11 @@ public class LuaRuntimeException : LuaException
         throw new LuaRuntimeException(traceback, $"bad argument #{argumentId} to '{functionName}' (number has no integer representation)");
     }
 
-    public static void ThrowBadArgumentIfNumberIsNotInteger(LuaState state, string functionName, int argumentId, double value)
+    public static void ThrowBadArgumentIfNumberIsNotInteger(LuaThread thread, string functionName, int argumentId, double value)
     {
         if (!MathEx.IsInteger(value))
         {
-            BadArgumentNumberIsNotInteger(state.GetTraceback(), argumentId, functionName);
+            BadArgumentNumberIsNotInteger(thread.GetTraceback(), argumentId, functionName);
         }
     }
 

+ 1 - 1
src/Lua/Internal/Constants.cs

@@ -1,6 +1,6 @@
 namespace Lua.Internal;
 
- internal class Constants
+internal class Constants
 {
     public const int VersionMajor = 5;
     public const int VersionMinor = 2;

+ 5 - 5
src/Lua/Internal/LuaDebug.cs

@@ -281,7 +281,7 @@ internal readonly struct LuaDebug : IDisposable
                 Source = p.ChunkName;
                 LineDefined = p.LineDefined;
                 LastLineDefined = p.LastLineDefined;
-                What = (LineDefined==0) ? "main" : "Lua";
+                What = (LineDefined == 0) ? "main" : "Lua";
             }
 
             ShortSourceLength = WriteShortSource(Source, ShortSource);
@@ -292,11 +292,11 @@ internal readonly struct LuaDebug : IDisposable
     internal static string? GetLocalName(Prototype prototype, int register, int pc)
     {
         var locals = prototype.LocalVariables;
-        var localId = register+1;
+        var localId = register + 1;
         foreach (var l in locals)
         {
-            if(pc<l.StartPc)break;
-            if(l.EndPc<=pc)continue;
+            if (pc < l.StartPc) break;
+            if (l.EndPc <= pc) continue;
             localId--;
             if (localId == 0)
             {
@@ -381,7 +381,7 @@ internal readonly struct LuaDebug : IDisposable
         if (c >= 256)
         {
             /* is 'c' a constant? */
-             var kvalue =  p.Constants[c - 256];
+            var kvalue = p.Constants[c - 256];
             if (kvalue.TryReadString(out name))
             {
                 /* literal constant? */

+ 168 - 21
src/Lua/LuaCoroutine.cs

@@ -1,12 +1,37 @@
-using System.Buffers;
 using System.Threading.Tasks.Sources;
 using Lua.Internal;
 using Lua.Runtime;
 
 namespace Lua;
 
-public sealed class LuaCoroutine : LuaThread, IValueTaskSource<LuaCoroutine.YieldContext>, IValueTaskSource<LuaCoroutine.ResumeContext>
+public sealed class LuaCoroutine : LuaThread, IValueTaskSource<LuaCoroutine.YieldContext>, IValueTaskSource<LuaCoroutine.ResumeContext>, IPoolNode<LuaCoroutine>
 {
+    static LinkedPool<LuaCoroutine> pool;
+    LuaCoroutine? nextNode;
+    ref LuaCoroutine? IPoolNode<LuaCoroutine>.NextNode => ref nextNode;
+
+    public static LuaCoroutine Create(LuaThread parent, LuaFunction function, bool isProtectedMode)
+    {
+        if (!pool.TryPop(out LuaCoroutine result))
+        {
+            result = new ();
+        }
+
+        result.Init(parent, function, isProtectedMode);
+        return result;
+    }
+
+    public void Release()
+    {
+        if (CoreData != null && CoreData.CallStack.Count != 0)
+        {
+            throw new InvalidOperationException("This thread is running! Call stack is not empty!!");
+        }
+
+        ReleaseCore();
+        pool.TryPush(this);
+    }
+
     struct YieldContext
     {
         public required LuaValue[] Results;
@@ -20,15 +45,15 @@ public sealed class LuaCoroutine : LuaThread, IValueTaskSource<LuaCoroutine.Yiel
     byte status;
     bool isFirstCall = true;
     ValueTask<int> functionTask;
-    int returnFrameBase;
 
     ManualResetValueTaskSourceCore<ResumeContext> resume;
     ManualResetValueTaskSourceCore<YieldContext> yield;
     Traceback? traceback;
-    internal int ReturnFrameBase => returnFrameBase;
 
-    public LuaCoroutine(LuaFunction function, bool isProtectedMode)
+    internal void Init(LuaThread parent, LuaFunction function, bool isProtectedMode)
     {
+        CoreData = ThreadCoreData.Create();
+        State = parent.State;
         IsProtectedMode = isProtectedMode;
         Function = function;
     }
@@ -40,10 +65,131 @@ public sealed class LuaCoroutine : LuaThread, IValueTaskSource<LuaCoroutine.Yiel
         this.status = (byte)status;
     }
 
-    public bool IsProtectedMode { get; }
-    public LuaFunction Function { get; }
+    public bool IsProtectedMode { get; private set; }
+    public LuaFunction Function { get; private set; }
     internal Traceback? LuaTraceback => traceback;
 
+    public bool CanResume => status == (byte)LuaThreadStatus.Suspended;
+
+    int lastBase;
+
+    private LuaStack? stackForDead;
+
+    LuaStack GetStackForDead(int capacity)
+    {
+        stackForDead ??= new LuaStack(capacity);
+        stackForDead.Clear();
+        return stackForDead;
+    }
+
+    public async ValueTask<LuaResult> ResumeAsync(CancellationToken cancellationToken = default)
+    {
+        switch ((LuaThreadStatus)Volatile.Read(ref status))
+        {
+            case LuaThreadStatus.Suspended:
+                Volatile.Write(ref status, (byte)LuaThreadStatus.Running);
+
+                if (!isFirstCall)
+                {
+                    yield.SetResult(new()
+                    {
+                        Results = lastBase == Stack.Count
+                            ? []
+                            : Stack.AsSpan()[lastBase..].ToArray()
+                    });
+                    Stack.PopUntil(lastBase);
+                }
+
+                break;
+            case LuaThreadStatus.Normal:
+            case LuaThreadStatus.Running:
+                if (IsProtectedMode)
+                {
+                    Stack.PopUntil(lastBase);
+                    Stack.Push("cannot resume non-suspended coroutine");
+                    return new LuaResult(Stack, lastBase);
+                }
+                else
+                {
+                    throw new LuaException("cannot resume non-suspended coroutine");
+                }
+            case LuaThreadStatus.Dead:
+                if (IsProtectedMode)
+                {
+                    var stack = GetStackForDead(1);
+                    stack.Push("cannot resume dead coroutine");
+                    return new LuaResult(stack, 0);
+                }
+                else
+                {
+                    throw new LuaException("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
+        {
+            if (isFirstCall)
+            {
+                functionTask = Function.InvokeAsync(new() { Thread = this, ArgumentCount = Stack.Count, ReturnFrameBase = 0 }, cancellationToken).Preserve();
+
+                Volatile.Write(ref isFirstCall, false);
+            }
+
+            var (index, result0, result1) = await ValueTaskEx.WhenAny(resumeTask, functionTask!);
+
+            if (index == 0)
+            {
+                var results = result0.Results;
+                Stack.PushRange(results.AsSpan());
+                lastBase = Stack.Count - results.Length;
+                return new LuaResult(Stack, Stack.Count - results.Length);
+                ;
+            }
+            else
+            {
+                Volatile.Write(ref status, (byte)LuaThreadStatus.Dead);
+                var results = Stack.AsSpan();
+                var stack = GetStackForDead(Math.Max(1, results.Length));
+                stack.PushRange(results);
+                ReleaseCore();
+                return new LuaResult(stack, 0);
+            }
+        }
+        catch (Exception ex) when (ex is not OperationCanceledException)
+        {
+            if (IsProtectedMode)
+            {
+                traceback = (ex as LuaRuntimeException)?.LuaTraceback;
+                Volatile.Write(ref status, (byte)LuaThreadStatus.Dead);
+                ReleaseCore();
+                var stack = GetStackForDead(1);
+                stack.Push(ex is LuaRuntimeException luaEx ? luaEx.ErrorObject : ex.Message);
+                return new LuaResult(stack, 0);
+            }
+            else
+            {
+                throw;
+            }
+        }
+        finally
+        {
+            registration.Dispose();
+            resume.Reset();
+        }
+    }
+
     public override async ValueTask<int> ResumeAsync(LuaFunctionExecutionContext context, CancellationToken cancellationToken = default)
     {
         var baseThread = context.Thread;
@@ -76,7 +222,7 @@ public sealed class LuaCoroutine : LuaThread, IValueTaskSource<LuaCoroutine.Yiel
                     }
                     else
                     {
-                        throw new LuaRuntimeException(context.State.GetTraceback(), "cannot resume non-suspended coroutine");
+                        throw new LuaRuntimeException(context.Thread.GetTraceback(), "cannot resume non-suspended coroutine");
                     }
                 case LuaThreadStatus.Dead:
                     if (IsProtectedMode)
@@ -85,7 +231,7 @@ public sealed class LuaCoroutine : LuaThread, IValueTaskSource<LuaCoroutine.Yiel
                     }
                     else
                     {
-                        throw new LuaRuntimeException(context.State.GetTraceback(), "cannot resume dead coroutine");
+                        throw new LuaRuntimeException(context.Thread.GetTraceback(), "cannot resume dead coroutine");
                     }
             }
 
@@ -105,15 +251,8 @@ public sealed class LuaCoroutine : LuaThread, IValueTaskSource<LuaCoroutine.Yiel
             {
                 if (isFirstCall)
                 {
-                    returnFrameBase = Stack.Count;
                     Stack.PushRange(context.Arguments);
-                    functionTask = Function.InvokeAsync(new()
-                    {
-                        State = context.State,
-                        Thread = this,
-                        ArgumentCount = Stack.Count-returnFrameBase,
-                        ReturnFrameBase = returnFrameBase
-                    }, cancellationToken).Preserve();
+                    functionTask = Function.InvokeAsync(new() { Thread = this, ArgumentCount = Stack.Count, ReturnFrameBase = 0 }, cancellationToken).Preserve();
 
                     Volatile.Write(ref isFirstCall, false);
                 }
@@ -128,8 +267,8 @@ public sealed class LuaCoroutine : LuaThread, IValueTaskSource<LuaCoroutine.Yiel
                 else
                 {
                     Volatile.Write(ref status, (byte)LuaThreadStatus.Dead);
-                    var count = context.Return(true, Stack.AsSpan()[returnFrameBase..]);
-                    Stack.PopUntil(returnFrameBase);
+                    var count = context.Return(true, Stack.AsSpan());
+                    ReleaseCore();
                     return count;
                 }
             }
@@ -139,6 +278,7 @@ public sealed class LuaCoroutine : LuaThread, IValueTaskSource<LuaCoroutine.Yiel
                 {
                     traceback = (ex as LuaRuntimeException)?.LuaTraceback;
                     Volatile.Write(ref status, (byte)LuaThreadStatus.Dead);
+                    ReleaseCore();
                     return context.Return(false, ex is LuaRuntimeException luaEx ? luaEx.ErrorObject : ex.Message);
                 }
                 else
@@ -163,12 +303,12 @@ public sealed class LuaCoroutine : LuaThread, IValueTaskSource<LuaCoroutine.Yiel
     {
         if (Volatile.Read(ref status) != (byte)LuaThreadStatus.Running)
         {
-            throw new LuaRuntimeException(context.State.GetTraceback(), "cannot call yield on a coroutine that is not currently running");
+            throw new LuaRuntimeException(context.Thread.GetTraceback(), "cannot call yield on a coroutine that is not currently running");
         }
 
         if (context.Thread.GetCallStackFrames()[^2].Function is not LuaClosure)
         {
-            throw new LuaRuntimeException(context.State.GetTraceback(), "attempt to yield across a C#-call boundary");
+            throw new LuaRuntimeException(context.Thread.GetTraceback(), "attempt to yield across a C#-call boundary");
         }
 
         resume.SetResult(new() { Results = context.Arguments.ToArray(), });
@@ -232,4 +372,11 @@ public sealed class LuaCoroutine : LuaThread, IValueTaskSource<LuaCoroutine.Yiel
     {
         resume.OnCompleted(continuation, state, token, flags);
     }
+
+    void ReleaseCore()
+    {
+        // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
+        CoreData?.Release();
+        CoreData = null!;
+    }
 }

+ 4 - 3
src/Lua/LuaFunction.cs

@@ -16,10 +16,11 @@ public class LuaFunction(string name, Func<LuaFunctionExecutionContext, Cancella
         var varArgumentCount = this.GetVariableArgumentCount(context.ArgumentCount);
         if (varArgumentCount != 0)
         {
-           LuaVirtualMachine.PrepareVariableArgument(context.Thread.Stack,context.ArgumentCount, varArgumentCount);
-           context =context with{ArgumentCount = context.ArgumentCount- varArgumentCount};
+            LuaVirtualMachine.PrepareVariableArgument(context.Thread.Stack, context.ArgumentCount, varArgumentCount);
+            context = context with { ArgumentCount = context.ArgumentCount - varArgumentCount };
         }
-        var frame = new CallStackFrame { Base = context.FrameBase , VariableArgumentCount = varArgumentCount, Function = this, ReturnBase = context.ReturnFrameBase };
+
+        var frame = new CallStackFrame { Base = context.FrameBase, VariableArgumentCount = varArgumentCount, Function = this, ReturnBase = context.ReturnFrameBase };
         context.Thread.PushCallStackFrame(frame);
         try
         {

+ 14 - 14
src/Lua/LuaFunctionExecutionContext.cs

@@ -7,10 +7,10 @@ namespace Lua;
 [StructLayout(LayoutKind.Auto)]
 public readonly record struct LuaFunctionExecutionContext
 {
-    public required LuaState State { get; init; }
+    public LuaState State => Thread.State;
     public required LuaThread Thread { get; init; }
     public required int ArgumentCount { get; init; }
-    public  int FrameBase => Thread.Stack.Count-ArgumentCount;
+    public int FrameBase => Thread.Stack.Count - ArgumentCount;
     public required int ReturnFrameBase { get; init; }
     public int? SourceLine { get; init; }
     public int? CallerInstructionIndex { get; init; }
@@ -20,8 +20,8 @@ public readonly record struct LuaFunctionExecutionContext
     {
         get
         {
-           var stack = Thread.Stack.AsSpan(); 
-            return stack.Slice(stack.Length-ArgumentCount);
+            var stack = Thread.Stack.AsSpan();
+            return stack.Slice(stack.Length - ArgumentCount);
         }
     }
 
@@ -60,19 +60,19 @@ public readonly record struct LuaFunctionExecutionContext
             var t = typeof(T);
             if ((t == typeof(int) || t == typeof(long)) && arg.TryReadNumber(out _))
             {
-                LuaRuntimeException.BadArgumentNumberIsNotInteger(State.GetTraceback(), index + 1, Thread.GetCurrentFrame().Function.Name);
+                LuaRuntimeException.BadArgumentNumberIsNotInteger(Thread.GetTraceback(), index + 1, Thread.GetCurrentFrame().Function.Name);
             }
             else if (LuaValue.TryGetLuaValueType(t, out var type))
             {
-                LuaRuntimeException.BadArgument(State.GetTraceback(), index + 1, Thread.GetCurrentFrame().Function.Name, type.ToString(), arg.Type.ToString());
+                LuaRuntimeException.BadArgument(Thread.GetTraceback(), index + 1, Thread.GetCurrentFrame().Function.Name, type.ToString(), arg.Type.ToString());
             }
             else if (arg.Type is LuaValueType.UserData or LuaValueType.LightUserData)
             {
-                LuaRuntimeException.BadArgument(State.GetTraceback(), index + 1, Thread.GetCurrentFrame().Function.Name, t.Name, arg.UnsafeRead<object>()?.GetType().ToString() ?? "userdata: 0");
+                LuaRuntimeException.BadArgument(Thread.GetTraceback(), index + 1, Thread.GetCurrentFrame().Function.Name, t.Name, arg.UnsafeRead<object>()?.GetType().ToString() ?? "userdata: 0");
             }
             else
             {
-                LuaRuntimeException.BadArgument(State.GetTraceback(), index + 1, Thread.GetCurrentFrame().Function.Name, t.Name, arg.Type.ToString());
+                LuaRuntimeException.BadArgument(Thread.GetTraceback(), index + 1, Thread.GetCurrentFrame().Function.Name, t.Name, arg.Type.ToString());
             }
         }
 
@@ -99,19 +99,19 @@ public readonly record struct LuaFunctionExecutionContext
             var t = typeof(T);
             if ((t == typeof(int) || t == typeof(long)) && arg.TryReadNumber(out _))
             {
-                LuaRuntimeException.BadArgumentNumberIsNotInteger(State.GetTraceback(), index + 1, Thread.GetCurrentFrame().Function.Name);
+                LuaRuntimeException.BadArgumentNumberIsNotInteger(Thread.GetTraceback(), index + 1, Thread.GetCurrentFrame().Function.Name);
             }
             else if (LuaValue.TryGetLuaValueType(t, out var type))
             {
-                LuaRuntimeException.BadArgument(State.GetTraceback(), index + 1, Thread.GetCurrentFrame().Function.Name, type.ToString(), arg.Type.ToString());
+                LuaRuntimeException.BadArgument(Thread.GetTraceback(), index + 1, Thread.GetCurrentFrame().Function.Name, type.ToString(), arg.Type.ToString());
             }
             else if (arg.Type is LuaValueType.UserData or LuaValueType.LightUserData)
             {
-                LuaRuntimeException.BadArgument(State.GetTraceback(), index + 1, Thread.GetCurrentFrame().Function.Name, t.Name, arg.UnsafeRead<object>()?.GetType().ToString() ?? "userdata: 0");
+                LuaRuntimeException.BadArgument(Thread.GetTraceback(), index + 1, Thread.GetCurrentFrame().Function.Name, t.Name, arg.UnsafeRead<object>()?.GetType().ToString() ?? "userdata: 0");
             }
             else
             {
-                LuaRuntimeException.BadArgument(State.GetTraceback(), index + 1, Thread.GetCurrentFrame().Function.Name, t.Name, arg.Type.ToString());
+                LuaRuntimeException.BadArgument(Thread.GetTraceback(), index + 1, Thread.GetCurrentFrame().Function.Name, t.Name, arg.Type.ToString());
             }
         }
 
@@ -186,14 +186,14 @@ public readonly record struct LuaFunctionExecutionContext
 
     internal void ThrowBadArgument(int index, string message)
     {
-        LuaRuntimeException.BadArgument(State.GetTraceback(), index, Thread.GetCurrentFrame().Function.Name, message);
+        LuaRuntimeException.BadArgument(Thread.GetTraceback(), index, Thread.GetCurrentFrame().Function.Name, message);
     }
 
     void ThrowIfArgumentNotExists(int index)
     {
         if (ArgumentCount <= index)
         {
-            LuaRuntimeException.BadArgument(State.GetTraceback(), index + 1, Thread.GetCurrentFrame().Function.Name);
+            LuaRuntimeException.BadArgument(Thread.GetTraceback(), index + 1, Thread.GetCurrentFrame().Function.Name);
         }
     }
 }

+ 21 - 21
src/Lua/LuaFunctionExtensions.cs

@@ -4,25 +4,25 @@ namespace Lua;
 
 public static class LuaFunctionExtensions
 {
-    public static async ValueTask<LuaValue[]> InvokeAsync(this LuaFunction function, LuaState state, LuaValue[] arguments, CancellationToken cancellationToken = default)
-    {
-        var thread = state.CurrentThread;
-        var frameBase = thread.Stack.Count;
-
-        for (int i = 0; i < arguments.Length; i++)
-        {
-            thread.Stack.Push(arguments[i]);
-        }
-
-        var resultCount = await function.InvokeAsync(new()
-        {
-            State = state,
-            Thread = thread,
-            ArgumentCount = arguments.Length,
-            ReturnFrameBase = frameBase,
-        }, cancellationToken);
-        var r = thread.Stack.GetBuffer()[frameBase..(frameBase + resultCount)].ToArray();
-        thread.Stack.PopUntil(frameBase);
-        return r;
-    }
+    // public static async ValueTask<LuaValue[]> InvokeAsync(this LuaFunction function, LuaState state, LuaValue[] arguments, CancellationToken cancellationToken = default)
+    // {
+    //     var thread = state.CurrentThread;
+    //     var frameBase = thread.Stack.Count;
+    //
+    //     for (int i = 0; i < arguments.Length; i++)
+    //     {
+    //         thread.Stack.Push(arguments[i]);
+    //     }
+    //
+    //     var resultCount = await function.InvokeAsync(new()
+    //     {
+    //         State = state,
+    //         Thread = thread,
+    //         ArgumentCount = arguments.Length,
+    //         ReturnFrameBase = frameBase,
+    //     }, cancellationToken);
+    //     var r = thread.Stack.GetBuffer()[frameBase..(frameBase + resultCount)].ToArray();
+    //     thread.Stack.PopUntil(frameBase);
+    //     return r;
+    // }
 }

+ 5 - 14
src/Lua/LuaMainThread.cs

@@ -2,23 +2,14 @@ namespace Lua;
 
 public sealed class LuaMainThread : LuaThread
 {
-    public override LuaThreadStatus GetStatus()
-    {
-        return LuaThreadStatus.Running;
-    }
-
-    public override void UnsafeSetStatus(LuaThreadStatus status)
+    internal LuaMainThread(LuaState state)
     {
-        // Do nothing
+        State = state;
+        CoreData = ThreadCoreData.Create();
     }
 
-    public override ValueTask<int> ResumeAsync(LuaFunctionExecutionContext context, CancellationToken cancellationToken = default)
-    {
-        return new(context.Return(false, "cannot resume non-suspended coroutine"));
-    }
-
-    public override ValueTask<int> YieldAsync(LuaFunctionExecutionContext context, CancellationToken cancellationToken = default)
+    public override LuaThreadStatus GetStatus()
     {
-        throw new LuaRuntimeException(context.State.GetTraceback(), "attempt to yield from outside a coroutine");
+        return LuaThreadStatus.Running;
     }
 }

+ 16 - 64
src/Lua/LuaState.cs

@@ -11,10 +11,8 @@ namespace Lua;
 
 public sealed class LuaState
 {
-    public const string DefaultChunkName = "chunk";
-
     // states
-    readonly LuaMainThread mainThread = new();
+    readonly LuaMainThread mainThread;
     FastListCore<UpValue> openUpValues;
     FastStackCore<LuaThread> threadStack;
     readonly LuaTable packages = new();
@@ -35,15 +33,7 @@ public sealed class LuaState
     public LuaTable Registry => registry;
     public LuaTable LoadedModules => packages;
     public LuaMainThread MainThread => mainThread;
-
-    public LuaThread CurrentThread
-    {
-        get
-        {
-            if (threadStack.TryPeek(out var thread)) return thread;
-            return mainThread;
-        }
-    }
+    
 
     public ILuaModuleLoader ModuleLoader { get; set; } = FileModuleLoader.Instance;
 
@@ -62,69 +52,31 @@ public sealed class LuaState
 
     LuaState()
     {
+        mainThread = new(this);
         environment = new();
         envUpValue = UpValue.Closed(environment);
     }
 
     public async ValueTask<LuaResult> RunAsync(LuaClosure closure, CancellationToken cancellationToken = default)
     {
-        ThrowIfResultNotDisposed();
         ThrowIfRunning();
 
-        Volatile.Write(ref isRunning, true);
+        isRunning = true;
         try
         {
             await closure.InvokeAsync(new()
             {
-                State = this,
-                Thread = CurrentThread,
-                ArgumentCount = 0,
-                ReturnFrameBase = 0,
-                SourceLine = null,
+                Thread = MainThread, ArgumentCount = 0, ReturnFrameBase = 0, SourceLine = null,
             }, cancellationToken);
 
-            return new LuaResult(CurrentThread.Stack, 0);
+            return new LuaResult(MainThread.Stack, 0);
         }
         finally
         {
-            Volatile.Write(ref isRunning, false);
+            isRunning = false;
         }
     }
 
-    public void Push(LuaValue value)
-    {
-        CurrentThread.Stack.Push(value);
-    }
-
-    public Traceback GetTraceback()
-    {
-        return GetTraceback(CurrentThread);
-    }
-
-    internal Traceback GetTraceback(LuaThread thread)
-    {
-        using var list = new PooledList<CallStackFrame>(8);
-        foreach (var frame in thread.GetCallStackFrames()[1..])
-        {
-            list.Add(frame);
-        }
-
-        LuaClosure rootFunc;
-        if (thread.GetCallStackFrames()[0].Function is LuaClosure closure)
-        {
-            rootFunc = closure;
-        }
-        else
-        {
-            rootFunc = (LuaClosure)MainThread.GetCallStackFrames()[0].Function;
-        }
-
-        return new(this)
-        {
-            RootFunc = rootFunc,
-            StackFrames = list.AsSpan().ToArray()
-        };
-    }
 
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
     internal bool TryGetMetatable(LuaValue value, [NotNullWhen(true)] out LuaTable? result)
@@ -225,38 +177,38 @@ public sealed class LuaState
     }
 
 
-    public unsafe LuaClosure Compile(ReadOnlySpan<char> chunk, string chunkName,LuaTable? environment = null)
+    public unsafe LuaClosure Compile(ReadOnlySpan<char> chunk, string chunkName, LuaTable? environment = null)
     {
         Prototype prototype;
         fixed (char* ptr = chunk)
         {
-            prototype= Parser.Parse(this, new (ptr,chunk.Length), chunkName);
+            prototype = Parser.Parse(this, new(ptr, chunk.Length), chunkName);
         }
-        
-        return new LuaClosure(this, prototype, environment);
+
+        return new LuaClosure(MainThread, prototype, environment);
     }
-    
-    public LuaClosure Compile(ReadOnlySpan<byte> chunk, string chunkName , string mode = "bt", LuaTable? environment = null)
+
+    public LuaClosure Compile(ReadOnlySpan<byte> chunk, string chunkName, string mode = "bt", LuaTable? environment = null)
     {
         if (chunk.Length > 4)
         {
             if (chunk[0] == '\e')
             {
-                return new LuaClosure(this,Parser.UnDump(chunk,chunkName),environment);
+                return new LuaClosure(MainThread, Parser.UnDump(chunk, chunkName), environment);
             }
         }
+
         var charCount = Encoding.UTF8.GetCharCount(chunk);
         var pooled = ArrayPool<char>.Shared.Rent(charCount);
         try
         {
             var chars = pooled.AsSpan(0, charCount);
             Encoding.UTF8.GetChars(chunk, chars);
-            return Compile(chars, chunkName,environment);
+            return Compile(chars, chunkName, environment);
         }
         finally
         {
             ArrayPool<char>.Shared.Return(pooled);
         }
     }
-    
 }

+ 5 - 5
src/Lua/LuaStateExtensions.cs

@@ -7,15 +7,15 @@ public static class LuaStateExtensions
 {
     public static async ValueTask<int> DoStringAsync(this LuaState state, string source, Memory<LuaValue> buffer, string? chunkName = null, CancellationToken cancellationToken = default)
     {
-        var closure = state.Compile(source, chunkName??source);
+        var closure = state.Compile(source, chunkName ?? source);
         using var result = await state.RunAsync(closure, cancellationToken);
-        result.AsSpan()[..Math.Min(buffer.Length,result.Length)].CopyTo(buffer.Span);
+        result.AsSpan()[..Math.Min(buffer.Length, result.Length)].CopyTo(buffer.Span);
         return result.Count;
     }
-    
+
     public static async ValueTask<LuaValue[]> DoStringAsync(this LuaState state, string source, string? chunkName = null, CancellationToken cancellationToken = default)
     {
-        var chunk = state.Compile(source, chunkName??source);
+        var chunk = state.Compile(source, chunkName ?? source);
         using var result = await state.RunAsync(chunk, cancellationToken);
         return result.AsSpan().ToArray();
     }
@@ -26,7 +26,7 @@ public static class LuaStateExtensions
         var fileName = "@" + Path.GetFileName(path);
         var closure = state.Compile(bytes, fileName);
         using var result = await state.RunAsync(closure, cancellationToken);
-        result.AsSpan()[..Math.Min(buffer.Length,result.Length)].CopyTo(buffer.Span);
+        result.AsSpan()[..Math.Min(buffer.Length, result.Length)].CopyTo(buffer.Span);
         return result.Count;
     }
 

+ 67 - 66
src/Lua/LuaThread.cs

@@ -4,22 +4,17 @@ using Lua.Runtime;
 
 namespace Lua;
 
-public class LuaThread : IPoolNode<LuaThread>
+public abstract class LuaThread
 {
-    static LinkedPool<LuaThread> pool;
-    LuaThread? parent;
-    ref LuaThread? IPoolNode<LuaThread>.NextNode => ref parent;
-    public static LuaThread Create(LuaState state)
-    {
-        var thread = new LuaThread { CoreData = { State = state } };
-        return thread;
-    }
+    internal LuaThread() { }
+
     public virtual LuaThreadStatus GetStatus()
     {
         return LuaThreadStatus.Running;
     }
 
-    public virtual void UnsafeSetStatus(LuaThreadStatus status){}
+    public virtual void UnsafeSetStatus(LuaThreadStatus status) { }
+
     public virtual ValueTask<int> ResumeAsync(LuaFunctionExecutionContext context, CancellationToken cancellationToken = default)
     {
         return new(context.Return(false, "cannot resume non-suspended coroutine"));
@@ -27,97 +22,103 @@ public class LuaThread : IPoolNode<LuaThread>
 
     public virtual ValueTask<int> YieldAsync(LuaFunctionExecutionContext context, CancellationToken cancellationToken = default)
     {
-        throw new LuaRuntimeException(context.State.GetTraceback(), "attempt to yield from outside a coroutine");
+        throw new LuaRuntimeException(context.Thread.GetTraceback(), "attempt to yield from outside a coroutine");
     }
 
-    internal class ThreadCoreData
+    internal class ThreadCoreData: IPoolNode<ThreadCoreData>
     {
-        internal LuaState State;
         //internal  LuaCoroutineData? coroutineData;
         internal LuaStack Stack = new();
         internal FastStackCore<CallStackFrame> CallStack;
-        internal BitFlags2 LineAndCountHookMask;
-        internal BitFlags2 CallOrReturnHookMask;
-        internal bool IsInHook;
-        internal int HookCount;
-        internal int BaseHookCount;
-        internal int LastPc;
-        internal LuaFunction? Hook { get; set; }
-    }
+        public void Clear()
+        {
+            Stack.Clear();
+            CallStack.Clear();
+        }
 
-    internal ThreadCoreData CoreData = new();
-    
-    public LuaState State=> CoreData.State;
+        static LinkedPool<ThreadCoreData> pool;
+        ThreadCoreData? nextNode;
+        public ref ThreadCoreData? NextNode => ref nextNode;
+        
+        public static ThreadCoreData Create()
+        {
+            if (!pool.TryPop(out ThreadCoreData result))
+            {
+                result = new ThreadCoreData();
+            }
 
-    internal LuaStack Stack => CoreData.Stack;
-    internal ref FastStackCore<CallStackFrame> CallStack => ref CoreData.CallStack;
+            return result;
+        }
+
+        public void Release()
+        {
+            Clear();
+            pool.TryPush(this);
+        }
+    }
+    public LuaState State { get;protected set; }
+
+    internal ThreadCoreData? CoreData = new();
+   
+    internal BitFlags2 LineAndCountHookMask;
+    internal BitFlags2 CallOrReturnHookMask;
+    internal bool IsInHook;
+    internal int HookCount;
+    internal int BaseHookCount;
+    internal int LastPc;
+    internal LuaFunction? Hook { get; set; }
+   
+    internal LuaStack Stack => CoreData!.Stack;
+    internal ref FastStackCore<CallStackFrame> CallStack => ref CoreData!.CallStack;
 
     internal bool IsLineHookEnabled
     {
-        get => CoreData.LineAndCountHookMask.Flag0;
-        set => CoreData.LineAndCountHookMask.Flag0 = value;
+        get => LineAndCountHookMask.Flag0;
+        set => LineAndCountHookMask.Flag0 = value;
     }
 
     internal bool IsCountHookEnabled
     {
-        get => CoreData.LineAndCountHookMask.Flag1;
-        set => CoreData.LineAndCountHookMask.Flag1 = value;
+        get => LineAndCountHookMask.Flag1;
+        set => LineAndCountHookMask.Flag1 = value;
     }
 
 
     internal bool IsCallHookEnabled
     {
-        get => CoreData.CallOrReturnHookMask.Flag0;
-        set => CoreData.CallOrReturnHookMask.Flag0 = value;
+        get => CallOrReturnHookMask.Flag0;
+        set => CallOrReturnHookMask.Flag0 = value;
     }
 
     internal bool IsReturnHookEnabled
     {
-        get => CoreData.CallOrReturnHookMask.Flag1;
-        set => CoreData.CallOrReturnHookMask.Flag1 = value;
+        get => CallOrReturnHookMask.Flag1;
+        set => CallOrReturnHookMask.Flag1 = value;
     }
 
-    internal BitFlags2 LineAndCountHookMask
-    {
-        get => CoreData.LineAndCountHookMask;
-        set => CoreData.LineAndCountHookMask = value;
-    }
+ 
 
-    internal BitFlags2 CallOrReturnHookMask
-    {
-        get => CoreData.CallOrReturnHookMask;
-        set => CoreData.CallOrReturnHookMask = value;
-    }
 
-    internal bool IsInHook
+    public void Push(LuaValue value)
     {
-        get => CoreData.IsInHook;
-        set => CoreData.IsInHook = value;
+        CoreData.Stack.Push(value);
     }
 
-    internal int HookCount
+    public void Push(params ReadOnlySpan<LuaValue> span)
     {
-        get => CoreData.HookCount;
-        set => CoreData.HookCount = value;
+        CoreData.Stack.PushRange(span);
     }
 
-    internal int BaseHookCount
+    public void Pop(int count)
     {
-        get => CoreData.BaseHookCount;
-        set => CoreData.BaseHookCount = value;
+        CoreData.Stack.Pop(count);
     }
 
-    internal int LastPc
+    public LuaValue Pop()
     {
-        get => CoreData.LastPc;
-        set => CoreData.LastPc = value;
+        return CoreData.Stack.Pop();
     }
 
-    internal LuaFunction? Hook
-    {
-        get => CoreData.Hook;
-        set => CoreData.Hook = value;
-    }
 
     public ref readonly CallStackFrame GetCurrentFrame()
     {
@@ -184,13 +185,13 @@ public class LuaThread : IPoolNode<LuaThread>
         }
     }
 
-    public void Release()
+    public Traceback GetTraceback()
     {
-        if (CoreData.CallStack.Count != 0)
-        {
-            throw new InvalidOperationException("This thread is running! Call stack is not empty!!");
-        }
+        var frames = GetCallStackFrames();
+
+        return new(State) { RootFunc = frames[0].Function, StackFrames = GetCallStackFrames()[1..].ToArray() };
     }
 
+
     static void ThrowForEmptyStack() => throw new InvalidOperationException("Empty stack");
 }

+ 15 - 32
src/Lua/LuaThreadExtensions.cs

@@ -1,46 +1,29 @@
 using Lua.Internal;
+using Lua.Runtime;
 
 namespace Lua;
 
 public static class LuaThreadExtensions
 {
-    public static async ValueTask<LuaValue[]> ResumeAsync(this LuaThread thread, LuaState state, CancellationToken cancellationToken = default)
+    public static async ValueTask<LuaResult> RunAsync(this LuaThread thread, LuaClosure closure, int argumentCount, CancellationToken cancellationToken = default)
     {
-        var frameBase = thread.Stack.Count;
-        thread.Stack.Push(thread);
-
-        await thread.ResumeAsync(new()
+        var top = thread.CoreData.Stack.Count;
+        await closure.InvokeAsync(new()
         {
-            State = state,
-            Thread = state.CurrentThread,
-            ArgumentCount = 1,
-            ReturnFrameBase = frameBase,
+            Thread = thread, ArgumentCount = argumentCount, ReturnFrameBase = top - argumentCount, SourceLine = null,
         }, cancellationToken);
-        var returnBase = ((LuaCoroutine)thread).ReturnFrameBase;
-        var results = thread.Stack.AsSpan()[returnBase..].ToArray();
-        thread.Stack.PopUntil(returnBase);
-        return results;
-    }
 
-    public static async ValueTask<LuaValue[]> ResumeAsync(this LuaThread thread, LuaState state, LuaValue[] arguments, CancellationToken cancellationToken = default)
+        return new LuaResult(thread.Stack, top);
+    }
+    
+    
+    public static UseThreadLease RentUseThread(this LuaThread thread)
     {
-        var frameBase = thread.Stack.Count;
-        thread.Stack.Push(thread);
-        for (int i = 0; i < arguments.Length; i++)
-        {
-            thread.Stack.Push(arguments[i]);
-        }
+        return new UseThreadLease(LuaUserThread.Create(thread));
+    }
 
-        await thread.ResumeAsync(new()
-        {
-            State = state,
-            Thread = state.CurrentThread,
-            ArgumentCount = 1 + arguments.Length,
-            ReturnFrameBase = frameBase,
-        }, cancellationToken);
-        var returnBase = ((LuaCoroutine)thread).ReturnFrameBase;
-        var results = thread.Stack.AsSpan()[returnBase..].ToArray();
-        thread.Stack.PopUntil(returnBase);
-        return results;
+    public static CoroutineLease RentCoroutine(this LuaThread thread,LuaFunction function ,bool isProtectedMode = false)
+    {
+        return new CoroutineLease(LuaCoroutine.Create(thread, function, isProtectedMode));
     }
 }

+ 34 - 0
src/Lua/LuaUserThread.cs

@@ -0,0 +1,34 @@
+using Lua.Internal;
+
+namespace Lua;
+
+public sealed class LuaUserThread : LuaThread, IPoolNode<LuaUserThread>
+{
+    static LinkedPool<LuaUserThread> pool;
+    LuaUserThread? nextNode;
+    ref LuaUserThread? IPoolNode<LuaUserThread>.NextNode => ref nextNode;
+
+    public static LuaUserThread Create(LuaThread parent)
+    {
+        if (!pool.TryPop(out LuaUserThread result))
+        {
+            result = new LuaUserThread();
+        }
+        result.State = parent.State;
+        result.CoreData =ThreadCoreData.Create();
+
+        return result;
+    }
+
+    public void Release()
+    {
+        if (CoreData!.CallStack.Count != 0)
+        {
+            throw new InvalidOperationException("This thread is running! Call stack is not empty!!");
+        }
+
+        CoreData.Release();
+        CoreData = null!;
+        pool.TryPush(this);
+    }
+}

+ 2 - 2
src/Lua/LuaValue.cs

@@ -295,7 +295,7 @@ public readonly struct LuaValue : IEquatable<LuaValue>
     {
         return value;
     }
-    
+
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
     internal string UnsafeReadString()
     {
@@ -596,7 +596,7 @@ public readonly struct LuaValue : IEquatable<LuaValue>
         {
             if (!metamethod.TryReadFunction(out var func))
             {
-                LuaRuntimeException.AttemptInvalidOperation(context.State.GetTraceback(), "call", metamethod);
+                LuaRuntimeException.AttemptInvalidOperation(context.Thread.GetTraceback(), "call", metamethod);
             }
 
             var stack = context.Thread.Stack;

+ 43 - 199
src/Lua/Runtime/Instruction.cs

@@ -6,8 +6,8 @@ public partial struct Instruction(uint value)
     public const int IABx = 1;
     public const int IAsBx = 2;
     public const int IAx = 3;
-    
-    
+
+
     public uint Value = value;
     public static implicit operator Instruction(uint value) => new(value);
 
@@ -416,18 +416,13 @@ opmode(0, 0, opArgU, opArgU, iAx),   // opExtraArg
         OpMode(0, 1, OpArgU, OpArgN, IABC), // opVarArg
         OpMode(0, 0, OpArgU, OpArgU, IAx), // opExtraArg
     ];
-    
+
     /// <summary>
     /// R(A) := R(B)
     /// </summary>
     public static Instruction Move(byte a, ushort b)
     {
-        return new()
-        {
-            OpCode = OpCode.Move,
-            A = a,
-            B = b,
-        };
+        return new() { OpCode = OpCode.Move, A = a, B = b, };
     }
 
     /// <summary>
@@ -435,12 +430,7 @@ opmode(0, 0, opArgU, opArgU, iAx),   // opExtraArg
     /// </summary>
     public static Instruction LoadK(byte a, uint bx)
     {
-        return new()
-        {
-            OpCode = OpCode.LoadK,
-            A = a,
-            Bx = (int)bx,
-        };
+        return new() { OpCode = OpCode.LoadK, A = a, Bx = (int)bx, };
     }
 
     /// <summary>
@@ -448,11 +438,7 @@ opmode(0, 0, opArgU, opArgU, iAx),   // opExtraArg
     /// </summary>
     public static Instruction LoadKX(byte a)
     {
-        return new()
-        {
-            OpCode = OpCode.LoadKX,
-            A = a,
-        };
+        return new() { OpCode = OpCode.LoadKX, A = a, };
     }
 
     /// <summary>
@@ -463,10 +449,7 @@ opmode(0, 0, opArgU, opArgU, iAx),   // opExtraArg
     {
         return new()
         {
-            OpCode = OpCode.LoadBool,
-            A = a,
-            B = b,
-            C = c,
+            OpCode = OpCode.LoadBool, A = a, B = b, C = c,
         };
     }
 
@@ -475,12 +458,7 @@ opmode(0, 0, opArgU, opArgU, iAx),   // opExtraArg
     /// </summary>
     public static Instruction LoadNil(byte a, ushort b)
     {
-        return new()
-        {
-            OpCode = OpCode.LoadNil,
-            A = a,
-            B = b,
-        };
+        return new() { OpCode = OpCode.LoadNil, A = a, B = b, };
     }
 
     /// <summary>
@@ -488,12 +466,7 @@ opmode(0, 0, opArgU, opArgU, iAx),   // opExtraArg
     /// </summary>
     public static Instruction GetUpVal(byte a, ushort b)
     {
-        return new()
-        {
-            OpCode = OpCode.GetUpVal,
-            A = a,
-            B = b,
-        };
+        return new() { OpCode = OpCode.GetUpVal, A = a, B = b, };
     }
 
     /// <summary>
@@ -503,10 +476,7 @@ opmode(0, 0, opArgU, opArgU, iAx),   // opExtraArg
     {
         return new()
         {
-            OpCode = OpCode.GetTabUp,
-            A = a,
-            B = b,
-            C = c,
+            OpCode = OpCode.GetTabUp, A = a, B = b, C = c,
         };
     }
 
@@ -517,10 +487,7 @@ opmode(0, 0, opArgU, opArgU, iAx),   // opExtraArg
     {
         return new()
         {
-            OpCode = OpCode.GetTable,
-            A = a,
-            B = b,
-            C = c,
+            OpCode = OpCode.GetTable, A = a, B = b, C = c,
         };
     }
 
@@ -529,12 +496,7 @@ opmode(0, 0, opArgU, opArgU, iAx),   // opExtraArg
     /// </summary>
     public static Instruction SetUpVal(byte a, ushort b)
     {
-        return new()
-        {
-            OpCode = OpCode.SetUpVal,
-            A = a,
-            B = b,
-        };
+        return new() { OpCode = OpCode.SetUpVal, A = a, B = b, };
     }
 
     /// <summary>
@@ -544,10 +506,7 @@ opmode(0, 0, opArgU, opArgU, iAx),   // opExtraArg
     {
         return new()
         {
-            OpCode = OpCode.SetTabUp,
-            A = a,
-            B = b,
-            C = c,
+            OpCode = OpCode.SetTabUp, A = a, B = b, C = c,
         };
     }
 
@@ -558,10 +517,7 @@ opmode(0, 0, opArgU, opArgU, iAx),   // opExtraArg
     {
         return new()
         {
-            OpCode = OpCode.SetTable,
-            A = a,
-            B = b,
-            C = c,
+            OpCode = OpCode.SetTable, A = a, B = b, C = c,
         };
     }
 
@@ -572,10 +528,7 @@ opmode(0, 0, opArgU, opArgU, iAx),   // opExtraArg
     {
         return new()
         {
-            OpCode = OpCode.NewTable,
-            A = a,
-            B = b,
-            C = c,
+            OpCode = OpCode.NewTable, A = a, B = b, C = c,
         };
     }
 
@@ -586,10 +539,7 @@ opmode(0, 0, opArgU, opArgU, iAx),   // opExtraArg
     {
         return new()
         {
-            OpCode = OpCode.Self,
-            A = a,
-            B = b,
-            C = c,
+            OpCode = OpCode.Self, A = a, B = b, C = c,
         };
     }
 
@@ -600,10 +550,7 @@ opmode(0, 0, opArgU, opArgU, iAx),   // opExtraArg
     {
         return new()
         {
-            OpCode = OpCode.Add,
-            A = a,
-            B = b,
-            C = c,
+            OpCode = OpCode.Add, A = a, B = b, C = c,
         };
     }
 
@@ -614,10 +561,7 @@ opmode(0, 0, opArgU, opArgU, iAx),   // opExtraArg
     {
         return new()
         {
-            OpCode = OpCode.Sub,
-            A = a,
-            B = b,
-            C = c,
+            OpCode = OpCode.Sub, A = a, B = b, C = c,
         };
     }
 
@@ -628,10 +572,7 @@ opmode(0, 0, opArgU, opArgU, iAx),   // opExtraArg
     {
         return new()
         {
-            OpCode = OpCode.Mul,
-            A = a,
-            B = b,
-            C = c,
+            OpCode = OpCode.Mul, A = a, B = b, C = c,
         };
     }
 
@@ -642,10 +583,7 @@ opmode(0, 0, opArgU, opArgU, iAx),   // opExtraArg
     {
         return new()
         {
-            OpCode = OpCode.Div,
-            A = a,
-            B = b,
-            C = c,
+            OpCode = OpCode.Div, A = a, B = b, C = c,
         };
     }
 
@@ -656,10 +594,7 @@ opmode(0, 0, opArgU, opArgU, iAx),   // opExtraArg
     {
         return new()
         {
-            OpCode = OpCode.Mod,
-            A = a,
-            B = b,
-            C = c,
+            OpCode = OpCode.Mod, A = a, B = b, C = c,
         };
     }
 
@@ -670,10 +605,7 @@ opmode(0, 0, opArgU, opArgU, iAx),   // opExtraArg
     {
         return new()
         {
-            OpCode = OpCode.Pow,
-            A = a,
-            B = b,
-            C = c,
+            OpCode = OpCode.Pow, A = a, B = b, C = c,
         };
     }
 
@@ -682,12 +614,7 @@ opmode(0, 0, opArgU, opArgU, iAx),   // opExtraArg
     /// </summary>
     public static Instruction Unm(byte a, ushort b)
     {
-        return new()
-        {
-            OpCode = OpCode.Unm,
-            A = a,
-            B = b,
-        };
+        return new() { OpCode = OpCode.Unm, A = a, B = b, };
     }
 
     /// <summary>
@@ -695,12 +622,7 @@ opmode(0, 0, opArgU, opArgU, iAx),   // opExtraArg
     /// </summary>
     public static Instruction Not(byte a, ushort b)
     {
-        return new()
-        {
-            OpCode = OpCode.Not,
-            A = a,
-            B = b,
-        };
+        return new() { OpCode = OpCode.Not, A = a, B = b, };
     }
 
     /// <summary>
@@ -708,12 +630,7 @@ opmode(0, 0, opArgU, opArgU, iAx),   // opExtraArg
     /// </summary>
     public static Instruction Len(byte a, ushort b)
     {
-        return new()
-        {
-            OpCode = OpCode.Len,
-            A = a,
-            B = b,
-        };
+        return new() { OpCode = OpCode.Len, A = a, B = b, };
     }
 
     /// <summary>
@@ -723,10 +640,7 @@ opmode(0, 0, opArgU, opArgU, iAx),   // opExtraArg
     {
         return new()
         {
-            OpCode = OpCode.Concat,
-            A = a,
-            B = b,
-            C = c,
+            OpCode = OpCode.Concat, A = a, B = b, C = c,
         };
     }
 
@@ -736,12 +650,7 @@ opmode(0, 0, opArgU, opArgU, iAx),   // opExtraArg
     /// </summary>
     public static Instruction Jmp(byte a, int sBx)
     {
-        return new()
-        {
-            OpCode = OpCode.Jmp,
-            A = a,
-            SBx = sBx,
-        };
+        return new() { OpCode = OpCode.Jmp, A = a, SBx = sBx, };
     }
 
     /// <summary>
@@ -751,10 +660,7 @@ opmode(0, 0, opArgU, opArgU, iAx),   // opExtraArg
     {
         return new()
         {
-            OpCode = OpCode.Eq,
-            A = a,
-            B = b,
-            C = c,
+            OpCode = OpCode.Eq, A = a, B = b, C = c,
         };
     }
 
@@ -765,10 +671,7 @@ opmode(0, 0, opArgU, opArgU, iAx),   // opExtraArg
     {
         return new()
         {
-            OpCode = OpCode.Lt,
-            A = a,
-            B = b,
-            C = c,
+            OpCode = OpCode.Lt, A = a, B = b, C = c,
         };
     }
 
@@ -779,10 +682,7 @@ opmode(0, 0, opArgU, opArgU, iAx),   // opExtraArg
     {
         return new()
         {
-            OpCode = OpCode.Le,
-            A = a,
-            B = b,
-            C = c,
+            OpCode = OpCode.Le, A = a, B = b, C = c,
         };
     }
 
@@ -791,12 +691,7 @@ opmode(0, 0, opArgU, opArgU, iAx),   // opExtraArg
     /// </summary>
     public static Instruction Test(byte a, ushort c)
     {
-        return new()
-        {
-            OpCode = OpCode.Test,
-            A = a,
-            C = c,
-        };
+        return new() { OpCode = OpCode.Test, A = a, C = c, };
     }
 
     /// <summary>
@@ -806,10 +701,7 @@ opmode(0, 0, opArgU, opArgU, iAx),   // opExtraArg
     {
         return new()
         {
-            OpCode = OpCode.TestSet,
-            A = a,
-            B = b,
-            C = c,
+            OpCode = OpCode.TestSet, A = a, B = b, C = c,
         };
     }
 
@@ -820,10 +712,7 @@ opmode(0, 0, opArgU, opArgU, iAx),   // opExtraArg
     {
         return new()
         {
-            OpCode = OpCode.Call,
-            A = a,
-            B = b,
-            C = c,
+            OpCode = OpCode.Call, A = a, B = b, C = c,
         };
     }
 
@@ -834,10 +723,7 @@ opmode(0, 0, opArgU, opArgU, iAx),   // opExtraArg
     {
         return new()
         {
-            OpCode = OpCode.TailCall,
-            A = a,
-            B = b,
-            C = c,
+            OpCode = OpCode.TailCall, A = a, B = b, C = c,
         };
     }
 
@@ -846,12 +732,7 @@ opmode(0, 0, opArgU, opArgU, iAx),   // opExtraArg
     /// </summary>
     public static Instruction Return(byte a, ushort b)
     {
-        return new()
-        {
-            OpCode = OpCode.Return,
-            A = a,
-            B = b,
-        };
+        return new() { OpCode = OpCode.Return, A = a, B = b, };
     }
 
     /// <summary>
@@ -860,12 +741,7 @@ opmode(0, 0, opArgU, opArgU, iAx),   // opExtraArg
     /// </summary>
     public static Instruction ForLoop(byte a, int sBx)
     {
-        return new()
-        {
-            OpCode = OpCode.ForLoop,
-            A = a,
-            SBx = sBx,
-        };
+        return new() { OpCode = OpCode.ForLoop, A = a, SBx = sBx, };
     }
 
     /// <summary>
@@ -874,12 +750,7 @@ opmode(0, 0, opArgU, opArgU, iAx),   // opExtraArg
     /// </summary>
     public static Instruction ForPrep(byte a, int sBx)
     {
-        return new()
-        {
-            OpCode = OpCode.ForPrep,
-            A = a,
-            SBx = sBx,
-        };
+        return new() { OpCode = OpCode.ForPrep, A = a, SBx = sBx, };
     }
 
     /// <summary>
@@ -887,12 +758,7 @@ opmode(0, 0, opArgU, opArgU, iAx),   // opExtraArg
     /// </summary>
     public static Instruction TForCall(byte a, ushort c)
     {
-        return new()
-        {
-            OpCode = OpCode.TForCall,
-            A = a,
-            C = c,
-        };
+        return new() { OpCode = OpCode.TForCall, A = a, C = c, };
     }
 
     /// <summary>
@@ -900,12 +766,7 @@ opmode(0, 0, opArgU, opArgU, iAx),   // opExtraArg
     /// </summary>
     public static Instruction TForLoop(byte a, int sBx)
     {
-        return new()
-        {
-            OpCode = OpCode.TForLoop,
-            A = a,
-            SBx = sBx,
-        };
+        return new() { OpCode = OpCode.TForLoop, A = a, SBx = sBx, };
     }
 
     /// <summary>
@@ -915,10 +776,7 @@ opmode(0, 0, opArgU, opArgU, iAx),   // opExtraArg
     {
         return new()
         {
-            OpCode = OpCode.SetList,
-            A = a,
-            B = b,
-            C = c,
+            OpCode = OpCode.SetList, A = a, B = b, C = c,
         };
     }
 
@@ -927,12 +785,7 @@ opmode(0, 0, opArgU, opArgU, iAx),   // opExtraArg
     /// </summary>
     public static Instruction Closure(byte a, int sBx)
     {
-        return new()
-        {
-            OpCode = OpCode.Closure,
-            A = a,
-            SBx = sBx,
-        };
+        return new() { OpCode = OpCode.Closure, A = a, SBx = sBx, };
     }
 
     /// <summary>
@@ -940,12 +793,7 @@ opmode(0, 0, opArgU, opArgU, iAx),   // opExtraArg
     /// </summary>
     public static Instruction VarArg(byte a, ushort b)
     {
-        return new()
-        {
-            OpCode = OpCode.VarArg,
-            A = a,
-            B = b,
-        };
+        return new() { OpCode = OpCode.VarArg, A = a, B = b, };
     }
 
     /// <summary>
@@ -953,10 +801,6 @@ opmode(0, 0, opArgU, opArgU, iAx),   // opExtraArg
     /// </summary>
     public static Instruction ExtraArg(uint ax)
     {
-        return new()
-        {
-            OpCode = OpCode.ExtraArg,
-            Ax = (int)ax,
-        };
+        return new() { OpCode = OpCode.ExtraArg, Ax = (int)ax, };
     }
 }

+ 22 - 0
src/Lua/Runtime/Lease.cs

@@ -0,0 +1,22 @@
+namespace Lua.Runtime
+{
+    public readonly struct UseThreadLease(LuaUserThread thread) : IDisposable
+    {
+        public LuaUserThread Thread { get; } = thread;
+
+        public void Dispose()
+        {
+            Thread.Release();
+        }
+    }
+
+    public readonly struct CoroutineLease(LuaCoroutine thread) : IDisposable
+    {
+        public LuaCoroutine Thread { get; } = thread;
+
+        public void Dispose()
+        {
+            Thread.Release();
+        }
+    }
+}

+ 6 - 6
src/Lua/Runtime/LuaClosure.cs

@@ -8,8 +8,8 @@ public sealed class LuaClosure : LuaFunction
 {
     FastListCore<UpValue> upValues;
 
-    public LuaClosure(LuaState state, Prototype proto, LuaTable? environment = null)
-        : base(proto.ChunkName, static (context, ct) => LuaVirtualMachine.ExecuteClosureAsync(context.State, ct))
+    public LuaClosure(LuaThread thread, Prototype proto, LuaTable? environment = null)
+        : base(proto.ChunkName, static (context, ct) => LuaVirtualMachine.ExecuteClosureAsync(context.Thread, ct))
     {
         Proto = proto;
         if (environment != null)
@@ -18,19 +18,19 @@ public sealed class LuaClosure : LuaFunction
             return;
         }
 
-        if (state.CurrentThread.CallStack.Count == 0)
+        if (thread.CallStack.Count == 0)
         {
-            upValues.Add(state.EnvUpValue);
+            upValues.Add(thread.State.EnvUpValue);
             return;
         }
 
-        var baseIndex = state.CurrentThread.CallStack.Peek().Base;
+        var baseIndex = thread.CallStack.Peek().Base;
 
         // add upvalues
         for (int i = 0; i < proto.UpValues.Length; i++)
         {
             var description = proto.UpValues[i];
-            var upValue = GetUpValueFromDescription(state, state.CurrentThread, description, baseIndex);
+            var upValue = GetUpValueFromDescription(thread.State, thread, description, baseIndex);
             upValues.Add(upValue);
         }
     }

+ 12 - 2
src/Lua/Runtime/LuaStack.cs

@@ -69,6 +69,16 @@ public sealed class LuaStack(int initialSize = 256)
         return item;
     }
 
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public void Pop(int count)
+    {
+        var newSize = top - count;
+        if (newSize >= top) return;
+
+        array.AsSpan(newSize, top - newSize).Clear();
+        top = newSize;
+    }
+
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
     public void PopUntil(int newSize)
     {
@@ -140,9 +150,9 @@ public sealed class LuaStack(int initialSize = 256)
     {
         throw new InvalidOperationException("Empty stack");
     }
-    
+
     [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
-    private  Span<LuaValue> Span => AsSpan();
+    private Span<LuaValue> Span => AsSpan();
 
     internal void SetTop(int top)
     {

+ 6 - 40
src/Lua/Runtime/LuaVirtualMachine.Debug.cs

@@ -35,13 +35,7 @@ public static partial class LuaVirtualMachine
                 var stack = context.Thread.Stack;
                 stack.Push("count");
                 stack.Push(LuaValue.Nil);
-                var funcContext = new LuaFunctionExecutionContext
-                {
-                    State = context.State,
-                    Thread = context.Thread,
-                    ArgumentCount = 2,
-                    ReturnFrameBase = context.Thread.Stack.Count - 2,
-                };
+                var funcContext = new LuaFunctionExecutionContext { Thread = context.Thread, ArgumentCount = 2, ReturnFrameBase = context.Thread.Stack.Count - 2, };
                 var frame = new CallStackFrame
                 {
                     Base = funcContext.FrameBase,
@@ -78,13 +72,7 @@ public static partial class LuaVirtualMachine
                     var stack = context.Thread.Stack;
                     stack.Push("line");
                     stack.Push(line);
-                    var funcContext = new LuaFunctionExecutionContext
-                    {
-                        State = context.State,
-                        Thread = context.Thread,
-                        ArgumentCount = 2,
-                        ReturnFrameBase = context.Thread.Stack.Count - 2,
-                    };
+                    var funcContext = new LuaFunctionExecutionContext { Thread = context.Thread, ArgumentCount = 2, ReturnFrameBase = context.Thread.Stack.Count - 2, };
                     var frame = new CallStackFrame
                     {
                         Base = funcContext.FrameBase,
@@ -119,11 +107,7 @@ public static partial class LuaVirtualMachine
     {
         return ExecuteCallHook(new()
         {
-            State = context.State,
-            Thread = context.Thread,
-            ArgumentCount = arguments,
-            ReturnFrameBase = frame.ReturnBase,
-            CallerInstructionIndex = frame.CallerInstructionIndex,
+            Thread = context.Thread, ArgumentCount = arguments, ReturnFrameBase = frame.ReturnBase, CallerInstructionIndex = frame.CallerInstructionIndex,
         }, context.CancellationToken, isTailCall);
     }
 
@@ -137,13 +121,7 @@ public static partial class LuaVirtualMachine
             stack.Push((isTailCall ? "tail call" : "call"));
 
             stack.Push(LuaValue.Nil);
-            var funcContext = new LuaFunctionExecutionContext
-            {
-                State = context.State,
-                Thread = context.Thread,
-                ArgumentCount = 2,
-                ReturnFrameBase = context.Thread.Stack.Count - 2,
-            };
+            var funcContext = new LuaFunctionExecutionContext { Thread = context.Thread, ArgumentCount = 2, ReturnFrameBase = context.Thread.Stack.Count - 2, };
             CallStackFrame frame = new()
             {
                 Base = funcContext.FrameBase,
@@ -169,13 +147,7 @@ public static partial class LuaVirtualMachine
 
         {
             ref readonly var frame = ref context.Thread.GetCurrentFrame();
-            var task = frame.Function.Func(new()
-            {
-                State = context.State,
-                Thread = context.Thread,
-                ArgumentCount = argCount,
-                ReturnFrameBase = frame.ReturnBase,
-            }, cancellationToken);
+            var task = frame.Function.Func(new() { Thread = context.Thread, ArgumentCount = argCount, ReturnFrameBase = frame.ReturnBase, }, cancellationToken);
             var r = await task;
             if (isTailCall || !context.Thread.IsReturnHookEnabled)
             {
@@ -184,13 +156,7 @@ public static partial class LuaVirtualMachine
 
             stack.Push("return");
             stack.Push(LuaValue.Nil);
-            var funcContext = new LuaFunctionExecutionContext
-            {
-                State = context.State,
-                Thread = context.Thread,
-                ArgumentCount = 2,
-                ReturnFrameBase = context.Thread.Stack.Count - 2,
-            };
+            var funcContext = new LuaFunctionExecutionContext { Thread = context.Thread, ArgumentCount = 2, ReturnFrameBase = context.Thread.Stack.Count - 2, };
 
 
             context.Thread.PushCallStackFrame(new()

+ 40 - 45
src/Lua/Runtime/LuaVirtualMachine.cs

@@ -15,8 +15,7 @@ public static partial class LuaVirtualMachine
         : IPoolNode<VirtualMachineExecutionContext>
     {
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        public static VirtualMachineExecutionContext Get(LuaState state,
-            LuaStack stack,
+        public static VirtualMachineExecutionContext Get(
             LuaThread thread,
             in CallStackFrame frame,
             CancellationToken cancellationToken)
@@ -26,18 +25,16 @@ public static partial class LuaVirtualMachine
                 executionContext = new VirtualMachineExecutionContext();
             }
 
-            executionContext.Init(state, stack, thread, frame, cancellationToken);
+            executionContext.Init(thread, frame, cancellationToken);
             return executionContext;
         }
 
-        void Init(LuaState state,
-            LuaStack stack,
+        void Init(
             LuaThread thread,
             in CallStackFrame frame,
             CancellationToken cancellationToken)
         {
-            State = state;
-            Stack = stack;
+            Stack = thread.Stack;
             Thread = thread;
             LuaClosure = (LuaClosure)frame.Function;
             FrameBase = frame.Base;
@@ -51,9 +48,7 @@ public static partial class LuaVirtualMachine
             LastHookPc = -1;
             Task = default;
         }
-
-
-        public LuaState State = default!;
+        public LuaState State => Thread.State;
         public LuaStack Stack = default!;
         public LuaClosure LuaClosure = default!;
         public LuaThread Thread = default!;
@@ -292,12 +287,11 @@ public static partial class LuaVirtualMachine
         DontPop,
     }
 
-    internal static ValueTask<int> ExecuteClosureAsync(LuaState luaState, CancellationToken cancellationToken)
+    internal static ValueTask<int> ExecuteClosureAsync(LuaThread thread, CancellationToken cancellationToken)
     {
-        var thread = luaState.CurrentThread;
         ref readonly var frame = ref thread.GetCurrentFrame();
 
-        var context = VirtualMachineExecutionContext.Get(luaState, thread.Stack, thread, in frame,
+        var context = VirtualMachineExecutionContext.Get(thread, in frame,
             cancellationToken);
 
         return context.ExecuteClosureAsyncImpl();
@@ -314,7 +308,7 @@ public static partial class LuaVirtualMachine
             var stack = context.Stack;
             stack.EnsureCapacity(frameBase + context.Prototype.MaxStackSize);
             ref var constHead = ref MemoryMarshalEx.UnsafeElementAt(context.Prototype.Constants, 0);
-            ref var lineAndCountHookMask = ref context.Thread.CoreData.LineAndCountHookMask;
+            ref var lineAndCountHookMask = ref context.Thread.LineAndCountHookMask;
             goto Loop;
         LineHook:
 
@@ -552,7 +546,7 @@ public static partial class LuaVirtualMachine
 
                         if (iA != 0)
                         {
-                            context.State.CloseUpValues(context.Thread, frameBase + iA - 1);
+                            context.Thread.State.CloseUpValues(context.Thread, frameBase + iA - 1);
                         }
 
                         continue;
@@ -728,7 +722,7 @@ public static partial class LuaVirtualMachine
                     case OpCode.Closure:
                         ra1 = iA + frameBase + 1;
                         stack.EnsureCapacity(ra1);
-                        stack.Get(ra1 - 1) = new LuaClosure(context.State, context.Prototype.ChildPrototypes[instruction.Bx]);
+                        stack.Get(ra1 - 1) = new LuaClosure(context.Thread, context.Prototype.ChildPrototypes[instruction.Bx]);
                         stack.NotifyTop(ra1);
                         continue;
                     case OpCode.VarArg:
@@ -774,7 +768,7 @@ public static partial class LuaVirtualMachine
             context.State.CloseUpValues(context.Thread, context.FrameBase);
             if (e is not LuaRuntimeException)
             {
-                var newException = new LuaRuntimeException(context.State.GetTraceback(), e);
+                var newException = new LuaRuntimeException(context.Thread.GetTraceback(), e);
                 context.PopOnTopCallStackFrames();
                 throw newException;
             }
@@ -787,12 +781,12 @@ public static partial class LuaVirtualMachine
 
     static void ThrowLuaRuntimeException(VirtualMachineExecutionContext context, string message)
     {
-        throw new LuaRuntimeException(context.State.GetTraceback(), message);
+        throw new LuaRuntimeException(context.Thread.GetTraceback(), message);
     }
 
     static void ThrowLuaNotImplementedException(VirtualMachineExecutionContext context, OpCode opcode)
     {
-        throw new LuaRuntimeException(context.State.GetTraceback(), $"OpCode {opcode} is not implemented");
+        throw new LuaRuntimeException(context.Thread.GetTraceback(), $"OpCode {opcode} is not implemented");
     }
 
 
@@ -817,19 +811,20 @@ public static partial class LuaVirtualMachine
         var top = stack.Count - 1;
         var b = instruction.B;
         var c = instruction.C;
-        stack.NotifyTop(context.FrameBase+c+1);
+        stack.NotifyTop(context.FrameBase + c + 1);
         var a = instruction.A;
-        var task =Concat(context,context.FrameBase+a,c-b+1);
+        var task = Concat(context, context.FrameBase + a, c - b + 1);
         if (task.IsCompleted)
         {
             return true;
         }
+
         context.Task = task;
         context.PostOperation = PostOperationType.None;
         return false;
     }
 
-    static async ValueTask<int> Concat(VirtualMachineExecutionContext context,int target, int total)
+    static async ValueTask<int> Concat(VirtualMachineExecutionContext context, int target, int total)
     {
         static bool ToString(ref LuaValue v)
         {
@@ -842,6 +837,7 @@ public static partial class LuaVirtualMachine
 
             return false;
         }
+
         var stack = context.Stack;
         do
         {
@@ -849,9 +845,9 @@ public static partial class LuaVirtualMachine
             var n = 2;
             ref var lhs = ref stack.Get(top - 2);
             ref var rhs = ref stack.Get(top - 1);
-            if (!(lhs.Type is LuaValueType.String or LuaValueType.Number )|| !ToString(ref rhs))
+            if (!(lhs.Type is LuaValueType.String or LuaValueType.Number) || !ToString(ref rhs))
             {
-                await ExecuteBinaryOperationMetaMethod(top - 2,lhs, rhs, context, OpCode.Concat);
+                await ExecuteBinaryOperationMetaMethod(top - 2, lhs, rhs, context, OpCode.Concat);
             }
             else if (rhs.UnsafeReadString().Length == 0)
             {
@@ -896,10 +892,11 @@ public static partial class LuaVirtualMachine
         } while (total > 1);
 
         stack.Get(target) = stack.AsSpan()[^1];
-        
+
         return 1;
     }
-    static async ValueTask ExecuteBinaryOperationMetaMethod(int target,LuaValue vb, LuaValue vc,
+
+    static async ValueTask ExecuteBinaryOperationMetaMethod(int target, LuaValue vb, LuaValue vc,
         VirtualMachineExecutionContext context, OpCode opCode)
     {
         var (name, description) = opCode.GetNameAndDescription();
@@ -926,7 +923,7 @@ public static partial class LuaVirtualMachine
 
 
             await func.Invoke(context, newFrame, 2);
-            stack.PopUntil(target+1);
+            stack.PopUntil(target + 1);
             context.Thread.PopCallStackFrame();
             context.PostOperation = PostOperationType.DontPop;
             return;
@@ -937,7 +934,6 @@ public static partial class LuaVirtualMachine
     }
 
 
-
     static bool Call(VirtualMachineExecutionContext context, out bool doRestart)
     {
         var instruction = context.Instruction;
@@ -962,7 +958,7 @@ public static partial class LuaVirtualMachine
         var thread = context.Thread;
         var (argumentCount, variableArgumentCount) = PrepareForFunctionCall(thread, func, instruction, newBase, isMetamethod);
         newBase += variableArgumentCount;
-        thread.Stack.PopUntil(newBase+argumentCount);
+        thread.Stack.PopUntil(newBase + argumentCount);
 
         var newFrame = func.CreateNewFrame(context, newBase, RA, variableArgumentCount);
 
@@ -1061,7 +1057,7 @@ public static partial class LuaVirtualMachine
 
         var (argumentCount, variableArgumentCount) = PrepareForFunctionTailCall(thread, func, instruction, newBase, isMetamethod);
         newBase = context.FrameBase + variableArgumentCount;
-        stack.PopUntil(newBase+argumentCount);
+        stack.PopUntil(newBase + argumentCount);
         var lastPc = thread.GetCurrentFrame().CallerInstructionIndex;
         context.Thread.PopCallStackFrame();
         var newFrame = func.CreateNewTailCallFrame(context, newBase, context.CurrentReturnFrameBase, variableArgumentCount);
@@ -1150,7 +1146,8 @@ public static partial class LuaVirtualMachine
             PrepareVariableArgument(stack, newBase, argumentCount, variableArgumentCount);
             newBase += variableArgumentCount;
         }
-        stack.PopUntil(newBase+argumentCount);
+
+        stack.PopUntil(newBase + argumentCount);
 
         var newFrame = iterator.CreateNewFrame(context, newBase, RA + 3, variableArgumentCount);
         context.Thread.PushCallStackFrame(newFrame);
@@ -1370,7 +1367,7 @@ public static partial class LuaVirtualMachine
             {
                 LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(context), "index", table);
             }
-            
+
             table = metatableValue;
 
         Function:
@@ -1646,16 +1643,18 @@ public static partial class LuaVirtualMachine
         int variableArgumentCount)
     {
         var top = stack.Count;
-        var newBase=stack.Count-argumentCount;
+        var newBase = stack.Count - argumentCount;
         var temp = newBase;
         newBase += variableArgumentCount;
         stack.EnsureCapacity(newBase + argumentCount);
         stack.NotifyTop(newBase + argumentCount);
         var stackBuffer = stack.GetBuffer()[temp..];
         stackBuffer[..argumentCount].CopyTo(stackBuffer[variableArgumentCount..]);
-        stackBuffer.Slice(argumentCount, variableArgumentCount).CopyTo(stackBuffer);;
+        stackBuffer.Slice(argumentCount, variableArgumentCount).CopyTo(stackBuffer);
+        ;
         stack.PopUntil(top);
     }
+
     // If there are variable arguments, the base of the stack is moved by that number and the values of the variable arguments are placed in front of it.
     // see: https://wubingzheng.github.io/build-lua-in-rust/en/ch08-02.arguments.html
     [MethodImpl(MethodImplOptions.NoInlining)]
@@ -1747,15 +1746,15 @@ public static partial class LuaVirtualMachine
 
     static Traceback GetTracebacks(VirtualMachineExecutionContext context)
     {
-        return GetTracebacks(context.State, context.Pc);
+        return GetTracebacks(context.Thread, context.Pc);
     }
 
-    static Traceback GetTracebacks(LuaState state, int pc)
+    static Traceback GetTracebacks(LuaThread thread, int pc)
     {
-        var frame = state.CurrentThread.GetCurrentFrame();
-        state.CurrentThread.PushCallStackFrame(frame with { CallerInstructionIndex = pc });
-        var tracebacks = state.GetTraceback();
-        state.CurrentThread.PopCallStackFrameWithStackPop();
+        var frame = thread.GetCurrentFrame();
+        thread.PushCallStackFrame(frame with { CallerInstructionIndex = pc });
+        var tracebacks = thread.GetTraceback();
+        thread.PopCallStackFrameWithStackPop();
         return tracebacks;
     }
 
@@ -1805,11 +1804,7 @@ public static partial class LuaVirtualMachine
     {
         return function.Func(new()
         {
-            State = context.State,
-            Thread = context.Thread,
-            ArgumentCount = arguments,
-            ReturnFrameBase = frame.ReturnBase,
-            CallerInstructionIndex = frame.CallerInstructionIndex,
+            Thread = context.Thread, ArgumentCount = arguments, ReturnFrameBase = frame.ReturnBase, CallerInstructionIndex = frame.CallerInstructionIndex,
         }, context.CancellationToken);
     }
 }

+ 4 - 3
src/Lua/Runtime/Prototype.cs

@@ -1,7 +1,8 @@
 using Lua.CodeAnalysis;
 
 namespace Lua.Runtime;
-public  sealed  class Prototype(
+
+public sealed class Prototype(
     string chunkName,
     int lineDefined,
     int lastLineDefined,
@@ -17,7 +18,7 @@ public  sealed  class Prototype(
 )
 {
     public ReadOnlySpan<LuaValue> Constants => constants;
-    public   ReadOnlySpan<Instruction> Code => code;
+    public ReadOnlySpan<Instruction> Code => code;
     public ReadOnlySpan<Prototype> ChildPrototypes => childPrototypes;
     public ReadOnlySpan<int> LineInfo => lineInfo;
     public ReadOnlySpan<LocalVariable> LocalVariables => localVariables;
@@ -26,6 +27,6 @@ public  sealed  class Prototype(
     //public LuaClosure Cache;
     public readonly string ChunkName = chunkName;
     public readonly int LineDefined = lineDefined, LastLineDefined = lastLineDefined;
-    public readonly int ParameterCount = parameterCount,  MaxStackSize = maxStackSize;
+    public readonly int ParameterCount = parameterCount, MaxStackSize = maxStackSize;
     public readonly bool HasVariableArguments = hasVariableArguments;
 }

+ 2 - 4
src/Lua/Runtime/Tracebacks.cs

@@ -1,6 +1,4 @@
 using System.Globalization;
-using System.Runtime.CompilerServices;
-using Lua.CodeAnalysis;
 using Lua.Internal;
 
 namespace Lua.Runtime;
@@ -8,7 +6,7 @@ namespace Lua.Runtime;
 public class Traceback(LuaState state)
 {
     public LuaState State => state;
-    public required LuaClosure RootFunc { get; init; }
+    public required LuaFunction RootFunc { get; init; }
     public required CallStackFrame[] StackFrames { get; init; }
 
     internal void WriteLastLuaTrace(ref PooledList<char> list)
@@ -83,7 +81,7 @@ public class Traceback(LuaState state)
         return GetTracebackString(State, RootFunc, StackFrames[..^skipFrames], LuaValue.Nil);
     }
 
-    internal static string GetTracebackString(LuaState state, LuaClosure rootFunc, ReadOnlySpan<CallStackFrame> stackFrames, LuaValue message, bool skipFirstCsharpCall = false)
+    internal static string GetTracebackString(LuaState state, LuaFunction rootFunc, ReadOnlySpan<CallStackFrame> stackFrames, LuaValue message, bool skipFirstCsharpCall = false)
     {
         using var list = new PooledList<char>(64);
         if (message.Type is not LuaValueType.Nil)

+ 12 - 12
src/Lua/Standard/BasicLibrary.cs

@@ -71,7 +71,7 @@ public sealed class BasicLibrary
                 message = context.GetArgument<string>(1);
             }
 
-            throw new LuaAssertionException(context.State.GetTraceback(), message);
+            throw new LuaAssertionException(context.Thread.GetTraceback(), message);
         }
 
         return new(context.Return(context.Arguments));
@@ -100,7 +100,7 @@ public sealed class BasicLibrary
             ? LuaValue.Nil
             : context.Arguments[0];
 
-        var traceback = context.State.GetTraceback();
+        var traceback = context.Thread.GetTraceback();
         throw new LuaRuntimeException(traceback, value);
     }
 
@@ -136,7 +136,7 @@ public sealed class BasicLibrary
         {
             if (!metamethod.TryRead<LuaFunction>(out var function))
             {
-                LuaRuntimeException.AttemptInvalidOperation(context.State.GetTraceback(), "call", metamethod);
+                LuaRuntimeException.AttemptInvalidOperation(context.Thread.GetTraceback(), "call", metamethod);
             }
 
             return function.InvokeAsync(context, cancellationToken);
@@ -200,7 +200,7 @@ public sealed class BasicLibrary
             }
             else
             {
-                LuaRuntimeException.BadArgument(context.State.GetTraceback(), 1, "load");
+                LuaRuntimeException.BadArgument(context.Thread.GetTraceback(), 1, "load");
                 return default; // dummy
             }
         }
@@ -234,7 +234,7 @@ public sealed class BasicLibrary
         {
             if (!metamethod.TryRead<LuaFunction>(out var function))
             {
-                LuaRuntimeException.AttemptInvalidOperation(context.State.GetTraceback(), "call", metamethod);
+                LuaRuntimeException.AttemptInvalidOperation(context.Thread.GetTraceback(), "call", metamethod);
             }
 
             return function.InvokeAsync(context, cancellationToken);
@@ -308,7 +308,7 @@ public sealed class BasicLibrary
         }
         else
         {
-            LuaRuntimeException.BadArgument(context.State.GetTraceback(), 2, "rawlen", [LuaValueType.String, LuaValueType.Table]);
+            LuaRuntimeException.BadArgument(context.Thread.GetTraceback(), 2, "rawlen", [LuaValueType.String, LuaValueType.Table]);
             return default;
         }
     }
@@ -331,7 +331,7 @@ public sealed class BasicLibrary
         {
             if (Math.Abs(index) > context.ArgumentCount)
             {
-                throw new LuaRuntimeException(context.State.GetTraceback(), "bad argument #1 to 'select' (index out of range)");
+                throw new LuaRuntimeException(context.Thread.GetTraceback(), "bad argument #1 to 'select' (index out of range)");
             }
 
             var span = index >= 0
@@ -346,7 +346,7 @@ public sealed class BasicLibrary
         }
         else
         {
-            LuaRuntimeException.BadArgument(context.State.GetTraceback(), 1, "select", "number", arg0.Type.ToString());
+            LuaRuntimeException.BadArgument(context.Thread.GetTraceback(), 1, "select", "number", arg0.Type.ToString());
             return default;
         }
     }
@@ -358,12 +358,12 @@ public sealed class BasicLibrary
 
         if (arg1.Type is not (LuaValueType.Nil or LuaValueType.Table))
         {
-            LuaRuntimeException.BadArgument(context.State.GetTraceback(), 2, "setmetatable", [LuaValueType.Nil, LuaValueType.Table]);
+            LuaRuntimeException.BadArgument(context.Thread.GetTraceback(), 2, "setmetatable", [LuaValueType.Nil, LuaValueType.Table]);
         }
 
         if (arg0.Metatable != null && arg0.Metatable.TryGetValue(Metamethods.Metatable, out _))
         {
-            throw new LuaRuntimeException(context.State.GetTraceback(), "cannot change a protected metatable");
+            throw new LuaRuntimeException(context.Thread.GetTraceback(), "cannot change a protected metatable");
         }
         else if (arg1.Type is LuaValueType.Nil)
         {
@@ -387,7 +387,7 @@ public sealed class BasicLibrary
 
         if (toBase != null && (toBase < 2 || toBase > 36))
         {
-            throw new LuaRuntimeException(context.State.GetTraceback(), "bad argument #2 to 'tonumber' (base out of range)");
+            throw new LuaRuntimeException(context.Thread.GetTraceback(), "bad argument #2 to 'tonumber' (base out of range)");
         }
 
         double? value = null;
@@ -557,7 +557,7 @@ public sealed class BasicLibrary
         {
             var error = ex is LuaRuntimeException luaEx ? luaEx.ErrorObject : ex.Message;
 
-            context.State.Push(error);
+            context.Thread.Push(error);
 
             // invoke error handler
             var count = await arg1.InvokeAsync(context with { ArgumentCount = 1, ReturnFrameBase = context.ReturnFrameBase + 1 }, cancellationToken);

+ 28 - 28
src/Lua/Standard/BitwiseLibrary.cs

@@ -32,8 +32,8 @@ public sealed class BitwiseLibrary
         var x = context.GetArgument<double>(0);
         var disp = context.GetArgument<double>(1);
 
-        LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.State, "arshift", 1, x);
-        LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.State, "arshift", 2, disp);
+        LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.Thread, "arshift", 1, x);
+        LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.Thread, "arshift", 2, disp);
 
         var v = Bit32Helper.ToInt32(x);
         var a = (int)disp;
@@ -59,14 +59,14 @@ public sealed class BitwiseLibrary
         }
 
         var arg0 = context.GetArgument<double>(0);
-        LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.State, "band", 1, arg0);
+        LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.Thread, "band", 1, arg0);
 
         var value = Bit32Helper.ToUInt32(arg0);
 
         for (int i = 1; i < context.ArgumentCount; i++)
         {
             var arg = context.GetArgument<double>(i);
-            LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.State, "band", 1 + i, arg);
+            LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.Thread, "band", 1 + i, arg);
 
             var v = Bit32Helper.ToUInt32(arg);
             value &= v;
@@ -79,7 +79,7 @@ public sealed class BitwiseLibrary
     public ValueTask<int> BNot(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
     {
         var arg0 = context.GetArgument<double>(0);
-        LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.State, "bnot", 1, arg0);
+        LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.Thread, "bnot", 1, arg0);
 
         var value = Bit32Helper.ToUInt32(arg0);
         return new(context.Return(~value));
@@ -93,14 +93,14 @@ public sealed class BitwiseLibrary
         }
 
         var arg0 = context.GetArgument<double>(0);
-        LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.State, "bor", 1, arg0);
+        LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.Thread, "bor", 1, arg0);
 
         var value = Bit32Helper.ToUInt32(arg0);
 
         for (int i = 1; i < context.ArgumentCount; i++)
         {
             var arg = context.GetArgument<double>(i);
-            LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.State, "bor", 1 + i, arg);
+            LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.Thread, "bor", 1 + i, arg);
 
             var v = Bit32Helper.ToUInt32(arg);
             value |= v;
@@ -118,14 +118,14 @@ public sealed class BitwiseLibrary
         }
 
         var arg0 = context.GetArgument<double>(0);
-        LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.State, "btest", 1, arg0);
+        LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.Thread, "btest", 1, arg0);
 
         var value = Bit32Helper.ToUInt32(arg0);
 
         for (int i = 1; i < context.ArgumentCount; i++)
         {
             var arg = context.GetArgument<double>(i);
-            LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.State, "btest", 1 + i, arg);
+            LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.Thread, "btest", 1 + i, arg);
 
             var v = Bit32Helper.ToUInt32(arg);
             value &= v;
@@ -142,14 +142,14 @@ public sealed class BitwiseLibrary
         }
 
         var arg0 = context.GetArgument<double>(0);
-        LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.State, "bxor", 1, arg0);
+        LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.Thread, "bxor", 1, arg0);
 
         var value = Bit32Helper.ToUInt32(arg0);
 
         for (int i = 1; i < context.ArgumentCount; i++)
         {
             var arg = context.GetArgument<double>(i);
-            LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.State, "bxor", 1 + i, arg);
+            LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.Thread, "bxor", 1 + i, arg);
 
             var v = Bit32Helper.ToUInt32(arg);
             value ^= v;
@@ -166,15 +166,15 @@ public sealed class BitwiseLibrary
             ? context.GetArgument<double>(2)
             : 1;
 
-        LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.State, "extract", 1, arg0);
-        LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.State, "extract", 2, arg1);
-        LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.State, "extract", 3, arg2);
+        LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.Thread, "extract", 1, arg0);
+        LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.Thread, "extract", 2, arg1);
+        LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.Thread, "extract", 3, arg2);
 
         var n = Bit32Helper.ToUInt32(arg0);
         var field = (int)arg1;
         var width = (int)arg2;
 
-        Bit32Helper.ValidateFieldAndWidth(context.State, "extract", 2, field, width);
+        Bit32Helper.ValidateFieldAndWidth(context.Thread, "extract", 2, field, width);
 
         if (field == 0 && width == 32)
         {
@@ -192,8 +192,8 @@ public sealed class BitwiseLibrary
         var x = context.GetArgument<double>(0);
         var disp = context.GetArgument<double>(1);
 
-        LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.State, "lrotate", 1, x);
-        LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.State, "lrotate", 2, disp);
+        LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.Thread, "lrotate", 1, x);
+        LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.Thread, "lrotate", 2, disp);
 
         var v = Bit32Helper.ToUInt32(x);
         var a = ((int)disp) % 32;
@@ -216,8 +216,8 @@ public sealed class BitwiseLibrary
         var x = context.GetArgument<double>(0);
         var disp = context.GetArgument<double>(1);
 
-        LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.State, "lshift", 1, x);
-        LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.State, "lshift", 2, disp);
+        LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.Thread, "lshift", 1, x);
+        LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.Thread, "lshift", 2, disp);
 
         var v = Bit32Helper.ToUInt32(x);
         var a = (int)disp;
@@ -247,17 +247,17 @@ public sealed class BitwiseLibrary
             ? context.GetArgument<double>(3)
             : 1;
 
-        LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.State, "replace", 1, arg0);
-        LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.State, "replace", 2, arg1);
-        LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.State, "replace", 3, arg2);
-        LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.State, "replace", 4, arg3);
+        LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.Thread, "replace", 1, arg0);
+        LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.Thread, "replace", 2, arg1);
+        LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.Thread, "replace", 3, arg2);
+        LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.Thread, "replace", 4, arg3);
 
         var n = Bit32Helper.ToUInt32(arg0);
         var v = Bit32Helper.ToUInt32(arg1);
         var field = (int)arg2;
         var width = (int)arg3;
 
-        Bit32Helper.ValidateFieldAndWidth(context.State, "replace", 2, field, width);
+        Bit32Helper.ValidateFieldAndWidth(context.Thread, "replace", 2, field, width);
         uint mask;
         if (width == 32)
         {
@@ -278,8 +278,8 @@ public sealed class BitwiseLibrary
         var x = context.GetArgument<double>(0);
         var disp = context.GetArgument<double>(1);
 
-        LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.State, "rrotate", 1, x);
-        LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.State, "rrotate", 2, disp);
+        LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.Thread, "rrotate", 1, x);
+        LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.Thread, "rrotate", 2, disp);
 
         var v = Bit32Helper.ToUInt32(x);
         var a = ((int)disp) % 32;
@@ -301,8 +301,8 @@ public sealed class BitwiseLibrary
         var x = context.GetArgument<double>(0);
         var disp = context.GetArgument<double>(1);
 
-        LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.State, "rshift", 1, x);
-        LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.State, "rshift", 2, disp);
+        LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.Thread, "rshift", 1, x);
+        LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.Thread, "rshift", 2, disp);
 
         var v = Bit32Helper.ToUInt32(x);
         var a = (int)disp;

+ 3 - 3
src/Lua/Standard/CoroutineLibrary.cs

@@ -24,13 +24,13 @@ public sealed class CoroutineLibrary
     public ValueTask<int> Create(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
     {
         var arg0 = context.GetArgument<LuaFunction>(0);
-        return new(context.Return(new LuaCoroutine(arg0, true)));
+        return new(context.Return(LuaCoroutine.Create(context.Thread, arg0, true)));
     }
 
     public ValueTask<int> Resume(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
     {
         var thread = context.GetArgument<LuaThread>(0);
-        return thread.ResumeAsync(context with{ArgumentCount = context.ArgumentCount-1}, cancellationToken);
+        return thread.ResumeAsync(context with { ArgumentCount = context.ArgumentCount - 1 }, cancellationToken);
     }
 
     public ValueTask<int> Running(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
@@ -55,7 +55,7 @@ public sealed class CoroutineLibrary
     public ValueTask<int> Wrap(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
     {
         var arg0 = context.GetArgument<LuaFunction>(0);
-        var thread = new LuaCoroutine(arg0, false);
+        var thread = LuaCoroutine.Create(context.Thread, arg0, false);
         return new(context.Return(new CSharpClosure("wrap", [thread],
             static async (context, cancellationToken) =>
             {

+ 6 - 12
src/Lua/Standard/DebugLibrary.cs

@@ -96,11 +96,11 @@ public class DebugLibrary
                 }
             }
 
-            var localId = index+1;
+            var localId = index + 1;
             foreach (var l in locals)
             {
-                if(currentPc<l.StartPc)break;
-                if(l.EndPc<=currentPc)continue;
+                if (currentPc < l.StartPc) break;
+                if (l.EndPc <= currentPc) continue;
                 localId--;
                 if (localId == 0)
                 {
@@ -277,7 +277,7 @@ public class DebugLibrary
 
         if (arg1.Type is not (LuaValueType.Nil or LuaValueType.Table))
         {
-            LuaRuntimeException.BadArgument(context.State.GetTraceback(), 2, "setmetatable", [LuaValueType.Nil, LuaValueType.Table]);
+            LuaRuntimeException.BadArgument(context.Thread.GetTraceback(), 2, "setmetatable", [LuaValueType.Nil, LuaValueType.Table]);
         }
 
         context.State.SetMetatable(arg0, arg1.UnsafeRead<LuaTable>());
@@ -354,7 +354,7 @@ public class DebugLibrary
 
         var skipCount = Math.Min(Math.Max(level - 1, 0), callStack.Length - 1);
         var frames = callStack[1..^skipCount];
-        return new(context.Return(Runtime.Traceback.GetTracebackString(context.State, (LuaClosure)callStack[0].Function, frames, message, level == 1)));
+        return new(context.Return(Runtime.Traceback.GetTracebackString(context.State, callStack[0].Function, frames, message, level == 1)));
     }
 
     public ValueTask<int> GetRegistry(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
@@ -458,13 +458,7 @@ public class DebugLibrary
             var stack = thread.Stack;
             stack.Push("return");
             stack.Push(LuaValue.Nil);
-            var funcContext = new LuaFunctionExecutionContext
-            {
-                State = context.State,
-                Thread = context.Thread,
-                ArgumentCount = 2,
-                ReturnFrameBase = stack.Count - 2,
-            };
+            var funcContext = new LuaFunctionExecutionContext { Thread = context.Thread, ArgumentCount = 2, ReturnFrameBase = stack.Count - 2, };
             var frame = new CallStackFrame
             {
                 Base = funcContext.FrameBase, ReturnBase = funcContext.ReturnFrameBase, VariableArgumentCount = hook.GetVariableArgumentCount(2), Function = hook,

+ 3 - 3
src/Lua/Standard/FileHandle.cs

@@ -172,7 +172,7 @@ public class FileHandle : ILuaUserData
             var upValues = context.GetCsClosure()!.UpValues.AsSpan();
             var file = upValues[0].Read<FileHandle>();
             context.Return();
-            var resultCount = IOHelper.Read(context.State, file, "lines", 0, upValues[1..], context.Thread.Stack, true);
+            var resultCount = IOHelper.Read(context.Thread, file, "lines", 0, upValues[1..], true);
             return new(resultCount);
         })));
     });
@@ -181,7 +181,7 @@ public class FileHandle : ILuaUserData
     {
         var file = context.GetArgument<FileHandle>(0);
         context.Return();
-        var resultCount = IOHelper.Read(context.State, file, "read", 1, context.Arguments[1..], context.Thread.Stack, false);
+        var resultCount = IOHelper.Read(context.Thread, file, "read", 1, context.Arguments[1..], false);
         return new(resultCount);
     });
 
@@ -197,7 +197,7 @@ public class FileHandle : ILuaUserData
 
         if (whence is not ("set" or "cur" or "end"))
         {
-            throw new LuaRuntimeException(context.State.GetTraceback(), $"bad argument #2 to 'seek' (invalid option '{whence}')");
+            throw new LuaRuntimeException(context.Thread.GetTraceback(), $"bad argument #2 to 'seek' (invalid option '{whence}')");
         }
 
         try

+ 5 - 5
src/Lua/Standard/IOLibrary.cs

@@ -91,7 +91,7 @@ public sealed class IOLibrary
             {
                 var file = context.GetCsClosure()!.UpValues[0].Read<FileHandle>();
                 context.Return();
-                var resultCount = IOHelper.Read(context.State, file, "lines", 0, [], context.Thread.Stack, true);
+                var resultCount = IOHelper.Read(context.Thread, file, "lines", 0, [], true);
                 if (resultCount > 0 && context.Thread.Stack.Get(context.ReturnFrameBase).Type is LuaValueType.Nil)
                 {
                     file.Close();
@@ -106,7 +106,7 @@ public sealed class IOLibrary
             var stack = context.Thread.Stack;
             context.Return();
 
-            IOHelper.Open(context.State, fileName, "r", stack, true);
+            IOHelper.Open(context.Thread, fileName, "r", true);
 
             var file = stack.Get(context.ReturnFrameBase).Read<FileHandle>();
             var upValues = new LuaValue[context.Arguments.Length];
@@ -120,7 +120,7 @@ public sealed class IOLibrary
                 var formats = upValues.AsSpan(1);
                 var stack = context.Thread.Stack;
                 context.Return();
-                var resultCount = IOHelper.Read(context.State, file, "lines", 0, formats, stack, true);
+                var resultCount = IOHelper.Read(context.Thread, file, "lines", 0, formats, true);
                 if (resultCount > 0 && stack.Get(context.ReturnFrameBase).Type is LuaValueType.Nil)
                 {
                     file.Close();
@@ -138,7 +138,7 @@ public sealed class IOLibrary
             ? context.GetArgument<string>(1)
             : "r";
         context.Return();
-        var resultCount = IOHelper.Open(context.State, fileName, mode, context.Thread.Stack, false);
+        var resultCount = IOHelper.Open(context.Thread, fileName, mode, false);
         return new(resultCount);
     }
 
@@ -172,7 +172,7 @@ public sealed class IOLibrary
         context.Return();
         var stack = context.Thread.Stack;
 
-        var resultCount = IOHelper.Read(context.State, file, "read", 0, context.Arguments, stack, false);
+        var resultCount = IOHelper.Read(context.Thread, file, "read", 0, context.Arguments, false);
         return new(resultCount);
     }
 

+ 4 - 4
src/Lua/Standard/Internal/Bit32Helper.cs

@@ -18,15 +18,15 @@ internal static class Bit32Helper
         return (int)(long)Math.IEEERemainder(d, Bit32);
     }
 
-    public static void ValidateFieldAndWidth(LuaState state, string functionName, int argumentId, int field, int width)
+    public static void ValidateFieldAndWidth(LuaThread thread, string functionName, int argumentId, int field, int width)
     {
         if (field > 31 || (field + width) > 32)
-            throw new LuaRuntimeException(state.GetTraceback(), "trying to access non-existent bits");
+            throw new LuaRuntimeException(thread.GetTraceback(), "trying to access non-existent bits");
 
         if (field < 0)
-            throw new LuaRuntimeException(state.GetTraceback(), $"bad argument #{argumentId} to '{functionName}' (field cannot be negative)");
+            throw new LuaRuntimeException(thread.GetTraceback(), $"bad argument #{argumentId} to '{functionName}' (field cannot be negative)");
 
         if (width <= 0)
-            throw new LuaRuntimeException(state.GetTraceback(), $"bad argument #{argumentId} to '{functionName}' (width must be positive)");
+            throw new LuaRuntimeException(thread.GetTraceback(), $"bad argument #{argumentId} to '{functionName}' (width must be positive)");
     }
 }

+ 12 - 12
src/Lua/Standard/Internal/DateTimeHelper.cs

@@ -26,15 +26,15 @@ internal static class DateTimeHelper
         return DateTime.UnixEpoch + ts;
     }
 
-    public static DateTime ParseTimeTable(LuaState state, LuaTable table)
+    public static DateTime ParseTimeTable(LuaThread thread, LuaTable table)
     {
-        static int GetTimeField(LuaState state, LuaTable table, string key, bool required = true, int defaultValue = 0)
+        static int GetTimeField(LuaThread thread, LuaTable table, string key, bool required = true, int defaultValue = 0)
         {
             if (!table.TryGetValue(key, out var value))
             {
                 if (required)
                 {
-                    throw new LuaRuntimeException(state.GetTraceback(), $"field '{key}' missing in date table");
+                    throw new LuaRuntimeException(thread.GetTraceback(), $"field '{key}' missing in date table");
                 }
                 else
                 {
@@ -47,20 +47,20 @@ internal static class DateTimeHelper
                 return (int)d;
             }
 
-            throw new LuaRuntimeException(state.GetTraceback(), $"field '{key}' is not an integer");
+            throw new LuaRuntimeException(thread.GetTraceback(), $"field '{key}' is not an integer");
         }
 
-        var day = GetTimeField(state, table, "day");
-        var month = GetTimeField(state, table, "month");
-        var year = GetTimeField(state, table, "year");
-        var sec = GetTimeField(state, table, "sec", false, 0);
-        var min = GetTimeField(state, table, "min", false, 0);
-        var hour = GetTimeField(state, table, "hour", false, 12);
+        var day = GetTimeField(thread, table, "day");
+        var month = GetTimeField(thread, table, "month");
+        var year = GetTimeField(thread, table, "year");
+        var sec = GetTimeField(thread, table, "sec", false, 0);
+        var min = GetTimeField(thread, table, "min", false, 0);
+        var hour = GetTimeField(thread, table, "hour", false, 12);
 
         return new DateTime(year, month, day, hour, min, sec);
     }
 
-    public static string StrFTime(LuaState state, ReadOnlySpan<char> format, DateTime d)
+    public static string StrFTime(LuaThread thread, ReadOnlySpan<char> format, DateTime d)
     {
         // reference: http://www.cplusplus.com/reference/ctime/strftime/
 
@@ -186,7 +186,7 @@ internal static class DateTimeHelper
             }
             else
             {
-                throw new LuaRuntimeException(state.GetTraceback(), $"bad argument #1 to 'date' (invalid conversion specifier '{format.ToString()}')");
+                throw new LuaRuntimeException(thread.GetTraceback(), $"bad argument #1 to 'date' (invalid conversion specifier '{format.ToString()}')");
             }
         }
 

+ 10 - 9
src/Lua/Standard/Internal/IOHelper.cs

@@ -6,14 +6,14 @@ namespace Lua.Standard.Internal;
 
 internal static class IOHelper
 {
-    public static int Open(LuaState state, string fileName, string mode, LuaStack stack, bool throwError)
+    public static int Open(LuaThread thread, string fileName, string mode, bool throwError)
     {
         var fileMode = mode switch
         {
             "r" or "rb" or "r+" or "r+b" => FileMode.Open,
             "w" or "wb" or "w+" or "w+b" => FileMode.Create,
             "a" or "ab" or "a+" or "a+b" => FileMode.Append,
-            _ => throw new LuaRuntimeException(state.GetTraceback(), "bad argument #2 to 'open' (invalid mode)"),
+            _ => throw new LuaRuntimeException(thread.GetTraceback(), "bad argument #2 to 'open' (invalid mode)"),
         };
 
         var fileAccess = mode switch
@@ -26,7 +26,7 @@ internal static class IOHelper
         try
         {
             var stream = File.Open(fileName, fileMode, fileAccess);
-            stack.Push(new LuaValue(new FileHandle(stream)));
+            thread.Push(new LuaValue(new FileHandle(stream)));
             return 1;
         }
         catch (IOException ex)
@@ -36,9 +36,9 @@ internal static class IOHelper
                 throw;
             }
 
-            stack.Push(LuaValue.Nil);
-            stack.Push(ex.Message);
-            stack.Push(ex.HResult);
+            thread.Push(LuaValue.Nil);
+            thread.Push(ex.Message);
+            thread.Push(ex.HResult);
             return 3;
         }
     }
@@ -65,7 +65,7 @@ internal static class IOHelper
                 }
                 else
                 {
-                    LuaRuntimeException.BadArgument(context.State.GetTraceback(), i + 1, name);
+                    LuaRuntimeException.BadArgument(context.Thread.GetTraceback(), i + 1, name);
                 }
             }
         }
@@ -84,13 +84,14 @@ internal static class IOHelper
 
     static readonly LuaValue[] defaultReadFormat = ["*l"];
 
-    public static int Read(LuaState state, FileHandle file, string name, int startArgumentIndex, ReadOnlySpan<LuaValue> formats, LuaStack stack, bool throwError)
+    public static int Read(LuaThread thread, FileHandle file, string name, int startArgumentIndex, ReadOnlySpan<LuaValue> formats, bool throwError)
     {
         if (formats.Length == 0)
         {
             formats = defaultReadFormat;
         }
 
+        var stack = thread.Stack;
         var top = stack.Count;
 
         try
@@ -142,7 +143,7 @@ internal static class IOHelper
                 }
                 else
                 {
-                    LuaRuntimeException.BadArgument(state.GetTraceback(), i + 1, name);
+                    LuaRuntimeException.BadArgument(thread.GetTraceback(), i + 1, name);
                 }
             }
 

+ 3 - 3
src/Lua/Standard/OperatingSystemLibrary.cs

@@ -78,7 +78,7 @@ public sealed class OperatingSystemLibrary
         }
         else
         {
-            return new(context.Return(DateTimeHelper.StrFTime(context.State, format, now)));
+            return new(context.Return(DateTimeHelper.StrFTime(context.Thread, format, now)));
         }
     }
 
@@ -121,7 +121,7 @@ public sealed class OperatingSystemLibrary
             }
             else
             {
-                LuaRuntimeException.BadArgument(context.State.GetTraceback(), 1, "exit", LuaValueType.Nil.ToString(), code.Type.ToString());
+                LuaRuntimeException.BadArgument(context.Thread.GetTraceback(), 1, "exit", LuaValueType.Nil.ToString(), code.Type.ToString());
             }
         }
         else
@@ -179,7 +179,7 @@ public sealed class OperatingSystemLibrary
         if (context.HasArgument(0))
         {
             var table = context.GetArgument<LuaTable>(0);
-            var date = DateTimeHelper.ParseTimeTable(context.State, table);
+            var date = DateTimeHelper.ParseTimeTable(context.Thread, table);
             return new(context.Return(DateTimeHelper.GetUnixTime(date)));
         }
         else

+ 23 - 23
src/Lua/Standard/StringLibrary.cs

@@ -42,8 +42,8 @@ public sealed class StringLibrary
             ? context.GetArgument<double>(2)
             : i;
 
-        LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.State, "byte", 2, i);
-        LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.State, "byte", 3, j);
+        LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.Thread, "byte", 2, i);
+        LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.Thread, "byte", 3, j);
 
         var span = StringHelper.Slice(s, (int)i, (int)j);
         var buffer = context.GetReturnBuffer(span.Length);
@@ -66,7 +66,7 @@ public sealed class StringLibrary
         for (int i = 0; i < context.ArgumentCount; i++)
         {
             var arg = context.GetArgument<double>(i);
-            LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.State, "char", i + 1, arg);
+            LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.Thread, "char", i + 1, arg);
             builder.Append((char)arg);
         }
 
@@ -88,7 +88,7 @@ public sealed class StringLibrary
             : 1;
         var plain = context.HasArgument(3) && context.GetArgument(3).ToBoolean();
 
-        LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.State, "find", 3, init);
+        LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.Thread, "find", 3, init);
 
         // init can be negative value
         if (init < 0)
@@ -174,23 +174,23 @@ public sealed class StringLibrary
                     switch (c)
                     {
                         case '-':
-                            if (leftJustify) throw new LuaRuntimeException(context.State.GetTraceback(), "invalid format (repeated flags)");
+                            if (leftJustify) throw new LuaRuntimeException(context.Thread.GetTraceback(), "invalid format (repeated flags)");
                             leftJustify = true;
                             break;
                         case '+':
-                            if (plusSign) throw new LuaRuntimeException(context.State.GetTraceback(), "invalid format (repeated flags)");
+                            if (plusSign) throw new LuaRuntimeException(context.Thread.GetTraceback(), "invalid format (repeated flags)");
                             plusSign = true;
                             break;
                         case '0':
-                            if (zeroPadding) throw new LuaRuntimeException(context.State.GetTraceback(), "invalid format (repeated flags)");
+                            if (zeroPadding) throw new LuaRuntimeException(context.Thread.GetTraceback(), "invalid format (repeated flags)");
                             zeroPadding = true;
                             break;
                         case '#':
-                            if (alternateForm) throw new LuaRuntimeException(context.State.GetTraceback(), "invalid format (repeated flags)");
+                            if (alternateForm) throw new LuaRuntimeException(context.Thread.GetTraceback(), "invalid format (repeated flags)");
                             alternateForm = true;
                             break;
                         case ' ':
-                            if (blank) throw new LuaRuntimeException(context.State.GetTraceback(), "invalid format (repeated flags)");
+                            if (blank) throw new LuaRuntimeException(context.Thread.GetTraceback(), "invalid format (repeated flags)");
                             blank = true;
                             break;
                         default:
@@ -208,7 +208,7 @@ public sealed class StringLibrary
                 {
                     i++;
                     if (char.IsDigit(format[i])) i++;
-                    if (char.IsDigit(format[i])) throw new LuaRuntimeException(context.State.GetTraceback(), "invalid format (width or precision too long)");
+                    if (char.IsDigit(format[i])) throw new LuaRuntimeException(context.Thread.GetTraceback(), "invalid format (width or precision too long)");
                     width = int.Parse(format.AsSpan()[start..i]);
                 }
 
@@ -219,7 +219,7 @@ public sealed class StringLibrary
                     start = i;
                     if (char.IsDigit(format[i])) i++;
                     if (char.IsDigit(format[i])) i++;
-                    if (char.IsDigit(format[i])) throw new LuaRuntimeException(context.State.GetTraceback(), "invalid format (width or precision too long)");
+                    if (char.IsDigit(format[i])) throw new LuaRuntimeException(context.Thread.GetTraceback(), "invalid format (width or precision too long)");
                     precision = int.Parse(format.AsSpan()[start..i]);
                 }
 
@@ -228,7 +228,7 @@ public sealed class StringLibrary
 
                 if (context.ArgumentCount <= parameterIndex)
                 {
-                    throw new LuaRuntimeException(context.State.GetTraceback(), $"bad argument #{parameterIndex + 1} to 'format' (no value)");
+                    throw new LuaRuntimeException(context.Thread.GetTraceback(), $"bad argument #{parameterIndex + 1} to 'format' (no value)");
                 }
 
                 var parameter = context.GetArgument(parameterIndex++);
@@ -243,7 +243,7 @@ public sealed class StringLibrary
                     case 'G':
                         if (!parameter.TryRead<double>(out var f))
                         {
-                            LuaRuntimeException.BadArgument(context.State.GetTraceback(), parameterIndex + 1, "format", LuaValueType.Number.ToString(), parameter.Type.ToString());
+                            LuaRuntimeException.BadArgument(context.Thread.GetTraceback(), parameterIndex + 1, "format", LuaValueType.Number.ToString(), parameter.Type.ToString());
                         }
 
                         switch (specifier)
@@ -324,10 +324,10 @@ public sealed class StringLibrary
                     case 'X':
                         if (!parameter.TryRead<double>(out var x))
                         {
-                            LuaRuntimeException.BadArgument(context.State.GetTraceback(), parameterIndex + 1, "format", LuaValueType.Number.ToString(), parameter.Type.ToString());
+                            LuaRuntimeException.BadArgument(context.Thread.GetTraceback(), parameterIndex + 1, "format", LuaValueType.Number.ToString(), parameter.Type.ToString());
                         }
 
-                        LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.State, "format", parameterIndex + 1, x);
+                        LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.Thread, "format", parameterIndex + 1, x);
 
                         switch (specifier)
                         {
@@ -382,7 +382,7 @@ public sealed class StringLibrary
 
                         break;
                     default:
-                        throw new LuaRuntimeException(context.State.GetTraceback(), $"invalid option '%{specifier}' to 'format'");
+                        throw new LuaRuntimeException(context.Thread.GetTraceback(), $"invalid option '%{specifier}' to 'format'");
                 }
 
                 // Apply blank (' ') flag for positive numbers
@@ -469,7 +469,7 @@ public sealed class StringLibrary
             ? context.GetArgument<double>(3)
             : int.MaxValue;
 
-        LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.State, "gsub", 4, n_arg);
+        LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.Thread, "gsub", 4, n_arg);
 
         var n = (int)n_arg;
         var regex = StringHelper.ToRegex(pattern);
@@ -509,7 +509,7 @@ public sealed class StringLibrary
             {
                 for (int k = 1; k <= match.Groups.Count; k++)
                 {
-                    context.State.Push(match.Groups[k].Value);
+                    context.Thread.Push(match.Groups[k].Value);
                 }
 
 
@@ -519,7 +519,7 @@ public sealed class StringLibrary
             }
             else
             {
-                throw new LuaRuntimeException(context.State.GetTraceback(), "bad argument #3 to 'gsub' (string/function/table expected)");
+                throw new LuaRuntimeException(context.Thread.GetTraceback(), "bad argument #3 to 'gsub' (string/function/table expected)");
             }
 
             if (result.TryRead<string>(out var rs))
@@ -537,7 +537,7 @@ public sealed class StringLibrary
             }
             else
             {
-                throw new LuaRuntimeException(context.State.GetTraceback(), $"invalid replacement value (a {result.Type})");
+                throw new LuaRuntimeException(context.Thread.GetTraceback(), $"invalid replacement value (a {result.Type})");
             }
 
             lastIndex = match.Index + match.Length;
@@ -568,7 +568,7 @@ public sealed class StringLibrary
             ? context.GetArgument<string>(2)
             : null;
 
-        LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.State, "rep", 2, n_arg);
+        LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.Thread, "rep", 2, n_arg);
 
         var n = (int)n_arg;
 
@@ -603,8 +603,8 @@ public sealed class StringLibrary
             ? context.GetArgument<double>(2)
             : -1;
 
-        LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.State, "sub", 2, i);
-        LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.State, "sub", 3, j);
+        LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.Thread, "sub", 2, i);
+        LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.Thread, "sub", 3, j);
 
         return new(context.Return(StringHelper.Slice(s, (int)i, (int)j).ToString()));
     }

+ 8 - 7
src/Lua/Standard/TableLibrary.cs

@@ -2,6 +2,7 @@ using Lua.CodeAnalysis;
 using System.Runtime.CompilerServices;
 using System.Text;
 using Lua.Runtime;
+using System.Globalization;
 
 namespace Lua.Standard;
 
@@ -63,11 +64,11 @@ public sealed class TableLibrary
             }
             else if (value.Type is LuaValueType.Number)
             {
-                builder.Append(value.Read<double>().ToString());
+                builder.Append(value.Read<double>().ToString(CultureInfo.InvariantCulture));
             }
             else
             {
-                throw new LuaRuntimeException(context.State.GetTraceback(), $"invalid value ({value.Type}) at index {i} in table for 'concat'");
+                throw new LuaRuntimeException(context.Thread.GetTraceback(), $"invalid value ({value.Type}) at index {i} in table for 'concat'");
             }
 
             if (i != arg3) builder.Append(arg1);
@@ -88,13 +89,13 @@ public sealed class TableLibrary
             ? context.GetArgument<double>(1)
             : table.ArrayLength + 1;
 
-        LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.State, "insert", 2, pos_arg);
+        LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.Thread, "insert", 2, pos_arg);
 
         var pos = (int)pos_arg;
 
         if (pos <= 0 || pos > table.ArrayLength + 1)
         {
-            throw new LuaRuntimeException(context.State.GetTraceback(), "bad argument #2 to 'insert' (position out of bounds)");
+            throw new LuaRuntimeException(context.Thread.GetTraceback(), "bad argument #2 to 'insert' (position out of bounds)");
         }
 
         table.Insert(pos, value);
@@ -123,7 +124,7 @@ public sealed class TableLibrary
             ? context.GetArgument<double>(1)
             : table.ArrayLength;
 
-        LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.State, "remove", 2, n_arg);
+        LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.Thread, "remove", 2, n_arg);
 
         var n = (int)n_arg;
 
@@ -134,7 +135,7 @@ public sealed class TableLibrary
                 return new(context.Return(LuaValue.Nil));
             }
 
-            throw new LuaRuntimeException(context.State.GetTraceback(), "bad argument #2 to 'remove' (position out of bounds)");
+            throw new LuaRuntimeException(context.Thread.GetTraceback(), "bad argument #2 to 'remove' (position out of bounds)");
         }
         else if (n > table.ArrayLength)
         {
@@ -149,7 +150,7 @@ public sealed class TableLibrary
         var arg0 = context.GetArgument<LuaTable>(0);
         var arg1 = context.HasArgument(1)
             ? context.GetArgument<LuaFunction>(1)
-            : new LuaClosure(context.State, defaultComparer);
+            : new LuaClosure(context.Thread, defaultComparer);
 
         // discard extra  arguments
         context = context with { ArgumentCount = 2 };

+ 1 - 0
tests/Lua.Tests/LuaObjectTests.cs

@@ -26,6 +26,7 @@ public partial class TestUserData
     [LuaMember]
     public static double StaticMethodWithReturnValue(double a, double b)
     {
+        Console.WriteLine($"HEY! {a} {b}");
         return a + b;
     }
 

+ 2 - 0
tests/Lua.Tests/MetatableTests.cs

@@ -46,6 +46,7 @@ return a + b
             Assert.That(table[3].Read<double>(), Is.EqualTo(9));
         });
     }
+
     [Test]
     public async Task Test_Metamethod_Concat()
     {
@@ -79,6 +80,7 @@ return a .. b
             Assert.That(table[3].Read<double>(), Is.EqualTo(9));
         });
     }
+
     [Test]
     public async Task Test_Metamethod_Index()
     {

+ 0 - 1
tests/Lua.Tests/StringTests.cs

@@ -1,4 +1,3 @@
-
 using Lua.Standard;
 using System.Globalization;