Update: Translations from eints
[openttd-github.git] / src / script / squirrel.cpp
blobdf0cf44e511c5080c883537a2cedd1ff00e46bf2
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 */
8 /** @file squirrel.cpp the implementation of the Squirrel class. It handles all Squirrel-stuff and gives a nice API back to work with. */
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"
22 /**
23 * In the memory allocator for Squirrel we want to directly use malloc/realloc, so when the OS
24 * does not have enough memory the game does not go into unrecoverable error mode and kill the
25 * whole game, but rather let the AI die though then we need to circumvent MallocT/ReallocT.
27 * So no #include "../safeguards.h" here as is required, but after the allocator's implementation.
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
36 struct ScriptAllocator {
37 size_t allocated_size; ///< Sum of allocated data size
38 size_t allocation_limit; ///< Maximum this allocator may use before allocations fail
39 /**
40 * Whether the error has already been thrown, so to not throw secondary errors in
41 * the handling of the allocation error. This as the handling of the error will
42 * throw a Squirrel error so the Squirrel stack can be dumped, however that gets
43 * allocated by this allocator and then you might end up in an infinite loop.
45 bool error_thrown;
47 static const size_t SAFE_LIMIT = 0x8000000; ///< 128 MiB, a safe choice for almost any situation
49 #ifdef SCRIPT_DEBUG_ALLOCATIONS
50 std::map<void *, size_t> allocations;
51 #endif
53 void CheckLimit() const
55 if (this->allocated_size > this->allocation_limit) throw Script_FatalError("Maximum memory allocation exceeded");
58 /**
59 * Catch all validation for the allocation; did it allocate too much memory according
60 * to the allocation limit or did the allocation at the OS level maybe fail? In those
61 * error situations a Script_FatalError is thrown, but once that has been done further
62 * allocations are allowed to make it possible for Squirrel to throw the error and
63 * clean everything up.
64 * @param requested_size The requested size that was requested to be allocated.
65 * @param p The pointer to the allocated object, or null if allocation failed.
67 void CheckAllocation(size_t requested_size, void *p)
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);
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);
91 this->error_thrown = true;
92 std::string msg = fmt::format("Out of memory. Cannot allocate {} bytes", requested_size);
93 throw Script_FatalError(msg);
97 void *Malloc(SQUnsignedInteger size)
99 void *p = malloc(size);
101 this->CheckAllocation(size, p);
103 this->allocated_size += size;
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
111 return p;
114 void *Realloc(void *p, SQUnsignedInteger oldsize, SQUnsignedInteger size)
116 if (p == nullptr) {
117 return this->Malloc(size);
119 if (size == 0) {
120 this->Free(p, oldsize);
121 return nullptr;
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.
132 void *new_p = malloc(size);
134 this->CheckAllocation(size - oldsize, new_p);
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);
140 this->allocated_size -= oldsize;
141 this->allocated_size += size;
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
149 return new_p;
152 void Free(void *p, SQUnsignedInteger size)
154 if (p == nullptr) return;
155 free(p);
156 this->allocated_size -= size;
158 #ifdef SCRIPT_DEBUG_ALLOCATIONS
159 assert(this->allocations.at(p) == size);
160 this->allocations.erase(p);
161 #endif
164 ScriptAllocator()
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;
172 ~ScriptAllocator()
174 #ifdef SCRIPT_DEBUG_ALLOCATIONS
175 assert(this->allocations.empty());
176 #endif
181 * In the memory allocator for Squirrel we want to directly use malloc/realloc, so when the OS
182 * does not have enough memory the game does not go into unrecoverable error mode and kill the
183 * whole game, but rather let the AI die though then we need to circumvent MallocT/ReallocT.
184 * For the rest of this code, the safeguards should be in place though!
186 #include "../safeguards.h"
188 ScriptAllocator *_squirrel_allocator = nullptr;
190 /* See 3rdparty/squirrel/squirrel/sqmem.cpp for the default allocator implementation, which this overrides */
191 #ifndef SQUIRREL_DEFAULT_ALLOCATOR
192 void *sq_vm_malloc(SQUnsignedInteger size) { return _squirrel_allocator->Malloc(size); }
193 void *sq_vm_realloc(void *p, SQUnsignedInteger oldsize, SQUnsignedInteger size) { return _squirrel_allocator->Realloc(p, oldsize, size); }
194 void sq_vm_free(void *p, SQUnsignedInteger size) { _squirrel_allocator->Free(p, size); }
195 #endif
197 size_t Squirrel::GetAllocatedMemory() const noexcept
199 assert(this->allocator != nullptr);
200 return this->allocator->allocated_size;
204 void Squirrel::CompileError(HSQUIRRELVM vm, const SQChar *desc, const SQChar *source, SQInteger line, SQInteger column)
206 std::string msg = fmt::format("Error {}:{}/{}: {}", source, line, column, desc);
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);
219 void Squirrel::ErrorPrintFunc(HSQUIRRELVM vm, const std::string &s)
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);
230 void Squirrel::RunError(HSQUIRRELVM vm, const SQChar *error)
232 /* Set the print function to something that prints to stderr */
233 SQPRINTFUNCTION pf = sq_getprintfunc(vm);
234 sq_setprintfunc(vm, &Squirrel::ErrorPrintFunc);
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);
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);
252 SQInteger Squirrel::_RunError(HSQUIRRELVM vm)
254 const SQChar *sErr = nullptr;
256 if (sq_gettop(vm) >= 1) {
257 if (SQ_SUCCEEDED(sq_getstring(vm, -1, &sErr))) {
258 Squirrel::RunError(vm, sErr);
259 return 0;
263 Squirrel::RunError(vm, "unknown error");
264 return 0;
267 void Squirrel::PrintFunc(HSQUIRRELVM vm, const std::string &s)
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);
278 void Squirrel::AddMethod(const char *method_name, SQFUNCTION proc, uint nparam, const char *params, void *userdata, int size)
280 ScriptAllocatorScope alloc_scope(this);
282 sq_pushstring(this->vm, method_name, -1);
284 if (size != 0) {
285 void *ptr = sq_newuserdata(vm, size);
286 memcpy(ptr, userdata, size);
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);
295 void Squirrel::AddConst(const char *var_name, int value)
297 ScriptAllocatorScope alloc_scope(this);
299 sq_pushstring(this->vm, var_name, -1);
300 sq_pushinteger(this->vm, value);
301 sq_newslot(this->vm, -3, SQTrue);
304 void Squirrel::AddConst(const char *var_name, bool value)
306 ScriptAllocatorScope alloc_scope(this);
308 sq_pushstring(this->vm, var_name, -1);
309 sq_pushbool(this->vm, value);
310 sq_newslot(this->vm, -3, SQTrue);
313 void Squirrel::AddClassBegin(const char *class_name)
315 ScriptAllocatorScope alloc_scope(this);
317 sq_pushroottable(this->vm);
318 sq_pushstring(this->vm, class_name, -1);
319 sq_newclass(this->vm, SQFalse);
322 void Squirrel::AddClassBegin(const char *class_name, const char *parent_class)
324 ScriptAllocatorScope alloc_scope(this);
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;
334 sq_newclass(this->vm, SQTrue);
337 void Squirrel::AddClassEnd()
339 ScriptAllocatorScope alloc_scope(this);
341 sq_newslot(vm, -3, SQFalse);
342 sq_pop(vm, 1);
345 bool Squirrel::MethodExists(HSQOBJECT instance, const char *method_name)
347 assert(!this->crashed);
348 ScriptAllocatorScope alloc_scope(this);
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;
359 sq_settop(this->vm, top);
360 return true;
363 bool Squirrel::Resume(int suspend)
365 assert(!this->crashed);
366 ScriptAllocatorScope alloc_scope(this);
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;
375 /* We can now only run whatever is "left". */
376 suspend = -this->overdrawn_ops;
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;
385 void Squirrel::ResumeError()
387 assert(!this->crashed);
388 ScriptAllocatorScope alloc_scope(this);
389 sq_resumeerror(this->vm);
392 void Squirrel::CollectGarbage()
394 ScriptAllocatorScope alloc_scope(this);
395 sq_collectgarbage(this->vm);
398 bool Squirrel::CallMethod(HSQOBJECT instance, const char *method_name, HSQOBJECT *ret, int suspend)
400 assert(!this->crashed);
401 ScriptAllocatorScope alloc_scope(this);
402 this->allocator->CheckLimit();
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;
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;
429 return true;
432 bool Squirrel::CallStringMethod(HSQOBJECT instance, const char *method_name, std::string *res, int suspend)
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;
441 bool Squirrel::CallIntegerMethod(HSQOBJECT instance, const char *method_name, int *res, int suspend)
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;
450 bool Squirrel::CallBoolMethod(HSQOBJECT instance, const char *method_name, bool *res, int suspend)
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;
459 /* static */ bool Squirrel::CreateClassInstanceVM(HSQUIRRELVM vm, const std::string &class_name, void *real_instance, HSQOBJECT *instance, SQRELEASEHOOK release_hook, bool prepend_API_name)
461 Squirrel *engine = (Squirrel *)sq_getforeignptr(vm);
463 int oldtop = sq_gettop(vm);
465 /* First, find the class */
466 sq_pushroottable(vm);
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);
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;
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;
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);
495 sq_remove(vm, -2); // Class-name
496 sq_remove(vm, -2); // Root-table
498 /* Store it in the class */
499 sq_setinstanceup(vm, -1, real_instance);
500 if (release_hook != nullptr) sq_setreleasehook(vm, -1, release_hook);
502 if (instance != nullptr) sq_settop(vm, oldtop);
504 return true;
507 bool Squirrel::CreateClassInstance(const std::string &class_name, void *real_instance, HSQOBJECT *instance)
509 ScriptAllocatorScope alloc_scope(this);
510 return Squirrel::CreateClassInstanceVM(this->vm, class_name, real_instance, instance, nullptr);
513 Squirrel::Squirrel(const char *APIName) :
514 APIName(APIName), allocator(new ScriptAllocator())
516 this->Initialize();
519 void Squirrel::Initialize()
521 ScriptAllocatorScope alloc_scope(this);
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);
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);
538 /* Set the foreign pointer, so we can always find this instance from within the VM */
539 sq_setforeignptr(this->vm, this);
541 sq_pushroottable(this->vm);
542 squirrel_register_global_std(this);
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);
549 class SQFile {
550 private:
551 FileHandle file;
552 size_t size;
553 size_t pos;
555 public:
556 SQFile(FileHandle file, size_t size) : file(std::move(file)), size(size), pos(0) {}
558 size_t Read(void *buf, size_t elemsize, size_t count)
560 assert(elemsize != 0);
561 if (this->pos + (elemsize * count) > this->size) {
562 count = (this->size - this->pos) / elemsize;
564 if (count == 0) return 0;
565 size_t ret = fread(buf, elemsize, count, this->file);
566 this->pos += ret * elemsize;
567 return ret;
571 static char32_t _io_file_lexfeed_ASCII(SQUserPointer file)
573 unsigned char c;
574 if (((SQFile *)file)->Read(&c, sizeof(c), 1) > 0) return c;
575 return 0;
578 static char32_t _io_file_lexfeed_UTF8(SQUserPointer file)
580 char buffer[5];
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;
587 /* Read the remaining bits. */
588 if (len > 1 && ((SQFile *)file)->Read(buffer + 1, sizeof(buffer[0]), len - 1) != len - 1) return 0;
590 /* Convert the character, and when definitely invalid, bail out as well. */
591 char32_t c;
592 if (Utf8Decode(&c, buffer) != len) return -1;
594 return c;
597 static char32_t _io_file_lexfeed_UCS2_no_swap(SQUserPointer file)
599 unsigned short c;
600 if (((SQFile *)file)->Read(&c, sizeof(c), 1) > 0) return (char32_t)c;
601 return 0;
604 static char32_t _io_file_lexfeed_UCS2_swap(SQUserPointer file)
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;
611 return 0;
614 static SQInteger _io_file_read(SQUserPointer file, SQUserPointer buf, SQInteger size)
616 SQInteger ret = ((SQFile *)file)->Read(buf, 1, size);
617 if (ret == 0) return -1;
618 return ret;
621 SQRESULT Squirrel::LoadFile(HSQUIRRELVM vm, const std::string &filename, SQBool printerror)
623 ScriptAllocatorScope alloc_scope(this);
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();
637 if (!file.has_value()) {
638 return sq_throwerror(vm, "cannot open the file");
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");;
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");
652 SQFile f(std::move(*file), size);
653 if (SQ_SUCCEEDED(sq_readclosure(vm, _io_file_read, &f))) {
654 return SQ_OK;
656 return sq_throwerror(vm, "Couldn't read bytecode");
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");
675 unsigned char uc;
676 if (fread(&uc, 1, sizeof(uc), *file) != sizeof(uc) || uc != 0xBF) {
677 return sq_throwerror(vm, "Unrecognized encoding");
679 func = _io_file_lexfeed_UTF8;
680 size -= 3; // Skip BOM
681 break;
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");
689 break;
692 SQFile f(std::move(*file), size);
693 if (SQ_SUCCEEDED(sq_compile(vm, func, &f, filename.c_str(), printerror))) {
694 return SQ_OK;
696 return SQ_ERROR;
699 bool Squirrel::LoadScript(HSQUIRRELVM vm, const std::string &script, bool in_root)
701 ScriptAllocatorScope alloc_scope(this);
703 /* Make sure we are always in the root-table */
704 if (in_root) sq_pushroottable(vm);
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;
718 vm->_ops_till_suspend = ops_left;
719 Debug(misc, 0, "[squirrel] Failed to compile '{}'", script);
720 return false;
723 bool Squirrel::LoadScript(const std::string &script)
725 return LoadScript(this->vm, script);
728 Squirrel::~Squirrel()
730 this->Uninitialize();
733 void Squirrel::Uninitialize()
735 ScriptAllocatorScope alloc_scope(this);
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);
743 /* Clean up the stuff */
744 sq_pop(this->vm, 1);
745 sq_close(this->vm);
747 assert(this->allocator->allocated_size == 0);
749 /* Reset memory allocation errors. */
750 this->allocator->error_thrown = false;
753 void Squirrel::Reset()
755 this->Uninitialize();
756 this->Initialize();
759 void Squirrel::InsertResult(bool result)
761 ScriptAllocatorScope alloc_scope(this);
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();
770 void Squirrel::InsertResult(int result)
772 ScriptAllocatorScope alloc_scope(this);
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();
781 /* static */ void Squirrel::DecreaseOps(HSQUIRRELVM vm, int ops)
783 vm->DecreaseOps(ops);
786 bool Squirrel::IsSuspended()
788 return this->vm->_suspended != 0;
791 bool Squirrel::HasScriptCrashed()
793 return this->crashed;
796 void Squirrel::CrashOccurred()
798 this->crashed = true;
801 bool Squirrel::CanSuspend()
803 ScriptAllocatorScope alloc_scope(this);
804 return sq_can_suspend(this->vm);
807 SQInteger Squirrel::GetOpsTillSuspend()
809 return this->vm->_ops_till_suspend;