OpenTTD Source: src/script/script_instance.cpp Source File

OpenTTD Source 15.0-RC2

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
10#include "../stdafx.h"
11#include "../debug.h"
12#include "../saveload/saveload.h"
13
14#include "../script/squirrel_class.hpp"
15#include "../script/squirrel_std.hpp"
16
17#include "script_fatalerror.hpp"
18#include "script_storage.hpp"
19#include "script_info.hpp"
20#include "script_instance.hpp"
21
22#include "api/script_controller.hpp"
23#include "api/script_error.hpp"
24#include "api/script_event.hpp"
25#include "api/script_log.hpp"
26
27#include "../company_type.h"
28#include "../fileio_func.h"
29#include "../goal_type.h"
30#include "../league_type.h"
31#include "../signs_type.h"
32#include "../story_type.h"
33#include "../misc/endian_buffer.hpp"
34
35#include "../safeguards.h"
36
37ScriptStorage::ScriptStorage() = default;
38ScriptStorage::~ScriptStorage() = default;
39
45 static 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
58 void 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
100 void ScriptInstance::RegisterAPI()
101{
102 squirrel_register_std(*this->engine);
103}
104
105 bool 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
125 bool ScriptInstance::LoadCompatibilityScripts(Subdirectory dir, std::span<const std::string_view> api_versions)
126{
127 /* Don't try to load compatibility scripts for the current version. */
128 if (this->api_version == api_versions.back()) return true;
129
130 ScriptLog::Info(fmt::format("Downgrading API to be compatible with version {}", this->api_version));
131
132 HSQUIRRELVM vm = this->engine->GetVM();
133 sq_pushroottable(vm);
134 sq_pushstring(vm, "CompatScriptRootTable");
135 sq_pushroottable(vm);
136 sq_newslot(vm, -3, SQFalse);
137 sq_pop(vm, 1);
138
139 /* Downgrade the API till we are the same version as the script. The last
140 * entry in the list is always the current version, so skip that one. */
141 for (auto it = std::rbegin(api_versions) + 1; it != std::rend(api_versions); ++it) {
142 if (!this->LoadCompatibilityScript(*it, dir)) return false;
143
144 if (*it == this->api_version) break;
145 }
146
147 sq_pushroottable(vm);
148 sq_pushstring(vm, "CompatScriptRootTable");
149 sq_deleteslot(vm, -2, SQFalse);
150 sq_pop(vm, 1);
151
152 return true;
153}
154
155ScriptInstance::~ScriptInstance()
156{
157 ScriptObject::ActiveInstance active(*this);
158 this->in_shutdown = true;
159
160 if (instance != nullptr) this->engine->ReleaseObject(this->instance.get());
161
162 /* Engine must be reset explicitly in scope of the active instance. */
163 this->engine.reset();
164}
165
166 void ScriptInstance::Continue()
167{
168 assert(this->suspend < 0);
169 this->suspend = -this->suspend - 1;
170}
171
172 void ScriptInstance::Died()
173{
174 Debug(script, 0, "The script died unexpectedly.");
175 this->is_dead = true;
176 this->in_shutdown = true;
177
178 this->last_allocated_memory = this->GetAllocatedMemory(); // Update cache
179
180 if (this->instance != nullptr) this->engine->ReleaseObject(this->instance.get());
181 this->engine.reset();
182 this->instance.reset();
183}
184
185 void ScriptInstance::GameLoop()
186{
187 ScriptObject::ActiveInstance active(*this);
188
189 if (this->IsDead()) return;
190 if (this->engine->HasScriptCrashed()) {
191 /* The script crashed during saving, kill it here. */
192 this->Died();
193 return;
194 }
195 if (this->is_paused) return;
196 this->controller->ticks++;
197
198 if (this->suspend < -1) this->suspend++; // Multiplayer suspend, increase up to -1.
199 if (this->suspend < 0) return; // Multiplayer suspend, wait for Continue().
200 if (--this->suspend > 0) return; // Singleplayer suspend, decrease to 0.
201
202 _current_company = ScriptObject::GetCompany();
203
204 /* If there is a callback to call, call that first */
205 if (this->callback != nullptr) {
206 if (this->is_save_data_on_stack) {
207 sq_poptop(this->engine->GetVM());
208 this->is_save_data_on_stack = false;
209 }
210 try {
211 this->callback(*this);
212 } catch (Script_Suspend &e) {
213 this->suspend = e.GetSuspendTime();
214 this->callback = e.GetSuspendCallback();
215
216 return;
217 }
218 }
219
220 this->suspend = 0;
221 this->callback = nullptr;
222
223 if (!this->is_started) {
224 try {
225 {
226 ScriptObject::DisableDoCommandScope disabler{};
227 /* Run the constructor if it exists. Don't allow any DoCommands in it. */
228 if (this->engine->MethodExists(*this->instance, "constructor")) {
229 if (!this->engine->CallMethod(*this->instance, "constructor", MAX_CONSTRUCTOR_OPS) || this->engine->IsSuspended()) {
230 if (this->engine->IsSuspended()) ScriptLog::Error("This script took too long to initialize. Script is not started.");
231 this->Died();
232 return;
233 }
234 }
235 if (!this->CallLoad() || this->engine->IsSuspended()) {
236 if (this->engine->IsSuspended()) ScriptLog::Error("This script took too long in the Load function. Script is not started.");
237 this->Died();
238 return;
239 }
240 }
241 /* Start the script by calling Start() */
242 if (!this->engine->CallMethod(*this->instance, "Start", _settings_game.script.script_max_opcode_till_suspend) || !this->engine->IsSuspended()) this->Died();
243 } catch (Script_Suspend &e) {
244 this->suspend = e.GetSuspendTime();
245 this->callback = e.GetSuspendCallback();
246 } catch (Script_FatalError &e) {
247 this->is_dead = true;
248 this->engine->ThrowError(e.GetErrorMessage());
249 this->engine->ResumeError();
250 this->Died();
251 }
252
253 this->is_started = true;
254 return;
255 }
256 if (this->is_save_data_on_stack) {
257 sq_poptop(this->engine->GetVM());
258 this->is_save_data_on_stack = false;
259 }
260
261 /* Continue the VM */
262 try {
263 if (!this->engine->Resume(_settings_game.script.script_max_opcode_till_suspend)) this->Died();
264 } catch (Script_Suspend &e) {
265 this->suspend = e.GetSuspendTime();
266 this->callback = e.GetSuspendCallback();
267 } catch (Script_FatalError &e) {
268 this->is_dead = true;
269 this->engine->ThrowError(e.GetErrorMessage());
270 this->engine->ResumeError();
271 this->Died();
272 }
273}
274
275 void ScriptInstance::CollectGarbage()
276{
277 if (this->is_started && !this->IsDead()) {
278 ScriptObject::ActiveInstance active(*this);
279 this->engine->CollectGarbage();
280 }
281}
282
283/* static */ void ScriptInstance::DoCommandReturn(ScriptInstance &instance)
284{
285 instance.engine->InsertResult(ScriptObject::GetLastCommandRes());
286}
287
288/* static */ void ScriptInstance::DoCommandReturnVehicleID(ScriptInstance &instance)
289{
290 instance.engine->InsertResult(EndianBufferReader::ToValue<VehicleID>(ScriptObject::GetLastCommandResData()));
291}
292
293/* static */ void ScriptInstance::DoCommandReturnSignID(ScriptInstance &instance)
294{
295 instance.engine->InsertResult(EndianBufferReader::ToValue<SignID>(ScriptObject::GetLastCommandResData()));
296}
297
298/* static */ void ScriptInstance::DoCommandReturnGroupID(ScriptInstance &instance)
299{
300 instance.engine->InsertResult(EndianBufferReader::ToValue<GroupID>(ScriptObject::GetLastCommandResData()));
301}
302
303/* static */ void ScriptInstance::DoCommandReturnGoalID(ScriptInstance &instance)
304{
305 instance.engine->InsertResult(EndianBufferReader::ToValue<GoalID>(ScriptObject::GetLastCommandResData()));
306}
307
308/* static */ void ScriptInstance::DoCommandReturnStoryPageID(ScriptInstance &instance)
309{
310 instance.engine->InsertResult(EndianBufferReader::ToValue<StoryPageID>(ScriptObject::GetLastCommandResData()));
311}
312
313/* static */ void ScriptInstance::DoCommandReturnStoryPageElementID(ScriptInstance &instance)
314{
315 instance.engine->InsertResult(EndianBufferReader::ToValue<StoryPageElementID>(ScriptObject::GetLastCommandResData()));
316}
317
318/* static */ void ScriptInstance::DoCommandReturnLeagueTableElementID(ScriptInstance &instance)
319{
320 instance.engine->InsertResult(EndianBufferReader::ToValue<LeagueTableElementID>(ScriptObject::GetLastCommandResData()));
321}
322
323/* static */ void ScriptInstance::DoCommandReturnLeagueTableID(ScriptInstance &instance)
324{
325 instance.engine->InsertResult(EndianBufferReader::ToValue<LeagueTableID>(ScriptObject::GetLastCommandResData()));
326}
327
328
329 ScriptStorage &ScriptInstance::GetStorage()
330{
331 assert(this->storage != nullptr);
332 return *this->storage;
333}
334
335ScriptLogTypes::LogData &ScriptInstance::GetLogData()
336{
337 ScriptObject::ActiveInstance active(*this);
338
339 return ScriptObject::GetLogData();
340}
341
342/*
343 * All data is stored in the following format:
344 * First 1 byte indicating if there is a data blob at all.
345 * 1 byte indicating the type of data.
346 * The data itself, this differs per type:
347 * - integer: a binary representation of the integer (int32_t).
348 * - string: First one byte with the string length, then a 0-terminated char
349 * array. The string can't be longer than 255 bytes (including
350 * terminating '\0').
351 * - array: All data-elements of the array are saved recursive in this
352 * format, and ended with an element of the type
353 * SQSL_ARRAY_TABLE_END.
354 * - table: All key/value pairs are saved in this format (first key 1, then
355 * value 1, then key 2, etc.). All keys and values can have an
356 * arbitrary type (as long as it is supported by the save function
357 * of course). The table is ended with an element of the type
358 * SQSL_ARRAY_TABLE_END.
359 * - bool: A single byte with value 1 representing true and 0 false.
360 * - null: No data.
361 */
362
363 static uint8_t _script_sl_byte;
364
366 static const SaveLoad _script_byte[] = {
367 SLEG_VAR("type", _script_sl_byte, SLE_UINT8),
368};
369
370/* static */ bool ScriptInstance::SaveObject(HSQUIRRELVM vm, SQInteger index, int max_depth, bool test)
371{
372 if (max_depth == 0) {
373 ScriptLog::Error("Savedata can only be nested to 25 deep. No data saved."); // SQUIRREL_MAX_DEPTH = 25
374 return false;
375 }
376
377 switch (sq_gettype(vm, index)) {
378 case OT_INTEGER: {
379 if (!test) {
380 _script_sl_byte = SQSL_INT;
381 SlObject(nullptr, _script_byte);
382 }
383 SQInteger res;
384 sq_getinteger(vm, index, &res);
385 if (!test) {
386 int64_t value = (int64_t)res;
387 SlCopy(&value, 1, SLE_INT64);
388 }
389 return true;
390 }
391
392 case OT_STRING: {
393 if (!test) {
394 _script_sl_byte = SQSL_STRING;
395 SlObject(nullptr, _script_byte);
396 }
397 std::string_view view;
398 sq_getstring(vm, index, view);
399 size_t len = view.size() + 1;
400 if (len >= 255) {
401 ScriptLog::Error("Maximum string length is 254 chars. No data saved.");
402 return false;
403 }
404 if (!test) {
405 _script_sl_byte = (uint8_t)len;
406 SlObject(nullptr, _script_byte);
407 SlCopy(const_cast<char *>(view.data()), len, SLE_CHAR);
408 }
409 return true;
410 }
411
412 case OT_ARRAY: {
413 if (!test) {
414 _script_sl_byte = SQSL_ARRAY;
415 SlObject(nullptr, _script_byte);
416 }
417 sq_pushnull(vm);
418 while (SQ_SUCCEEDED(sq_next(vm, index - 1))) {
419 /* Store the value */
420 bool res = SaveObject(vm, -1, max_depth - 1, test);
421 sq_pop(vm, 2);
422 if (!res) {
423 sq_pop(vm, 1);
424 return false;
425 }
426 }
427 sq_pop(vm, 1);
428 if (!test) {
429 _script_sl_byte = SQSL_ARRAY_TABLE_END;
430 SlObject(nullptr, _script_byte);
431 }
432 return true;
433 }
434
435 case OT_TABLE: {
436 if (!test) {
437 _script_sl_byte = SQSL_TABLE;
438 SlObject(nullptr, _script_byte);
439 }
440 sq_pushnull(vm);
441 while (SQ_SUCCEEDED(sq_next(vm, index - 1))) {
442 /* Store the key + value */
443 bool res = SaveObject(vm, -2, max_depth - 1, test) && SaveObject(vm, -1, max_depth - 1, test);
444 sq_pop(vm, 2);
445 if (!res) {
446 sq_pop(vm, 1);
447 return false;
448 }
449 }
450 sq_pop(vm, 1);
451 if (!test) {
452 _script_sl_byte = SQSL_ARRAY_TABLE_END;
453 SlObject(nullptr, _script_byte);
454 }
455 return true;
456 }
457
458 case OT_BOOL: {
459 if (!test) {
460 _script_sl_byte = SQSL_BOOL;
461 SlObject(nullptr, _script_byte);
462 }
463 SQBool res;
464 sq_getbool(vm, index, &res);
465 if (!test) {
466 _script_sl_byte = res ? 1 : 0;
467 SlObject(nullptr, _script_byte);
468 }
469 return true;
470 }
471
472 case OT_NULL: {
473 if (!test) {
474 _script_sl_byte = SQSL_NULL;
475 SlObject(nullptr, _script_byte);
476 }
477 return true;
478 }
479
480 case OT_INSTANCE:{
481 if (!test) {
482 _script_sl_byte = SQSL_INSTANCE;
483 SlObject(nullptr, _script_byte);
484 }
485 SQInteger top = sq_gettop(vm);
486 try {
487 ScriptObject *obj = static_cast<ScriptObject *>(Squirrel::GetRealInstance(vm, -1, "Object"));
488 if (!obj->SaveObject(vm)) throw std::exception();
489 if (sq_gettop(vm) != top + 2) throw std::exception();
490 if (sq_gettype(vm, -2) != OT_STRING || !SaveObject(vm, -2, max_depth - 1, test)) throw std::exception();
491 if (!SaveObject(vm, -1, max_depth - 1, test)) throw std::exception();
492 sq_settop(vm, top);
493 return true;
494 } catch (...) {
495 ScriptLog::Error("You tried to save an unsupported type. No data saved.");
496 sq_settop(vm, top);
497 return false;
498 }
499 }
500
501 default:
502 ScriptLog::Error("You tried to save an unsupported type. No data saved.");
503 return false;
504 }
505}
506
507/* static */ void ScriptInstance::SaveEmpty()
508{
509 _script_sl_byte = 0;
510 SlObject(nullptr, _script_byte);
511}
512
513 void ScriptInstance::Save()
514{
515 ScriptObject::ActiveInstance active(*this);
516
517 /* Don't save data if the script didn't start yet or if it crashed. */
518 if (this->engine == nullptr || this->engine->HasScriptCrashed()) {
519 SaveEmpty();
520 return;
521 }
522
523 HSQUIRRELVM vm = this->engine->GetVM();
524 if (this->is_save_data_on_stack) {
525 _script_sl_byte = 1;
526 SlObject(nullptr, _script_byte);
527 /* Save the data that was just loaded. */
528 SaveObject(vm, -1, SQUIRREL_MAX_DEPTH, false);
529 } else if (!this->is_started) {
530 SaveEmpty();
531 return;
532 } else if (this->engine->MethodExists(*this->instance, "Save")) {
533 HSQOBJECT savedata;
534 try {
535 /* We don't want to be interrupted during the save function. */
536 ScriptObject::DisableDoCommandScope disabler{};
537 if (!this->engine->CallMethod(*this->instance, "Save", &savedata, MAX_SL_OPS)) {
538 /* The script crashed in the Save function. We can't kill
539 * it here, but do so in the next script tick. */
540 SaveEmpty();
541 this->engine->CrashOccurred();
542 return;
543 }
544 } catch (Script_FatalError &e) {
545 /* If we don't mark the script as dead here cleaning up the squirrel
546 * stack could throw Script_FatalError again. */
547 this->is_dead = true;
548 this->engine->ThrowError(e.GetErrorMessage());
549 this->engine->ResumeError();
550 SaveEmpty();
551 /* We can't kill the script here, so mark it as crashed (not dead) and
552 * kill it in the next script tick. */
553 this->is_dead = false;
554 this->engine->CrashOccurred();
555 return;
556 }
557
558 if (!sq_istable(savedata)) {
559 ScriptLog::Error(this->GetOpsTillSuspend() <= 0 ? "This script took too long to Save." : "Save function should return a table.");
560 SaveEmpty();
561 this->engine->CrashOccurred();
562 return;
563 }
564 sq_pushobject(vm, savedata);
565 if (SaveObject(vm, -1, SQUIRREL_MAX_DEPTH, true)) {
566 _script_sl_byte = 1;
567 SlObject(nullptr, _script_byte);
568 SaveObject(vm, -1, SQUIRREL_MAX_DEPTH, false);
569 this->is_save_data_on_stack = true;
570 } else {
571 SaveEmpty();
572 this->engine->CrashOccurred();
573 }
574 } else {
575 ScriptLog::Warning("Save function is not implemented");
576 _script_sl_byte = 0;
577 SlObject(nullptr, _script_byte);
578 }
579}
580
581 void ScriptInstance::Pause()
582{
583 /* Suspend script. */
584 HSQUIRRELVM vm = this->engine->GetVM();
585 Squirrel::DecreaseOps(vm, _settings_game.script.script_max_opcode_till_suspend);
586
587 this->is_paused = true;
588}
589
590 void ScriptInstance::Unpause()
591{
592 this->is_paused = false;
593}
594
595 bool ScriptInstance::IsPaused()
596{
597 return this->is_paused;
598}
599
600/* static */ bool ScriptInstance::LoadObjects(ScriptData *data)
601{
602 SlObject(nullptr, _script_byte);
603 switch (_script_sl_byte) {
604 case SQSL_INT: {
605 int64_t value;
606 SlCopy(&value, 1, IsSavegameVersionBefore(SLV_SCRIPT_INT64) ? SLE_FILE_I32 | SLE_VAR_I64 : SLE_INT64);
607 if (data != nullptr) data->push_back(static_cast<SQInteger>(value));
608 return true;
609 }
610
611 case SQSL_STRING: {
612 SlObject(nullptr, _script_byte);
613 static char buf[std::numeric_limits<decltype(_script_sl_byte)>::max()];
614 SlCopy(buf, _script_sl_byte, SLE_CHAR);
615 if (data != nullptr) data->push_back(StrMakeValid(std::string_view(buf, _script_sl_byte)));
616 return true;
617 }
618
619 case SQSL_ARRAY:
620 case SQSL_TABLE: {
621 if (data != nullptr) data->push_back(static_cast<SQSaveLoadType>(_script_sl_byte));
622 while (LoadObjects(data));
623 return true;
624 }
625
626 case SQSL_BOOL: {
627 SlObject(nullptr, _script_byte);
628 if (data != nullptr) data->push_back(static_cast<SQBool>(_script_sl_byte != 0));
629 return true;
630 }
631
632 case SQSL_NULL: {
633 if (data != nullptr) data->push_back(static_cast<SQSaveLoadType>(_script_sl_byte));
634 return true;
635 }
636
637 case SQSL_INSTANCE: {
638 if (data != nullptr) data->push_back(static_cast<SQSaveLoadType>(_script_sl_byte));
639 return true;
640 }
641
642 case SQSL_ARRAY_TABLE_END: {
643 if (data != nullptr) data->push_back(static_cast<SQSaveLoadType>(_script_sl_byte));
644 return false;
645 }
646
647 default: SlErrorCorrupt("Invalid script data type");
648 }
649}
650
651/* static */ bool ScriptInstance::LoadObjects(HSQUIRRELVM vm, ScriptData *data)
652{
653 ScriptDataVariant value = data->front();
654 data->pop_front();
655
656 struct visitor {
657 HSQUIRRELVM vm;
658 ScriptData *data;
659
660 bool operator()(const SQInteger &value) { sq_pushinteger(this->vm, value); return true; }
661 bool operator()(const std::string &value) { sq_pushstring(this->vm, value); return true; }
662 bool operator()(const SQBool &value) { sq_pushbool(this->vm, value); return true; }
663 bool operator()(const SQSaveLoadType &type)
664 {
665 switch (type) {
666 case SQSL_ARRAY:
667 sq_newarray(this->vm, 0);
668 while (LoadObjects(this->vm, this->data)) {
669 sq_arrayappend(this->vm, -2);
670 /* The value is popped from the stack by squirrel. */
671 }
672 return true;
673
674 case SQSL_TABLE:
675 sq_newtable(this->vm);
676 while (LoadObjects(this->vm, this->data)) {
677 LoadObjects(this->vm, this->data);
678 sq_rawset(this->vm, -3);
679 /* The key (-2) and value (-1) are popped from the stack by squirrel. */
680 }
681 return true;
682
683 case SQSL_NULL:
684 sq_pushnull(this->vm);
685 return true;
686
687 case SQSL_INSTANCE: {
688 SQInteger top = sq_gettop(this->vm);
689 LoadObjects(this->vm, this->data);
690 std::string_view view;
691 sq_getstring(this->vm, -1, view);
692 Squirrel *engine = static_cast<Squirrel *>(sq_getforeignptr(this->vm));
693 std::string class_name = fmt::format("{}{}", engine->GetAPIName(), view);
694 sq_pushroottable(this->vm);
695 sq_pushstring(this->vm, class_name);
696 if (SQ_FAILED(sq_get(this->vm, -2))) throw Script_FatalError(fmt::format("'{}' doesn't exist", class_name));
697 sq_pushroottable(vm);
698 if (SQ_FAILED(sq_call(this->vm, 1, SQTrue, SQFalse))) throw Script_FatalError(fmt::format("Failed to instantiate '{}'", class_name));
699 HSQOBJECT res;
700 sq_getstackobj(vm, -1, &res);
701 sq_addref(vm, &res);
702 sq_settop(this->vm, top);
703 sq_pushobject(vm, res);
704 sq_release(vm, &res);
705 ScriptObject *obj = static_cast<ScriptObject *>(Squirrel::GetRealInstance(vm, -1, "Object"));
706 LoadObjects(this->vm, this->data);
707 if (!obj->LoadObject(vm)) throw Script_FatalError(fmt::format("Failed to load '{}'", class_name));
708 sq_pop(this->vm, 1);
709 return true;
710 }
711
712 case SQSL_ARRAY_TABLE_END:
713 return false;
714
715 default: NOT_REACHED();
716 }
717 }
718 };
719
720 return std::visit(visitor{vm, data}, value);
721}
722
723/* static */ void ScriptInstance::LoadEmpty()
724{
725 SlObject(nullptr, _script_byte);
726 /* Check if there was anything saved at all. */
727 if (_script_sl_byte == 0) return;
728
729 LoadObjects(nullptr);
730}
731
732/* static */ ScriptInstance::ScriptData *ScriptInstance::Load(int version)
733{
734 if (version == -1) {
735 LoadEmpty();
736 return nullptr;
737 }
738
739 SlObject(nullptr, _script_byte);
740 /* Check if there was anything saved at all. */
741 if (_script_sl_byte == 0) return nullptr;
742
743 ScriptData *data = new ScriptData();
744 data->push_back((SQInteger)version);
745 LoadObjects(data);
746 return data;
747}
748
749 void ScriptInstance::LoadOnStack(ScriptData *data)
750{
751 ScriptObject::ActiveInstance active(*this);
752
753 if (this->IsDead() || data == nullptr) return;
754
755 HSQUIRRELVM vm = this->engine->GetVM();
756
757 ScriptDataVariant version = data->front();
758 data->pop_front();
759 SQInteger top = sq_gettop(vm);
760 try {
761 sq_pushinteger(vm, std::get<SQInteger>(version));
762 LoadObjects(vm, data);
763 this->is_save_data_on_stack = true;
764 } catch (Script_FatalError &e) {
765 ScriptLog::Warning(fmt::format("Loading failed: {}", e.GetErrorMessage()));
766 /* Discard partially loaded savegame data and version. */
767 sq_settop(vm, top);
768 }
769}
770
771 bool ScriptInstance::CallLoad()
772{
773 HSQUIRRELVM vm = this->engine->GetVM();
774 /* Is there save data that we should load? */
775 if (!this->is_save_data_on_stack) return true;
776 /* Whatever happens, after CallLoad the savegame data is removed from the stack. */
777 this->is_save_data_on_stack = false;
778
779 if (!this->engine->MethodExists(*this->instance, "Load")) {
780 ScriptLog::Warning("Loading failed: there was data for the script to load, but the script does not have a Load() function.");
781
782 /* Pop the savegame data and version. */
783 sq_pop(vm, 2);
784 return true;
785 }
786
787 /* Go to the instance-root */
788 sq_pushobject(vm, *this->instance);
789 /* Find the function-name inside the script */
790 sq_pushstring(vm, "Load");
791 /* Change the "Load" string in a function pointer */
792 sq_get(vm, -2);
793 /* Push the main instance as "this" object */
794 sq_pushobject(vm, *this->instance);
795 /* Push the version data and savegame data as arguments */
796 sq_push(vm, -5);
797 sq_push(vm, -5);
798
799 /* Call the script load function. sq_call removes the arguments (but not the
800 * function pointer) from the stack. */
801 if (SQ_FAILED(sq_call(vm, 3, SQFalse, SQTrue, MAX_SL_OPS))) return false;
802
803 /* Pop 1) The version, 2) the savegame data, 3) the object instance, 4) the function pointer. */
804 sq_pop(vm, 4);
805 return true;
806}
807
808SQInteger ScriptInstance::GetOpsTillSuspend()
809{
810 return this->engine->GetOpsTillSuspend();
811}
812
813 bool ScriptInstance::DoCommandCallback(const CommandCost &result, const CommandDataBuffer &data, CommandDataBuffer result_data, Commands cmd)
814{
815 ScriptObject::ActiveInstance active(*this);
816
817 if (!ScriptObject::CheckLastCommand(data, cmd)) {
818 Debug(script, 1, "DoCommandCallback terminating a script, last command does not match expected command");
819 return false;
820 }
821
822 ScriptObject::SetLastCommandRes(result.Succeeded());
823 ScriptObject::SetLastCommandResData(std::move(result_data));
824
825 if (result.Failed()) {
826 ScriptObject::SetLastError(ScriptError::StringToError(result.GetErrorMessage()));
827 } else {
828 ScriptObject::IncreaseDoCommandCosts(result.GetCost());
829 ScriptObject::SetLastCost(result.GetCost());
830 }
831
832 ScriptObject::SetLastCommand({}, CMD_END);
833
834 return true;
835}
836
837 void ScriptInstance::InsertEvent(class ScriptEvent *event)
838{
839 ScriptObject::ActiveInstance active(*this);
840
841 ScriptEventController::InsertEvent(event);
842}
843
844size_t ScriptInstance::GetAllocatedMemory() const
845{
846 if (this->engine == nullptr) return this->last_allocated_memory;
847 return this->engine->GetAllocatedMemory();
848}
849
850 void ScriptInstance::ReleaseSQObject(HSQOBJECT *obj)
851{
852 if (!this->in_shutdown) this->engine->ReleaseObject(obj);
853}
CommandCost
Common return value for all commands.
Definition command_type.h:24
CommandCost::Succeeded
bool Succeeded() const
Did this command succeed?
Definition command_type.h:168
CommandCost::GetCost
Money GetCost() const
The costs as made up to this moment.
Definition command_type.h:118
CommandCost::Failed
bool Failed() const
Did this command fail?
Definition command_type.h:177
CommandCost::GetErrorMessage
StringID GetErrorMessage() const
Returns the error message of a command.
Definition command_type.h:148
ScriptInstance
Runtime information about a script like a pointer to the squirrel vm and the current state.
Definition script_instance.hpp:25
ScriptInstance::LoadCompatibilityScripts
bool LoadCompatibilityScripts(Subdirectory dir, std::span< const std::string_view > api_versions)
Load squirrel scripts to emulate an older API.
Definition script_instance.cpp:125
ScriptInstance::Load
static ScriptData * Load(int version)
Load data from a savegame.
Definition script_instance.cpp:732
ScriptInstance::RegisterAPI
virtual void RegisterAPI()
Register all API functions to the VM.
Definition script_instance.cpp:100
ScriptInstance::InsertEvent
void InsertEvent(class ScriptEvent *event)
Insert an event for this script.
Definition script_instance.cpp:837
ScriptInstance::Unpause
void Unpause()
Resume execution of the script.
Definition script_instance.cpp:590
ScriptInstance::DoCommandReturnGroupID
static void DoCommandReturnGroupID(ScriptInstance &instance)
Return a GroupID reply for a DoCommand.
Definition script_instance.cpp:298
ScriptInstance::IsPaused
bool IsPaused()
Checks if the script is paused.
Definition script_instance.cpp:595
ScriptInstance::Died
virtual void Died()
Tell the script it died.
Definition script_instance.cpp:172
ScriptInstance::LoadObjects
static bool LoadObjects(ScriptData *data)
Load all objects from a savegame.
Definition script_instance.cpp:600
ScriptInstance::ReleaseSQObject
void ReleaseSQObject(HSQOBJECT *obj)
Decrease the ref count of a squirrel object.
Definition script_instance.cpp:850
ScriptInstance::DoCommandReturnVehicleID
static void DoCommandReturnVehicleID(ScriptInstance &instance)
Return a VehicleID reply for a DoCommand.
Definition script_instance.cpp:288
ScriptInstance::DoCommandReturnSignID
static void DoCommandReturnSignID(ScriptInstance &instance)
Return a SignID reply for a DoCommand.
Definition script_instance.cpp:293
ScriptInstance::engine
std::unique_ptr< class Squirrel > engine
A wrapper around the squirrel vm.
Definition script_instance.hpp:259
ScriptInstance::CollectGarbage
void CollectGarbage()
Let the VM collect any garbage.
Definition script_instance.cpp:275
ScriptInstance::DoCommandReturn
static void DoCommandReturn(ScriptInstance &instance)
Return a true/false reply for a DoCommand.
Definition script_instance.cpp:283
ScriptInstance::SQSaveLoadType
SQSaveLoadType
The type of the data that follows in the savegame.
Definition script_instance.hpp:28
ScriptInstance::SQSL_BOOL
@ SQSL_BOOL
The following data is a boolean.
Definition script_instance.hpp:33
ScriptInstance::SQSL_STRING
@ SQSL_STRING
The following data is an string.
Definition script_instance.hpp:30
ScriptInstance::SQSL_TABLE
@ SQSL_TABLE
The following data is an table.
Definition script_instance.hpp:32
ScriptInstance::SQSL_ARRAY_TABLE_END
@ SQSL_ARRAY_TABLE_END
Marks the end of an array or table, no data follows.
Definition script_instance.hpp:36
ScriptInstance::SQSL_ARRAY
@ SQSL_ARRAY
The following data is an array.
Definition script_instance.hpp:31
ScriptInstance::SQSL_NULL
@ SQSL_NULL
A null variable.
Definition script_instance.hpp:34
ScriptInstance::SQSL_INT
@ SQSL_INT
The following data is an integer.
Definition script_instance.hpp:29
ScriptInstance::SQSL_INSTANCE
@ SQSL_INSTANCE
The following data is an instance.
Definition script_instance.hpp:35
ScriptInstance::DoCommandReturnGoalID
static void DoCommandReturnGoalID(ScriptInstance &instance)
Return a GoalID reply for a DoCommand.
Definition script_instance.cpp:303
ScriptInstance::SaveEmpty
static void SaveEmpty()
Don't save any data in the savegame.
Definition script_instance.cpp:507
ScriptInstance::DoCommandReturnLeagueTableElementID
static void DoCommandReturnLeagueTableElementID(ScriptInstance &instance)
Return a LeagueTableElementID reply for a DoCommand.
Definition script_instance.cpp:318
ScriptInstance::LoadOnStack
void LoadOnStack(ScriptData *data)
Store loaded data on the stack.
Definition script_instance.cpp:749
ScriptInstance::Save
void Save()
Call the script Save function and save all data in the savegame.
Definition script_instance.cpp:513
ScriptInstance::is_save_data_on_stack
bool is_save_data_on_stack
Is the save data still on the squirrel stack?
Definition script_instance.hpp:297
ScriptInstance::in_shutdown
bool in_shutdown
Is this instance currently being destructed?
Definition script_instance.hpp:300
ScriptInstance::LoadDummyScript
virtual void LoadDummyScript()=0
Load the dummy script.
ScriptInstance::api_version
std::string api_version
Current API used by this script.
Definition script_instance.hpp:260
ScriptInstance::DoCommandCallback
bool DoCommandCallback(const CommandCost &result, const CommandDataBuffer &data, CommandDataBuffer result_data, Commands cmd)
DoCommand callback function for all commands executed by scripts.
Definition script_instance.cpp:813
ScriptInstance::GetLogData
ScriptLogTypes::LogData & GetLogData()
Get the log pointer of this script.
Definition script_instance.cpp:335
ScriptInstance::Pause
void Pause()
Suspends the script for the current tick and then pause the execution of script.
Definition script_instance.cpp:581
ScriptInstance::ScriptInstance
ScriptInstance(std::string_view api_name)
Create a new script.
Definition script_instance.cpp:51
ScriptInstance::callback
Script_SuspendCallbackProc * callback
Callback that should be called in the next tick the script runs.
Definition script_instance.hpp:301
ScriptInstance::Initialize
void Initialize(const std::string &main_script, const std::string &instance_name, CompanyID company)
Initialize the script and prepare it for its first run.
Definition script_instance.cpp:58
ScriptInstance::SaveObject
static bool SaveObject(HSQUIRRELVM vm, SQInteger index, int max_depth, bool test)
Save one object (int / string / array / table) to the savegame.
Definition script_instance.cpp:370
ScriptInstance::CallLoad
bool CallLoad()
Call the script Load function if it exists and data was loaded from a savegame.
Definition script_instance.cpp:771
ScriptInstance::is_dead
bool is_dead
True if the script has been stopped.
Definition script_instance.hpp:296
ScriptInstance::controller
std::unique_ptr< class ScriptController > controller
The script main class.
Definition script_instance.hpp:292
ScriptInstance::IsDead
bool IsDead() const
Return the "this script died" value.
Definition script_instance.hpp:158
ScriptInstance::last_allocated_memory
size_t last_allocated_memory
Last known allocated memory value (for display for crashed scripts)
Definition script_instance.hpp:302
ScriptInstance::Continue
void Continue()
A script in multiplayer waits for the server to handle its DoCommand.
Definition script_instance.cpp:166
ScriptInstance::LoadEmpty
static void LoadEmpty()
Load and discard data from a savegame.
Definition script_instance.cpp:723
ScriptInstance::GameLoop
void GameLoop()
Run the GameLoop of a script.
Definition script_instance.cpp:185
ScriptInstance::DoCommandReturnStoryPageElementID
static void DoCommandReturnStoryPageElementID(ScriptInstance &instance)
Return a StoryPageElementID reply for a DoCommand.
Definition script_instance.cpp:313
ScriptInstance::suspend
int suspend
The amount of ticks to suspend this script before it's allowed to continue.
Definition script_instance.hpp:298
ScriptInstance::GetOpsTillSuspend
SQInteger GetOpsTillSuspend()
Get the number of operations the script can execute before being suspended.
Definition script_instance.cpp:808
ScriptInstance::storage
std::unique_ptr< class ScriptStorage > storage
Some global information for each running script.
Definition script_instance.hpp:291
ScriptInstance::GetStorage
class ScriptStorage & GetStorage()
Get the storage of this script.
Definition script_instance.cpp:329
ScriptInstance::is_started
bool is_started
Is the scripts constructor executed?
Definition script_instance.hpp:295
ScriptInstance::DoCommandReturnLeagueTableID
static void DoCommandReturnLeagueTableID(ScriptInstance &instance)
Return a LeagueTableID reply for a DoCommand.
Definition script_instance.cpp:323
ScriptInstance::DoCommandReturnStoryPageID
static void DoCommandReturnStoryPageID(ScriptInstance &instance)
Return a StoryPageID reply for a DoCommand.
Definition script_instance.cpp:308
ScriptInstance::LoadCompatibilityScript
bool LoadCompatibilityScript(std::string_view api_version, Subdirectory dir)
Load squirrel script for a specific version to emulate an older API.
Definition script_instance.cpp:105
ScriptInstance::is_paused
bool is_paused
Is the script paused? (a paused script will not be executed until unpaused)
Definition script_instance.hpp:299
ScriptInstance::instance
std::unique_ptr< SQObject > instance
Squirrel-pointer to the script main class.
Definition script_instance.hpp:293
ScriptStorage
The storage for each script.
Definition script_storage.hpp:43
Script_FatalError
A throw-class that is given when the script made a fatal error.
Definition script_fatalerror.hpp:16
Script_FatalError::GetErrorMessage
const std::string & GetErrorMessage() const
The error message associated with the fatal error.
Definition script_fatalerror.hpp:30
Script_Suspend
A throw-class that is given when the script wants to suspend.
Definition script_suspend.hpp:21
Script_Suspend::GetSuspendTime
int GetSuspendTime()
Get the amount of ticks the script should be suspended.
Definition script_suspend.hpp:37
Script_Suspend::GetSuspendCallback
Script_SuspendCallbackProc * GetSuspendCallback()
Get the callback to call when the script can run again.
Definition script_suspend.hpp:43
Squirrel
Definition squirrel.hpp:24
Squirrel::GetRealInstance
static SQUserPointer GetRealInstance(HSQUIRRELVM vm, int index, std::string_view tag)
Get the real-instance pointer.
Definition squirrel.cpp:493
Squirrel::GetOpsTillSuspend
SQInteger GetOpsTillSuspend()
How many operations can we execute till suspension?
Definition squirrel.cpp:795
Squirrel::ReleaseObject
void ReleaseObject(HSQOBJECT *ptr)
Release a SQ object.
Definition squirrel.hpp:252
Squirrel::GetAllocatedMemory
size_t GetAllocatedMemory() const noexcept
Get number of bytes allocated by this VM.
Definition squirrel.cpp:175
Squirrel::DecreaseOps
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
Squirrel::GetVM
HSQUIRRELVM GetVM()
Get the squirrel VM.
Definition squirrel.hpp:82
Squirrel::GetAPIName
std::string_view GetAPIName()
Get the API name.
Definition squirrel.hpp:47
Squirrel::MethodExists
bool MethodExists(HSQOBJECT instance, std::string_view method_name)
Check if a method exists in an instance.
Definition squirrel.cpp:323
CommandDataBuffer
std::vector< uint8_t > CommandDataBuffer
Storage buffer for serialized command data.
Definition command_type.h:481
Commands
Commands
List of commands.
Definition command_type.h:196
CMD_END
@ CMD_END
Must ALWAYS be on the end of this list!! (period)
Definition command_type.h:378
_current_company
CompanyID _current_company
Company currently doing an action.
Definition company_cmd.cpp:56
Debug
#define Debug(category, level, format_string,...)
Output a line of debugging information.
Definition debug.h:37
FileExists
bool FileExists(std::string_view filename)
Test whether the given filename exists.
Definition fileio.cpp:132
Searchpath
Searchpath
Types of searchpaths OpenTTD might use.
Definition fileio_type.h:113
Subdirectory
Subdirectory
The different kinds of subdirectories OpenTTD uses.
Definition fileio_type.h:88
SlCopy
void SlCopy(void *object, size_t length, VarType conv)
Copy a list of SL_VARs to/from a savegame.
Definition saveload.cpp:1150
SlErrorCorrupt
void SlErrorCorrupt(const std::string &msg)
Error handler for corrupt savegames.
Definition saveload.cpp:369
SlObject
void SlObject(void *object, const SaveLoadTable &slt)
Main SaveLoad function.
Definition saveload.cpp:1846
SLEG_VAR
#define SLEG_VAR(name, variable, type)
Storage of a global variable in every savegame version.
Definition saveload.h:1201
IsSavegameVersionBefore
bool IsSavegameVersionBefore(SaveLoadVersion major, uint8_t minor=0)
Checks whether the savegame is below major.
Definition saveload.h:1278
SLV_SCRIPT_INT64
@ SLV_SCRIPT_INT64
296 PR#9415 SQInteger is 64bit but was saved as 32bit.
Definition saveload.h:335
script_fatalerror.hpp
The definition of Script_FatalError.
script_info.hpp
ScriptInfo keeps track of all information of a script, like Author, Description, ....
MAX_SL_OPS
static const int MAX_SL_OPS
The maximum number of operations for saving or loading the data of a script.
Definition script_info.hpp:19
MAX_CONSTRUCTOR_OPS
static const int MAX_CONSTRUCTOR_OPS
The maximum number of operations for initial start of a script.
Definition script_info.hpp:21
PrintFunc
static void PrintFunc(bool error_msg, std::string_view message)
Callback called by squirrel when a script uses "print" and for error messages.
Definition script_instance.cpp:45
_script_sl_byte
static uint8_t _script_sl_byte
Used as source/target by the script saveload code to store/load a single byte.
Definition script_instance.cpp:363
_script_byte
static const SaveLoad _script_byte[]
SaveLoad array that saves/loads exactly one byte.
Definition script_instance.cpp:366
script_instance.hpp
The ScriptInstance tracks a script.
SQUIRREL_MAX_DEPTH
static const uint SQUIRREL_MAX_DEPTH
The maximum recursive depth for items stored in the savegame.
Definition script_instance.hpp:22
script_storage.hpp
Defines ScriptStorage and includes all files required for it.
_settings_game
GameSettings _settings_game
Game settings of a running game or the scenario editor.
Definition settings.cpp:61
squirrel_register_std
void squirrel_register_std(Squirrel &engine)
Register all standard functions we want to give to a script.
Definition squirrel_std.cpp:92
StrMakeValid
static void StrMakeValid(Builder &builder, StringConsumer &consumer, StringValidationSettings settings)
Copies the valid (UTF-8) characters from consumer to the builder.
Definition string.cpp:119
GameSettings::script
ScriptSettings script
settings for scripts
Definition settings_type.h:637
PoolID< uint8_t, struct CompanyIDTag, 0xF, 0xFF >
SaveLoad
SaveLoad type struct.
Definition saveload.h:732
ScriptSettings::script_max_opcode_till_suspend
uint32_t script_max_opcode_till_suspend
max opcode calls till scripts will suspend
Definition settings_type.h:448