OpenTTD

- Main Page
- Related Pages
- Modules
- Namespaces
- Data Structures
- Files

- File List
- Globals

script_instance.cpp
Go to the documentation of this file.
1 /* $Id: script_instance.cpp 26785 2014-09-07 09:30:57Z rubidium $ */
2
3 /*
4 * This file is part of OpenTTD.
5 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
6 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
7 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
8 */
9
12 #include "../stdafx.h"
13 #include "../debug.h"
14 #include "../saveload/saveload.h"
15
16 #include "../script/squirrel_class.hpp"
17
18 #include "script_fatalerror.hpp"
19 #include "script_storage.hpp"
20 #include "script_info.hpp"
21 #include "script_instance.hpp"
22
23 #include "api/script_controller.hpp"
24 #include "api/script_error.hpp"
25 #include "api/script_event.hpp"
26 #include "api/script_log.hpp"
27
28 #include "../company_base.h"
29 #include "../company_func.h"
30 #include "../fileio_func.h"
31
32 #include "../safeguards.h"
33
34 ScriptStorage::~ScriptStorage()
35 {
36 /* Free our pointers */
37 if (event_data != NULL) ScriptEventController::FreeEventPointer();
38 if (log_data != NULL) ScriptLog::FreeLogPointer();
39 }
40
46 static void PrintFunc(bool error_msg, const SQChar *message)
47 {
48 /* Convert to OpenTTD internal capable string */
49 ScriptController::Print(error_msg, message);
50 }
51
52 ScriptInstance::ScriptInstance(const char *APIName) :
53 engine(NULL),
54 versionAPI(NULL),
55 controller(NULL),
56 storage(NULL),
57 instance(NULL),
58 is_started(false),
59 is_dead(false),
60 is_save_data_on_stack(false),
61 suspend(0),
62 is_paused(false),
63 callback(NULL)
64 {
65 this->storage = new ScriptStorage();
66 this->engine = new Squirrel(APIName);
67 this->engine->SetPrintFunction(&PrintFunc);
68 }
69
70 void ScriptInstance::Initialize(const char *main_script, const char *instance_name, CompanyID company)
71 {
72 ScriptObject::ActiveInstance active(this);
73
74 this->controller = new ScriptController(company);
75
76 /* Register the API functions and classes */
77 this->engine->SetGlobalPointer(this->engine);
78 this->RegisterAPI();
79
80 try {
81 ScriptObject::SetAllowDoCommand(false);
82 /* Load and execute the script for this script */
83 if (strcmp(main_script, "%_dummy") == 0) {
84 this->LoadDummyScript();
85 } else if (!this->engine->LoadScript(main_script) || this->engine->IsSuspended()) {
86 if (this->engine->IsSuspended()) ScriptLog::Error("This script took too long to load script. AI is not started.");
87 this->Died();
88 return;
89 }
90
91 /* Create the main-class */
92 this->instance = MallocT<SQObject>(1);
93 if (!this->engine->CreateClassInstance(instance_name, this->controller, this->instance)) {
94 this->Died();
95 return;
96 }
97 ScriptObject::SetAllowDoCommand(true);
98 } catch (Script_FatalError e) {
99 this->is_dead = true;
100 this->engine->ThrowError(e.GetErrorMessage());
101 this->engine->ResumeError();
102 this->Died();
103 }
104 }
105
106 void ScriptInstance::RegisterAPI()
107 {
108 extern void squirrel_register_std(Squirrel *engine);
109 squirrel_register_std(this->engine);
110 }
111
112 bool ScriptInstance::LoadCompatibilityScripts(const char *api_version, Subdirectory dir)
113 {
114 char script_name[32];
115 seprintf(script_name, lastof(script_name), "compat_%s.nut", api_version);
116 char buf[MAX_PATH];
117 Searchpath sp;
118 FOR_ALL_SEARCHPATHS(sp) {
119 FioAppendDirectory(buf, lastof(buf), sp, dir);
120 strecat(buf, script_name, lastof(buf));
121 if (!FileExists(buf)) continue;
122
123 if (this->engine->LoadScript(buf)) return true;
124
125 ScriptLog::Error("Failed to load API compatibility script");
126 DEBUG(script, 0, "Error compiling / running API compatibility script: %s", buf);
127 return false;
128 }
129
130 ScriptLog::Warning("API compatibility script not found");
131 return true;
132 }
133
134 ScriptInstance::~ScriptInstance()
135 {
136 ScriptObject::ActiveInstance active(this);
137
138 if (instance != NULL) this->engine->ReleaseObject(this->instance);
139 if (engine != NULL) delete this->engine;
140 delete this->storage;
141 delete this->controller;
142 free(this->instance);
143 }
144
145 void ScriptInstance::Continue()
146 {
147 assert(this->suspend < 0);
148 this->suspend = -this->suspend - 1;
149 }
150
151 void ScriptInstance::Died()
152 {
153 DEBUG(script, 0, "The script died unexpectedly.");
154 this->is_dead = true;
155
156 if (this->instance != NULL) this->engine->ReleaseObject(this->instance);
157 delete this->engine;
158 this->instance = NULL;
159 this->engine = NULL;
160 }
161
162 void ScriptInstance::GameLoop()
163 {
164 ScriptObject::ActiveInstance active(this);
165
166 if (this->IsDead()) return;
167 if (this->engine->HasScriptCrashed()) {
168 /* The script crashed during saving, kill it here. */
169 this->Died();
170 return;
171 }
172 if (this->is_paused) return;
173 this->controller->ticks++;
174
175 if (this->suspend < -1) this->suspend++; // Multiplayer suspend, increase up to -1.
176 if (this->suspend < 0) return; // Multiplayer suspend, wait for Continue().
177 if (--this->suspend > 0) return; // Singleplayer suspend, decrease to 0.
178
179 _current_company = ScriptObject::GetCompany();
180
181 /* If there is a callback to call, call that first */
182 if (this->callback != NULL) {
183 if (this->is_save_data_on_stack) {
184 sq_poptop(this->engine->GetVM());
185 this->is_save_data_on_stack = false;
186 }
187 try {
188 this->callback(this);
189 } catch (Script_Suspend e) {
190 this->suspend = e.GetSuspendTime();
191 this->callback = e.GetSuspendCallback();
192
193 return;
194 }
195 }
196
197 this->suspend = 0;
198 this->callback = NULL;
199
200 if (!this->is_started) {
201 try {
202 ScriptObject::SetAllowDoCommand(false);
203 /* Run the constructor if it exists. Don't allow any DoCommands in it. */
204 if (this->engine->MethodExists(*this->instance, "constructor")) {
205 if (!this->engine->CallMethod(*this->instance, "constructor", MAX_CONSTRUCTOR_OPS) || this->engine->IsSuspended()) {
206 if (this->engine->IsSuspended()) ScriptLog::Error("This script took too long to initialize. Script is not started.");
207 this->Died();
208 return;
209 }
210 }
211 if (!this->CallLoad() || this->engine->IsSuspended()) {
212 if (this->engine->IsSuspended()) ScriptLog::Error("This script took too long in the Load function. Script is not started.");
213 this->Died();
214 return;
215 }
216 ScriptObject::SetAllowDoCommand(true);
217 /* Start the script by calling Start() */
218 if (!this->engine->CallMethod(*this->instance, "Start", _settings_game.script.script_max_opcode_till_suspend) || !this->engine->IsSuspended()) this->Died();
219 } catch (Script_Suspend e) {
220 this->suspend = e.GetSuspendTime();
221 this->callback = e.GetSuspendCallback();
222 } catch (Script_FatalError e) {
223 this->is_dead = true;
224 this->engine->ThrowError(e.GetErrorMessage());
225 this->engine->ResumeError();
226 this->Died();
227 }
228
229 this->is_started = true;
230 return;
231 }
232 if (this->is_save_data_on_stack) {
233 sq_poptop(this->engine->GetVM());
234 this->is_save_data_on_stack = false;
235 }
236
237 /* Continue the VM */
238 try {
239 if (!this->engine->Resume(_settings_game.script.script_max_opcode_till_suspend)) this->Died();
240 } catch (Script_Suspend e) {
241 this->suspend = e.GetSuspendTime();
242 this->callback = e.GetSuspendCallback();
243 } catch (Script_FatalError e) {
244 this->is_dead = true;
245 this->engine->ThrowError(e.GetErrorMessage());
246 this->engine->ResumeError();
247 this->Died();
248 }
249 }
250
251 void ScriptInstance::CollectGarbage() const
252 {
253 if (this->is_started && !this->IsDead()) this->engine->CollectGarbage();
254 }
255
256 /* static */ void ScriptInstance::DoCommandReturn(ScriptInstance *instance)
257 {
258 instance->engine->InsertResult(ScriptObject::GetLastCommandRes());
259 }
260
261 /* static */ void ScriptInstance::DoCommandReturnVehicleID(ScriptInstance *instance)
262 {
263 instance->engine->InsertResult(ScriptObject::GetNewVehicleID());
264 }
265
266 /* static */ void ScriptInstance::DoCommandReturnSignID(ScriptInstance *instance)
267 {
268 instance->engine->InsertResult(ScriptObject::GetNewSignID());
269 }
270
271 /* static */ void ScriptInstance::DoCommandReturnGroupID(ScriptInstance *instance)
272 {
273 instance->engine->InsertResult(ScriptObject::GetNewGroupID());
274 }
275
276 /* static */ void ScriptInstance::DoCommandReturnGoalID(ScriptInstance *instance)
277 {
278 instance->engine->InsertResult(ScriptObject::GetNewGoalID());
279 }
280
281 /* static */ void ScriptInstance::DoCommandReturnStoryPageID(ScriptInstance *instance)
282 {
283 instance->engine->InsertResult(ScriptObject::GetNewStoryPageID());
284 }
285
286 /* static */ void ScriptInstance::DoCommandReturnStoryPageElementID(ScriptInstance *instance)
287 {
288 instance->engine->InsertResult(ScriptObject::GetNewStoryPageElementID());
289 }
290
291 ScriptStorage *ScriptInstance::GetStorage()
292 {
293 return this->storage;
294 }
295
296 void *ScriptInstance::GetLogPointer()
297 {
298 ScriptObject::ActiveInstance active(this);
299
300 return ScriptObject::GetLogPointer();
301 }
302
303 /*
304 * All data is stored in the following format:
305 * First 1 byte indicating if there is a data blob at all.
306 * 1 byte indicating the type of data.
307 * The data itself, this differs per type:
308 * - integer: a binary representation of the integer (int32).
309 * - string: First one byte with the string length, then a 0-terminated char
310 * array. The string can't be longer than 255 bytes (including
311 * terminating '\0').
312 * - array: All data-elements of the array are saved recursive in this
313 * format, and ended with an element of the type
314 * SQSL_ARRAY_TABLE_END.
315 * - table: All key/value pairs are saved in this format (first key 1, then
316 * value 1, then key 2, etc.). All keys and values can have an
317 * arbitrary type (as long as it is supported by the save function
318 * of course). The table is ended with an element of the type
319 * SQSL_ARRAY_TABLE_END.
320 * - bool: A single byte with value 1 representing true and 0 false.
321 * - null: No data.
322 */
323
325 enum SQSaveLoadType {
326 SQSL_INT = 0x00,
327 SQSL_STRING = 0x01,
328 SQSL_ARRAY = 0x02,
329 SQSL_TABLE = 0x03,
330 SQSL_BOOL = 0x04,
331 SQSL_NULL = 0x05,
332 SQSL_ARRAY_TABLE_END = 0xFF,
333 };
334
335 static byte _script_sl_byte;
336
338 static const SaveLoad _script_byte[] = {
339 SLEG_VAR(_script_sl_byte, SLE_UINT8),
340 SLE_END()
341 };
342
343 /* static */ bool ScriptInstance::SaveObject(HSQUIRRELVM vm, SQInteger index, int max_depth, bool test)
344 {
345 if (max_depth == 0) {
346 ScriptLog::Error("Savedata can only be nested to 25 deep. No data saved."); // SQUIRREL_MAX_DEPTH = 25
347 return false;
348 }
349
350 switch (sq_gettype(vm, index)) {
351 case OT_INTEGER: {
352 if (!test) {
353 _script_sl_byte = SQSL_INT;
354 SlObject(NULL, _script_byte);
355 }
356 SQInteger res;
357 sq_getinteger(vm, index, &res);
358 if (!test) {
359 int value = (int)res;
360 SlArray(&value, 1, SLE_INT32);
361 }
362 return true;
363 }
364
365 case OT_STRING: {
366 if (!test) {
367 _script_sl_byte = SQSL_STRING;
368 SlObject(NULL, _script_byte);
369 }
370 const SQChar *buf;
371 sq_getstring(vm, index, &buf);
372 size_t len = strlen(buf) + 1;
373 if (len >= 255) {
374 ScriptLog::Error("Maximum string length is 254 chars. No data saved.");
375 return false;
376 }
377 if (!test) {
378 _script_sl_byte = (byte)len;
379 SlObject(NULL, _script_byte);
380 SlArray(const_cast<char *>(buf), len, SLE_CHAR);
381 }
382 return true;
383 }
384
385 case OT_ARRAY: {
386 if (!test) {
387 _script_sl_byte = SQSL_ARRAY;
388 SlObject(NULL, _script_byte);
389 }
390 sq_pushnull(vm);
391 while (SQ_SUCCEEDED(sq_next(vm, index - 1))) {
392 /* Store the value */
393 bool res = SaveObject(vm, -1, max_depth - 1, test);
394 sq_pop(vm, 2);
395 if (!res) {
396 sq_pop(vm, 1);
397 return false;
398 }
399 }
400 sq_pop(vm, 1);
401 if (!test) {
402 _script_sl_byte = SQSL_ARRAY_TABLE_END;
403 SlObject(NULL, _script_byte);
404 }
405 return true;
406 }
407
408 case OT_TABLE: {
409 if (!test) {
410 _script_sl_byte = SQSL_TABLE;
411 SlObject(NULL, _script_byte);
412 }
413 sq_pushnull(vm);
414 while (SQ_SUCCEEDED(sq_next(vm, index - 1))) {
415 /* Store the key + value */
416 bool res = SaveObject(vm, -2, max_depth - 1, test) && SaveObject(vm, -1, max_depth - 1, test);
417 sq_pop(vm, 2);
418 if (!res) {
419 sq_pop(vm, 1);
420 return false;
421 }
422 }
423 sq_pop(vm, 1);
424 if (!test) {
425 _script_sl_byte = SQSL_ARRAY_TABLE_END;
426 SlObject(NULL, _script_byte);
427 }
428 return true;
429 }
430
431 case OT_BOOL: {
432 if (!test) {
433 _script_sl_byte = SQSL_BOOL;
434 SlObject(NULL, _script_byte);
435 }
436 SQBool res;
437 sq_getbool(vm, index, &res);
438 if (!test) {
439 _script_sl_byte = res ? 1 : 0;
440 SlObject(NULL, _script_byte);
441 }
442 return true;
443 }
444
445 case OT_NULL: {
446 if (!test) {
447 _script_sl_byte = SQSL_NULL;
448 SlObject(NULL, _script_byte);
449 }
450 return true;
451 }
452
453 default:
454 ScriptLog::Error("You tried to save an unsupported type. No data saved.");
455 return false;
456 }
457 }
458
459 /* static */ void ScriptInstance::SaveEmpty()
460 {
461 _script_sl_byte = 0;
462 SlObject(NULL, _script_byte);
463 }
464
465 void ScriptInstance::Save()
466 {
467 ScriptObject::ActiveInstance active(this);
468
469 /* Don't save data if the script didn't start yet or if it crashed. */
470 if (this->engine == NULL || this->engine->HasScriptCrashed()) {
471 SaveEmpty();
472 return;
473 }
474
475 HSQUIRRELVM vm = this->engine->GetVM();
476 if (this->is_save_data_on_stack) {
477 _script_sl_byte = 1;
478 SlObject(NULL, _script_byte);
479 /* Save the data that was just loaded. */
480 SaveObject(vm, -1, SQUIRREL_MAX_DEPTH, false);
481 } else if (!this->is_started) {
482 SaveEmpty();
483 return;
484 } else if (this->engine->MethodExists(*this->instance, "Save")) {
485 HSQOBJECT savedata;
486 /* We don't want to be interrupted during the save function. */
487 bool backup_allow = ScriptObject::GetAllowDoCommand();
488 ScriptObject::SetAllowDoCommand(false);
489 try {
490 if (!this->engine->CallMethod(*this->instance, "Save", &savedata, MAX_SL_OPS)) {
491 /* The script crashed in the Save function. We can't kill
492 * it here, but do so in the next script tick. */
493 SaveEmpty();
494 this->engine->CrashOccurred();
495 return;
496 }
497 } catch (Script_FatalError e) {
498 /* If we don't mark the script as dead here cleaning up the squirrel
499 * stack could throw Script_FatalError again. */
500 this->is_dead = true;
501 this->engine->ThrowError(e.GetErrorMessage());
502 this->engine->ResumeError();
503 SaveEmpty();
504 /* We can't kill the script here, so mark it as crashed (not dead) and
505 * kill it in the next script tick. */
506 this->is_dead = false;
507 this->engine->CrashOccurred();
508 return;
509 }
510 ScriptObject::SetAllowDoCommand(backup_allow);
511
512 if (!sq_istable(savedata)) {
513 ScriptLog::Error(this->engine->IsSuspended() ? "This script took too long to Save." : "Save function should return a table.");
514 SaveEmpty();
515 this->engine->CrashOccurred();
516 return;
517 }
518 sq_pushobject(vm, savedata);
519 if (SaveObject(vm, -1, SQUIRREL_MAX_DEPTH, true)) {
520 _script_sl_byte = 1;
521 SlObject(NULL, _script_byte);
522 SaveObject(vm, -1, SQUIRREL_MAX_DEPTH, false);
523 this->is_save_data_on_stack = true;
524 } else {
525 SaveEmpty();
526 this->engine->CrashOccurred();
527 }
528 } else {
529 ScriptLog::Warning("Save function is not implemented");
530 _script_sl_byte = 0;
531 SlObject(NULL, _script_byte);
532 }
533 }
534
535 void ScriptInstance::Pause()
536 {
537 /* Suspend script. */
538 HSQUIRRELVM vm = this->engine->GetVM();
539 Squirrel::DecreaseOps(vm, _settings_game.script.script_max_opcode_till_suspend);
540
541 this->is_paused = true;
542 }
543
544 void ScriptInstance::Unpause()
545 {
546 this->is_paused = false;
547 }
548
549 bool ScriptInstance::IsPaused()
550 {
551 return this->is_paused;
552 }
553
554 /* static */ bool ScriptInstance::LoadObjects(HSQUIRRELVM vm)
555 {
556 SlObject(NULL, _script_byte);
557 switch (_script_sl_byte) {
558 case SQSL_INT: {
559 int value;
560 SlArray(&value, 1, SLE_INT32);
561 if (vm != NULL) sq_pushinteger(vm, (SQInteger)value);
562 return true;
563 }
564
565 case SQSL_STRING: {
566 SlObject(NULL, _script_byte);
567 static char buf[256];
568 SlArray(buf, _script_sl_byte, SLE_CHAR);
569 if (vm != NULL) sq_pushstring(vm, buf, -1);
570 return true;
571 }
572
573 case SQSL_ARRAY: {
574 if (vm != NULL) sq_newarray(vm, 0);
575 while (LoadObjects(vm)) {
576 if (vm != NULL) sq_arrayappend(vm, -2);
577 /* The value is popped from the stack by squirrel. */
578 }
579 return true;
580 }
581
582 case SQSL_TABLE: {
583 if (vm != NULL) sq_newtable(vm);
584 while (LoadObjects(vm)) {
585 LoadObjects(vm);
586 if (vm != NULL) sq_rawset(vm, -3);
587 /* The key (-2) and value (-1) are popped from the stack by squirrel. */
588 }
589 return true;
590 }
591
592 case SQSL_BOOL: {
593 SlObject(NULL, _script_byte);
594 if (vm != NULL) sq_pushbool(vm, (SQBool)(_script_sl_byte != 0));
595 return true;
596 }
597
598 case SQSL_NULL: {
599 if (vm != NULL) sq_pushnull(vm);
600 return true;
601 }
602
603 case SQSL_ARRAY_TABLE_END: {
604 return false;
605 }
606
607 default: NOT_REACHED();
608 }
609 }
610
611 /* static */ void ScriptInstance::LoadEmpty()
612 {
613 SlObject(NULL, _script_byte);
614 /* Check if there was anything saved at all. */
615 if (_script_sl_byte == 0) return;
616
617 LoadObjects(NULL);
618 }
619
620 void ScriptInstance::Load(int version)
621 {
622 ScriptObject::ActiveInstance active(this);
623
624 if (this->engine == NULL || version == -1) {
625 LoadEmpty();
626 return;
627 }
628 HSQUIRRELVM vm = this->engine->GetVM();
629
630 SlObject(NULL, _script_byte);
631 /* Check if there was anything saved at all. */
632 if (_script_sl_byte == 0) return;
633
634 sq_pushinteger(vm, version);
635 LoadObjects(vm);
636 this->is_save_data_on_stack = true;
637 }
638
639 bool ScriptInstance::CallLoad()
640 {
641 HSQUIRRELVM vm = this->engine->GetVM();
642 /* Is there save data that we should load? */
643 if (!this->is_save_data_on_stack) return true;
644 /* Whatever happens, after CallLoad the savegame data is removed from the stack. */
645 this->is_save_data_on_stack = false;
646
647 if (!this->engine->MethodExists(*this->instance, "Load")) {
648 ScriptLog::Warning("Loading failed: there was data for the script to load, but the script does not have a Load() function.");
649
650 /* Pop the savegame data and version. */
651 sq_pop(vm, 2);
652 return true;
653 }
654
655 /* Go to the instance-root */
656 sq_pushobject(vm, *this->instance);
657 /* Find the function-name inside the script */
658 sq_pushstring(vm, "Load", -1);
659 /* Change the "Load" string in a function pointer */
660 sq_get(vm, -2);
661 /* Push the main instance as "this" object */
662 sq_pushobject(vm, *this->instance);
663 /* Push the version data and savegame data as arguments */
664 sq_push(vm, -5);
665 sq_push(vm, -5);
666
667 /* Call the script load function. sq_call removes the arguments (but not the
668 * function pointer) from the stack. */
669 if (SQ_FAILED(sq_call(vm, 3, SQFalse, SQFalse, MAX_SL_OPS))) return false;
670
671 /* Pop 1) The version, 2) the savegame data, 3) the object instance, 4) the function pointer. */
672 sq_pop(vm, 4);
673 return true;
674 }
675
676 SQInteger ScriptInstance::GetOpsTillSuspend()
677 {
678 return this->engine->GetOpsTillSuspend();
679 }
680
681 void ScriptInstance::DoCommandCallback(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2)
682 {
683 ScriptObject::ActiveInstance active(this);
684
685 ScriptObject::SetLastCommandRes(result.Succeeded());
686
687 if (result.Failed()) {
688 ScriptObject::SetLastError(ScriptError::StringToError(result.GetErrorMessage()));
689 } else {
690 ScriptObject::IncreaseDoCommandCosts(result.GetCost());
691 ScriptObject::SetLastCost(result.GetCost());
692 }
693 }
694
695 void ScriptInstance::InsertEvent(class ScriptEvent *event)
696 {
697 ScriptObject::ActiveInstance active(this);
698
699 ScriptEventController::InsertEvent(event);
700 }