OpenTTD Source 20250816-master-g94ba9b1454
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 = std::make_unique<ScriptStorage>();
54 this->engine = std::make_unique<Squirrel>(api_name);
55 this->engine->SetPrintFunction(&PrintFunc);
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 = std::make_unique<ScriptController>(company);
63
64 /* Register the API functions and classes */
65 this->engine->SetGlobalPointer(this->engine.get());
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 = std::make_unique<SQObject>();
85 if (!this->engine->CreateClassInstance(instance_name, this->controller.get(), this->instance.get())) {
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 this->instance.reset();
89 this->Died();
90 return;
91 }
92 } catch (Script_FatalError &e) {
93 this->is_dead = true;
94 this->engine->ThrowError(e.GetErrorMessage());
95 this->engine->ResumeError();
96 this->Died();
97 }
98}
99
104
105bool ScriptInstance::LoadCompatibilityScript(std::string_view api_version, Subdirectory dir)
106{
107 std::string script_name = fmt::format("compat_{}.nut", api_version);
108
109 for (Searchpath sp : _valid_searchpaths) {
110 std::string buf = FioGetDirectory(sp, dir);
111 buf += script_name;
112 if (!FileExists(buf)) continue;
113
114 if (this->engine->LoadScript(buf)) return true;
115
116 ScriptLog::Error(fmt::format("Failed to load API compatibility script for {}", api_version));
117 Debug(script, 0, "Error compiling / running API compatibility script: {}", buf);
118 return false;
119 }
120
121 ScriptLog::Warning(fmt::format("API compatibility script for {} not found", api_version));
122 return true;
123}
124
125bool ScriptInstance::LoadCompatibilityScripts(Subdirectory dir, std::span<const std::string_view> api_versions)
126{
127 /* Don't try to load compatibility scripts for the current version. */
128 if (this->api_version == api_versions.back()) return true;
129
130 ScriptLog::Info(fmt::format("Downgrading API to be compatible with version {}", this->api_version));
131
132 HSQUIRRELVM vm = this->engine->GetVM();
133 sq_pushroottable(vm);
134 sq_pushstring(vm, "CompatScriptRootTable");
135 sq_pushroottable(vm);
136 sq_newslot(vm, -3, SQFalse);
137 sq_pop(vm, 1);
138
139 /* Downgrade the API till we are the same version as the script. The last
140 * entry in the list is always the current version, so skip that one. */
141 for (auto it = std::rbegin(api_versions) + 1; it != std::rend(api_versions); ++it) {
142 if (!this->LoadCompatibilityScript(*it, dir)) return false;
143
144 if (*it == this->api_version) break;
145 }
146
147 sq_pushroottable(vm);
148 sq_pushstring(vm, "CompatScriptRootTable");
149 sq_deleteslot(vm, -2, SQFalse);
150 sq_pop(vm, 1);
151
152 return true;
153}
154
155ScriptInstance::~ScriptInstance()
156{
157 ScriptObject::ActiveInstance active(*this);
158 this->in_shutdown = true;
159
160 if (instance != nullptr) this->engine->ReleaseObject(this->instance.get());
161
162 /* Engine must be reset explicitly in scope of the active instance. */
163 this->engine.reset();
164}
165
167{
168 assert(this->suspend < 0);
169 this->suspend = -this->suspend - 1;
170}
171
173{
174 Debug(script, 0, "The script died unexpectedly.");
175 this->is_dead = true;
176 this->in_shutdown = true;
177
178 this->last_allocated_memory = this->GetAllocatedMemory(); // Update cache
179
180 if (this->instance != nullptr) this->engine->ReleaseObject(this->instance.get());
181 this->engine.reset();
182 this->instance.reset();
183}
184
186{
187 ScriptObject::ActiveInstance active(*this);
188
189 if (this->IsDead()) return;
190 if (this->engine->HasScriptCrashed()) {
191 /* The script crashed during saving, kill it here. */
192 this->Died();
193 return;
194 }
195 if (this->is_paused) return;
196 this->controller->ticks++;
197
198 if (this->suspend < -1) this->suspend++; // Multiplayer suspend, increase up to -1.
199 if (this->suspend < 0) return; // Multiplayer suspend, wait for Continue().
200 if (--this->suspend > 0) return; // Singleplayer suspend, decrease to 0.
201
202 _current_company = ScriptObject::GetCompany();
203
204 /* If there is a callback to call, call that first */
205 if (this->callback != nullptr) {
206 if (this->is_save_data_on_stack) {
207 sq_poptop(this->engine->GetVM());
208 this->is_save_data_on_stack = false;
209 }
210 try {
211 this->callback(*this);
212 } catch (Script_Suspend &e) {
213 this->suspend = e.GetSuspendTime();
214 this->callback = e.GetSuspendCallback();
215
216 return;
217 }
218 }
219
220 this->suspend = 0;
221 this->callback = nullptr;
222
223 if (!this->is_started) {
224 try {
225 {
226 ScriptObject::DisableDoCommandScope disabler{};
227 /* Run the constructor if it exists. Don't allow any DoCommands in it. */
228 if (this->engine->MethodExists(*this->instance, "constructor")) {
229 if (!this->engine->CallMethod(*this->instance, "constructor", MAX_CONSTRUCTOR_OPS) || this->engine->IsSuspended()) {
230 if (this->engine->IsSuspended()) ScriptLog::Error("This script took too long to initialize. Script is not started.");
231 this->Died();
232 return;
233 }
234 }
235 if (!this->CallLoad() || this->engine->IsSuspended()) {
236 if (this->engine->IsSuspended()) ScriptLog::Error("This script took too long in the Load function. Script is not started.");
237 this->Died();
238 return;
239 }
240 }
241 /* Start the script by calling Start() */
242 if (!this->engine->CallMethod(*this->instance, "Start", _settings_game.script.script_max_opcode_till_suspend) || !this->engine->IsSuspended()) this->Died();
243 } catch (Script_Suspend &e) {
244 this->suspend = e.GetSuspendTime();
245 this->callback = e.GetSuspendCallback();
246 } catch (Script_FatalError &e) {
247 this->is_dead = true;
248 this->engine->ThrowError(e.GetErrorMessage());
249 this->engine->ResumeError();
250 this->Died();
251 }
252
253 this->is_started = true;
254 return;
255 }
256 if (this->is_save_data_on_stack) {
257 sq_poptop(this->engine->GetVM());
258 this->is_save_data_on_stack = false;
259 }
260
261 /* Continue the VM */
262 try {
264 } catch (Script_Suspend &e) {
265 this->suspend = e.GetSuspendTime();
266 this->callback = e.GetSuspendCallback();
267 } catch (Script_FatalError &e) {
268 this->is_dead = true;
269 this->engine->ThrowError(e.GetErrorMessage());
270 this->engine->ResumeError();
271 this->Died();
272 }
273}
274
276{
277 if (this->is_started && !this->IsDead()) {
278 ScriptObject::ActiveInstance active(*this);
279 this->engine->CollectGarbage();
280 }
281}
282
284{
285 instance.engine->InsertResult(ScriptObject::GetLastCommandRes());
286}
287
289{
290 instance.engine->InsertResult(EndianBufferReader::ToValue<VehicleID>(ScriptObject::GetLastCommandResData()));
291}
292
294{
295 instance.engine->InsertResult(EndianBufferReader::ToValue<SignID>(ScriptObject::GetLastCommandResData()));
296}
297
299{
300 instance.engine->InsertResult(EndianBufferReader::ToValue<GroupID>(ScriptObject::GetLastCommandResData()));
301}
302
304{
305 instance.engine->InsertResult(EndianBufferReader::ToValue<GoalID>(ScriptObject::GetLastCommandResData()));
306}
307
309{
310 instance.engine->InsertResult(EndianBufferReader::ToValue<StoryPageID>(ScriptObject::GetLastCommandResData()));
311}
312
314{
315 instance.engine->InsertResult(EndianBufferReader::ToValue<StoryPageElementID>(ScriptObject::GetLastCommandResData()));
316}
317
319{
320 instance.engine->InsertResult(EndianBufferReader::ToValue<LeagueTableElementID>(ScriptObject::GetLastCommandResData()));
321}
322
324{
325 instance.engine->InsertResult(EndianBufferReader::ToValue<LeagueTableID>(ScriptObject::GetLastCommandResData()));
326}
327
328
330{
331 assert(this->storage != nullptr);
332 return *this->storage;
333}
334
335ScriptLogTypes::LogData &ScriptInstance::GetLogData()
336{
337 ScriptObject::ActiveInstance active(*this);
338
339 return ScriptObject::GetLogData();
340}
341
342/*
343 * All data is stored in the following format:
344 * First 1 byte indicating if there is a data blob at all.
345 * 1 byte indicating the type of data.
346 * The data itself, this differs per type:
347 * - integer: a binary representation of the integer (int32_t).
348 * - string: First one byte with the string length, then a 0-terminated char
349 * array. The string can't be longer than 255 bytes (including
350 * terminating '\0').
351 * - array: All data-elements of the array are saved recursive in this
352 * format, and ended with an element of the type
353 * SQSL_ARRAY_TABLE_END.
354 * - table: All key/value pairs are saved in this format (first key 1, then
355 * value 1, then key 2, etc.). All keys and values can have an
356 * arbitrary type (as long as it is supported by the save function
357 * of course). The table is ended with an element of the type
358 * SQSL_ARRAY_TABLE_END.
359 * - bool: A single byte with value 1 representing true and 0 false.
360 * - null: No data.
361 */
362
363static uint8_t _script_sl_byte;
364
366static const SaveLoad _script_byte[] = {
367 SLEG_VAR("type", _script_sl_byte, SLE_UINT8),
368};
369
370/* static */ bool ScriptInstance::SaveObject(HSQUIRRELVM vm, SQInteger index, int max_depth, bool test)
371{
372 if (max_depth == 0) {
373 ScriptLog::Error("Savedata can only be nested to 25 deep. No data saved."); // SQUIRREL_MAX_DEPTH = 25
374 return false;
375 }
376
377 switch (sq_gettype(vm, index)) {
378 case OT_INTEGER: {
379 if (!test) {
381 SlObject(nullptr, _script_byte);
382 }
383 SQInteger res;
384 sq_getinteger(vm, index, &res);
385 if (!test) {
386 int64_t value = (int64_t)res;
387 SlCopy(&value, 1, SLE_INT64);
388 }
389 return true;
390 }
391
392 case OT_STRING: {
393 if (!test) {
395 SlObject(nullptr, _script_byte);
396 }
397 std::string_view view;
398 sq_getstring(vm, index, view);
399 size_t len = view.size() + 1;
400 if (len >= 255) {
401 ScriptLog::Error("Maximum string length is 254 chars. No data saved.");
402 return false;
403 }
404 if (!test) {
405 _script_sl_byte = (uint8_t)len;
406 SlObject(nullptr, _script_byte);
407 SlCopy(const_cast<char *>(view.data()), len, SLE_CHAR);
408 }
409 return true;
410 }
411
412 case OT_ARRAY: {
413 if (!test) {
415 SlObject(nullptr, _script_byte);
416 }
417 sq_pushnull(vm);
418 while (SQ_SUCCEEDED(sq_next(vm, index - 1))) {
419 /* Store the value */
420 bool res = SaveObject(vm, -1, max_depth - 1, test);
421 sq_pop(vm, 2);
422 if (!res) {
423 sq_pop(vm, 1);
424 return false;
425 }
426 }
427 sq_pop(vm, 1);
428 if (!test) {
430 SlObject(nullptr, _script_byte);
431 }
432 return true;
433 }
434
435 case OT_TABLE: {
436 if (!test) {
438 SlObject(nullptr, _script_byte);
439 }
440 sq_pushnull(vm);
441 while (SQ_SUCCEEDED(sq_next(vm, index - 1))) {
442 /* Store the key + value */
443 bool res = SaveObject(vm, -2, max_depth - 1, test) && SaveObject(vm, -1, max_depth - 1, test);
444 sq_pop(vm, 2);
445 if (!res) {
446 sq_pop(vm, 1);
447 return false;
448 }
449 }
450 sq_pop(vm, 1);
451 if (!test) {
453 SlObject(nullptr, _script_byte);
454 }
455 return true;
456 }
457
458 case OT_BOOL: {
459 if (!test) {
461 SlObject(nullptr, _script_byte);
462 }
463 SQBool res;
464 sq_getbool(vm, index, &res);
465 if (!test) {
466 _script_sl_byte = res ? 1 : 0;
467 SlObject(nullptr, _script_byte);
468 }
469 return true;
470 }
471
472 case OT_NULL: {
473 if (!test) {
475 SlObject(nullptr, _script_byte);
476 }
477 return true;
478 }
479
480 case OT_INSTANCE:{
481 if (!test) {
483 SlObject(nullptr, _script_byte);
484 }
485 SQInteger top = sq_gettop(vm);
486 try {
487 ScriptObject *obj = static_cast<ScriptObject *>(Squirrel::GetRealInstance(vm, -1, "Object"));
488 if (!obj->SaveObject(vm)) throw std::exception();
489 if (sq_gettop(vm) != top + 2) throw std::exception();
490 if (sq_gettype(vm, -2) != OT_STRING || !SaveObject(vm, -2, max_depth - 1, test)) throw std::exception();
491 if (!SaveObject(vm, -1, max_depth - 1, test)) throw std::exception();
492 sq_settop(vm, top);
493 return true;
494 } catch (...) {
495 ScriptLog::Error("You tried to save an unsupported type. No data saved.");
496 sq_settop(vm, top);
497 return false;
498 }
499 }
500
501 default:
502 ScriptLog::Error("You tried to save an unsupported type. No data saved.");
503 return false;
504 }
505}
506
507/* static */ void ScriptInstance::SaveEmpty()
508{
509 _script_sl_byte = 0;
510 SlObject(nullptr, _script_byte);
511}
512
514{
515 ScriptObject::ActiveInstance active(*this);
516
517 /* Don't save data if the script didn't start yet or if it crashed. */
518 if (this->engine == nullptr || this->engine->HasScriptCrashed()) {
519 SaveEmpty();
520 return;
521 }
522
523 HSQUIRRELVM vm = this->engine->GetVM();
524 if (this->is_save_data_on_stack) {
525 _script_sl_byte = 1;
526 SlObject(nullptr, _script_byte);
527 /* Save the data that was just loaded. */
528 SaveObject(vm, -1, SQUIRREL_MAX_DEPTH, false);
529 } else if (!this->is_started) {
530 SaveEmpty();
531 return;
532 } else if (this->engine->MethodExists(*this->instance, "Save")) {
533 HSQOBJECT savedata;
534 try {
535 /* We don't want to be interrupted during the save function. */
536 ScriptObject::DisableDoCommandScope disabler{};
537 if (!this->engine->CallMethod(*this->instance, "Save", &savedata, MAX_SL_OPS)) {
538 /* The script crashed in the Save function. We can't kill
539 * it here, but do so in the next script tick. */
540 SaveEmpty();
541 this->engine->CrashOccurred();
542 return;
543 }
544 } catch (Script_FatalError &e) {
545 /* If we don't mark the script as dead here cleaning up the squirrel
546 * stack could throw Script_FatalError again. */
547 this->is_dead = true;
548 this->engine->ThrowError(e.GetErrorMessage());
549 this->engine->ResumeError();
550 SaveEmpty();
551 /* We can't kill the script here, so mark it as crashed (not dead) and
552 * kill it in the next script tick. */
553 this->is_dead = false;
554 this->engine->CrashOccurred();
555 return;
556 }
557
558 if (!sq_istable(savedata)) {
559 ScriptLog::Error(this->GetOpsTillSuspend() <= 0 ? "This script took too long to Save." : "Save function should return a table.");
560 SaveEmpty();
561 this->engine->CrashOccurred();
562 return;
563 }
564 sq_pushobject(vm, savedata);
565 if (SaveObject(vm, -1, SQUIRREL_MAX_DEPTH, true)) {
566 _script_sl_byte = 1;
567 SlObject(nullptr, _script_byte);
568 SaveObject(vm, -1, SQUIRREL_MAX_DEPTH, false);
569 this->is_save_data_on_stack = true;
570 } else {
571 SaveEmpty();
572 this->engine->CrashOccurred();
573 }
574 } else {
575 ScriptLog::Warning("Save function is not implemented");
576 _script_sl_byte = 0;
577 SlObject(nullptr, _script_byte);
578 }
579}
580
582{
583 /* Suspend script. */
584 HSQUIRRELVM vm = this->engine->GetVM();
586
587 this->is_paused = true;
588}
589
591{
592 this->is_paused = false;
593}
594
596{
597 return this->is_paused;
598}
599
600/* static */ bool ScriptInstance::LoadObjects(ScriptData *data)
601{
602 SlObject(nullptr, _script_byte);
603 switch (_script_sl_byte) {
604 case SQSL_INT: {
605 int64_t value;
606 SlCopy(&value, 1, IsSavegameVersionBefore(SLV_SCRIPT_INT64) ? SLE_FILE_I32 | SLE_VAR_I64 : SLE_INT64);
607 if (data != nullptr) data->push_back(static_cast<SQInteger>(value));
608 return true;
609 }
610
611 case SQSL_STRING: {
612 SlObject(nullptr, _script_byte);
613 static char buf[std::numeric_limits<decltype(_script_sl_byte)>::max()];
614 SlCopy(buf, _script_sl_byte, SLE_CHAR);
615 if (data != nullptr) data->push_back(StrMakeValid(std::string_view(buf, _script_sl_byte)));
616 return true;
617 }
618
619 case SQSL_ARRAY:
620 case SQSL_TABLE: {
621 if (data != nullptr) data->push_back(static_cast<SQSaveLoadType>(_script_sl_byte));
622 while (LoadObjects(data));
623 return true;
624 }
625
626 case SQSL_BOOL: {
627 SlObject(nullptr, _script_byte);
628 if (data != nullptr) data->push_back(static_cast<SQBool>(_script_sl_byte != 0));
629 return true;
630 }
631
632 case SQSL_NULL: {
633 if (data != nullptr) data->push_back(static_cast<SQSaveLoadType>(_script_sl_byte));
634 return true;
635 }
636
637 case SQSL_INSTANCE: {
638 if (data != nullptr) data->push_back(static_cast<SQSaveLoadType>(_script_sl_byte));
639 return true;
640 }
641
643 if (data != nullptr) data->push_back(static_cast<SQSaveLoadType>(_script_sl_byte));
644 return false;
645 }
646
647 default: SlErrorCorrupt("Invalid script data type");
648 }
649}
650
651/* static */ bool ScriptInstance::LoadObjects(HSQUIRRELVM vm, ScriptData *data)
652{
653 ScriptDataVariant value = data->front();
654 data->pop_front();
655
656 struct visitor {
657 HSQUIRRELVM vm;
658 ScriptData *data;
659
660 bool operator()(const SQInteger &value) { sq_pushinteger(this->vm, value); return true; }
661 bool operator()(const std::string &value) { sq_pushstring(this->vm, value); return true; }
662 bool operator()(const SQBool &value) { sq_pushbool(this->vm, value); return true; }
663 bool operator()(const SQSaveLoadType &type)
664 {
665 switch (type) {
666 case SQSL_ARRAY:
667 sq_newarray(this->vm, 0);
668 while (LoadObjects(this->vm, this->data)) {
669 sq_arrayappend(this->vm, -2);
670 /* The value is popped from the stack by squirrel. */
671 }
672 return true;
673
674 case SQSL_TABLE:
675 sq_newtable(this->vm);
676 while (LoadObjects(this->vm, this->data)) {
677 LoadObjects(this->vm, this->data);
678 sq_rawset(this->vm, -3);
679 /* The key (-2) and value (-1) are popped from the stack by squirrel. */
680 }
681 return true;
682
683 case SQSL_NULL:
684 sq_pushnull(this->vm);
685 return true;
686
687 case SQSL_INSTANCE: {
688 SQInteger top = sq_gettop(this->vm);
689 LoadObjects(this->vm, this->data);
690 std::string_view view;
691 sq_getstring(this->vm, -1, view);
692 Squirrel *engine = static_cast<Squirrel *>(sq_getforeignptr(this->vm));
693 std::string class_name = fmt::format("{}{}", engine->GetAPIName(), view);
694 sq_pushroottable(this->vm);
695 sq_pushstring(this->vm, class_name);
696 if (SQ_FAILED(sq_get(this->vm, -2))) throw Script_FatalError(fmt::format("'{}' doesn't exist", class_name));
697 sq_pushroottable(vm);
698 if (SQ_FAILED(sq_call(this->vm, 1, SQTrue, SQFalse))) throw Script_FatalError(fmt::format("Failed to instantiate '{}'", class_name));
699 HSQOBJECT res;
700 sq_getstackobj(vm, -1, &res);
701 sq_addref(vm, &res);
702 sq_settop(this->vm, top);
703 sq_pushobject(vm, res);
704 sq_release(vm, &res);
705 ScriptObject *obj = static_cast<ScriptObject *>(Squirrel::GetRealInstance(vm, -1, "Object"));
706 LoadObjects(this->vm, this->data);
707 if (!obj->LoadObject(vm)) throw Script_FatalError(fmt::format("Failed to load '{}'", class_name));
708 sq_pop(this->vm, 1);
709 return true;
710 }
711
712 case SQSL_ARRAY_TABLE_END:
713 return false;
714
715 default: NOT_REACHED();
716 }
717 }
718 };
719
720 return std::visit(visitor{vm, data}, value);
721}
722
723/* static */ void ScriptInstance::LoadEmpty()
724{
725 SlObject(nullptr, _script_byte);
726 /* Check if there was anything saved at all. */
727 if (_script_sl_byte == 0) return;
728
729 LoadObjects(nullptr);
730}
731
732/* static */ ScriptInstance::ScriptData *ScriptInstance::Load(int version)
733{
734 if (version == -1) {
735 LoadEmpty();
736 return nullptr;
737 }
738
739 SlObject(nullptr, _script_byte);
740 /* Check if there was anything saved at all. */
741 if (_script_sl_byte == 0) return nullptr;
742
743 ScriptData *data = new ScriptData();
744 data->push_back((SQInteger)version);
745 LoadObjects(data);
746 return data;
747}
748
749void ScriptInstance::LoadOnStack(ScriptData *data)
750{
751 ScriptObject::ActiveInstance active(*this);
752
753 if (this->IsDead() || data == nullptr) return;
754
755 HSQUIRRELVM vm = this->engine->GetVM();
756
757 ScriptDataVariant version = data->front();
758 data->pop_front();
759 SQInteger top = sq_gettop(vm);
760 try {
761 sq_pushinteger(vm, std::get<SQInteger>(version));
762 LoadObjects(vm, data);
763 this->is_save_data_on_stack = true;
764 } catch (Script_FatalError &e) {
765 ScriptLog::Warning(fmt::format("Loading failed: {}", e.GetErrorMessage()));
766 /* Discard partially loaded savegame data and version. */
767 sq_settop(vm, top);
768 }
769}
770
772{
773 HSQUIRRELVM vm = this->engine->GetVM();
774 /* Is there save data that we should load? */
775 if (!this->is_save_data_on_stack) return true;
776 /* Whatever happens, after CallLoad the savegame data is removed from the stack. */
777 this->is_save_data_on_stack = false;
778
779 if (!this->engine->MethodExists(*this->instance, "Load")) {
780 ScriptLog::Warning("Loading failed: there was data for the script to load, but the script does not have a Load() function.");
781
782 /* Pop the savegame data and version. */
783 sq_pop(vm, 2);
784 return true;
785 }
786
787 /* Go to the instance-root */
788 sq_pushobject(vm, *this->instance);
789 /* Find the function-name inside the script */
790 sq_pushstring(vm, "Load");
791 /* Change the "Load" string in a function pointer */
792 sq_get(vm, -2);
793 /* Push the main instance as "this" object */
794 sq_pushobject(vm, *this->instance);
795 /* Push the version data and savegame data as arguments */
796 sq_push(vm, -5);
797 sq_push(vm, -5);
798
799 /* Call the script load function. sq_call removes the arguments (but not the
800 * function pointer) from the stack. */
801 if (SQ_FAILED(sq_call(vm, 3, SQFalse, SQTrue, MAX_SL_OPS))) return false;
802
803 /* Pop 1) The version, 2) the savegame data, 3) the object instance, 4) the function pointer. */
804 sq_pop(vm, 4);
805 return true;
806}
807
809{
810 return this->engine->GetOpsTillSuspend();
811}
812
814{
815 ScriptObject::ActiveInstance active(*this);
816
817 if (!ScriptObject::CheckLastCommand(data, cmd)) {
818 Debug(script, 1, "DoCommandCallback terminating a script, last command does not match expected command");
819 return false;
820 }
821
822 ScriptObject::SetLastCommandRes(result.Succeeded());
823 ScriptObject::SetLastCommandResData(std::move(result_data));
824
825 if (result.Failed()) {
826 ScriptObject::SetLastError(ScriptError::StringToError(result.GetErrorMessage()));
827 } else {
828 ScriptObject::IncreaseDoCommandCosts(result.GetCost());
829 ScriptObject::SetLastCost(result.GetCost());
830 }
831
832 ScriptObject::SetLastCommand({}, CMD_END);
833
834 return true;
835}
836
837void ScriptInstance::InsertEvent(class ScriptEvent *event)
838{
839 ScriptObject::ActiveInstance active(*this);
840
841 ScriptEventController::InsertEvent(event);
842}
843
844size_t ScriptInstance::GetAllocatedMemory() const
845{
846 if (this->engine == nullptr) return this->last_allocated_memory;
847 return this->engine->GetAllocatedMemory();
848}
849
851{
852 if (!this->in_shutdown) this->engine->ReleaseObject(obj);
853}
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.
std::unique_ptr< class Squirrel > engine
A wrapper around the squirrel vm.
void CollectGarbage()
Let the VM collect any garbage.
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.
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.
std::unique_ptr< class ScriptController > controller
The script main class.
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.
SQInteger GetOpsTillSuspend()
Get the number of operations the script can execute before being suspended.
std::unique_ptr< class ScriptStorage > storage
Some global information for each running script.
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.
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)
std::unique_ptr< SQObject > instance
Squirrel-pointer to the script main class.
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.
static SQUserPointer GetRealInstance(HSQUIRRELVM vm, int index, std::string_view tag)
Get the real-instance pointer.
Definition squirrel.cpp:493
SQInteger GetOpsTillSuspend()
How many operations can we execute till suspension?
Definition squirrel.cpp:795
void ReleaseObject(HSQOBJECT *ptr)
Release a SQ object.
Definition squirrel.hpp:246
size_t GetAllocatedMemory() const noexcept
Get number of bytes allocated by this VM.
Definition squirrel.cpp:175
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
HSQUIRRELVM GetVM()
Get the squirrel VM.
Definition squirrel.hpp:82
std::string_view GetAPIName()
Get the API name.
Definition squirrel.hpp:47
bool MethodExists(HSQOBJECT instance, std::string_view method_name)
Check if a method exists in an instance.
Definition squirrel.cpp:323
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:1196
bool IsSavegameVersionBefore(SaveLoadVersion major, uint8_t minor=0)
Checks whether the savegame is below major.
Definition saveload.h:1273
@ 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:727
uint32_t script_max_opcode_till_suspend
max opcode calls till scripts will suspend