LuaState.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511
  1. using Lua.CodeAnalysis.Compilation;
  2. using System.Runtime.CompilerServices;
  3. using Lua.Internal;
  4. using Lua.Platforms;
  5. using Lua.Runtime;
  6. using System.Buffers;
  7. namespace Lua;
  8. public class LuaState : IDisposable
  9. {
  10. internal LuaState(LuaGlobalState globalState)
  11. {
  12. GlobalState = globalState;
  13. CoreData = ThreadCoreData.Create();
  14. }
  15. internal LuaState(LuaGlobalState globalState, LuaFunction function, bool isProtectedMode)
  16. {
  17. GlobalState = globalState;
  18. CoreData = ThreadCoreData.Create();
  19. coroutine = new(this, function, isProtectedMode);
  20. }
  21. public static LuaState Create()
  22. {
  23. var globalState = LuaGlobalState.Create();
  24. return globalState.MainThread;
  25. }
  26. public static LuaState Create(LuaPlatform platform)
  27. {
  28. return LuaGlobalState.Create(platform).MainThread;
  29. }
  30. internal static LuaState CreateCoroutine(LuaGlobalState globalState, LuaFunction function, bool isProtectedMode = false)
  31. {
  32. return new(globalState, function, isProtectedMode);
  33. }
  34. public LuaState CreateThread()
  35. {
  36. return new(GlobalState);
  37. }
  38. public LuaState CreateCoroutine(LuaFunction function, bool isProtectedMode = false)
  39. {
  40. return new(GlobalState, function, isProtectedMode);
  41. }
  42. public LuaThreadStatus GetStatus()
  43. {
  44. if (coroutine is not null)
  45. {
  46. return (LuaThreadStatus)coroutine.status;
  47. }
  48. return LuaThreadStatus.Running;
  49. }
  50. public void UnsafeSetStatus(LuaThreadStatus status)
  51. {
  52. if (coroutine is null)
  53. {
  54. return;
  55. }
  56. coroutine.status = (byte)status;
  57. }
  58. public ValueTask<int> ResumeAsync(LuaFunctionExecutionContext context, CancellationToken cancellationToken = default)
  59. {
  60. if (coroutine is not null)
  61. {
  62. return coroutine.ResumeAsyncCore(context.State.Stack, context.ArgumentCount, context.ReturnFrameBase, context.State, cancellationToken);
  63. }
  64. return new(context.Return(false, "cannot resume non-suspended coroutine"));
  65. }
  66. public ValueTask<int> ResumeAsync(LuaStack stack, CancellationToken cancellationToken = default)
  67. {
  68. if (coroutine is not null)
  69. {
  70. return coroutine.ResumeAsyncCore(stack, stack.Count, 0, null, cancellationToken);
  71. }
  72. stack.Push(false);
  73. stack.Push("cannot resume non-suspended coroutine");
  74. return new(2);
  75. }
  76. public ValueTask<int> YieldAsync(LuaFunctionExecutionContext context, CancellationToken cancellationToken = default)
  77. {
  78. if (coroutine is not null)
  79. {
  80. return coroutine.YieldAsyncCore(context.State.Stack, context.ArgumentCount, context.ReturnFrameBase, context.State, cancellationToken);
  81. }
  82. throw new LuaRuntimeException(context.State, "cannot yield from a non-running coroutine");
  83. }
  84. public ValueTask<int> YieldAsync(LuaStack stack, CancellationToken cancellationToken = default)
  85. {
  86. if (coroutine is not null)
  87. {
  88. return coroutine.YieldAsyncCore(stack, stack.Count, 0, null, cancellationToken);
  89. }
  90. throw new LuaRuntimeException(null, "cannot yield from a non-running coroutine");
  91. }
  92. class ThreadCoreData : IPoolNode<ThreadCoreData>
  93. {
  94. //internal LuaCoroutineData? coroutineData;
  95. internal readonly LuaStack Stack = new();
  96. internal FastStackCore<CallStackFrame> CallStack;
  97. void Clear()
  98. {
  99. Stack.Clear();
  100. CallStack.Clear();
  101. }
  102. static LinkedPool<ThreadCoreData> pool;
  103. ThreadCoreData? nextNode;
  104. public ref ThreadCoreData? NextNode => ref nextNode;
  105. public static ThreadCoreData Create()
  106. {
  107. if (!pool.TryPop(out var result))
  108. {
  109. result = new();
  110. }
  111. return result;
  112. }
  113. public void Release()
  114. {
  115. Clear();
  116. pool.TryPush(this);
  117. }
  118. }
  119. FastListCore<UpValue> openUpValues;
  120. internal int CallCount;
  121. internal LuaGlobalState GlobalState { get; }
  122. ThreadCoreData? CoreData;
  123. CoroutineCore? coroutine;
  124. internal bool IsLineHookEnabled;
  125. internal BitFlags2 CallOrReturnHookMask;
  126. internal bool IsInHook;
  127. internal long HookCount;
  128. internal int BaseHookCount;
  129. internal int LastPc;
  130. internal ILuaTracebackBuildable? CurrentException;
  131. internal readonly ReversedStack<CallStackFrame> ExceptionTrace = new();
  132. internal LuaFunction? LastCallerFunction;
  133. internal ref FastListCore<UpValue> OpenUpValues => ref openUpValues;
  134. public bool IsRunning => CallStackFrameCount != 0;
  135. public bool IsCoroutine => coroutine != null;
  136. internal LuaFunction? Hook { get; set; }
  137. public LuaFunction? CoroutineFunction => coroutine?.Function;
  138. public bool CanResume => GetStatus() == LuaThreadStatus.Suspended;
  139. public LuaStack Stack => CoreData!.Stack;
  140. internal Traceback? LuaTraceback => coroutine?.Traceback;
  141. public LuaTable Environment => GlobalState.Environment;
  142. public LuaTable Registry => GlobalState.Registry;
  143. public LuaTable LoadedModules => GlobalState.LoadedModules;
  144. public LuaTable PreloadModules => GlobalState.PreloadModules;
  145. public LuaState MainThread => GlobalState.MainThread;
  146. public ILuaModuleLoader? ModuleLoader
  147. {
  148. get => GlobalState.ModuleLoader;
  149. set => GlobalState.ModuleLoader = value;
  150. }
  151. public LuaPlatform Platform
  152. {
  153. get => GlobalState.Platform;
  154. set => GlobalState.Platform = value;
  155. }
  156. internal bool IsCallHookEnabled
  157. {
  158. get => CallOrReturnHookMask.Flag0;
  159. set => CallOrReturnHookMask.Flag0 = value;
  160. }
  161. internal bool IsReturnHookEnabled
  162. {
  163. get => CallOrReturnHookMask.Flag1;
  164. set => CallOrReturnHookMask.Flag1 = value;
  165. }
  166. public int CallStackFrameCount => CoreData == null ? 0 : CoreData!.CallStack.Count;
  167. public ref readonly CallStackFrame GetCurrentFrame()
  168. {
  169. return ref CoreData!.CallStack.PeekRef();
  170. }
  171. public ReadOnlySpan<LuaValue> GetStackValues()
  172. {
  173. return CoreData == null ? default : CoreData!.Stack.AsSpan();
  174. }
  175. public ReadOnlySpan<CallStackFrame> GetCallStackFrames()
  176. {
  177. return CoreData == null ? default : CoreData!.CallStack.AsSpan();
  178. }
  179. internal CallStackFrame CreateCallStackFrame(LuaFunction function, int argumentCount, int returnBase, int callerInstructionIndex)
  180. {
  181. var state = this;
  182. var varArgumentCount = function.GetVariableArgumentCount(argumentCount);
  183. if (varArgumentCount != 0)
  184. {
  185. if (varArgumentCount < 0)
  186. {
  187. state.Stack.SetTop(state.Stack.Count - varArgumentCount);
  188. argumentCount -= varArgumentCount;
  189. varArgumentCount = 0;
  190. }
  191. else
  192. {
  193. LuaVirtualMachine.PrepareVariableArgument(state.Stack, argumentCount, varArgumentCount);
  194. }
  195. }
  196. CallStackFrame frame = new()
  197. {
  198. Base = state.Stack.Count - argumentCount,
  199. VariableArgumentCount = varArgumentCount,
  200. Function = function,
  201. ReturnBase = returnBase,
  202. CallerInstructionIndex = callerInstructionIndex
  203. };
  204. if (state.IsInHook)
  205. {
  206. frame.Flags |= CallStackFrameFlags.InHook;
  207. }
  208. return frame;
  209. }
  210. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  211. internal void PushCallStackFrame(in CallStackFrame frame)
  212. {
  213. CurrentException?.BuildOrGet();
  214. CurrentException = null;
  215. ref var callStack = ref CoreData!.CallStack;
  216. callStack.Push(frame);
  217. }
  218. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  219. internal void PopCallStackFrameWithStackPop()
  220. {
  221. var coreData = CoreData!;
  222. ref var callStack = ref coreData.CallStack;
  223. var popFrame = callStack.Pop();
  224. if (CurrentException != null)
  225. {
  226. ExceptionTrace.Push(popFrame);
  227. }
  228. coreData.Stack.PopUntil(popFrame.ReturnBase);
  229. }
  230. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  231. internal void PopCallStackFrameWithStackPop(int frameBase)
  232. {
  233. var coreData = CoreData!;
  234. ref var callStack = ref coreData.CallStack;
  235. var popFrame = callStack.Pop();
  236. if (CurrentException != null)
  237. {
  238. ExceptionTrace.Push(popFrame);
  239. }
  240. {
  241. coreData.Stack.PopUntil(frameBase);
  242. }
  243. }
  244. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  245. internal void PopCallStackFrame()
  246. {
  247. var coreData = CoreData!;
  248. ref var callStack = ref coreData.CallStack;
  249. var popFrame = callStack.Pop();
  250. if (CurrentException != null)
  251. {
  252. ExceptionTrace.Push(popFrame);
  253. }
  254. }
  255. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  256. internal void PopCallStackFrameUntil(int top)
  257. {
  258. var coreData = CoreData!;
  259. ref var callStack = ref coreData.CallStack;
  260. if (CurrentException != null)
  261. {
  262. ExceptionTrace.Push(callStack.AsSpan()[top..]);
  263. }
  264. callStack.PopUntil(top);
  265. }
  266. public void SetHook(LuaFunction? hook, string mask, int count = 0)
  267. {
  268. if (hook is null)
  269. {
  270. HookCount = 0;
  271. BaseHookCount = 0;
  272. Hook = null;
  273. IsLineHookEnabled = false;
  274. IsCallHookEnabled = false;
  275. IsReturnHookEnabled = false;
  276. return;
  277. }
  278. HookCount = count > 0 ? count + 1 : 0;
  279. BaseHookCount = count;
  280. IsLineHookEnabled = mask.Contains('l');
  281. IsCallHookEnabled = mask.Contains('c');
  282. IsReturnHookEnabled = mask.Contains('r');
  283. if (IsLineHookEnabled)
  284. {
  285. LastPc = CallStackFrameCount > 0 ? GetCurrentFrame().CallerInstructionIndex : -1;
  286. }
  287. Hook = hook;
  288. }
  289. internal void DumpStackValues()
  290. {
  291. var span = GetStackValues();
  292. for (var i = 0; i < span.Length; i++)
  293. {
  294. Console.WriteLine($"LuaStack [{i}]\t{span[i]}");
  295. }
  296. }
  297. public Traceback GetTraceback()
  298. {
  299. return new(this, GetCallStackFrames());
  300. }
  301. public ValueTask<int> RunAsync(LuaFunction function, CancellationToken cancellationToken = default)
  302. {
  303. return RunAsync(function, 0, Stack.Count, cancellationToken);
  304. }
  305. public ValueTask<int> RunAsync(LuaFunction function, int argumentCount, CancellationToken cancellationToken = default)
  306. {
  307. return RunAsync(function, argumentCount, Stack.Count - argumentCount, cancellationToken);
  308. }
  309. public async ValueTask<int> RunAsync(LuaFunction function, int argumentCount, int returnBase, CancellationToken cancellationToken = default)
  310. {
  311. if (function == null)
  312. {
  313. throw new ArgumentNullException(nameof(function));
  314. }
  315. this.ThrowIfCancellationRequested(cancellationToken);
  316. var state = this;
  317. var varArgumentCount = function.GetVariableArgumentCount(argumentCount);
  318. if (varArgumentCount != 0)
  319. {
  320. if (varArgumentCount < 0)
  321. {
  322. state.Stack.SetTop(state.Stack.Count - varArgumentCount);
  323. varArgumentCount = 0;
  324. }
  325. else
  326. {
  327. LuaVirtualMachine.PrepareVariableArgument(state.Stack, argumentCount, varArgumentCount);
  328. }
  329. argumentCount -= varArgumentCount;
  330. }
  331. CallStackFrame frame = new() { Base = state.Stack.Count - argumentCount, VariableArgumentCount = varArgumentCount, Function = function, ReturnBase = returnBase };
  332. if (state.IsInHook)
  333. {
  334. frame.Flags |= CallStackFrameFlags.InHook;
  335. }
  336. state.PushCallStackFrame(frame);
  337. LuaFunctionExecutionContext context = new() { State = state, ArgumentCount = argumentCount, ReturnFrameBase = returnBase };
  338. var callStackTop = state.CallStackFrameCount;
  339. try
  340. {
  341. if (CallOrReturnHookMask.Value != 0 && !IsInHook)
  342. {
  343. return await LuaVirtualMachine.ExecuteCallHook(context, cancellationToken);
  344. }
  345. return await function.Func(context, cancellationToken);
  346. }
  347. finally
  348. {
  349. PopCallStackFrameUntil(callStackTop - 1);
  350. }
  351. }
  352. public unsafe LuaClosure Load(ReadOnlySpan<char> chunk, string chunkName, LuaTable? environment = null)
  353. {
  354. Prototype prototype;
  355. fixed (char* ptr = chunk)
  356. {
  357. prototype = Parser.Parse(this, new(ptr, chunk.Length), chunkName);
  358. }
  359. return new(this, prototype, environment);
  360. }
  361. public LuaClosure Load(ReadOnlySpan<byte> chunk, string? chunkName = null, string mode = "bt", LuaTable? environment = null)
  362. {
  363. if (chunk.Length > 4)
  364. {
  365. if (chunk[0] == '\e')
  366. {
  367. return new(this, Parser.UnDump(chunk, chunkName), environment);
  368. }
  369. }
  370. chunk = BomUtility.GetEncodingFromBytes(chunk, out var encoding);
  371. var charCount = encoding.GetCharCount(chunk);
  372. var pooled = ArrayPool<char>.Shared.Rent(charCount);
  373. try
  374. {
  375. var chars = pooled.AsSpan(0, charCount);
  376. encoding.GetChars(chunk, chars);
  377. chunkName ??= chars.ToString();
  378. return Load(chars, chunkName, environment);
  379. }
  380. finally
  381. {
  382. ArrayPool<char>.Shared.Return(pooled);
  383. }
  384. }
  385. internal UpValue GetOrAddUpValue(int registerIndex)
  386. {
  387. foreach (var upValue in openUpValues.AsSpan())
  388. {
  389. if (upValue.RegisterIndex == registerIndex)
  390. {
  391. return upValue;
  392. }
  393. }
  394. var newUpValue = UpValue.Open(this, registerIndex);
  395. openUpValues.Add(newUpValue);
  396. return newUpValue;
  397. }
  398. internal void CloseUpValues(int frameBase)
  399. {
  400. for (var i = 0; i < openUpValues.Length; i++)
  401. {
  402. var upValue = openUpValues[i];
  403. if (upValue.RegisterIndex >= frameBase)
  404. {
  405. upValue.Close();
  406. openUpValues.RemoveAtSwapBack(i);
  407. i--;
  408. }
  409. }
  410. }
  411. public void Dispose()
  412. {
  413. if (CoreData!.CallStack.Count != 0)
  414. {
  415. throw new InvalidOperationException("This state is running! Call stack is not empty!!");
  416. }
  417. CoreData.Release();
  418. CoreData = null!;
  419. }
  420. }