LuaThread.cs 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. namespace Lua;
  2. public sealed class LuaThread
  3. {
  4. LuaThreadStatus status;
  5. bool isProtectedMode;
  6. LuaState threadState;
  7. Task<int>? functionTask;
  8. TaskCompletionSource<LuaValue[]> resume = new();
  9. TaskCompletionSource<object?> yield = new();
  10. public LuaThreadStatus Status => status;
  11. public bool IsProtectedMode => isProtectedMode;
  12. public LuaFunction Function { get; }
  13. internal LuaThread(LuaState state, LuaFunction function, bool isProtectedMode)
  14. {
  15. this.isProtectedMode = isProtectedMode;
  16. threadState = state.CreateCoroutineState();
  17. Function = function;
  18. function.thread = this;
  19. }
  20. public async Task<int> Resume(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken = default)
  21. {
  22. if (status is LuaThreadStatus.Dead)
  23. {
  24. if (IsProtectedMode)
  25. {
  26. buffer.Span[0] = false;
  27. buffer.Span[1] = "cannot resume dead coroutine";
  28. return 2;
  29. }
  30. else
  31. {
  32. throw new InvalidOperationException("cannot resume dead coroutine");
  33. }
  34. }
  35. if (status is LuaThreadStatus.Normal)
  36. {
  37. status = LuaThreadStatus.Running;
  38. // first argument is LuaThread object
  39. for (int i = 0; i < context.ArgumentCount - 1; i++)
  40. {
  41. threadState.Push(context.Arguments[i + 1]);
  42. }
  43. functionTask = Function.InvokeAsync(new()
  44. {
  45. State = threadState,
  46. ArgumentCount = context.ArgumentCount - 1,
  47. ChunkName = Function.Name,
  48. RootChunkName = context.RootChunkName,
  49. }, buffer[1..], cancellationToken).AsTask();
  50. }
  51. else
  52. {
  53. status = LuaThreadStatus.Running;
  54. if (cancellationToken.IsCancellationRequested)
  55. {
  56. yield.TrySetCanceled();
  57. }
  58. else
  59. {
  60. yield.TrySetResult(null);
  61. }
  62. }
  63. var resumeTask = resume.Task;
  64. var completedTask = await Task.WhenAny(resumeTask, functionTask!);
  65. if (!completedTask.IsCompletedSuccessfully)
  66. {
  67. if (IsProtectedMode)
  68. {
  69. status = LuaThreadStatus.Dead;
  70. buffer.Span[0] = false;
  71. buffer.Span[1] = completedTask.Exception.InnerException.Message;
  72. return 2;
  73. }
  74. else
  75. {
  76. throw completedTask.Exception.InnerException;
  77. }
  78. }
  79. if (completedTask == resumeTask)
  80. {
  81. resume = new();
  82. var results = resumeTask.Result;
  83. buffer.Span[0] = true;
  84. for (int i = 0; i < results.Length; i++)
  85. {
  86. buffer.Span[i + 1] = results[i];
  87. }
  88. return results.Length + 1;
  89. }
  90. else
  91. {
  92. status = LuaThreadStatus.Dead;
  93. buffer.Span[0] = true;
  94. return 1 + functionTask!.Result;
  95. }
  96. }
  97. public async Task Yield(LuaFunctionExecutionContext context, CancellationToken cancellationToken = default)
  98. {
  99. if (status is not LuaThreadStatus.Running)
  100. {
  101. throw new InvalidOperationException("cannot call yield on a coroutine that is not currently running");
  102. }
  103. if (cancellationToken.IsCancellationRequested)
  104. {
  105. resume.TrySetCanceled();
  106. }
  107. else
  108. {
  109. resume.TrySetResult(context.Arguments.ToArray());
  110. }
  111. status = LuaThreadStatus.Suspended;
  112. RETRY:
  113. try
  114. {
  115. await yield.Task;
  116. }
  117. catch (Exception ex) when (ex is not OperationCanceledException)
  118. {
  119. yield = new();
  120. goto RETRY;
  121. }
  122. yield = new();
  123. }
  124. public Task<int> Close(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken = default)
  125. {
  126. if (status is LuaThreadStatus.Normal or LuaThreadStatus.Running)
  127. {
  128. throw new Exception(); // TODO:
  129. }
  130. threadState.CloseUpValues(0);
  131. yield.TrySetCanceled();
  132. status = LuaThreadStatus.Dead;
  133. buffer.Span[0] = true;
  134. return Task.FromResult(1);
  135. }
  136. }