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/>.
8 /** @file script_instance.cpp Implementation of ScriptInstance. */
10 #include "../stdafx.h"
12 #include "../saveload/saveload.h"
14 #include "../script/squirrel_class.hpp"
15 #include "../script/squirrel_std.hpp"
17 #include "script_fatalerror.hpp"
18 #include "script_storage.hpp"
19 #include "script_info.hpp"
20 #include "script_instance.hpp"
22 #include "api/script_controller.hpp"
23 #include "api/script_error.hpp"
24 #include "api/script_event.hpp"
25 #include "api/script_log.hpp"
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"
33 #include "../safeguards.h"
35 ScriptStorage::~ScriptStorage()
37 /* Free our pointers */
38 if (event_data
!= nullptr) ScriptEventController::FreeEventPointer();
42 * Callback called by squirrel when a script uses "print" and for error messages.
43 * @param error_msg Is this an error message?
44 * @param message The actual message text.
46 static void PrintFunc(bool error_msg
, const std::string
&message
)
48 /* Convert to OpenTTD internal capable string */
49 ScriptController::Print(error_msg
, message
);
52 ScriptInstance::ScriptInstance(const char *APIName
) :
59 is_save_data_on_stack(false),
65 this->storage
= new ScriptStorage();
66 this->engine
= new Squirrel(APIName
);
67 this->engine
->SetPrintFunction(&PrintFunc
);
70 void ScriptInstance::Initialize(const std::string
&main_script
, const std::string
&instance_name
, CompanyID company
)
72 ScriptObject::ActiveInstance
active(this);
74 this->controller
= new ScriptController(company
);
76 /* Register the API functions and classes */
77 this->engine
->SetGlobalPointer(this->engine
);
80 /* Failed to register API; a message has already been logged. */
85 ScriptObject::SetAllowDoCommand(false);
86 /* Load and execute the script for this script */
87 if (main_script
== "%_dummy") {
88 this->LoadDummyScript();
89 } else if (!this->engine
->LoadScript(main_script
) || this->engine
->IsSuspended()) {
90 if (this->engine
->IsSuspended()) ScriptLog::Error("This script took too long to load script. AI is not started.");
95 /* Create the main-class */
96 this->instance
= new SQObject();
97 if (!this->engine
->CreateClassInstance(instance_name
, this->controller
, this->instance
)) {
98 /* If CreateClassInstance has returned false instance has not been
99 * registered with squirrel, so avoid trying to Release it by clearing it now */
100 delete this->instance
;
101 this->instance
= nullptr;
105 ScriptObject::SetAllowDoCommand(true);
106 } catch (Script_FatalError
&e
) {
107 this->is_dead
= true;
108 this->engine
->ThrowError(e
.GetErrorMessage());
109 this->engine
->ResumeError();
114 void ScriptInstance::RegisterAPI()
116 squirrel_register_std(this->engine
);
119 bool ScriptInstance::LoadCompatibilityScripts(const std::string
&api_version
, Subdirectory dir
)
121 std::string script_name
= fmt::format("compat_{}.nut", api_version
);
122 for (Searchpath sp
: _valid_searchpaths
) {
123 std::string buf
= FioGetDirectory(sp
, dir
);
125 if (!FileExists(buf
)) continue;
127 if (this->engine
->LoadScript(buf
)) return true;
129 ScriptLog::Error("Failed to load API compatibility script");
130 Debug(script
, 0, "Error compiling / running API compatibility script: {}", buf
);
134 ScriptLog::Warning("API compatibility script not found");
138 ScriptInstance::~ScriptInstance()
140 ScriptObject::ActiveInstance
active(this);
141 this->in_shutdown
= true;
143 if (instance
!= nullptr) this->engine
->ReleaseObject(this->instance
);
144 if (engine
!= nullptr) delete this->engine
;
145 delete this->storage
;
146 delete this->controller
;
147 delete this->instance
;
150 void ScriptInstance::Continue()
152 assert(this->suspend
< 0);
153 this->suspend
= -this->suspend
- 1;
156 void ScriptInstance::Died()
158 Debug(script
, 0, "The script died unexpectedly.");
159 this->is_dead
= true;
160 this->in_shutdown
= true;
162 this->last_allocated_memory
= this->GetAllocatedMemory(); // Update cache
164 if (this->instance
!= nullptr) this->engine
->ReleaseObject(this->instance
);
165 delete this->instance
;
167 this->instance
= nullptr;
168 this->engine
= nullptr;
171 void ScriptInstance::GameLoop()
173 ScriptObject::ActiveInstance
active(this);
175 if (this->IsDead()) return;
176 if (this->engine
->HasScriptCrashed()) {
177 /* The script crashed during saving, kill it here. */
181 if (this->is_paused
) return;
182 this->controller
->ticks
++;
184 if (this->suspend
< -1) this->suspend
++; // Multiplayer suspend, increase up to -1.
185 if (this->suspend
< 0) return; // Multiplayer suspend, wait for Continue().
186 if (--this->suspend
> 0) return; // Singleplayer suspend, decrease to 0.
188 _current_company
= ScriptObject::GetCompany();
190 /* If there is a callback to call, call that first */
191 if (this->callback
!= nullptr) {
192 if (this->is_save_data_on_stack
) {
193 sq_poptop(this->engine
->GetVM());
194 this->is_save_data_on_stack
= false;
197 this->callback(this);
198 } catch (Script_Suspend
&e
) {
199 this->suspend
= e
.GetSuspendTime();
200 this->callback
= e
.GetSuspendCallback();
207 this->callback
= nullptr;
209 if (!this->is_started
) {
211 ScriptObject::SetAllowDoCommand(false);
212 /* Run the constructor if it exists. Don't allow any DoCommands in it. */
213 if (this->engine
->MethodExists(*this->instance
, "constructor")) {
214 if (!this->engine
->CallMethod(*this->instance
, "constructor", MAX_CONSTRUCTOR_OPS
) || this->engine
->IsSuspended()) {
215 if (this->engine
->IsSuspended()) ScriptLog::Error("This script took too long to initialize. Script is not started.");
220 if (!this->CallLoad() || this->engine
->IsSuspended()) {
221 if (this->engine
->IsSuspended()) ScriptLog::Error("This script took too long in the Load function. Script is not started.");
225 ScriptObject::SetAllowDoCommand(true);
226 /* Start the script by calling Start() */
227 if (!this->engine
->CallMethod(*this->instance
, "Start", _settings_game
.script
.script_max_opcode_till_suspend
) || !this->engine
->IsSuspended()) this->Died();
228 } catch (Script_Suspend
&e
) {
229 this->suspend
= e
.GetSuspendTime();
230 this->callback
= e
.GetSuspendCallback();
231 } catch (Script_FatalError
&e
) {
232 this->is_dead
= true;
233 this->engine
->ThrowError(e
.GetErrorMessage());
234 this->engine
->ResumeError();
238 this->is_started
= true;
241 if (this->is_save_data_on_stack
) {
242 sq_poptop(this->engine
->GetVM());
243 this->is_save_data_on_stack
= false;
246 /* Continue the VM */
248 if (!this->engine
->Resume(_settings_game
.script
.script_max_opcode_till_suspend
)) this->Died();
249 } catch (Script_Suspend
&e
) {
250 this->suspend
= e
.GetSuspendTime();
251 this->callback
= e
.GetSuspendCallback();
252 } catch (Script_FatalError
&e
) {
253 this->is_dead
= true;
254 this->engine
->ThrowError(e
.GetErrorMessage());
255 this->engine
->ResumeError();
260 void ScriptInstance::CollectGarbage()
262 if (this->is_started
&& !this->IsDead()) {
263 ScriptObject::ActiveInstance
active(this);
264 this->engine
->CollectGarbage();
268 /* static */ void ScriptInstance::DoCommandReturn(ScriptInstance
*instance
)
270 instance
->engine
->InsertResult(ScriptObject::GetLastCommandRes());
273 /* static */ void ScriptInstance::DoCommandReturnVehicleID(ScriptInstance
*instance
)
275 instance
->engine
->InsertResult(EndianBufferReader::ToValue
<VehicleID
>(ScriptObject::GetLastCommandResData()));
278 /* static */ void ScriptInstance::DoCommandReturnSignID(ScriptInstance
*instance
)
280 instance
->engine
->InsertResult(EndianBufferReader::ToValue
<SignID
>(ScriptObject::GetLastCommandResData()));
283 /* static */ void ScriptInstance::DoCommandReturnGroupID(ScriptInstance
*instance
)
285 instance
->engine
->InsertResult(EndianBufferReader::ToValue
<GroupID
>(ScriptObject::GetLastCommandResData()));
288 /* static */ void ScriptInstance::DoCommandReturnGoalID(ScriptInstance
*instance
)
290 instance
->engine
->InsertResult(EndianBufferReader::ToValue
<GoalID
>(ScriptObject::GetLastCommandResData()));
293 /* static */ void ScriptInstance::DoCommandReturnStoryPageID(ScriptInstance
*instance
)
295 instance
->engine
->InsertResult(EndianBufferReader::ToValue
<StoryPageID
>(ScriptObject::GetLastCommandResData()));
298 /* static */ void ScriptInstance::DoCommandReturnStoryPageElementID(ScriptInstance
*instance
)
300 instance
->engine
->InsertResult(EndianBufferReader::ToValue
<StoryPageElementID
>(ScriptObject::GetLastCommandResData()));
303 /* static */ void ScriptInstance::DoCommandReturnLeagueTableElementID(ScriptInstance
*instance
)
305 instance
->engine
->InsertResult(EndianBufferReader::ToValue
<LeagueTableElementID
>(ScriptObject::GetLastCommandResData()));
308 /* static */ void ScriptInstance::DoCommandReturnLeagueTableID(ScriptInstance
*instance
)
310 instance
->engine
->InsertResult(EndianBufferReader::ToValue
<LeagueTableID
>(ScriptObject::GetLastCommandResData()));
314 ScriptStorage
*ScriptInstance::GetStorage()
316 return this->storage
;
319 ScriptLogTypes::LogData
&ScriptInstance::GetLogData()
321 ScriptObject::ActiveInstance
active(this);
323 return ScriptObject::GetLogData();
327 * All data is stored in the following format:
328 * First 1 byte indicating if there is a data blob at all.
329 * 1 byte indicating the type of data.
330 * The data itself, this differs per type:
331 * - integer: a binary representation of the integer (int32_t).
332 * - string: First one byte with the string length, then a 0-terminated char
333 * array. The string can't be longer than 255 bytes (including
335 * - array: All data-elements of the array are saved recursive in this
336 * format, and ended with an element of the type
337 * SQSL_ARRAY_TABLE_END.
338 * - table: All key/value pairs are saved in this format (first key 1, then
339 * value 1, then key 2, etc.). All keys and values can have an
340 * arbitrary type (as long as it is supported by the save function
341 * of course). The table is ended with an element of the type
342 * SQSL_ARRAY_TABLE_END.
343 * - bool: A single byte with value 1 representing true and 0 false.
347 static uint8_t _script_sl_byte
; ///< Used as source/target by the script saveload code to store/load a single byte.
349 /** SaveLoad array that saves/loads exactly one byte. */
350 static const SaveLoad _script_byte
[] = {
351 SLEG_VAR("type", _script_sl_byte
, SLE_UINT8
),
354 /* static */ bool ScriptInstance::SaveObject(HSQUIRRELVM vm
, SQInteger index
, int max_depth
, bool test
)
356 if (max_depth
== 0) {
357 ScriptLog::Error("Savedata can only be nested to 25 deep. No data saved."); // SQUIRREL_MAX_DEPTH = 25
361 switch (sq_gettype(vm
, index
)) {
364 _script_sl_byte
= SQSL_INT
;
365 SlObject(nullptr, _script_byte
);
368 sq_getinteger(vm
, index
, &res
);
370 int64_t value
= (int64_t)res
;
371 SlCopy(&value
, 1, SLE_INT64
);
378 _script_sl_byte
= SQSL_STRING
;
379 SlObject(nullptr, _script_byte
);
382 sq_getstring(vm
, index
, &buf
);
383 size_t len
= strlen(buf
) + 1;
385 ScriptLog::Error("Maximum string length is 254 chars. No data saved.");
389 _script_sl_byte
= (uint8_t)len
;
390 SlObject(nullptr, _script_byte
);
391 SlCopy(const_cast<char *>(buf
), len
, SLE_CHAR
);
398 _script_sl_byte
= SQSL_ARRAY
;
399 SlObject(nullptr, _script_byte
);
402 while (SQ_SUCCEEDED(sq_next(vm
, index
- 1))) {
403 /* Store the value */
404 bool res
= SaveObject(vm
, -1, max_depth
- 1, test
);
413 _script_sl_byte
= SQSL_ARRAY_TABLE_END
;
414 SlObject(nullptr, _script_byte
);
421 _script_sl_byte
= SQSL_TABLE
;
422 SlObject(nullptr, _script_byte
);
425 while (SQ_SUCCEEDED(sq_next(vm
, index
- 1))) {
426 /* Store the key + value */
427 bool res
= SaveObject(vm
, -2, max_depth
- 1, test
) && SaveObject(vm
, -1, max_depth
- 1, test
);
436 _script_sl_byte
= SQSL_ARRAY_TABLE_END
;
437 SlObject(nullptr, _script_byte
);
444 _script_sl_byte
= SQSL_BOOL
;
445 SlObject(nullptr, _script_byte
);
448 sq_getbool(vm
, index
, &res
);
450 _script_sl_byte
= res
? 1 : 0;
451 SlObject(nullptr, _script_byte
);
458 _script_sl_byte
= SQSL_NULL
;
459 SlObject(nullptr, _script_byte
);
465 ScriptLog::Error("You tried to save an unsupported type. No data saved.");
470 /* static */ void ScriptInstance::SaveEmpty()
473 SlObject(nullptr, _script_byte
);
476 void ScriptInstance::Save()
478 ScriptObject::ActiveInstance
active(this);
480 /* Don't save data if the script didn't start yet or if it crashed. */
481 if (this->engine
== nullptr || this->engine
->HasScriptCrashed()) {
486 HSQUIRRELVM vm
= this->engine
->GetVM();
487 if (this->is_save_data_on_stack
) {
489 SlObject(nullptr, _script_byte
);
490 /* Save the data that was just loaded. */
491 SaveObject(vm
, -1, SQUIRREL_MAX_DEPTH
, false);
492 } else if (!this->is_started
) {
495 } else if (this->engine
->MethodExists(*this->instance
, "Save")) {
497 /* We don't want to be interrupted during the save function. */
498 bool backup_allow
= ScriptObject::GetAllowDoCommand();
499 ScriptObject::SetAllowDoCommand(false);
501 if (!this->engine
->CallMethod(*this->instance
, "Save", &savedata
, MAX_SL_OPS
)) {
502 /* The script crashed in the Save function. We can't kill
503 * it here, but do so in the next script tick. */
505 this->engine
->CrashOccurred();
508 } catch (Script_FatalError
&e
) {
509 /* If we don't mark the script as dead here cleaning up the squirrel
510 * stack could throw Script_FatalError again. */
511 this->is_dead
= true;
512 this->engine
->ThrowError(e
.GetErrorMessage());
513 this->engine
->ResumeError();
515 /* We can't kill the script here, so mark it as crashed (not dead) and
516 * kill it in the next script tick. */
517 this->is_dead
= false;
518 this->engine
->CrashOccurred();
521 ScriptObject::SetAllowDoCommand(backup_allow
);
523 if (!sq_istable(savedata
)) {
524 ScriptLog::Error(this->engine
->IsSuspended() ? "This script took too long to Save." : "Save function should return a table.");
526 this->engine
->CrashOccurred();
529 sq_pushobject(vm
, savedata
);
530 if (SaveObject(vm
, -1, SQUIRREL_MAX_DEPTH
, true)) {
532 SlObject(nullptr, _script_byte
);
533 SaveObject(vm
, -1, SQUIRREL_MAX_DEPTH
, false);
534 this->is_save_data_on_stack
= true;
537 this->engine
->CrashOccurred();
540 ScriptLog::Warning("Save function is not implemented");
542 SlObject(nullptr, _script_byte
);
546 void ScriptInstance::Pause()
548 /* Suspend script. */
549 HSQUIRRELVM vm
= this->engine
->GetVM();
550 Squirrel::DecreaseOps(vm
, _settings_game
.script
.script_max_opcode_till_suspend
);
552 this->is_paused
= true;
555 void ScriptInstance::Unpause()
557 this->is_paused
= false;
560 bool ScriptInstance::IsPaused()
562 return this->is_paused
;
565 /* static */ bool ScriptInstance::LoadObjects(ScriptData
*data
)
567 SlObject(nullptr, _script_byte
);
568 switch (_script_sl_byte
) {
571 SlCopy(&value
, 1, IsSavegameVersionBefore(SLV_SCRIPT_INT64
) ? SLE_FILE_I32
| SLE_VAR_I64
: SLE_INT64
);
572 if (data
!= nullptr) data
->push_back((SQInteger
)value
);
577 SlObject(nullptr, _script_byte
);
578 static char buf
[std::numeric_limits
<decltype(_script_sl_byte
)>::max()];
579 SlCopy(buf
, _script_sl_byte
, SLE_CHAR
);
580 if (data
!= nullptr) data
->push_back(StrMakeValid(std::string_view(buf
, _script_sl_byte
)));
586 if (data
!= nullptr) data
->push_back((SQSaveLoadType
)_script_sl_byte
);
587 while (LoadObjects(data
));
592 SlObject(nullptr, _script_byte
);
593 if (data
!= nullptr) data
->push_back((SQBool
)(_script_sl_byte
!= 0));
598 if (data
!= nullptr) data
->push_back((SQSaveLoadType
)_script_sl_byte
);
602 case SQSL_ARRAY_TABLE_END
: {
603 if (data
!= nullptr) data
->push_back((SQSaveLoadType
)_script_sl_byte
);
607 default: SlErrorCorrupt("Invalid script data type");
611 /* static */ bool ScriptInstance::LoadObjects(HSQUIRRELVM vm
, ScriptData
*data
)
613 ScriptDataVariant value
= data
->front();
616 if (std::holds_alternative
<SQInteger
>(value
)) {
617 sq_pushinteger(vm
, std::get
<SQInteger
>(value
));
621 if (std::holds_alternative
<std::string
>(value
)) {
622 sq_pushstring(vm
, std::get
<std::string
>(value
), -1);
626 if (std::holds_alternative
<SQBool
>(value
)) {
627 sq_pushbool(vm
, std::get
<SQBool
>(value
));
631 switch (std::get
<SQSaveLoadType
>(value
)) {
634 while (LoadObjects(vm
, data
)) {
635 sq_arrayappend(vm
, -2);
636 /* The value is popped from the stack by squirrel. */
643 while (LoadObjects(vm
, data
)) {
644 LoadObjects(vm
, data
);
646 /* The key (-2) and value (-1) are popped from the stack by squirrel. */
656 case SQSL_ARRAY_TABLE_END
: {
660 default: NOT_REACHED();
664 /* static */ void ScriptInstance::LoadEmpty()
666 SlObject(nullptr, _script_byte
);
667 /* Check if there was anything saved at all. */
668 if (_script_sl_byte
== 0) return;
670 LoadObjects(nullptr);
673 /* static */ ScriptInstance::ScriptData
*ScriptInstance::Load(int version
)
680 SlObject(nullptr, _script_byte
);
681 /* Check if there was anything saved at all. */
682 if (_script_sl_byte
== 0) return nullptr;
684 ScriptData
*data
= new ScriptData();
685 data
->push_back((SQInteger
)version
);
690 void ScriptInstance::LoadOnStack(ScriptData
*data
)
692 ScriptObject::ActiveInstance
active(this);
694 if (this->IsDead() || data
== nullptr) return;
696 HSQUIRRELVM vm
= this->engine
->GetVM();
698 ScriptDataVariant version
= data
->front();
700 SQInteger top
= sq_gettop(vm
);
702 sq_pushinteger(vm
, std::get
<SQInteger
>(version
));
703 LoadObjects(vm
, data
);
704 this->is_save_data_on_stack
= true;
705 } catch (Script_FatalError
&e
) {
706 ScriptLog::Warning(fmt::format("Loading failed: {}", e
.GetErrorMessage()));
707 /* Discard partially loaded savegame data and version. */
712 bool ScriptInstance::CallLoad()
714 HSQUIRRELVM vm
= this->engine
->GetVM();
715 /* Is there save data that we should load? */
716 if (!this->is_save_data_on_stack
) return true;
717 /* Whatever happens, after CallLoad the savegame data is removed from the stack. */
718 this->is_save_data_on_stack
= false;
720 if (!this->engine
->MethodExists(*this->instance
, "Load")) {
721 ScriptLog::Warning("Loading failed: there was data for the script to load, but the script does not have a Load() function.");
723 /* Pop the savegame data and version. */
728 /* Go to the instance-root */
729 sq_pushobject(vm
, *this->instance
);
730 /* Find the function-name inside the script */
731 sq_pushstring(vm
, "Load", -1);
732 /* Change the "Load" string in a function pointer */
734 /* Push the main instance as "this" object */
735 sq_pushobject(vm
, *this->instance
);
736 /* Push the version data and savegame data as arguments */
740 /* Call the script load function. sq_call removes the arguments (but not the
741 * function pointer) from the stack. */
742 if (SQ_FAILED(sq_call(vm
, 3, SQFalse
, SQFalse
, MAX_SL_OPS
))) return false;
744 /* Pop 1) The version, 2) the savegame data, 3) the object instance, 4) the function pointer. */
749 SQInteger
ScriptInstance::GetOpsTillSuspend()
751 return this->engine
->GetOpsTillSuspend();
754 bool ScriptInstance::DoCommandCallback(const CommandCost
&result
, const CommandDataBuffer
&data
, CommandDataBuffer result_data
, Commands cmd
)
756 ScriptObject::ActiveInstance
active(this);
758 if (!ScriptObject::CheckLastCommand(data
, cmd
)) {
759 Debug(script
, 1, "DoCommandCallback terminating a script, last command does not match expected command");
763 ScriptObject::SetLastCommandRes(result
.Succeeded());
764 ScriptObject::SetLastCommandResData(std::move(result_data
));
766 if (result
.Failed()) {
767 ScriptObject::SetLastError(ScriptError::StringToError(result
.GetErrorMessage()));
769 ScriptObject::IncreaseDoCommandCosts(result
.GetCost());
770 ScriptObject::SetLastCost(result
.GetCost());
773 ScriptObject::SetLastCommand({}, CMD_END
);
778 void ScriptInstance::InsertEvent(class ScriptEvent
*event
)
780 ScriptObject::ActiveInstance
active(this);
782 ScriptEventController::InsertEvent(event
);
785 size_t ScriptInstance::GetAllocatedMemory() const
787 if (this->engine
== nullptr) return this->last_allocated_memory
;
788 return this->engine
->GetAllocatedMemory();
791 void ScriptInstance::ReleaseSQObject(HSQOBJECT
*obj
)
793 if (!this->in_shutdown
) this->engine
->ReleaseObject(obj
);