19#include <../squirrel/sqpcheader.h>
20#include <../squirrel/sqvm.h>
32struct ScriptAllocator {
33 friend class Squirrel;
36 std::allocator<uint8_t> allocator;
49#ifdef SCRIPT_DEBUG_ALLOCATIONS
50 std::map<void *, size_t> allocations;
61 if (this->error_thrown)
return;
66 this->error_thrown =
true;
67 std::string msg = fmt::format(
"Maximum memory allocation exceeded by {} bytes when allocating {} bytes",
68 this->allocated_size + requested_size - this->allocation_limit, requested_size);
78 void *
DoAlloc(SQUnsignedInteger requested_size)
81 void *p = this->allocator.allocate(requested_size);
83 this->allocated_size += requested_size;
85#ifdef SCRIPT_DEBUG_ALLOCATIONS
86 assert(this->allocations.find(p) == this->allocations.end());
87 this->allocations[p] = requested_size;
90 }
catch (
const std::bad_alloc &) {
93 if (this->error_thrown) {
97 FatalError(
"Out of memory. Cannot allocate {} bytes", requested_size);
100 this->error_thrown =
true;
101 std::string msg = fmt::format(
"Out of memory. Cannot allocate {} bytes", requested_size);
107 size_t GetAllocatedSize()
const {
return this->allocated_size; }
109 void CheckLimit()
const
120 void *Malloc(SQUnsignedInteger size)
126 void *Realloc(
void *p, SQUnsignedInteger oldsize, SQUnsignedInteger size)
129 return this->Malloc(size);
132 this->Free(p, oldsize);
138 void *new_p = this->
DoAlloc(size);
139 std::copy_n(
static_cast<std::byte *
>(p), std::min(oldsize, size),
static_cast<std::byte *
>(new_p));
140 this->Free(p, oldsize);
145 void Free(
void *p, SQUnsignedInteger size)
147 if (p ==
nullptr)
return;
148 this->allocator.deallocate(
reinterpret_cast<uint8_t*
>(p), size);
151#ifdef SCRIPT_DEBUG_ALLOCATIONS
152 assert(this->allocations.at(p) == size);
153 this->allocations.erase(p);
166#ifdef SCRIPT_DEBUG_ALLOCATIONS
167 assert(this->allocations.empty());
174void *sq_vm_malloc(SQUnsignedInteger size) {
return _squirrel_allocator->Malloc(size); }
175void *sq_vm_realloc(
void *p, SQUnsignedInteger oldsize, SQUnsignedInteger size) {
return _squirrel_allocator->Realloc(p, oldsize, size); }
176void sq_vm_free(
void *p, SQUnsignedInteger size) { _squirrel_allocator->Free(p, size); }
180 assert(this->allocator !=
nullptr);
181 return this->allocator->GetAllocatedSize();
187 std::string msg = fmt::format(
"Error {}:{}/{}: {}", source, line, column, desc);
190 Squirrel *engine = (Squirrel *)sq_getforeignptr(
vm);
193 if (func ==
nullptr) {
194 Debug(misc, 0,
"[Squirrel] Compile error: {}", msg);
203 SQPrintFunc *func = ((Squirrel *)sq_getforeignptr(
vm))->print_func;
204 if (func ==
nullptr) {
205 fmt::print(stderr,
"{}", s);
214 SQPRINTFUNCTION pf = sq_getprintfunc(
vm);
218 std::string msg = fmt::format(
"Your script made an error: {}\n", error);
219 Squirrel *engine = (Squirrel *)sq_getforeignptr(
vm);
221 if (func ==
nullptr) {
222 fmt::print(stderr,
"{}", msg);
228 sqstd_printcallstack(
vm);
230 sq_setprintfunc(
vm, pf);
235 std::string_view view;
237 if (sq_gettop(
vm) >= 1) {
238 if (SQ_SUCCEEDED(sq_getstring(
vm, -1, view))) {
251 SQPrintFunc *func = ((Squirrel *)sq_getforeignptr(
vm))->print_func;
252 if (func ==
nullptr) {
259void Squirrel::AddMethod(std::string_view method_name, SQFUNCTION proc, std::string_view params,
void *userdata,
int size,
bool suspendable)
261 ScriptAllocatorScope alloc_scope(
this);
263 sq_pushstring(this->
vm, fmt::format(
"{1}{0}{1}", method_name, suspendable ?
"@" :
""));
266 void *ptr = sq_newuserdata(
vm, size);
267 std::copy_n(
static_cast<std::byte *
>(userdata), size,
static_cast<std::byte *
>(ptr));
270 sq_newclosure(this->
vm, proc, size != 0 ? 1 : 0);
271 if (!params.empty()) sq_setparamscheck(this->
vm, params.size(), params);
272 sq_setnativeclosurename(this->
vm, -1, method_name);
273 sq_newslot(this->
vm, -3, SQFalse);
276 std::string squirrel_script = fmt::format(
277 "function {0}(...)\n"
279 " local args = [this];\n"
280 " for(local i = 0; i < vargc; i++) args.push(vargv[i]);\n"
281 " while(this[\"@{0}@\"].acall(args)); \n"
283 "return {0};\n", method_name);
285 sq_pushstring(this->
vm, method_name);
286 if (SQ_FAILED(sq_compilebuffer(this->
vm, squirrel_script, method_name, SQTrue))) NOT_REACHED();
287 sq_pushroottable(this->
vm);
288 if (SQ_FAILED(sq_call(this->
vm, 1, SQTrue, SQTrue))) NOT_REACHED();
289 sq_remove(this->
vm, -2);
290 sq_newslot(this->
vm, -3, SQFalse);
296 ScriptAllocatorScope alloc_scope(
this);
298 sq_pushstring(this->
vm, var_name);
299 sq_pushinteger(this->
vm, value);
300 sq_newslot(this->
vm, -3, SQTrue);
305 ScriptAllocatorScope alloc_scope(
this);
307 sq_pushstring(this->
vm, var_name);
308 sq_pushbool(this->
vm, value);
309 sq_newslot(this->
vm, -3, SQTrue);
314 ScriptAllocatorScope alloc_scope(
this);
316 sq_pushroottable(this->
vm);
317 sq_pushstring(this->
vm, class_name);
318 sq_newclass(this->
vm, SQFalse);
323 ScriptAllocatorScope alloc_scope(
this);
325 sq_pushroottable(this->
vm);
326 sq_pushstring(this->
vm, class_name);
327 sq_pushstring(this->
vm, parent_class);
328 if (SQ_FAILED(sq_get(this->
vm, -3))) {
329 Debug(misc, 0,
"[squirrel] Failed to initialize class '{}' based on parent class '{}'", class_name, parent_class);
330 Debug(misc, 0,
"[squirrel] Make sure that '{}' exists before trying to define '{}'", parent_class, class_name);
333 sq_newclass(this->
vm, SQTrue);
338 ScriptAllocatorScope alloc_scope(
this);
340 sq_newslot(
vm, -3, SQFalse);
347 ScriptAllocatorScope alloc_scope(
this);
349 int top = sq_gettop(this->
vm);
351 sq_pushobject(this->
vm, instance);
353 sq_pushstring(this->
vm, method_name);
354 if (SQ_FAILED(sq_get(this->
vm, -2))) {
355 sq_settop(this->
vm, top);
358 sq_settop(this->
vm, top);
365 ScriptAllocatorScope alloc_scope(
this);
378 this->
crashed = !sq_resumecatch(this->
vm, suspend);
380 this->allocator->CheckLimit();
381 return this->
vm->_suspended != 0;
387 ScriptAllocatorScope alloc_scope(
this);
388 sq_resumeerror(this->
vm);
393 ScriptAllocatorScope alloc_scope(
this);
394 sq_collectgarbage(this->
vm);
400 ScriptAllocatorScope alloc_scope(
this);
401 this->allocator->CheckLimit();
406 SQInteger last_target = this->
vm->_suspended_target;
408 int top = sq_gettop(this->
vm);
410 sq_pushobject(this->
vm, instance);
412 sq_pushstring(this->
vm, method_name);
413 if (SQ_FAILED(sq_get(this->
vm, -2))) {
414 Debug(misc, 0,
"[squirrel] Could not find '{}' in the class", method_name);
415 sq_settop(this->
vm, top);
419 sq_pushobject(this->
vm, instance);
420 if (SQ_FAILED(sq_call(this->
vm, 1, ret ==
nullptr ? SQFalse : SQTrue, SQTrue, suspend)))
return false;
421 if (ret !=
nullptr) sq_getstackobj(
vm, -1, ret);
424 if (suspend == -1 || !this->
IsSuspended()) sq_settop(this->
vm, top);
426 this->
vm->_suspended_target = last_target;
434 if (!this->
CallMethod(instance, method_name, &ret, suspend))
return false;
437 if (!str.has_value())
return false;
446 if (!this->
CallMethod(instance, method_name, &ret, suspend))
return false;
447 if (ret._type != OT_INTEGER)
return false;
455 if (!this->
CallMethod(instance, method_name, &ret, suspend))
return false;
456 if (ret._type != OT_BOOL)
return false;
461 bool Squirrel::CreateClassInstanceVM(HSQUIRRELVM
vm,
const std::string &class_name,
void *real_instance, HSQOBJECT *instance, SQRELEASEHOOK release_hook,
bool prepend_API_name)
463 Squirrel *engine = (Squirrel *)sq_getforeignptr(
vm);
465 int oldtop = sq_gettop(
vm);
468 sq_pushroottable(
vm);
470 if (prepend_API_name) {
471 std::string prepended_class_name = fmt::format(
"{}{}", engine->
GetAPIName(), class_name);
472 sq_pushstring(
vm, prepended_class_name);
474 sq_pushstring(
vm, class_name);
477 if (SQ_FAILED(sq_get(
vm, -2))) {
478 Debug(misc, 0,
"[squirrel] Failed to find class by the name '{}{}'", prepend_API_name ? engine->
GetAPIName() :
"", class_name);
479 sq_settop(
vm, oldtop);
484 if (SQ_FAILED(sq_createinstance(
vm, -1))) {
485 Debug(misc, 0,
"[squirrel] Failed to create instance for class '{}{}'", prepend_API_name ? engine->
GetAPIName() :
"", class_name);
486 sq_settop(
vm, oldtop);
490 if (instance !=
nullptr) {
492 sq_getstackobj(
vm, -1, instance);
494 sq_addref(
vm, instance);
500 sq_setinstanceup(
vm, -1, real_instance);
501 if (release_hook !=
nullptr) sq_setreleasehook(
vm, -1, release_hook);
503 if (instance !=
nullptr) sq_settop(
vm, oldtop);
510 ScriptAllocatorScope alloc_scope(
this);
516 if (index < 0) index += sq_gettop(
vm) + 1;
517 Squirrel *engine =
static_cast<Squirrel *
>(sq_getforeignptr(
vm));
518 std::string class_name = fmt::format(
"{}{}", engine->
GetAPIName(), tag);
519 sq_pushroottable(
vm);
520 sq_pushstring(
vm, class_name);
523 if (sq_instanceof(
vm) == SQTrue) {
525 SQUserPointer ptr =
nullptr;
526 if (SQ_SUCCEEDED(sq_getinstanceup(
vm, index, &ptr,
nullptr)))
return ptr;
528 throw sq_throwerror(
vm, fmt::format(
"parameter {} has an invalid type ; expected: '{}'", index - 1, class_name));
531Squirrel::Squirrel(std::string_view
api_name) :
539 ScriptAllocatorScope alloc_scope(
this);
545 this->
vm = sq_open(1024);
549 sq_notifyallexceptions(this->
vm, _debug_script_level > 5);
554 sq_seterrorhandler(this->
vm);
557 sq_setforeignptr(this->
vm,
this);
559 sq_pushroottable(this->
vm);
563 sq_pushconsttable(this->
vm);
564 sq_setdelegate(this->
vm, -2);
575 size_t ReadInternal(std::span<char> buf)
577 size_t count = buf.size();
578 if (this->pos + count > this->size) {
579 count = this->size - this->pos;
581 if (count > 0) count = fread(buf.data(), 1, count, this->file);
587 SQFile(
FileHandle file,
size_t size) : file(std::move(file)), size(size), pos(0), consumer(buffer) {}
591 if (this->consumer.
GetBytesLeft() < min_size && this->pos < this->size) {
594 size_t buffer_size = this->buffer.size();
595 size_t read_size =
Align(min_size - buffer_size, 4096);
597 this->buffer.resize(buffer_size + read_size);
598 auto dest = std::span(this->buffer.data(), this->buffer.size()).subspan(buffer_size);
599 buffer_size += this->ReadInternal(dest);
600 this->buffer.resize(buffer_size);
604 return this->consumer;
607 size_t Read(
void *buf,
size_t max_size)
609 std::span<char> dest(
reinterpret_cast<char *
>(buf), max_size);
611 auto view = this->consumer.
Read(max_size);
612 std::copy(view.data(), view.data() + view.size(), dest.data());
613 size_t result_size = view.size();
615 if (result_size < max_size) {
617 result_size += this->ReadInternal(dest.subspan(result_size));
624static char32_t _io_file_lexfeed_ASCII(SQUserPointer file)
630static char32_t _io_file_lexfeed_UTF8(SQUserPointer file)
636static SQInteger _io_file_read(SQUserPointer file, SQUserPointer buf, SQInteger size)
638 SQInteger ret =
reinterpret_cast<SQFile *
>(file)->Read(buf, size);
639 if (ret == 0)
return -1;
645 ScriptAllocatorScope alloc_scope(
this);
647 std::optional<FileHandle> file = std::nullopt;
652 }
else if (this->
GetAPIName().starts_with(
"GS")) {
659 if (!file.has_value()) {
660 return sq_throwerror(
vm,
"cannot open the file");
662 unsigned short bom = 0;
664 if (fread(&bom, 1,
sizeof(bom), *file) !=
sizeof(bom))
return sq_throwerror(
vm,
"cannot read the file");;
669 case SQ_BYTECODE_STREAM_TAG: {
670 if (fseek(*file, -2, SEEK_CUR) < 0) {
671 return sq_throwerror(
vm,
"cannot seek the file");
674 SQFile f(std::move(*file), size);
675 if (SQ_SUCCEEDED(sq_readclosure(
vm, _io_file_read, &f))) {
678 return sq_throwerror(
vm,
"Couldn't read bytecode");
684 return sq_throwerror(
vm,
"I/O error");
687 if (fread(&uc, 1,
sizeof(uc), *file) !=
sizeof(uc) || uc != 0xBF) {
688 return sq_throwerror(
vm,
"Unrecognized encoding");
690 func = _io_file_lexfeed_UTF8;
695 func = _io_file_lexfeed_ASCII;
697 if (size >= 2 && fseek(*file, -2, SEEK_CUR) < 0) {
698 return sq_throwerror(
vm,
"cannot seek the file");
703 SQFile f(std::move(*file), size);
704 if (SQ_SUCCEEDED(sq_compile(
vm, func, &f, filename.c_str(), printerror))) {
715 if (in_root) sq_pushroottable(
vm);
717 SQInteger ops_left =
vm->_ops_till_suspend;
719 if (SQ_SUCCEEDED(
LoadFile(
vm, script, SQTrue))) {
721 if (SQ_SUCCEEDED(sq_call(
vm, 1, SQFalse, SQTrue, 100000))) {
724 vm->_ops_till_suspend = ops_left;
729 vm->_ops_till_suspend = ops_left;
730 Debug(misc, 0,
"[squirrel] Failed to compile '{}'", script);
747 ScriptAllocatorScope alloc_scope(
this);
750 sq_pushroottable(this->vm);
751 sq_pushnull(this->vm);
752 sq_setdelegate(this->vm, -2);
760 this->allocator->Reset();
769void Squirrel::InsertResult(
bool result)
773 sq_pushbool(this->vm, result);
775 vm->GetAt(
vm->_stackbase +
vm->_suspended_target) =
vm->GetUp(-1);
780void Squirrel::InsertResult(
int result)
782 ScriptAllocatorScope alloc_scope(
this);
784 sq_pushinteger(this->
vm, result);
786 vm->GetAt(
vm->_stackbase +
vm->_suspended_target) =
vm->GetUp(-1);
793 vm->DecreaseOps(ops);
798 return this->vm->_suspended != 0;
813 ScriptAllocatorScope alloc_scope(
this);
814 return sq_can_suspend(this->vm);
819 return this->vm->_ops_till_suspend;
824 _squirrel_allocator->CheckAllocationAllowed(bytes);
825 _squirrel_allocator->allocated_size += bytes;
830 _squirrel_allocator->allocated_size -= bytes;
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.
static SQUserPointer GetRealInstance(HSQUIRRELVM vm, int index, std::string_view tag)
Get the real-instance pointer.
void AddConst(std::string_view var_name, SQInteger value)
Adds a const to the stack.
void * global_pointer
Can be set by who ever initializes Squirrel.
bool CallIntegerMethod(HSQOBJECT instance, std::string_view method_name, int *res, int suspend)
Call a method of an instance returning a integer.
void CollectGarbage()
Tell the VM to do a garbage collection run.
HSQUIRRELVM vm
The VirtualMachine instance for squirrel.
void AddMethod(std::string_view method_name, SQFUNCTION proc, std::string_view params={}, void *userdata=nullptr, int size=0, bool suspendable=false)
Adds a function to the stack.
void Reset()
Completely reset the engine; start from scratch.
SQInteger GetOpsTillSuspend()
How many operations can we execute till suspension?
bool LoadScript(const std::string &script)
Load a script.
bool IsSuspended()
Did the squirrel code suspend or return normally.
bool CanSuspend()
Are we allowed to suspend the squirrel script at this moment?
std::string_view api_name
Name of the API used for this squirrel.
SQPrintFunc * print_func
Points to either nullptr, or a custom print handler.
~Squirrel()
Clean up the Squirrel virtual machine state.
static void DecreaseAllocatedSize(size_t bytes)
Decrease number of bytes allocated in the current script allocator scope.
void AddClassBegin(std::string_view class_name)
Adds a class to the global scope.
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.
bool HasScriptCrashed()
Find out if the squirrel script made an error before.
bool CallBoolMethod(HSQOBJECT instance, std::string_view method_name, bool *res, int suspend)
Call a method of an instance returning a boolean.
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.
static void CompileError(HSQUIRRELVM vm, std::string_view desc, std::string_view source, SQInteger line, SQInteger column)
The CompileError handler.
static void IncreaseAllocatedSize(size_t bytes)
Increase number of bytes allocated in the current script allocator scope.
static void RunError(HSQUIRRELVM vm, std::string_view error)
The RunError handler.
void Initialize()
Perform all initialization steps to create the engine.
bool crashed
True if the squirrel script made an error.
static void PrintFunc(HSQUIRRELVM vm, std::string_view s)
If a user runs 'print' inside a script, this function gets the params.
static bool ObjectToBool(HSQOBJECT *ptr)
Convert a Squirrel-object to a bool.
int overdrawn_ops
The amount of operations we have overdrawn.
static std::optional< std::string_view > ObjectToString(HSQOBJECT *ptr)
Convert a Squirrel-object to a string.
void Uninitialize()
Perform all the cleanups for the engine.
std::string_view GetAPIName()
Get the API name.
bool CallMethod(HSQOBJECT instance, std::string_view method_name, HSQOBJECT *ret, int suspend)
Call a method of an instance returning a Squirrel object.
bool MethodExists(HSQOBJECT instance, std::string_view method_name)
Check if a method exists in an instance.
void ResumeError()
Resume the VM with an error so it prints a stack trace.
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 CreateClassInstance(const std::string &class_name, void *real_instance, HSQOBJECT *instance)
Create a class instance.
static void ErrorPrintFunc(HSQUIRRELVM vm, std::string_view s)
If an error has to be print, this function is called.
bool CallStringMethod(HSQOBJECT instance, std::string_view method_name, std::string *res, int suspend)
Call a method of an instance returning a string.
static int ObjectToInteger(HSQOBJECT *ptr)
Convert a Squirrel-object to an integer.
std::unique_ptr< ScriptAllocator > allocator
Allocator object used by this script.
Parse data from a string / buffer.
char32_t ReadUtf8(char32_t def='?')
Read UTF-8 character, and advance reader.
bool AnyBytesLeft() const noexcept
Check whether any bytes left to read.
size_type GetBytesLeft() const noexcept
Get number of bytes left to read.
std::optional< uint8_t > TryReadUint8()
Try to read binary uint8, and then advance reader.
std::string_view Read(size_type len)
Read the next 'len' bytes, and advance reader.
size_type GetBytesRead() const noexcept
Get number of already read bytes.
Functions related to debugging.
#define Debug(category, level, format_string,...)
Output a line of debugging information.
Error reporting related functions.
std::optional< FileHandle > FioFOpenFile(std::string_view filename, std::string_view mode, Subdirectory subdir, size_t *filesize)
Opens a OpenTTD file somewhere in a personal or global directory.
Functions for standard in/out file operations.
@ 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.
constexpr T Align(const T x, uint n)
Return the smallest multiple of n equal or greater than x.
A number of safeguards to prevent using unsafe methods.
The definition of Script_FatalError.
GameSettings _settings_game
Game settings of a running game or the scenario editor.
Types related to global configuration settings.
void squirrel_register_global_std(Squirrel &engine)
Register all standard functions that are available on first startup.
Defines the Squirrel Standard Function class.
Definition of base types and functions in a cross-platform compatible way.
static void StrMakeValid(Builder &builder, StringConsumer &consumer, StringValidationSettings settings)
Copies the valid (UTF-8) characters from consumer to the builder.
Functions related to low-level strings.
ScriptSettings script
settings for scripts
static const size_t SAFE_LIMIT
128 MiB, a safe choice for almost any situation
~ScriptAllocator()
Ensure the allocations have already been released.
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 * DoAlloc(SQUnsignedInteger requested_size)
Internal helper to allocate the given amount of bytes.
void CheckAllocationAllowed(size_t requested_size)
Checks whether an allocation is allowed by the memory limit set for the script.
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