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