OpenTTD Source 20260311-master-g511d3794ce
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 <https://www.gnu.org/licenses/old-licenses/gpl-2.0>.
6 */
7
9
10#include "../stdafx.h"
11#include "../debug.h"
13
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"
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
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
157{
158 ScriptObject::ActiveInstance active(*this);
159 this->in_shutdown = true;
160
161 if (instance != nullptr) this->engine->ReleaseObject(this->instance.get());
162
163 /* Engine must be reset explicitly in scope of the active instance. */
164 this->engine.reset();
165}
166
168{
169 assert(this->suspend < 0);
170 this->suspend = -this->suspend - 1;
171}
172
174{
175 Debug(script, 0, "The script died unexpectedly.");
176 this->is_dead = true;
177 this->in_shutdown = true;
178
179 this->last_allocated_memory = this->GetAllocatedMemory(); // Update cache
180
181 if (this->instance != nullptr) this->engine->ReleaseObject(this->instance.get());
182 this->engine.reset();
183 this->instance.reset();
184}
185
187{
188 ScriptObject::ActiveInstance active(*this);
189
190 if (this->IsDead()) return;
191 if (this->engine->HasScriptCrashed()) {
192 /* The script crashed during saving, kill it here. */
193 this->Died();
194 return;
195 }
196 if (this->is_paused) return;
197 this->controller->ticks++;
198
199 if (this->suspend < -1) this->suspend++; // Multiplayer suspend, increase up to -1.
200 if (this->suspend < 0) return; // Multiplayer suspend, wait for Continue().
201 if (--this->suspend > 0) return; // Singleplayer suspend, decrease to 0.
202
203 _current_company = ScriptObject::GetCompany();
204
205 /* If there is a callback to call, call that first */
206 if (this->callback != nullptr) {
207 if (this->is_save_data_on_stack) {
208 sq_poptop(this->engine->GetVM());
209 this->is_save_data_on_stack = false;
210 }
211 try {
212 this->callback(*this);
213 } catch (Script_Suspend &e) {
214 this->suspend = e.GetSuspendTime();
215 this->callback = e.GetSuspendCallback();
216
217 return;
218 }
219 }
220
221 this->suspend = 0;
222 this->callback = nullptr;
223
224 if (!this->is_started) {
225 try {
226 {
227 ScriptObject::DisableDoCommandScope disabler{};
228 /* Run the constructor if it exists. Don't allow any DoCommands in it. */
229 if (this->engine->MethodExists(*this->instance, "constructor")) {
230 if (!this->engine->CallMethod(*this->instance, "constructor", MAX_CONSTRUCTOR_OPS) || this->engine->IsSuspended()) {
231 if (this->engine->IsSuspended()) ScriptLog::Error("This script took too long to initialize. Script is not started.");
232 this->Died();
233 return;
234 }
235 }
236 if (!this->CallLoad() || this->engine->IsSuspended()) {
237 if (this->engine->IsSuspended()) ScriptLog::Error("This script took too long in the Load function. Script is not started.");
238 this->Died();
239 return;
240 }
241 }
242 /* Start the script by calling Start() */
243 if (!this->engine->CallMethod(*this->instance, "Start", _settings_game.script.script_max_opcode_till_suspend) || !this->engine->IsSuspended()) this->Died();
244 } catch (Script_Suspend &e) {
245 this->suspend = e.GetSuspendTime();
246 this->callback = e.GetSuspendCallback();
247 } catch (Script_FatalError &e) {
248 this->is_dead = true;
249 this->engine->ThrowError(e.GetErrorMessage());
250 this->engine->ResumeError();
251 this->Died();
252 }
253
254 this->is_started = true;
255 return;
256 }
257 if (this->is_save_data_on_stack) {
258 sq_poptop(this->engine->GetVM());
259 this->is_save_data_on_stack = false;
260 }
261
262 /* Continue the VM */
263 try {
264 if (!this->engine->Resume(_settings_game.script.script_max_opcode_till_suspend)) this->Died();
265 } catch (Script_Suspend &e) {
266 this->suspend = e.GetSuspendTime();
267 this->callback = e.GetSuspendCallback();
268 } catch (Script_FatalError &e) {
269 this->is_dead = true;
270 this->engine->ThrowError(e.GetErrorMessage());
271 this->engine->ResumeError();
272 this->Died();
273 }
274}
275
277{
278 if (this->is_started && !this->IsDead()) {
279 ScriptObject::ActiveInstance active(*this);
280 this->engine->CollectGarbage();
281 }
282}
283
285{
286 instance.engine->InsertResult(ScriptObject::GetLastCommandRes());
287}
288
290{
291 instance.engine->InsertResult(EndianBufferReader::ToValue<VehicleID>(ScriptObject::GetLastCommandResData()));
292}
293
295{
296 instance.engine->InsertResult(EndianBufferReader::ToValue<SignID>(ScriptObject::GetLastCommandResData()));
297}
298
300{
301 instance.engine->InsertResult(EndianBufferReader::ToValue<GroupID>(ScriptObject::GetLastCommandResData()));
302}
303
305{
306 instance.engine->InsertResult(EndianBufferReader::ToValue<GoalID>(ScriptObject::GetLastCommandResData()));
307}
308
310{
311 instance.engine->InsertResult(EndianBufferReader::ToValue<StoryPageID>(ScriptObject::GetLastCommandResData()));
312}
313
315{
316 instance.engine->InsertResult(EndianBufferReader::ToValue<StoryPageElementID>(ScriptObject::GetLastCommandResData()));
317}
318
320{
321 instance.engine->InsertResult(EndianBufferReader::ToValue<LeagueTableElementID>(ScriptObject::GetLastCommandResData()));
322}
323
325{
326 instance.engine->InsertResult(EndianBufferReader::ToValue<LeagueTableID>(ScriptObject::GetLastCommandResData()));
327}
328
329
331{
332 assert(this->storage != nullptr);
333 return *this->storage;
334}
335
336ScriptLogTypes::LogData &ScriptInstance::GetLogData()
337{
338 ScriptObject::ActiveInstance active(*this);
339
340 return ScriptObject::GetLogData();
341}
342
343/*
344 * All data is stored in the following format:
345 * First 1 byte indicating if there is a data blob at all.
346 * 1 byte indicating the type of data.
347 * The data itself, this differs per type:
348 * - integer: a binary representation of the integer (int32_t).
349 * - string: First one byte with the string length, then a 0-terminated char
350 * array. The string can't be longer than 255 bytes (including
351 * terminating '\0').
352 * - array: All data-elements of the array are saved recursive in this
353 * format, and ended with an element of the type
354 * SQSL_ARRAY_TABLE_END.
355 * - table: All key/value pairs are saved in this format (first key 1, then
356 * value 1, then key 2, etc.). All keys and values can have an
357 * arbitrary type (as long as it is supported by the save function
358 * of course). The table is ended with an element of the type
359 * SQSL_ARRAY_TABLE_END.
360 * - bool: A single byte with value 1 representing true and 0 false.
361 * - null: No data.
362 */
363
364static uint8_t _script_sl_byte;
365
367static const SaveLoad _script_byte[] = {
368 SLEG_VAR("type", _script_sl_byte, SLE_UINT8),
369};
370
371/* static */ bool ScriptInstance::SaveObject(HSQUIRRELVM vm, SQInteger index, int max_depth, bool test)
372{
373 if (max_depth == 0) {
374 ScriptLog::Error("Savedata can only be nested to 25 deep. No data saved."); // SQUIRREL_MAX_DEPTH = 25
375 return false;
376 }
377
378 switch (sq_gettype(vm, index)) {
379 case OT_INTEGER: {
380 if (!test) {
382 SlObject(nullptr, _script_byte);
383 }
384 SQInteger res;
385 sq_getinteger(vm, index, &res);
386 if (!test) {
387 int64_t value = (int64_t)res;
388 SlCopy(&value, 1, SLE_INT64);
389 }
390 return true;
391 }
392
393 case OT_STRING: {
394 if (!test) {
396 SlObject(nullptr, _script_byte);
397 }
398 std::string_view view;
399 sq_getstring(vm, index, view);
400 size_t len = view.size() + 1;
401 if (len >= 255) {
402 ScriptLog::Error("Maximum string length is 254 chars. No data saved.");
403 return false;
404 }
405 if (!test) {
406 _script_sl_byte = (uint8_t)len;
407 SlObject(nullptr, _script_byte);
408 SlCopy(const_cast<char *>(view.data()), len, SLE_CHAR);
409 }
410 return true;
411 }
412
413 case OT_ARRAY: {
414 if (!test) {
416 SlObject(nullptr, _script_byte);
417 }
418 sq_pushnull(vm);
419 while (SQ_SUCCEEDED(sq_next(vm, index - 1))) {
420 /* Store the value */
421 bool res = SaveObject(vm, -1, max_depth - 1, test);
422 sq_pop(vm, 2);
423 if (!res) {
424 sq_pop(vm, 1);
425 return false;
426 }
427 }
428 sq_pop(vm, 1);
429 if (!test) {
431 SlObject(nullptr, _script_byte);
432 }
433 return true;
434 }
435
436 case OT_TABLE: {
437 if (!test) {
439 SlObject(nullptr, _script_byte);
440 }
441 sq_pushnull(vm);
442 while (SQ_SUCCEEDED(sq_next(vm, index - 1))) {
443 /* Store the key + value */
444 bool res = SaveObject(vm, -2, max_depth - 1, test) && SaveObject(vm, -1, max_depth - 1, test);
445 sq_pop(vm, 2);
446 if (!res) {
447 sq_pop(vm, 1);
448 return false;
449 }
450 }
451 sq_pop(vm, 1);
452 if (!test) {
454 SlObject(nullptr, _script_byte);
455 }
456 return true;
457 }
458
459 case OT_BOOL: {
460 if (!test) {
462 SlObject(nullptr, _script_byte);
463 }
464 SQBool res;
465 sq_getbool(vm, index, &res);
466 if (!test) {
467 _script_sl_byte = res ? 1 : 0;
468 SlObject(nullptr, _script_byte);
469 }
470 return true;
471 }
472
473 case OT_NULL: {
474 if (!test) {
476 SlObject(nullptr, _script_byte);
477 }
478 return true;
479 }
480
481 case OT_INSTANCE:{
482 if (!test) {
484 SlObject(nullptr, _script_byte);
485 }
486 SQInteger top = sq_gettop(vm);
487 try {
488 ScriptObject *obj = static_cast<ScriptObject *>(Squirrel::GetRealInstance(vm, -1, "Object"));
489 if (!obj->SaveObject(vm)) throw std::exception();
490 if (sq_gettop(vm) != top + 2) throw std::exception();
491 if (sq_gettype(vm, -2) != OT_STRING || !SaveObject(vm, -2, max_depth - 1, test)) throw std::exception();
492 if (!SaveObject(vm, -1, max_depth - 1, test)) throw std::exception();
493 sq_settop(vm, top);
494 return true;
495 } catch (...) {
496 ScriptLog::Error("You tried to save an unsupported type. No data saved.");
497 sq_settop(vm, top);
498 return false;
499 }
500 }
501
502 default:
503 ScriptLog::Error("You tried to save an unsupported type. No data saved.");
504 return false;
505 }
506}
507
508/* static */ void ScriptInstance::SaveEmpty()
509{
510 _script_sl_byte = 0;
511 SlObject(nullptr, _script_byte);
512}
513
515{
516 ScriptObject::ActiveInstance active(*this);
517
518 /* Don't save data if the script didn't start yet or if it crashed. */
519 if (this->engine == nullptr || this->engine->HasScriptCrashed()) {
520 SaveEmpty();
521 return;
522 }
523
524 HSQUIRRELVM vm = this->engine->GetVM();
525 if (this->is_save_data_on_stack) {
526 _script_sl_byte = 1;
527 SlObject(nullptr, _script_byte);
528 /* Save the data that was just loaded. */
529 SaveObject(vm, -1, SQUIRREL_MAX_DEPTH, false);
530 } else if (!this->is_started) {
531 SaveEmpty();
532 return;
533 } else if (this->engine->MethodExists(*this->instance, "Save")) {
534 HSQOBJECT savedata;
535 try {
536 /* We don't want to be interrupted during the save function. */
537 ScriptObject::DisableDoCommandScope disabler{};
538 if (!this->engine->CallMethod(*this->instance, "Save", &savedata, MAX_SL_OPS)) {
539 /* The script crashed in the Save function. We can't kill
540 * it here, but do so in the next script tick. */
541 SaveEmpty();
542 this->engine->CrashOccurred();
543 return;
544 }
545 } catch (Script_FatalError &e) {
546 /* If we don't mark the script as dead here cleaning up the squirrel
547 * stack could throw Script_FatalError again. */
548 this->is_dead = true;
549 this->engine->ThrowError(e.GetErrorMessage());
550 this->engine->ResumeError();
551 SaveEmpty();
552 /* We can't kill the script here, so mark it as crashed (not dead) and
553 * kill it in the next script tick. */
554 this->is_dead = false;
555 this->engine->CrashOccurred();
556 return;
557 }
558
559 if (!sq_istable(savedata)) {
560 ScriptLog::Error(this->GetOpsTillSuspend() <= 0 ? "This script took too long to Save." : "Save function should return a table.");
561 SaveEmpty();
562 this->engine->CrashOccurred();
563 return;
564 }
565 sq_pushobject(vm, savedata);
566 if (SaveObject(vm, -1, SQUIRREL_MAX_DEPTH, true)) {
567 _script_sl_byte = 1;
568 SlObject(nullptr, _script_byte);
569 SaveObject(vm, -1, SQUIRREL_MAX_DEPTH, false);
570 this->is_save_data_on_stack = true;
571 } else {
572 SaveEmpty();
573 this->engine->CrashOccurred();
574 }
575 } else {
576 ScriptLog::Warning("Save function is not implemented");
577 _script_sl_byte = 0;
578 SlObject(nullptr, _script_byte);
579 }
580}
581
583{
584 /* Suspend script. */
585 HSQUIRRELVM vm = this->engine->GetVM();
586 Squirrel::DecreaseOps(vm, _settings_game.script.script_max_opcode_till_suspend);
587
588 this->is_paused = true;
589}
590
592{
593 this->is_paused = false;
594}
595
597{
598 return this->is_paused;
599}
600
601/* static */ bool ScriptInstance::LoadObjects(ScriptData *data)
602{
603 SlObject(nullptr, _script_byte);
604 switch (_script_sl_byte) {
605 case SQSL_INT: {
606 int64_t value;
607 SlCopy(&value, 1, IsSavegameVersionBefore(SLV_SCRIPT_INT64) ? SLE_FILE_I32 | SLE_VAR_I64 : SLE_INT64);
608 if (data != nullptr) data->push_back(static_cast<SQInteger>(value));
609 return true;
610 }
611
612 case SQSL_STRING: {
613 SlObject(nullptr, _script_byte);
614 static char buf[std::numeric_limits<decltype(_script_sl_byte)>::max()];
615 SlCopy(buf, _script_sl_byte, SLE_CHAR);
616 if (data != nullptr) data->push_back(StrMakeValid(std::string_view(buf, _script_sl_byte)));
617 return true;
618 }
619
620 case SQSL_ARRAY:
621 case SQSL_TABLE: {
622 if (data != nullptr) data->push_back(static_cast<SQSaveLoadType>(_script_sl_byte));
623 while (LoadObjects(data));
624 return true;
625 }
626
627 case SQSL_BOOL: {
628 SlObject(nullptr, _script_byte);
629 if (data != nullptr) data->push_back(static_cast<SQBool>(_script_sl_byte != 0));
630 return true;
631 }
632
633 case SQSL_NULL: {
634 if (data != nullptr) data->push_back(static_cast<SQSaveLoadType>(_script_sl_byte));
635 return true;
636 }
637
638 case SQSL_INSTANCE: {
639 if (data != nullptr) data->push_back(static_cast<SQSaveLoadType>(_script_sl_byte));
640 return true;
641 }
642
644 if (data != nullptr) data->push_back(static_cast<SQSaveLoadType>(_script_sl_byte));
645 return false;
646 }
647
648 default: SlErrorCorrupt("Invalid script data type");
649 }
650}
651
652/* static */ bool ScriptInstance::LoadObjects(HSQUIRRELVM vm, ScriptData *data)
653{
654 ScriptDataVariant value = data->front();
655 data->pop_front();
656
657 struct visitor {
658 HSQUIRRELVM vm;
659 ScriptData *data;
660
661 bool operator()(const SQInteger &value) { sq_pushinteger(this->vm, value); return true; }
662 bool operator()(const std::string &value) { sq_pushstring(this->vm, value); return true; }
663 bool operator()(const SQBool &value) { sq_pushbool(this->vm, value); return true; }
664 bool operator()(const SQSaveLoadType &type)
665 {
666 switch (type) {
667 case SQSL_ARRAY:
668 sq_newarray(this->vm, 0);
669 while (LoadObjects(this->vm, this->data)) {
670 sq_arrayappend(this->vm, -2);
671 /* The value is popped from the stack by squirrel. */
672 }
673 return true;
674
675 case SQSL_TABLE:
676 sq_newtable(this->vm);
677 while (LoadObjects(this->vm, this->data)) {
678 LoadObjects(this->vm, this->data);
679 sq_rawset(this->vm, -3);
680 /* The key (-2) and value (-1) are popped from the stack by squirrel. */
681 }
682 return true;
683
684 case SQSL_NULL:
685 sq_pushnull(this->vm);
686 return true;
687
688 case SQSL_INSTANCE: {
689 SQInteger top = sq_gettop(this->vm);
690 LoadObjects(this->vm, this->data);
691 std::string_view view;
692 sq_getstring(this->vm, -1, view);
693 Squirrel *engine = static_cast<Squirrel *>(sq_getforeignptr(this->vm));
694 std::string class_name = fmt::format("{}{}", engine->GetAPIName(), view);
695 sq_pushroottable(this->vm);
696 sq_pushstring(this->vm, class_name);
697 if (SQ_FAILED(sq_get(this->vm, -2))) throw Script_FatalError(fmt::format("'{}' doesn't exist", class_name));
698 sq_pushroottable(vm);
699 if (SQ_FAILED(sq_call(this->vm, 1, SQTrue, SQFalse))) throw Script_FatalError(fmt::format("Failed to instantiate '{}'", class_name));
700 HSQOBJECT res;
701 sq_getstackobj(vm, -1, &res);
702 sq_addref(vm, &res);
703 sq_settop(this->vm, top);
704 sq_pushobject(vm, res);
705 sq_release(vm, &res);
706 ScriptObject *obj = static_cast<ScriptObject *>(Squirrel::GetRealInstance(vm, -1, "Object"));
707 LoadObjects(this->vm, this->data);
708 if (!obj->LoadObject(vm)) throw Script_FatalError(fmt::format("Failed to load '{}'", class_name));
709 sq_pop(this->vm, 1);
710 return true;
711 }
712
713 case SQSL_ARRAY_TABLE_END:
714 return false;
715
716 default: NOT_REACHED();
717 }
718 }
719 };
720
721 return std::visit(visitor{vm, data}, value);
722}
723
724/* static */ void ScriptInstance::LoadEmpty()
725{
726 SlObject(nullptr, _script_byte);
727 /* Check if there was anything saved at all. */
728 if (_script_sl_byte == 0) return;
729
730 LoadObjects(nullptr);
731}
732
733/* static */ ScriptInstance::ScriptData *ScriptInstance::Load(int version)
734{
735 if (version == -1) {
736 LoadEmpty();
737 return nullptr;
738 }
739
740 SlObject(nullptr, _script_byte);
741 /* Check if there was anything saved at all. */
742 if (_script_sl_byte == 0) return nullptr;
743
744 ScriptData *data = new ScriptData();
745 data->push_back((SQInteger)version);
746 LoadObjects(data);
747 return data;
748}
749
750void ScriptInstance::LoadOnStack(ScriptData *data)
751{
752 ScriptObject::ActiveInstance active(*this);
753
754 if (this->IsDead() || data == nullptr) return;
755
756 HSQUIRRELVM vm = this->engine->GetVM();
757
758 ScriptDataVariant version = data->front();
759 data->pop_front();
760 SQInteger top = sq_gettop(vm);
761 try {
762 sq_pushinteger(vm, std::get<SQInteger>(version));
763 LoadObjects(vm, data);
764 this->is_save_data_on_stack = true;
765 } catch (Script_FatalError &e) {
766 ScriptLog::Warning(fmt::format("Loading failed: {}", e.GetErrorMessage()));
767 /* Discard partially loaded savegame data and version. */
768 sq_settop(vm, top);
769 }
770}
771
773{
774 HSQUIRRELVM vm = this->engine->GetVM();
775 /* Is there save data that we should load? */
776 if (!this->is_save_data_on_stack) return true;
777 /* Whatever happens, after CallLoad the savegame data is removed from the stack. */
778 this->is_save_data_on_stack = false;
779
780 if (!this->engine->MethodExists(*this->instance, "Load")) {
781 ScriptLog::Warning("Loading failed: there was data for the script to load, but the script does not have a Load() function.");
782
783 /* Pop the savegame data and version. */
784 sq_pop(vm, 2);
785 return true;
786 }
787
788 /* Go to the instance-root */
789 sq_pushobject(vm, *this->instance);
790 /* Find the function-name inside the script */
791 sq_pushstring(vm, "Load");
792 /* Change the "Load" string in a function pointer */
793 sq_get(vm, -2);
794 /* Push the main instance as "this" object */
795 sq_pushobject(vm, *this->instance);
796 /* Push the version data and savegame data as arguments */
797 sq_push(vm, -5);
798 sq_push(vm, -5);
799
800 /* Call the script load function. sq_call removes the arguments (but not the
801 * function pointer) from the stack. */
802 if (SQ_FAILED(sq_call(vm, 3, SQFalse, SQTrue, MAX_SL_OPS))) return false;
803
804 /* Pop 1) The version, 2) the savegame data, 3) the object instance, 4) the function pointer. */
805 sq_pop(vm, 4);
806 return true;
807}
808
810{
811 return this->engine->GetOpsTillSuspend();
812}
813
815{
816 ScriptObject::ActiveInstance active(*this);
817
818 if (!ScriptObject::CheckLastCommand(data, cmd)) {
819 Debug(script, 1, "DoCommandCallback terminating a script, last command does not match expected command");
820 return false;
821 }
822
823 ScriptObject::SetLastCommandRes(result.Succeeded());
824 ScriptObject::SetLastCommandResData(std::move(result_data));
825
826 if (result.Failed()) {
827 ScriptObject::SetLastError(ScriptError::StringToError(result.GetErrorMessage()));
828 } else {
829 ScriptObject::IncreaseDoCommandCosts(result.GetCost());
830 ScriptObject::SetLastCost(result.GetCost());
831 }
832
833 ScriptObject::SetLastCommand({}, Commands::End);
834
835 return true;
836}
837
838void ScriptInstance::InsertEvent(class ScriptEvent *event)
839{
840 ScriptObject::ActiveInstance active(*this);
841
842 ScriptEventController::InsertEvent(event);
843}
844
845size_t ScriptInstance::GetAllocatedMemory() const
846{
847 if (this->engine == nullptr) return this->last_allocated_memory;
848 return this->engine->GetAllocatedMemory();
849}
850
852{
853 if (!this->in_shutdown) this->engine->ReleaseObject(obj);
854}
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.
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.
virtual ~ScriptInstance()
Release our hold on the engine and reset it in the right scope.
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:514
SQInteger GetOpsTillSuspend()
How many operations can we execute till suspension?
Definition squirrel.cpp:817
void ReleaseObject(HSQOBJECT *ptr)
Release a SQ object.
Definition squirrel.hpp:361
size_t GetAllocatedMemory() const noexcept
Get number of bytes allocated by this VM.
Definition squirrel.cpp:178
static void DecreaseOps(HSQUIRRELVM vm, int amount)
Tell the VM to remove amount ops from the number of ops till suspend.
Definition squirrel.cpp:791
HSQUIRRELVM GetVM()
Get the squirrel VM.
Definition squirrel.hpp:97
std::string_view GetAPIName()
Get the API name.
Definition squirrel.hpp:50
bool MethodExists(HSQOBJECT instance, std::string_view method_name)
Check if a method exists in an instance.
Definition squirrel.cpp:344
std::vector< uint8_t > CommandDataBuffer
Storage buffer for serialized command data.
Commands
List of commands.
CompanyID _current_company
Company currently doing an action.
Types related to companies.
Functions related to debugging.
#define Debug(category, level, format_string,...)
Output a line of debugging information.
Definition debug.h:37
Endian-aware buffer.
bool FileExists(std::string_view filename)
Test whether the given filename exists.
Definition fileio.cpp:132
Functions for standard in/out file operations.
Searchpath
Types of searchpaths OpenTTD might use.
Subdirectory
The different kinds of subdirectories OpenTTD uses.
Definition fileio_type.h:88
Basic types related to goals.
Basic types related to league tables.
A number of safeguards to prevent using unsafe methods.
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:369
void SlObject(void *object, const SaveLoadTable &slt)
Main SaveLoad function.
Functions/types related to saving and loading games.
#define SLEG_VAR(name, variable, type)
Storage of a global variable in every savegame version.
Definition saveload.h:1225
bool IsSavegameVersionBefore(SaveLoadVersion major, uint8_t minor=0)
Checks whether the savegame is below major.
Definition saveload.h:1302
@ 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
Types related to signs.
Defines templates for converting C++ classes to Squirrel classes.
void squirrel_register_std(Squirrel &engine)
Register all standard functions we want to give to a script.
Defines the Squirrel Standard Function class.
Definition of base types and functions in a cross-platform compatible way.
Basic types related to story pages.
static void StrMakeValid(Builder &builder, StringConsumer &consumer, StringValidationSettings settings)
Copies the valid (UTF-8) characters from consumer to the builder.
Definition string.cpp:119
SaveLoad type struct.
Definition saveload.h:756