OpenTTD Source 20250716-master-g6b6caa6fa8
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_type.h"
28#include "../fileio_func.h"
29#include "../goal_type.h"
30#include "../league_type.h"
31#include "../signs_type.h"
32#include "../story_type.h"
33#include "../misc/endian_buffer.hpp"
34
35#include "../safeguards.h"
36
37ScriptStorage::ScriptStorage() = default;
38ScriptStorage::~ScriptStorage() = default;
39
45static void PrintFunc(bool error_msg, std::string_view message)
46{
47 /* Convert to OpenTTD internal capable string */
48 ScriptController::Print(error_msg, std::string{message});
49}
50
51ScriptInstance::ScriptInstance(std::string_view api_name)
52{
53 this->storage = new ScriptStorage();
54 this->engine = new Squirrel(api_name);
56}
57
58void ScriptInstance::Initialize(const std::string &main_script, const std::string &instance_name, CompanyID company)
59{
60 ScriptObject::ActiveInstance active(*this);
61
62 this->controller = new ScriptController(company);
63
64 /* Register the API functions and classes */
65 this->engine->SetGlobalPointer(this->engine);
66 this->RegisterAPI();
67 if (this->IsDead()) {
68 /* Failed to register API; a message has already been logged. */
69 return;
70 }
71
72 try {
73 ScriptObject::DisableDoCommandScope disabler{};
74 /* Load and execute the script for this script */
75 if (main_script == "%_dummy") {
76 this->LoadDummyScript();
77 } else if (!this->engine->LoadScript(main_script) || this->engine->IsSuspended()) {
78 if (this->engine->IsSuspended()) ScriptLog::Error("This script took too long to load script. AI is not started.");
79 this->Died();
80 return;
81 }
82
83 /* Create the main-class */
84 this->instance = new SQObject();
85 if (!this->engine->CreateClassInstance(instance_name, this->controller, this->instance)) {
86 /* If CreateClassInstance has returned false instance has not been
87 * registered with squirrel, so avoid trying to Release it by clearing it now */
88 delete this->instance;
89 this->instance = nullptr;
90 this->Died();
91 return;
92 }
93 } catch (Script_FatalError &e) {
94 this->is_dead = true;
96 this->engine->ResumeError();
97 this->Died();
98 }
99}
100
105
106bool ScriptInstance::LoadCompatibilityScript(std::string_view api_version, Subdirectory dir)
107{
108 std::string script_name = fmt::format("compat_{}.nut", api_version);
109
110 for (Searchpath sp : _valid_searchpaths) {
111 std::string buf = FioGetDirectory(sp, dir);
112 buf += script_name;
113 if (!FileExists(buf)) continue;
114
115 if (this->engine->LoadScript(buf)) return true;
116
117 ScriptLog::Error(fmt::format("Failed to load API compatibility script for {}", api_version));
118 Debug(script, 0, "Error compiling / running API compatibility script: {}", buf);
119 return false;
120 }
121
122 ScriptLog::Warning(fmt::format("API compatibility script for {} not found", api_version));
123 return true;
124}
125
126bool ScriptInstance::LoadCompatibilityScripts(Subdirectory dir, std::span<const std::string_view> api_versions)
127{
128 /* Don't try to load compatibility scripts for the current version. */
129 if (this->api_version == api_versions.back()) return true;
130
131 ScriptLog::Info(fmt::format("Downgrading API to be compatible with version {}", this->api_version));
132
133 /* Downgrade the API till we are the same version as the script. The last
134 * entry in the list is always the current version, so skip that one. */
135 for (auto it = std::rbegin(api_versions) + 1; it != std::rend(api_versions); ++it) {
136 if (!this->LoadCompatibilityScript(*it, dir)) return false;
137
138 if (*it == this->api_version) break;
139 }
140
141 return true;
142}
143
144ScriptInstance::~ScriptInstance()
145{
146 ScriptObject::ActiveInstance active(*this);
147 this->in_shutdown = true;
148
149 if (instance != nullptr) this->engine->ReleaseObject(this->instance);
150 if (engine != nullptr) delete this->engine;
151 delete this->storage;
152 delete this->controller;
153 delete this->instance;
154}
155
157{
158 assert(this->suspend < 0);
159 this->suspend = -this->suspend - 1;
160}
161
163{
164 Debug(script, 0, "The script died unexpectedly.");
165 this->is_dead = true;
166 this->in_shutdown = true;
167
168 this->last_allocated_memory = this->GetAllocatedMemory(); // Update cache
169
170 if (this->instance != nullptr) this->engine->ReleaseObject(this->instance);
171 delete this->instance;
172 delete this->engine;
173 this->instance = nullptr;
174 this->engine = nullptr;
175}
176
178{
179 ScriptObject::ActiveInstance active(*this);
180
181 if (this->IsDead()) return;
182 if (this->engine->HasScriptCrashed()) {
183 /* The script crashed during saving, kill it here. */
184 this->Died();
185 return;
186 }
187 if (this->is_paused) return;
188 this->controller->ticks++;
189
190 if (this->suspend < -1) this->suspend++; // Multiplayer suspend, increase up to -1.
191 if (this->suspend < 0) return; // Multiplayer suspend, wait for Continue().
192 if (--this->suspend > 0) return; // Singleplayer suspend, decrease to 0.
193
194 _current_company = ScriptObject::GetCompany();
195
196 /* If there is a callback to call, call that first */
197 if (this->callback != nullptr) {
198 if (this->is_save_data_on_stack) {
199 sq_poptop(this->engine->GetVM());
200 this->is_save_data_on_stack = false;
201 }
202 try {
203 this->callback(*this);
204 } catch (Script_Suspend &e) {
205 this->suspend = e.GetSuspendTime();
206 this->callback = e.GetSuspendCallback();
207
208 return;
209 }
210 }
211
212 this->suspend = 0;
213 this->callback = nullptr;
214
215 if (!this->is_started) {
216 try {
217 {
218 ScriptObject::DisableDoCommandScope disabler{};
219 /* Run the constructor if it exists. Don't allow any DoCommands in it. */
220 if (this->engine->MethodExists(*this->instance, "constructor")) {
221 if (!this->engine->CallMethod(*this->instance, "constructor", MAX_CONSTRUCTOR_OPS) || this->engine->IsSuspended()) {
222 if (this->engine->IsSuspended()) ScriptLog::Error("This script took too long to initialize. Script is not started.");
223 this->Died();
224 return;
225 }
226 }
227 if (!this->CallLoad() || this->engine->IsSuspended()) {
228 if (this->engine->IsSuspended()) ScriptLog::Error("This script took too long in the Load function. Script is not started.");
229 this->Died();
230 return;
231 }
232 }
233 /* Start the script by calling Start() */
234 if (!this->engine->CallMethod(*this->instance, "Start", _settings_game.script.script_max_opcode_till_suspend) || !this->engine->IsSuspended()) this->Died();
235 } catch (Script_Suspend &e) {
236 this->suspend = e.GetSuspendTime();
237 this->callback = e.GetSuspendCallback();
238 } catch (Script_FatalError &e) {
239 this->is_dead = true;
240 this->engine->ThrowError(e.GetErrorMessage());
241 this->engine->ResumeError();
242 this->Died();
243 }
244
245 this->is_started = true;
246 return;
247 }
248 if (this->is_save_data_on_stack) {
249 sq_poptop(this->engine->GetVM());
250 this->is_save_data_on_stack = false;
251 }
252
253 /* Continue the VM */
254 try {
256 } catch (Script_Suspend &e) {
257 this->suspend = e.GetSuspendTime();
258 this->callback = e.GetSuspendCallback();
259 } catch (Script_FatalError &e) {
260 this->is_dead = true;
261 this->engine->ThrowError(e.GetErrorMessage());
262 this->engine->ResumeError();
263 this->Died();
264 }
265}
266
268{
269 if (this->is_started && !this->IsDead()) {
270 ScriptObject::ActiveInstance active(*this);
271 this->engine->CollectGarbage();
272 }
273}
274
276{
277 instance.engine->InsertResult(ScriptObject::GetLastCommandRes());
278}
279
281{
282 instance.engine->InsertResult(EndianBufferReader::ToValue<VehicleID>(ScriptObject::GetLastCommandResData()));
283}
284
286{
287 instance.engine->InsertResult(EndianBufferReader::ToValue<SignID>(ScriptObject::GetLastCommandResData()));
288}
289
291{
292 instance.engine->InsertResult(EndianBufferReader::ToValue<GroupID>(ScriptObject::GetLastCommandResData()));
293}
294
296{
297 instance.engine->InsertResult(EndianBufferReader::ToValue<GoalID>(ScriptObject::GetLastCommandResData()));
298}
299
301{
302 instance.engine->InsertResult(EndianBufferReader::ToValue<StoryPageID>(ScriptObject::GetLastCommandResData()));
303}
304
306{
307 instance.engine->InsertResult(EndianBufferReader::ToValue<StoryPageElementID>(ScriptObject::GetLastCommandResData()));
308}
309
311{
312 instance.engine->InsertResult(EndianBufferReader::ToValue<LeagueTableElementID>(ScriptObject::GetLastCommandResData()));
313}
314
316{
317 instance.engine->InsertResult(EndianBufferReader::ToValue<LeagueTableID>(ScriptObject::GetLastCommandResData()));
318}
319
320
322{
323 assert(this->storage != nullptr);
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 std::string_view view;
390 sq_getstring(vm, index, view);
391 size_t len = view.size() + 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 *>(view.data()), 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 try {
527 /* We don't want to be interrupted during the save function. */
528 ScriptObject::DisableDoCommandScope disabler{};
529 if (!this->engine->CallMethod(*this->instance, "Save", &savedata, MAX_SL_OPS)) {
530 /* The script crashed in the Save function. We can't kill
531 * it here, but do so in the next script tick. */
532 SaveEmpty();
533 this->engine->CrashOccurred();
534 return;
535 }
536 } catch (Script_FatalError &e) {
537 /* If we don't mark the script as dead here cleaning up the squirrel
538 * stack could throw Script_FatalError again. */
539 this->is_dead = true;
541 this->engine->ResumeError();
542 SaveEmpty();
543 /* We can't kill the script here, so mark it as crashed (not dead) and
544 * kill it in the next script tick. */
545 this->is_dead = false;
546 this->engine->CrashOccurred();
547 return;
548 }
549
550 if (!sq_istable(savedata)) {
551 ScriptLog::Error(this->GetOpsTillSuspend() <= 0 ? "This script took too long to Save." : "Save function should return a table.");
552 SaveEmpty();
553 this->engine->CrashOccurred();
554 return;
555 }
556 sq_pushobject(vm, savedata);
557 if (SaveObject(vm, -1, SQUIRREL_MAX_DEPTH, true)) {
558 _script_sl_byte = 1;
559 SlObject(nullptr, _script_byte);
560 SaveObject(vm, -1, SQUIRREL_MAX_DEPTH, false);
561 this->is_save_data_on_stack = true;
562 } else {
563 SaveEmpty();
564 this->engine->CrashOccurred();
565 }
566 } else {
567 ScriptLog::Warning("Save function is not implemented");
568 _script_sl_byte = 0;
569 SlObject(nullptr, _script_byte);
570 }
571}
572
574{
575 /* Suspend script. */
576 HSQUIRRELVM vm = this->engine->GetVM();
578
579 this->is_paused = true;
580}
581
583{
584 this->is_paused = false;
585}
586
588{
589 return this->is_paused;
590}
591
592/* static */ bool ScriptInstance::LoadObjects(ScriptData *data)
593{
594 SlObject(nullptr, _script_byte);
595 switch (_script_sl_byte) {
596 case SQSL_INT: {
597 int64_t value;
598 SlCopy(&value, 1, IsSavegameVersionBefore(SLV_SCRIPT_INT64) ? SLE_FILE_I32 | SLE_VAR_I64 : SLE_INT64);
599 if (data != nullptr) data->push_back(static_cast<SQInteger>(value));
600 return true;
601 }
602
603 case SQSL_STRING: {
604 SlObject(nullptr, _script_byte);
605 static char buf[std::numeric_limits<decltype(_script_sl_byte)>::max()];
606 SlCopy(buf, _script_sl_byte, SLE_CHAR);
607 if (data != nullptr) data->push_back(StrMakeValid(std::string_view(buf, _script_sl_byte)));
608 return true;
609 }
610
611 case SQSL_ARRAY:
612 case SQSL_TABLE: {
613 if (data != nullptr) data->push_back(static_cast<SQSaveLoadType>(_script_sl_byte));
614 while (LoadObjects(data));
615 return true;
616 }
617
618 case SQSL_BOOL: {
619 SlObject(nullptr, _script_byte);
620 if (data != nullptr) data->push_back(static_cast<SQBool>(_script_sl_byte != 0));
621 return true;
622 }
623
624 case SQSL_NULL: {
625 if (data != nullptr) data->push_back(static_cast<SQSaveLoadType>(_script_sl_byte));
626 return true;
627 }
628
629 case SQSL_INSTANCE: {
630 if (data != nullptr) data->push_back(static_cast<SQSaveLoadType>(_script_sl_byte));
631 return true;
632 }
633
635 if (data != nullptr) data->push_back(static_cast<SQSaveLoadType>(_script_sl_byte));
636 return false;
637 }
638
639 default: SlErrorCorrupt("Invalid script data type");
640 }
641}
642
643/* static */ bool ScriptInstance::LoadObjects(HSQUIRRELVM vm, ScriptData *data)
644{
645 ScriptDataVariant value = data->front();
646 data->pop_front();
647
648 struct visitor {
649 HSQUIRRELVM vm;
650 ScriptData *data;
651
652 bool operator()(const SQInteger &value) { sq_pushinteger(this->vm, value); return true; }
653 bool operator()(const std::string &value) { sq_pushstring(this->vm, value); return true; }
654 bool operator()(const SQBool &value) { sq_pushbool(this->vm, value); return true; }
655 bool operator()(const SQSaveLoadType &type)
656 {
657 switch (type) {
658 case SQSL_ARRAY:
659 sq_newarray(this->vm, 0);
660 while (LoadObjects(this->vm, this->data)) {
661 sq_arrayappend(this->vm, -2);
662 /* The value is popped from the stack by squirrel. */
663 }
664 return true;
665
666 case SQSL_TABLE:
667 sq_newtable(this->vm);
668 while (LoadObjects(this->vm, this->data)) {
669 LoadObjects(this->vm, this->data);
670 sq_rawset(this->vm, -3);
671 /* The key (-2) and value (-1) are popped from the stack by squirrel. */
672 }
673 return true;
674
675 case SQSL_NULL:
676 sq_pushnull(this->vm);
677 return true;
678
679 case SQSL_INSTANCE: {
680 SQInteger top = sq_gettop(this->vm);
681 LoadObjects(this->vm, this->data);
682 std::string_view view;
683 sq_getstring(this->vm, -1, view);
684 Squirrel *engine = static_cast<Squirrel *>(sq_getforeignptr(this->vm));
685 std::string class_name = fmt::format("{}{}", engine->GetAPIName(), view);
686 sq_pushroottable(this->vm);
687 sq_pushstring(this->vm, class_name);
688 if (SQ_FAILED(sq_get(this->vm, -2))) throw Script_FatalError(fmt::format("'{}' doesn't exist", class_name));
689 sq_pushroottable(vm);
690 if (SQ_FAILED(sq_call(this->vm, 1, SQTrue, SQFalse))) throw Script_FatalError(fmt::format("Failed to instantiate '{}'", class_name));
691 HSQOBJECT res;
692 sq_getstackobj(vm, -1, &res);
693 sq_addref(vm, &res);
694 sq_settop(this->vm, top);
695 sq_pushobject(vm, res);
696 sq_release(vm, &res);
697 ScriptObject *obj = static_cast<ScriptObject *>(Squirrel::GetRealInstance(vm, -1, "Object"));
698 LoadObjects(this->vm, this->data);
699 if (!obj->LoadObject(vm)) throw Script_FatalError(fmt::format("Failed to load '{}'", class_name));
700 sq_pop(this->vm, 1);
701 return true;
702 }
703
704 case SQSL_ARRAY_TABLE_END:
705 return false;
706
707 default: NOT_REACHED();
708 }
709 }
710 };
711
712 return std::visit(visitor{vm, data}, value);
713}
714
715/* static */ void ScriptInstance::LoadEmpty()
716{
717 SlObject(nullptr, _script_byte);
718 /* Check if there was anything saved at all. */
719 if (_script_sl_byte == 0) return;
720
721 LoadObjects(nullptr);
722}
723
724/* static */ ScriptInstance::ScriptData *ScriptInstance::Load(int version)
725{
726 if (version == -1) {
727 LoadEmpty();
728 return nullptr;
729 }
730
731 SlObject(nullptr, _script_byte);
732 /* Check if there was anything saved at all. */
733 if (_script_sl_byte == 0) return nullptr;
734
735 ScriptData *data = new ScriptData();
736 data->push_back((SQInteger)version);
737 LoadObjects(data);
738 return data;
739}
740
741void ScriptInstance::LoadOnStack(ScriptData *data)
742{
743 ScriptObject::ActiveInstance active(*this);
744
745 if (this->IsDead() || data == nullptr) return;
746
747 HSQUIRRELVM vm = this->engine->GetVM();
748
749 ScriptDataVariant version = data->front();
750 data->pop_front();
751 SQInteger top = sq_gettop(vm);
752 try {
753 sq_pushinteger(vm, std::get<SQInteger>(version));
754 LoadObjects(vm, data);
755 this->is_save_data_on_stack = true;
756 } catch (Script_FatalError &e) {
757 ScriptLog::Warning(fmt::format("Loading failed: {}", e.GetErrorMessage()));
758 /* Discard partially loaded savegame data and version. */
759 sq_settop(vm, top);
760 }
761}
762
764{
765 HSQUIRRELVM vm = this->engine->GetVM();
766 /* Is there save data that we should load? */
767 if (!this->is_save_data_on_stack) return true;
768 /* Whatever happens, after CallLoad the savegame data is removed from the stack. */
769 this->is_save_data_on_stack = false;
770
771 if (!this->engine->MethodExists(*this->instance, "Load")) {
772 ScriptLog::Warning("Loading failed: there was data for the script to load, but the script does not have a Load() function.");
773
774 /* Pop the savegame data and version. */
775 sq_pop(vm, 2);
776 return true;
777 }
778
779 /* Go to the instance-root */
780 sq_pushobject(vm, *this->instance);
781 /* Find the function-name inside the script */
782 sq_pushstring(vm, "Load");
783 /* Change the "Load" string in a function pointer */
784 sq_get(vm, -2);
785 /* Push the main instance as "this" object */
786 sq_pushobject(vm, *this->instance);
787 /* Push the version data and savegame data as arguments */
788 sq_push(vm, -5);
789 sq_push(vm, -5);
790
791 /* Call the script load function. sq_call removes the arguments (but not the
792 * function pointer) from the stack. */
793 if (SQ_FAILED(sq_call(vm, 3, SQFalse, SQTrue, MAX_SL_OPS))) return false;
794
795 /* Pop 1) The version, 2) the savegame data, 3) the object instance, 4) the function pointer. */
796 sq_pop(vm, 4);
797 return true;
798}
799
801{
802 return this->engine->GetOpsTillSuspend();
803}
804
806{
807 ScriptObject::ActiveInstance active(*this);
808
809 if (!ScriptObject::CheckLastCommand(data, cmd)) {
810 Debug(script, 1, "DoCommandCallback terminating a script, last command does not match expected command");
811 return false;
812 }
813
814 ScriptObject::SetLastCommandRes(result.Succeeded());
815 ScriptObject::SetLastCommandResData(std::move(result_data));
816
817 if (result.Failed()) {
818 ScriptObject::SetLastError(ScriptError::StringToError(result.GetErrorMessage()));
819 } else {
820 ScriptObject::IncreaseDoCommandCosts(result.GetCost());
821 ScriptObject::SetLastCost(result.GetCost());
822 }
823
824 ScriptObject::SetLastCommand({}, CMD_END);
825
826 return true;
827}
828
829void ScriptInstance::InsertEvent(class ScriptEvent *event)
830{
831 ScriptObject::ActiveInstance active(*this);
832
833 ScriptEventController::InsertEvent(event);
834}
835
836size_t ScriptInstance::GetAllocatedMemory() const
837{
838 if (this->engine == nullptr) return this->last_allocated_memory;
839 return this->engine->GetAllocatedMemory();
840}
841
843{
844 if (!this->in_shutdown) this->engine->ReleaseObject(obj);
845}
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.
static void DoCommandReturnGroupID(ScriptInstance &instance)
Return a GroupID reply for a DoCommand.
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 DoCommandReturnVehicleID(ScriptInstance &instance)
Return a VehicleID reply for a DoCommand.
static void DoCommandReturnSignID(ScriptInstance &instance)
Return a SignID reply for a DoCommand.
void CollectGarbage()
Let the VM collect any garbage.
class Squirrel * engine
A wrapper around the squirrel vm.
static void DoCommandReturn(ScriptInstance &instance)
Return a true/false reply for a DoCommand.
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 DoCommandReturnGoalID(ScriptInstance &instance)
Return a GoalID reply for a DoCommand.
static void SaveEmpty()
Don't save any data in the savegame.
static void DoCommandReturnLeagueTableElementID(ScriptInstance &instance)
Return a LeagueTableElementID 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.
std::string api_version
Current API used by this script.
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.
ScriptInstance(std::string_view api_name)
Create a new 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.
static void DoCommandReturnStoryPageElementID(ScriptInstance &instance)
Return a StoryPageElementID reply for a DoCommand.
int suspend
The amount of ticks to suspend this script before it's allowed to continue.
class ScriptController * controller
The script main class.
SQInteger GetOpsTillSuspend()
Get the number of operations the script can execute before being suspended.
class ScriptStorage & GetStorage()
Get the storage of this script.
bool is_started
Is the scripts constructor executed?
static void DoCommandReturnLeagueTableID(ScriptInstance &instance)
Return a LeagueTableID reply for a DoCommand.
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.
bool is_paused
Is the script paused? (a paused script will not be executed until unpaused)
The storage for each script.
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:341
static SQUserPointer GetRealInstance(HSQUIRRELVM vm, int index, std::string_view tag)
Get the real-instance pointer.
Definition squirrel.cpp:493
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:370
SQInteger GetOpsTillSuspend()
How many operations can we execute till suspension?
Definition squirrel.cpp:795
bool LoadScript(const std::string &script)
Load a script.
Definition squirrel.cpp:713
bool IsSuspended()
Did the squirrel code suspend or return normally.
Definition squirrel.cpp:774
void ReleaseObject(HSQOBJECT *ptr)
Release a SQ object.
Definition squirrel.hpp:246
void ThrowError(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:175
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:769
bool HasScriptCrashed()
Find out if the squirrel script made an error before.
Definition squirrel.cpp:779
void CrashOccurred()
Set the script status to crashed.
Definition squirrel.cpp:784
HSQUIRRELVM GetVM()
Get the squirrel VM.
Definition squirrel.hpp:82
std::string_view GetAPIName()
Get the API name.
Definition squirrel.hpp:47
bool CallMethod(HSQOBJECT instance, std::string_view method_name, HSQOBJECT *ret, int suspend)
Call a method of an instance, in various flavors.
Definition squirrel.cpp:376
bool MethodExists(HSQOBJECT instance, std::string_view method_name)
Check if a method exists in an instance.
Definition squirrel.cpp:323
void ResumeError()
Resume the VM with an error so it prints a stack trace.
Definition squirrel.cpp:363
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:487
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(std::string_view filename)
Test whether the given filename exists.
Definition fileio.cpp:132
Searchpath
Types of searchpaths OpenTTD might use.
Subdirectory
The different kinds of subdirectories OpenTTD uses.
Definition fileio_type.h:88
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:357
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:1194
bool IsSavegameVersionBefore(SaveLoadVersion major, uint8_t minor=0)
Checks whether the savegame is below major.
Definition saveload.h:1271
@ 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 void PrintFunc(bool error_msg, std::string_view message)
Callback called by squirrel when a script uses "print" and for error messages.
static uint8_t _script_sl_byte
Used as source/target by the script saveload code to store/load a single byte.
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:61
void squirrel_register_std(Squirrel &engine)
Register all standard functions we want to give to a script.
static void StrMakeValid(Builder &builder, StringConsumer &consumer, StringValidationSettings settings)
Copies the valid (UTF-8) characters from consumer to the builder.
Definition string.cpp:117
ScriptSettings script
settings for scripts
SaveLoad type struct.
Definition saveload.h:725
uint32_t script_max_opcode_till_suspend
max opcode calls till scripts will suspend