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);
346 ScriptAllocatorScope alloc_scope(
this);
348 sq_pushstring(this->
vm, enum_name);
349 sq_newclass(this->
vm, SQFalse);
354 ScriptAllocatorScope alloc_scope(
this);
356 sq_newslot(
vm, -3, SQFalse);
362 ScriptAllocatorScope alloc_scope(
this);
364 int top = sq_gettop(this->
vm);
366 sq_pushobject(this->
vm, instance);
368 sq_pushstring(this->
vm, method_name);
369 if (SQ_FAILED(sq_get(this->
vm, -2))) {
370 sq_settop(this->
vm, top);
373 sq_settop(this->
vm, top);
380 ScriptAllocatorScope alloc_scope(
this);
393 this->
crashed = !sq_resumecatch(this->
vm, suspend);
395 this->allocator->CheckLimit();
396 return this->
vm->_suspended != 0;
402 ScriptAllocatorScope alloc_scope(
this);
403 sq_resumeerror(this->
vm);
408 ScriptAllocatorScope alloc_scope(
this);
409 sq_collectgarbage(this->
vm);
415 ScriptAllocatorScope alloc_scope(
this);
416 this->allocator->CheckLimit();
421 SQInteger last_target = this->
vm->_suspended_target;
423 int top = sq_gettop(this->
vm);
425 sq_pushobject(this->
vm, instance);
427 sq_pushstring(this->
vm, method_name);
428 if (SQ_FAILED(sq_get(this->
vm, -2))) {
429 Debug(misc, 0,
"[squirrel] Could not find '{}' in the class", method_name);
430 sq_settop(this->
vm, top);
434 sq_pushobject(this->
vm, instance);
435 if (SQ_FAILED(sq_call(this->
vm, 1, ret ==
nullptr ? SQFalse : SQTrue, SQTrue, suspend)))
return false;
436 if (ret !=
nullptr) sq_getstackobj(
vm, -1, ret);
439 if (suspend == -1 || !this->
IsSuspended()) sq_settop(this->
vm, top);
441 this->
vm->_suspended_target = last_target;
449 if (!this->
CallMethod(instance, method_name, &ret, suspend))
return false;
452 if (!str.has_value())
return false;
461 if (!this->
CallMethod(instance, method_name, &ret, suspend))
return false;
462 if (ret._type != OT_INTEGER)
return false;
470 if (!this->
CallMethod(instance, method_name, &ret, suspend))
return false;
471 if (ret._type != OT_BOOL)
return false;
476 bool Squirrel::CreateClassInstanceVM(HSQUIRRELVM
vm,
const std::string &class_name,
void *real_instance, HSQOBJECT *instance, SQRELEASEHOOK release_hook,
bool prepend_API_name)
478 Squirrel *engine = (Squirrel *)sq_getforeignptr(
vm);
480 int oldtop = sq_gettop(
vm);
483 sq_pushroottable(
vm);
485 if (prepend_API_name) {
486 std::string prepended_class_name = fmt::format(
"{}{}", engine->
GetAPIName(), class_name);
487 sq_pushstring(
vm, prepended_class_name);
489 sq_pushstring(
vm, class_name);
492 if (SQ_FAILED(sq_get(
vm, -2))) {
493 Debug(misc, 0,
"[squirrel] Failed to find class by the name '{}{}'", prepend_API_name ? engine->
GetAPIName() :
"", class_name);
494 sq_settop(
vm, oldtop);
499 if (SQ_FAILED(sq_createinstance(
vm, -1))) {
500 Debug(misc, 0,
"[squirrel] Failed to create instance for class '{}{}'", prepend_API_name ? engine->
GetAPIName() :
"", class_name);
501 sq_settop(
vm, oldtop);
505 if (instance !=
nullptr) {
507 sq_getstackobj(
vm, -1, instance);
509 sq_addref(
vm, instance);
515 sq_setinstanceup(
vm, -1, real_instance);
516 if (release_hook !=
nullptr) sq_setreleasehook(
vm, -1, release_hook);
518 if (instance !=
nullptr) sq_settop(
vm, oldtop);
525 ScriptAllocatorScope alloc_scope(
this);
531 if (index < 0) index += sq_gettop(
vm) + 1;
532 Squirrel *engine =
static_cast<Squirrel *
>(sq_getforeignptr(
vm));
533 std::string class_name = fmt::format(
"{}{}", engine->
GetAPIName(), tag);
534 sq_pushroottable(
vm);
535 sq_pushstring(
vm, class_name);
538 if (sq_instanceof(
vm) == SQTrue) {
540 SQUserPointer ptr =
nullptr;
541 if (SQ_SUCCEEDED(sq_getinstanceup(
vm, index, &ptr,
nullptr)))
return ptr;
543 throw sq_throwerror(
vm, fmt::format(
"parameter {} has an invalid type ; expected: '{}'", index - 1, class_name));
546Squirrel::Squirrel(std::string_view
api_name) :
554 ScriptAllocatorScope alloc_scope(
this);
560 this->
vm = sq_open(1024);
564 sq_notifyallexceptions(this->
vm, _debug_script_level > 5);
569 sq_seterrorhandler(this->
vm);
572 sq_setforeignptr(this->
vm,
this);
574 sq_pushroottable(this->
vm);
578 sq_pushconsttable(this->
vm);
579 sq_setdelegate(this->
vm, -2);
590 size_t ReadInternal(std::span<char> buf)
592 size_t count = buf.size();
593 if (this->pos + count > this->size) {
594 count = this->size - this->pos;
596 if (count > 0) count = fread(buf.data(), 1, count, this->file);
602 SQFile(
FileHandle file,
size_t size) : file(std::move(file)), size(size), pos(0), consumer(buffer) {}
606 if (this->consumer.
GetBytesLeft() < min_size && this->pos < this->size) {
609 size_t buffer_size = this->buffer.size();
610 size_t read_size =
Align(min_size - buffer_size, 4096);
612 this->buffer.resize(buffer_size + read_size);
613 auto dest = std::span(this->buffer.data(), this->buffer.size()).subspan(buffer_size);
614 buffer_size += this->ReadInternal(dest);
615 this->buffer.resize(buffer_size);
619 return this->consumer;
622 size_t Read(
void *buf,
size_t max_size)
624 std::span<char> dest(
reinterpret_cast<char *
>(buf), max_size);
626 auto view = this->consumer.
Read(max_size);
627 std::copy(view.data(), view.data() + view.size(), dest.data());
628 size_t result_size = view.size();
630 if (result_size < max_size) {
632 result_size += this->ReadInternal(dest.subspan(result_size));
639static char32_t _io_file_lexfeed_ASCII(SQUserPointer file)
645static char32_t _io_file_lexfeed_UTF8(SQUserPointer file)
651static SQInteger _io_file_read(SQUserPointer file, SQUserPointer buf, SQInteger size)
653 SQInteger ret =
reinterpret_cast<SQFile *
>(file)->Read(buf, size);
654 if (ret == 0)
return -1;
660 ScriptAllocatorScope alloc_scope(
this);
662 std::optional<FileHandle> file = std::nullopt;
667 }
else if (this->
GetAPIName().starts_with(
"GS")) {
674 if (!file.has_value()) {
675 return sq_throwerror(
vm,
"cannot open the file");
677 unsigned short bom = 0;
679 if (fread(&bom, 1,
sizeof(bom), *file) !=
sizeof(bom))
return sq_throwerror(
vm,
"cannot read the file");;
684 case SQ_BYTECODE_STREAM_TAG: {
685 if (fseek(*file, -2, SEEK_CUR) < 0) {
686 return sq_throwerror(
vm,
"cannot seek the file");
689 SQFile f(std::move(*file), size);
690 if (SQ_SUCCEEDED(sq_readclosure(
vm, _io_file_read, &f))) {
693 return sq_throwerror(
vm,
"Couldn't read bytecode");
699 return sq_throwerror(
vm,
"I/O error");
702 if (fread(&uc, 1,
sizeof(uc), *file) !=
sizeof(uc) || uc != 0xBF) {
703 return sq_throwerror(
vm,
"Unrecognized encoding");
705 func = _io_file_lexfeed_UTF8;
710 func = _io_file_lexfeed_ASCII;
712 if (size >= 2 && fseek(*file, -2, SEEK_CUR) < 0) {
713 return sq_throwerror(
vm,
"cannot seek the file");
718 SQFile f(std::move(*file), size);
719 if (SQ_SUCCEEDED(sq_compile(
vm, func, &f, filename.c_str(), printerror))) {
730 if (in_root) sq_pushroottable(
vm);
732 SQInteger ops_left =
vm->_ops_till_suspend;
734 if (SQ_SUCCEEDED(
LoadFile(
vm, script, SQTrue))) {
736 if (SQ_SUCCEEDED(sq_call(
vm, 1, SQFalse, SQTrue, 100000))) {
739 vm->_ops_till_suspend = ops_left;
744 vm->_ops_till_suspend = ops_left;
745 Debug(misc, 0,
"[squirrel] Failed to compile '{}'", script);
762 ScriptAllocatorScope alloc_scope(
this);
765 sq_pushroottable(this->vm);
766 sq_pushnull(this->vm);
767 sq_setdelegate(this->vm, -2);
775 this->allocator->Reset();
784void Squirrel::InsertResult(
bool result)
788 sq_pushbool(this->vm, result);
790 vm->GetAt(
vm->_stackbase +
vm->_suspended_target) =
vm->GetUp(-1);
795void Squirrel::InsertResult(
int result)
797 ScriptAllocatorScope alloc_scope(
this);
799 sq_pushinteger(this->
vm, result);
801 vm->GetAt(
vm->_stackbase +
vm->_suspended_target) =
vm->GetUp(-1);
808 vm->DecreaseOps(ops);
813 return this->vm->_suspended != 0;
828 ScriptAllocatorScope alloc_scope(
this);
829 return sq_can_suspend(this->vm);
834 return this->vm->_ops_till_suspend;
839 _squirrel_allocator->CheckAllocationAllowed(bytes);
840 _squirrel_allocator->allocated_size += bytes;
845 _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.
void AddScopedEnumBegin(std::string_view enum_name)
Adds an enum to the scope.
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 AddScopedEnumEnd()
Finishes adding an enum to the scope.
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.
@ GsLibrary
Subdirectory for all GS libraries.
@ Ai
Subdirectory for all AI files.
@ Gs
Subdirectory for all game scripts.
@ AiLibrary
Subdirectory for all AI libraries.
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