(svn r28004) -Update from Eints:
[openttd.git] / src / script / script_instance.cpp
blob5df06ad8902d65a9a8c038fdd01932b434b87b0c
1 /* $Id$ */
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 */
10 /** @file script_instance.cpp Implementation of ScriptInstance. */
12 #include "../stdafx.h"
13 #include "../debug.h"
14 #include "../saveload/saveload.h"
16 #include "../script/squirrel_class.hpp"
18 #include "script_fatalerror.hpp"
19 #include "script_storage.hpp"
20 #include "script_info.hpp"
21 #include "script_instance.hpp"
23 #include "api/script_controller.hpp"
24 #include "api/script_error.hpp"
25 #include "api/script_event.hpp"
26 #include "api/script_log.hpp"
28 #include "../company_base.h"
29 #include "../company_func.h"
30 #include "../fileio_func.h"
32 #include "../safeguards.h"
34 ScriptStorage::~ScriptStorage()
36 /* Free our pointers */
37 if (event_data != NULL) ScriptEventController::FreeEventPointer();
38 if (log_data != NULL) ScriptLog::FreeLogPointer();
41 /**
42 * Callback called by squirrel when a script uses "print" and for error messages.
43 * @param error_msg Is this an error message?
44 * @param message The actual message text.
46 static void PrintFunc(bool error_msg, const SQChar *message)
48 /* Convert to OpenTTD internal capable string */
49 ScriptController::Print(error_msg, message);
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)
65 this->storage = new ScriptStorage();
66 this->engine = new Squirrel(APIName);
67 this->engine->SetPrintFunction(&PrintFunc);
70 void ScriptInstance::Initialize(const char *main_script, const char *instance_name, CompanyID company)
72 ScriptObject::ActiveInstance active(this);
74 this->controller = new ScriptController(company);
76 /* Register the API functions and classes */
77 this->engine->SetGlobalPointer(this->engine);
78 this->RegisterAPI();
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;
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;
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();
106 void ScriptInstance::RegisterAPI()
108 extern void squirrel_register_std(Squirrel *engine);
109 squirrel_register_std(this->engine);
112 bool ScriptInstance::LoadCompatibilityScripts(const char *api_version, Subdirectory dir)
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;
123 if (this->engine->LoadScript(buf)) return true;
125 ScriptLog::Error("Failed to load API compatibility script");
126 DEBUG(script, 0, "Error compiling / running API compatibility script: %s", buf);
127 return false;
130 ScriptLog::Warning("API compatibility script not found");
131 return true;
134 ScriptInstance::~ScriptInstance()
136 ScriptObject::ActiveInstance active(this);
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);
145 void ScriptInstance::Continue()
147 assert(this->suspend < 0);
148 this->suspend = -this->suspend - 1;
151 void ScriptInstance::Died()
153 DEBUG(script, 0, "The script died unexpectedly.");
154 this->is_dead = true;
156 if (this->instance != NULL) this->engine->ReleaseObject(this->instance);
157 delete this->engine;
158 this->instance = NULL;
159 this->engine = NULL;
162 void ScriptInstance::GameLoop()
164 ScriptObject::ActiveInstance active(this);
166 if (this->IsDead()) return;
167 if (this->engine->HasScriptCrashed()) {
168 /* The script crashed during saving, kill it here. */
169 this->Died();
170 return;
172 if (this->is_paused) return;
173 this->controller->ticks++;
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.
179 _current_company = ScriptObject::GetCompany();
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;
187 try {
188 this->callback(this);
189 } catch (Script_Suspend e) {
190 this->suspend = e.GetSuspendTime();
191 this->callback = e.GetSuspendCallback();
193 return;
197 this->suspend = 0;
198 this->callback = NULL;
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;
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;
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();
229 this->is_started = true;
230 return;
232 if (this->is_save_data_on_stack) {
233 sq_poptop(this->engine->GetVM());
234 this->is_save_data_on_stack = false;
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();
251 void ScriptInstance::CollectGarbage() const
253 if (this->is_started && !this->IsDead()) this->engine->CollectGarbage();
256 /* static */ void ScriptInstance::DoCommandReturn(ScriptInstance *instance)
258 instance->engine->InsertResult(ScriptObject::GetLastCommandRes());
261 /* static */ void ScriptInstance::DoCommandReturnVehicleID(ScriptInstance *instance)
263 instance->engine->InsertResult(ScriptObject::GetNewVehicleID());
266 /* static */ void ScriptInstance::DoCommandReturnSignID(ScriptInstance *instance)
268 instance->engine->InsertResult(ScriptObject::GetNewSignID());
271 /* static */ void ScriptInstance::DoCommandReturnGroupID(ScriptInstance *instance)
273 instance->engine->InsertResult(ScriptObject::GetNewGroupID());
276 /* static */ void ScriptInstance::DoCommandReturnGoalID(ScriptInstance *instance)
278 instance->engine->InsertResult(ScriptObject::GetNewGoalID());
281 /* static */ void ScriptInstance::DoCommandReturnStoryPageID(ScriptInstance *instance)
283 instance->engine->InsertResult(ScriptObject::GetNewStoryPageID());
286 /* static */ void ScriptInstance::DoCommandReturnStoryPageElementID(ScriptInstance *instance)
288 instance->engine->InsertResult(ScriptObject::GetNewStoryPageElementID());
291 ScriptStorage *ScriptInstance::GetStorage()
293 return this->storage;
296 void *ScriptInstance::GetLogPointer()
298 ScriptObject::ActiveInstance active(this);
300 return ScriptObject::GetLogPointer();
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.
324 /** The type of the data that follows in the savegame. */
325 enum SQSaveLoadType {
326 SQSL_INT = 0x00, ///< The following data is an integer.
327 SQSL_STRING = 0x01, ///< The following data is an string.
328 SQSL_ARRAY = 0x02, ///< The following data is an array.
329 SQSL_TABLE = 0x03, ///< The following data is an table.
330 SQSL_BOOL = 0x04, ///< The following data is a boolean.
331 SQSL_NULL = 0x05, ///< A null variable.
332 SQSL_ARRAY_TABLE_END = 0xFF, ///< Marks the end of an array or table, no data follows.
335 static byte _script_sl_byte; ///< Used as source/target by the script saveload code to store/load a single byte.
337 /** SaveLoad array that saves/loads exactly one byte. */
338 static const SaveLoad _script_byte[] = {
339 SLEG_VAR(_script_sl_byte, SLE_UINT8),
340 SLE_END()
343 /* static */ bool ScriptInstance::SaveObject(HSQUIRRELVM vm, SQInteger index, int max_depth, bool test)
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;
350 switch (sq_gettype(vm, index)) {
351 case OT_INTEGER: {
352 if (!test) {
353 _script_sl_byte = SQSL_INT;
354 SlObject(NULL, _script_byte);
356 SQInteger res;
357 sq_getinteger(vm, index, &res);
358 if (!test) {
359 int value = (int)res;
360 SlArray(&value, 1, SLE_INT32);
362 return true;
365 case OT_STRING: {
366 if (!test) {
367 _script_sl_byte = SQSL_STRING;
368 SlObject(NULL, _script_byte);
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;
377 if (!test) {
378 _script_sl_byte = (byte)len;
379 SlObject(NULL, _script_byte);
380 SlArray(const_cast<char *>(buf), len, SLE_CHAR);
382 return true;
385 case OT_ARRAY: {
386 if (!test) {
387 _script_sl_byte = SQSL_ARRAY;
388 SlObject(NULL, _script_byte);
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;
400 sq_pop(vm, 1);
401 if (!test) {
402 _script_sl_byte = SQSL_ARRAY_TABLE_END;
403 SlObject(NULL, _script_byte);
405 return true;
408 case OT_TABLE: {
409 if (!test) {
410 _script_sl_byte = SQSL_TABLE;
411 SlObject(NULL, _script_byte);
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;
423 sq_pop(vm, 1);
424 if (!test) {
425 _script_sl_byte = SQSL_ARRAY_TABLE_END;
426 SlObject(NULL, _script_byte);
428 return true;
431 case OT_BOOL: {
432 if (!test) {
433 _script_sl_byte = SQSL_BOOL;
434 SlObject(NULL, _script_byte);
436 SQBool res;
437 sq_getbool(vm, index, &res);
438 if (!test) {
439 _script_sl_byte = res ? 1 : 0;
440 SlObject(NULL, _script_byte);
442 return true;
445 case OT_NULL: {
446 if (!test) {
447 _script_sl_byte = SQSL_NULL;
448 SlObject(NULL, _script_byte);
450 return true;
453 default:
454 ScriptLog::Error("You tried to save an unsupported type. No data saved.");
455 return false;
459 /* static */ void ScriptInstance::SaveEmpty()
461 _script_sl_byte = 0;
462 SlObject(NULL, _script_byte);
465 void ScriptInstance::Save()
467 ScriptObject::ActiveInstance active(this);
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;
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;
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;
510 ScriptObject::SetAllowDoCommand(backup_allow);
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;
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();
528 } else {
529 ScriptLog::Warning("Save function is not implemented");
530 _script_sl_byte = 0;
531 SlObject(NULL, _script_byte);
535 void ScriptInstance::Pause()
537 /* Suspend script. */
538 HSQUIRRELVM vm = this->engine->GetVM();
539 Squirrel::DecreaseOps(vm, _settings_game.script.script_max_opcode_till_suspend);
541 this->is_paused = true;
544 void ScriptInstance::Unpause()
546 this->is_paused = false;
549 bool ScriptInstance::IsPaused()
551 return this->is_paused;
554 /* static */ bool ScriptInstance::LoadObjects(HSQUIRRELVM vm)
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;
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;
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. */
579 return true;
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. */
589 return true;
592 case SQSL_BOOL: {
593 SlObject(NULL, _script_byte);
594 if (vm != NULL) sq_pushbool(vm, (SQBool)(_script_sl_byte != 0));
595 return true;
598 case SQSL_NULL: {
599 if (vm != NULL) sq_pushnull(vm);
600 return true;
603 case SQSL_ARRAY_TABLE_END: {
604 return false;
607 default: NOT_REACHED();
611 /* static */ void ScriptInstance::LoadEmpty()
613 SlObject(NULL, _script_byte);
614 /* Check if there was anything saved at all. */
615 if (_script_sl_byte == 0) return;
617 LoadObjects(NULL);
620 void ScriptInstance::Load(int version)
622 ScriptObject::ActiveInstance active(this);
624 if (this->engine == NULL || version == -1) {
625 LoadEmpty();
626 return;
628 HSQUIRRELVM vm = this->engine->GetVM();
630 SlObject(NULL, _script_byte);
631 /* Check if there was anything saved at all. */
632 if (_script_sl_byte == 0) return;
634 sq_pushinteger(vm, version);
635 LoadObjects(vm);
636 this->is_save_data_on_stack = true;
639 bool ScriptInstance::CallLoad()
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;
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.");
650 /* Pop the savegame data and version. */
651 sq_pop(vm, 2);
652 return true;
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);
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;
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;
676 SQInteger ScriptInstance::GetOpsTillSuspend()
678 return this->engine->GetOpsTillSuspend();
681 void ScriptInstance::DoCommandCallback(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2)
683 ScriptObject::ActiveInstance active(this);
685 ScriptObject::SetLastCommandRes(result.Succeeded());
687 if (result.Failed()) {
688 ScriptObject::SetLastError(ScriptError::StringToError(result.GetErrorMessage()));
689 } else {
690 ScriptObject::IncreaseDoCommandCosts(result.GetCost());
691 ScriptObject::SetLastCost(result.GetCost());
695 void ScriptInstance::InsertEvent(class ScriptEvent *event)
697 ScriptObject::ActiveInstance active(this);
699 ScriptEventController::InsertEvent(event);