OpenTTD Source 20241224-master-gf74b0cf984
squirrel.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 "squirrel_std.hpp"
13#include "../fileio_func.h"
14#include "../string_func.h"
15#include "script_fatalerror.hpp"
16#include "../settings_type.h"
17#include <sqstdaux.h>
18#include <../squirrel/sqpcheader.h>
19#include <../squirrel/sqvm.h>
20#include "../core/alloc_func.hpp"
21
30/*
31 * If changing the call paths into the scripting engine, define this symbol to enable full debugging of allocations.
32 * This lets you track whether the allocator context is being switched correctly in all call paths.
33#define SCRIPT_DEBUG_ALLOCATIONS
34 */
35
46
47 static const size_t SAFE_LIMIT = 0x8000000;
48
49#ifdef SCRIPT_DEBUG_ALLOCATIONS
50 std::map<void *, size_t> allocations;
51#endif
52
53 void CheckLimit() const
54 {
55 if (this->allocated_size > this->allocation_limit) throw Script_FatalError("Maximum memory allocation exceeded");
56 }
57
67 void CheckAllocation(size_t requested_size, void *p)
68 {
69 if (this->allocated_size + requested_size > this->allocation_limit && !this->error_thrown) {
70 /* Do not allow allocating more than the allocation limit, except when an error is
71 * already as then the allocation is for throwing that error in Squirrel, the
72 * associated stack trace information and while cleaning up the AI. */
73 this->error_thrown = true;
74 std::string msg = fmt::format("Maximum memory allocation exceeded by {} bytes when allocating {} bytes",
75 this->allocated_size + requested_size - this->allocation_limit, requested_size);
76 /* Don't leak the rejected allocation. */
77 free(p);
78 throw Script_FatalError(msg);
79 }
80
81 if (p == nullptr) {
82 /* The OS did not have enough memory to allocate the object, regardless of the
83 * limit imposed by OpenTTD on the amount of memory that may be allocated. */
84 if (this->error_thrown) {
85 /* The allocation is called in the error handling of a memory allocation
86 * failure, then not being able to allocate that small amount of memory
87 * means there is no other choice than to bug out completely. */
88 MallocError(requested_size);
89 }
90
91 this->error_thrown = true;
92 std::string msg = fmt::format("Out of memory. Cannot allocate {} bytes", requested_size);
93 throw Script_FatalError(msg);
94 }
95 }
96
97 void *Malloc(SQUnsignedInteger size)
98 {
99 void *p = malloc(size);
100
101 this->CheckAllocation(size, p);
102
103 this->allocated_size += size;
104
105#ifdef SCRIPT_DEBUG_ALLOCATIONS
106 assert(p != nullptr);
107 assert(this->allocations.find(p) == this->allocations.end());
108 this->allocations[p] = size;
109#endif
110
111 return p;
112 }
113
114 void *Realloc(void *p, SQUnsignedInteger oldsize, SQUnsignedInteger size)
115 {
116 if (p == nullptr) {
117 return this->Malloc(size);
118 }
119 if (size == 0) {
120 this->Free(p, oldsize);
121 return nullptr;
122 }
123
124#ifdef SCRIPT_DEBUG_ALLOCATIONS
125 assert(this->allocations[p] == oldsize);
126 this->allocations.erase(p);
127#endif
128 /* Can't use realloc directly because memory limit check.
129 * If memory exception is thrown, the old pointer is expected
130 * to be valid for engine cleanup.
131 */
132 void *new_p = malloc(size);
133
134 this->CheckAllocation(size - oldsize, new_p);
135
136 /* Memory limit test passed, we can copy data and free old pointer. */
137 memcpy(new_p, p, std::min(oldsize, size));
138 free(p);
139
140 this->allocated_size -= oldsize;
141 this->allocated_size += size;
142
143#ifdef SCRIPT_DEBUG_ALLOCATIONS
144 assert(new_p != nullptr);
145 assert(this->allocations.find(p) == this->allocations.end());
146 this->allocations[new_p] = size;
147#endif
148
149 return new_p;
150 }
151
152 void Free(void *p, SQUnsignedInteger size)
153 {
154 if (p == nullptr) return;
155 free(p);
156 this->allocated_size -= size;
157
158#ifdef SCRIPT_DEBUG_ALLOCATIONS
159 assert(this->allocations.at(p) == size);
160 this->allocations.erase(p);
161#endif
162 }
163
165 {
166 this->allocated_size = 0;
167 this->allocation_limit = static_cast<size_t>(_settings_game.script.script_max_memory_megabytes) << 20;
168 if (this->allocation_limit == 0) this->allocation_limit = SAFE_LIMIT; // in case the setting is somehow zero
169 this->error_thrown = false;
170 }
171
173 {
174#ifdef SCRIPT_DEBUG_ALLOCATIONS
175 assert(this->allocations.empty());
176#endif
177 }
178};
179
186#include "../safeguards.h"
187
189
190/* See 3rdparty/squirrel/squirrel/sqmem.cpp for the default allocator implementation, which this overrides */
191#ifndef SQUIRREL_DEFAULT_ALLOCATOR
192void *sq_vm_malloc(SQUnsignedInteger size) { return _squirrel_allocator->Malloc(size); }
193void *sq_vm_realloc(void *p, SQUnsignedInteger oldsize, SQUnsignedInteger size) { return _squirrel_allocator->Realloc(p, oldsize, size); }
194void sq_vm_free(void *p, SQUnsignedInteger size) { _squirrel_allocator->Free(p, size); }
195#endif
196
197size_t Squirrel::GetAllocatedMemory() const noexcept
198{
199 assert(this->allocator != nullptr);
200 return this->allocator->allocated_size;
201}
202
203
204void Squirrel::CompileError(HSQUIRRELVM vm, const SQChar *desc, const SQChar *source, SQInteger line, SQInteger column)
205{
206 std::string msg = fmt::format("Error {}:{}/{}: {}", source, line, column, desc);
207
208 /* Check if we have a custom print function */
209 Squirrel *engine = (Squirrel *)sq_getforeignptr(vm);
210 engine->crashed = true;
211 SQPrintFunc *func = engine->print_func;
212 if (func == nullptr) {
213 Debug(misc, 0, "[Squirrel] Compile error: {}", msg);
214 } else {
215 (*func)(true, msg);
216 }
217}
218
219void Squirrel::ErrorPrintFunc(HSQUIRRELVM vm, const std::string &s)
220{
221 /* Check if we have a custom print function */
222 SQPrintFunc *func = ((Squirrel *)sq_getforeignptr(vm))->print_func;
223 if (func == nullptr) {
224 fmt::print(stderr, "{}", s);
225 } else {
226 (*func)(true, s);
227 }
228}
229
230void Squirrel::RunError(HSQUIRRELVM vm, const SQChar *error)
231{
232 /* Set the print function to something that prints to stderr */
233 SQPRINTFUNCTION pf = sq_getprintfunc(vm);
234 sq_setprintfunc(vm, &Squirrel::ErrorPrintFunc);
235
236 /* Check if we have a custom print function */
237 std::string msg = fmt::format("Your script made an error: {}\n", error);
238 Squirrel *engine = (Squirrel *)sq_getforeignptr(vm);
239 SQPrintFunc *func = engine->print_func;
240 if (func == nullptr) {
241 fmt::print(stderr, "{}", msg);
242 } else {
243 (*func)(true, msg);
244 }
245
246 /* Print below the error the stack, so the users knows what is happening */
247 sqstd_printcallstack(vm);
248 /* Reset the old print function */
249 sq_setprintfunc(vm, pf);
250}
251
252SQInteger Squirrel::_RunError(HSQUIRRELVM vm)
253{
254 const SQChar *sErr = nullptr;
255
256 if (sq_gettop(vm) >= 1) {
257 if (SQ_SUCCEEDED(sq_getstring(vm, -1, &sErr))) {
258 Squirrel::RunError(vm, sErr);
259 return 0;
260 }
261 }
262
263 Squirrel::RunError(vm, "unknown error");
264 return 0;
265}
266
267void Squirrel::PrintFunc(HSQUIRRELVM vm, const std::string &s)
268{
269 /* Check if we have a custom print function */
270 SQPrintFunc *func = ((Squirrel *)sq_getforeignptr(vm))->print_func;
271 if (func == nullptr) {
272 fmt::print("{}", s);
273 } else {
274 (*func)(false, s);
275 }
276}
277
278void Squirrel::AddMethod(const char *method_name, SQFUNCTION proc, uint nparam, const char *params, void *userdata, int size)
279{
280 ScriptAllocatorScope alloc_scope(this);
281
282 sq_pushstring(this->vm, method_name, -1);
283
284 if (size != 0) {
285 void *ptr = sq_newuserdata(vm, size);
286 memcpy(ptr, userdata, size);
287 }
288
289 sq_newclosure(this->vm, proc, size != 0 ? 1 : 0);
290 if (nparam != 0) sq_setparamscheck(this->vm, nparam, params);
291 sq_setnativeclosurename(this->vm, -1, method_name);
292 sq_newslot(this->vm, -3, SQFalse);
293}
294
295void Squirrel::AddConst(const char *var_name, int value)
296{
297 ScriptAllocatorScope alloc_scope(this);
298
299 sq_pushstring(this->vm, var_name, -1);
300 sq_pushinteger(this->vm, value);
301 sq_newslot(this->vm, -3, SQTrue);
302}
303
304void Squirrel::AddConst(const char *var_name, bool value)
305{
306 ScriptAllocatorScope alloc_scope(this);
307
308 sq_pushstring(this->vm, var_name, -1);
309 sq_pushbool(this->vm, value);
310 sq_newslot(this->vm, -3, SQTrue);
311}
312
313void Squirrel::AddClassBegin(const char *class_name)
314{
315 ScriptAllocatorScope alloc_scope(this);
316
317 sq_pushroottable(this->vm);
318 sq_pushstring(this->vm, class_name, -1);
319 sq_newclass(this->vm, SQFalse);
320}
321
322void Squirrel::AddClassBegin(const char *class_name, const char *parent_class)
323{
324 ScriptAllocatorScope alloc_scope(this);
325
326 sq_pushroottable(this->vm);
327 sq_pushstring(this->vm, class_name, -1);
328 sq_pushstring(this->vm, parent_class, -1);
329 if (SQ_FAILED(sq_get(this->vm, -3))) {
330 Debug(misc, 0, "[squirrel] Failed to initialize class '{}' based on parent class '{}'", class_name, parent_class);
331 Debug(misc, 0, "[squirrel] Make sure that '{}' exists before trying to define '{}'", parent_class, class_name);
332 return;
333 }
334 sq_newclass(this->vm, SQTrue);
335}
336
338{
339 ScriptAllocatorScope alloc_scope(this);
340
341 sq_newslot(vm, -3, SQFalse);
342 sq_pop(vm, 1);
343}
344
345bool Squirrel::MethodExists(HSQOBJECT instance, const char *method_name)
346{
347 assert(!this->crashed);
348 ScriptAllocatorScope alloc_scope(this);
349
350 int top = sq_gettop(this->vm);
351 /* Go to the instance-root */
352 sq_pushobject(this->vm, instance);
353 /* Find the function-name inside the script */
354 sq_pushstring(this->vm, method_name, -1);
355 if (SQ_FAILED(sq_get(this->vm, -2))) {
356 sq_settop(this->vm, top);
357 return false;
358 }
359 sq_settop(this->vm, top);
360 return true;
361}
362
363bool Squirrel::Resume(int suspend)
364{
365 assert(!this->crashed);
366 ScriptAllocatorScope alloc_scope(this);
367
368 /* Did we use more operations than we should have in the
369 * previous tick? If so, subtract that from the current run. */
370 if (this->overdrawn_ops > 0 && suspend > 0) {
371 this->overdrawn_ops -= suspend;
372 /* Do we need to wait even more? */
373 if (this->overdrawn_ops >= 0) return true;
374
375 /* We can now only run whatever is "left". */
376 suspend = -this->overdrawn_ops;
377 }
378
379 this->crashed = !sq_resumecatch(this->vm, suspend);
380 this->overdrawn_ops = -this->vm->_ops_till_suspend;
381 this->allocator->CheckLimit();
382 return this->vm->_suspended != 0;
383}
384
386{
387 assert(!this->crashed);
388 ScriptAllocatorScope alloc_scope(this);
389 sq_resumeerror(this->vm);
390}
391
393{
394 ScriptAllocatorScope alloc_scope(this);
395 sq_collectgarbage(this->vm);
396}
397
398bool Squirrel::CallMethod(HSQOBJECT instance, const char *method_name, HSQOBJECT *ret, int suspend)
399{
400 assert(!this->crashed);
401 ScriptAllocatorScope alloc_scope(this);
402 this->allocator->CheckLimit();
403
404 /* Store the stack-location for the return value. We need to
405 * restore this after saving or the stack will be corrupted
406 * if we're in the middle of a DoCommand. */
407 SQInteger last_target = this->vm->_suspended_target;
408 /* Store the current top */
409 int top = sq_gettop(this->vm);
410 /* Go to the instance-root */
411 sq_pushobject(this->vm, instance);
412 /* Find the function-name inside the script */
413 sq_pushstring(this->vm, method_name, -1);
414 if (SQ_FAILED(sq_get(this->vm, -2))) {
415 Debug(misc, 0, "[squirrel] Could not find '{}' in the class", method_name);
416 sq_settop(this->vm, top);
417 return false;
418 }
419 /* Call the method */
420 sq_pushobject(this->vm, instance);
421 if (SQ_FAILED(sq_call(this->vm, 1, ret == nullptr ? SQFalse : SQTrue, SQTrue, suspend))) return false;
422 if (ret != nullptr) sq_getstackobj(vm, -1, ret);
423 /* Reset the top, but don't do so for the script main function, as we need
424 * a correct stack when resuming. */
425 if (suspend == -1 || !this->IsSuspended()) sq_settop(this->vm, top);
426 /* Restore the return-value location. */
427 this->vm->_suspended_target = last_target;
428
429 return true;
430}
431
432bool Squirrel::CallStringMethod(HSQOBJECT instance, const char *method_name, std::string *res, int suspend)
433{
434 HSQOBJECT ret;
435 if (!this->CallMethod(instance, method_name, &ret, suspend)) return false;
436 if (ret._type != OT_STRING) return false;
437 *res = StrMakeValid(ObjectToString(&ret));
438 return true;
439}
440
441bool Squirrel::CallIntegerMethod(HSQOBJECT instance, const char *method_name, int *res, int suspend)
442{
443 HSQOBJECT ret;
444 if (!this->CallMethod(instance, method_name, &ret, suspend)) return false;
445 if (ret._type != OT_INTEGER) return false;
446 *res = ObjectToInteger(&ret);
447 return true;
448}
449
450bool Squirrel::CallBoolMethod(HSQOBJECT instance, const char *method_name, bool *res, int suspend)
451{
452 HSQOBJECT ret;
453 if (!this->CallMethod(instance, method_name, &ret, suspend)) return false;
454 if (ret._type != OT_BOOL) return false;
455 *res = ObjectToBool(&ret);
456 return true;
457}
458
459/* static */ bool Squirrel::CreateClassInstanceVM(HSQUIRRELVM vm, const std::string &class_name, void *real_instance, HSQOBJECT *instance, SQRELEASEHOOK release_hook, bool prepend_API_name)
460{
461 Squirrel *engine = (Squirrel *)sq_getforeignptr(vm);
462
463 int oldtop = sq_gettop(vm);
464
465 /* First, find the class */
466 sq_pushroottable(vm);
467
468 if (prepend_API_name) {
469 std::string prepended_class_name = engine->GetAPIName();
470 prepended_class_name += class_name;
471 sq_pushstring(vm, prepended_class_name, -1);
472 } else {
473 sq_pushstring(vm, class_name, -1);
474 }
475
476 if (SQ_FAILED(sq_get(vm, -2))) {
477 Debug(misc, 0, "[squirrel] Failed to find class by the name '{}{}'", prepend_API_name ? engine->GetAPIName() : "", class_name);
478 sq_settop(vm, oldtop);
479 return false;
480 }
481
482 /* Create the instance */
483 if (SQ_FAILED(sq_createinstance(vm, -1))) {
484 Debug(misc, 0, "[squirrel] Failed to create instance for class '{}{}'", prepend_API_name ? engine->GetAPIName() : "", class_name);
485 sq_settop(vm, oldtop);
486 return false;
487 }
488
489 if (instance != nullptr) {
490 /* Find our instance */
491 sq_getstackobj(vm, -1, instance);
492 /* Add a reference to it, so it survives for ever */
493 sq_addref(vm, instance);
494 }
495 sq_remove(vm, -2); // Class-name
496 sq_remove(vm, -2); // Root-table
497
498 /* Store it in the class */
499 sq_setinstanceup(vm, -1, real_instance);
500 if (release_hook != nullptr) sq_setreleasehook(vm, -1, release_hook);
501
502 if (instance != nullptr) sq_settop(vm, oldtop);
503
504 return true;
505}
506
507bool Squirrel::CreateClassInstance(const std::string &class_name, void *real_instance, HSQOBJECT *instance)
508{
509 ScriptAllocatorScope alloc_scope(this);
510 return Squirrel::CreateClassInstanceVM(this->vm, class_name, real_instance, instance, nullptr);
511}
512
513Squirrel::Squirrel(const char *APIName) :
514 APIName(APIName), allocator(new ScriptAllocator())
515{
516 this->Initialize();
517}
518
520{
521 ScriptAllocatorScope alloc_scope(this);
522
523 this->global_pointer = nullptr;
524 this->print_func = nullptr;
525 this->crashed = false;
526 this->overdrawn_ops = 0;
527 this->vm = sq_open(1024);
528
529 /* Handle compile-errors ourself, so we can display it nicely */
530 sq_setcompilererrorhandler(this->vm, &Squirrel::CompileError);
531 sq_notifyallexceptions(this->vm, _debug_script_level > 5);
532 /* Set a good print-function */
533 sq_setprintfunc(this->vm, &Squirrel::PrintFunc);
534 /* Handle runtime-errors ourself, so we can display it nicely */
535 sq_newclosure(this->vm, &Squirrel::_RunError, 0);
536 sq_seterrorhandler(this->vm);
537
538 /* Set the foreign pointer, so we can always find this instance from within the VM */
539 sq_setforeignptr(this->vm, this);
540
541 sq_pushroottable(this->vm);
543
544 /* Set consts table as delegate of root table, so consts/enums defined via require() are accessible */
545 sq_pushconsttable(this->vm);
546 sq_setdelegate(this->vm, -2);
547}
548
549class SQFile {
550private:
551 FileHandle file;
552 size_t size;
553 size_t pos;
554
555public:
556 SQFile(FileHandle file, size_t size) : file(std::move(file)), size(size), pos(0) {}
557
558 size_t Read(void *buf, size_t elemsize, size_t count)
559 {
560 assert(elemsize != 0);
561 if (this->pos + (elemsize * count) > this->size) {
562 count = (this->size - this->pos) / elemsize;
563 }
564 if (count == 0) return 0;
565 size_t ret = fread(buf, elemsize, count, this->file);
566 this->pos += ret * elemsize;
567 return ret;
568 }
569};
570
571static char32_t _io_file_lexfeed_ASCII(SQUserPointer file)
572{
573 unsigned char c;
574 if (((SQFile *)file)->Read(&c, sizeof(c), 1) > 0) return c;
575 return 0;
576}
577
578static char32_t _io_file_lexfeed_UTF8(SQUserPointer file)
579{
580 char buffer[5];
581
582 /* Read the first character, and get the length based on UTF-8 specs. If invalid, bail out. */
583 if (((SQFile *)file)->Read(buffer, sizeof(buffer[0]), 1) != 1) return 0;
584 uint len = Utf8EncodedCharLen(buffer[0]);
585 if (len == 0) return -1;
586
587 /* Read the remaining bits. */
588 if (len > 1 && ((SQFile *)file)->Read(buffer + 1, sizeof(buffer[0]), len - 1) != len - 1) return 0;
589
590 /* Convert the character, and when definitely invalid, bail out as well. */
591 char32_t c;
592 if (Utf8Decode(&c, buffer) != len) return -1;
593
594 return c;
595}
596
597static char32_t _io_file_lexfeed_UCS2_no_swap(SQUserPointer file)
598{
599 unsigned short c;
600 if (((SQFile *)file)->Read(&c, sizeof(c), 1) > 0) return (char32_t)c;
601 return 0;
602}
603
604static char32_t _io_file_lexfeed_UCS2_swap(SQUserPointer file)
605{
606 unsigned short c;
607 if (((SQFile *)file)->Read(&c, sizeof(c), 1) > 0) {
608 c = ((c >> 8) & 0x00FF)| ((c << 8) & 0xFF00);
609 return (char32_t)c;
610 }
611 return 0;
612}
613
614static SQInteger _io_file_read(SQUserPointer file, SQUserPointer buf, SQInteger size)
615{
616 SQInteger ret = ((SQFile *)file)->Read(buf, 1, size);
617 if (ret == 0) return -1;
618 return ret;
619}
620
621SQRESULT Squirrel::LoadFile(HSQUIRRELVM vm, const std::string &filename, SQBool printerror)
622{
623 ScriptAllocatorScope alloc_scope(this);
624
625 std::optional<FileHandle> file = std::nullopt;
626 size_t size;
627 if (strncmp(this->GetAPIName(), "AI", 2) == 0) {
628 file = FioFOpenFile(filename, "rb", AI_DIR, &size);
629 if (!file.has_value()) file = FioFOpenFile(filename, "rb", AI_LIBRARY_DIR, &size);
630 } else if (strncmp(this->GetAPIName(), "GS", 2) == 0) {
631 file = FioFOpenFile(filename, "rb", GAME_DIR, &size);
632 if (!file.has_value()) file = FioFOpenFile(filename, "rb", GAME_LIBRARY_DIR, &size);
633 } else {
634 NOT_REACHED();
635 }
636
637 if (!file.has_value()) {
638 return sq_throwerror(vm, "cannot open the file");
639 }
640 unsigned short bom = 0;
641 if (size >= 2) {
642 if (fread(&bom, 1, sizeof(bom), *file) != sizeof(bom)) return sq_throwerror(vm, "cannot read the file");;
643 }
644
645 SQLEXREADFUNC func;
646 switch (bom) {
647 case SQ_BYTECODE_STREAM_TAG: { // BYTECODE
648 if (fseek(*file, -2, SEEK_CUR) < 0) {
649 return sq_throwerror(vm, "cannot seek the file");
650 }
651
652 SQFile f(std::move(*file), size);
653 if (SQ_SUCCEEDED(sq_readclosure(vm, _io_file_read, &f))) {
654 return SQ_OK;
655 }
656 return sq_throwerror(vm, "Couldn't read bytecode");
657 }
658 case 0xFFFE:
659 /* Either this file is encoded as big-endian and we're on a little-endian
660 * machine, or this file is encoded as little-endian and we're on a big-endian
661 * machine. Either way, swap the bytes of every word we read. */
662 func = _io_file_lexfeed_UCS2_swap;
663 size -= 2; // Skip BOM
664 break;
665 case 0xFEFF:
666 func = _io_file_lexfeed_UCS2_no_swap;
667 size -= 2; // Skip BOM
668 break;
669 case 0xBBEF: // UTF-8
670 case 0xEFBB: { // UTF-8 on big-endian machine
671 /* Similarly, check the file is actually big enough to finish checking BOM */
672 if (size < 3) {
673 return sq_throwerror(vm, "I/O error");
674 }
675 unsigned char uc;
676 if (fread(&uc, 1, sizeof(uc), *file) != sizeof(uc) || uc != 0xBF) {
677 return sq_throwerror(vm, "Unrecognized encoding");
678 }
679 func = _io_file_lexfeed_UTF8;
680 size -= 3; // Skip BOM
681 break;
682 }
683 default: // ASCII
684 func = _io_file_lexfeed_ASCII;
685 /* Account for when we might not have fread'd earlier */
686 if (size >= 2 && fseek(*file, -2, SEEK_CUR) < 0) {
687 return sq_throwerror(vm, "cannot seek the file");
688 }
689 break;
690 }
691
692 SQFile f(std::move(*file), size);
693 if (SQ_SUCCEEDED(sq_compile(vm, func, &f, filename.c_str(), printerror))) {
694 return SQ_OK;
695 }
696 return SQ_ERROR;
697}
698
699bool Squirrel::LoadScript(HSQUIRRELVM vm, const std::string &script, bool in_root)
700{
701 ScriptAllocatorScope alloc_scope(this);
702
703 /* Make sure we are always in the root-table */
704 if (in_root) sq_pushroottable(vm);
705
706 SQInteger ops_left = vm->_ops_till_suspend;
707 /* Load and run the script */
708 if (SQ_SUCCEEDED(LoadFile(vm, script, SQTrue))) {
709 sq_push(vm, -2);
710 if (SQ_SUCCEEDED(sq_call(vm, 1, SQFalse, SQTrue, 100000))) {
711 sq_pop(vm, 1);
712 /* After compiling the file we want to reset the amount of opcodes. */
713 vm->_ops_till_suspend = ops_left;
714 return true;
715 }
716 }
717
718 vm->_ops_till_suspend = ops_left;
719 Debug(misc, 0, "[squirrel] Failed to compile '{}'", script);
720 return false;
721}
722
723bool Squirrel::LoadScript(const std::string &script)
724{
725 return LoadScript(this->vm, script);
726}
727
728Squirrel::~Squirrel()
729{
730 this->Uninitialize();
731}
732
734{
735 ScriptAllocatorScope alloc_scope(this);
736
737 /* Remove the delegation */
738 sq_pushroottable(this->vm);
739 sq_pushnull(this->vm);
740 sq_setdelegate(this->vm, -2);
741 sq_pop(this->vm, 1);
742
743 /* Clean up the stuff */
744 sq_pop(this->vm, 1);
745 sq_close(this->vm);
746
747 assert(this->allocator->allocated_size == 0);
748
749 /* Reset memory allocation errors. */
750 this->allocator->error_thrown = false;
751}
752
754{
755 this->Uninitialize();
756 this->Initialize();
757}
758
759void Squirrel::InsertResult(bool result)
760{
761 ScriptAllocatorScope alloc_scope(this);
762
763 sq_pushbool(this->vm, result);
764 if (this->IsSuspended()) { // Called before resuming a suspended script?
765 vm->GetAt(vm->_stackbase + vm->_suspended_target) = vm->GetUp(-1);
766 vm->Pop();
767 }
768}
769
770void Squirrel::InsertResult(int result)
771{
772 ScriptAllocatorScope alloc_scope(this);
773
774 sq_pushinteger(this->vm, result);
775 if (this->IsSuspended()) { // Called before resuming a suspended script?
776 vm->GetAt(vm->_stackbase + vm->_suspended_target) = vm->GetUp(-1);
777 vm->Pop();
778 }
779}
780
781/* static */ void Squirrel::DecreaseOps(HSQUIRRELVM vm, int ops)
782{
783 vm->DecreaseOps(ops);
784}
785
787{
788 return this->vm->_suspended != 0;
789}
790
792{
793 return this->crashed;
794}
795
797{
798 this->crashed = true;
799}
800
802{
803 ScriptAllocatorScope alloc_scope(this);
804 return sq_can_suspend(this->vm);
805}
806
808{
809 return this->vm->_ops_till_suspend;
810}
void MallocError(size_t size)
Function to exit with an error message after malloc() or calloc() have failed.
A throw-class that is given when the script made a fatal error.
void AddClassEnd()
Finishes adding a class to the global scope.
Definition squirrel.cpp:337
bool Resume(int suspend=-1)
Resume a VM when it was suspended via a throw.
Definition squirrel.cpp:363
void * global_pointer
Can be set by who ever initializes Squirrel.
Definition squirrel.hpp:30
static void ErrorPrintFunc(HSQUIRRELVM vm, const std::string &s)
If an error has to be print, this function is called.
Definition squirrel.cpp:219
void CollectGarbage()
Tell the VM to do a garbage collection run.
Definition squirrel.cpp:392
HSQUIRRELVM vm
The VirtualMachine instance for squirrel.
Definition squirrel.hpp:29
void Reset()
Completely reset the engine; start from scratch.
Definition squirrel.cpp:753
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 AddMethod(const char *method_name, SQFUNCTION proc, uint nparam=0, const char *params=nullptr, void *userdata=nullptr, int size=0)
Adds a function to the stack.
Definition squirrel.cpp:278
static void RunError(HSQUIRRELVM vm, const SQChar *error)
The RunError handler.
Definition squirrel.cpp:230
bool CanSuspend()
Are we allowed to suspend the squirrel script at this moment?
Definition squirrel.cpp:801
SQPrintFunc * print_func
Points to either nullptr, or a custom print handler.
Definition squirrel.hpp:31
static const char * ObjectToString(HSQOBJECT *ptr)
Convert a Squirrel-object to a string.
Definition squirrel.hpp:205
static void CompileError(HSQUIRRELVM vm, const SQChar *desc, const SQChar *source, SQInteger line, SQInteger column)
The CompileError handler.
Definition squirrel.cpp:204
static SQInteger _RunError(HSQUIRRELVM vm)
The internal RunError handler.
Definition squirrel.cpp:252
size_t GetAllocatedMemory() const noexcept
Get number of bytes allocated by this VM.
Definition squirrel.cpp:197
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
static void PrintFunc(HSQUIRRELVM vm, const std::string &s)
If a user runs 'print' inside a script, this function gets the params.
Definition squirrel.cpp:267
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
SQRESULT LoadFile(HSQUIRRELVM vm, const std::string &filename, SQBool printerror)
Load a file to a given VM.
Definition squirrel.cpp:621
void Initialize()
Perform all initialization steps to create the engine.
Definition squirrel.cpp:519
bool crashed
True if the squirrel script made an error.
Definition squirrel.hpp:32
static bool ObjectToBool(HSQOBJECT *ptr)
Convert a Squirrel-object to a bool.
Definition squirrel.hpp:215
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 AddClassBegin(const char *class_name)
Adds a class to the global scope.
Definition squirrel.cpp:313
int overdrawn_ops
The amount of operations we have overdrawn.
Definition squirrel.hpp:33
void Uninitialize()
Perform all the cleanups for the engine.
Definition squirrel.cpp:733
const char * GetAPIName()
Get the API name.
Definition squirrel.hpp:45
void ResumeError()
Resume the VM with an error so it prints a stack trace.
Definition squirrel.cpp:385
void AddConst(const char *var_name, int value)
Adds a const to the stack.
Definition squirrel.cpp:295
static bool CreateClassInstanceVM(HSQUIRRELVM vm, const std::string &class_name, void *real_instance, HSQOBJECT *instance, SQRELEASEHOOK release_hook, bool prepend_API_name=false)
Creates a class instance.
Definition squirrel.cpp:459
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
static int ObjectToInteger(HSQOBJECT *ptr)
Convert a Squirrel-object to an integer.
Definition squirrel.hpp:210
std::unique_ptr< ScriptAllocator > allocator
Allocator object used by this script.
Definition squirrel.hpp:35
#define Debug(category, level, format_string,...)
Ouptut a line of debugging information.
Definition debug.h:37
std::optional< FileHandle > FioFOpenFile(const std::string &filename, const char *mode, Subdirectory subdir, size_t *filesize)
Opens a OpenTTD file somewhere in a personal or global directory.
Definition fileio.cpp:242
@ AI_LIBRARY_DIR
Subdirectory for all AI libraries.
@ GAME_LIBRARY_DIR
Subdirectory for all GS libraries.
@ AI_DIR
Subdirectory for all AI files.
@ GAME_DIR
Subdirectory for all game scripts.
The definition of Script_FatalError.
GameSettings _settings_game
Game settings of a running game or the scenario editor.
Definition settings.cpp:57
ScriptAllocator * _squirrel_allocator
In the memory allocator for Squirrel we want to directly use malloc/realloc, so when the OS does not ...
Definition squirrel.cpp:188
void squirrel_register_global_std(Squirrel *engine)
Register all standard functions that are available on first startup.
defines the Squirrel Standard Function class
void free(const void *ptr)
Version of the standard free that accepts const pointers.
Definition stdafx.h:334
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
size_t Utf8Decode(char32_t *c, const char *s)
Decode and consume the next UTF-8 encoded character.
Definition string.cpp:419
int8_t Utf8EncodedCharLen(char c)
Return the length of an UTF-8 encoded value based on a single char.
ScriptSettings script
settings for scripts
In the memory allocator for Squirrel we want to directly use malloc/realloc, so when the OS does not ...
Definition squirrel.cpp:36
static const size_t SAFE_LIMIT
128 MiB, a safe choice for almost any situation
Definition squirrel.cpp:47
bool error_thrown
Whether the error has already been thrown, so to not throw secondary errors in the handling of the al...
Definition squirrel.cpp:45
size_t allocation_limit
Maximum this allocator may use before allocations fail.
Definition squirrel.cpp:38
void CheckAllocation(size_t requested_size, void *p)
Catch all validation for the allocation; did it allocate too much memory according to the allocation ...
Definition squirrel.cpp:67
size_t allocated_size
Sum of allocated data size.
Definition squirrel.cpp:37
uint32_t script_max_memory_megabytes
limit on memory a single script instance may have allocated