LuaCoroutine.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  1. using System.Threading.Tasks.Sources;
  2. using Lua.Internal;
  3. using Lua.Runtime;
  4. namespace Lua;
  5. public sealed class LuaCoroutine : LuaThread, IValueTaskSource<LuaCoroutine.YieldContext>, IValueTaskSource<LuaCoroutine.ResumeContext>, IPoolNode<LuaCoroutine>
  6. {
  7. static LinkedPool<LuaCoroutine> pool;
  8. LuaCoroutine? nextNode;
  9. ref LuaCoroutine? IPoolNode<LuaCoroutine>.NextNode => ref nextNode;
  10. public static LuaCoroutine Create(LuaThread parent, LuaFunction function, bool isProtectedMode)
  11. {
  12. if (!pool.TryPop(out LuaCoroutine result))
  13. {
  14. result = new();
  15. }
  16. result.Init(parent, function, isProtectedMode);
  17. return result;
  18. }
  19. public void Release()
  20. {
  21. if (CoreData != null && CoreData.CallStack.Count != 0)
  22. {
  23. throw new InvalidOperationException("This thread is running! Call stack is not empty!!");
  24. }
  25. ReleaseCore();
  26. pool.TryPush(this);
  27. }
  28. struct YieldContext
  29. {
  30. public required LuaValue[] Results;
  31. }
  32. struct ResumeContext
  33. {
  34. public required LuaValue[] Results;
  35. }
  36. byte status;
  37. bool isFirstCall = true;
  38. ValueTask<int> functionTask;
  39. ManualResetValueTaskSourceCore<ResumeContext> resume;
  40. ManualResetValueTaskSourceCore<YieldContext> yield;
  41. Traceback? traceback;
  42. internal void Init(LuaThread parent, LuaFunction function, bool isProtectedMode)
  43. {
  44. CoreData = ThreadCoreData.Create();
  45. State = parent.State;
  46. IsProtectedMode = isProtectedMode;
  47. Function = function;
  48. IsRunning = false;
  49. }
  50. public override LuaThreadStatus GetStatus() => (LuaThreadStatus)status;
  51. public override void UnsafeSetStatus(LuaThreadStatus status)
  52. {
  53. this.status = (byte)status;
  54. }
  55. public bool IsProtectedMode { get; private set; }
  56. public LuaFunction Function { get; private set; } = null!;
  57. internal Traceback? LuaTraceback => traceback;
  58. public bool CanResume => status == (byte)LuaThreadStatus.Suspended;
  59. public ValueTask<int> ResumeAsync(LuaStack stack, CancellationToken cancellationToken = default)
  60. {
  61. return ResumeAsync(stack,stack.Count,0, cancellationToken);
  62. }
  63. public async ValueTask<int> ResumeAsync(LuaStack stack, int argCount, int returnBase,CancellationToken cancellationToken = default)
  64. {
  65. if (isFirstCall)
  66. {
  67. ThrowIfRunning();
  68. IsRunning = true;
  69. }
  70. switch ((LuaThreadStatus)Volatile.Read(ref status))
  71. {
  72. case LuaThreadStatus.Suspended:
  73. Volatile.Write(ref status, (byte)LuaThreadStatus.Running);
  74. if (!isFirstCall)
  75. {
  76. yield.SetResult(new()
  77. {
  78. Results = argCount == 0
  79. ? []
  80. : stack.AsSpan()[^argCount..].ToArray()
  81. });
  82. }
  83. break;
  84. case LuaThreadStatus.Normal:
  85. case LuaThreadStatus.Running:
  86. if (IsProtectedMode)
  87. {
  88. stack.PopUntil(returnBase);
  89. stack.Push(false);
  90. stack.Push( "cannot resume non-suspended coroutine");
  91. return 2;
  92. }
  93. else
  94. {
  95. throw new LuaException( "cannot resume non-suspended coroutine");
  96. }
  97. case LuaThreadStatus.Dead:
  98. if (IsProtectedMode)
  99. {
  100. stack.PopUntil(returnBase);
  101. stack.Push(false);
  102. stack.Push( "cannot resume non-suspended coroutine");
  103. return 2;
  104. }
  105. else
  106. {
  107. throw new LuaException("cannot resume dead coroutine");
  108. }
  109. }
  110. var resumeTask = new ValueTask<ResumeContext>(this, resume.Version);
  111. CancellationTokenRegistration registration = default;
  112. if (cancellationToken.CanBeCanceled)
  113. {
  114. registration = cancellationToken.UnsafeRegister(static x =>
  115. {
  116. var coroutine = (LuaCoroutine)x!;
  117. coroutine.yield.SetException(new OperationCanceledException());
  118. }, this);
  119. }
  120. try
  121. {
  122. if (isFirstCall)
  123. {
  124. Stack.PushRange(stack.AsSpan()[^argCount..]);
  125. functionTask = Function.InvokeAsync(new() { Thread = this, ArgumentCount = Stack.Count, ReturnFrameBase = 0 }, cancellationToken).Preserve();
  126. Volatile.Write(ref isFirstCall, false);
  127. }
  128. var (index, result0, result1) = await ValueTaskEx.WhenAny(resumeTask, functionTask!);
  129. if (index == 0)
  130. {
  131. var results = result0.Results;
  132. stack.PopUntil(returnBase);
  133. stack.Push(true);
  134. stack.PushRange(results.AsSpan());
  135. return results.Length+1;
  136. }
  137. else
  138. {
  139. Volatile.Write(ref status, (byte)LuaThreadStatus.Dead);
  140. stack.PopUntil(returnBase);
  141. stack.Push(true);
  142. stack.PushRange(Stack.AsSpan());
  143. ReleaseCore();
  144. return stack.Count-returnBase;
  145. }
  146. }
  147. catch (Exception ex) when (ex is not OperationCanceledException)
  148. {
  149. if (IsProtectedMode)
  150. {
  151. traceback = (ex as LuaRuntimeException)?.LuaTraceback;
  152. Volatile.Write(ref status, (byte)LuaThreadStatus.Dead);
  153. ReleaseCore();
  154. stack.PopUntil(returnBase);
  155. stack.Push(false);
  156. stack.Push( ex is LuaRuntimeException luaEx ? luaEx.ErrorObject : ex.Message);
  157. return 2;
  158. }
  159. else
  160. {
  161. throw;
  162. }
  163. }
  164. finally
  165. {
  166. registration.Dispose();
  167. resume.Reset();
  168. }
  169. }
  170. public override async ValueTask<int> ResumeAsync(LuaFunctionExecutionContext context, CancellationToken cancellationToken = default)
  171. {
  172. var baseThread = context.Thread;
  173. baseThread.UnsafeSetStatus(LuaThreadStatus.Normal);
  174. context.State.ThreadStack.Push(this);
  175. try
  176. {
  177. switch ((LuaThreadStatus)Volatile.Read(ref status))
  178. {
  179. case LuaThreadStatus.Suspended:
  180. Volatile.Write(ref status, (byte)LuaThreadStatus.Running);
  181. if (!isFirstCall)
  182. {
  183. yield.SetResult(new()
  184. {
  185. Results = context.ArgumentCount == 0
  186. ? []
  187. : context.Arguments.ToArray()
  188. });
  189. }
  190. break;
  191. case LuaThreadStatus.Normal:
  192. case LuaThreadStatus.Running:
  193. if (IsProtectedMode)
  194. {
  195. return context.Return(false, "cannot resume non-suspended coroutine");
  196. }
  197. else
  198. {
  199. throw new LuaRuntimeException(context.Thread.GetTraceback(), "cannot resume non-suspended coroutine");
  200. }
  201. case LuaThreadStatus.Dead:
  202. if (IsProtectedMode)
  203. {
  204. return context.Return(false, "cannot resume dead coroutine");
  205. }
  206. else
  207. {
  208. throw new LuaRuntimeException(context.Thread.GetTraceback(), "cannot resume dead coroutine");
  209. }
  210. }
  211. var resumeTask = new ValueTask<ResumeContext>(this, resume.Version);
  212. CancellationTokenRegistration registration = default;
  213. if (cancellationToken.CanBeCanceled)
  214. {
  215. registration = cancellationToken.UnsafeRegister(static x =>
  216. {
  217. var coroutine = (LuaCoroutine)x!;
  218. coroutine.yield.SetException(new OperationCanceledException());
  219. }, this);
  220. }
  221. try
  222. {
  223. if (isFirstCall)
  224. {
  225. Stack.PushRange(context.Arguments);
  226. functionTask = Function.InvokeAsync(new() { Thread = this, ArgumentCount = Stack.Count, ReturnFrameBase = 0 }, cancellationToken).Preserve();
  227. Volatile.Write(ref isFirstCall, false);
  228. }
  229. var (index, result0, result1) = await ValueTaskEx.WhenAny(resumeTask, functionTask!);
  230. if (index == 0)
  231. {
  232. var results = result0.Results;
  233. return context.Return(true, results.AsSpan());
  234. }
  235. else
  236. {
  237. Volatile.Write(ref status, (byte)LuaThreadStatus.Dead);
  238. var count = context.Return(true, Stack.AsSpan());
  239. ReleaseCore();
  240. return count;
  241. }
  242. }
  243. catch (Exception ex) when (ex is not OperationCanceledException)
  244. {
  245. if (IsProtectedMode)
  246. {
  247. traceback = (ex as LuaRuntimeException)?.LuaTraceback;
  248. Volatile.Write(ref status, (byte)LuaThreadStatus.Dead);
  249. ReleaseCore();
  250. return context.Return(false, ex is LuaRuntimeException luaEx ? luaEx.ErrorObject : ex.Message);
  251. }
  252. else
  253. {
  254. throw;
  255. }
  256. }
  257. finally
  258. {
  259. registration.Dispose();
  260. resume.Reset();
  261. }
  262. }
  263. finally
  264. {
  265. context.State.ThreadStack.Pop();
  266. baseThread.UnsafeSetStatus(LuaThreadStatus.Running);
  267. }
  268. }
  269. public override async ValueTask<int> YieldAsync(LuaFunctionExecutionContext context, CancellationToken cancellationToken = default)
  270. {
  271. if (Volatile.Read(ref status) != (byte)LuaThreadStatus.Running)
  272. {
  273. throw new LuaRuntimeException(context.Thread.GetTraceback(), "cannot call yield on a coroutine that is not currently running");
  274. }
  275. if (context.Thread.GetCallStackFrames()[^2].Function is not LuaClosure)
  276. {
  277. throw new LuaRuntimeException(context.Thread.GetTraceback(), "attempt to yield across a C#-call boundary");
  278. }
  279. resume.SetResult(new() { Results = context.Arguments.ToArray(), });
  280. Volatile.Write(ref status, (byte)LuaThreadStatus.Suspended);
  281. CancellationTokenRegistration registration = default;
  282. if (cancellationToken.CanBeCanceled)
  283. {
  284. registration = cancellationToken.UnsafeRegister(static x =>
  285. {
  286. var coroutine = (LuaCoroutine)x!;
  287. coroutine.yield.SetException(new OperationCanceledException());
  288. }, this);
  289. }
  290. RETRY:
  291. try
  292. {
  293. var result = await new ValueTask<YieldContext>(this, yield.Version);
  294. return (context.Return(result.Results));
  295. }
  296. catch (Exception ex) when (ex is not OperationCanceledException)
  297. {
  298. yield.Reset();
  299. goto RETRY;
  300. }
  301. finally
  302. {
  303. registration.Dispose();
  304. yield.Reset();
  305. }
  306. }
  307. YieldContext IValueTaskSource<YieldContext>.GetResult(short token)
  308. {
  309. return yield.GetResult(token);
  310. }
  311. ValueTaskSourceStatus IValueTaskSource<YieldContext>.GetStatus(short token)
  312. {
  313. return yield.GetStatus(token);
  314. }
  315. void IValueTaskSource<YieldContext>.OnCompleted(Action<object?> continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags)
  316. {
  317. yield.OnCompleted(continuation, state, token, flags);
  318. }
  319. ResumeContext IValueTaskSource<ResumeContext>.GetResult(short token)
  320. {
  321. return resume.GetResult(token);
  322. }
  323. ValueTaskSourceStatus IValueTaskSource<ResumeContext>.GetStatus(short token)
  324. {
  325. return resume.GetStatus(token);
  326. }
  327. void IValueTaskSource<ResumeContext>.OnCompleted(Action<object?> continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags)
  328. {
  329. resume.OnCompleted(continuation, state, token, flags);
  330. }
  331. void ReleaseCore()
  332. {
  333. // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
  334. CoreData?.Release();
  335. CoreData = null!;
  336. }
  337. }