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 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"
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"
18 #include <../squirrel/sqpcheader.h>
19 #include <../squirrel/sqvm.h>
20 #include "../core/alloc_func.hpp"
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
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.
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
;
53 void CheckLimit() const
55 if (this->allocated_size
> this->allocation_limit
) throw Script_FatalError("Maximum memory allocation exceeded");
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. */
78 throw Script_FatalError(msg
);
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
;
114 void *Realloc(void *p
, SQUnsignedInteger oldsize
, SQUnsignedInteger size
)
117 return this->Malloc(size
);
120 this->Free(p
, oldsize
);
124 #ifdef SCRIPT_DEBUG_ALLOCATIONS
125 assert(this->allocations
[p
] == oldsize
);
126 this->allocations
.erase(p
);
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
));
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
;
152 void Free(void *p
, SQUnsignedInteger size
)
154 if (p
== nullptr) return;
156 this->allocated_size
-= size
;
158 #ifdef SCRIPT_DEBUG_ALLOCATIONS
159 assert(this->allocations
.at(p
) == size
);
160 this->allocations
.erase(p
);
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;
174 #ifdef SCRIPT_DEBUG_ALLOCATIONS
175 assert(this->allocations
.empty());
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
); }
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
);
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
);
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
);
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
);
263 Squirrel::RunError(vm
, "unknown error");
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) {
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);
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
);
334 sq_newclass(this->vm
, SQTrue
);
337 void Squirrel::AddClassEnd()
339 ScriptAllocatorScope
alloc_scope(this);
341 sq_newslot(vm
, -3, SQFalse
);
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
);
359 sq_settop(this->vm
, top
);
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
);
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
;
432 bool Squirrel::CallStringMethod(HSQOBJECT instance
, const char *method_name
, std::string
*res
, int suspend
)
435 if (!this->CallMethod(instance
, method_name
, &ret
, suspend
)) return false;
436 if (ret
._type
!= OT_STRING
) return false;
437 *res
= StrMakeValid(ObjectToString(&ret
));
441 bool Squirrel::CallIntegerMethod(HSQOBJECT instance
, const char *method_name
, int *res
, int suspend
)
444 if (!this->CallMethod(instance
, method_name
, &ret
, suspend
)) return false;
445 if (ret
._type
!= OT_INTEGER
) return false;
446 *res
= ObjectToInteger(&ret
);
450 bool Squirrel::CallBoolMethod(HSQOBJECT instance
, const char *method_name
, bool *res
, int suspend
)
453 if (!this->CallMethod(instance
, method_name
, &ret
, suspend
)) return false;
454 if (ret
._type
!= OT_BOOL
) return false;
455 *res
= ObjectToBool(&ret
);
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);
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
);
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
);
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
);
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())
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);
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
;
571 static char32_t
_io_file_lexfeed_ASCII(SQUserPointer file
)
574 if (((SQFile
*)file
)->Read(&c
, sizeof(c
), 1) > 0) return c
;
578 static char32_t
_io_file_lexfeed_UTF8(SQUserPointer file
)
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. */
592 if (Utf8Decode(&c
, buffer
) != len
) return -1;
597 static char32_t
_io_file_lexfeed_UCS2_no_swap(SQUserPointer file
)
600 if (((SQFile
*)file
)->Read(&c
, sizeof(c
), 1) > 0) return (char32_t
)c
;
604 static char32_t
_io_file_lexfeed_UCS2_swap(SQUserPointer file
)
607 if (((SQFile
*)file
)->Read(&c
, sizeof(c
), 1) > 0) {
608 c
= ((c
>> 8) & 0x00FF)| ((c
<< 8) & 0xFF00);
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;
621 SQRESULT
Squirrel::LoadFile(HSQUIRRELVM vm
, const std::string
&filename
, SQBool printerror
)
623 ScriptAllocatorScope
alloc_scope(this);
625 std::optional
<FileHandle
> file
= std::nullopt
;
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
);
637 if (!file
.has_value()) {
638 return sq_throwerror(vm
, "cannot open the file");
640 unsigned short bom
= 0;
642 if (fread(&bom
, 1, sizeof(bom
), *file
) != sizeof(bom
)) return sq_throwerror(vm
, "cannot read the file");;
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
))) {
656 return sq_throwerror(vm
, "Couldn't read bytecode");
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
666 func
= _io_file_lexfeed_UCS2_no_swap
;
667 size
-= 2; // Skip BOM
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 */
673 return sq_throwerror(vm
, "I/O error");
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
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");
692 SQFile
f(std::move(*file
), size
);
693 if (SQ_SUCCEEDED(sq_compile(vm
, func
, &f
, filename
.c_str(), printerror
))) {
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
))) {
710 if (SQ_SUCCEEDED(sq_call(vm
, 1, SQFalse
, SQTrue
, 100000))) {
712 /* After compiling the file we want to reset the amount of opcodes. */
713 vm
->_ops_till_suspend
= ops_left
;
718 vm
->_ops_till_suspend
= ops_left
;
719 Debug(misc
, 0, "[squirrel] Failed to compile '{}'", script
);
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);
743 /* Clean up the stuff */
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();
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);
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);
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
;