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 &currentThread.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 }