1 module zua.vm.std.coroutine; 2 import zua.vm.engine; 3 import zua.vm.reflection; 4 import std.typecons; 5 import std.random; 6 import core.thread; 7 8 private Value[] resumeParams; 9 private Value[] resumeResult; 10 private ThreadValue currentThread; 11 12 /** Represents a coroutine context */ 13 struct Context { 14 private Value[] resumeParams; 15 private Value[] resumeResult; 16 private ThreadValue currentThread; 17 } 18 19 /** Coroutine stack size */ 20 const size_t STACKSIZE = 1024 * 1024 * 8; 21 22 /** Run a function in a toplevel thread */ 23 Value[] runToplevel(TableValue env, FunctionValue func, Value[] args) { 24 Context ctx = { 25 resumeParams: resumeParams, 26 resumeResult: resumeResult, 27 currentThread: currentThread 28 }; 29 30 ThreadValue co = new ThreadValue; 31 co.env = env; 32 co.status = CoroutineStatus.Suspended; 33 co.fiber = new Fiber(delegate() { 34 resumeResult = func.rawcall(resumeParams); 35 co.status = CoroutineStatus.Dead; 36 }, STACKSIZE); 37 38 resumeParams = []; 39 resumeResult = []; 40 currentThread = co; 41 42 scope(exit) { 43 resumeParams = ctx.resumeParams; 44 resumeResult = ctx.resumeResult; 45 currentThread = ctx.currentThread; 46 } 47 48 co.status = CoroutineStatus.Running; 49 resumeParams = args; 50 Throwable err = co.fiber.call!(Fiber.Rethrow.no); 51 if (co.status != CoroutineStatus.Dead) { 52 co.status = CoroutineStatus.Suspended; 53 } 54 if (err !is null) { 55 co.status = CoroutineStatus.Dead; 56 throw err; 57 } 58 59 return resumeResult; 60 } 61 62 TableValue* getGlobalEnvPtr() { 63 if (currentThread is null) { 64 throw new Exception("internal error (escaped toplevel thread)"); 65 } 66 else { 67 return ¤tThread.env; 68 } 69 } 70 71 private ThreadValue lcoroutine_create(FunctionValue value) { 72 FunctionValue caller = callstack[$ - 1].func; 73 74 TableValue env = caller.env; 75 if (env is null) env = *getGlobalEnvPtr; 76 77 ThreadValue res = new ThreadValue; 78 res.env = env; 79 res.status = CoroutineStatus.Suspended; 80 res.fiber = new Fiber(delegate() { 81 resumeResult = value.rawcall(resumeParams); 82 res.status = CoroutineStatus.Dead; 83 }, STACKSIZE); 84 85 return res; 86 } 87 88 private Value[] lcoroutine_resume(ThreadValue co, Value[] params...) { 89 if (co.status == CoroutineStatus.Dead) { 90 return [Value(false), Value("cannot resume dead coroutine")]; 91 } 92 else if (co.status == CoroutineStatus.Running) { 93 return [Value(false), Value("cannot resume running coroutine")]; 94 } 95 else if (co.status == CoroutineStatus.Normal) { 96 return [Value(false), Value("cannot resume normal coroutine")]; 97 } 98 99 auto save = currentThread; 100 if (currentThread) currentThread.status = CoroutineStatus.Normal; 101 currentThread = co; 102 currentThread.status = CoroutineStatus.Running; 103 resumeParams = params; 104 Throwable err = co.fiber.call!(Fiber.Rethrow.no); 105 if (currentThread.status != CoroutineStatus.Dead) { 106 currentThread.status = CoroutineStatus.Suspended; 107 } 108 currentThread = save; 109 if (currentThread) currentThread.status = CoroutineStatus.Running; 110 if (err !is null) { 111 co.status = CoroutineStatus.Dead; 112 if (auto e = cast(LuaError)err) { 113 return [Value(false), e.data]; 114 } 115 else if (auto e = cast(Exception)err) { 116 return [Value(false), Value("an internal error occurred")]; 117 } 118 else { 119 throw err; 120 } 121 } 122 return Value(true) ~ resumeResult; 123 } 124 125 private Value[] lcoroutine_yield(Value[] params...) { 126 resumeResult = params; 127 Fiber.yield(); 128 return resumeParams; 129 } 130 131 private ThreadValue lcoroutine_running() { 132 return currentThread; 133 } 134 135 private string lcoroutine_status(ThreadValue co) { 136 final switch (co.status) { 137 case CoroutineStatus.Suspended: return "suspended"; 138 case CoroutineStatus.Running: return "running"; 139 case CoroutineStatus.Normal: return "normal"; 140 case CoroutineStatus.Dead: return "dead"; 141 } 142 } 143 144 private FunctionValue lcoroutine_wrap(FunctionValue func) { 145 ThreadValue co = lcoroutine_create(func); 146 FunctionValue res = new FunctionValue; 147 res.env = null; 148 res.engine = new class Engine { 149 150 override Value[] callf(FunctionValue, Value[] args) { 151 Value[] wres = lcoroutine_resume(co, args); 152 if (wres[0] == Value(false)) { 153 throw new LuaError(wres[1]); 154 } 155 else { 156 return wres[1..$]; 157 } 158 } 159 160 }; 161 return res; 162 } 163 164 /** Get coroutine library */ 165 Value coroutinelib() { 166 TableValue res = new TableValue; 167 res.set(Value("create"), exposeFunction!(lcoroutine_create, "create")); 168 res.set(Value("resume"), exposeFunction!(lcoroutine_resume, "resume")); 169 res.set(Value("running"), exposeFunction!(lcoroutine_running, "running")); 170 res.set(Value("status"), exposeFunction!(lcoroutine_status, "status")); 171 res.set(Value("wrap"), exposeFunction!(lcoroutine_wrap, "wrap")); 172 res.set(Value("yield"), exposeFunction!(lcoroutine_yield, "yield")); 173 return Value(res); 174 }