1 #include "script.h"
2 
3 #include <stdarg.h>
4 #include <string.h>
5 #include "../squirrel/sqstdaux.h" // for error handlers
6 #include "../squirrel/sqstdio.h" // for loadfile
7 #include "../squirrel/sqstdstring.h" // export for scripts
8 #include "../squirrel/sqstdmath.h" // export for scripts
9 #include "../squirrel/sqstdsystem.h" // export for scripts
10 #include "../squirrel/sq_extensions.h" // for sq_call_restricted
11 
12 #include "../utils/log.h"
13 
14 #include "../tpl/inthashtable_tpl.h"
15 #include "../tpl/vector_tpl.h"
16 // for error popups
17 #include "../gui/help_frame.h"
18 #include "../gui/simwin.h"
19 #include "../utils/cbuffer_t.h"
20 #include "../utils/plainstring.h"
21 
22 
23 // debug: store stack pointer
24 #define BEGIN_STACK_WATCH(v) int stack_top = sq_gettop(v);
25 // debug: compare stack pointer with expected stack pointer, raise warning if failure
26 #define END_STACK_WATCH(v, delta) if ( (stack_top+(delta)) != sq_gettop(v)) { dbg->warning( __FUNCTION__, "(%d) stack in %d expected %d out %d", __LINE__,stack_top,stack_top+(delta),sq_gettop(v)); }
27 
28 
printfunc(HSQUIRRELVM vm,const SQChar * s,...)29 void script_vm_t::printfunc(HSQUIRRELVM vm, const SQChar *s, ...)
30 {
31 	va_list vl;
32 	va_start(vl, s);
33 	if (script_vm_t *script = (script_vm_t*)sq_getforeignptr(vm)) {
34 		script->log->vmessage("Script", "Print", s, vl);
35 	}
36 	va_end(vl);
37 }
38 
39 
errorfunc(HSQUIRRELVM vm,const SQChar * s_,...)40 void script_vm_t::errorfunc(HSQUIRRELVM vm, const SQChar *s_, ...)
41 {
42 
43 	char *s = strdup(s_);
44 	char *s_dup = s;
45 	// remove these linebreaks
46 	while (s  &&  *s=='\n') s++;
47 	while (s  &&  s[strlen(s)-1]=='\n') s[strlen(s)-1]=0;
48 
49 	// get failed script
50 	script_vm_t *script = (script_vm_t*)sq_getforeignptr(vm);
51 
52 	if (script) {
53 		va_list vl;
54 		va_start(vl, s_);
55 		script->log->vmessage("Script", "Error", s, vl);
56 		va_end(vl);
57 	}
58 
59 	// only one error message at a time - hopefully
60 	// collect into static buffer
61 	static cbuffer_t buf;
62 	if (strcmp(s, "<error>")==0) {
63 		// start of error message
64 		buf.clear();
65 		buf.printf("<st>An error occured within a script!</st><br>\n");
66 	}
67 	else if (strcmp(s, "</error>")==0) {
68 		// end of error message
69 		help_frame_t *win = dynamic_cast<help_frame_t*>(win_get_magic((ptrdiff_t)script));
70 		if (win == NULL) {
71 			win = new help_frame_t();
72 			create_win( win, w_info, magic_script_error);
73 		}
74 		win->set_text(buf);
75 		win->set_name("Script error occurred");
76 
77 		if (script) {
78 			script->set_error(buf);
79 		}
80 	}
81 	else {
82 		va_list vl;
83 		va_start(vl, s_);
84 		buf.vprintf(s, vl);
85 		va_end(vl);
86 		buf.append("<br>");
87 	}
88 	free(s_dup);
89 }
90 
91 void export_include(HSQUIRRELVM vm, const char* include_path); // api_include.cc
92 
93 // virtual machine
script_vm_t(const char * include_path_,const char * log_name)94 script_vm_t::script_vm_t(const char* include_path_, const char* log_name)
95 {
96 	vm = sq_open(1024);
97 	sqstd_seterrorhandlers(vm);
98 	sq_setprintfunc(vm, printfunc, errorfunc);
99 	register_vm(vm);
100 	log = new log_t(log_name, true, true, true, "script engine started.\n");
101 
102 	// store ptr to us in vm
103 	sq_setforeignptr(vm, this);
104 
105 	// create thread, and put it into registry-table
106 	sq_pushregistrytable(vm);
107 	sq_pushstring(vm, "thread", -1);
108 	thread = sq_newthread(vm, 100);
109 	sq_newslot(vm, -3, false);
110 	register_vm(thread);
111 	// create queue array
112 	sq_pushstring(vm, "queue", -1);
113 	sq_newarray(vm, 0);
114 	sq_newslot(vm, -3, false);
115 	// create queued-callbacks array
116 	sq_pushstring(vm, "queued_callbacks", -1);
117 	sq_newarray(vm, 0);
118 	sq_newslot(vm, -3, false);
119 	// pop registry
120 	sq_pop(vm, 1);
121 	// store ptr to us in vm
122 	sq_setforeignptr(vm, this);
123 	sq_setforeignptr(thread, this);
124 
125 	error_msg = NULL;
126 	include_path = include_path_;
127 	// register libraries
128 	sq_pushroottable(vm);
129 	sqstd_register_stringlib(vm);
130 	sqstd_register_mathlib(vm);
131 	sqstd_register_systemlib(vm);
132 	sq_pop(vm, 1);
133 	// export include command
134 	export_include(vm, include_path);
135 	// initialize coordinate and rotation handling
136 	script_api::coordinate_transform_t::initialize();
137 }
138 
139 
~script_vm_t()140 script_vm_t::~script_vm_t()
141 {
142 	unregister_vm(thread);
143 	unregister_vm(vm);
144 	// remove from suspended calls list
145 	suspended_scripts_t::remove_vm(thread);
146 	suspended_scripts_t::remove_vm(vm);
147 	// close vm, also closes thread
148 	sq_close(vm);
149 
150 	delete log;
151 }
152 
call_script(const char * filename)153 const char* script_vm_t::call_script(const char* filename)
154 {
155 	// load script
156 	if (!SQ_SUCCEEDED(sqstd_loadfile(vm, filename, true))) {
157 		return "Reading / compiling script failed";
158 	}
159 	// call it
160 	sq_pushroottable(vm);
161 	if (!SQ_SUCCEEDED(sq_call_restricted(vm, 1, SQFalse, SQTrue, 100000))) {
162 		sq_pop(vm, 1); // pop script
163 		return "Call script failed";
164 	}
165 	if (sq_getvmstate(vm) == SQ_VMSTATE_SUSPENDED) {
166 		set_error("Calling scriptfile took too long");
167 		return get_error();
168 	}
169 	sq_pop(vm, 1); // pop script
170 	return NULL;
171 }
172 
173 
eval_string(const char * squirrel_string)174 const char* script_vm_t::eval_string(const char* squirrel_string)
175 {
176 	// compile string
177 	if (!SQ_SUCCEEDED(sq_compilebuffer(vm, squirrel_string, strlen(squirrel_string), "userdefinedstringmethod", true))) {
178 		set_error("Error compiling string buffer");
179 		return get_error();
180 	}
181 	// execute
182 	sq_pushroottable(vm);
183 	sq_call_restricted(vm, 1, SQFalse, SQTrue, 100000);
184 	sq_pop(vm, 1);
185 	return get_error();
186 }
187 
188 
is_call_suspended(const char * err)189 bool script_vm_t::is_call_suspended(const char* err)
190 {
191 	return (err != NULL)  &&  ( strcmp(err, "suspended") == 0);
192 }
193 
194 
intern_prepare_call(HSQUIRRELVM & job,call_type_t ct,const char * function)195 const char* script_vm_t::intern_prepare_call(HSQUIRRELVM &job, call_type_t ct, const char* function)
196 {
197 	const char* err = NULL;
198 
199 	switch (ct) {
200 		case FORCE:
201 		case FORCEX:
202 			job = vm;
203 			// block calls to suspendable functions
204 			sq_block_suspend(job, function);
205 			break;
206 		case TRY:
207 			if (sq_getvmstate(thread) == SQ_VMSTATE_SUSPENDED) {
208 				return "suspended";
209 			}
210 			// fall through
211 		case QUEUE:
212 			job = thread;
213 			break;
214 	}
215 
216 	dbg->message("script_vm_t::intern_prepare_call", "ct=%d function=%s stack=%d", ct, function, sq_gettop(job));
217 	sq_pushroottable(job);
218 	sq_pushstring(job, function, -1);
219 	// fetch function from root
220 	if (SQ_SUCCEEDED(sq_get(job,-2))) {
221 		sq_remove(job, -2); // root table
222 		sq_pushroottable(job);
223 	}
224 	else {
225 		err = "Function not found";
226 		sq_poptop(job); // array, root table
227 	}
228 	return err;
229 	// stack: closure, root table (1st param)
230 }
231 
232 
intern_finish_call(HSQUIRRELVM job,call_type_t ct,int nparams,bool retvalue)233 const char* script_vm_t::intern_finish_call(HSQUIRRELVM job, call_type_t ct, int nparams, bool retvalue)
234 {
235 	BEGIN_STACK_WATCH(job);
236 	// stack: closure, nparams*objects
237 	const char* err = NULL;
238 	bool suspended = sq_getvmstate(job) == SQ_VMSTATE_SUSPENDED;
239 	// check queue, if not empty resume first job in queue
240 	if (!suspended  &&  ct != FORCE  &&  ct != FORCEX) {
241 		sq_pushregistrytable(job);
242 		sq_pushstring(job, "queue", -1);
243 		sq_get(job, -2);
244 		// stack: registry, queue
245 		if (sq_getsize(job, -1) > 0) {
246 			suspended = true;
247 		}
248 		sq_pop(job, 2);
249 	}
250 	// queue function call?
251 	if (suspended  &&  ct == QUEUE) {
252 		intern_queue_call(job, nparams, retvalue);
253 		err = "suspended";
254 		// stack: clean
255 	}
256 	if (suspended) {
257 		intern_resume_call(job);
258 	}
259 	if (!suspended  ||  ct == FORCE  ||  ct == FORCEX) {
260 		// set active callback if call could be suspended
261 		if (ct == QUEUE) {
262 			intern_make_pending_callback_active();
263 		}
264 		// mark as not queued
265 		sq_pushregistrytable(job);
266 		script_api::create_slot(job, "was_queued", false);
267 		sq_poptop(job);
268 		END_STACK_WATCH(job,0);
269 		err = intern_call_function(job, ct, nparams, retvalue);
270 	}
271 	return err;
272 }
273 
274 /**
275  * Calls function. If it was a queued call, also calls callbacks.
276  * Stack(job): expects closure, nparam*objects, on exit: return value (or clean stack if failure).
277  * @returns NULL on success, error message or "suspended" otherwise.
278  */
intern_call_function(HSQUIRRELVM job,call_type_t ct,int nparams,bool retvalue)279 const char* script_vm_t::intern_call_function(HSQUIRRELVM job, call_type_t ct, int nparams, bool retvalue)
280 {
281 	BEGIN_STACK_WATCH(job);
282 	dbg->message("script_vm_t::intern_call_function", "start: stack=%d nparams=%d ret=%d", sq_gettop(job), nparams, retvalue);
283 	const char* err = NULL;
284 	uint32 opcodes = ct == FORCEX ? 100000 : 10000;
285 	// call the script
286 	if (!SQ_SUCCEEDED(sq_call_restricted(job, nparams, retvalue, ct == FORCE  ||  ct == FORCEX, opcodes))) {
287 		err = "Call function failed";
288 		retvalue = false;
289 	}
290 	if (ct == FORCE  ||  ct == FORCEX) {
291 		sq_block_suspend(job, NULL);
292 	}
293 	if (sq_getvmstate(job) != SQ_VMSTATE_SUSPENDED) {
294 		// remove closure
295 		sq_remove(job, retvalue ? -2 : -1);
296 		if (ct == QUEUE  &&  retvalue) {
297 
298 			sq_pushregistrytable(job);
299 			// notify call backs only if call was queued
300 			bool notify = false;
301 			script_api::get_slot(job, "was_queued", notify, -1);
302 			sq_pop(job, 1);
303 
304 			if (notify) {
305 				intern_call_callbacks(job);
306 			}
307 		}
308 		END_STACK_WATCH(job, -nparams-1 + retvalue);
309 	}
310 	else {
311 		assert(ct != FORCE  &&  ct != FORCEX);
312 		// call suspended: pop dummy return value
313 		if (retvalue) {
314 			sq_poptop(job);
315 		}
316 		// save retvalue flag, number of parameters
317 		sq_pushregistrytable(job);
318 		script_api::create_slot(job, "retvalue", retvalue);
319 		script_api::create_slot(job, "nparams", nparams);
320 		sq_poptop(job);
321 		err = "suspended";
322 	}
323 	dbg->message("script_vm_t::intern_call_function", "stack=%d err=%s", sq_gettop(job)-retvalue, err);
324 	return err;
325 }
326 
intern_resume_call(HSQUIRRELVM job)327 void script_vm_t::intern_resume_call(HSQUIRRELVM job)
328 {
329 	BEGIN_STACK_WATCH(job);
330 	// stack: clean
331 	// get retvalue flag
332 	sq_pushregistrytable(job);
333 	bool retvalue = false;
334 	script_api::get_slot(job, "retvalue", retvalue, -1);
335 	int nparams = 0;
336 	script_api::get_slot(job, "nparams", nparams, -1);
337 	bool wait = false;
338 	script_api::get_slot(job, "wait_external", wait, -1);
339 	sq_pop(job, 1);
340 	END_STACK_WATCH(job, 0);
341 
342 	if (wait) {
343 		dbg->message("script_vm_t::intern_resume_call", "waits for external call to be able to proceed");
344 		return;
345 	}
346 	if (!sq_canresumevm(job)) {
347 		// vm waits for return value to suspended call
348 		dbg->message("script_vm_t::intern_resume_call", "waiting for return value");
349 		return;
350 	}
351 	// vm suspended, but not from call to our methods
352 	if (nparams < 0) {
353 		retvalue = false;
354 	}
355 
356 	// resume v.m.
357 	if (!SQ_SUCCEEDED(sq_resumevm(job, retvalue, 10000))) {
358 		retvalue = false;
359 	}
360 	// if finished, clear stack
361 	if (sq_getvmstate(job) != SQ_VMSTATE_SUSPENDED) {
362 
363 		if (nparams >=0 ) {
364 			BEGIN_STACK_WATCH(job);
365 			// remove closure & args
366 			for(int i=0; i<nparams+1; i++) {
367 				sq_remove(job, retvalue ? -2 : -1);
368 			}
369 			if (retvalue) {
370 				intern_call_callbacks(job);
371 				sq_poptop(job);
372 			}
373 			dbg->message("script_vm_t::intern_resume_call", "in between stack=%d", sq_gettop(job));
374 			END_STACK_WATCH(job, -1-nparams-retvalue);
375 		}
376 		else {
377 			// brute force clean stack
378 			sq_settop(job, 0);
379 		}
380 
381 		// make nparams invalid
382 		sq_pushregistrytable(job);
383 		script_api::create_slot(job, "nparams", -1);
384 		sq_poptop(job);
385 
386 		// proceed with next call in queue
387 		if (intern_prepare_queued(job, nparams, retvalue)) {
388 			const char* err = intern_call_function(job, QUEUE, nparams, retvalue);
389 			if (err == NULL  &&  retvalue) {
390 				// remove return value: call was queued thus remove return value from stack
391 				sq_poptop(job);
392 			}
393 		}
394 	}
395 	else {
396 		if (retvalue) {
397 			sq_poptop(job);
398 		}
399 	}
400 
401 	dbg->message("script_vm_t::intern_resume_call", "stack=%d", sq_gettop(job));
402 }
403 
404 /**
405  * Stack(job): expects closure, nparams*objects, clean on exit.
406  * Put call into registry.queue, callback into registry.queued_callbacks.
407  * Cleans registry.pending_callback.
408  * @param nparams number of parameter of the queued call
409  * @param retvalue whether the call would return something
410  */
intern_queue_call(HSQUIRRELVM job,int nparams,bool retvalue)411 void script_vm_t::intern_queue_call(HSQUIRRELVM job, int nparams, bool retvalue)
412 {
413 	BEGIN_STACK_WATCH(job);
414 	// stack: closure, nparams*objects
415 	// queue call: put closure and parameters in array
416 	script_api::param<bool>::push(job, retvalue);
417 	sq_newarray(job, 0); // to hold parameters for queued calls
418 	// stack: closure, nparams*objects, retvalue, array
419 	for(int i=nparams+2; i>0; i--) {
420 		sq_push(job, -2);
421 		sq_arrayappend(job, -2);
422 		sq_remove(job, -2);
423 	}
424 	// stack: array
425 	sq_pushregistrytable(job);
426 	sq_pushstring(job, "queue", -1);
427 	sq_get(job, -2);
428 	// stack: array, registry, queue
429 	// search queue whether our call is already there
430 	sint32 size = sq_getsize(job, -1);
431 	bool equal = false;
432 	for(sint32 i=0; (i<size) && !equal; i++) {
433 		sq_pushinteger(job, i);
434 		sq_get(job, -2);
435 		// stack: [...], queue[i]
436 		sint32 n = sq_getsize(job, -1);
437 		if (n != nparams+2) {
438 			continue; // different number of arguments
439 		}
440 		equal = true;
441 		// compare arguments of call (including closure and retvalue)
442 		for(sint32 j=0; (j<n)  &&  equal; j++) {
443 			sq_pushinteger(job, j);
444 			sq_get(job, -2);
445 			// stack: array, registry, queue, queue[i], queue[i][j]
446 			sq_pushinteger(job, j);
447 			sq_get(job, -6);
448 			equal = sq_cmp(job)==0;
449 			sq_pop(job, 2);
450 			// stack: array, registry, queue, queue[i]
451 		}
452 		// found identic call
453 		// add callback to queue
454 		if (equal) {
455 			sq_pushstring(job, "queued_callbacks", -1);
456 			sq_get(job, -4);
457 			sq_pushinteger(job, i);
458 			sq_get(job, -2);
459 			// stack: array, registry, queue, queue[i], queued_callbacks, queued_callbacks[i]
460 			sq_pushstring(job, "pending_callback", -1);
461 			// delete pending_callback slot and push it
462 			if (SQ_SUCCEEDED( sq_deleteslot(job, -6, true) )) {
463 				sq_arrayappend(job, -2);
464 			}
465 			sq_pop(job, 2);
466 		}
467 		sq_poptop(job);
468 	}
469 	// stack: array, registry, queue
470 	if (!equal) {
471 		sq_push(job, -3);
472 		sq_arrayappend(job, -2);
473 		// add callback to queue
474 		sq_pushstring(job, "queued_callbacks", -1);
475 		sq_get(job, -3);
476 		sq_newarray(job, 10);
477 		// stack: array, registry, queue, queued_callbacks, queued_callbacks[end]
478 		sq_pushstring(job, "pending_callback", -1);
479 		// delete pending_callback slot and push it
480 		if (SQ_SUCCEEDED( sq_deleteslot(job, -5, true) )) {
481 			sq_arrayappend(job, -2);
482 		}
483 		sq_arrayappend(job, -2);
484 		sq_poptop(job);
485 	}
486 	else {
487 		dbg->message("script_vm_t::intern_queue_call", "NOT QUEUED stack=%d", sq_gettop(job));
488 	}
489 	sq_pop(job, 3);
490 
491 	END_STACK_WATCH(job, -nparams-1);
492 	dbg->message("script_vm_t::intern_queue_call", "stack=%d", sq_gettop(job));
493 	// stack: clean
494 }
495 
496 /**
497  * Removes queue(0) and puts it on stack.
498  * Activates callback by intern_make_queued_callback_active().
499  * Stack(job): on success - closure, nparams*objects, on failure - unchanged.
500  * @param nparams number of parameters of the call on the stack
501  * @param retvalue whether queued call returns something
502  * @returns true if a new queued call is on the stack
503  */
intern_prepare_queued(HSQUIRRELVM job,int & nparams,bool & retvalue)504 bool script_vm_t::intern_prepare_queued(HSQUIRRELVM job, int &nparams, bool &retvalue)
505 {
506 	BEGIN_STACK_WATCH(job);
507 	// queued calls
508 	sq_pushregistrytable(job);
509 	sq_pushstring(job, "queue", -1);
510 	sq_get(job, -2);
511 	sq_pushinteger(job, 0);
512 	if (SQ_SUCCEEDED(sq_get(job, -2))) {
513 		// there are suspended jobs
514 		// stack: registry, queue, array[ retvalue nparams*objects closure]
515 		nparams = -2;
516 		while( SQ_SUCCEEDED(sq_arraypop(job, -3-nparams, true))) {
517 			nparams++;
518 		}
519 		retvalue = script_api::param<bool>::get(job, -1);
520 		sq_poptop(job);
521 
522 		intern_make_queued_callback_active();
523 		// stack: registry, queue, array, closure, nparams*objects
524 		END_STACK_WATCH(job, nparams+4);
525 		// mark this call as queued
526 		sq_pushstring(job, "was_queued", -1);
527 		script_api::param<bool>::push(job, true);
528 		sq_createslot(job, -6-nparams);
529 		// cleanup
530 		sq_remove(job, -2-nparams);
531 		sq_arrayremove(job, -2-nparams, 0);
532 		sq_remove(job, -2-nparams);
533 		sq_remove(job, -2-nparams);
534 		// stack: closure, nparams*objects
535 		dbg->message("script_vm_t::intern_prepare_queued", "stack=%d nparams=%d ret=%d", sq_gettop(job), nparams, retvalue);
536 
537 		END_STACK_WATCH(job, nparams+1);
538 		return true;
539 	}
540 	else {
541 		// no suspended jobs: clean stack
542 		sq_pop(job, 2);
543 	}
544 	END_STACK_WATCH(job, 0);
545 	return false;
546 }
547 
548 
549 /**
550  * Prepare function call.
551  * Stack(vm): pushes registry, nret, closure, root.
552  */
intern_prepare_pending_callback(const char * function,sint32 nret)553 bool script_vm_t::intern_prepare_pending_callback(const char* function, sint32 nret)
554 {
555 	BEGIN_STACK_WATCH(vm);
556 	sq_pushregistrytable(vm);
557 	script_api::param<sint32>::push(vm, nret+1); // +1 to account for root table
558 	sq_pushstring(vm, function, -1);
559 	bool ok = SQ_SUCCEEDED(sq_get(vm, -3));
560 	if (ok) {
561 		// stack: registry, nret, closure, root
562 		sq_pushroottable(vm);
563 	}
564 	else {
565 		// stack: clear
566 		sq_pop(vm, 2);
567 	}
568 	END_STACK_WATCH(vm, ok ? 4 : 0);
569 	return ok;
570 }
571 
572 /**
573  * Sets registry.pending_callback.
574  * Stack(vm): expects registry, nret, closure, nparams*objects; clears upon exit.
575  */
intern_store_pending_callback(sint32 nparams)576 void script_vm_t::intern_store_pending_callback(sint32 nparams)
577 {
578 	BEGIN_STACK_WATCH(vm);
579 	// stack: registry, nret, closure, nparams*objects
580 	// put closure and parameters in array
581 	sq_newarray(vm, 0);
582 	// stack: registry, nret, closure, nparams*objects, array
583 	for(int i=0; i<nparams+2; i++) {
584 		sq_push(vm, -2);
585 		sq_arrayappend(vm, -2);
586 		sq_remove(vm, -2);
587 	}
588 	// stack: registry, array
589 	sq_pushstring(vm, "pending_callback", -1);
590 	sq_push(vm, -2);
591 	// stack: registry, array, string, array
592 	sq_createslot(vm, -4);
593 	sq_pop(vm, 2);
594 	END_STACK_WATCH(vm,-nparams-3);
595 }
596 
597 /**
598  * Deletes registry.pending_callback.
599  * Stack(vm): unchanged
600  */
clear_pending_callback()601 void script_vm_t::clear_pending_callback()
602 {
603 	BEGIN_STACK_WATCH(vm);
604 
605 	sq_pushregistrytable(vm);
606 	sq_pushstring(vm, "pending_callback", -1);
607 	sq_deleteslot(vm, -2, false);
608 	sq_poptop(vm);
609 	END_STACK_WATCH(vm,0);
610 }
611 
612 /**
613  * Sets registry.active_callbacks.
614  * Deletes registry.queued_callbacks[0].
615  * Stack(vm) unchanged.
616  */
intern_make_queued_callback_active()617 void script_vm_t::intern_make_queued_callback_active()
618 {
619 	BEGIN_STACK_WATCH(vm);
620 	sq_pushregistrytable(vm);
621 	sq_pushstring(vm, "queued_callbacks", -1);
622 	sq_get(vm, -2);
623 	sq_pushstring(vm, "active_callbacks", -1);
624 	sq_pushinteger(vm, 0);
625 	if (SQ_SUCCEEDED(sq_get(vm, -3))) {
626 		// stack: registry, queued_callbacks, "..", queued_callbacks[0]
627 		// active_callbacks = queued_callbacks[0]
628 		sq_createslot(vm, -4);
629 		// remove queued_callbacks[0]
630 		sq_arrayremove(vm, -1, 0);
631 	}
632 	else {
633 		sq_poptop(vm);
634 	}
635 	sq_pop(vm,2);
636 	END_STACK_WATCH(vm,0);
637 }
638 
639 /**
640  * Sets registry.active_callbacks.
641  * Deletes registry.pending_callback.
642  * Stack(vm) unchanged.
643  */
intern_make_pending_callback_active()644 void script_vm_t::intern_make_pending_callback_active()
645 {
646 	BEGIN_STACK_WATCH(vm);
647 	sq_pushregistrytable(vm);
648 	sq_pushstring(vm, "active_callbacks", -1);
649 	sq_newarray(vm, 1);
650 	sq_pushstring(vm, "pending_callback", -1);
651 	if (SQ_SUCCEEDED( sq_deleteslot(vm, -4, true) ) ) {
652 		// stack: registry, "..", array[], pending_callback
653 		sq_arrayappend(vm, -2);
654 		// stack: registry, "..", array[ 0=>pending_callback ]
655 		sq_createslot(vm, -3);
656 	}
657 	else {
658 		// stack: registry, "..", array[]
659 		sq_poptop(vm);
660 		// delete active_callbacks slot
661 		sq_deleteslot(vm, -2, false);
662 	}
663 	sq_poptop(vm);
664 	END_STACK_WATCH(vm,0);
665 }
666 
667 /**
668  *
669  *
670  * Stack(job): return value, unchanged
671  */
intern_call_callbacks(HSQUIRRELVM job)672 void script_vm_t::intern_call_callbacks(HSQUIRRELVM job)
673 {
674 	BEGIN_STACK_WATCH(job);
675 	sq_pushregistrytable(job);
676 	sq_pushstring(job, "active_callbacks", -1);
677 	if (SQ_SUCCEEDED( sq_deleteslot(job, -2, true) ) ) {
678 		BEGIN_STACK_WATCH(job);
679 		if (SQ_SUCCEEDED( sq_arraypop(job, -1, true))) {
680 			if (SQ_SUCCEEDED(sq_arraypop(job, -1, true))) {
681 				sint32 nret = script_api::param<sint32>::get(job, -1);
682 				sq_poptop(job);
683 				BEGIN_STACK_WATCH(job);
684 				// stack: retval, registry, active_cb, active_cb[end]=array[ nparams*objects closure]
685 				int nparams = -1;
686 				while( SQ_SUCCEEDED(sq_arraypop(job, -2-nparams, true))) {
687 					nparams++;
688 					// replace parameter by return value
689 					if (nret > 0  &&  nret == nparams) {
690 						sq_poptop(job);
691 						sq_push(job, -4-nparams);
692 					}
693 				}
694 				// stack: retval, registry, active_cb, active_cb[end], closure, nparams*objects
695 				sq_call_restricted(job, nparams, false, true, 100);
696 				// remove closure and finished callback
697 				sq_pop(job, 1);
698 				END_STACK_WATCH(job,0);
699 			}
700 			sq_poptop(job);
701 		}
702 		END_STACK_WATCH(job,0);
703 		sq_poptop(job);
704 	}
705 	sq_poptop(job);
706 	END_STACK_WATCH(job,0);
707 }
708 
709 
set_my_player(uint8 player_nr)710 void script_vm_t::set_my_player(uint8 player_nr)
711 {
712 	sq_pushregistrytable(vm);
713 	script_api::create_slot(vm, "my_player_nr", player_nr);
714 	sq_poptop(vm);
715 }
716 
717 
718 /* -------- management of suspended scripts that wait for return value ----------- */
719 
720 inthashtable_tpl<uint32,HSQUIRRELVM> suspended_scripts_t::suspended_scripts;
721 
722 
get_unique_key(void * ptr)723 uint32 suspended_scripts_t::get_unique_key(void* ptr)
724 {
725 	uint32 key = (uint32)(size_t)ptr;
726 	while (key == 0  ||  suspended_scripts.get(key)) {
727 		key ++;
728 	}
729 	return key;
730 }
731 
732 
register_suspended_script(uint32 key,HSQUIRRELVM vm)733 void suspended_scripts_t::register_suspended_script(uint32 key, HSQUIRRELVM vm)
734 {
735 	suspended_scripts.set(key, vm);
736 
737 	// mark vm to wait for external call to allow for wake-up
738 	sq_pushregistrytable(vm);
739 	bool wait = true;
740 	script_api::create_slot(vm, "wait_external", wait);
741 	sq_poptop(vm);
742 }
743 
744 
remove_suspended_script(uint32 key)745 HSQUIRRELVM suspended_scripts_t::remove_suspended_script(uint32 key)
746 {
747 	return suspended_scripts.remove(key);
748 }
749 
750 
remove_vm(HSQUIRRELVM vm)751 void suspended_scripts_t::remove_vm(HSQUIRRELVM vm)
752 {
753 	inthashtable_tpl<uint32,HSQUIRRELVM>::iterator iter=suspended_scripts.begin(), end=suspended_scripts.end();
754 	for(; iter != end; ) {
755 		if ( (*iter).value == vm) {
756 			iter = suspended_scripts.erase(iter);
757 		}
758 		else {
759 			++iter;
760 		}
761 	}
762 }
763