10 #include "../stdafx.h"
13 #include "../fileio_func.h"
14 #include "../string_func.h"
16 #include "../settings_type.h"
18 #include <../squirrel/sqpcheader.h>
19 #include <../squirrel/sqvm.h>
20 #include "../core/alloc_func.hpp"
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");
69 if (this->allocated_size + requested_size > this->allocation_limit && !this->error_thrown) {
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);
84 if (this->error_thrown) {
91 this->error_thrown =
true;
92 std::string msg = fmt::format(
"Out of memory. Cannot allocate {} bytes", requested_size);
97 void *Malloc(SQUnsignedInteger size)
99 void *p = malloc(size);
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);
132 void *new_p = malloc(size);
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;
168 if (this->allocation_limit == 0) this->allocation_limit =
SAFE_LIMIT;
169 this->error_thrown =
false;
174 #ifdef SCRIPT_DEBUG_ALLOCATIONS
175 assert(this->allocations.empty());
186 #include "../safeguards.h"
191 #ifndef SQUIRREL_DEFAULT_ALLOCATOR
193 void *sq_vm_realloc(
void *p, SQUnsignedInteger oldsize, SQUnsignedInteger size) {
return _squirrel_allocator->Realloc(p, oldsize, size); }
206 std::string msg = fmt::format(
"Error {}:{}/{}: {}", source, line, column, desc);
212 if (func ==
nullptr) {
213 Debug(misc, 0,
"[Squirrel] Compile error: {}", msg);
223 if (func ==
nullptr) {
224 fmt::print(stderr,
"{}", s);
233 SQPRINTFUNCTION pf = sq_getprintfunc(
vm);
237 std::string msg = fmt::format(
"Your script made an error: {}\n", error);
240 if (func ==
nullptr) {
241 fmt::print(stderr,
"{}", msg);
247 sqstd_printcallstack(
vm);
249 sq_setprintfunc(
vm, pf);
254 const SQChar *sErr =
nullptr;
256 if (sq_gettop(
vm) >= 1) {
257 if (SQ_SUCCEEDED(sq_getstring(
vm, -1, &sErr))) {
271 if (func ==
nullptr) {
278 void Squirrel::AddMethod(
const char *method_name, SQFUNCTION proc, uint nparam,
const char *params,
void *userdata,
int size)
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);
299 sq_pushstring(this->
vm, var_name, -1);
300 sq_pushinteger(this->
vm, value);
301 sq_newslot(this->
vm, -3, SQTrue);
308 sq_pushstring(this->
vm, var_name, -1);
309 sq_pushbool(this->
vm, value);
310 sq_newslot(this->
vm, -3, SQTrue);
317 sq_pushroottable(this->
vm);
318 sq_pushstring(this->
vm, class_name, -1);
319 sq_newclass(this->
vm, SQFalse);
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);
341 sq_newslot(
vm, -3, SQFalse);
350 int top = sq_gettop(this->
vm);
352 sq_pushobject(this->
vm, instance);
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);
379 this->
crashed = !sq_resumecatch(this->
vm, suspend);
382 return this->
vm->_suspended != 0;
389 sq_resumeerror(this->
vm);
395 sq_collectgarbage(this->
vm);
407 SQInteger last_target = this->
vm->_suspended_target;
409 int top = sq_gettop(this->
vm);
411 sq_pushobject(this->
vm, instance);
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);
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);
425 if (suspend == -1 || !this->
IsSuspended()) sq_settop(this->
vm, top);
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;
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;
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;
459 bool Squirrel::CreateClassInstanceVM(HSQUIRRELVM vm,
const std::string &class_name,
void *real_instance, HSQOBJECT *instance, SQRELEASEHOOK release_hook,
bool prepend_API_name)
463 int oldtop = sq_gettop(
vm);
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);
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) {
491 sq_getstackobj(
vm, -1, instance);
493 sq_addref(
vm, instance);
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);
513 Squirrel::Squirrel(
const char *APIName) :
527 this->
vm = sq_open(1024);
531 sq_notifyallexceptions(this->
vm, _debug_script_level > 5);
536 sq_seterrorhandler(this->
vm);
539 sq_setforeignptr(this->
vm,
this);
541 sq_pushroottable(this->
vm);
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)
583 if (((
SQFile *)file)->Read(buffer,
sizeof(buffer[0]), 1) != 1)
return 0;
585 if (len == 0)
return -1;
588 if (len > 1 && ((
SQFile *)file)->Read(buffer + 1,
sizeof(buffer[0]), len - 1) != len - 1)
return 0;
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;
625 std::optional<FileHandle> file = std::nullopt;
627 if (strncmp(this->
GetAPIName(),
"AI", 2) == 0) {
630 }
else if (strncmp(this->
GetAPIName(),
"GS", 2) == 0) {
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: {
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");
662 func = _io_file_lexfeed_UCS2_swap;
666 func = _io_file_lexfeed_UCS2_no_swap;
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;
684 func = _io_file_lexfeed_ASCII;
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))) {
704 if (in_root) sq_pushroottable(
vm);
706 SQInteger ops_left =
vm->_ops_till_suspend;
708 if (SQ_SUCCEEDED(
LoadFile(
vm, script, SQTrue))) {
710 if (SQ_SUCCEEDED(sq_call(
vm, 1, SQFalse, SQTrue, 100000))) {
713 vm->_ops_till_suspend = ops_left;
718 vm->_ops_till_suspend = ops_left;
719 Debug(misc, 0,
"[squirrel] Failed to compile '{}'", script);
728 Squirrel::~Squirrel()
738 sq_pushroottable(this->vm);
739 sq_pushnull(this->vm);
740 sq_setdelegate(this->vm, -2);
747 assert(this->
allocator->allocated_size == 0);
759 void Squirrel::InsertResult(
bool result)
763 sq_pushbool(this->vm, result);
765 vm->GetAt(
vm->_stackbase +
vm->_suspended_target) =
vm->GetUp(-1);
770 void Squirrel::InsertResult(
int result)
774 sq_pushinteger(this->vm, result);
776 vm->GetAt(
vm->_stackbase +
vm->_suspended_target) =
vm->GetUp(-1);
783 vm->DecreaseOps(ops);
788 return this->vm->_suspended != 0;
804 return sq_can_suspend(this->vm);
809 return this->vm->_ops_till_suspend;
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.
bool Resume(int suspend=-1)
Resume a VM when it was suspended via a throw.
void * global_pointer
Can be set by who ever initializes Squirrel.
static void ErrorPrintFunc(HSQUIRRELVM vm, const std::string &s)
If an error has to be print, this function is called.
void CollectGarbage()
Tell the VM to do a garbage collection run.
HSQUIRRELVM vm
The VirtualMachine instance for squirrel.
void Reset()
Completely reset the engine; start from scratch.
SQInteger GetOpsTillSuspend()
How many operations can we execute till suspension?
static const char * ObjectToString(HSQOBJECT *ptr)
Convert a Squirrel-object to a string.
bool LoadScript(const std::string &script)
Load a script.
bool IsSuspended()
Did the squirrel code suspend or return normally.
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.
static void RunError(HSQUIRRELVM vm, const SQChar *error)
The RunError handler.
bool CanSuspend()
Are we allowed to suspend the squirrel script at this moment?
SQPrintFunc * print_func
Points to either nullptr, or a custom print handler.
static void CompileError(HSQUIRRELVM vm, const SQChar *desc, const SQChar *source, SQInteger line, SQInteger column)
The CompileError handler.
static SQInteger _RunError(HSQUIRRELVM vm)
The internal RunError handler.
size_t GetAllocatedMemory() const noexcept
Get number of bytes allocated by this VM.
static void DecreaseOps(HSQUIRRELVM vm, int amount)
Tell the VM to remove amount ops from the number of ops till suspend.
static void PrintFunc(HSQUIRRELVM vm, const std::string &s)
If a user runs 'print' inside a script, this function gets the params.
bool HasScriptCrashed()
Find out if the squirrel script made an error before.
void CrashOccurred()
Set the script status to crashed.
SQRESULT LoadFile(HSQUIRRELVM vm, const std::string &filename, SQBool printerror)
Load a file to a given VM.
const char * GetAPIName()
Get the API name.
void Initialize()
Perform all initialization steps to create the engine.
bool crashed
True if the squirrel script made an error.
static bool ObjectToBool(HSQOBJECT *ptr)
Convert a Squirrel-object to a bool.
bool CallMethod(HSQOBJECT instance, const char *method_name, HSQOBJECT *ret, int suspend)
Call a method of an instance, in various flavors.
void AddClassBegin(const char *class_name)
Adds a class to the global scope.
int overdrawn_ops
The amount of operations we have overdrawn.
void Uninitialize()
Perform all the cleanups for the engine.
void ResumeError()
Resume the VM with an error so it prints a stack trace.
void AddConst(const char *var_name, int value)
Adds a const to the stack.
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.
bool MethodExists(HSQOBJECT instance, const char *method_name)
Check if a method exists in an instance.
bool CreateClassInstance(const std::string &class_name, void *real_instance, HSQOBJECT *instance)
Exactly the same as CreateClassInstanceVM, only callable without instance of Squirrel.
static int ObjectToInteger(HSQOBJECT *ptr)
Convert a Squirrel-object to an integer.
std::unique_ptr< ScriptAllocator > allocator
Allocator object used by this script.
#define Debug(category, level, format_string,...)
Ouptut a line of debugging information.
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.
@ 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.
ScriptAllocator * _squirrel_allocator
In the memory allocator for Squirrel we want to directly use malloc/realloc, so when the OS does not ...
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.
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.
size_t Utf8Decode(char32_t *c, const char *s)
Decode and consume the next UTF-8 encoded character.
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 ...
static const size_t SAFE_LIMIT
128 MiB, a safe choice for almost any situation
bool error_thrown
Whether the error has already been thrown, so to not throw secondary errors in the handling of the al...
size_t allocation_limit
Maximum this allocator may use before allocations fail.
void CheckAllocation(size_t requested_size, void *p)
Catch all validation for the allocation; did it allocate too much memory according to the allocation ...
size_t allocated_size
Sum of allocated data size.
uint32_t script_max_memory_megabytes
limit on memory a single script instance may have allocated