OpenTTD Source 20250312-master-gcdcc6b491d
script_instance.cpp
Go to the documentation of this file.
1/*
2 * This file is part of OpenTTD.
3 * 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.
4 * 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.
5 * 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/>.
6 */
7
10#include "../stdafx.h"
11#include "../debug.h"
12#include "../saveload/saveload.h"
13
14#include "../script/squirrel_class.hpp"
15#include "../script/squirrel_std.hpp"
16
17#include "script_fatalerror.hpp"
18#include "script_storage.hpp"
19#include "script_info.hpp"
20#include "script_instance.hpp"
21
22#include "api/script_controller.hpp"
23#include "api/script_error.hpp"
24#include "api/script_event.hpp"
25#include "api/script_log.hpp"
26
27#include "../company_base.h"
28#include "../company_func.h"
29#include "../fileio_func.h"
30#include "../league_type.h"
31#include "../misc/endian_buffer.hpp"
32
33#include "../safeguards.h"
34
35ScriptStorage::~ScriptStorage()
36{
37 /* Free our pointers */
38 if (event_data != nullptr) ScriptEventController::FreeEventPointer();
39}
40
46static void PrintFunc(bool error_msg, const std::string &message)
47{
48 /* Convert to OpenTTD internal capable string */
49 ScriptController::Print(error_msg, message);
50}
51
53{
54 this->storage = new ScriptStorage();
55 this->engine = new Squirrel(APIName);
57}
58
59void ScriptInstance::Initialize(const std::string &main_script, const std::string &instance_name, CompanyID company)
60{
61 ScriptObject::ActiveInstance active(this);
62
63 this->controller = new ScriptController(company);
64
65 /* Register the API functions and classes */
66 this->engine->SetGlobalPointer(this->engine);
67 this->RegisterAPI();
68 if (this->IsDead()) {
69 /* Failed to register API; a message has already been logged. */
70 return;
71 }
72
73 try {
74 ScriptObject::SetAllowDoCommand(false);
75 /* Load and execute the script for this script */
76 if (main_script == "%_dummy") {
77 this->LoadDummyScript();
78 } else if (!this->engine->LoadScript(main_script) || this->engine->IsSuspended()) {
79 if (this->engine->IsSuspended()) ScriptLog::Error("This script took too long to load script. AI is not started.");
80 this->Died();
81 return;
82 }
83
84 /* Create the main-class */
85 this->instance = new SQObject();
86 if (!this->engine->CreateClassInstance(instance_name, this->controller, this->instance)) {
87 /* If CreateClassInstance has returned false instance has not been
88 * registered with squirrel, so avoid trying to Release it by clearing it now */
89 delete this->instance;
90 this->instance = nullptr;
91 this->Died();
92 return;
93 }
94 ScriptObject::SetAllowDoCommand(true);
95 } catch (Script_FatalError &e) {
96 this->is_dead = true;
98 this->engine->ResumeError();
99 this->Died();
100 }
101}
102
107
108bool ScriptInstance::LoadCompatibilityScript(std::string_view api_version, Subdirectory dir)
109{
110 std::string script_name = fmt::format("compat_{}.nut", api_version);
111
112 for (Searchpath sp : _valid_searchpaths) {
113 std::string buf = FioGetDirectory(sp, dir);
114 buf += script_name;
115 if (!FileExists(buf)) continue;
116
117 if (this->engine->LoadScript(buf)) return true;
118
119 ScriptLog::Error(fmt::format("Failed to load API compatibility script for {}", api_version));
120 Debug(script, 0, "Error compiling / running API compatibility script: {}", buf);
121 return false;
122 }
123
124 ScriptLog::Warning(fmt::format("API compatibility script for {} not found", api_version));
125 return true;
126}
127
128bool ScriptInstance::LoadCompatibilityScripts(Subdirectory dir, std::span<const std::string_view> api_versions)
129{
130 /* Don't try to load compatibility scripts for the current version. */
131 if (this->versionAPI == std::rbegin(api_versions)->data()) return true;
132
133 ScriptLog::Info(fmt::format("Downgrading API to be compatible with version {}", this->versionAPI));
134
135 /* Downgrade the API till we are the same version as the script. The last
136 * entry in the list is always the current version, so skip that one. */
137 for (auto it = std::rbegin(api_versions) + 1; it != std::rend(api_versions); ++it) {
138 if (!this->LoadCompatibilityScript(*it, dir)) return false;
139
140 if (*it == this->versionAPI) break;
141 }
142
143 return true;
144}
145
146ScriptInstance::~ScriptInstance()
147{
148 ScriptObject::ActiveInstance active(this);
149 this->in_shutdown = true;
150
151 if (instance != nullptr) this->engine->ReleaseObject(this->instance);
152 if (engine != nullptr) delete this->engine;
153 delete this->storage;
154 delete this->controller;
155 delete this->instance;
156}
157
159{
160 assert(this->suspend < 0);
161 this->suspend = -this->suspend - 1;
162}
163
165{
166 Debug(script, 0, "The script died unexpectedly.");
167 this->is_dead = true;
168 this->in_shutdown = true;
169
170 this->last_allocated_memory = this->GetAllocatedMemory(); // Update cache
171
172 if (this->instance != nullptr) this->engine->ReleaseObject(this->instance);
173 delete this->instance;
174 delete this->engine;
175 this->instance = nullptr;
176 this->engine = nullptr;
177}
178
180{
181 ScriptObject::ActiveInstance active(this);
182
183 if (this->IsDead()) return;
184 if (this->engine->HasScriptCrashed()) {
185 /* The script crashed during saving, kill it here. */
186 this->Died();
187 return;
188 }
189 if (this->is_paused) return;
190 this->controller->ticks++;
191
192 if (this->suspend < -1) this->suspend++; // Multiplayer suspend, increase up to -1.
193 if (this->suspend < 0) return; // Multiplayer suspend, wait for Continue().
194 if (--this->suspend > 0) return; // Singleplayer suspend, decrease to 0.
195
196 _current_company = ScriptObject::GetCompany();
197
198 /* If there is a callback to call, call that first */
199 if (this->callback != nullptr) {
200 if (this->is_save_data_on_stack) {
201 sq_poptop(this->engine->GetVM());
202 this->is_save_data_on_stack = false;
203 }
204 try {
205 this->callback(this);
206 } catch (Script_Suspend &e) {
207 this->suspend = e.GetSuspendTime();
208 this->callback = e.GetSuspendCallback();
209
210 return;
211 }
212 }
213
214 this->suspend = 0;
215 this->callback = nullptr;
216
217 if (!this->is_started) {
218 try {
219 ScriptObject::SetAllowDoCommand(false);
220 /* Run the constructor if it exists. Don't allow any DoCommands in it. */
221 if (this->engine->MethodExists(*this->instance, "constructor")) {
222 if (!this->engine->CallMethod(*this->instance, "constructor", MAX_CONSTRUCTOR_OPS) || this->engine->IsSuspended()) {
223 if (this->engine->IsSuspended()) ScriptLog::Error("This script took too long to initialize. Script is not started.");
224 this->Died();
225 return;
226 }
227 }
228 if (!this->CallLoad() || this->engine->IsSuspended()) {
229 if (this->engine->IsSuspended()) ScriptLog::Error("This script took too long in the Load function. Script is not started.");
230 this->Died();
231 return;
232 }
233 ScriptObject::SetAllowDoCommand(true);
234 /* Start the script by calling Start() */
235 if (!this->engine->CallMethod(*this->instance, "Start", _settings_game.script.script_max_opcode_till_suspend) || !this->engine->IsSuspended()) this->Died();
236 } catch (Script_Suspend &e) {
237 this->suspend = e.GetSuspendTime();
238 this->callback = e.GetSuspendCallback();
239 } catch (Script_FatalError &e) {
240 this->is_dead = true;
241 this->engine->ThrowError(e.GetErrorMessage());
242 this->engine->ResumeError();
243 this->Died();
244 }
245
246 this->is_started = true;
247 return;
248 }
249 if (this->is_save_data_on_stack) {
250 sq_poptop(this->engine->GetVM());
251 this->is_save_data_on_stack = false;
252 }
253
254 /* Continue the VM */
255 try {
257 } catch (Script_Suspend &e) {
258 this->suspend = e.GetSuspendTime();
259 this->callback = e.GetSuspendCallback();
260 } catch (Script_FatalError &e) {
261 this->is_dead = true;
262 this->engine->ThrowError(e.GetErrorMessage());
263 this->engine->ResumeError();
264 this->Died();
265 }
266}
267
269{
270 if (this->is_started && !this->IsDead()) {
271 ScriptObject::ActiveInstance active(this);
272 this->engine->CollectGarbage();
273 }
274}
275
277{
278 instance->engine->InsertResult(ScriptObject::GetLastCommandRes());
279}
280
282{
283 instance->engine->InsertResult(EndianBufferReader::ToValue<VehicleID>(ScriptObject::GetLastCommandResData()));
284}
285
287{
288 instance->engine->InsertResult(EndianBufferReader::ToValue<SignID>(ScriptObject::GetLastCommandResData()));
289}
290
292{
293 instance->engine->InsertResult(EndianBufferReader::ToValue<GroupID>(ScriptObject::GetLastCommandResData()));
294}
295
297{
298 instance->engine->InsertResult(EndianBufferReader::ToValue<GoalID>(ScriptObject::GetLastCommandResData()));
299}
300
302{
303 instance->engine->InsertResult(EndianBufferReader::ToValue<StoryPageID>(ScriptObject::GetLastCommandResData()));
304}
305
307{
308 instance->engine->InsertResult(EndianBufferReader::ToValue<StoryPageElementID>(ScriptObject::GetLastCommandResData()));
309}
310
312{
313 instance->engine->InsertResult(EndianBufferReader::ToValue<LeagueTableElementID>(ScriptObject::GetLastCommandResData()));
314}
315
317{
318 instance->engine->InsertResult(EndianBufferReader::ToValue<LeagueTableID>(ScriptObject::GetLastCommandResData()));
319}
320
321
323{
324 return this->storage;
325}
326
327ScriptLogTypes::LogData &ScriptInstance::GetLogData()
328{
329 ScriptObject::ActiveInstance active(this);
330
331 return ScriptObject::GetLogData();
332}
333
334/*
335 * All data is stored in the following format:
336 * First 1 byte indicating if there is a data blob at all.
337 * 1 byte indicating the type of data.
338 * The data itself, this differs per type:
339 * - integer: a binary representation of the integer (int32_t).
340 * - string: First one byte with the string length, then a 0-terminated char
341 * array. The string can't be longer than 255 bytes (including
342 * terminating '\0').
343 * - array: All data-elements of the array are saved recursive in this
344 * format, and ended with an element of the type
345 * SQSL_ARRAY_TABLE_END.
346 * - table: All key/value pairs are saved in this format (first key 1, then
347 * value 1, then key 2, etc.). All keys and values can have an
348 * arbitrary type (as long as it is supported by the save function
349 * of course). The table is ended with an element of the type
350 * SQSL_ARRAY_TABLE_END.
351 * - bool: A single byte with value 1 representing true and 0 false.
352 * - null: No data.
353 */
354
355static uint8_t _script_sl_byte;
356
358static const SaveLoad _script_byte[] = {
359 SLEG_VAR("type", _script_sl_byte, SLE_UINT8),
360};
361
362/* static */ bool ScriptInstance::SaveObject(HSQUIRRELVM vm, SQInteger index, int max_depth, bool test)
363{
364 if (max_depth == 0) {
365 ScriptLog::Error("Savedata can only be nested to 25 deep. No data saved."); // SQUIRREL_MAX_DEPTH = 25
366 return false;
367 }
368
369 switch (sq_gettype(vm, index)) {
370 case OT_INTEGER: {
371 if (!test) {
373 SlObject(nullptr, _script_byte);
374 }
375 SQInteger res;
376 sq_getinteger(vm, index, &res);
377 if (!test) {
378 int64_t value = (int64_t)res;
379 SlCopy(&value, 1, SLE_INT64);
380 }
381 return true;
382 }
383
384 case OT_STRING: {
385 if (!test) {
387 SlObject(nullptr, _script_byte);
388 }
389 const SQChar *buf;
390 sq_getstring(vm, index, &buf);
391 size_t len = strlen(buf) + 1;
392 if (len >= 255) {
393 ScriptLog::Error("Maximum string length is 254 chars. No data saved.");
394 return false;
395 }
396 if (!test) {
397 _script_sl_byte = (uint8_t)len;
398 SlObject(nullptr, _script_byte);
399 SlCopy(const_cast<char *>(buf), len, SLE_CHAR);
400 }
401 return true;
402 }
403
404 case OT_ARRAY: {
405 if (!test) {
407 SlObject(nullptr, _script_byte);
408 }
409 sq_pushnull(vm);
410 while (SQ_SUCCEEDED(sq_next(vm, index - 1))) {
411 /* Store the value */
412 bool res = SaveObject(vm, -1, max_depth - 1, test);
413 sq_pop(vm, 2);
414 if (!res) {
415 sq_pop(vm, 1);
416 return false;
417 }
418 }
419 sq_pop(vm, 1);
420 if (!test) {
422 SlObject(nullptr, _script_byte);
423 }
424 return true;
425 }
426
427 case OT_TABLE: {
428 if (!test) {
430 SlObject(nullptr, _script_byte);
431 }
432 sq_pushnull(vm);
433 while (SQ_SUCCEEDED(sq_next(vm, index - 1))) {
434 /* Store the key + value */
435 bool res = SaveObject(vm, -2, max_depth - 1, test) && SaveObject(vm, -1, max_depth - 1, test);
436 sq_pop(vm, 2);
437 if (!res) {
438 sq_pop(vm, 1);
439 return false;
440 }
441 }
442 sq_pop(vm, 1);
443 if (!test) {
445 SlObject(nullptr, _script_byte);
446 }
447 return true;
448 }
449
450 case OT_BOOL: {
451 if (!test) {
453 SlObject(nullptr, _script_byte);
454 }
455 SQBool res;
456 sq_getbool(vm, index, &res);
457 if (!test) {
458 _script_sl_byte = res ? 1 : 0;
459 SlObject(nullptr, _script_byte);
460 }
461 return true;
462 }
463
464 case OT_NULL: {
465 if (!test) {
467 SlObject(nullptr, _script_byte);
468 }
469 return true;
470 }
471
472 case OT_INSTANCE:{
473 if (!test) {
475 SlObject(nullptr, _script_byte);
476 }
477 SQInteger top = sq_gettop(vm);
478 try {
479 ScriptObject *obj = static_cast<ScriptObject *>(Squirrel::GetRealInstance(vm, -1, "Object"));
480 if (!obj->SaveObject(vm)) throw std::exception();
481 if (sq_gettop(vm) != top + 2) throw std::exception();
482 if (sq_gettype(vm, -2) != OT_STRING || !SaveObject(vm, -2, max_depth - 1, test)) throw std::exception();
483 if (!SaveObject(vm, -1, max_depth - 1, test)) throw std::exception();
484 sq_settop(vm, top);
485 return true;
486 } catch (...) {
487 ScriptLog::Error("You tried to save an unsupported type. No data saved.");
488 sq_settop(vm, top);
489 return false;
490 }
491 }
492
493 default:
494 ScriptLog::Error("You tried to save an unsupported type. No data saved.");
495 return false;
496 }
497}
498
499/* static */ void ScriptInstance::SaveEmpty()
500{
501 _script_sl_byte = 0;
502 SlObject(nullptr, _script_byte);
503}
504
506{
507 ScriptObject::ActiveInstance active(this);
508
509 /* Don't save data if the script didn't start yet or if it crashed. */
510 if (this->engine == nullptr || this->engine->HasScriptCrashed()) {
511 SaveEmpty();
512 return;
513 }
514
515 HSQUIRRELVM vm = this->engine->GetVM();
516 if (this->is_save_data_on_stack) {
517 _script_sl_byte = 1;
518 SlObject(nullptr, _script_byte);
519 /* Save the data that was just loaded. */
520 SaveObject(vm, -1, SQUIRREL_MAX_DEPTH, false);
521 } else if (!this->is_started) {
522 SaveEmpty();
523 return;
524 } else if (this->engine->MethodExists(*this->instance, "Save")) {
525 HSQOBJECT savedata;
526 /* We don't want to be interrupted during the save function. */
527 bool backup_allow = ScriptObject::GetAllowDoCommand();
528 ScriptObject::SetAllowDoCommand(false);
529 try {
530 if (!this->engine->CallMethod(*this->instance, "Save", &savedata, MAX_SL_OPS)) {
531 /* The script crashed in the Save function. We can't kill
532 * it here, but do so in the next script tick. */
533 SaveEmpty();
534 this->engine->CrashOccurred();
535 return;
536 }
537 } catch (Script_FatalError &e) {
538 /* If we don't mark the script as dead here cleaning up the squirrel
539 * stack could throw Script_FatalError again. */
540 this->is_dead = true;
542 this->engine->ResumeError();
543 SaveEmpty();
544 /* We can't kill the script here, so mark it as crashed (not dead) and
545 * kill it in the next script tick. */
546 this->is_dead = false;
547 this->engine->CrashOccurred();
548 return;
549 }
550 ScriptObject::SetAllowDoCommand(backup_allow);
551
552 if (!sq_istable(savedata)) {
553 ScriptLog::Error(this->engine->IsSuspended() ? "This script took too long to Save." : "Save function should return a table.");
554 SaveEmpty();
555 this->engine->CrashOccurred();
556 return;
557 }
558 sq_pushobject(vm, savedata);
559 if (SaveObject(vm, -1, SQUIRREL_MAX_DEPTH, true)) {
560 _script_sl_byte = 1;
561 SlObject(nullptr, _script_byte);
562 SaveObject(vm, -1, SQUIRREL_MAX_DEPTH, false);
563 this->is_save_data_on_stack = true;
564 } else {
565 SaveEmpty();
566 this->engine->CrashOccurred();
567 }
568 } else {
569 ScriptLog::Warning("Save function is not implemented");
570 _script_sl_byte = 0;
571 SlObject(nullptr, _script_byte);
572 }
573}
574
576{
577 /* Suspend script. */
578 HSQUIRRELVM vm = this->engine->GetVM();
580
581 this->is_paused = true;
582}
583
585{
586 this->is_paused = false;
587}
588
590{
591 return this->is_paused;
592}
593
594/* static */ bool ScriptInstance::LoadObjects(ScriptData *data)
595{
596 SlObject(nullptr, _script_byte);
597 switch (_script_sl_byte) {
598 case SQSL_INT: {
599 int64_t value;
600 SlCopy(&value, 1, IsSavegameVersionBefore(SLV_SCRIPT_INT64) ? SLE_FILE_I32 | SLE_VAR_I64 : SLE_INT64);
601 if (data != nullptr) data->push_back(static_cast<SQInteger>(value));
602 return true;
603 }
604
605 case SQSL_STRING: {
606 SlObject(nullptr, _script_byte);
607 static char buf[std::numeric_limits<decltype(_script_sl_byte)>::max()];
608 SlCopy(buf, _script_sl_byte, SLE_CHAR);
609 if (data != nullptr) data->push_back(StrMakeValid(std::string_view(buf, _script_sl_byte)));
610 return true;
611 }
612
613 case SQSL_ARRAY:
614 case SQSL_TABLE: {
615 if (data != nullptr) data->push_back(static_cast<SQSaveLoadType>(_script_sl_byte));
616 while (LoadObjects(data));
617 return true;
618 }
619
620 case SQSL_BOOL: {
621 SlObject(nullptr, _script_byte);
622 if (data != nullptr) data->push_back(static_cast<SQBool>(_script_sl_byte != 0));
623 return true;
624 }
625
626 case SQSL_NULL: {
627 if (data != nullptr) data->push_back(static_cast<SQSaveLoadType>(_script_sl_byte));
628 return true;
629 }
630
631 case SQSL_INSTANCE: {
632 if (data != nullptr) data->push_back(static_cast<SQSaveLoadType>(_script_sl_byte));
633 return true;
634 }
635
637 if (data != nullptr) data->push_back(static_cast<SQSaveLoadType>(_script_sl_byte));
638 return false;
639 }
640
641 default: SlErrorCorrupt("Invalid script data type");
642 }
643}
644
645/* static */ bool ScriptInstance::LoadObjects(HSQUIRRELVM vm, ScriptData *data)
646{
647 ScriptDataVariant value = data->front();
648 data->pop_front();
649
650 struct visitor {
651 HSQUIRRELVM vm;
652 ScriptData *data;
653
654 bool operator()(const SQInteger &value) { sq_pushinteger(this->vm, value); return true; }
655 bool operator()(const std::string &value) { sq_pushstring(this->vm, value, -1); return true; }
656 bool operator()(const SQBool &value) { sq_pushbool(this->vm, value); return true; }
657 bool operator()(const SQSaveLoadType &type)
658 {
659 switch (type) {
660 case SQSL_ARRAY:
661 sq_newarray(this->vm, 0);
662 while (LoadObjects(this->vm, this->data)) {
663 sq_arrayappend(this->vm, -2);
664 /* The value is popped from the stack by squirrel. */
665 }
666 return true;
667
668 case SQSL_TABLE:
669 sq_newtable(this->vm);
670 while (LoadObjects(this->vm, this->data)) {
671 LoadObjects(this->vm, this->data);
672 sq_rawset(this->vm, -3);
673 /* The key (-2) and value (-1) are popped from the stack by squirrel. */
674 }
675 return true;
676
677 case SQSL_NULL:
678 sq_pushnull(this->vm);
679 return true;
680
681 case SQSL_INSTANCE: {
682 SQInteger top = sq_gettop(this->vm);
683 LoadObjects(this->vm, this->data);
684 const SQChar *buf;
685 sq_getstring(this->vm, -1, &buf);
686 Squirrel *engine = static_cast<Squirrel *>(sq_getforeignptr(this->vm));
687 std::string class_name = fmt::format("{}{}", engine->GetAPIName(), buf);
688 sq_pushroottable(this->vm);
689 sq_pushstring(this->vm, class_name);
690 if (SQ_FAILED(sq_get(this->vm, -2))) throw Script_FatalError(fmt::format("'{}' doesn't exist", class_name));
691 sq_pushroottable(vm);
692 if (SQ_FAILED(sq_call(this->vm, 1, SQTrue, SQFalse))) throw Script_FatalError(fmt::format("Failed to instantiate '{}'", class_name));
693 HSQOBJECT res;
694 sq_getstackobj(vm, -1, &res);
695 sq_addref(vm, &res);
696 sq_settop(this->vm, top);
697 sq_pushobject(vm, res);
698 sq_release(vm, &res);
699 ScriptObject *obj = static_cast<ScriptObject *>(Squirrel::GetRealInstance(vm, -1, "Object"));
700 LoadObjects(this->vm, this->data);
701 if (!obj->LoadObject(vm)) throw Script_FatalError(fmt::format("Failed to load '{}'", class_name));
702 sq_pop(this->vm, 1);
703 return true;
704 }
705
706 case SQSL_ARRAY_TABLE_END:
707 return false;
708
709 default: NOT_REACHED();
710 }
711 }
712 };
713
714 return std::visit(visitor{vm, data}, value);
715}
716
717/* static */ void ScriptInstance::LoadEmpty()
718{
719 SlObject(nullptr, _script_byte);
720 /* Check if there was anything saved at all. */
721 if (_script_sl_byte == 0) return;
722
723 LoadObjects(nullptr);
724}
725
726/* static */ ScriptInstance::ScriptData *ScriptInstance::Load(int version)
727{
728 if (version == -1) {
729 LoadEmpty();
730 return nullptr;
731 }
732
733 SlObject(nullptr, _script_byte);
734 /* Check if there was anything saved at all. */
735 if (_script_sl_byte == 0) return nullptr;
736
737 ScriptData *data = new ScriptData();
738 data->push_back((SQInteger)version);
739 LoadObjects(data);
740 return data;
741}
742
743void ScriptInstance::LoadOnStack(ScriptData *data)
744{
745 ScriptObject::ActiveInstance active(this);
746
747 if (this->IsDead() || data == nullptr) return;
748
749 HSQUIRRELVM vm = this->engine->GetVM();
750
751 ScriptDataVariant version = data->front();
752 data->pop_front();
753 SQInteger top = sq_gettop(vm);
754 try {
755 sq_pushinteger(vm, std::get<SQInteger>(version));
756 LoadObjects(vm, data);
757 this->is_save_data_on_stack = true;
758 } catch (Script_FatalError &e) {
759 ScriptLog::Warning(fmt::format("Loading failed: {}", e.GetErrorMessage()));
760 /* Discard partially loaded savegame data and version. */
761 sq_settop(vm, top);
762 }
763}
764
766{
767 HSQUIRRELVM vm = this->engine->GetVM();
768 /* Is there save data that we should load? */
769 if (!this->is_save_data_on_stack) return true;
770 /* Whatever happens, after CallLoad the savegame data is removed from the stack. */
771 this->is_save_data_on_stack = false;
772
773 if (!this->engine->MethodExists(*this->instance, "Load")) {
774 ScriptLog::Warning("Loading failed: there was data for the script to load, but the script does not have a Load() function.");
775
776 /* Pop the savegame data and version. */
777 sq_pop(vm, 2);
778 return true;
779 }
780
781 /* Go to the instance-root */
782 sq_pushobject(vm, *this->instance);
783 /* Find the function-name inside the script */
784 sq_pushstring(vm, "Load", -1);
785 /* Change the "Load" string in a function pointer */
786 sq_get(vm, -2);
787 /* Push the main instance as "this" object */
788 sq_pushobject(vm, *this->instance);
789 /* Push the version data and savegame data as arguments */
790 sq_push(vm, -5);
791 sq_push(vm, -5);
792
793 /* Call the script load function. sq_call removes the arguments (but not the
794 * function pointer) from the stack. */
795 if (SQ_FAILED(sq_call(vm, 3, SQFalse, SQTrue, MAX_SL_OPS))) return false;
796
797 /* Pop 1) The version, 2) the savegame data, 3) the object instance, 4) the function pointer. */
798 sq_pop(vm, 4);
799 return true;
800}
801
803{
804 return this->engine->GetOpsTillSuspend();
805}
806
808{
809 ScriptObject::ActiveInstance active(this);
810
811 if (!ScriptObject::CheckLastCommand(data, cmd)) {
812 Debug(script, 1, "DoCommandCallback terminating a script, last command does not match expected command");
813 return false;
814 }
815
816 ScriptObject::SetLastCommandRes(result.Succeeded());
817 ScriptObject::SetLastCommandResData(std::move(result_data));
818
819 if (result.Failed()) {
820 ScriptObject::SetLastError(ScriptError::StringToError(result.GetErrorMessage()));
821 } else {
822 ScriptObject::IncreaseDoCommandCosts(result.GetCost());
823 ScriptObject::SetLastCost(result.GetCost());
824 }
825
826 ScriptObject::SetLastCommand({}, CMD_END);
827
828 return true;
829}
830
831void ScriptInstance::InsertEvent(class ScriptEvent *event)
832{
833 ScriptObject::ActiveInstance active(this);
834
835 ScriptEventController::InsertEvent(event);
836}
837
838size_t ScriptInstance::GetAllocatedMemory() const
839{
840 if (this->engine == nullptr) return this->last_allocated_memory;
841 return this->engine->GetAllocatedMemory();
842}
843
845{
846 if (!this->in_shutdown) this->engine->ReleaseObject(obj);
847}
Common return value for all commands.
bool Succeeded() const
Did this command succeed?
Money GetCost() const
The costs as made up to this moment.
bool Failed() const
Did this command fail?
StringID GetErrorMessage() const
Returns the error message of a command.
Runtime information about a script like a pointer to the squirrel vm and the current state.
bool LoadCompatibilityScripts(Subdirectory dir, std::span< const std::string_view > api_versions)
Load squirrel scripts to emulate an older API.
static ScriptData * Load(int version)
Load data from a savegame.
virtual void RegisterAPI()
Register all API functions to the VM.
void InsertEvent(class ScriptEvent *event)
Insert an event for this script.
void Unpause()
Resume execution of the script.
class ScriptStorage * GetStorage()
Get the storage of this script.
bool IsPaused()
Checks if the script is paused.
virtual void Died()
Tell the script it died.
static bool LoadObjects(ScriptData *data)
Load all objects from a savegame.
void ReleaseSQObject(HSQOBJECT *obj)
Decrease the ref count of a squirrel object.
static void DoCommandReturnLeagueTableElementID(ScriptInstance *instance)
Return a LeagueTableElementID reply for a DoCommand.
void CollectGarbage()
Let the VM collect any garbage.
class Squirrel * engine
A wrapper around the squirrel vm.
SQSaveLoadType
The type of the data that follows in the savegame.
@ SQSL_BOOL
The following data is a boolean.
@ SQSL_STRING
The following data is an string.
@ SQSL_TABLE
The following data is an table.
@ SQSL_ARRAY_TABLE_END
Marks the end of an array or table, no data follows.
@ SQSL_ARRAY
The following data is an array.
@ SQSL_NULL
A null variable.
@ SQSL_INT
The following data is an integer.
@ SQSL_INSTANCE
The following data is an instance.
static void DoCommandReturn(ScriptInstance *instance)
Return a true/false reply for a DoCommand.
static void SaveEmpty()
Don't save any data in the savegame.
static void DoCommandReturnStoryPageElementID(ScriptInstance *instance)
Return a StoryPageElementID reply for a DoCommand.
void LoadOnStack(ScriptData *data)
Store loaded data on the stack.
void Save()
Call the script Save function and save all data in the savegame.
bool is_save_data_on_stack
Is the save data still on the squirrel stack?
bool in_shutdown
Is this instance currently being destructed?
virtual void LoadDummyScript()=0
Load the dummy script.
static void DoCommandReturnVehicleID(ScriptInstance *instance)
Return a VehicleID reply for a DoCommand.
static void DoCommandReturnGoalID(ScriptInstance *instance)
Return a GoalID reply for a DoCommand.
static void DoCommandReturnGroupID(ScriptInstance *instance)
Return a GroupID reply for a DoCommand.
bool DoCommandCallback(const CommandCost &result, const CommandDataBuffer &data, CommandDataBuffer result_data, Commands cmd)
DoCommand callback function for all commands executed by scripts.
SQObject * instance
Squirrel-pointer to the script main class.
ScriptLogTypes::LogData & GetLogData()
Get the log pointer of this script.
void Pause()
Suspends the script for the current tick and then pause the execution of script.
Script_SuspendCallbackProc * callback
Callback that should be called in the next tick the script runs.
void Initialize(const std::string &main_script, const std::string &instance_name, CompanyID company)
Initialize the script and prepare it for its first run.
static bool SaveObject(HSQUIRRELVM vm, SQInteger index, int max_depth, bool test)
Save one object (int / string / array / table) to the savegame.
bool CallLoad()
Call the script Load function if it exists and data was loaded from a savegame.
bool is_dead
True if the script has been stopped.
bool IsDead() const
Return the "this script died" value.
size_t last_allocated_memory
Last known allocated memory value (for display for crashed scripts)
void Continue()
A script in multiplayer waits for the server to handle its DoCommand.
static void LoadEmpty()
Load and discard data from a savegame.
void GameLoop()
Run the GameLoop of a script.
int suspend
The amount of ticks to suspend this script before it's allowed to continue.
std::string versionAPI
Current API used by this script.
static void DoCommandReturnLeagueTableID(ScriptInstance *instance)
Return a LeagueTableID reply for a DoCommand.
class ScriptController * controller
The script main class.
static void DoCommandReturnSignID(ScriptInstance *instance)
Return a SignID reply for a DoCommand.
SQInteger GetOpsTillSuspend()
Get the number of operations the script can execute before being suspended.
bool is_started
Is the scripts constructor executed?
static void DoCommandReturnStoryPageID(ScriptInstance *instance)
Return a StoryPageID reply for a DoCommand.
class ScriptStorage * storage
Some global information for each running script.
bool LoadCompatibilityScript(std::string_view api_version, Subdirectory dir)
Load squirrel script for a specific version to emulate an older API.
ScriptInstance(const char *APIName)
Create a new script.
bool is_paused
Is the script paused? (a paused script will not be executed until unpaused)
The storage for each script.
void * event_data
Pointer to the event data storage.
A throw-class that is given when the script made a fatal error.
const std::string & GetErrorMessage() const
The error message associated with the fatal error.
A throw-class that is given when the script wants to suspend.
int GetSuspendTime()
Get the amount of ticks the script should be suspended.
Script_SuspendCallbackProc * GetSuspendCallback()
Get the callback to call when the script can run again.
bool Resume(int suspend=-1)
Resume a VM when it was suspended via a throw.
Definition squirrel.cpp:339
void SetPrintFunction(SQPrintFunc *func)
Set a custom print function, so you can handle outputs from SQ yourself.
Definition squirrel.hpp:236
void CollectGarbage()
Tell the VM to do a garbage collection run.
Definition squirrel.cpp:368
SQInteger GetOpsTillSuspend()
How many operations can we execute till suspension?
Definition squirrel.cpp:798
bool LoadScript(const std::string &script)
Load a script.
Definition squirrel.cpp:716
bool IsSuspended()
Did the squirrel code suspend or return normally.
Definition squirrel.cpp:777
void ReleaseObject(HSQOBJECT *ptr)
Release a SQ object.
Definition squirrel.hpp:246
void ThrowError(const std::string_view error)
Throw a Squirrel error that will be nicely displayed to the user.
Definition squirrel.hpp:241
size_t GetAllocatedMemory() const noexcept
Get number of bytes allocated by this VM.
Definition squirrel.cpp:173
void SetGlobalPointer(void *ptr)
Sets a pointer in the VM that is reachable from where ever you are in SQ.
Definition squirrel.hpp:226
static void DecreaseOps(HSQUIRRELVM vm, int amount)
Tell the VM to remove amount ops from the number of ops till suspend.
Definition squirrel.cpp:772
bool HasScriptCrashed()
Find out if the squirrel script made an error before.
Definition squirrel.cpp:782
void CrashOccurred()
Set the script status to crashed.
Definition squirrel.cpp:787
HSQUIRRELVM GetVM()
Get the squirrel VM.
Definition squirrel.hpp:82
static SQUserPointer GetRealInstance(HSQUIRRELVM vm, int index, const char *tag)
Get the real-instance pointer.
Definition squirrel.cpp:489
bool CallMethod(HSQOBJECT instance, const char *method_name, HSQOBJECT *ret, int suspend)
Call a method of an instance, in various flavors.
Definition squirrel.cpp:374
const char * GetAPIName()
Get the API name.
Definition squirrel.hpp:47
void ResumeError()
Resume the VM with an error so it prints a stack trace.
Definition squirrel.cpp:361
bool MethodExists(HSQOBJECT instance, const char *method_name)
Check if a method exists in an instance.
Definition squirrel.cpp:321
bool CreateClassInstance(const std::string &class_name, void *real_instance, HSQOBJECT *instance)
Exactly the same as CreateClassInstanceVM, only callable without instance of Squirrel.
Definition squirrel.cpp:483
std::vector< uint8_t > CommandDataBuffer
Storage buffer for serialized command data.
Commands
List of commands.
@ CMD_END
Must ALWAYS be on the end of this list!! (period)
CompanyID _current_company
Company currently doing an action.
#define Debug(category, level, format_string,...)
Output a line of debugging information.
Definition debug.h:37
bool FileExists(const std::string &filename)
Test whether the given filename exists.
Definition fileio.cpp:133
Searchpath
Types of searchpaths OpenTTD might use.
Subdirectory
The different kinds of subdirectories OpenTTD uses.
void SlCopy(void *object, size_t length, VarType conv)
Copy a list of SL_VARs to/from a savegame.
void SlErrorCorrupt(const std::string &msg)
Error handler for corrupt savegames.
Definition saveload.cpp:355
void SlObject(void *object, const SaveLoadTable &slt)
Main SaveLoad function.
#define SLEG_VAR(name, variable, type)
Storage of a global variable in every savegame version.
Definition saveload.h:1191
bool IsSavegameVersionBefore(SaveLoadVersion major, uint8_t minor=0)
Checks whether the savegame is below major.
Definition saveload.h:1268
@ SLV_SCRIPT_INT64
296 PR#9415 SQInteger is 64bit but was saved as 32bit.
Definition saveload.h:335
The definition of Script_FatalError.
ScriptInfo keeps track of all information of a script, like Author, Description, ....
static const int MAX_SL_OPS
The maximum number of operations for saving or loading the data of a script.
static const int MAX_CONSTRUCTOR_OPS
The maximum number of operations for initial start of a script.
static uint8_t _script_sl_byte
Used as source/target by the script saveload code to store/load a single byte.
static void PrintFunc(bool error_msg, const std::string &message)
Callback called by squirrel when a script uses "print" and for error messages.
static const SaveLoad _script_byte[]
SaveLoad array that saves/loads exactly one byte.
The ScriptInstance tracks a script.
static const uint SQUIRREL_MAX_DEPTH
The maximum recursive depth for items stored in the savegame.
Defines ScriptStorage and includes all files required for it.
GameSettings _settings_game
Game settings of a running game or the scenario editor.
Definition settings.cpp:58
void squirrel_register_std(Squirrel *engine)
Register all standard functions we want to give to a script.
static void StrMakeValid(T &dst, const char *str, const char *last, StringValidationSettings settings)
Copies the valid (UTF-8) characters from str up to last to the dst.
Definition string.cpp:125
ScriptSettings script
settings for scripts
SaveLoad type struct.
Definition saveload.h:722
uint32_t script_max_opcode_till_suspend
max opcode calls till scripts will suspend