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"
34 std::allocator<uint8_t> allocator;
47#ifdef SCRIPT_DEBUG_ALLOCATIONS
48 std::map<void *, size_t> allocations;
59 if (this->error_thrown)
return;
64 this->error_thrown =
true;
65 std::string msg = fmt::format(
"Maximum memory allocation exceeded by {} bytes when allocating {} bytes",
66 this->allocated_size + requested_size - this->allocation_limit, requested_size);
76 void *
DoAlloc(SQUnsignedInteger requested_size)
79 void *p = this->allocator.allocate(requested_size);
81 this->allocated_size += requested_size;
83#ifdef SCRIPT_DEBUG_ALLOCATIONS
84 assert(this->allocations.find(p) == this->allocations.end());
85 this->allocations[p] = requested_size;
88 }
catch (
const std::bad_alloc &) {
91 if (this->error_thrown) {
95 FatalError(
"Out of memory. Cannot allocate {} bytes", requested_size);
98 this->error_thrown =
true;
99 std::string msg = fmt::format(
"Out of memory. Cannot allocate {} bytes", requested_size);
107 void CheckLimit()
const
109 if (this->allocated_size > this->allocation_limit)
throw Script_FatalError(
"Maximum memory allocation exceeded");
114 assert(this->allocated_size == 0);
115 this->error_thrown =
false;
118 void *Malloc(SQUnsignedInteger size)
124 void *Realloc(
void *p, SQUnsignedInteger oldsize, SQUnsignedInteger size)
127 return this->Malloc(size);
130 this->Free(p, oldsize);
136 void *new_p = this->
DoAlloc(size);
137 std::copy_n(
static_cast<std::byte *
>(p), std::min(oldsize, size),
static_cast<std::byte *
>(new_p));
138 this->Free(p, oldsize);
143 void Free(
void *p, SQUnsignedInteger size)
145 if (p ==
nullptr)
return;
146 this->allocator.deallocate(
reinterpret_cast<uint8_t*
>(p), size);
147 this->allocated_size -= size;
149#ifdef SCRIPT_DEBUG_ALLOCATIONS
150 assert(this->allocations.at(p) == size);
151 this->allocations.erase(p);
158 if (this->allocation_limit == 0) this->allocation_limit =
SAFE_LIMIT;
163#ifdef SCRIPT_DEBUG_ALLOCATIONS
164 assert(this->allocations.empty());
171void *sq_vm_malloc(SQUnsignedInteger size) {
return _squirrel_allocator->Malloc(size); }
172void *sq_vm_realloc(
void *p, SQUnsignedInteger oldsize, SQUnsignedInteger size) {
return _squirrel_allocator->Realloc(p, oldsize, size); }
173void sq_vm_free(
void *p, SQUnsignedInteger size) { _squirrel_allocator->Free(p, size); }
177 assert(this->allocator !=
nullptr);
178 return this->allocator->GetAllocatedSize();
182void Squirrel::CompileError(HSQUIRRELVM vm, std::string_view desc, std::string_view source, SQInteger line, SQInteger column)
184 std::string msg = fmt::format(
"Error {}:{}/{}: {}", source, line, column, desc);
190 if (func ==
nullptr) {
191 Debug(misc, 0,
"[Squirrel] Compile error: {}", msg);
201 if (func ==
nullptr) {
202 fmt::print(stderr,
"{}", s);
211 SQPRINTFUNCTION pf = sq_getprintfunc(
vm);
215 std::string msg = fmt::format(
"Your script made an error: {}\n", error);
218 if (func ==
nullptr) {
219 fmt::print(stderr,
"{}", msg);
225 sqstd_printcallstack(
vm);
227 sq_setprintfunc(
vm, pf);
232 std::string_view view;
234 if (sq_gettop(
vm) >= 1) {
235 if (SQ_SUCCEEDED(sq_getstring(
vm, -1, view))) {
249 if (func ==
nullptr) {
256void Squirrel::AddMethod(std::string_view method_name, SQFUNCTION proc, std::string_view params,
void *userdata,
int size,
bool suspendable)
260 sq_pushstring(this->
vm, fmt::format(
"{1}{0}{1}", method_name, suspendable ?
"@" :
""));
263 void *ptr = sq_newuserdata(
vm, size);
264 std::copy_n(
static_cast<std::byte *
>(userdata), size,
static_cast<std::byte *
>(ptr));
267 sq_newclosure(this->
vm, proc, size != 0 ? 1 : 0);
268 if (!params.empty()) sq_setparamscheck(this->
vm, params.size(), params);
269 sq_setnativeclosurename(this->
vm, -1, method_name);
270 sq_newslot(this->
vm, -3, SQFalse);
273 std::string squirrel_script = fmt::format(
274 "function {0}(...)\n"
276 " local args = [this];\n"
277 " for(local i = 0; i < vargc; i++) args.push(vargv[i]);\n"
278 " while(this[\"@{0}@\"].acall(args)); \n"
280 "return {0};\n", method_name);
282 sq_pushstring(this->
vm, method_name);
283 if (SQ_FAILED(sq_compilebuffer(this->
vm, squirrel_script, method_name, SQTrue))) NOT_REACHED();
284 sq_pushroottable(this->
vm);
285 if (SQ_FAILED(sq_call(this->
vm, 1, SQTrue, SQTrue))) NOT_REACHED();
286 sq_remove(this->
vm, -2);
287 sq_newslot(this->
vm, -3, SQFalse);
295 sq_pushstring(this->
vm, var_name);
296 sq_pushinteger(this->
vm, value);
297 sq_newslot(this->
vm, -3, SQTrue);
304 sq_pushstring(this->
vm, var_name);
305 sq_pushbool(this->
vm, value);
306 sq_newslot(this->
vm, -3, SQTrue);
313 sq_pushroottable(this->
vm);
314 sq_pushstring(this->
vm, class_name);
315 sq_newclass(this->
vm, SQFalse);
322 sq_pushroottable(this->
vm);
323 sq_pushstring(this->
vm, class_name);
324 sq_pushstring(this->
vm, parent_class);
325 if (SQ_FAILED(sq_get(this->
vm, -3))) {
326 Debug(misc, 0,
"[squirrel] Failed to initialize class '{}' based on parent class '{}'", class_name, parent_class);
327 Debug(misc, 0,
"[squirrel] Make sure that '{}' exists before trying to define '{}'", parent_class, class_name);
330 sq_newclass(this->
vm, SQTrue);
337 sq_newslot(
vm, -3, SQFalse);
346 int top = sq_gettop(this->
vm);
348 sq_pushobject(this->
vm, instance);
350 sq_pushstring(this->
vm, method_name);
351 if (SQ_FAILED(sq_get(this->
vm, -2))) {
352 sq_settop(this->
vm, top);
355 sq_settop(this->
vm, top);
375 this->
crashed = !sq_resumecatch(this->
vm, suspend);
377 this->allocator->CheckLimit();
378 return this->
vm->_suspended != 0;
385 sq_resumeerror(this->
vm);
391 sq_collectgarbage(this->
vm);
398 this->allocator->CheckLimit();
403 SQInteger last_target = this->
vm->_suspended_target;
405 int top = sq_gettop(this->
vm);
407 sq_pushobject(this->
vm, instance);
409 sq_pushstring(this->
vm, method_name);
410 if (SQ_FAILED(sq_get(this->
vm, -2))) {
411 Debug(misc, 0,
"[squirrel] Could not find '{}' in the class", method_name);
412 sq_settop(this->
vm, top);
416 sq_pushobject(this->
vm, instance);
417 if (SQ_FAILED(sq_call(this->
vm, 1, ret ==
nullptr ? SQFalse : SQTrue, SQTrue, suspend)))
return false;
418 if (ret !=
nullptr) sq_getstackobj(
vm, -1, ret);
421 if (suspend == -1 || !this->
IsSuspended()) sq_settop(this->
vm, top);
423 this->
vm->_suspended_target = last_target;
428bool Squirrel::CallStringMethod(HSQOBJECT instance, std::string_view method_name, std::string *res,
int suspend)
431 if (!this->
CallMethod(instance, method_name, &ret, suspend))
return false;
434 if (!str.has_value())
return false;
440bool Squirrel::CallIntegerMethod(HSQOBJECT instance, std::string_view method_name,
int *res,
int suspend)
443 if (!this->
CallMethod(instance, method_name, &ret, suspend))
return false;
444 if (ret._type != OT_INTEGER)
return false;
449bool Squirrel::CallBoolMethod(HSQOBJECT instance, std::string_view method_name,
bool *res,
int suspend)
452 if (!this->
CallMethod(instance, method_name, &ret, suspend))
return false;
453 if (ret._type != OT_BOOL)
return false;
458 bool Squirrel::CreateClassInstanceVM(HSQUIRRELVM vm,
const std::string &class_name,
void *real_instance, HSQOBJECT *instance, SQRELEASEHOOK release_hook,
bool prepend_API_name)
462 int oldtop = sq_gettop(
vm);
465 sq_pushroottable(
vm);
467 if (prepend_API_name) {
468 std::string prepended_class_name = fmt::format(
"{}{}", engine->
GetAPIName(), class_name);
469 sq_pushstring(
vm, prepended_class_name);
471 sq_pushstring(
vm, class_name);
474 if (SQ_FAILED(sq_get(
vm, -2))) {
475 Debug(misc, 0,
"[squirrel] Failed to find class by the name '{}{}'", prepend_API_name ? engine->
GetAPIName() :
"", class_name);
476 sq_settop(
vm, oldtop);
481 if (SQ_FAILED(sq_createinstance(
vm, -1))) {
482 Debug(misc, 0,
"[squirrel] Failed to create instance for class '{}{}'", prepend_API_name ? engine->
GetAPIName() :
"", class_name);
483 sq_settop(
vm, oldtop);
487 if (instance !=
nullptr) {
489 sq_getstackobj(
vm, -1, instance);
491 sq_addref(
vm, instance);
497 sq_setinstanceup(
vm, -1, real_instance);
498 if (release_hook !=
nullptr) sq_setreleasehook(
vm, -1, release_hook);
500 if (instance !=
nullptr) sq_settop(
vm, oldtop);
513 if (index < 0) index += sq_gettop(
vm) + 1;
515 std::string class_name = fmt::format(
"{}{}", engine->
GetAPIName(), tag);
516 sq_pushroottable(
vm);
517 sq_pushstring(
vm, class_name);
520 if (sq_instanceof(
vm) == SQTrue) {
522 SQUserPointer ptr =
nullptr;
523 if (SQ_SUCCEEDED(sq_getinstanceup(
vm, index, &ptr,
nullptr)))
return ptr;
525 throw sq_throwerror(
vm, fmt::format(
"parameter {} has an invalid type ; expected: '{}'", index - 1, class_name));
528Squirrel::Squirrel(std::string_view api_name) :
542 this->
vm = sq_open(1024);
546 sq_notifyallexceptions(this->
vm, _debug_script_level > 5);
551 sq_seterrorhandler(this->
vm);
554 sq_setforeignptr(this->
vm,
this);
556 sq_pushroottable(this->
vm);
560 sq_pushconsttable(this->
vm);
561 sq_setdelegate(this->
vm, -2);
572 size_t ReadInternal(std::span<char> buf)
574 size_t count = buf.size();
575 if (this->pos + count > this->size) {
576 count = this->size - this->pos;
578 if (count > 0) count = fread(buf.data(), 1, count, this->file);
584 SQFile(
FileHandle file,
size_t size) : file(std::move(file)), size(size), pos(0), consumer(buffer) {}
588 if (this->consumer.
GetBytesLeft() < min_size && this->pos < this->size) {
591 size_t buffer_size = this->buffer.size();
592 size_t read_size =
Align(min_size - buffer_size, 4096);
594 this->buffer.resize(buffer_size + read_size);
595 auto dest = std::span(this->buffer.data(), this->buffer.size()).subspan(buffer_size);
596 buffer_size += this->ReadInternal(dest);
597 this->buffer.resize(buffer_size);
601 return this->consumer;
604 size_t Read(
void *buf,
size_t max_size)
606 std::span<char> dest(
reinterpret_cast<char *
>(buf), max_size);
608 auto view = this->consumer.
Read(max_size);
609 std::copy(view.data(), view.data() + view.size(), dest.data());
610 size_t result_size = view.size();
612 if (result_size < max_size) {
614 result_size += this->ReadInternal(dest.subspan(result_size));
621static char32_t _io_file_lexfeed_ASCII(SQUserPointer file)
627static char32_t _io_file_lexfeed_UTF8(SQUserPointer file)
633static SQInteger _io_file_read(SQUserPointer file, SQUserPointer buf, SQInteger size)
635 SQInteger ret =
reinterpret_cast<SQFile *
>(file)->Read(buf, size);
636 if (ret == 0)
return -1;
644 std::optional<FileHandle> file = std::nullopt;
649 }
else if (this->
GetAPIName().starts_with(
"GS")) {
656 if (!file.has_value()) {
657 return sq_throwerror(
vm,
"cannot open the file");
659 unsigned short bom = 0;
661 if (fread(&bom, 1,
sizeof(bom), *file) !=
sizeof(bom))
return sq_throwerror(
vm,
"cannot read the file");;
666 case SQ_BYTECODE_STREAM_TAG: {
667 if (fseek(*file, -2, SEEK_CUR) < 0) {
668 return sq_throwerror(
vm,
"cannot seek the file");
671 SQFile f(std::move(*file), size);
672 if (SQ_SUCCEEDED(sq_readclosure(
vm, _io_file_read, &f))) {
675 return sq_throwerror(
vm,
"Couldn't read bytecode");
681 return sq_throwerror(
vm,
"I/O error");
684 if (fread(&uc, 1,
sizeof(uc), *file) !=
sizeof(uc) || uc != 0xBF) {
685 return sq_throwerror(
vm,
"Unrecognized encoding");
687 func = _io_file_lexfeed_UTF8;
692 func = _io_file_lexfeed_ASCII;
694 if (size >= 2 && fseek(*file, -2, SEEK_CUR) < 0) {
695 return sq_throwerror(
vm,
"cannot seek the file");
700 SQFile f(std::move(*file), size);
701 if (SQ_SUCCEEDED(sq_compile(
vm, func, &f, filename.c_str(), printerror))) {
712 if (in_root) sq_pushroottable(
vm);
714 SQInteger ops_left =
vm->_ops_till_suspend;
716 if (SQ_SUCCEEDED(
LoadFile(
vm, script, SQTrue))) {
718 if (SQ_SUCCEEDED(sq_call(
vm, 1, SQFalse, SQTrue, 100000))) {
721 vm->_ops_till_suspend = ops_left;
726 vm->_ops_till_suspend = ops_left;
727 Debug(misc, 0,
"[squirrel] Failed to compile '{}'", script);
746 sq_pushroottable(this->vm);
747 sq_pushnull(this->vm);
748 sq_setdelegate(this->vm, -2);
756 this->allocator->Reset();
765void Squirrel::InsertResult(
bool result)
769 sq_pushbool(this->vm, result);
771 vm->GetAt(
vm->_stackbase +
vm->_suspended_target) =
vm->GetUp(-1);
776void Squirrel::InsertResult(
int result)
780 sq_pushinteger(this->vm, result);
782 vm->GetAt(
vm->_stackbase +
vm->_suspended_target) =
vm->GetUp(-1);
789 vm->DecreaseOps(ops);
794 return this->vm->_suspended != 0;
810 return sq_can_suspend(this->vm);
815 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?
SQPrintFunc * print_func
Points to either nullptr, or a custom print handler.
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 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.
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