OpenTTD Source 20250527-master-g808af15975
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::SetAllowDoCommand(false);
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 ScriptObject::SetAllowDoCommand(true);
92 } catch (Script_FatalError &e) {
93 this->is_dead = true;
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 /* Downgrade the API till we are the same version as the script. The last
133 * entry in the list is always the current version, so skip that one. */
134 for (auto it = std::rbegin(api_versions) + 1; it != std::rend(api_versions); ++it) {
135 if (!this->LoadCompatibilityScript(*it, dir)) return false;
136
137 if (*it == this->api_version) break;
138 }
139
140 return true;
141}
142
143ScriptInstance::~ScriptInstance()
144{
145 ScriptObject::ActiveInstance active(*this);
146 this->in_shutdown = true;
147
148 if (instance != nullptr) this->engine->ReleaseObject(this->instance);
149 if (engine != nullptr) delete this->engine;
150 delete this->storage;
151 delete this->controller;
152 delete this->instance;
153}
154
156{
157 assert(this->suspend < 0);
158 this->suspend = -this->suspend - 1;
159}
160
162{
163 Debug(script, 0, "The script died unexpectedly.");
164 this->is_dead = true;
165 this->in_shutdown = true;
166
167 this->last_allocated_memory = this->GetAllocatedMemory(); // Update cache
168
169 if (this->instance != nullptr) this->engine->ReleaseObject(this->instance);
170 delete this->instance;
171 delete this->engine;
172 this->instance = nullptr;
173 this->engine = nullptr;
174}
175
177{
178 ScriptObject::ActiveInstance active(*this);
179
180 if (this->IsDead()) return;
181 if (this->engine->HasScriptCrashed()) {
182 /* The script crashed during saving, kill it here. */
183 this->Died();
184 return;
185 }
186 if (this->is_paused) return;
187 this->controller->ticks++;
188
189 if (this->suspend < -1) this->suspend++; // Multiplayer suspend, increase up to -1.
190 if (this->suspend < 0) return; // Multiplayer suspend, wait for Continue().
191 if (--this->suspend > 0) return; // Singleplayer suspend, decrease to 0.
192
193 _current_company = ScriptObject::GetCompany();
194
195 /* If there is a callback to call, call that first */
196 if (this->callback != nullptr) {
197 if (this->is_save_data_on_stack) {
198 sq_poptop(this->engine->GetVM());
199 this->is_save_data_on_stack = false;
200 }
201 try {
202 this->callback(*this);
203 } catch (Script_Suspend &e) {
204 this->suspend = e.GetSuspendTime();
205 this->callback = e.GetSuspendCallback();
206
207 return;
208 }
209 }
210
211 this->suspend = 0;
212 this->callback = nullptr;
213
214 if (!this->is_started) {
215 try {
216 ScriptObject::SetAllowDoCommand(false);
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 ScriptObject::SetAllowDoCommand(true);
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 /* We don't want to be interrupted during the save function. */
524 bool backup_allow = ScriptObject::GetAllowDoCommand();
525 ScriptObject::SetAllowDoCommand(false);
526 try {
527 if (!this->engine->CallMethod(*this->instance, "Save", &savedata, MAX_SL_OPS)) {
528 /* The script crashed in the Save function. We can't kill
529 * it here, but do so in the next script tick. */
530 SaveEmpty();
531 this->engine->CrashOccurred();
532 return;
533 }
534 } catch (Script_FatalError &e) {
535 /* If we don't mark the script as dead here cleaning up the squirrel
536 * stack could throw Script_FatalError again. */
537 this->is_dead = true;
539 this->engine->ResumeError();
540 SaveEmpty();
541 /* We can't kill the script here, so mark it as crashed (not dead) and
542 * kill it in the next script tick. */
543 this->is_dead = false;
544 this->engine->CrashOccurred();
545 return;
546 }
547 ScriptObject::SetAllowDoCommand(backup_allow);
548
549 if (!sq_istable(savedata)) {
550 ScriptLog::Error(this->engine->IsSuspended() ? "This script took too long to Save." : "Save function should return a table.");
551 SaveEmpty();
552 this->engine->CrashOccurred();
553 return;
554 }
555 sq_pushobject(vm, savedata);
556 if (SaveObject(vm, -1, SQUIRREL_MAX_DEPTH, true)) {
557 _script_sl_byte = 1;
558 SlObject(nullptr, _script_byte);
559 SaveObject(vm, -1, SQUIRREL_MAX_DEPTH, false);
560 this->is_save_data_on_stack = true;
561 } else {
562 SaveEmpty();
563 this->engine->CrashOccurred();
564 }
565 } else {
566 ScriptLog::Warning("Save function is not implemented");
567 _script_sl_byte = 0;
568 SlObject(nullptr, _script_byte);
569 }
570}
571
573{
574 /* Suspend script. */
575 HSQUIRRELVM vm = this->engine->GetVM();
577
578 this->is_paused = true;
579}
580
582{
583 this->is_paused = false;
584}
585
587{
588 return this->is_paused;
589}
590
591/* static */ bool ScriptInstance::LoadObjects(ScriptData *data)
592{
593 SlObject(nullptr, _script_byte);
594 switch (_script_sl_byte) {
595 case SQSL_INT: {
596 int64_t value;
597 SlCopy(&value, 1, IsSavegameVersionBefore(SLV_SCRIPT_INT64) ? SLE_FILE_I32 | SLE_VAR_I64 : SLE_INT64);
598 if (data != nullptr) data->push_back(static_cast<SQInteger>(value));
599 return true;
600 }
601
602 case SQSL_STRING: {
603 SlObject(nullptr, _script_byte);
604 static char buf[std::numeric_limits<decltype(_script_sl_byte)>::max()];
605 SlCopy(buf, _script_sl_byte, SLE_CHAR);
606 if (data != nullptr) data->push_back(StrMakeValid(std::string_view(buf, _script_sl_byte)));
607 return true;
608 }
609
610 case SQSL_ARRAY:
611 case SQSL_TABLE: {
612 if (data != nullptr) data->push_back(static_cast<SQSaveLoadType>(_script_sl_byte));
613 while (LoadObjects(data));
614 return true;
615 }
616
617 case SQSL_BOOL: {
618 SlObject(nullptr, _script_byte);
619 if (data != nullptr) data->push_back(static_cast<SQBool>(_script_sl_byte != 0));
620 return true;
621 }
622
623 case SQSL_NULL: {
624 if (data != nullptr) data->push_back(static_cast<SQSaveLoadType>(_script_sl_byte));
625 return true;
626 }
627
628 case SQSL_INSTANCE: {
629 if (data != nullptr) data->push_back(static_cast<SQSaveLoadType>(_script_sl_byte));
630 return true;
631 }
632
634 if (data != nullptr) data->push_back(static_cast<SQSaveLoadType>(_script_sl_byte));
635 return false;
636 }
637
638 default: SlErrorCorrupt("Invalid script data type");
639 }
640}
641
642/* static */ bool ScriptInstance::LoadObjects(HSQUIRRELVM vm, ScriptData *data)
643{
644 ScriptDataVariant value = data->front();
645 data->pop_front();
646
647 struct visitor {
648 HSQUIRRELVM vm;
649 ScriptData *data;
650
651 bool operator()(const SQInteger &value) { sq_pushinteger(this->vm, value); return true; }
652 bool operator()(const std::string &value) { sq_pushstring(this->vm, value); return true; }
653 bool operator()(const SQBool &value) { sq_pushbool(this->vm, value); return true; }
654 bool operator()(const SQSaveLoadType &type)
655 {
656 switch (type) {
657 case SQSL_ARRAY:
658 sq_newarray(this->vm, 0);
659 while (LoadObjects(this->vm, this->data)) {
660 sq_arrayappend(this->vm, -2);
661 /* The value is popped from the stack by squirrel. */
662 }
663 return true;
664
665 case SQSL_TABLE:
666 sq_newtable(this->vm);
667 while (LoadObjects(this->vm, this->data)) {
668 LoadObjects(this->vm, this->data);
669 sq_rawset(this->vm, -3);
670 /* The key (-2) and value (-1) are popped from the stack by squirrel. */
671 }
672 return true;
673
674 case SQSL_NULL:
675 sq_pushnull(this->vm);
676 return true;
677
678 case SQSL_INSTANCE: {
679 SQInteger top = sq_gettop(this->vm);
680 LoadObjects(this->vm, this->data);
681 std::string_view view;
682 sq_getstring(this->vm, -1, view);
683 Squirrel *engine = static_cast<Squirrel *>(sq_getforeignptr(this->vm));
684 std::string class_name = fmt::format("{}{}", engine->GetAPIName(), view);
685 sq_pushroottable(this->vm);
686 sq_pushstring(this->vm, class_name);
687 if (SQ_FAILED(sq_get(this->vm, -2))) throw Script_FatalError(fmt::format("'{}' doesn't exist", class_name));
688 sq_pushroottable(vm);
689 if (SQ_FAILED(sq_call(this->vm, 1, SQTrue, SQFalse))) throw Script_FatalError(fmt::format("Failed to instantiate '{}'", class_name));
690 HSQOBJECT res;
691 sq_getstackobj(vm, -1, &res);
692 sq_addref(vm, &res);
693 sq_settop(this->vm, top);
694 sq_pushobject(vm, res);
695 sq_release(vm, &res);
696 ScriptObject *obj = static_cast<ScriptObject *>(Squirrel::GetRealInstance(vm, -1, "Object"));
697 LoadObjects(this->vm, this->data);
698 if (!obj->LoadObject(vm)) throw Script_FatalError(fmt::format("Failed to load '{}'", class_name));
699 sq_pop(this->vm, 1);
700 return true;
701 }
702
703 case SQSL_ARRAY_TABLE_END:
704 return false;
705
706 default: NOT_REACHED();
707 }
708 }
709 };
710
711 return std::visit(visitor{vm, data}, value);
712}
713
714/* static */ void ScriptInstance::LoadEmpty()
715{
716 SlObject(nullptr, _script_byte);
717 /* Check if there was anything saved at all. */
718 if (_script_sl_byte == 0) return;
719
720 LoadObjects(nullptr);
721}
722
723/* static */ ScriptInstance::ScriptData *ScriptInstance::Load(int version)
724{
725 if (version == -1) {
726 LoadEmpty();
727 return nullptr;
728 }
729
730 SlObject(nullptr, _script_byte);
731 /* Check if there was anything saved at all. */
732 if (_script_sl_byte == 0) return nullptr;
733
734 ScriptData *data = new ScriptData();
735 data->push_back((SQInteger)version);
736 LoadObjects(data);
737 return data;
738}
739
740void ScriptInstance::LoadOnStack(ScriptData *data)
741{
742 ScriptObject::ActiveInstance active(*this);
743
744 if (this->IsDead() || data == nullptr) return;
745
746 HSQUIRRELVM vm = this->engine->GetVM();
747
748 ScriptDataVariant version = data->front();
749 data->pop_front();
750 SQInteger top = sq_gettop(vm);
751 try {
752 sq_pushinteger(vm, std::get<SQInteger>(version));
753 LoadObjects(vm, data);
754 this->is_save_data_on_stack = true;
755 } catch (Script_FatalError &e) {
756 ScriptLog::Warning(fmt::format("Loading failed: {}", e.GetErrorMessage()));
757 /* Discard partially loaded savegame data and version. */
758 sq_settop(vm, top);
759 }
760}
761
763{
764 HSQUIRRELVM vm = this->engine->GetVM();
765 /* Is there save data that we should load? */
766 if (!this->is_save_data_on_stack) return true;
767 /* Whatever happens, after CallLoad the savegame data is removed from the stack. */
768 this->is_save_data_on_stack = false;
769
770 if (!this->engine->MethodExists(*this->instance, "Load")) {
771 ScriptLog::Warning("Loading failed: there was data for the script to load, but the script does not have a Load() function.");
772
773 /* Pop the savegame data and version. */
774 sq_pop(vm, 2);
775 return true;
776 }
777
778 /* Go to the instance-root */
779 sq_pushobject(vm, *this->instance);
780 /* Find the function-name inside the script */
781 sq_pushstring(vm, "Load");
782 /* Change the "Load" string in a function pointer */
783 sq_get(vm, -2);
784 /* Push the main instance as "this" object */
785 sq_pushobject(vm, *this->instance);
786 /* Push the version data and savegame data as arguments */
787 sq_push(vm, -5);
788 sq_push(vm, -5);
789
790 /* Call the script load function. sq_call removes the arguments (but not the
791 * function pointer) from the stack. */
792 if (SQ_FAILED(sq_call(vm, 3, SQFalse, SQTrue, MAX_SL_OPS))) return false;
793
794 /* Pop 1) The version, 2) the savegame data, 3) the object instance, 4) the function pointer. */
795 sq_pop(vm, 4);
796 return true;
797}
798
800{
801 return this->engine->GetOpsTillSuspend();
802}
803
805{
806 ScriptObject::ActiveInstance active(*this);
807
808 if (!ScriptObject::CheckLastCommand(data, cmd)) {
809 Debug(script, 1, "DoCommandCallback terminating a script, last command does not match expected command");
810 return false;
811 }
812
813 ScriptObject::SetLastCommandRes(result.Succeeded());
814 ScriptObject::SetLastCommandResData(std::move(result_data));
815
816 if (result.Failed()) {
817 ScriptObject::SetLastError(ScriptError::StringToError(result.GetErrorMessage()));
818 } else {
819 ScriptObject::IncreaseDoCommandCosts(result.GetCost());
820 ScriptObject::SetLastCost(result.GetCost());
821 }
822
823 ScriptObject::SetLastCommand({}, CMD_END);
824
825 return true;
826}
827
828void ScriptInstance::InsertEvent(class ScriptEvent *event)
829{
830 ScriptObject::ActiveInstance active(*this);
831
832 ScriptEventController::InsertEvent(event);
833}
834
835size_t ScriptInstance::GetAllocatedMemory() const
836{
837 if (this->engine == nullptr) return this->last_allocated_memory;
838 return this->engine->GetAllocatedMemory();
839}
840
842{
843 if (!this->in_shutdown) this->engine->ReleaseObject(obj);
844}
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:87
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