OpenTTD Source  20241108-master-g80f628063a
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 
35 ScriptStorage::~ScriptStorage()
36 {
37  /* Free our pointers */
38  if (event_data != nullptr) ScriptEventController::FreeEventPointer();
39 }
40 
46 static void PrintFunc(bool error_msg, const std::string &message)
47 {
48  /* Convert to OpenTTD internal capable string */
49  ScriptController::Print(error_msg, message);
50 }
51 
52 ScriptInstance::ScriptInstance(const char *APIName) :
53  engine(nullptr),
54  controller(nullptr),
55  storage(nullptr),
56  instance(nullptr),
57  is_started(false),
58  is_dead(false),
59  is_save_data_on_stack(false),
60  suspend(0),
61  is_paused(false),
62  in_shutdown(false),
63  callback(nullptr)
64 {
65  this->storage = new ScriptStorage();
66  this->engine = new Squirrel(APIName);
68 }
69 
70 void ScriptInstance::Initialize(const std::string &main_script, const std::string &instance_name, CompanyID company)
71 {
72  ScriptObject::ActiveInstance active(this);
73 
74  this->controller = new ScriptController(company);
75 
76  /* Register the API functions and classes */
77  this->engine->SetGlobalPointer(this->engine);
78  this->RegisterAPI();
79  if (this->IsDead()) {
80  /* Failed to register API; a message has already been logged. */
81  return;
82  }
83 
84  try {
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.");
91  this->Died();
92  return;
93  }
94 
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;
102  this->Died();
103  return;
104  }
105  ScriptObject::SetAllowDoCommand(true);
106  } catch (Script_FatalError &e) {
107  this->is_dead = true;
108  this->engine->ThrowError(e.GetErrorMessage());
109  this->engine->ResumeError();
110  this->Died();
111  }
112 }
113 
115 {
117 }
118 
119 bool ScriptInstance::LoadCompatibilityScripts(const std::string &api_version, Subdirectory dir)
120 {
121  std::string script_name = fmt::format("compat_{}.nut", api_version);
122  for (Searchpath sp : _valid_searchpaths) {
123  std::string buf = FioGetDirectory(sp, dir);
124  buf += script_name;
125  if (!FileExists(buf)) continue;
126 
127  if (this->engine->LoadScript(buf)) return true;
128 
129  ScriptLog::Error("Failed to load API compatibility script");
130  Debug(script, 0, "Error compiling / running API compatibility script: {}", buf);
131  return false;
132  }
133 
134  ScriptLog::Warning("API compatibility script not found");
135  return true;
136 }
137 
138 ScriptInstance::~ScriptInstance()
139 {
140  ScriptObject::ActiveInstance active(this);
141  this->in_shutdown = true;
142 
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;
148 }
149 
151 {
152  assert(this->suspend < 0);
153  this->suspend = -this->suspend - 1;
154 }
155 
157 {
158  Debug(script, 0, "The script died unexpectedly.");
159  this->is_dead = true;
160  this->in_shutdown = true;
161 
162  this->last_allocated_memory = this->GetAllocatedMemory(); // Update cache
163 
164  if (this->instance != nullptr) this->engine->ReleaseObject(this->instance);
165  delete this->instance;
166  delete this->engine;
167  this->instance = nullptr;
168  this->engine = nullptr;
169 }
170 
172 {
173  ScriptObject::ActiveInstance active(this);
174 
175  if (this->IsDead()) return;
176  if (this->engine->HasScriptCrashed()) {
177  /* The script crashed during saving, kill it here. */
178  this->Died();
179  return;
180  }
181  if (this->is_paused) return;
182  this->controller->ticks++;
183 
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.
187 
188  _current_company = ScriptObject::GetCompany();
189 
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;
195  }
196  try {
197  this->callback(this);
198  } catch (Script_Suspend &e) {
199  this->suspend = e.GetSuspendTime();
200  this->callback = e.GetSuspendCallback();
201 
202  return;
203  }
204  }
205 
206  this->suspend = 0;
207  this->callback = nullptr;
208 
209  if (!this->is_started) {
210  try {
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.");
216  this->Died();
217  return;
218  }
219  }
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.");
222  this->Died();
223  return;
224  }
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();
235  this->Died();
236  }
237 
238  this->is_started = true;
239  return;
240  }
241  if (this->is_save_data_on_stack) {
242  sq_poptop(this->engine->GetVM());
243  this->is_save_data_on_stack = false;
244  }
245 
246  /* Continue the VM */
247  try {
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();
256  this->Died();
257  }
258 }
259 
261 {
262  if (this->is_started && !this->IsDead()) {
263  ScriptObject::ActiveInstance active(this);
264  this->engine->CollectGarbage();
265  }
266 }
267 
269 {
270  instance->engine->InsertResult(ScriptObject::GetLastCommandRes());
271 }
272 
274 {
275  instance->engine->InsertResult(EndianBufferReader::ToValue<VehicleID>(ScriptObject::GetLastCommandResData()));
276 }
277 
279 {
280  instance->engine->InsertResult(EndianBufferReader::ToValue<SignID>(ScriptObject::GetLastCommandResData()));
281 }
282 
284 {
285  instance->engine->InsertResult(EndianBufferReader::ToValue<GroupID>(ScriptObject::GetLastCommandResData()));
286 }
287 
289 {
290  instance->engine->InsertResult(EndianBufferReader::ToValue<GoalID>(ScriptObject::GetLastCommandResData()));
291 }
292 
294 {
295  instance->engine->InsertResult(EndianBufferReader::ToValue<StoryPageID>(ScriptObject::GetLastCommandResData()));
296 }
297 
299 {
300  instance->engine->InsertResult(EndianBufferReader::ToValue<StoryPageElementID>(ScriptObject::GetLastCommandResData()));
301 }
302 
304 {
305  instance->engine->InsertResult(EndianBufferReader::ToValue<LeagueTableElementID>(ScriptObject::GetLastCommandResData()));
306 }
307 
309 {
310  instance->engine->InsertResult(EndianBufferReader::ToValue<LeagueTableID>(ScriptObject::GetLastCommandResData()));
311 }
312 
313 
315 {
316  return this->storage;
317 }
318 
319 ScriptLogTypes::LogData &ScriptInstance::GetLogData()
320 {
321  ScriptObject::ActiveInstance active(this);
322 
323  return ScriptObject::GetLogData();
324 }
325 
326 /*
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
334  * terminating '\0').
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.
344  * - null: No data.
345  */
346 
347 static uint8_t _script_sl_byte;
348 
350 static const SaveLoad _script_byte[] = {
351  SLEG_VAR("type", _script_sl_byte, SLE_UINT8),
352 };
353 
354 /* static */ bool ScriptInstance::SaveObject(HSQUIRRELVM vm, SQInteger index, int max_depth, bool test)
355 {
356  if (max_depth == 0) {
357  ScriptLog::Error("Savedata can only be nested to 25 deep. No data saved."); // SQUIRREL_MAX_DEPTH = 25
358  return false;
359  }
360 
361  switch (sq_gettype(vm, index)) {
362  case OT_INTEGER: {
363  if (!test) {
365  SlObject(nullptr, _script_byte);
366  }
367  SQInteger res;
368  sq_getinteger(vm, index, &res);
369  if (!test) {
370  int64_t value = (int64_t)res;
371  SlCopy(&value, 1, SLE_INT64);
372  }
373  return true;
374  }
375 
376  case OT_STRING: {
377  if (!test) {
379  SlObject(nullptr, _script_byte);
380  }
381  const SQChar *buf;
382  sq_getstring(vm, index, &buf);
383  size_t len = strlen(buf) + 1;
384  if (len >= 255) {
385  ScriptLog::Error("Maximum string length is 254 chars. No data saved.");
386  return false;
387  }
388  if (!test) {
389  _script_sl_byte = (uint8_t)len;
390  SlObject(nullptr, _script_byte);
391  SlCopy(const_cast<char *>(buf), len, SLE_CHAR);
392  }
393  return true;
394  }
395 
396  case OT_ARRAY: {
397  if (!test) {
399  SlObject(nullptr, _script_byte);
400  }
401  sq_pushnull(vm);
402  while (SQ_SUCCEEDED(sq_next(vm, index - 1))) {
403  /* Store the value */
404  bool res = SaveObject(vm, -1, max_depth - 1, test);
405  sq_pop(vm, 2);
406  if (!res) {
407  sq_pop(vm, 1);
408  return false;
409  }
410  }
411  sq_pop(vm, 1);
412  if (!test) {
414  SlObject(nullptr, _script_byte);
415  }
416  return true;
417  }
418 
419  case OT_TABLE: {
420  if (!test) {
422  SlObject(nullptr, _script_byte);
423  }
424  sq_pushnull(vm);
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);
428  sq_pop(vm, 2);
429  if (!res) {
430  sq_pop(vm, 1);
431  return false;
432  }
433  }
434  sq_pop(vm, 1);
435  if (!test) {
437  SlObject(nullptr, _script_byte);
438  }
439  return true;
440  }
441 
442  case OT_BOOL: {
443  if (!test) {
445  SlObject(nullptr, _script_byte);
446  }
447  SQBool res;
448  sq_getbool(vm, index, &res);
449  if (!test) {
450  _script_sl_byte = res ? 1 : 0;
451  SlObject(nullptr, _script_byte);
452  }
453  return true;
454  }
455 
456  case OT_NULL: {
457  if (!test) {
459  SlObject(nullptr, _script_byte);
460  }
461  return true;
462  }
463 
464  default:
465  ScriptLog::Error("You tried to save an unsupported type. No data saved.");
466  return false;
467  }
468 }
469 
470 /* static */ void ScriptInstance::SaveEmpty()
471 {
472  _script_sl_byte = 0;
473  SlObject(nullptr, _script_byte);
474 }
475 
477 {
478  ScriptObject::ActiveInstance active(this);
479 
480  /* Don't save data if the script didn't start yet or if it crashed. */
481  if (this->engine == nullptr || this->engine->HasScriptCrashed()) {
482  SaveEmpty();
483  return;
484  }
485 
486  HSQUIRRELVM vm = this->engine->GetVM();
487  if (this->is_save_data_on_stack) {
488  _script_sl_byte = 1;
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) {
493  SaveEmpty();
494  return;
495  } else if (this->engine->MethodExists(*this->instance, "Save")) {
496  HSQOBJECT savedata;
497  /* We don't want to be interrupted during the save function. */
498  bool backup_allow = ScriptObject::GetAllowDoCommand();
499  ScriptObject::SetAllowDoCommand(false);
500  try {
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. */
504  SaveEmpty();
505  this->engine->CrashOccurred();
506  return;
507  }
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();
514  SaveEmpty();
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();
519  return;
520  }
521  ScriptObject::SetAllowDoCommand(backup_allow);
522 
523  if (!sq_istable(savedata)) {
524  ScriptLog::Error(this->engine->IsSuspended() ? "This script took too long to Save." : "Save function should return a table.");
525  SaveEmpty();
526  this->engine->CrashOccurred();
527  return;
528  }
529  sq_pushobject(vm, savedata);
530  if (SaveObject(vm, -1, SQUIRREL_MAX_DEPTH, true)) {
531  _script_sl_byte = 1;
532  SlObject(nullptr, _script_byte);
533  SaveObject(vm, -1, SQUIRREL_MAX_DEPTH, false);
534  this->is_save_data_on_stack = true;
535  } else {
536  SaveEmpty();
537  this->engine->CrashOccurred();
538  }
539  } else {
540  ScriptLog::Warning("Save function is not implemented");
541  _script_sl_byte = 0;
542  SlObject(nullptr, _script_byte);
543  }
544 }
545 
547 {
548  /* Suspend script. */
549  HSQUIRRELVM vm = this->engine->GetVM();
551 
552  this->is_paused = true;
553 }
554 
556 {
557  this->is_paused = false;
558 }
559 
561 {
562  return this->is_paused;
563 }
564 
565 /* static */ bool ScriptInstance::LoadObjects(ScriptData *data)
566 {
567  SlObject(nullptr, _script_byte);
568  switch (_script_sl_byte) {
569  case SQSL_INT: {
570  int64_t value;
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);
573  return true;
574  }
575 
576  case SQSL_STRING: {
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)));
581  return true;
582  }
583 
584  case SQSL_ARRAY:
585  case SQSL_TABLE: {
586  if (data != nullptr) data->push_back((SQSaveLoadType)_script_sl_byte);
587  while (LoadObjects(data));
588  return true;
589  }
590 
591  case SQSL_BOOL: {
592  SlObject(nullptr, _script_byte);
593  if (data != nullptr) data->push_back((SQBool)(_script_sl_byte != 0));
594  return true;
595  }
596 
597  case SQSL_NULL: {
598  if (data != nullptr) data->push_back((SQSaveLoadType)_script_sl_byte);
599  return true;
600  }
601 
602  case SQSL_ARRAY_TABLE_END: {
603  if (data != nullptr) data->push_back((SQSaveLoadType)_script_sl_byte);
604  return false;
605  }
606 
607  default: SlErrorCorrupt("Invalid script data type");
608  }
609 }
610 
611 /* static */ bool ScriptInstance::LoadObjects(HSQUIRRELVM vm, ScriptData *data)
612 {
613  ScriptDataVariant value = data->front();
614  data->pop_front();
615 
616  if (std::holds_alternative<SQInteger>(value)) {
617  sq_pushinteger(vm, std::get<SQInteger>(value));
618  return true;
619  }
620 
621  if (std::holds_alternative<std::string>(value)) {
622  sq_pushstring(vm, std::get<std::string>(value), -1);
623  return true;
624  }
625 
626  if (std::holds_alternative<SQBool>(value)) {
627  sq_pushbool(vm, std::get<SQBool>(value));
628  return true;
629  }
630 
631  switch (std::get<SQSaveLoadType>(value)) {
632  case SQSL_ARRAY: {
633  sq_newarray(vm, 0);
634  while (LoadObjects(vm, data)) {
635  sq_arrayappend(vm, -2);
636  /* The value is popped from the stack by squirrel. */
637  }
638  return true;
639  }
640 
641  case SQSL_TABLE: {
642  sq_newtable(vm);
643  while (LoadObjects(vm, data)) {
644  LoadObjects(vm, data);
645  sq_rawset(vm, -3);
646  /* The key (-2) and value (-1) are popped from the stack by squirrel. */
647  }
648  return true;
649  }
650 
651  case SQSL_NULL: {
652  sq_pushnull(vm);
653  return true;
654  }
655 
656  case SQSL_ARRAY_TABLE_END: {
657  return false;
658  }
659 
660  default: NOT_REACHED();
661  }
662 }
663 
664 /* static */ void ScriptInstance::LoadEmpty()
665 {
666  SlObject(nullptr, _script_byte);
667  /* Check if there was anything saved at all. */
668  if (_script_sl_byte == 0) return;
669 
670  LoadObjects(nullptr);
671 }
672 
673 /* static */ ScriptInstance::ScriptData *ScriptInstance::Load(int version)
674 {
675  if (version == -1) {
676  LoadEmpty();
677  return nullptr;
678  }
679 
680  SlObject(nullptr, _script_byte);
681  /* Check if there was anything saved at all. */
682  if (_script_sl_byte == 0) return nullptr;
683 
684  ScriptData *data = new ScriptData();
685  data->push_back((SQInteger)version);
686  LoadObjects(data);
687  return data;
688 }
689 
690 void ScriptInstance::LoadOnStack(ScriptData *data)
691 {
692  ScriptObject::ActiveInstance active(this);
693 
694  if (this->IsDead() || data == nullptr) return;
695 
696  HSQUIRRELVM vm = this->engine->GetVM();
697 
698  ScriptDataVariant version = data->front();
699  data->pop_front();
700  SQInteger top = sq_gettop(vm);
701  try {
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. */
708  sq_settop(vm, top);
709  }
710 }
711 
713 {
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;
719 
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.");
722 
723  /* Pop the savegame data and version. */
724  sq_pop(vm, 2);
725  return true;
726  }
727 
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 */
733  sq_get(vm, -2);
734  /* Push the main instance as "this" object */
735  sq_pushobject(vm, *this->instance);
736  /* Push the version data and savegame data as arguments */
737  sq_push(vm, -5);
738  sq_push(vm, -5);
739 
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;
743 
744  /* Pop 1) The version, 2) the savegame data, 3) the object instance, 4) the function pointer. */
745  sq_pop(vm, 4);
746  return true;
747 }
748 
750 {
751  return this->engine->GetOpsTillSuspend();
752 }
753 
755 {
756  ScriptObject::ActiveInstance active(this);
757 
758  if (!ScriptObject::CheckLastCommand(data, cmd)) {
759  Debug(script, 1, "DoCommandCallback terminating a script, last command does not match expected command");
760  return false;
761  }
762 
763  ScriptObject::SetLastCommandRes(result.Succeeded());
764  ScriptObject::SetLastCommandResData(std::move(result_data));
765 
766  if (result.Failed()) {
767  ScriptObject::SetLastError(ScriptError::StringToError(result.GetErrorMessage()));
768  } else {
769  ScriptObject::IncreaseDoCommandCosts(result.GetCost());
770  ScriptObject::SetLastCost(result.GetCost());
771  }
772 
773  ScriptObject::SetLastCommand({}, CMD_END);
774 
775  return true;
776 }
777 
778 void ScriptInstance::InsertEvent(class ScriptEvent *event)
779 {
780  ScriptObject::ActiveInstance active(this);
781 
782  ScriptEventController::InsertEvent(event);
783 }
784 
785 size_t ScriptInstance::GetAllocatedMemory() const
786 {
787  if (this->engine == nullptr) return this->last_allocated_memory;
788  return this->engine->GetAllocatedMemory();
789 }
790 
792 {
793  if (!this->in_shutdown) this->engine->ReleaseObject(obj);
794 }
Common return value for all commands.
Definition: command_type.h:23
bool Succeeded() const
Did this command succeed?
Definition: command_type.h:162
Money GetCost() const
The costs as made up to this moment.
Definition: command_type.h:83
bool Failed() const
Did this command fail?
Definition: command_type.h:171
StringID GetErrorMessage() const
Returns the error message of a command.
Definition: command_type.h:142
Runtime information about a script like a pointer to the squirrel vm and the current state.
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.
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 DoCommandReturnLeagueTableElementID(ScriptInstance *instance)
Return a LeagueTableElementID reply for a DoCommand.
void CollectGarbage()
Let the VM collect any garbage.
class Squirrel * engine
A wrapper around the squirrel vm.
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.
static void DoCommandReturn(ScriptInstance *instance)
Return a true/false reply for a DoCommand.
static void SaveEmpty()
Don't save any data in the savegame.
static void DoCommandReturnStoryPageElementID(ScriptInstance *instance)
Return a StoryPageElementID 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.
bool LoadCompatibilityScripts(const std::string &api_version, Subdirectory dir)
Load squirrel scripts to emulate an older API.
static void DoCommandReturnVehicleID(ScriptInstance *instance)
Return a VehicleID reply for a DoCommand.
static void DoCommandReturnGoalID(ScriptInstance *instance)
Return a GoalID reply for a DoCommand.
static void DoCommandReturnGroupID(ScriptInstance *instance)
Return a GroupID reply for a DoCommand.
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.
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.
int suspend
The amount of ticks to suspend this script before it's allowed to continue.
static void DoCommandReturnLeagueTableID(ScriptInstance *instance)
Return a LeagueTableID reply for a DoCommand.
class ScriptController * controller
The script main class.
static void DoCommandReturnSignID(ScriptInstance *instance)
Return a SignID reply for a DoCommand.
SQInteger GetOpsTillSuspend()
Get the number of operations the script can execute before being suspended.
bool is_started
Is the scripts constructor executed?
static void DoCommandReturnStoryPageID(ScriptInstance *instance)
Return a StoryPageID reply for a DoCommand.
class ScriptStorage * storage
Some global information for each running script.
ScriptInstance(const char *APIName)
Create a new script.
bool is_paused
Is the script paused? (a paused script will not be executed until unpaused)
The storage for each script.
void * event_data
Pointer to the event data storage.
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:363
void SetPrintFunction(SQPrintFunc *func)
Set a custom print function, so you can handle outputs from SQ yourself.
Definition: squirrel.hpp:231
void CollectGarbage()
Tell the VM to do a garbage collection run.
Definition: squirrel.cpp:392
SQInteger GetOpsTillSuspend()
How many operations can we execute till suspension?
Definition: squirrel.cpp:807
bool LoadScript(const std::string &script)
Load a script.
Definition: squirrel.cpp:723
bool IsSuspended()
Did the squirrel code suspend or return normally.
Definition: squirrel.cpp:786
void ReleaseObject(HSQOBJECT *ptr)
Release a SQ object.
Definition: squirrel.hpp:241
void ThrowError(const std::string_view error)
Throw a Squirrel error that will be nicely displayed to the user.
Definition: squirrel.hpp:236
size_t GetAllocatedMemory() const noexcept
Get number of bytes allocated by this VM.
Definition: squirrel.cpp:197
void SetGlobalPointer(void *ptr)
Sets a pointer in the VM that is reachable from where ever you are in SQ.
Definition: squirrel.hpp:221
static void DecreaseOps(HSQUIRRELVM vm, int amount)
Tell the VM to remove amount ops from the number of ops till suspend.
Definition: squirrel.cpp:781
bool HasScriptCrashed()
Find out if the squirrel script made an error before.
Definition: squirrel.cpp:791
void CrashOccurred()
Set the script status to crashed.
Definition: squirrel.cpp:796
HSQUIRRELVM GetVM()
Get the squirrel VM.
Definition: squirrel.hpp:80
bool CallMethod(HSQOBJECT instance, const char *method_name, HSQOBJECT *ret, int suspend)
Call a method of an instance, in various flavors.
Definition: squirrel.cpp:398
void ResumeError()
Resume the VM with an error so it prints a stack trace.
Definition: squirrel.cpp:385
bool MethodExists(HSQOBJECT instance, const char *method_name)
Check if a method exists in an instance.
Definition: squirrel.cpp:345
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:507
std::vector< uint8_t > CommandDataBuffer
Storage buffer for serialized command data.
Definition: command_type.h:470
Commands
List of commands.
Definition: command_type.h:187
@ CMD_END
Must ALWAYS be on the end of this list!! (period)
Definition: command_type.h:366
CompanyID _current_company
Company currently doing an action.
Definition: company_cmd.cpp:53
Owner
Enum for all companies/owners.
Definition: company_type.h:18
#define Debug(category, level, format_string,...)
Ouptut a line of debugging information.
Definition: debug.h:37
bool FileExists(const std::string &filename)
Test whether the given filename exists.
Definition: fileio.cpp:132
Searchpath
Types of searchpaths OpenTTD might use.
Definition: fileio_type.h:139
Subdirectory
The different kinds of subdirectories OpenTTD uses.
Definition: fileio_type.h:115
void SlCopy(void *object, size_t length, VarType conv)
Copy a list of SL_VARs to/from a savegame.
Definition: saveload.cpp:1029
void SlErrorCorrupt(const std::string &msg)
Error handler for corrupt savegames.
Definition: saveload.cpp:351
void SlObject(void *object, const SaveLoadTable &slt)
Main SaveLoad function.
Definition: saveload.cpp:1697
#define SLEG_VAR(name, variable, type)
Storage of a global variable in every savegame version.
Definition: saveload.h:1151
bool IsSavegameVersionBefore(SaveLoadVersion major, uint8_t minor=0)
Checks whether the savegame is below major.
Definition: saveload.h:1227
@ 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.
Definition: script_info.hpp:19
static const int MAX_CONSTRUCTOR_OPS
The maximum number of operations for initial start of a script.
Definition: script_info.hpp:21
static uint8_t _script_sl_byte
Used as source/target by the script saveload code to store/load a single byte.
static void PrintFunc(bool error_msg, const std::string &message)
Callback called by squirrel when a script uses "print" and for error messages.
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:57
void squirrel_register_std(Squirrel *engine)
Register all standard functions we want to give to a script.
static void StrMakeValid(T &dst, const char *str, const char *last, StringValidationSettings settings)
Copies the valid (UTF-8) characters from str up to last to the dst.
Definition: string.cpp:107
ScriptSettings script
settings for scripts
SaveLoad type struct.
Definition: saveload.h:711
uint32_t script_max_opcode_till_suspend
max opcode calls till scripts will suspend