13#include "../error_func.h"
14#include "../fileio_func.h"
15#include "../string_func.h"
17#include "../settings_type.h"
19#include <../squirrel/sqpcheader.h>
20#include <../squirrel/sqvm.h>
21#include "../core/math_func.hpp"
22#include "../core/string_consumer.hpp"
24#include "../safeguards.h"
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);
109 void CheckLimit()
const
111 if (this->allocated_size > this->allocation_limit)
throw Script_FatalError(
"Maximum memory allocation exceeded");
116 assert(this->allocated_size == 0);
117 this->error_thrown =
false;
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);
149 this->allocated_size -= size;
151#ifdef SCRIPT_DEBUG_ALLOCATIONS
152 assert(this->allocations.at(p) == size);
153 this->allocations.erase(p);
160 if (this->allocation_limit == 0) this->allocation_limit =
SAFE_LIMIT;
165#ifdef SCRIPT_DEBUG_ALLOCATIONS
166 assert(this->allocations.empty());
173void *sq_vm_malloc(SQUnsignedInteger size) {
return _squirrel_allocator->Malloc(size); }
174void *sq_vm_realloc(
void *p, SQUnsignedInteger oldsize, SQUnsignedInteger size) {
return _squirrel_allocator->Realloc(p, oldsize, size); }
175void sq_vm_free(
void *p, SQUnsignedInteger size) { _squirrel_allocator->Free(p, size); }
179 assert(this->allocator !=
nullptr);
180 return this->allocator->GetAllocatedSize();
186 std::string msg = fmt::format(
"Error {}:{}/{}: {}", source, line, column, desc);
192 if (func ==
nullptr) {
193 Debug(misc, 0,
"[Squirrel] Compile error: {}", msg);
203 if (func ==
nullptr) {
204 fmt::print(stderr,
"{}", s);
213 SQPRINTFUNCTION pf = sq_getprintfunc(
vm);
217 std::string msg = fmt::format(
"Your script made an error: {}\n", error);
220 if (func ==
nullptr) {
221 fmt::print(stderr,
"{}", msg);
227 sqstd_printcallstack(
vm);
229 sq_setprintfunc(
vm, pf);
234 std::string_view view;
236 if (sq_gettop(
vm) >= 1) {
237 if (SQ_SUCCEEDED(sq_getstring(
vm, -1, view))) {
251 if (func ==
nullptr) {
258void Squirrel::AddMethod(std::string_view method_name, SQFUNCTION proc, std::string_view params,
void *userdata,
int size,
bool suspendable)
262 sq_pushstring(this->
vm, fmt::format(
"{1}{0}{1}", method_name, suspendable ?
"@" :
""));
265 void *ptr = sq_newuserdata(
vm, size);
266 std::copy_n(
static_cast<std::byte *
>(userdata), size,
static_cast<std::byte *
>(ptr));
269 sq_newclosure(this->
vm, proc, size != 0 ? 1 : 0);
270 if (!params.empty()) sq_setparamscheck(this->
vm, params.size(), params);
271 sq_setnativeclosurename(this->
vm, -1, method_name);
272 sq_newslot(this->
vm, -3, SQFalse);
275 std::string squirrel_script = fmt::format(
276 "function {0}(...)\n"
278 " local args = [this];\n"
279 " for(local i = 0; i < vargc; i++) args.push(vargv[i]);\n"
280 " while(this[\"@{0}@\"].acall(args)); \n"
282 "return {0};\n", method_name);
284 sq_pushstring(this->
vm, method_name);
285 if (SQ_FAILED(sq_compilebuffer(this->
vm, squirrel_script, method_name, SQTrue))) NOT_REACHED();
286 sq_pushroottable(this->
vm);
287 if (SQ_FAILED(sq_call(this->
vm, 1, SQTrue, SQTrue))) NOT_REACHED();
288 sq_remove(this->
vm, -2);
289 sq_newslot(this->
vm, -3, SQFalse);
297 sq_pushstring(this->
vm, var_name);
298 sq_pushinteger(this->
vm, value);
299 sq_newslot(this->
vm, -3, SQTrue);
306 sq_pushstring(this->
vm, var_name);
307 sq_pushbool(this->
vm, value);
308 sq_newslot(this->
vm, -3, SQTrue);
315 sq_pushroottable(this->
vm);
316 sq_pushstring(this->
vm, class_name);
317 sq_newclass(this->
vm, SQFalse);
324 sq_pushroottable(this->
vm);
325 sq_pushstring(this->
vm, class_name);
326 sq_pushstring(this->
vm, parent_class);
327 if (SQ_FAILED(sq_get(this->
vm, -3))) {
328 Debug(misc, 0,
"[squirrel] Failed to initialize class '{}' based on parent class '{}'", class_name, parent_class);
329 Debug(misc, 0,
"[squirrel] Make sure that '{}' exists before trying to define '{}'", parent_class, class_name);
332 sq_newclass(this->
vm, SQTrue);
339 sq_newslot(
vm, -3, SQFalse);
348 int top = sq_gettop(this->
vm);
350 sq_pushobject(this->
vm, instance);
352 sq_pushstring(this->
vm, method_name);
353 if (SQ_FAILED(sq_get(this->
vm, -2))) {
354 sq_settop(this->
vm, top);
357 sq_settop(this->
vm, top);
377 this->
crashed = !sq_resumecatch(this->
vm, suspend);
379 this->allocator->CheckLimit();
380 return this->
vm->_suspended != 0;
387 sq_resumeerror(this->
vm);
393 sq_collectgarbage(this->
vm);
400 this->allocator->CheckLimit();
405 SQInteger last_target = this->
vm->_suspended_target;
407 int top = sq_gettop(this->
vm);
409 sq_pushobject(this->
vm, instance);
411 sq_pushstring(this->
vm, method_name);
412 if (SQ_FAILED(sq_get(this->
vm, -2))) {
413 Debug(misc, 0,
"[squirrel] Could not find '{}' in the class", method_name);
414 sq_settop(this->
vm, top);
418 sq_pushobject(this->
vm, instance);
419 if (SQ_FAILED(sq_call(this->
vm, 1, ret ==
nullptr ? SQFalse : SQTrue, SQTrue, suspend)))
return false;
420 if (ret !=
nullptr) sq_getstackobj(
vm, -1, ret);
423 if (suspend == -1 || !this->
IsSuspended()) sq_settop(this->
vm, top);
425 this->
vm->_suspended_target = last_target;
430bool Squirrel::CallStringMethod(HSQOBJECT instance, std::string_view method_name, std::string *res,
int suspend)
433 if (!this->
CallMethod(instance, method_name, &ret, suspend))
return false;
436 if (!str.has_value())
return false;
442bool Squirrel::CallIntegerMethod(HSQOBJECT instance, std::string_view method_name,
int *res,
int suspend)
445 if (!this->
CallMethod(instance, method_name, &ret, suspend))
return false;
446 if (ret._type != OT_INTEGER)
return false;
451bool Squirrel::CallBoolMethod(HSQOBJECT instance, std::string_view method_name,
bool *res,
int suspend)
454 if (!this->
CallMethod(instance, method_name, &ret, suspend))
return false;
455 if (ret._type != OT_BOOL)
return false;
460 bool Squirrel::CreateClassInstanceVM(HSQUIRRELVM
vm,
const std::string &class_name,
void *real_instance, HSQOBJECT *instance, SQRELEASEHOOK release_hook,
bool prepend_API_name)
464 int oldtop = sq_gettop(
vm);
467 sq_pushroottable(
vm);
469 if (prepend_API_name) {
470 std::string prepended_class_name = fmt::format(
"{}{}", engine->
GetAPIName(), class_name);
471 sq_pushstring(
vm, prepended_class_name);
473 sq_pushstring(
vm, class_name);
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);
515 if (index < 0) index += sq_gettop(
vm) + 1;
517 std::string class_name = fmt::format(
"{}{}", engine->
GetAPIName(), tag);
518 sq_pushroottable(
vm);
519 sq_pushstring(
vm, class_name);
522 if (sq_instanceof(
vm) == SQTrue) {
524 SQUserPointer ptr =
nullptr;
525 if (SQ_SUCCEEDED(sq_getinstanceup(
vm, index, &ptr,
nullptr)))
return ptr;
527 throw sq_throwerror(
vm, fmt::format(
"parameter {} has an invalid type ; expected: '{}'", index - 1, class_name));
530Squirrel::Squirrel(std::string_view
api_name) :
544 this->
vm = sq_open(1024);
548 sq_notifyallexceptions(this->
vm, _debug_script_level > 5);
553 sq_seterrorhandler(this->
vm);
556 sq_setforeignptr(this->
vm,
this);
558 sq_pushroottable(this->
vm);
562 sq_pushconsttable(this->
vm);
563 sq_setdelegate(this->
vm, -2);
574 size_t ReadInternal(std::span<char> buf)
576 size_t count = buf.size();
577 if (this->pos + count > this->size) {
578 count = this->size - this->pos;
580 if (count > 0) count = fread(buf.data(), 1, count, this->file);
586 SQFile(
FileHandle file,
size_t size) : file(std::move(file)), size(size), pos(0), consumer(buffer) {}
590 if (this->consumer.
GetBytesLeft() < min_size && this->pos < this->size) {
593 size_t buffer_size = this->buffer.size();
594 size_t read_size =
Align(min_size - buffer_size, 4096);
596 this->buffer.resize(buffer_size + read_size);
597 auto dest = std::span(this->buffer.data(), this->buffer.size()).subspan(buffer_size);
598 buffer_size += this->ReadInternal(dest);
599 this->buffer.resize(buffer_size);
603 return this->consumer;
606 size_t Read(
void *buf,
size_t max_size)
608 std::span<char> dest(
reinterpret_cast<char *
>(buf), max_size);
610 auto view = this->consumer.
Read(max_size);
611 std::copy(view.data(), view.data() + view.size(), dest.data());
612 size_t result_size = view.size();
614 if (result_size < max_size) {
616 result_size += this->ReadInternal(dest.subspan(result_size));
623static char32_t _io_file_lexfeed_ASCII(SQUserPointer file)
629static char32_t _io_file_lexfeed_UTF8(SQUserPointer file)
635static SQInteger _io_file_read(SQUserPointer file, SQUserPointer buf, SQInteger size)
637 SQInteger ret =
reinterpret_cast<SQFile *
>(file)->Read(buf, size);
638 if (ret == 0)
return -1;
646 std::optional<FileHandle> file = std::nullopt;
651 }
else if (this->
GetAPIName().starts_with(
"GS")) {
658 if (!file.has_value()) {
659 return sq_throwerror(
vm,
"cannot open the file");
661 unsigned short bom = 0;
663 if (fread(&bom, 1,
sizeof(bom), *file) !=
sizeof(bom))
return sq_throwerror(
vm,
"cannot read the file");;
668 case SQ_BYTECODE_STREAM_TAG: {
669 if (fseek(*file, -2, SEEK_CUR) < 0) {
670 return sq_throwerror(
vm,
"cannot seek the file");
673 SQFile f(std::move(*file), size);
674 if (SQ_SUCCEEDED(sq_readclosure(
vm, _io_file_read, &f))) {
677 return sq_throwerror(
vm,
"Couldn't read bytecode");
683 return sq_throwerror(
vm,
"I/O error");
686 if (fread(&uc, 1,
sizeof(uc), *file) !=
sizeof(uc) || uc != 0xBF) {
687 return sq_throwerror(
vm,
"Unrecognized encoding");
689 func = _io_file_lexfeed_UTF8;
694 func = _io_file_lexfeed_ASCII;
696 if (size >= 2 && fseek(*file, -2, SEEK_CUR) < 0) {
697 return sq_throwerror(
vm,
"cannot seek the file");
702 SQFile f(std::move(*file), size);
703 if (SQ_SUCCEEDED(sq_compile(
vm, func, &f, filename.c_str(), printerror))) {
714 if (in_root) sq_pushroottable(
vm);
716 SQInteger ops_left =
vm->_ops_till_suspend;
718 if (SQ_SUCCEEDED(
LoadFile(
vm, script, SQTrue))) {
720 if (SQ_SUCCEEDED(sq_call(
vm, 1, SQFalse, SQTrue, 100000))) {
723 vm->_ops_till_suspend = ops_left;
728 vm->_ops_till_suspend = ops_left;
729 Debug(misc, 0,
"[squirrel] Failed to compile '{}'", script);
748 sq_pushroottable(this->vm);
749 sq_pushnull(this->vm);
750 sq_setdelegate(this->vm, -2);
758 this->allocator->Reset();
767void Squirrel::InsertResult(
bool result)
771 sq_pushbool(this->vm, result);
773 vm->GetAt(
vm->_stackbase +
vm->_suspended_target) =
vm->GetUp(-1);
778void Squirrel::InsertResult(
int result)
782 sq_pushinteger(this->vm, result);
784 vm->GetAt(
vm->_stackbase +
vm->_suspended_target) =
vm->GetUp(-1);
791 vm->DecreaseOps(ops);
796 return this->vm->_suspended != 0;
812 return sq_can_suspend(this->vm);
817 return this->vm->_ops_till_suspend;
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.
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.
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.
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, in various flavors.
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)
Exactly the same as CreateClassInstanceVM, only callable without instance of Squirrel.
static void ErrorPrintFunc(HSQUIRRELVM vm, std::string_view s)
If an error has to be print, this function is called.
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.
#define Debug(category, level, format_string,...)
Output a line of debugging information.
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.
@ 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.
The definition of Script_FatalError.
GameSettings _settings_game
Game settings of a running game or the scenario editor.
void squirrel_register_global_std(Squirrel &engine)
Register all standard functions that are available on first startup.
Defines the Squirrel Standard Function class.
static void StrMakeValid(Builder &builder, StringConsumer &consumer, StringValidationSettings settings)
Copies the valid (UTF-8) characters from consumer to the builder.
ScriptSettings script
settings for scripts
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 * 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