From 14246c9a6aa5afc99fc3e67c76e96e0c5588a010 Mon Sep 17 00:00:00 2001 From: Ethin Probst Date: Sat, 28 Sep 2024 07:47:13 -0500 Subject: [PATCH 1/4] Push because git asked me to --- .gitignore | 100 +- ASAddon/include/scriptarray.h | 288 +- ASAddon/plugin/readme.md | 18 +- ASAddon/plugin/scriptany.cpp | 6 +- ASAddon/plugin/scriptarray.cpp | 6 +- ASAddon/plugin/scriptdictionary.cpp | 6 +- ASAddon/plugin/scriptgrid.cpp | 6 +- ASAddon/plugin/scriptmathcomplex.cpp | 6 +- ASAddon/src/scriptarray.cpp | 4540 ++++++++--------- ASAddon/src/scripthelper.cpp | 2056 ++++---- build/build_linux.sh | 310 +- build/build_macos.sh | 236 +- doc/src/advanced/Plugin Creation.md | 374 +- .../main/java/org/libsdl/app/SDLSurface.java | 906 ++-- plugin/curl/_SConscript | 30 +- plugin/git/_SConscript | 24 +- plugin/git/git.cpp | 900 ++-- plugin/sqlite/_SConscript | 20 +- plugin/systemd_notify/_SConscript | 26 +- plugin/systemd_notify/sd_notify.cpp | 40 +- test/interact/lowlevel_touch.nvgt | 48 +- 21 files changed, 4973 insertions(+), 4973 deletions(-) diff --git a/.gitignore b/.gitignore index cfc3ffbd..1e0971c2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,50 +1,50 @@ -# build/binary artifacts -*.exe -*.dll -*.bin -*.obj -*.pdb -*.exp -*.lib -*.a -*.o -*.os -*.dylib -__pycache__ - -# other intermediet build -build/config.log -build/lastbuild -build/windev_path -src/version.cpp -.sconf_temp -.sconsign.dblite -*.orig -jni/.gradle -jni/build -install/nvgt_version.nsh -libs -obj -release/lib/3rd_party_licenses.html -release/lib_* - -# private user space and dependencies -user/** -!user/readme.md -jni/Custom.mk -droidev -iosdev -lindev -macosdev -windev -deps - -# website builds -web/public_html -web/src/docs - -# Files generated by operating systems that we don't want committed. -.DS_Store - -# Compilation databases; easy to generate -compile_commands.json +# build/binary artifacts +*.exe +*.dll +*.bin +*.obj +*.pdb +*.exp +*.lib +*.a +*.o +*.os +*.dylib +__pycache__ + +# other intermediet build +build/config.log +build/lastbuild +build/windev_path +src/version.cpp +.sconf_temp +.sconsign.dblite +*.orig +jni/.gradle +jni/build +install/nvgt_version.nsh +libs +obj +release/lib/3rd_party_licenses.html +release/lib_* + +# private user space and dependencies +user/** +!user/readme.md +jni/Custom.mk +droidev +iosdev +lindev +macosdev +windev +deps + +# website builds +web/public_html +web/src/docs + +# Files generated by operating systems that we don't want committed. +.DS_Store + +# Compilation databases; easy to generate +compile_commands.json diff --git a/ASAddon/include/scriptarray.h b/ASAddon/include/scriptarray.h index babfd3aa..90080c59 100644 --- a/ASAddon/include/scriptarray.h +++ b/ASAddon/include/scriptarray.h @@ -1,144 +1,144 @@ -#ifndef SCRIPTARRAY_H -#define SCRIPTARRAY_H - -#ifndef ANGELSCRIPT_H -// Avoid having to inform include path if header is already include before -#include -#endif - -// Sometimes it may be desired to use the same method names as used by C++ STL. -// This may for example reduce time when converting code from script to C++ or -// back. -// -// 0 = off -// 1 = on -#ifndef AS_USE_STLNAMES -#define AS_USE_STLNAMES 0 -#endif - -// Some prefer to use property accessors to get/set the length of the array -// This option registers the accessors instead of the method length() -#ifndef AS_USE_ACCESSORS -#define AS_USE_ACCESSORS 0 -#endif - -BEGIN_AS_NAMESPACE - -struct SArrayBuffer; -struct SArrayCache; - -class CScriptArray -{ -public: - // Set the memory functions that should be used by all CScriptArrays - static void SetMemoryFunctions(asALLOCFUNC_t allocFunc, asFREEFUNC_t freeFunc); - - // Factory functions - static CScriptArray *Create(asITypeInfo *ot); - static CScriptArray *Create(asITypeInfo *ot, asUINT length); - static CScriptArray *Create(asITypeInfo *ot, asUINT length, void *defaultValue); - static CScriptArray *Create(asITypeInfo *ot, void *listBuffer); - - // Memory management - void AddRef() const; - void Release() const; - - // Type information - asITypeInfo *GetArrayObjectType() const; - int GetArrayTypeId() const; - int GetElementTypeId() const; - - // Get the current size - asUINT GetSize() const; - - // Returns true if the array is empty - bool IsEmpty() const; - - // Pre-allocates memory for elements - void Reserve(asUINT maxElements); - - // Resize the array - void Resize(asUINT numElements); - - // Get a pointer to an element. Returns 0 if out of bounds - void *At(asINT64 index); - const void *At(asINT64 index) const; - - // Set value of an element. - // The value arg should be a pointer to the value that will be copied to the element. - // Remember, if the array holds handles the value parameter should be the - // address of the handle. The refCount of the object will also be incremented - void SetValue(asINT64 index, void *value); - - // Copy the contents of one array to another (only if the types are the same) - CScriptArray &operator=(const CScriptArray&); - - // Compare two arrays - bool operator==(const CScriptArray &) const; - - // Array manipulation - void InsertAt(asUINT index, void *value); - void InsertAt(asUINT index, const CScriptArray &arr); - void InsertLast(void *value); - void InsertLast(const CScriptArray &arr); - void RemoveAt(asUINT index); - void RemoveLast(); - void RemoveRange(asUINT start, asUINT count); - void SortAsc(); - void SortDesc(); - void SortAsc(asUINT startAt, asUINT count); - void SortDesc(asUINT startAt, asUINT count); - void Sort(asUINT startAt, asUINT count, bool asc); - void Sort(asIScriptFunction *less, asUINT startAt, asUINT count); - void Reverse(); - int Find(void *value) const; - int Find(asUINT startAt, void *value) const; - int FindByRef(void *ref) const; - int FindByRef(asUINT startAt, void *ref) const; - - // Return the address of internal buffer for direct manipulation of elements - void *GetBuffer(); - - // GC methods - int GetRefCount(); - void SetFlag(); - bool GetFlag(); - void EnumReferences(asIScriptEngine *engine); - void ReleaseAllHandles(asIScriptEngine *engine); - -protected: - mutable int refCount; - mutable bool gcFlag; - asITypeInfo *objType; - SArrayBuffer *buffer; - int elementSize; - int subTypeId; - - // Constructors - CScriptArray(asITypeInfo *ot, void *initBuf); // Called from script when initialized with list - CScriptArray(asUINT length, asITypeInfo *ot); - CScriptArray(asUINT length, void *defVal, asITypeInfo *ot); - CScriptArray(const CScriptArray &other); - virtual ~CScriptArray(); - - bool Less(const void *a, const void *b, bool asc); - void *GetArrayItemPointer(int index); - void *GetDataPointer(void *buffer); - void Copy(void *dst, void *src); - void Swap(void *a, void *b); - void Precache(); - bool CheckMaxSize(asUINT numElements); - void Resize(int delta, asUINT at); - void CreateBuffer(SArrayBuffer **buf, asUINT numElements); - void DeleteBuffer(SArrayBuffer *buf); - void CopyBuffer(SArrayBuffer *dst, SArrayBuffer *src); - void Construct(SArrayBuffer *buf, asUINT start, asUINT end); - void Destruct(SArrayBuffer *buf, asUINT start, asUINT end); - bool Equals(const void *a, const void *b, asIScriptContext *ctx, SArrayCache *cache) const; -}; - -void RegisterScriptArray(asIScriptEngine *engine, bool defaultArray); - -END_AS_NAMESPACE - -#endif +#ifndef SCRIPTARRAY_H +#define SCRIPTARRAY_H + +#ifndef ANGELSCRIPT_H +// Avoid having to inform include path if header is already include before +#include +#endif + +// Sometimes it may be desired to use the same method names as used by C++ STL. +// This may for example reduce time when converting code from script to C++ or +// back. +// +// 0 = off +// 1 = on +#ifndef AS_USE_STLNAMES +#define AS_USE_STLNAMES 0 +#endif + +// Some prefer to use property accessors to get/set the length of the array +// This option registers the accessors instead of the method length() +#ifndef AS_USE_ACCESSORS +#define AS_USE_ACCESSORS 0 +#endif + +BEGIN_AS_NAMESPACE + +struct SArrayBuffer; +struct SArrayCache; + +class CScriptArray +{ +public: + // Set the memory functions that should be used by all CScriptArrays + static void SetMemoryFunctions(asALLOCFUNC_t allocFunc, asFREEFUNC_t freeFunc); + + // Factory functions + static CScriptArray *Create(asITypeInfo *ot); + static CScriptArray *Create(asITypeInfo *ot, asUINT length); + static CScriptArray *Create(asITypeInfo *ot, asUINT length, void *defaultValue); + static CScriptArray *Create(asITypeInfo *ot, void *listBuffer); + + // Memory management + void AddRef() const; + void Release() const; + + // Type information + asITypeInfo *GetArrayObjectType() const; + int GetArrayTypeId() const; + int GetElementTypeId() const; + + // Get the current size + asUINT GetSize() const; + + // Returns true if the array is empty + bool IsEmpty() const; + + // Pre-allocates memory for elements + void Reserve(asUINT maxElements); + + // Resize the array + void Resize(asUINT numElements); + + // Get a pointer to an element. Returns 0 if out of bounds + void *At(asINT64 index); + const void *At(asINT64 index) const; + + // Set value of an element. + // The value arg should be a pointer to the value that will be copied to the element. + // Remember, if the array holds handles the value parameter should be the + // address of the handle. The refCount of the object will also be incremented + void SetValue(asINT64 index, void *value); + + // Copy the contents of one array to another (only if the types are the same) + CScriptArray &operator=(const CScriptArray&); + + // Compare two arrays + bool operator==(const CScriptArray &) const; + + // Array manipulation + void InsertAt(asUINT index, void *value); + void InsertAt(asUINT index, const CScriptArray &arr); + void InsertLast(void *value); + void InsertLast(const CScriptArray &arr); + void RemoveAt(asUINT index); + void RemoveLast(); + void RemoveRange(asUINT start, asUINT count); + void SortAsc(); + void SortDesc(); + void SortAsc(asUINT startAt, asUINT count); + void SortDesc(asUINT startAt, asUINT count); + void Sort(asUINT startAt, asUINT count, bool asc); + void Sort(asIScriptFunction *less, asUINT startAt, asUINT count); + void Reverse(); + int Find(void *value) const; + int Find(asUINT startAt, void *value) const; + int FindByRef(void *ref) const; + int FindByRef(asUINT startAt, void *ref) const; + + // Return the address of internal buffer for direct manipulation of elements + void *GetBuffer(); + + // GC methods + int GetRefCount(); + void SetFlag(); + bool GetFlag(); + void EnumReferences(asIScriptEngine *engine); + void ReleaseAllHandles(asIScriptEngine *engine); + +protected: + mutable int refCount; + mutable bool gcFlag; + asITypeInfo *objType; + SArrayBuffer *buffer; + int elementSize; + int subTypeId; + + // Constructors + CScriptArray(asITypeInfo *ot, void *initBuf); // Called from script when initialized with list + CScriptArray(asUINT length, asITypeInfo *ot); + CScriptArray(asUINT length, void *defVal, asITypeInfo *ot); + CScriptArray(const CScriptArray &other); + virtual ~CScriptArray(); + + bool Less(const void *a, const void *b, bool asc); + void *GetArrayItemPointer(int index); + void *GetDataPointer(void *buffer); + void Copy(void *dst, void *src); + void Swap(void *a, void *b); + void Precache(); + bool CheckMaxSize(asUINT numElements); + void Resize(int delta, asUINT at); + void CreateBuffer(SArrayBuffer **buf, asUINT numElements); + void DeleteBuffer(SArrayBuffer *buf); + void CopyBuffer(SArrayBuffer *dst, SArrayBuffer *src); + void Construct(SArrayBuffer *buf, asUINT start, asUINT end); + void Destruct(SArrayBuffer *buf, asUINT start, asUINT end); + bool Equals(const void *a, const void *b, asIScriptContext *ctx, SArrayCache *cache) const; +}; + +void RegisterScriptArray(asIScriptEngine *engine, bool defaultArray); + +END_AS_NAMESPACE + +#endif diff --git a/ASAddon/plugin/readme.md b/ASAddon/plugin/readme.md index 9a413354..e615f9b4 100644 --- a/ASAddon/plugin/readme.md +++ b/ASAddon/plugin/readme.md @@ -1,9 +1,9 @@ -# What is this? -During NVGT plugin development, particularly when making a shared rather than a static plugin, the following lines should exist at the top of any compilation unit that uses `` to insure that it uses the manually imported Angelscript symbols that were sent to the plugin from NVGT: - -``` -#define NVGT_PLUGIN_INCLUDE -#include "../../src/nvgt_plugin.h" -``` - -This directory contains shims that set this up for the common Angelscript addons, making them easier to include in a shared plugin (see ../plugin/curl, ../plugin/git etc). +# What is this? +During NVGT plugin development, particularly when making a shared rather than a static plugin, the following lines should exist at the top of any compilation unit that uses `` to insure that it uses the manually imported Angelscript symbols that were sent to the plugin from NVGT: + +``` +#define NVGT_PLUGIN_INCLUDE +#include "../../src/nvgt_plugin.h" +``` + +This directory contains shims that set this up for the common Angelscript addons, making them easier to include in a shared plugin (see ../plugin/curl, ../plugin/git etc). diff --git a/ASAddon/plugin/scriptany.cpp b/ASAddon/plugin/scriptany.cpp index e535361c..df53f1b8 100644 --- a/ASAddon/plugin/scriptany.cpp +++ b/ASAddon/plugin/scriptany.cpp @@ -1,3 +1,3 @@ -#define NVGT_PLUGIN_INCLUDE -#include "../../src/nvgt_plugin.h" -#include "../src/scriptany.cpp" +#define NVGT_PLUGIN_INCLUDE +#include "../../src/nvgt_plugin.h" +#include "../src/scriptany.cpp" diff --git a/ASAddon/plugin/scriptarray.cpp b/ASAddon/plugin/scriptarray.cpp index d114c664..1a64bbb0 100644 --- a/ASAddon/plugin/scriptarray.cpp +++ b/ASAddon/plugin/scriptarray.cpp @@ -1,3 +1,3 @@ -#define NVGT_PLUGIN_INCLUDE -#include "../../src/nvgt_plugin.h" -#include "../src/scriptarray.cpp" +#define NVGT_PLUGIN_INCLUDE +#include "../../src/nvgt_plugin.h" +#include "../src/scriptarray.cpp" diff --git a/ASAddon/plugin/scriptdictionary.cpp b/ASAddon/plugin/scriptdictionary.cpp index 4baa450e..25634e63 100644 --- a/ASAddon/plugin/scriptdictionary.cpp +++ b/ASAddon/plugin/scriptdictionary.cpp @@ -1,3 +1,3 @@ -#define NVGT_PLUGIN_INCLUDE -#include "../../src/nvgt_plugin.h" -#include "../src/scriptdictionary.cpp" +#define NVGT_PLUGIN_INCLUDE +#include "../../src/nvgt_plugin.h" +#include "../src/scriptdictionary.cpp" diff --git a/ASAddon/plugin/scriptgrid.cpp b/ASAddon/plugin/scriptgrid.cpp index 4b6d63b2..216e1afe 100644 --- a/ASAddon/plugin/scriptgrid.cpp +++ b/ASAddon/plugin/scriptgrid.cpp @@ -1,3 +1,3 @@ -#define NVGT_PLUGIN_INCLUDE -#include "../../src/nvgt_plugin.h" -#include "../src/scriptgrid.cpp" +#define NVGT_PLUGIN_INCLUDE +#include "../../src/nvgt_plugin.h" +#include "../src/scriptgrid.cpp" diff --git a/ASAddon/plugin/scriptmathcomplex.cpp b/ASAddon/plugin/scriptmathcomplex.cpp index 4f8ed838..571e61c2 100644 --- a/ASAddon/plugin/scriptmathcomplex.cpp +++ b/ASAddon/plugin/scriptmathcomplex.cpp @@ -1,3 +1,3 @@ -#define NVGT_PLUGIN_INCLUDE -#include "../../src/nvgt_plugin.h" -#include "../src/scriptmathcomplex.cpp" +#define NVGT_PLUGIN_INCLUDE +#include "../../src/nvgt_plugin.h" +#include "../src/scriptmathcomplex.cpp" diff --git a/ASAddon/src/scriptarray.cpp b/ASAddon/src/scriptarray.cpp index 15d5323f..3f57e21b 100644 --- a/ASAddon/src/scriptarray.cpp +++ b/ASAddon/src/scriptarray.cpp @@ -1,2270 +1,2270 @@ -#include -#include -#include -#include -#include // sprintf -#include -#include // std::sort - -#include "scriptarray.h" - -using namespace std; - -BEGIN_AS_NAMESPACE - -// This macro is used to avoid warnings about unused variables. -// Usually where the variables are only used in debug mode. -#define UNUSED_VAR(x) (void)(x) - -// Set the default memory routines -// Use the angelscript engine's memory routines by default -static asALLOCFUNC_t userAlloc = asAllocMem; -static asFREEFUNC_t userFree = asFreeMem; - -// Allows the application to set which memory routines should be used by the array object -void CScriptArray::SetMemoryFunctions(asALLOCFUNC_t allocFunc, asFREEFUNC_t freeFunc) -{ - userAlloc = allocFunc; - userFree = freeFunc; -} - -static void RegisterScriptArray_Native(asIScriptEngine *engine); -static void RegisterScriptArray_Generic(asIScriptEngine *engine); - -struct SArrayBuffer -{ - asDWORD maxElements; - asDWORD numElements; - asBYTE data[1]; -}; - -struct SArrayCache -{ - asIScriptFunction *cmpFunc; - asIScriptFunction *eqFunc; - int cmpFuncReturnCode; // To allow better error message in case of multiple matches - int eqFuncReturnCode; -}; - -// We just define a number here that we assume nobody else is using for -// object type user data. The add-ons have reserved the numbers 1000 -// through 1999 for this purpose, so we should be fine. -const asPWORD ARRAY_CACHE = 1000; - -static void CleanupTypeInfoArrayCache(asITypeInfo *type) -{ - SArrayCache *cache = reinterpret_cast(type->GetUserData(ARRAY_CACHE)); - if( cache ) - { - cache->~SArrayCache(); - userFree(cache); - } -} - -CScriptArray* CScriptArray::Create(asITypeInfo *ti, asUINT length) -{ - // Allocate the memory - void *mem = userAlloc(sizeof(CScriptArray)); - if( mem == 0 ) - { - asIScriptContext *ctx = asGetActiveContext(); - if( ctx ) - ctx->SetException("Out of memory"); - - return 0; - } - - // Initialize the object - CScriptArray *a = new(mem) CScriptArray(length, ti); - - return a; -} - -CScriptArray* CScriptArray::Create(asITypeInfo *ti, void *initList) -{ - // Allocate the memory - void *mem = userAlloc(sizeof(CScriptArray)); - if( mem == 0 ) - { - asIScriptContext *ctx = asGetActiveContext(); - if( ctx ) - ctx->SetException("Out of memory"); - - return 0; - } - - // Initialize the object - CScriptArray *a = new(mem) CScriptArray(ti, initList); - - return a; -} - -CScriptArray* CScriptArray::Create(asITypeInfo *ti, asUINT length, void *defVal) -{ - // Allocate the memory - void *mem = userAlloc(sizeof(CScriptArray)); - if( mem == 0 ) - { - asIScriptContext *ctx = asGetActiveContext(); - if( ctx ) - ctx->SetException("Out of memory"); - - return 0; - } - - // Initialize the object - CScriptArray *a = new(mem) CScriptArray(length, defVal, ti); - - return a; -} - -CScriptArray* CScriptArray::Create(asITypeInfo *ti) -{ - return CScriptArray::Create(ti, asUINT(0)); -} - -// This optional callback is called when the template type is first used by the compiler. -// It allows the application to validate if the template can be instantiated for the requested -// subtype at compile time, instead of at runtime. The output argument dontGarbageCollect -// allow the callback to tell the engine if the template instance type shouldn't be garbage collected, -// i.e. no asOBJ_GC flag. -static bool ScriptArrayTemplateCallback(asITypeInfo *ti, bool &dontGarbageCollect) -{ - // Make sure the subtype can be instantiated with a default factory/constructor, - // otherwise we won't be able to instantiate the elements. - int typeId = ti->GetSubTypeId(); - if( typeId == asTYPEID_VOID ) - return false; - if( (typeId & asTYPEID_MASK_OBJECT) && !(typeId & asTYPEID_OBJHANDLE) ) - { - asITypeInfo *subtype = ti->GetEngine()->GetTypeInfoById(typeId); - asDWORD flags = subtype->GetFlags(); - if( (flags & asOBJ_VALUE) && !(flags & asOBJ_POD) ) - { - // Verify that there is a default constructor - bool found = false; - for( asUINT n = 0; n < subtype->GetBehaviourCount(); n++ ) - { - asEBehaviours beh; - asIScriptFunction *func = subtype->GetBehaviourByIndex(n, &beh); - if( beh != asBEHAVE_CONSTRUCT ) continue; - - if( func->GetParamCount() == 0 ) - { - // Found the default constructor - found = true; - break; - } - } - - if( !found ) - { - // There is no default constructor - // TODO: Should format the message to give the name of the subtype for better understanding - ti->GetEngine()->WriteMessage("array", 0, 0, asMSGTYPE_ERROR, "The subtype has no default constructor"); - return false; - } - } - else if( (flags & asOBJ_REF) ) - { - bool found = false; - - // If value assignment for ref type has been disabled then the array - // can be created if the type has a default factory function - if( !ti->GetEngine()->GetEngineProperty(asEP_DISALLOW_VALUE_ASSIGN_FOR_REF_TYPE) ) - { - // Verify that there is a default factory - for( asUINT n = 0; n < subtype->GetFactoryCount(); n++ ) - { - asIScriptFunction *func = subtype->GetFactoryByIndex(n); - if( func->GetParamCount() == 0 ) - { - // Found the default factory - found = true; - break; - } - } - } - - if( !found ) - { - // No default factory - char buffer[1024]; - snprintf(buffer, sizeof(buffer), "The subtype '%s' has no default factory", subtype ? subtype->GetEngine()->GetTypeDeclaration(subtype->GetTypeId()) : "UNKNOWN"); - ti->GetEngine()->WriteMessage("array", 0, 0, asMSGTYPE_ERROR, buffer); - return false; - } - } - - // If the object type is not garbage collected then the array also doesn't need to be - if( !(flags & asOBJ_GC) ) - dontGarbageCollect = true; - } - else if( !(typeId & asTYPEID_OBJHANDLE) ) - { - // Arrays with primitives cannot form circular references, - // thus there is no need to garbage collect them - dontGarbageCollect = true; - } - else - { - assert( typeId & asTYPEID_OBJHANDLE ); - - // It is not necessary to set the array as garbage collected for all handle types. - // If it is possible to determine that the handle cannot refer to an object type - // that can potentially form a circular reference with the array then it is not - // necessary to make the array garbage collected. - asITypeInfo *subtype = ti->GetEngine()->GetTypeInfoById(typeId); - asDWORD flags = subtype->GetFlags(); - if( !(flags & asOBJ_GC) ) - { - if( (flags & asOBJ_SCRIPT_OBJECT) ) - { - // Even if a script class is by itself not garbage collected, it is possible - // that classes that derive from it may be, so it is not possible to know - // that no circular reference can occur. - if( (flags & asOBJ_NOINHERIT) ) - { - // A script class declared as final cannot be inherited from, thus - // we can be certain that the object cannot be garbage collected. - dontGarbageCollect = true; - } - } - else - { - // For application registered classes we assume the application knows - // what it is doing and don't mark the array as garbage collected unless - // the type is also garbage collected. - dontGarbageCollect = true; - } - } - } - - // The type is ok - return true; -} - -// Registers the template array type -void RegisterScriptArray(asIScriptEngine *engine, bool defaultArray) -{ - if( strstr(asGetLibraryOptions(), "AS_MAX_PORTABILITY") == 0 ) - RegisterScriptArray_Native(engine); - else - RegisterScriptArray_Generic(engine); - - if( defaultArray ) - { - int r = engine->RegisterDefaultArrayType("array"); assert( r >= 0 ); - UNUSED_VAR(r); - } -} - -static void RegisterScriptArray_Native(asIScriptEngine *engine) -{ - int r = 0; - UNUSED_VAR(r); - - // Register the object type user data clean up - engine->SetTypeInfoUserDataCleanupCallback(CleanupTypeInfoArrayCache, ARRAY_CACHE); - - // Register the array type as a template - r = engine->RegisterObjectType("array", 0, asOBJ_REF | asOBJ_GC | asOBJ_TEMPLATE); assert( r >= 0 ); - - // Register a callback for validating the subtype before it is used - r = engine->RegisterObjectBehaviour("array", asBEHAVE_TEMPLATE_CALLBACK, "bool f(int&in, bool&out)", asFUNCTION(ScriptArrayTemplateCallback), asCALL_CDECL); assert( r >= 0 ); - - // Templates receive the object type as the first parameter. To the script writer this is hidden - r = engine->RegisterObjectBehaviour("array", asBEHAVE_FACTORY, "array@ f(int&in)", asFUNCTIONPR(CScriptArray::Create, (asITypeInfo*), CScriptArray*), asCALL_CDECL); assert( r >= 0 ); - r = engine->RegisterObjectBehaviour("array", asBEHAVE_FACTORY, "array@ f(int&in, uint length) explicit", asFUNCTIONPR(CScriptArray::Create, (asITypeInfo*, asUINT), CScriptArray*), asCALL_CDECL); assert( r >= 0 ); - r = engine->RegisterObjectBehaviour("array", asBEHAVE_FACTORY, "array@ f(int&in, uint length, const T &in value)", asFUNCTIONPR(CScriptArray::Create, (asITypeInfo*, asUINT, void *), CScriptArray*), asCALL_CDECL); assert( r >= 0 ); - - // Register the factory that will be used for initialization lists - r = engine->RegisterObjectBehaviour("array", asBEHAVE_LIST_FACTORY, "array@ f(int&in type, int&in list) {repeat T}", asFUNCTIONPR(CScriptArray::Create, (asITypeInfo*, void*), CScriptArray*), asCALL_CDECL); assert( r >= 0 ); - - // The memory management methods - r = engine->RegisterObjectBehaviour("array", asBEHAVE_ADDREF, "void f()", asMETHOD(CScriptArray,AddRef), asCALL_THISCALL); assert( r >= 0 ); - r = engine->RegisterObjectBehaviour("array", asBEHAVE_RELEASE, "void f()", asMETHOD(CScriptArray,Release), asCALL_THISCALL); assert( r >= 0 ); - - // The index operator returns the template subtype - r = engine->RegisterObjectMethod("array", "T &opIndex(int64 index)", asMETHODPR(CScriptArray, At, (asINT64), void*), asCALL_THISCALL); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "const T &opIndex(int64 index) const", asMETHODPR(CScriptArray, At, (asINT64) const, const void*), asCALL_THISCALL); assert( r >= 0 ); - - // The assignment operator - r = engine->RegisterObjectMethod("array", "array &opAssign(const array&in)", asMETHOD(CScriptArray, operator=), asCALL_THISCALL); assert( r >= 0 ); - - // Other methods - r = engine->RegisterObjectMethod("array", "void insert_at(uint index, const T&in value)", asMETHODPR(CScriptArray, InsertAt, (asUINT, void *), void), asCALL_THISCALL); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "void insert_at(uint index, const array& arr)", asMETHODPR(CScriptArray, InsertAt, (asUINT, const CScriptArray &), void), asCALL_THISCALL); assert(r >= 0); - r = engine->RegisterObjectMethod("array", "void insert_last(const T&in value)", asMETHODPR(CScriptArray, InsertLast, (void*), void), asCALL_THISCALL); assert(r >= 0); - r = engine->RegisterObjectMethod("array", "void insert_last(const array& arr)", asMETHODPR(CScriptArray, InsertLast, (const CScriptArray &), void), asCALL_THISCALL); assert(r >= 0); - r = engine->RegisterObjectMethod("array", "void remove_at(uint index)", asMETHOD(CScriptArray, RemoveAt), asCALL_THISCALL); assert(r >= 0); - r = engine->RegisterObjectMethod("array", "void remove_last()", asMETHOD(CScriptArray, RemoveLast), asCALL_THISCALL); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "void remove_range(uint start, uint count)", asMETHOD(CScriptArray, RemoveRange), asCALL_THISCALL); assert(r >= 0); - // TODO: Should length() and resize() be deprecated as the property accessors do the same thing? - // TODO: Register as size() for consistency with other types -#if AS_USE_ACCESSORS != 1 - r = engine->RegisterObjectMethod("array", "uint length() const", asMETHOD(CScriptArray, GetSize), asCALL_THISCALL); assert( r >= 0 ); -#endif - r = engine->RegisterObjectMethod("array", "void reserve(uint length)", asMETHOD(CScriptArray, Reserve), asCALL_THISCALL); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "void resize(uint length)", asMETHODPR(CScriptArray, Resize, (asUINT), void), asCALL_THISCALL); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "void sort_ascending()", asMETHODPR(CScriptArray, SortAsc, (), void), asCALL_THISCALL); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "void sort_ascending(uint startAt, uint count)", asMETHODPR(CScriptArray, SortAsc, (asUINT, asUINT), void), asCALL_THISCALL); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "void sort_descending()", asMETHODPR(CScriptArray, SortDesc, (), void), asCALL_THISCALL); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "void sort_descending(uint startAt, uint count)", asMETHODPR(CScriptArray, SortDesc, (asUINT, asUINT), void), asCALL_THISCALL); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "void reverse()", asMETHOD(CScriptArray, Reverse), asCALL_THISCALL); assert( r >= 0 ); - // The token 'if_handle_then_const' tells the engine that if the type T is a handle, then it should refer to a read-only object - r = engine->RegisterObjectMethod("array", "int find(const T&in if_handle_then_const value) const", asMETHODPR(CScriptArray, Find, (void*) const, int), asCALL_THISCALL); assert( r >= 0 ); - // TODO: It should be "int find(const T&in value, uint startAt = 0) const" - r = engine->RegisterObjectMethod("array", "int find(uint startAt, const T&in if_handle_then_const value) const", asMETHODPR(CScriptArray, Find, (asUINT, void*) const, int), asCALL_THISCALL); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "int find_by_ref(const T&in if_handle_then_const value) const", asMETHODPR(CScriptArray, FindByRef, (void*) const, int), asCALL_THISCALL); assert( r >= 0 ); - // TODO: It should be "int find_by_ref(const T&in value, uint startAt = 0) const" - r = engine->RegisterObjectMethod("array", "int find_by_ref(uint startAt, const T&in if_handle_then_const value) const", asMETHODPR(CScriptArray, FindByRef, (asUINT, void*) const, int), asCALL_THISCALL); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "bool opEquals(const array&in) const", asMETHOD(CScriptArray, operator==), asCALL_THISCALL); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "bool is_empty() const", asMETHOD(CScriptArray, IsEmpty), asCALL_THISCALL); assert( r >= 0 ); - - // Sort with callback for comparison - r = engine->RegisterFuncdef("bool array::less(const T&in if_handle_then_const a, const T&in if_handle_then_const b)"); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "void sort(const less &in, uint startAt = 0, uint count = uint(-1))", asMETHODPR(CScriptArray, Sort, (asIScriptFunction*, asUINT, asUINT), void), asCALL_THISCALL); assert(r >= 0); - -#if AS_USE_STLNAMES != 1 && AS_USE_ACCESSORS == 1 - // Register virtual properties - r = engine->RegisterObjectMethod("array", "uint get_length() const property", asMETHOD(CScriptArray, GetSize), asCALL_THISCALL); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "void set_length(uint) property", asMETHODPR(CScriptArray, Resize, (asUINT), void), asCALL_THISCALL); assert( r >= 0 ); -#endif - - // Register GC behaviours in case the array needs to be garbage collected - r = engine->RegisterObjectBehaviour("array", asBEHAVE_GETREFCOUNT, "int f()", asMETHOD(CScriptArray, GetRefCount), asCALL_THISCALL); assert( r >= 0 ); - r = engine->RegisterObjectBehaviour("array", asBEHAVE_SETGCFLAG, "void f()", asMETHOD(CScriptArray, SetFlag), asCALL_THISCALL); assert( r >= 0 ); - r = engine->RegisterObjectBehaviour("array", asBEHAVE_GETGCFLAG, "bool f()", asMETHOD(CScriptArray, GetFlag), asCALL_THISCALL); assert( r >= 0 ); - r = engine->RegisterObjectBehaviour("array", asBEHAVE_ENUMREFS, "void f(int&in)", asMETHOD(CScriptArray, EnumReferences), asCALL_THISCALL); assert( r >= 0 ); - r = engine->RegisterObjectBehaviour("array", asBEHAVE_RELEASEREFS, "void f(int&in)", asMETHOD(CScriptArray, ReleaseAllHandles), asCALL_THISCALL); assert( r >= 0 ); - -#if AS_USE_STLNAMES == 1 - // Same as length - r = engine->RegisterObjectMethod("array", "uint size() const", asMETHOD(CScriptArray, GetSize), asCALL_THISCALL); assert( r >= 0 ); - // Same as isEmpty - r = engine->RegisterObjectMethod("array", "bool empty() const", asMETHOD(CScriptArray, IsEmpty), asCALL_THISCALL); assert( r >= 0 ); - // Same as insertLast - r = engine->RegisterObjectMethod("array", "void push_back(const T&in)", asMETHODPR(CScriptArray, InsertLast, (void*), void), asCALL_THISCALL); assert( r >= 0 ); - // Same as removeLast - r = engine->RegisterObjectMethod("array", "void pop_back()", asMETHOD(CScriptArray, RemoveLast), asCALL_THISCALL); assert( r >= 0 ); - // Same as insertAt - r = engine->RegisterObjectMethod("array", "void insert(uint index, const T&in value)", asMETHODPR(CScriptArray, InsertAt, (asUINT, void *), void), asCALL_THISCALL); assert(r >= 0); - r = engine->RegisterObjectMethod("array", "void insert(uint index, const array& arr)", asMETHODPR(CScriptArray, InsertAt, (asUINT, const CScriptArray &), void), asCALL_THISCALL); assert(r >= 0); - // Same as removeAt - r = engine->RegisterObjectMethod("array", "void erase(uint)", asMETHOD(CScriptArray, RemoveAt), asCALL_THISCALL); assert( r >= 0 ); -#endif -} - -CScriptArray &CScriptArray::operator=(const CScriptArray &other) -{ - // Only perform the copy if the array types are the same - if( &other != this && - other.GetArrayObjectType() == GetArrayObjectType() ) - { - // Make sure the arrays are of the same size - Resize(other.buffer->numElements); - - // Copy the value of each element - CopyBuffer(buffer, other.buffer); - } - - return *this; -} - -CScriptArray::CScriptArray(asITypeInfo *ti, void *buf) -{ - // The object type should be the template instance of the array - assert( ti && string(ti->GetName()) == "array" ); - - refCount = 1; - gcFlag = false; - objType = ti; - objType->AddRef(); - buffer = 0; - - Precache(); - - asIScriptEngine *engine = ti->GetEngine(); - - // Determine element size - if( subTypeId & asTYPEID_MASK_OBJECT ) - elementSize = sizeof(asPWORD); - else - elementSize = engine->GetSizeOfPrimitiveType(subTypeId); - - // Determine the initial size from the buffer - asUINT length = *(asUINT*)buf; - - // Make sure the array size isn't too large for us to handle - if( !CheckMaxSize(length) ) - { - // Don't continue with the initialization - return; - } - - // Copy the values of the array elements from the buffer - if( (ti->GetSubTypeId() & asTYPEID_MASK_OBJECT) == 0 ) - { - CreateBuffer(&buffer, length); - - // Copy the values of the primitive type into the internal buffer - if( length > 0 ) - memcpy(At(0), (((asUINT*)buf)+1), length * elementSize); - } - else if( ti->GetSubTypeId() & asTYPEID_OBJHANDLE ) - { - CreateBuffer(&buffer, length); - - // Copy the handles into the internal buffer - if( length > 0 ) - memcpy(At(0), (((asUINT*)buf)+1), length * elementSize); - - // With object handles it is safe to clear the memory in the received buffer - // instead of increasing the ref count. It will save time both by avoiding the - // call the increase ref, and also relieve the engine from having to release - // its references too - memset((((asUINT*)buf)+1), 0, length * elementSize); - } - else if( ti->GetSubType()->GetFlags() & asOBJ_REF ) - { - // Only allocate the buffer, but not the objects - subTypeId |= asTYPEID_OBJHANDLE; - CreateBuffer(&buffer, length); - if (!buffer) - throw std::bad_alloc(); - subTypeId &= ~asTYPEID_OBJHANDLE; - - // Copy the handles into the internal buffer - if( length > 0 ) - memcpy(buffer->data, (((asUINT*)buf)+1), length * elementSize); - - // For ref types we can do the same as for handles, as they are - // implicitly stored as handles. - memset((((asUINT*)buf)+1), 0, length * elementSize); - } - else - { - // TODO: Optimize by calling the copy constructor of the object instead of - // constructing with the default constructor and then assigning the value - // TODO: With C++11 ideally we should be calling the move constructor, instead - // of the copy constructor as the engine will just discard the objects in the - // buffer afterwards. - CreateBuffer(&buffer, length); - - // For value types we need to call the opAssign for each individual object - for( asUINT n = 0; n < length; n++ ) - { - void *obj = At(n); - asBYTE *srcObj = (asBYTE*)buf; - srcObj += 4 + n*ti->GetSubType()->GetSize(); - engine->AssignScriptObject(obj, srcObj, ti->GetSubType()); - } - } - - // Notify the GC of the successful creation - if( objType->GetFlags() & asOBJ_GC ) - objType->GetEngine()->NotifyGarbageCollectorOfNewObject(this, objType); -} - -CScriptArray::CScriptArray(asUINT length, asITypeInfo *ti) -{ - // The object type should be the template instance of the array - assert( ti && string(ti->GetName()) == "array" ); - - refCount = 1; - gcFlag = false; - objType = ti; - objType->AddRef(); - buffer = 0; - - Precache(); - - // Determine element size - if( subTypeId & asTYPEID_MASK_OBJECT ) - elementSize = sizeof(asPWORD); - else - elementSize = objType->GetEngine()->GetSizeOfPrimitiveType(subTypeId); - - // Make sure the array size isn't too large for us to handle - if( !CheckMaxSize(length) ) - { - // Don't continue with the initialization - return; - } - - CreateBuffer(&buffer, length); - - // Notify the GC of the successful creation - if( objType->GetFlags() & asOBJ_GC ) - objType->GetEngine()->NotifyGarbageCollectorOfNewObject(this, objType); -} - -CScriptArray::CScriptArray(const CScriptArray &other) -{ - refCount = 1; - gcFlag = false; - objType = other.objType; - objType->AddRef(); - buffer = 0; - - Precache(); - - elementSize = other.elementSize; - - if( objType->GetFlags() & asOBJ_GC ) - objType->GetEngine()->NotifyGarbageCollectorOfNewObject(this, objType); - - CreateBuffer(&buffer, 0); - - // Copy the content - *this = other; -} - -CScriptArray::CScriptArray(asUINT length, void *defVal, asITypeInfo *ti) -{ - // The object type should be the template instance of the array - assert( ti && string(ti->GetName()) == "array" ); - - refCount = 1; - gcFlag = false; - objType = ti; - objType->AddRef(); - buffer = 0; - - Precache(); - - // Determine element size - if( subTypeId & asTYPEID_MASK_OBJECT ) - elementSize = sizeof(asPWORD); - else - elementSize = objType->GetEngine()->GetSizeOfPrimitiveType(subTypeId); - - // Make sure the array size isn't too large for us to handle - if( !CheckMaxSize(length) ) - { - // Don't continue with the initialization - return; - } - - CreateBuffer(&buffer, length); - - // Notify the GC of the successful creation - if( objType->GetFlags() & asOBJ_GC ) - objType->GetEngine()->NotifyGarbageCollectorOfNewObject(this, objType); - - // Initialize the elements with the default value - for( asUINT n = 0; n < GetSize(); n++ ) - SetValue(n, defVal); -} - -void CScriptArray::SetValue(asINT64 index, void *value) -{ - // At() will take care of the out-of-bounds checking, though - // if called from the application then nothing will be done - void *ptr = At(index); - if( ptr == 0 ) return; - - if ((subTypeId & ~asTYPEID_MASK_SEQNBR) && !(subTypeId & asTYPEID_OBJHANDLE)) - { - asITypeInfo *subType = objType->GetSubType(); - if (subType->GetFlags() & asOBJ_ASHANDLE) - { - // For objects that should work as handles we must use the opHndlAssign method - // TODO: Must support alternative syntaxes as well - // TODO: Move the lookup of the opHndlAssign method to Precache() so it is only done once - string decl = string(subType->GetName()) + "& opHndlAssign(const " + string(subType->GetName()) + "&in)"; - asIScriptFunction* func = subType->GetMethodByDecl(decl.c_str()); - if (func) - { - // TODO: Reuse active context if existing - asIScriptEngine* engine = objType->GetEngine(); - asIScriptContext* ctx = engine->RequestContext(); - ctx->Prepare(func); - ctx->SetObject(ptr); - ctx->SetArgAddress(0, value); - // TODO: Handle errors - ctx->Execute(); - engine->ReturnContext(ctx); - } - else - { - // opHndlAssign doesn't exist, so try ordinary value assign instead - objType->GetEngine()->AssignScriptObject(ptr, value, subType); - } - } - else - objType->GetEngine()->AssignScriptObject(ptr, value, subType); - } - else if( subTypeId & asTYPEID_OBJHANDLE ) - { - void *tmp = *(void**)ptr; - *(void**)ptr = *(void**)value; - objType->GetEngine()->AddRefScriptObject(*(void**)value, objType->GetSubType()); - if( tmp ) - objType->GetEngine()->ReleaseScriptObject(tmp, objType->GetSubType()); - } - else if( subTypeId == asTYPEID_BOOL || - subTypeId == asTYPEID_INT8 || - subTypeId == asTYPEID_UINT8 ) - *(char*)ptr = *(char*)value; - else if( subTypeId == asTYPEID_INT16 || - subTypeId == asTYPEID_UINT16 ) - *(short*)ptr = *(short*)value; - else if( subTypeId == asTYPEID_INT32 || - subTypeId == asTYPEID_UINT32 || - subTypeId == asTYPEID_FLOAT || - subTypeId > asTYPEID_DOUBLE ) // enums have a type id larger than doubles - *(int*)ptr = *(int*)value; - else if( subTypeId == asTYPEID_INT64 || - subTypeId == asTYPEID_UINT64 || - subTypeId == asTYPEID_DOUBLE ) - *(double*)ptr = *(double*)value; -} - -CScriptArray::~CScriptArray() -{ - if( buffer ) - { - DeleteBuffer(buffer); - buffer = 0; - } - if( objType ) objType->Release(); -} - -asUINT CScriptArray::GetSize() const -{ - return buffer->numElements; -} - -bool CScriptArray::IsEmpty() const -{ - return buffer->numElements == 0; -} - -void CScriptArray::Reserve(asUINT maxElements) -{ - if( maxElements <= buffer->maxElements ) - return; - - if( !CheckMaxSize(maxElements) ) - return; - - // Allocate memory for the buffer - SArrayBuffer *newBuffer = reinterpret_cast(userAlloc(sizeof(SArrayBuffer)-1 + elementSize*maxElements)); - if( newBuffer ) - { - newBuffer->numElements = buffer->numElements; - newBuffer->maxElements = maxElements; - } - else - { - // Out of memory - asIScriptContext *ctx = asGetActiveContext(); - if( ctx ) - ctx->SetException("Out of memory"); - return; - } - - // As objects in arrays of objects are not stored inline, it is safe to use memcpy here - // since we're just copying the pointers to objects and not the actual objects. - memcpy(newBuffer->data, buffer->data, buffer->numElements*elementSize); - - // Release the old buffer - userFree(buffer); - - buffer = newBuffer; -} - -void CScriptArray::Resize(asUINT numElements) -{ - if( !CheckMaxSize(numElements) ) - return; - - Resize((int)numElements - (int)buffer->numElements, (asUINT)-1); -} - -void CScriptArray::RemoveRange(asUINT start, asUINT count) -{ - if (count == 0) - return; - - if( buffer == 0 || start > buffer->numElements ) - { - // If this is called from a script we raise a script exception - asIScriptContext *ctx = asGetActiveContext(); - if (ctx) - ctx->SetException("Index out of bounds"); - return; - } - - // Cap count to the end of the array - if (start + count > buffer->numElements) - count = buffer->numElements - start; - - // Destroy the elements that are being removed - Destruct(buffer, start, start + count); - - // Compact the elements - // As objects in arrays of objects are not stored inline, it is safe to use memmove here - // since we're just copying the pointers to objects and not the actual objects. - memmove(buffer->data + start*elementSize, buffer->data + (start + count)*elementSize, (buffer->numElements - start - count)*elementSize); - buffer->numElements -= count; -} - -// Internal -void CScriptArray::Resize(int delta, asUINT at) -{ - if( delta < 0 ) - { - if( -delta > (int)buffer->numElements ) - delta = -(int)buffer->numElements; - if( at > buffer->numElements + delta ) - at = buffer->numElements + delta; - } - else if( delta > 0 ) - { - // Make sure the array size isn't too large for us to handle - if( delta > 0 && !CheckMaxSize(buffer->numElements + delta) ) - return; - - if( at > buffer->numElements ) - at = buffer->numElements; - } - - if( delta == 0 ) return; - - if( buffer->maxElements < buffer->numElements + delta ) - { - // Allocate memory for the buffer - SArrayBuffer *newBuffer = reinterpret_cast(userAlloc(sizeof(SArrayBuffer)-1 + elementSize*(buffer->numElements + delta))); - if( newBuffer ) - { - newBuffer->numElements = buffer->numElements + delta; - newBuffer->maxElements = newBuffer->numElements; - } - else - { - // Out of memory - asIScriptContext *ctx = asGetActiveContext(); - if( ctx ) - ctx->SetException("Out of memory"); - return; - } - - // As objects in arrays of objects are not stored inline, it is safe to use memcpy here - // since we're just copying the pointers to objects and not the actual objects. - memcpy(newBuffer->data, buffer->data, at*elementSize); - if( at < buffer->numElements ) - memcpy(newBuffer->data + (at+delta)*elementSize, buffer->data + at*elementSize, (buffer->numElements-at)*elementSize); - - // Initialize the new elements with default values - Construct(newBuffer, at, at+delta); - - // Release the old buffer - userFree(buffer); - - buffer = newBuffer; - } - else if( delta < 0 ) - { - Destruct(buffer, at, at-delta); - // As objects in arrays of objects are not stored inline, it is safe to use memmove here - // since we're just copying the pointers to objects and not the actual objects. - memmove(buffer->data + at*elementSize, buffer->data + (at-delta)*elementSize, (buffer->numElements - (at-delta))*elementSize); - buffer->numElements += delta; - } - else - { - // As objects in arrays of objects are not stored inline, it is safe to use memmove here - // since we're just copying the pointers to objects and not the actual objects. - memmove(buffer->data + (at+delta)*elementSize, buffer->data + at*elementSize, (buffer->numElements - at)*elementSize); - Construct(buffer, at, at+delta); - buffer->numElements += delta; - } -} - -// internal -bool CScriptArray::CheckMaxSize(asUINT numElements) -{ - // This code makes sure the size of the buffer that is allocated - // for the array doesn't overflow and becomes smaller than requested - - asUINT maxSize = 0xFFFFFFFFul - sizeof(SArrayBuffer) + 1; - if( elementSize > 0 ) - maxSize /= elementSize; - - if( numElements > maxSize ) - { - asIScriptContext *ctx = asGetActiveContext(); - if( ctx ) - ctx->SetException("Too large array size"); - - return false; - } - - // OK - return true; -} - -asITypeInfo *CScriptArray::GetArrayObjectType() const -{ - return objType; -} - -int CScriptArray::GetArrayTypeId() const -{ - return objType->GetTypeId(); -} - -int CScriptArray::GetElementTypeId() const -{ - return subTypeId; -} - -void CScriptArray::InsertAt(asUINT index, void *value) -{ - if( index > buffer->numElements ) - { - // If this is called from a script we raise a script exception - asIScriptContext *ctx = asGetActiveContext(); - if( ctx ) - ctx->SetException("Index out of bounds"); - return; - } - - // Make room for the new element - Resize(1, index); - - // Set the value of the new element - SetValue(index, value); -} - -void CScriptArray::InsertAt(asUINT index, const CScriptArray &arr) -{ - if (index > buffer->numElements) - { - asIScriptContext *ctx = asGetActiveContext(); - if (ctx) - ctx->SetException("Index out of bounds"); - return; - } - - if (objType != arr.objType) - { - // This shouldn't really be possible to happen when - // called from a script, but let's check for it anyway - asIScriptContext *ctx = asGetActiveContext(); - if (ctx) - ctx->SetException("Mismatching array types"); - return; - } - - asUINT elements = arr.GetSize(); - Resize(elements, index); - if (&arr != this) - { - for (asUINT n = 0; n < arr.GetSize(); n++) - { - // This const cast is allowed, since we know the - // value will only be used to make a copy of it - void *value = const_cast(arr.At(n)); - SetValue(index + n, value); - } - } - else - { - // The array that is being inserted is the same as this one. - // So we should iterate over the elements before the index, - // and then the elements after - for (asUINT n = 0; n < index; n++) - { - // This const cast is allowed, since we know the - // value will only be used to make a copy of it - void *value = const_cast(arr.At(n)); - SetValue(index + n, value); - } - - for (asUINT n = index + elements, m = 0; n < arr.GetSize(); n++, m++) - { - // This const cast is allowed, since we know the - // value will only be used to make a copy of it - void *value = const_cast(arr.At(n)); - SetValue(index + index + m, value); - } - } -} - -void CScriptArray::InsertLast(void *value) -{ - InsertAt(buffer->numElements, value); -} - -void CScriptArray::InsertLast(const CScriptArray &arr) -{ - InsertAt(buffer->numElements, arr); -} - -void CScriptArray::RemoveAt(asUINT index) -{ - if( index >= buffer->numElements ) - { - // If this is called from a script we raise a script exception - asIScriptContext *ctx = asGetActiveContext(); - if( ctx ) - ctx->SetException("Index out of bounds"); - return; - } - - // Remove the element - Resize(-1, index); -} - -void CScriptArray::RemoveLast() -{ - RemoveAt(buffer->numElements-1); -} - -// Return a pointer to the array element. Returns 0 if the index is out of bounds -const void *CScriptArray::At(asINT64 index) const -{ - if( index < 0) index = buffer->numElements + index; - if( buffer == 0 || index < 0 || index >= buffer->numElements ) - { - // If this is called from a script we raise a script exception - asIScriptContext *ctx = asGetActiveContext(); - if( ctx ) - ctx->SetException("Index out of bounds"); - return 0; - } - - if( (subTypeId & asTYPEID_MASK_OBJECT) && !(subTypeId & asTYPEID_OBJHANDLE) ) - return *(void**)(buffer->data + elementSize*index); - else - return buffer->data + elementSize*index; -} -void *CScriptArray::At(asINT64 index) -{ - return const_cast(const_cast(this)->At(index)); -} - -void *CScriptArray::GetBuffer() -{ - return buffer->data; -} - - -// internal -void CScriptArray::CreateBuffer(SArrayBuffer **buf, asUINT numElements) -{ - *buf = reinterpret_cast(userAlloc(sizeof(SArrayBuffer)-1+elementSize*numElements)); - - if( *buf ) - { - (*buf)->numElements = numElements; - (*buf)->maxElements = numElements; - Construct(*buf, 0, numElements); - } - else - { - // Oops, out of memory - asIScriptContext *ctx = asGetActiveContext(); - if( ctx ) - ctx->SetException("Out of memory"); - } -} - -// internal -void CScriptArray::DeleteBuffer(SArrayBuffer *buf) -{ - Destruct(buf, 0, buf->numElements); - - // Free the buffer - userFree(buf); -} - -// internal -void CScriptArray::Construct(SArrayBuffer *buf, asUINT start, asUINT end) -{ - if( (subTypeId & asTYPEID_MASK_OBJECT) && !(subTypeId & asTYPEID_OBJHANDLE) ) - { - // Create an object using the default constructor/factory for each element - void **max = (void**)(buf->data + end * sizeof(void*)); - void **d = (void**)(buf->data + start * sizeof(void*)); - - asIScriptEngine *engine = objType->GetEngine(); - asITypeInfo *subType = objType->GetSubType(); - - for( ; d < max; d++ ) - { - *d = (void*)engine->CreateScriptObject(subType); - if( *d == 0 ) - { - // Set the remaining entries to null so the destructor - // won't attempt to destroy invalid objects later - memset(d, 0, sizeof(void*)*(max-d)); - - // There is no need to set an exception on the context, - // as CreateScriptObject has already done that - return; - } - } - } - else - { - // Set all elements to zero whether they are handles or primitives - void *d = (void*)(buf->data + start * elementSize); - memset(d, 0, (end-start)*elementSize); - } -} - -// internal -void CScriptArray::Destruct(SArrayBuffer *buf, asUINT start, asUINT end) -{ - if( subTypeId & asTYPEID_MASK_OBJECT ) - { - asIScriptEngine *engine = objType->GetEngine(); - - void **max = (void**)(buf->data + end * sizeof(void*)); - void **d = (void**)(buf->data + start * sizeof(void*)); - - for( ; d < max; d++ ) - { - if( *d ) - engine->ReleaseScriptObject(*d, objType->GetSubType()); - } - } -} - - -// internal -bool CScriptArray::Less(const void *a, const void *b, bool asc) -{ - if( !asc ) - { - // Swap items - const void *TEMP = a; - a = b; - b = TEMP; - } - - if( !(subTypeId & ~asTYPEID_MASK_SEQNBR) ) - { - // Simple compare of values - switch( subTypeId ) - { - #define COMPARE(T) *((T*)a) < *((T*)b) - case asTYPEID_BOOL: return COMPARE(bool); - case asTYPEID_INT8: return COMPARE(asINT8); - case asTYPEID_INT16: return COMPARE(asINT16); - case asTYPEID_INT32: return COMPARE(asINT32); - case asTYPEID_INT64: return COMPARE(asINT64); - case asTYPEID_UINT8: return COMPARE(asBYTE); - case asTYPEID_UINT16: return COMPARE(asWORD); - case asTYPEID_UINT32: return COMPARE(asDWORD); - case asTYPEID_UINT64: return COMPARE(asQWORD); - case asTYPEID_FLOAT: return COMPARE(float); - case asTYPEID_DOUBLE: return COMPARE(double); - default: return COMPARE(signed int); // All enums fall in this case. TODO: update this when enums can have different sizes and types - #undef COMPARE - } - } - - return false; -} - -void CScriptArray::Reverse() -{ - asUINT size = GetSize(); - - if( size >= 2 ) - { - asBYTE TEMP[16]; - - for( asUINT i = 0; i < size / 2; i++ ) - { - Copy(TEMP, GetArrayItemPointer(i)); - Copy(GetArrayItemPointer(i), GetArrayItemPointer(size - i - 1)); - Copy(GetArrayItemPointer(size - i - 1), TEMP); - } - } -} - -bool CScriptArray::operator==(const CScriptArray &other) const -{ - if( objType != other.objType ) - return false; - - if( GetSize() != other.GetSize() ) - return false; - - asIScriptContext *cmpContext = 0; - bool isNested = false; - - if( subTypeId & ~asTYPEID_MASK_SEQNBR ) - { - // Try to reuse the active context - cmpContext = asGetActiveContext(); - if( cmpContext ) - { - if( cmpContext->GetEngine() == objType->GetEngine() && cmpContext->PushState() >= 0 ) - isNested = true; - else - cmpContext = 0; - } - if( cmpContext == 0 ) - { - // TODO: Ideally this context would be retrieved from a pool, so we don't have to - // create a new one everytime. We could keep a context with the array object - // but that would consume a lot of resources as each context is quite heavy. - cmpContext = objType->GetEngine()->CreateContext(); - } - } - - // Check if all elements are equal - bool isEqual = true; - SArrayCache *cache = reinterpret_cast(objType->GetUserData(ARRAY_CACHE)); - for( asUINT n = 0; n < GetSize(); n++ ) - if( !Equals(At(n), other.At(n), cmpContext, cache) ) - { - isEqual = false; - break; - } - - if( cmpContext ) - { - if( isNested ) - { - asEContextState state = cmpContext->GetState(); - cmpContext->PopState(); - if( state == asEXECUTION_ABORTED ) - cmpContext->Abort(); - } - else - cmpContext->Release(); - } - - return isEqual; -} - -// internal -bool CScriptArray::Equals(const void *a, const void *b, asIScriptContext *ctx, SArrayCache *cache) const -{ - if( !(subTypeId & ~asTYPEID_MASK_SEQNBR) ) - { - // Simple compare of values - switch( subTypeId ) - { - #define COMPARE(T) *((T*)a) == *((T*)b) - case asTYPEID_BOOL: return COMPARE(bool); - case asTYPEID_INT8: return COMPARE(asINT8); - case asTYPEID_INT16: return COMPARE(asINT16); - case asTYPEID_INT32: return COMPARE(asINT32); - case asTYPEID_INT64: return COMPARE(asINT64); - case asTYPEID_UINT8: return COMPARE(asBYTE); - case asTYPEID_UINT16: return COMPARE(asWORD); - case asTYPEID_UINT32: return COMPARE(asDWORD); - case asTYPEID_UINT64: return COMPARE(asQWORD); - case asTYPEID_FLOAT: return COMPARE(float); - case asTYPEID_DOUBLE: return COMPARE(double); - default: return COMPARE(signed int); // All enums fall here. TODO: update this when enums can have different sizes and types - #undef COMPARE - } - } - else - { - int r = 0; - - if( subTypeId & asTYPEID_OBJHANDLE ) - { - // Allow the find to work even if the array contains null handles - if( *(void**)a == *(void**)b ) return true; - } - - // Execute object opEquals if available - if( cache && cache->eqFunc ) - { - // TODO: Add proper error handling - r = ctx->Prepare(cache->eqFunc); assert(r >= 0); - - if( subTypeId & asTYPEID_OBJHANDLE ) - { - r = ctx->SetObject(*((void**)a)); assert(r >= 0); - r = ctx->SetArgObject(0, *((void**)b)); assert(r >= 0); - } - else - { - r = ctx->SetObject((void*)a); assert(r >= 0); - r = ctx->SetArgObject(0, (void*)b); assert(r >= 0); - } - - r = ctx->Execute(); - - if( r == asEXECUTION_FINISHED ) - return ctx->GetReturnByte() != 0; - - return false; - } - - // Execute object opCmp if available - if( cache && cache->cmpFunc ) - { - // TODO: Add proper error handling - r = ctx->Prepare(cache->cmpFunc); assert(r >= 0); - - if( subTypeId & asTYPEID_OBJHANDLE ) - { - r = ctx->SetObject(*((void**)a)); assert(r >= 0); - r = ctx->SetArgObject(0, *((void**)b)); assert(r >= 0); - } - else - { - r = ctx->SetObject((void*)a); assert(r >= 0); - r = ctx->SetArgObject(0, (void*)b); assert(r >= 0); - } - - r = ctx->Execute(); - - if( r == asEXECUTION_FINISHED ) - return (int)ctx->GetReturnDWord() == 0; - - return false; - } - } - - return false; -} - -int CScriptArray::FindByRef(void *ref) const -{ - return FindByRef(0, ref); -} - -int CScriptArray::FindByRef(asUINT startAt, void *ref) const -{ - // Find the matching element by its reference - asUINT size = GetSize(); - if( subTypeId & asTYPEID_OBJHANDLE ) - { - // Dereference the pointer - ref = *(void**)ref; - for( asUINT i = startAt; i < size; i++ ) - { - if( *(void**)At(i) == ref ) - return i; - } - } - else - { - // Compare the reference directly - for( asUINT i = startAt; i < size; i++ ) - { - if( At(i) == ref ) - return i; - } - } - - return -1; -} - -int CScriptArray::Find(void *value) const -{ - return Find(0, value); -} - -int CScriptArray::Find(asUINT startAt, void *value) const -{ - // Check if the subtype really supports find() - // TODO: Can't this be done at compile time too by the template callback - SArrayCache *cache = 0; - if( subTypeId & ~asTYPEID_MASK_SEQNBR ) - { - cache = reinterpret_cast(objType->GetUserData(ARRAY_CACHE)); - if( !cache || (cache->cmpFunc == 0 && cache->eqFunc == 0) ) - { - asIScriptContext *ctx = asGetActiveContext(); - asITypeInfo* subType = objType->GetEngine()->GetTypeInfoById(subTypeId); - - // Throw an exception - if( ctx ) - { - char tmp[512]; - - if( cache && cache->eqFuncReturnCode == asMULTIPLE_FUNCTIONS ) -#if defined(_MSC_VER) && _MSC_VER >= 1500 && !defined(__S3E__) - sprintf_s(tmp, 512, "Type '%s' has multiple matching opEquals or opCmp methods", subType->GetName()); -#else - snprintf(tmp, 512, "Type '%s' has multiple matching opEquals or opCmp methods", subType->GetName()); -#endif - else -#if defined(_MSC_VER) && _MSC_VER >= 1500 && !defined(__S3E__) - sprintf_s(tmp, 512, "Type '%s' does not have a matching opEquals or opCmp method", subType->GetName()); -#else - snprintf(tmp, 512, "Type '%s' does not have a matching opEquals or opCmp method", subType->GetName()); -#endif - ctx->SetException(tmp); - } - - return -1; - } - } - - asIScriptContext *cmpContext = 0; - bool isNested = false; - - if( subTypeId & ~asTYPEID_MASK_SEQNBR ) - { - // Try to reuse the active context - cmpContext = asGetActiveContext(); - if( cmpContext ) - { - if( cmpContext->GetEngine() == objType->GetEngine() && cmpContext->PushState() >= 0 ) - isNested = true; - else - cmpContext = 0; - } - if( cmpContext == 0 ) - { - // TODO: Ideally this context would be retrieved from a pool, so we don't have to - // create a new one everytime. We could keep a context with the array object - // but that would consume a lot of resources as each context is quite heavy. - cmpContext = objType->GetEngine()->CreateContext(); - } - } - - // Find the matching element - int ret = -1; - asUINT size = GetSize(); - - for( asUINT i = startAt; i < size; i++ ) - { - // value passed by reference - if( Equals(At(i), value, cmpContext, cache) ) - { - ret = (int)i; - break; - } - } - - if( cmpContext ) - { - if( isNested ) - { - asEContextState state = cmpContext->GetState(); - cmpContext->PopState(); - if( state == asEXECUTION_ABORTED ) - cmpContext->Abort(); - } - else - cmpContext->Release(); - } - - return ret; -} - - - -// internal -// Copy object handle or primitive value -// Even in arrays of objects the objects are allocated on -// the heap and the array stores the pointers to the objects -void CScriptArray::Copy(void *dst, void *src) -{ - memcpy(dst, src, elementSize); -} - - -// internal -// Swap two elements -// Even in arrays of objects the objects are allocated on -// the heap and the array stores the pointers to the objects. -void CScriptArray::Swap(void* a, void* b) -{ - asBYTE tmp[16]; - Copy(tmp, a); - Copy(a, b); - Copy(b, tmp); -} - - -// internal -// Return pointer to array item (object handle or primitive value) -void *CScriptArray::GetArrayItemPointer(int index) -{ - return buffer->data + index * elementSize; -} - -// internal -// Return pointer to data in buffer (object or primitive) -void *CScriptArray::GetDataPointer(void *buf) -{ - if ((subTypeId & asTYPEID_MASK_OBJECT) && !(subTypeId & asTYPEID_OBJHANDLE) ) - { - // Real address of object - return reinterpret_cast(*(size_t*)buf); - } - else - { - // Primitive is just a raw data - return buf; - } -} - - -// Sort ascending -void CScriptArray::SortAsc() -{ - Sort(0, GetSize(), true); -} - -// Sort ascending -void CScriptArray::SortAsc(asUINT startAt, asUINT count) -{ - Sort(startAt, count, true); -} - -// Sort descending -void CScriptArray::SortDesc() -{ - Sort(0, GetSize(), false); -} - -// Sort descending -void CScriptArray::SortDesc(asUINT startAt, asUINT count) -{ - Sort(startAt, count, false); -} - - -// internal -void CScriptArray::Sort(asUINT startAt, asUINT count, bool asc) -{ - // Subtype isn't primitive and doesn't have opCmp - SArrayCache *cache = reinterpret_cast(objType->GetUserData(ARRAY_CACHE)); - if( subTypeId & ~asTYPEID_MASK_SEQNBR ) - { - if( !cache || cache->cmpFunc == 0 ) - { - asIScriptContext *ctx = asGetActiveContext(); - asITypeInfo* subType = objType->GetEngine()->GetTypeInfoById(subTypeId); - - // Throw an exception - if( ctx ) - { - char tmp[512]; - - if( cache && cache->cmpFuncReturnCode == asMULTIPLE_FUNCTIONS ) -#if defined(_MSC_VER) && _MSC_VER >= 1500 && !defined(__S3E__) - sprintf_s(tmp, 512, "Type '%s' has multiple matching opCmp methods", subType->GetName()); -#else - snprintf(tmp, 512, "Type '%s' has multiple matching opCmp methods", subType->GetName()); -#endif - else -#if defined(_MSC_VER) && _MSC_VER >= 1500 && !defined(__S3E__) - sprintf_s(tmp, 512, "Type '%s' does not have a matching opCmp method", subType->GetName()); -#else - snprintf(tmp, 512, "Type '%s' does not have a matching opCmp method", subType->GetName()); -#endif - - ctx->SetException(tmp); - } - - return; - } - } - - // No need to sort - if( count < 2 ) - { - return; - } - - int start = startAt; - int end = startAt + count; - - // Check if we could access invalid item while sorting - if( start >= (int)buffer->numElements || end > (int)buffer->numElements ) - { - asIScriptContext *ctx = asGetActiveContext(); - - // Throw an exception - if( ctx ) - { - ctx->SetException("Index out of bounds"); - } - - return; - } - - if( subTypeId & ~asTYPEID_MASK_SEQNBR ) - { - asIScriptContext *cmpContext = 0; - bool isNested = false; - - // Try to reuse the active context - cmpContext = asGetActiveContext(); - if( cmpContext ) - { - if( cmpContext->GetEngine() == objType->GetEngine() && cmpContext->PushState() >= 0 ) - isNested = true; - else - cmpContext = 0; - } - if( cmpContext == 0 ) - cmpContext = objType->GetEngine()->RequestContext(); - - // Do the sorting - struct { - bool asc; - asIScriptContext *cmpContext; - asIScriptFunction *cmpFunc; - bool operator()(void *a, void *b) const - { - if( !asc ) - { - // Swap items - void *TEMP = a; - a = b; - b = TEMP; - } - - int r = 0; - - // Allow sort to work even if the array contains null handles - if( a == 0 ) return true; - if( b == 0 ) return false; - - // Execute object opCmp - if( cmpFunc ) - { - // TODO: Add proper error handling - r = cmpContext->Prepare(cmpFunc); assert(r >= 0); - r = cmpContext->SetObject(a); assert(r >= 0); - r = cmpContext->SetArgObject(0, b); assert(r >= 0); - r = cmpContext->Execute(); - - if( r == asEXECUTION_FINISHED ) - { - return (int)cmpContext->GetReturnDWord() < 0; - } - } - - return false; - } - } customLess = {asc, cmpContext, cache ? cache->cmpFunc : 0}; - std::sort((void**)GetArrayItemPointer(start), (void**)GetArrayItemPointer(end), customLess); - - // Clean up - if( cmpContext ) - { - if( isNested ) - { - asEContextState state = cmpContext->GetState(); - cmpContext->PopState(); - if( state == asEXECUTION_ABORTED ) - cmpContext->Abort(); - } - else - objType->GetEngine()->ReturnContext(cmpContext); - } - } - else - { - // TODO: Use std::sort for primitive types too - - // Insertion sort - asBYTE tmp[16]; - for( int i = start + 1; i < end; i++ ) - { - Copy(tmp, GetArrayItemPointer(i)); - - int j = i - 1; - - while( j >= start && Less(GetDataPointer(tmp), At(j), asc) ) - { - Copy(GetArrayItemPointer(j + 1), GetArrayItemPointer(j)); - j--; - } - - Copy(GetArrayItemPointer(j + 1), tmp); - } - } -} - -// Sort with script callback for comparing elements -void CScriptArray::Sort(asIScriptFunction *func, asUINT startAt, asUINT count) -{ - // No need to sort - if (count < 2) - return; - - // Check if we could access invalid item while sorting - asUINT start = startAt; - asUINT end = asQWORD(startAt) + asQWORD(count) >= (asQWORD(1)<<32) ? 0xFFFFFFFF : startAt + count; - if (end > buffer->numElements) - end = buffer->numElements; - - if (start >= buffer->numElements) - { - asIScriptContext *ctx = asGetActiveContext(); - - // Throw an exception - if (ctx) - ctx->SetException("Index out of bounds"); - - return; - } - - asIScriptContext *cmpContext = 0; - bool isNested = false; - - // Try to reuse the active context - cmpContext = asGetActiveContext(); - if (cmpContext) - { - if (cmpContext->GetEngine() == objType->GetEngine() && cmpContext->PushState() >= 0) - isNested = true; - else - cmpContext = 0; - } - if (cmpContext == 0) - cmpContext = objType->GetEngine()->RequestContext(); - - // TODO: Security issue: If the array is accessed from the callback while the sort is going on the result may be unpredictable - // For example, the callback resizes the array in the middle of the sort - // Possible solution: set a lock flag on the array, and prohibit modifications while the lock flag is set - - // Bubble sort - // TODO: optimize: Use an efficient sort algorithm - for (asUINT i = start; i+1 < end; i++) - { - asUINT best = i; - for (asUINT j = i + 1; j < end; j++) - { - cmpContext->Prepare(func); - cmpContext->SetArgAddress(0, At(j)); - cmpContext->SetArgAddress(1, At(best)); - int r = cmpContext->Execute(); - if (r != asEXECUTION_FINISHED) - break; - if (*(bool*)(cmpContext->GetAddressOfReturnValue())) - best = j; - } - - // With Swap we guarantee that the array always sees all references - // if the GC calls the EnumReferences in the middle of the sorting - if( best != i ) - Swap(GetArrayItemPointer(i), GetArrayItemPointer(best)); - } - - if (cmpContext) - { - if (isNested) - { - asEContextState state = cmpContext->GetState(); - cmpContext->PopState(); - if (state == asEXECUTION_ABORTED) - cmpContext->Abort(); - } - else - objType->GetEngine()->ReturnContext(cmpContext); - } -} - -// internal -void CScriptArray::CopyBuffer(SArrayBuffer *dst, SArrayBuffer *src) -{ - asIScriptEngine *engine = objType->GetEngine(); - if( subTypeId & asTYPEID_OBJHANDLE ) - { - // Copy the references and increase the reference counters - if( dst->numElements > 0 && src->numElements > 0 ) - { - int count = dst->numElements > src->numElements ? src->numElements : dst->numElements; - - void **max = (void**)(dst->data + count * sizeof(void*)); - void **d = (void**)dst->data; - void **s = (void**)src->data; - - for( ; d < max; d++, s++ ) - { - void *tmp = *d; - *d = *s; - if( *d ) - engine->AddRefScriptObject(*d, objType->GetSubType()); - // Release the old ref after incrementing the new to avoid problem incase it is the same ref - if( tmp ) - engine->ReleaseScriptObject(tmp, objType->GetSubType()); - } - } - } - else - { - if( dst->numElements > 0 && src->numElements > 0 ) - { - int count = dst->numElements > src->numElements ? src->numElements : dst->numElements; - if( subTypeId & asTYPEID_MASK_OBJECT ) - { - // Call the assignment operator on all of the objects - void **max = (void**)(dst->data + count * sizeof(void*)); - void **d = (void**)dst->data; - void **s = (void**)src->data; - - asITypeInfo *subType = objType->GetSubType(); - if (subType->GetFlags() & asOBJ_ASHANDLE) - { - // For objects that should work as handles we must use the opHndlAssign method - // TODO: Must support alternative syntaxes as well - // TODO: Move the lookup of the opHndlAssign method to Precache() so it is only done once - string decl = string(subType->GetName()) + "& opHndlAssign(const " + string(subType->GetName()) + "&in)"; - asIScriptFunction *func = subType->GetMethodByDecl(decl.c_str()); - if (func) - { - // TODO: Reuse active context if existing - asIScriptContext* ctx = engine->RequestContext(); - for (; d < max; d++, s++) - { - ctx->Prepare(func); - ctx->SetObject(*d); - ctx->SetArgAddress(0, *s); - // TODO: Handle errors - ctx->Execute(); - } - engine->ReturnContext(ctx); - } - else - { - // opHndlAssign doesn't exist, so try ordinary value assign instead - for (; d < max; d++, s++) - engine->AssignScriptObject(*d, *s, subType); - } - } - else - for( ; d < max; d++, s++ ) - engine->AssignScriptObject(*d, *s, subType); - } - else - { - // Primitives are copied byte for byte - memcpy(dst->data, src->data, count*elementSize); - } - } - } -} - -// internal -// Precache some info -void CScriptArray::Precache() -{ - subTypeId = objType->GetSubTypeId(); - - // Check if it is an array of objects. Only for these do we need to cache anything - // Type ids for primitives and enums only has the sequence number part - if( !(subTypeId & ~asTYPEID_MASK_SEQNBR) ) - return; - - // The opCmp and opEquals methods are cached because the searching for the - // methods is quite time consuming if a lot of array objects are created. - - // First check if a cache already exists for this array type - SArrayCache *cache = reinterpret_cast(objType->GetUserData(ARRAY_CACHE)); - if( cache ) return; - - // We need to make sure the cache is created only once, even - // if multiple threads reach the same point at the same time - asAcquireExclusiveLock(); - - // Now that we got the lock, we need to check again to make sure the - // cache wasn't created while we were waiting for the lock - cache = reinterpret_cast(objType->GetUserData(ARRAY_CACHE)); - if( cache ) - { - asReleaseExclusiveLock(); - return; - } - - // Create the cache - cache = reinterpret_cast(userAlloc(sizeof(SArrayCache))); - if( !cache ) - { - asIScriptContext *ctx = asGetActiveContext(); - if( ctx ) - ctx->SetException("Out of memory"); - asReleaseExclusiveLock(); - return; - } - memset(cache, 0, sizeof(SArrayCache)); - - // If the sub type is a handle to const, then the methods must be const too - bool mustBeConst = (subTypeId & asTYPEID_HANDLETOCONST) ? true : false; - - asITypeInfo *subType = objType->GetEngine()->GetTypeInfoById(subTypeId); - if( subType ) - { - for( asUINT i = 0; i < subType->GetMethodCount(); i++ ) - { - asIScriptFunction *func = subType->GetMethodByIndex(i); - - if( func->GetParamCount() == 1 && (!mustBeConst || func->IsReadOnly()) ) - { - asDWORD flags = 0; - int returnTypeId = func->GetReturnTypeId(&flags); - - // The method must not return a reference - if( flags != asTM_NONE ) - continue; - - // opCmp returns an int and opEquals returns a bool - bool isCmp = false, isEq = false; - if( returnTypeId == asTYPEID_INT32 && strcmp(func->GetName(), "opCmp") == 0 ) - isCmp = true; - if( returnTypeId == asTYPEID_BOOL && strcmp(func->GetName(), "opEquals") == 0 ) - isEq = true; - - if( !isCmp && !isEq ) - continue; - - // The parameter must either be a reference to the subtype or a handle to the subtype - int paramTypeId; - func->GetParam(0, ¶mTypeId, &flags); - - if( (paramTypeId & ~(asTYPEID_OBJHANDLE|asTYPEID_HANDLETOCONST)) != (subTypeId & ~(asTYPEID_OBJHANDLE|asTYPEID_HANDLETOCONST)) ) - continue; - - if( (flags & asTM_INREF) ) - { - if( (paramTypeId & asTYPEID_OBJHANDLE) || (mustBeConst && !(flags & asTM_CONST)) ) - continue; - } - else if( paramTypeId & asTYPEID_OBJHANDLE ) - { - if( mustBeConst && !(paramTypeId & asTYPEID_HANDLETOCONST) ) - continue; - } - else - continue; - - if( isCmp ) - { - if( cache->cmpFunc || cache->cmpFuncReturnCode ) - { - cache->cmpFunc = 0; - cache->cmpFuncReturnCode = asMULTIPLE_FUNCTIONS; - } - else - cache->cmpFunc = func; - } - else if( isEq ) - { - if( cache->eqFunc || cache->eqFuncReturnCode ) - { - cache->eqFunc = 0; - cache->eqFuncReturnCode = asMULTIPLE_FUNCTIONS; - } - else - cache->eqFunc = func; - } - } - } - } - - if( cache->eqFunc == 0 && cache->eqFuncReturnCode == 0 ) - cache->eqFuncReturnCode = asNO_FUNCTION; - if( cache->cmpFunc == 0 && cache->cmpFuncReturnCode == 0 ) - cache->cmpFuncReturnCode = asNO_FUNCTION; - - // Set the user data only at the end so others that retrieve it will know it is complete - objType->SetUserData(cache, ARRAY_CACHE); - - asReleaseExclusiveLock(); -} - -// GC behaviour -void CScriptArray::EnumReferences(asIScriptEngine *engine) -{ - // TODO: If garbage collection can be done from a separate thread, then this method must be - // protected so that it doesn't get lost during the iteration if the array is modified - - // If the array is holding handles, then we need to notify the GC of them - if( subTypeId & asTYPEID_MASK_OBJECT ) - { - void **d = (void**)buffer->data; - - asITypeInfo *subType = engine->GetTypeInfoById(subTypeId); - if ((subType->GetFlags() & asOBJ_REF)) - { - // For reference types we need to notify the GC of each instance - for (asUINT n = 0; n < buffer->numElements; n++) - { - if (d[n]) - engine->GCEnumCallback(d[n]); - } - } - else if ((subType->GetFlags() & asOBJ_VALUE) && (subType->GetFlags() & asOBJ_GC)) - { - // For value types we need to forward the enum callback - // to the object so it can decide what to do - for (asUINT n = 0; n < buffer->numElements; n++) - { - if (d[n]) - engine->ForwardGCEnumReferences(d[n], subType); - } - } - } -} - -// GC behaviour -void CScriptArray::ReleaseAllHandles(asIScriptEngine *) -{ - // Resizing to zero will release everything - Resize(0); -} - -void CScriptArray::AddRef() const -{ - // Clear the GC flag then increase the counter - gcFlag = false; - asAtomicInc(refCount); -} - -void CScriptArray::Release() const -{ - // Clearing the GC flag then descrease the counter - gcFlag = false; - if( asAtomicDec(refCount) == 0 ) - { - // When reaching 0 no more references to this instance - // exists and the object should be destroyed - this->~CScriptArray(); - userFree(const_cast(this)); - } -} - -// GC behaviour -int CScriptArray::GetRefCount() -{ - return refCount; -} - -// GC behaviour -void CScriptArray::SetFlag() -{ - gcFlag = true; -} - -// GC behaviour -bool CScriptArray::GetFlag() -{ - return gcFlag; -} - -//-------------------------------------------- -// Generic calling conventions - -static void ScriptArrayFactory_Generic(asIScriptGeneric *gen) -{ - asITypeInfo *ti = *(asITypeInfo**)gen->GetAddressOfArg(0); - - *reinterpret_cast(gen->GetAddressOfReturnLocation()) = CScriptArray::Create(ti); -} - -static void ScriptArrayFactory2_Generic(asIScriptGeneric *gen) -{ - asITypeInfo *ti = *(asITypeInfo**)gen->GetAddressOfArg(0); - asUINT length = gen->GetArgDWord(1); - - *reinterpret_cast(gen->GetAddressOfReturnLocation()) = CScriptArray::Create(ti, length); -} - -static void ScriptArrayListFactory_Generic(asIScriptGeneric *gen) -{ - asITypeInfo *ti = *(asITypeInfo**)gen->GetAddressOfArg(0); - void *buf = gen->GetArgAddress(1); - - *reinterpret_cast(gen->GetAddressOfReturnLocation()) = CScriptArray::Create(ti, buf); -} - -static void ScriptArrayFactoryDefVal_Generic(asIScriptGeneric *gen) -{ - asITypeInfo *ti = *(asITypeInfo**)gen->GetAddressOfArg(0); - asUINT length = gen->GetArgDWord(1); - void *defVal = gen->GetArgAddress(2); - - *reinterpret_cast(gen->GetAddressOfReturnLocation()) = CScriptArray::Create(ti, length, defVal); -} - -static void ScriptArrayTemplateCallback_Generic(asIScriptGeneric *gen) -{ - asITypeInfo *ti = *(asITypeInfo**)gen->GetAddressOfArg(0); - bool *dontGarbageCollect = *(bool**)gen->GetAddressOfArg(1); - *reinterpret_cast(gen->GetAddressOfReturnLocation()) = ScriptArrayTemplateCallback(ti, *dontGarbageCollect); -} - -static void ScriptArrayAssignment_Generic(asIScriptGeneric *gen) -{ - CScriptArray *other = (CScriptArray*)gen->GetArgObject(0); - CScriptArray *self = (CScriptArray*)gen->GetObject(); - *self = *other; - gen->SetReturnObject(self); -} - -static void ScriptArrayEquals_Generic(asIScriptGeneric *gen) -{ - CScriptArray *other = (CScriptArray*)gen->GetArgObject(0); - CScriptArray *self = (CScriptArray*)gen->GetObject(); - gen->SetReturnByte(self->operator==(*other)); -} - -static void ScriptArrayFind_Generic(asIScriptGeneric *gen) -{ - void *value = gen->GetArgAddress(0); - CScriptArray *self = (CScriptArray*)gen->GetObject(); - gen->SetReturnDWord(self->Find(value)); -} - -static void ScriptArrayFind2_Generic(asIScriptGeneric *gen) -{ - asUINT index = gen->GetArgDWord(0); - void *value = gen->GetArgAddress(1); - CScriptArray *self = (CScriptArray*)gen->GetObject(); - gen->SetReturnDWord(self->Find(index, value)); -} - -static void ScriptArrayFindByRef_Generic(asIScriptGeneric *gen) -{ - void *value = gen->GetArgAddress(0); - CScriptArray *self = (CScriptArray*)gen->GetObject(); - gen->SetReturnDWord(self->FindByRef(value)); -} - -static void ScriptArrayFindByRef2_Generic(asIScriptGeneric *gen) -{ - asUINT index = gen->GetArgDWord(0); - void *value = gen->GetArgAddress(1); - CScriptArray *self = (CScriptArray*)gen->GetObject(); - gen->SetReturnDWord(self->FindByRef(index, value)); -} - -static void ScriptArrayAt_Generic(asIScriptGeneric *gen) -{ - asINT64 index = gen->GetArgQWord(0); - CScriptArray *self = (CScriptArray*)gen->GetObject(); - - gen->SetReturnAddress(self->At(index)); -} - -static void ScriptArrayInsertAt_Generic(asIScriptGeneric *gen) -{ - asUINT index = gen->GetArgDWord(0); - void *value = gen->GetArgAddress(1); - CScriptArray *self = (CScriptArray*)gen->GetObject(); - self->InsertAt(index, value); -} - -static void ScriptArrayInsertAtArray_Generic(asIScriptGeneric *gen) -{ - asUINT index = gen->GetArgDWord(0); - CScriptArray *array = (CScriptArray*)gen->GetArgAddress(1); - CScriptArray *self = (CScriptArray*)gen->GetObject(); - self->InsertAt(index, *array); -} - -static void ScriptArrayRemoveAt_Generic(asIScriptGeneric *gen) -{ - asUINT index = gen->GetArgDWord(0); - CScriptArray *self = (CScriptArray*)gen->GetObject(); - self->RemoveAt(index); -} - -static void ScriptArrayRemoveRange_Generic(asIScriptGeneric *gen) -{ - asUINT start = gen->GetArgDWord(0); - asUINT count = gen->GetArgDWord(1); - CScriptArray *self = (CScriptArray*)gen->GetObject(); - self->RemoveRange(start, count); -} - -static void ScriptArrayInsertLast_Generic(asIScriptGeneric *gen) -{ - void *value = gen->GetArgAddress(0); - CScriptArray *self = (CScriptArray*)gen->GetObject(); - self->InsertLast(value); -} - -static void ScriptArrayRemoveLast_Generic(asIScriptGeneric *gen) -{ - CScriptArray *self = (CScriptArray*)gen->GetObject(); - self->RemoveLast(); -} - -static void ScriptArrayLength_Generic(asIScriptGeneric *gen) -{ - CScriptArray *self = (CScriptArray*)gen->GetObject(); - - gen->SetReturnDWord(self->GetSize()); -} - -static void ScriptArrayResize_Generic(asIScriptGeneric *gen) -{ - asUINT size = gen->GetArgDWord(0); - CScriptArray *self = (CScriptArray*)gen->GetObject(); - - self->Resize(size); -} - -static void ScriptArrayReserve_Generic(asIScriptGeneric *gen) -{ - asUINT size = gen->GetArgDWord(0); - CScriptArray *self = (CScriptArray*)gen->GetObject(); - self->Reserve(size); -} - -static void ScriptArraySortAsc_Generic(asIScriptGeneric *gen) -{ - CScriptArray *self = (CScriptArray*)gen->GetObject(); - self->SortAsc(); -} - -static void ScriptArrayReverse_Generic(asIScriptGeneric *gen) -{ - CScriptArray *self = (CScriptArray*)gen->GetObject(); - self->Reverse(); -} - -static void ScriptArrayIsEmpty_Generic(asIScriptGeneric *gen) -{ - CScriptArray *self = (CScriptArray*)gen->GetObject(); - *reinterpret_cast(gen->GetAddressOfReturnLocation()) = self->IsEmpty(); -} - -static void ScriptArraySortAsc2_Generic(asIScriptGeneric *gen) -{ - asUINT index = gen->GetArgDWord(0); - asUINT count = gen->GetArgDWord(1); - CScriptArray *self = (CScriptArray*)gen->GetObject(); - self->SortAsc(index, count); -} - -static void ScriptArraySortDesc_Generic(asIScriptGeneric *gen) -{ - CScriptArray *self = (CScriptArray*)gen->GetObject(); - self->SortDesc(); -} - -static void ScriptArraySortDesc2_Generic(asIScriptGeneric *gen) -{ - asUINT index = gen->GetArgDWord(0); - asUINT count = gen->GetArgDWord(1); - CScriptArray *self = (CScriptArray*)gen->GetObject(); - self->SortDesc(index, count); -} - -static void ScriptArraySortCallback_Generic(asIScriptGeneric *gen) -{ - asIScriptFunction *callback = (asIScriptFunction*)gen->GetArgAddress(0); - asUINT startAt = gen->GetArgDWord(1); - asUINT count = gen->GetArgDWord(2); - CScriptArray *self = (CScriptArray*)gen->GetObject(); - self->Sort(callback, startAt, count); -} - -static void ScriptArrayAddRef_Generic(asIScriptGeneric *gen) -{ - CScriptArray *self = (CScriptArray*)gen->GetObject(); - self->AddRef(); -} - -static void ScriptArrayRelease_Generic(asIScriptGeneric *gen) -{ - CScriptArray *self = (CScriptArray*)gen->GetObject(); - self->Release(); -} - -static void ScriptArrayGetRefCount_Generic(asIScriptGeneric *gen) -{ - CScriptArray *self = (CScriptArray*)gen->GetObject(); - *reinterpret_cast(gen->GetAddressOfReturnLocation()) = self->GetRefCount(); -} - -static void ScriptArraySetFlag_Generic(asIScriptGeneric *gen) -{ - CScriptArray *self = (CScriptArray*)gen->GetObject(); - self->SetFlag(); -} - -static void ScriptArrayGetFlag_Generic(asIScriptGeneric *gen) -{ - CScriptArray *self = (CScriptArray*)gen->GetObject(); - *reinterpret_cast(gen->GetAddressOfReturnLocation()) = self->GetFlag(); -} - -static void ScriptArrayEnumReferences_Generic(asIScriptGeneric *gen) -{ - CScriptArray *self = (CScriptArray*)gen->GetObject(); - asIScriptEngine *engine = *(asIScriptEngine**)gen->GetAddressOfArg(0); - self->EnumReferences(engine); -} - -static void ScriptArrayReleaseAllHandles_Generic(asIScriptGeneric *gen) -{ - CScriptArray *self = (CScriptArray*)gen->GetObject(); - asIScriptEngine *engine = *(asIScriptEngine**)gen->GetAddressOfArg(0); - self->ReleaseAllHandles(engine); -} - -static void RegisterScriptArray_Generic(asIScriptEngine *engine) -{ - int r = 0; - UNUSED_VAR(r); - - engine->SetTypeInfoUserDataCleanupCallback(CleanupTypeInfoArrayCache, ARRAY_CACHE); - - r = engine->RegisterObjectType("array", 0, asOBJ_REF | asOBJ_GC | asOBJ_TEMPLATE); assert( r >= 0 ); - r = engine->RegisterObjectBehaviour("array", asBEHAVE_TEMPLATE_CALLBACK, "bool f(int&in, bool&out)", asFUNCTION(ScriptArrayTemplateCallback_Generic), asCALL_GENERIC); assert( r >= 0 ); - - r = engine->RegisterObjectBehaviour("array", asBEHAVE_FACTORY, "array@ f(int&in)", asFUNCTION(ScriptArrayFactory_Generic), asCALL_GENERIC); assert( r >= 0 ); - r = engine->RegisterObjectBehaviour("array", asBEHAVE_FACTORY, "array@ f(int&in, uint length) explicit", asFUNCTION(ScriptArrayFactory2_Generic), asCALL_GENERIC); assert( r >= 0 ); - r = engine->RegisterObjectBehaviour("array", asBEHAVE_FACTORY, "array@ f(int&in, uint length, const T &in value)", asFUNCTION(ScriptArrayFactoryDefVal_Generic), asCALL_GENERIC); assert( r >= 0 ); - r = engine->RegisterObjectBehaviour("array", asBEHAVE_LIST_FACTORY, "array@ f(int&in, int&in) {repeat T}", asFUNCTION(ScriptArrayListFactory_Generic), asCALL_GENERIC); assert( r >= 0 ); - r = engine->RegisterObjectBehaviour("array", asBEHAVE_ADDREF, "void f()", asFUNCTION(ScriptArrayAddRef_Generic), asCALL_GENERIC); assert( r >= 0 ); - r = engine->RegisterObjectBehaviour("array", asBEHAVE_RELEASE, "void f()", asFUNCTION(ScriptArrayRelease_Generic), asCALL_GENERIC); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "T &opIndex(int64 index)", asFUNCTION(ScriptArrayAt_Generic), asCALL_GENERIC); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "const T &opIndex(int64 index) const", asFUNCTION(ScriptArrayAt_Generic), asCALL_GENERIC); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "array &opAssign(const array&in)", asFUNCTION(ScriptArrayAssignment_Generic), asCALL_GENERIC); assert( r >= 0 ); - - r = engine->RegisterObjectMethod("array", "void insert_at(uint index, const T&in value)", asFUNCTION(ScriptArrayInsertAt_Generic), asCALL_GENERIC); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "void insert_at(uint index, const array& arr)", asFUNCTION(ScriptArrayInsertAtArray_Generic), asCALL_GENERIC); assert(r >= 0); - r = engine->RegisterObjectMethod("array", "void insert_last(const T&in value)", asFUNCTION(ScriptArrayInsertLast_Generic), asCALL_GENERIC); assert(r >= 0); - r = engine->RegisterObjectMethod("array", "void remove_at(uint index)", asFUNCTION(ScriptArrayRemoveAt_Generic), asCALL_GENERIC); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "void remove_last()", asFUNCTION(ScriptArrayRemoveLast_Generic), asCALL_GENERIC); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "void remove_range(uint start, uint count)", asFUNCTION(ScriptArrayRemoveRange_Generic), asCALL_GENERIC); assert(r >= 0); -#if AS_USE_ACCESSORS != 1 - r = engine->RegisterObjectMethod("array", "uint length() const", asFUNCTION(ScriptArrayLength_Generic), asCALL_GENERIC); assert( r >= 0 ); -#endif - r = engine->RegisterObjectMethod("array", "void reserve(uint length)", asFUNCTION(ScriptArrayReserve_Generic), asCALL_GENERIC); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "void resize(uint length)", asFUNCTION(ScriptArrayResize_Generic), asCALL_GENERIC); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "void sort_ascending()", asFUNCTION(ScriptArraySortAsc_Generic), asCALL_GENERIC); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "void sort_ascending(uint startAt, uint count)", asFUNCTION(ScriptArraySortAsc2_Generic), asCALL_GENERIC); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "void sort_descending()", asFUNCTION(ScriptArraySortDesc_Generic), asCALL_GENERIC); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "void sort_descending(uint startAt, uint count)", asFUNCTION(ScriptArraySortDesc2_Generic), asCALL_GENERIC); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "void reverse()", asFUNCTION(ScriptArrayReverse_Generic), asCALL_GENERIC); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "int find(const T&in if_handle_then_const value) const", asFUNCTION(ScriptArrayFind_Generic), asCALL_GENERIC); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "int find(uint startAt, const T&in if_handle_then_const value) const", asFUNCTION(ScriptArrayFind2_Generic), asCALL_GENERIC); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "int find_by_ref(const T&in if_handle_then_const value) const", asFUNCTION(ScriptArrayFindByRef_Generic), asCALL_GENERIC); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "int find_by_ref(uint startAt, const T&in if_handle_then_const value) const", asFUNCTION(ScriptArrayFindByRef2_Generic), asCALL_GENERIC); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "bool opEquals(const array&in) const", asFUNCTION(ScriptArrayEquals_Generic), asCALL_GENERIC); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "bool is_empty() const", asFUNCTION(ScriptArrayIsEmpty_Generic), asCALL_GENERIC); assert( r >= 0 ); - r = engine->RegisterFuncdef("bool array::less(const T&in if_handle_then_const a, const T&in if_handle_then_const b)"); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "void sort(const less &in, uint startAt = 0, uint count = uint(-1))", asFUNCTION(ScriptArraySortCallback_Generic), asCALL_GENERIC); assert(r >= 0); -#if AS_USE_STLNAMES != 1 && AS_USE_ACCESSORS == 1 - r = engine->RegisterObjectMethod("array", "uint get_length() const property", asFUNCTION(ScriptArrayLength_Generic), asCALL_GENERIC); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "void set_length(uint) property", asFUNCTION(ScriptArrayResize_Generic), asCALL_GENERIC); assert( r >= 0 ); -#endif - r = engine->RegisterObjectBehaviour("array", asBEHAVE_GETREFCOUNT, "int f()", asFUNCTION(ScriptArrayGetRefCount_Generic), asCALL_GENERIC); assert( r >= 0 ); - r = engine->RegisterObjectBehaviour("array", asBEHAVE_SETGCFLAG, "void f()", asFUNCTION(ScriptArraySetFlag_Generic), asCALL_GENERIC); assert( r >= 0 ); - r = engine->RegisterObjectBehaviour("array", asBEHAVE_GETGCFLAG, "bool f()", asFUNCTION(ScriptArrayGetFlag_Generic), asCALL_GENERIC); assert( r >= 0 ); - r = engine->RegisterObjectBehaviour("array", asBEHAVE_ENUMREFS, "void f(int&in)", asFUNCTION(ScriptArrayEnumReferences_Generic), asCALL_GENERIC); assert( r >= 0 ); - r = engine->RegisterObjectBehaviour("array", asBEHAVE_RELEASEREFS, "void f(int&in)", asFUNCTION(ScriptArrayReleaseAllHandles_Generic), asCALL_GENERIC); assert( r >= 0 ); -} - -END_AS_NAMESPACE +#include +#include +#include +#include +#include // sprintf +#include +#include // std::sort + +#include "scriptarray.h" + +using namespace std; + +BEGIN_AS_NAMESPACE + +// This macro is used to avoid warnings about unused variables. +// Usually where the variables are only used in debug mode. +#define UNUSED_VAR(x) (void)(x) + +// Set the default memory routines +// Use the angelscript engine's memory routines by default +static asALLOCFUNC_t userAlloc = asAllocMem; +static asFREEFUNC_t userFree = asFreeMem; + +// Allows the application to set which memory routines should be used by the array object +void CScriptArray::SetMemoryFunctions(asALLOCFUNC_t allocFunc, asFREEFUNC_t freeFunc) +{ + userAlloc = allocFunc; + userFree = freeFunc; +} + +static void RegisterScriptArray_Native(asIScriptEngine *engine); +static void RegisterScriptArray_Generic(asIScriptEngine *engine); + +struct SArrayBuffer +{ + asDWORD maxElements; + asDWORD numElements; + asBYTE data[1]; +}; + +struct SArrayCache +{ + asIScriptFunction *cmpFunc; + asIScriptFunction *eqFunc; + int cmpFuncReturnCode; // To allow better error message in case of multiple matches + int eqFuncReturnCode; +}; + +// We just define a number here that we assume nobody else is using for +// object type user data. The add-ons have reserved the numbers 1000 +// through 1999 for this purpose, so we should be fine. +const asPWORD ARRAY_CACHE = 1000; + +static void CleanupTypeInfoArrayCache(asITypeInfo *type) +{ + SArrayCache *cache = reinterpret_cast(type->GetUserData(ARRAY_CACHE)); + if( cache ) + { + cache->~SArrayCache(); + userFree(cache); + } +} + +CScriptArray* CScriptArray::Create(asITypeInfo *ti, asUINT length) +{ + // Allocate the memory + void *mem = userAlloc(sizeof(CScriptArray)); + if( mem == 0 ) + { + asIScriptContext *ctx = asGetActiveContext(); + if( ctx ) + ctx->SetException("Out of memory"); + + return 0; + } + + // Initialize the object + CScriptArray *a = new(mem) CScriptArray(length, ti); + + return a; +} + +CScriptArray* CScriptArray::Create(asITypeInfo *ti, void *initList) +{ + // Allocate the memory + void *mem = userAlloc(sizeof(CScriptArray)); + if( mem == 0 ) + { + asIScriptContext *ctx = asGetActiveContext(); + if( ctx ) + ctx->SetException("Out of memory"); + + return 0; + } + + // Initialize the object + CScriptArray *a = new(mem) CScriptArray(ti, initList); + + return a; +} + +CScriptArray* CScriptArray::Create(asITypeInfo *ti, asUINT length, void *defVal) +{ + // Allocate the memory + void *mem = userAlloc(sizeof(CScriptArray)); + if( mem == 0 ) + { + asIScriptContext *ctx = asGetActiveContext(); + if( ctx ) + ctx->SetException("Out of memory"); + + return 0; + } + + // Initialize the object + CScriptArray *a = new(mem) CScriptArray(length, defVal, ti); + + return a; +} + +CScriptArray* CScriptArray::Create(asITypeInfo *ti) +{ + return CScriptArray::Create(ti, asUINT(0)); +} + +// This optional callback is called when the template type is first used by the compiler. +// It allows the application to validate if the template can be instantiated for the requested +// subtype at compile time, instead of at runtime. The output argument dontGarbageCollect +// allow the callback to tell the engine if the template instance type shouldn't be garbage collected, +// i.e. no asOBJ_GC flag. +static bool ScriptArrayTemplateCallback(asITypeInfo *ti, bool &dontGarbageCollect) +{ + // Make sure the subtype can be instantiated with a default factory/constructor, + // otherwise we won't be able to instantiate the elements. + int typeId = ti->GetSubTypeId(); + if( typeId == asTYPEID_VOID ) + return false; + if( (typeId & asTYPEID_MASK_OBJECT) && !(typeId & asTYPEID_OBJHANDLE) ) + { + asITypeInfo *subtype = ti->GetEngine()->GetTypeInfoById(typeId); + asDWORD flags = subtype->GetFlags(); + if( (flags & asOBJ_VALUE) && !(flags & asOBJ_POD) ) + { + // Verify that there is a default constructor + bool found = false; + for( asUINT n = 0; n < subtype->GetBehaviourCount(); n++ ) + { + asEBehaviours beh; + asIScriptFunction *func = subtype->GetBehaviourByIndex(n, &beh); + if( beh != asBEHAVE_CONSTRUCT ) continue; + + if( func->GetParamCount() == 0 ) + { + // Found the default constructor + found = true; + break; + } + } + + if( !found ) + { + // There is no default constructor + // TODO: Should format the message to give the name of the subtype for better understanding + ti->GetEngine()->WriteMessage("array", 0, 0, asMSGTYPE_ERROR, "The subtype has no default constructor"); + return false; + } + } + else if( (flags & asOBJ_REF) ) + { + bool found = false; + + // If value assignment for ref type has been disabled then the array + // can be created if the type has a default factory function + if( !ti->GetEngine()->GetEngineProperty(asEP_DISALLOW_VALUE_ASSIGN_FOR_REF_TYPE) ) + { + // Verify that there is a default factory + for( asUINT n = 0; n < subtype->GetFactoryCount(); n++ ) + { + asIScriptFunction *func = subtype->GetFactoryByIndex(n); + if( func->GetParamCount() == 0 ) + { + // Found the default factory + found = true; + break; + } + } + } + + if( !found ) + { + // No default factory + char buffer[1024]; + snprintf(buffer, sizeof(buffer), "The subtype '%s' has no default factory", subtype ? subtype->GetEngine()->GetTypeDeclaration(subtype->GetTypeId()) : "UNKNOWN"); + ti->GetEngine()->WriteMessage("array", 0, 0, asMSGTYPE_ERROR, buffer); + return false; + } + } + + // If the object type is not garbage collected then the array also doesn't need to be + if( !(flags & asOBJ_GC) ) + dontGarbageCollect = true; + } + else if( !(typeId & asTYPEID_OBJHANDLE) ) + { + // Arrays with primitives cannot form circular references, + // thus there is no need to garbage collect them + dontGarbageCollect = true; + } + else + { + assert( typeId & asTYPEID_OBJHANDLE ); + + // It is not necessary to set the array as garbage collected for all handle types. + // If it is possible to determine that the handle cannot refer to an object type + // that can potentially form a circular reference with the array then it is not + // necessary to make the array garbage collected. + asITypeInfo *subtype = ti->GetEngine()->GetTypeInfoById(typeId); + asDWORD flags = subtype->GetFlags(); + if( !(flags & asOBJ_GC) ) + { + if( (flags & asOBJ_SCRIPT_OBJECT) ) + { + // Even if a script class is by itself not garbage collected, it is possible + // that classes that derive from it may be, so it is not possible to know + // that no circular reference can occur. + if( (flags & asOBJ_NOINHERIT) ) + { + // A script class declared as final cannot be inherited from, thus + // we can be certain that the object cannot be garbage collected. + dontGarbageCollect = true; + } + } + else + { + // For application registered classes we assume the application knows + // what it is doing and don't mark the array as garbage collected unless + // the type is also garbage collected. + dontGarbageCollect = true; + } + } + } + + // The type is ok + return true; +} + +// Registers the template array type +void RegisterScriptArray(asIScriptEngine *engine, bool defaultArray) +{ + if( strstr(asGetLibraryOptions(), "AS_MAX_PORTABILITY") == 0 ) + RegisterScriptArray_Native(engine); + else + RegisterScriptArray_Generic(engine); + + if( defaultArray ) + { + int r = engine->RegisterDefaultArrayType("array"); assert( r >= 0 ); + UNUSED_VAR(r); + } +} + +static void RegisterScriptArray_Native(asIScriptEngine *engine) +{ + int r = 0; + UNUSED_VAR(r); + + // Register the object type user data clean up + engine->SetTypeInfoUserDataCleanupCallback(CleanupTypeInfoArrayCache, ARRAY_CACHE); + + // Register the array type as a template + r = engine->RegisterObjectType("array", 0, asOBJ_REF | asOBJ_GC | asOBJ_TEMPLATE); assert( r >= 0 ); + + // Register a callback for validating the subtype before it is used + r = engine->RegisterObjectBehaviour("array", asBEHAVE_TEMPLATE_CALLBACK, "bool f(int&in, bool&out)", asFUNCTION(ScriptArrayTemplateCallback), asCALL_CDECL); assert( r >= 0 ); + + // Templates receive the object type as the first parameter. To the script writer this is hidden + r = engine->RegisterObjectBehaviour("array", asBEHAVE_FACTORY, "array@ f(int&in)", asFUNCTIONPR(CScriptArray::Create, (asITypeInfo*), CScriptArray*), asCALL_CDECL); assert( r >= 0 ); + r = engine->RegisterObjectBehaviour("array", asBEHAVE_FACTORY, "array@ f(int&in, uint length) explicit", asFUNCTIONPR(CScriptArray::Create, (asITypeInfo*, asUINT), CScriptArray*), asCALL_CDECL); assert( r >= 0 ); + r = engine->RegisterObjectBehaviour("array", asBEHAVE_FACTORY, "array@ f(int&in, uint length, const T &in value)", asFUNCTIONPR(CScriptArray::Create, (asITypeInfo*, asUINT, void *), CScriptArray*), asCALL_CDECL); assert( r >= 0 ); + + // Register the factory that will be used for initialization lists + r = engine->RegisterObjectBehaviour("array", asBEHAVE_LIST_FACTORY, "array@ f(int&in type, int&in list) {repeat T}", asFUNCTIONPR(CScriptArray::Create, (asITypeInfo*, void*), CScriptArray*), asCALL_CDECL); assert( r >= 0 ); + + // The memory management methods + r = engine->RegisterObjectBehaviour("array", asBEHAVE_ADDREF, "void f()", asMETHOD(CScriptArray,AddRef), asCALL_THISCALL); assert( r >= 0 ); + r = engine->RegisterObjectBehaviour("array", asBEHAVE_RELEASE, "void f()", asMETHOD(CScriptArray,Release), asCALL_THISCALL); assert( r >= 0 ); + + // The index operator returns the template subtype + r = engine->RegisterObjectMethod("array", "T &opIndex(int64 index)", asMETHODPR(CScriptArray, At, (asINT64), void*), asCALL_THISCALL); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "const T &opIndex(int64 index) const", asMETHODPR(CScriptArray, At, (asINT64) const, const void*), asCALL_THISCALL); assert( r >= 0 ); + + // The assignment operator + r = engine->RegisterObjectMethod("array", "array &opAssign(const array&in)", asMETHOD(CScriptArray, operator=), asCALL_THISCALL); assert( r >= 0 ); + + // Other methods + r = engine->RegisterObjectMethod("array", "void insert_at(uint index, const T&in value)", asMETHODPR(CScriptArray, InsertAt, (asUINT, void *), void), asCALL_THISCALL); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "void insert_at(uint index, const array& arr)", asMETHODPR(CScriptArray, InsertAt, (asUINT, const CScriptArray &), void), asCALL_THISCALL); assert(r >= 0); + r = engine->RegisterObjectMethod("array", "void insert_last(const T&in value)", asMETHODPR(CScriptArray, InsertLast, (void*), void), asCALL_THISCALL); assert(r >= 0); + r = engine->RegisterObjectMethod("array", "void insert_last(const array& arr)", asMETHODPR(CScriptArray, InsertLast, (const CScriptArray &), void), asCALL_THISCALL); assert(r >= 0); + r = engine->RegisterObjectMethod("array", "void remove_at(uint index)", asMETHOD(CScriptArray, RemoveAt), asCALL_THISCALL); assert(r >= 0); + r = engine->RegisterObjectMethod("array", "void remove_last()", asMETHOD(CScriptArray, RemoveLast), asCALL_THISCALL); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "void remove_range(uint start, uint count)", asMETHOD(CScriptArray, RemoveRange), asCALL_THISCALL); assert(r >= 0); + // TODO: Should length() and resize() be deprecated as the property accessors do the same thing? + // TODO: Register as size() for consistency with other types +#if AS_USE_ACCESSORS != 1 + r = engine->RegisterObjectMethod("array", "uint length() const", asMETHOD(CScriptArray, GetSize), asCALL_THISCALL); assert( r >= 0 ); +#endif + r = engine->RegisterObjectMethod("array", "void reserve(uint length)", asMETHOD(CScriptArray, Reserve), asCALL_THISCALL); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "void resize(uint length)", asMETHODPR(CScriptArray, Resize, (asUINT), void), asCALL_THISCALL); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "void sort_ascending()", asMETHODPR(CScriptArray, SortAsc, (), void), asCALL_THISCALL); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "void sort_ascending(uint startAt, uint count)", asMETHODPR(CScriptArray, SortAsc, (asUINT, asUINT), void), asCALL_THISCALL); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "void sort_descending()", asMETHODPR(CScriptArray, SortDesc, (), void), asCALL_THISCALL); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "void sort_descending(uint startAt, uint count)", asMETHODPR(CScriptArray, SortDesc, (asUINT, asUINT), void), asCALL_THISCALL); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "void reverse()", asMETHOD(CScriptArray, Reverse), asCALL_THISCALL); assert( r >= 0 ); + // The token 'if_handle_then_const' tells the engine that if the type T is a handle, then it should refer to a read-only object + r = engine->RegisterObjectMethod("array", "int find(const T&in if_handle_then_const value) const", asMETHODPR(CScriptArray, Find, (void*) const, int), asCALL_THISCALL); assert( r >= 0 ); + // TODO: It should be "int find(const T&in value, uint startAt = 0) const" + r = engine->RegisterObjectMethod("array", "int find(uint startAt, const T&in if_handle_then_const value) const", asMETHODPR(CScriptArray, Find, (asUINT, void*) const, int), asCALL_THISCALL); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "int find_by_ref(const T&in if_handle_then_const value) const", asMETHODPR(CScriptArray, FindByRef, (void*) const, int), asCALL_THISCALL); assert( r >= 0 ); + // TODO: It should be "int find_by_ref(const T&in value, uint startAt = 0) const" + r = engine->RegisterObjectMethod("array", "int find_by_ref(uint startAt, const T&in if_handle_then_const value) const", asMETHODPR(CScriptArray, FindByRef, (asUINT, void*) const, int), asCALL_THISCALL); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "bool opEquals(const array&in) const", asMETHOD(CScriptArray, operator==), asCALL_THISCALL); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "bool is_empty() const", asMETHOD(CScriptArray, IsEmpty), asCALL_THISCALL); assert( r >= 0 ); + + // Sort with callback for comparison + r = engine->RegisterFuncdef("bool array::less(const T&in if_handle_then_const a, const T&in if_handle_then_const b)"); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "void sort(const less &in, uint startAt = 0, uint count = uint(-1))", asMETHODPR(CScriptArray, Sort, (asIScriptFunction*, asUINT, asUINT), void), asCALL_THISCALL); assert(r >= 0); + +#if AS_USE_STLNAMES != 1 && AS_USE_ACCESSORS == 1 + // Register virtual properties + r = engine->RegisterObjectMethod("array", "uint get_length() const property", asMETHOD(CScriptArray, GetSize), asCALL_THISCALL); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "void set_length(uint) property", asMETHODPR(CScriptArray, Resize, (asUINT), void), asCALL_THISCALL); assert( r >= 0 ); +#endif + + // Register GC behaviours in case the array needs to be garbage collected + r = engine->RegisterObjectBehaviour("array", asBEHAVE_GETREFCOUNT, "int f()", asMETHOD(CScriptArray, GetRefCount), asCALL_THISCALL); assert( r >= 0 ); + r = engine->RegisterObjectBehaviour("array", asBEHAVE_SETGCFLAG, "void f()", asMETHOD(CScriptArray, SetFlag), asCALL_THISCALL); assert( r >= 0 ); + r = engine->RegisterObjectBehaviour("array", asBEHAVE_GETGCFLAG, "bool f()", asMETHOD(CScriptArray, GetFlag), asCALL_THISCALL); assert( r >= 0 ); + r = engine->RegisterObjectBehaviour("array", asBEHAVE_ENUMREFS, "void f(int&in)", asMETHOD(CScriptArray, EnumReferences), asCALL_THISCALL); assert( r >= 0 ); + r = engine->RegisterObjectBehaviour("array", asBEHAVE_RELEASEREFS, "void f(int&in)", asMETHOD(CScriptArray, ReleaseAllHandles), asCALL_THISCALL); assert( r >= 0 ); + +#if AS_USE_STLNAMES == 1 + // Same as length + r = engine->RegisterObjectMethod("array", "uint size() const", asMETHOD(CScriptArray, GetSize), asCALL_THISCALL); assert( r >= 0 ); + // Same as isEmpty + r = engine->RegisterObjectMethod("array", "bool empty() const", asMETHOD(CScriptArray, IsEmpty), asCALL_THISCALL); assert( r >= 0 ); + // Same as insertLast + r = engine->RegisterObjectMethod("array", "void push_back(const T&in)", asMETHODPR(CScriptArray, InsertLast, (void*), void), asCALL_THISCALL); assert( r >= 0 ); + // Same as removeLast + r = engine->RegisterObjectMethod("array", "void pop_back()", asMETHOD(CScriptArray, RemoveLast), asCALL_THISCALL); assert( r >= 0 ); + // Same as insertAt + r = engine->RegisterObjectMethod("array", "void insert(uint index, const T&in value)", asMETHODPR(CScriptArray, InsertAt, (asUINT, void *), void), asCALL_THISCALL); assert(r >= 0); + r = engine->RegisterObjectMethod("array", "void insert(uint index, const array& arr)", asMETHODPR(CScriptArray, InsertAt, (asUINT, const CScriptArray &), void), asCALL_THISCALL); assert(r >= 0); + // Same as removeAt + r = engine->RegisterObjectMethod("array", "void erase(uint)", asMETHOD(CScriptArray, RemoveAt), asCALL_THISCALL); assert( r >= 0 ); +#endif +} + +CScriptArray &CScriptArray::operator=(const CScriptArray &other) +{ + // Only perform the copy if the array types are the same + if( &other != this && + other.GetArrayObjectType() == GetArrayObjectType() ) + { + // Make sure the arrays are of the same size + Resize(other.buffer->numElements); + + // Copy the value of each element + CopyBuffer(buffer, other.buffer); + } + + return *this; +} + +CScriptArray::CScriptArray(asITypeInfo *ti, void *buf) +{ + // The object type should be the template instance of the array + assert( ti && string(ti->GetName()) == "array" ); + + refCount = 1; + gcFlag = false; + objType = ti; + objType->AddRef(); + buffer = 0; + + Precache(); + + asIScriptEngine *engine = ti->GetEngine(); + + // Determine element size + if( subTypeId & asTYPEID_MASK_OBJECT ) + elementSize = sizeof(asPWORD); + else + elementSize = engine->GetSizeOfPrimitiveType(subTypeId); + + // Determine the initial size from the buffer + asUINT length = *(asUINT*)buf; + + // Make sure the array size isn't too large for us to handle + if( !CheckMaxSize(length) ) + { + // Don't continue with the initialization + return; + } + + // Copy the values of the array elements from the buffer + if( (ti->GetSubTypeId() & asTYPEID_MASK_OBJECT) == 0 ) + { + CreateBuffer(&buffer, length); + + // Copy the values of the primitive type into the internal buffer + if( length > 0 ) + memcpy(At(0), (((asUINT*)buf)+1), length * elementSize); + } + else if( ti->GetSubTypeId() & asTYPEID_OBJHANDLE ) + { + CreateBuffer(&buffer, length); + + // Copy the handles into the internal buffer + if( length > 0 ) + memcpy(At(0), (((asUINT*)buf)+1), length * elementSize); + + // With object handles it is safe to clear the memory in the received buffer + // instead of increasing the ref count. It will save time both by avoiding the + // call the increase ref, and also relieve the engine from having to release + // its references too + memset((((asUINT*)buf)+1), 0, length * elementSize); + } + else if( ti->GetSubType()->GetFlags() & asOBJ_REF ) + { + // Only allocate the buffer, but not the objects + subTypeId |= asTYPEID_OBJHANDLE; + CreateBuffer(&buffer, length); + if (!buffer) + throw std::bad_alloc(); + subTypeId &= ~asTYPEID_OBJHANDLE; + + // Copy the handles into the internal buffer + if( length > 0 ) + memcpy(buffer->data, (((asUINT*)buf)+1), length * elementSize); + + // For ref types we can do the same as for handles, as they are + // implicitly stored as handles. + memset((((asUINT*)buf)+1), 0, length * elementSize); + } + else + { + // TODO: Optimize by calling the copy constructor of the object instead of + // constructing with the default constructor and then assigning the value + // TODO: With C++11 ideally we should be calling the move constructor, instead + // of the copy constructor as the engine will just discard the objects in the + // buffer afterwards. + CreateBuffer(&buffer, length); + + // For value types we need to call the opAssign for each individual object + for( asUINT n = 0; n < length; n++ ) + { + void *obj = At(n); + asBYTE *srcObj = (asBYTE*)buf; + srcObj += 4 + n*ti->GetSubType()->GetSize(); + engine->AssignScriptObject(obj, srcObj, ti->GetSubType()); + } + } + + // Notify the GC of the successful creation + if( objType->GetFlags() & asOBJ_GC ) + objType->GetEngine()->NotifyGarbageCollectorOfNewObject(this, objType); +} + +CScriptArray::CScriptArray(asUINT length, asITypeInfo *ti) +{ + // The object type should be the template instance of the array + assert( ti && string(ti->GetName()) == "array" ); + + refCount = 1; + gcFlag = false; + objType = ti; + objType->AddRef(); + buffer = 0; + + Precache(); + + // Determine element size + if( subTypeId & asTYPEID_MASK_OBJECT ) + elementSize = sizeof(asPWORD); + else + elementSize = objType->GetEngine()->GetSizeOfPrimitiveType(subTypeId); + + // Make sure the array size isn't too large for us to handle + if( !CheckMaxSize(length) ) + { + // Don't continue with the initialization + return; + } + + CreateBuffer(&buffer, length); + + // Notify the GC of the successful creation + if( objType->GetFlags() & asOBJ_GC ) + objType->GetEngine()->NotifyGarbageCollectorOfNewObject(this, objType); +} + +CScriptArray::CScriptArray(const CScriptArray &other) +{ + refCount = 1; + gcFlag = false; + objType = other.objType; + objType->AddRef(); + buffer = 0; + + Precache(); + + elementSize = other.elementSize; + + if( objType->GetFlags() & asOBJ_GC ) + objType->GetEngine()->NotifyGarbageCollectorOfNewObject(this, objType); + + CreateBuffer(&buffer, 0); + + // Copy the content + *this = other; +} + +CScriptArray::CScriptArray(asUINT length, void *defVal, asITypeInfo *ti) +{ + // The object type should be the template instance of the array + assert( ti && string(ti->GetName()) == "array" ); + + refCount = 1; + gcFlag = false; + objType = ti; + objType->AddRef(); + buffer = 0; + + Precache(); + + // Determine element size + if( subTypeId & asTYPEID_MASK_OBJECT ) + elementSize = sizeof(asPWORD); + else + elementSize = objType->GetEngine()->GetSizeOfPrimitiveType(subTypeId); + + // Make sure the array size isn't too large for us to handle + if( !CheckMaxSize(length) ) + { + // Don't continue with the initialization + return; + } + + CreateBuffer(&buffer, length); + + // Notify the GC of the successful creation + if( objType->GetFlags() & asOBJ_GC ) + objType->GetEngine()->NotifyGarbageCollectorOfNewObject(this, objType); + + // Initialize the elements with the default value + for( asUINT n = 0; n < GetSize(); n++ ) + SetValue(n, defVal); +} + +void CScriptArray::SetValue(asINT64 index, void *value) +{ + // At() will take care of the out-of-bounds checking, though + // if called from the application then nothing will be done + void *ptr = At(index); + if( ptr == 0 ) return; + + if ((subTypeId & ~asTYPEID_MASK_SEQNBR) && !(subTypeId & asTYPEID_OBJHANDLE)) + { + asITypeInfo *subType = objType->GetSubType(); + if (subType->GetFlags() & asOBJ_ASHANDLE) + { + // For objects that should work as handles we must use the opHndlAssign method + // TODO: Must support alternative syntaxes as well + // TODO: Move the lookup of the opHndlAssign method to Precache() so it is only done once + string decl = string(subType->GetName()) + "& opHndlAssign(const " + string(subType->GetName()) + "&in)"; + asIScriptFunction* func = subType->GetMethodByDecl(decl.c_str()); + if (func) + { + // TODO: Reuse active context if existing + asIScriptEngine* engine = objType->GetEngine(); + asIScriptContext* ctx = engine->RequestContext(); + ctx->Prepare(func); + ctx->SetObject(ptr); + ctx->SetArgAddress(0, value); + // TODO: Handle errors + ctx->Execute(); + engine->ReturnContext(ctx); + } + else + { + // opHndlAssign doesn't exist, so try ordinary value assign instead + objType->GetEngine()->AssignScriptObject(ptr, value, subType); + } + } + else + objType->GetEngine()->AssignScriptObject(ptr, value, subType); + } + else if( subTypeId & asTYPEID_OBJHANDLE ) + { + void *tmp = *(void**)ptr; + *(void**)ptr = *(void**)value; + objType->GetEngine()->AddRefScriptObject(*(void**)value, objType->GetSubType()); + if( tmp ) + objType->GetEngine()->ReleaseScriptObject(tmp, objType->GetSubType()); + } + else if( subTypeId == asTYPEID_BOOL || + subTypeId == asTYPEID_INT8 || + subTypeId == asTYPEID_UINT8 ) + *(char*)ptr = *(char*)value; + else if( subTypeId == asTYPEID_INT16 || + subTypeId == asTYPEID_UINT16 ) + *(short*)ptr = *(short*)value; + else if( subTypeId == asTYPEID_INT32 || + subTypeId == asTYPEID_UINT32 || + subTypeId == asTYPEID_FLOAT || + subTypeId > asTYPEID_DOUBLE ) // enums have a type id larger than doubles + *(int*)ptr = *(int*)value; + else if( subTypeId == asTYPEID_INT64 || + subTypeId == asTYPEID_UINT64 || + subTypeId == asTYPEID_DOUBLE ) + *(double*)ptr = *(double*)value; +} + +CScriptArray::~CScriptArray() +{ + if( buffer ) + { + DeleteBuffer(buffer); + buffer = 0; + } + if( objType ) objType->Release(); +} + +asUINT CScriptArray::GetSize() const +{ + return buffer->numElements; +} + +bool CScriptArray::IsEmpty() const +{ + return buffer->numElements == 0; +} + +void CScriptArray::Reserve(asUINT maxElements) +{ + if( maxElements <= buffer->maxElements ) + return; + + if( !CheckMaxSize(maxElements) ) + return; + + // Allocate memory for the buffer + SArrayBuffer *newBuffer = reinterpret_cast(userAlloc(sizeof(SArrayBuffer)-1 + elementSize*maxElements)); + if( newBuffer ) + { + newBuffer->numElements = buffer->numElements; + newBuffer->maxElements = maxElements; + } + else + { + // Out of memory + asIScriptContext *ctx = asGetActiveContext(); + if( ctx ) + ctx->SetException("Out of memory"); + return; + } + + // As objects in arrays of objects are not stored inline, it is safe to use memcpy here + // since we're just copying the pointers to objects and not the actual objects. + memcpy(newBuffer->data, buffer->data, buffer->numElements*elementSize); + + // Release the old buffer + userFree(buffer); + + buffer = newBuffer; +} + +void CScriptArray::Resize(asUINT numElements) +{ + if( !CheckMaxSize(numElements) ) + return; + + Resize((int)numElements - (int)buffer->numElements, (asUINT)-1); +} + +void CScriptArray::RemoveRange(asUINT start, asUINT count) +{ + if (count == 0) + return; + + if( buffer == 0 || start > buffer->numElements ) + { + // If this is called from a script we raise a script exception + asIScriptContext *ctx = asGetActiveContext(); + if (ctx) + ctx->SetException("Index out of bounds"); + return; + } + + // Cap count to the end of the array + if (start + count > buffer->numElements) + count = buffer->numElements - start; + + // Destroy the elements that are being removed + Destruct(buffer, start, start + count); + + // Compact the elements + // As objects in arrays of objects are not stored inline, it is safe to use memmove here + // since we're just copying the pointers to objects and not the actual objects. + memmove(buffer->data + start*elementSize, buffer->data + (start + count)*elementSize, (buffer->numElements - start - count)*elementSize); + buffer->numElements -= count; +} + +// Internal +void CScriptArray::Resize(int delta, asUINT at) +{ + if( delta < 0 ) + { + if( -delta > (int)buffer->numElements ) + delta = -(int)buffer->numElements; + if( at > buffer->numElements + delta ) + at = buffer->numElements + delta; + } + else if( delta > 0 ) + { + // Make sure the array size isn't too large for us to handle + if( delta > 0 && !CheckMaxSize(buffer->numElements + delta) ) + return; + + if( at > buffer->numElements ) + at = buffer->numElements; + } + + if( delta == 0 ) return; + + if( buffer->maxElements < buffer->numElements + delta ) + { + // Allocate memory for the buffer + SArrayBuffer *newBuffer = reinterpret_cast(userAlloc(sizeof(SArrayBuffer)-1 + elementSize*(buffer->numElements + delta))); + if( newBuffer ) + { + newBuffer->numElements = buffer->numElements + delta; + newBuffer->maxElements = newBuffer->numElements; + } + else + { + // Out of memory + asIScriptContext *ctx = asGetActiveContext(); + if( ctx ) + ctx->SetException("Out of memory"); + return; + } + + // As objects in arrays of objects are not stored inline, it is safe to use memcpy here + // since we're just copying the pointers to objects and not the actual objects. + memcpy(newBuffer->data, buffer->data, at*elementSize); + if( at < buffer->numElements ) + memcpy(newBuffer->data + (at+delta)*elementSize, buffer->data + at*elementSize, (buffer->numElements-at)*elementSize); + + // Initialize the new elements with default values + Construct(newBuffer, at, at+delta); + + // Release the old buffer + userFree(buffer); + + buffer = newBuffer; + } + else if( delta < 0 ) + { + Destruct(buffer, at, at-delta); + // As objects in arrays of objects are not stored inline, it is safe to use memmove here + // since we're just copying the pointers to objects and not the actual objects. + memmove(buffer->data + at*elementSize, buffer->data + (at-delta)*elementSize, (buffer->numElements - (at-delta))*elementSize); + buffer->numElements += delta; + } + else + { + // As objects in arrays of objects are not stored inline, it is safe to use memmove here + // since we're just copying the pointers to objects and not the actual objects. + memmove(buffer->data + (at+delta)*elementSize, buffer->data + at*elementSize, (buffer->numElements - at)*elementSize); + Construct(buffer, at, at+delta); + buffer->numElements += delta; + } +} + +// internal +bool CScriptArray::CheckMaxSize(asUINT numElements) +{ + // This code makes sure the size of the buffer that is allocated + // for the array doesn't overflow and becomes smaller than requested + + asUINT maxSize = 0xFFFFFFFFul - sizeof(SArrayBuffer) + 1; + if( elementSize > 0 ) + maxSize /= elementSize; + + if( numElements > maxSize ) + { + asIScriptContext *ctx = asGetActiveContext(); + if( ctx ) + ctx->SetException("Too large array size"); + + return false; + } + + // OK + return true; +} + +asITypeInfo *CScriptArray::GetArrayObjectType() const +{ + return objType; +} + +int CScriptArray::GetArrayTypeId() const +{ + return objType->GetTypeId(); +} + +int CScriptArray::GetElementTypeId() const +{ + return subTypeId; +} + +void CScriptArray::InsertAt(asUINT index, void *value) +{ + if( index > buffer->numElements ) + { + // If this is called from a script we raise a script exception + asIScriptContext *ctx = asGetActiveContext(); + if( ctx ) + ctx->SetException("Index out of bounds"); + return; + } + + // Make room for the new element + Resize(1, index); + + // Set the value of the new element + SetValue(index, value); +} + +void CScriptArray::InsertAt(asUINT index, const CScriptArray &arr) +{ + if (index > buffer->numElements) + { + asIScriptContext *ctx = asGetActiveContext(); + if (ctx) + ctx->SetException("Index out of bounds"); + return; + } + + if (objType != arr.objType) + { + // This shouldn't really be possible to happen when + // called from a script, but let's check for it anyway + asIScriptContext *ctx = asGetActiveContext(); + if (ctx) + ctx->SetException("Mismatching array types"); + return; + } + + asUINT elements = arr.GetSize(); + Resize(elements, index); + if (&arr != this) + { + for (asUINT n = 0; n < arr.GetSize(); n++) + { + // This const cast is allowed, since we know the + // value will only be used to make a copy of it + void *value = const_cast(arr.At(n)); + SetValue(index + n, value); + } + } + else + { + // The array that is being inserted is the same as this one. + // So we should iterate over the elements before the index, + // and then the elements after + for (asUINT n = 0; n < index; n++) + { + // This const cast is allowed, since we know the + // value will only be used to make a copy of it + void *value = const_cast(arr.At(n)); + SetValue(index + n, value); + } + + for (asUINT n = index + elements, m = 0; n < arr.GetSize(); n++, m++) + { + // This const cast is allowed, since we know the + // value will only be used to make a copy of it + void *value = const_cast(arr.At(n)); + SetValue(index + index + m, value); + } + } +} + +void CScriptArray::InsertLast(void *value) +{ + InsertAt(buffer->numElements, value); +} + +void CScriptArray::InsertLast(const CScriptArray &arr) +{ + InsertAt(buffer->numElements, arr); +} + +void CScriptArray::RemoveAt(asUINT index) +{ + if( index >= buffer->numElements ) + { + // If this is called from a script we raise a script exception + asIScriptContext *ctx = asGetActiveContext(); + if( ctx ) + ctx->SetException("Index out of bounds"); + return; + } + + // Remove the element + Resize(-1, index); +} + +void CScriptArray::RemoveLast() +{ + RemoveAt(buffer->numElements-1); +} + +// Return a pointer to the array element. Returns 0 if the index is out of bounds +const void *CScriptArray::At(asINT64 index) const +{ + if( index < 0) index = buffer->numElements + index; + if( buffer == 0 || index < 0 || index >= buffer->numElements ) + { + // If this is called from a script we raise a script exception + asIScriptContext *ctx = asGetActiveContext(); + if( ctx ) + ctx->SetException("Index out of bounds"); + return 0; + } + + if( (subTypeId & asTYPEID_MASK_OBJECT) && !(subTypeId & asTYPEID_OBJHANDLE) ) + return *(void**)(buffer->data + elementSize*index); + else + return buffer->data + elementSize*index; +} +void *CScriptArray::At(asINT64 index) +{ + return const_cast(const_cast(this)->At(index)); +} + +void *CScriptArray::GetBuffer() +{ + return buffer->data; +} + + +// internal +void CScriptArray::CreateBuffer(SArrayBuffer **buf, asUINT numElements) +{ + *buf = reinterpret_cast(userAlloc(sizeof(SArrayBuffer)-1+elementSize*numElements)); + + if( *buf ) + { + (*buf)->numElements = numElements; + (*buf)->maxElements = numElements; + Construct(*buf, 0, numElements); + } + else + { + // Oops, out of memory + asIScriptContext *ctx = asGetActiveContext(); + if( ctx ) + ctx->SetException("Out of memory"); + } +} + +// internal +void CScriptArray::DeleteBuffer(SArrayBuffer *buf) +{ + Destruct(buf, 0, buf->numElements); + + // Free the buffer + userFree(buf); +} + +// internal +void CScriptArray::Construct(SArrayBuffer *buf, asUINT start, asUINT end) +{ + if( (subTypeId & asTYPEID_MASK_OBJECT) && !(subTypeId & asTYPEID_OBJHANDLE) ) + { + // Create an object using the default constructor/factory for each element + void **max = (void**)(buf->data + end * sizeof(void*)); + void **d = (void**)(buf->data + start * sizeof(void*)); + + asIScriptEngine *engine = objType->GetEngine(); + asITypeInfo *subType = objType->GetSubType(); + + for( ; d < max; d++ ) + { + *d = (void*)engine->CreateScriptObject(subType); + if( *d == 0 ) + { + // Set the remaining entries to null so the destructor + // won't attempt to destroy invalid objects later + memset(d, 0, sizeof(void*)*(max-d)); + + // There is no need to set an exception on the context, + // as CreateScriptObject has already done that + return; + } + } + } + else + { + // Set all elements to zero whether they are handles or primitives + void *d = (void*)(buf->data + start * elementSize); + memset(d, 0, (end-start)*elementSize); + } +} + +// internal +void CScriptArray::Destruct(SArrayBuffer *buf, asUINT start, asUINT end) +{ + if( subTypeId & asTYPEID_MASK_OBJECT ) + { + asIScriptEngine *engine = objType->GetEngine(); + + void **max = (void**)(buf->data + end * sizeof(void*)); + void **d = (void**)(buf->data + start * sizeof(void*)); + + for( ; d < max; d++ ) + { + if( *d ) + engine->ReleaseScriptObject(*d, objType->GetSubType()); + } + } +} + + +// internal +bool CScriptArray::Less(const void *a, const void *b, bool asc) +{ + if( !asc ) + { + // Swap items + const void *TEMP = a; + a = b; + b = TEMP; + } + + if( !(subTypeId & ~asTYPEID_MASK_SEQNBR) ) + { + // Simple compare of values + switch( subTypeId ) + { + #define COMPARE(T) *((T*)a) < *((T*)b) + case asTYPEID_BOOL: return COMPARE(bool); + case asTYPEID_INT8: return COMPARE(asINT8); + case asTYPEID_INT16: return COMPARE(asINT16); + case asTYPEID_INT32: return COMPARE(asINT32); + case asTYPEID_INT64: return COMPARE(asINT64); + case asTYPEID_UINT8: return COMPARE(asBYTE); + case asTYPEID_UINT16: return COMPARE(asWORD); + case asTYPEID_UINT32: return COMPARE(asDWORD); + case asTYPEID_UINT64: return COMPARE(asQWORD); + case asTYPEID_FLOAT: return COMPARE(float); + case asTYPEID_DOUBLE: return COMPARE(double); + default: return COMPARE(signed int); // All enums fall in this case. TODO: update this when enums can have different sizes and types + #undef COMPARE + } + } + + return false; +} + +void CScriptArray::Reverse() +{ + asUINT size = GetSize(); + + if( size >= 2 ) + { + asBYTE TEMP[16]; + + for( asUINT i = 0; i < size / 2; i++ ) + { + Copy(TEMP, GetArrayItemPointer(i)); + Copy(GetArrayItemPointer(i), GetArrayItemPointer(size - i - 1)); + Copy(GetArrayItemPointer(size - i - 1), TEMP); + } + } +} + +bool CScriptArray::operator==(const CScriptArray &other) const +{ + if( objType != other.objType ) + return false; + + if( GetSize() != other.GetSize() ) + return false; + + asIScriptContext *cmpContext = 0; + bool isNested = false; + + if( subTypeId & ~asTYPEID_MASK_SEQNBR ) + { + // Try to reuse the active context + cmpContext = asGetActiveContext(); + if( cmpContext ) + { + if( cmpContext->GetEngine() == objType->GetEngine() && cmpContext->PushState() >= 0 ) + isNested = true; + else + cmpContext = 0; + } + if( cmpContext == 0 ) + { + // TODO: Ideally this context would be retrieved from a pool, so we don't have to + // create a new one everytime. We could keep a context with the array object + // but that would consume a lot of resources as each context is quite heavy. + cmpContext = objType->GetEngine()->CreateContext(); + } + } + + // Check if all elements are equal + bool isEqual = true; + SArrayCache *cache = reinterpret_cast(objType->GetUserData(ARRAY_CACHE)); + for( asUINT n = 0; n < GetSize(); n++ ) + if( !Equals(At(n), other.At(n), cmpContext, cache) ) + { + isEqual = false; + break; + } + + if( cmpContext ) + { + if( isNested ) + { + asEContextState state = cmpContext->GetState(); + cmpContext->PopState(); + if( state == asEXECUTION_ABORTED ) + cmpContext->Abort(); + } + else + cmpContext->Release(); + } + + return isEqual; +} + +// internal +bool CScriptArray::Equals(const void *a, const void *b, asIScriptContext *ctx, SArrayCache *cache) const +{ + if( !(subTypeId & ~asTYPEID_MASK_SEQNBR) ) + { + // Simple compare of values + switch( subTypeId ) + { + #define COMPARE(T) *((T*)a) == *((T*)b) + case asTYPEID_BOOL: return COMPARE(bool); + case asTYPEID_INT8: return COMPARE(asINT8); + case asTYPEID_INT16: return COMPARE(asINT16); + case asTYPEID_INT32: return COMPARE(asINT32); + case asTYPEID_INT64: return COMPARE(asINT64); + case asTYPEID_UINT8: return COMPARE(asBYTE); + case asTYPEID_UINT16: return COMPARE(asWORD); + case asTYPEID_UINT32: return COMPARE(asDWORD); + case asTYPEID_UINT64: return COMPARE(asQWORD); + case asTYPEID_FLOAT: return COMPARE(float); + case asTYPEID_DOUBLE: return COMPARE(double); + default: return COMPARE(signed int); // All enums fall here. TODO: update this when enums can have different sizes and types + #undef COMPARE + } + } + else + { + int r = 0; + + if( subTypeId & asTYPEID_OBJHANDLE ) + { + // Allow the find to work even if the array contains null handles + if( *(void**)a == *(void**)b ) return true; + } + + // Execute object opEquals if available + if( cache && cache->eqFunc ) + { + // TODO: Add proper error handling + r = ctx->Prepare(cache->eqFunc); assert(r >= 0); + + if( subTypeId & asTYPEID_OBJHANDLE ) + { + r = ctx->SetObject(*((void**)a)); assert(r >= 0); + r = ctx->SetArgObject(0, *((void**)b)); assert(r >= 0); + } + else + { + r = ctx->SetObject((void*)a); assert(r >= 0); + r = ctx->SetArgObject(0, (void*)b); assert(r >= 0); + } + + r = ctx->Execute(); + + if( r == asEXECUTION_FINISHED ) + return ctx->GetReturnByte() != 0; + + return false; + } + + // Execute object opCmp if available + if( cache && cache->cmpFunc ) + { + // TODO: Add proper error handling + r = ctx->Prepare(cache->cmpFunc); assert(r >= 0); + + if( subTypeId & asTYPEID_OBJHANDLE ) + { + r = ctx->SetObject(*((void**)a)); assert(r >= 0); + r = ctx->SetArgObject(0, *((void**)b)); assert(r >= 0); + } + else + { + r = ctx->SetObject((void*)a); assert(r >= 0); + r = ctx->SetArgObject(0, (void*)b); assert(r >= 0); + } + + r = ctx->Execute(); + + if( r == asEXECUTION_FINISHED ) + return (int)ctx->GetReturnDWord() == 0; + + return false; + } + } + + return false; +} + +int CScriptArray::FindByRef(void *ref) const +{ + return FindByRef(0, ref); +} + +int CScriptArray::FindByRef(asUINT startAt, void *ref) const +{ + // Find the matching element by its reference + asUINT size = GetSize(); + if( subTypeId & asTYPEID_OBJHANDLE ) + { + // Dereference the pointer + ref = *(void**)ref; + for( asUINT i = startAt; i < size; i++ ) + { + if( *(void**)At(i) == ref ) + return i; + } + } + else + { + // Compare the reference directly + for( asUINT i = startAt; i < size; i++ ) + { + if( At(i) == ref ) + return i; + } + } + + return -1; +} + +int CScriptArray::Find(void *value) const +{ + return Find(0, value); +} + +int CScriptArray::Find(asUINT startAt, void *value) const +{ + // Check if the subtype really supports find() + // TODO: Can't this be done at compile time too by the template callback + SArrayCache *cache = 0; + if( subTypeId & ~asTYPEID_MASK_SEQNBR ) + { + cache = reinterpret_cast(objType->GetUserData(ARRAY_CACHE)); + if( !cache || (cache->cmpFunc == 0 && cache->eqFunc == 0) ) + { + asIScriptContext *ctx = asGetActiveContext(); + asITypeInfo* subType = objType->GetEngine()->GetTypeInfoById(subTypeId); + + // Throw an exception + if( ctx ) + { + char tmp[512]; + + if( cache && cache->eqFuncReturnCode == asMULTIPLE_FUNCTIONS ) +#if defined(_MSC_VER) && _MSC_VER >= 1500 && !defined(__S3E__) + sprintf_s(tmp, 512, "Type '%s' has multiple matching opEquals or opCmp methods", subType->GetName()); +#else + snprintf(tmp, 512, "Type '%s' has multiple matching opEquals or opCmp methods", subType->GetName()); +#endif + else +#if defined(_MSC_VER) && _MSC_VER >= 1500 && !defined(__S3E__) + sprintf_s(tmp, 512, "Type '%s' does not have a matching opEquals or opCmp method", subType->GetName()); +#else + snprintf(tmp, 512, "Type '%s' does not have a matching opEquals or opCmp method", subType->GetName()); +#endif + ctx->SetException(tmp); + } + + return -1; + } + } + + asIScriptContext *cmpContext = 0; + bool isNested = false; + + if( subTypeId & ~asTYPEID_MASK_SEQNBR ) + { + // Try to reuse the active context + cmpContext = asGetActiveContext(); + if( cmpContext ) + { + if( cmpContext->GetEngine() == objType->GetEngine() && cmpContext->PushState() >= 0 ) + isNested = true; + else + cmpContext = 0; + } + if( cmpContext == 0 ) + { + // TODO: Ideally this context would be retrieved from a pool, so we don't have to + // create a new one everytime. We could keep a context with the array object + // but that would consume a lot of resources as each context is quite heavy. + cmpContext = objType->GetEngine()->CreateContext(); + } + } + + // Find the matching element + int ret = -1; + asUINT size = GetSize(); + + for( asUINT i = startAt; i < size; i++ ) + { + // value passed by reference + if( Equals(At(i), value, cmpContext, cache) ) + { + ret = (int)i; + break; + } + } + + if( cmpContext ) + { + if( isNested ) + { + asEContextState state = cmpContext->GetState(); + cmpContext->PopState(); + if( state == asEXECUTION_ABORTED ) + cmpContext->Abort(); + } + else + cmpContext->Release(); + } + + return ret; +} + + + +// internal +// Copy object handle or primitive value +// Even in arrays of objects the objects are allocated on +// the heap and the array stores the pointers to the objects +void CScriptArray::Copy(void *dst, void *src) +{ + memcpy(dst, src, elementSize); +} + + +// internal +// Swap two elements +// Even in arrays of objects the objects are allocated on +// the heap and the array stores the pointers to the objects. +void CScriptArray::Swap(void* a, void* b) +{ + asBYTE tmp[16]; + Copy(tmp, a); + Copy(a, b); + Copy(b, tmp); +} + + +// internal +// Return pointer to array item (object handle or primitive value) +void *CScriptArray::GetArrayItemPointer(int index) +{ + return buffer->data + index * elementSize; +} + +// internal +// Return pointer to data in buffer (object or primitive) +void *CScriptArray::GetDataPointer(void *buf) +{ + if ((subTypeId & asTYPEID_MASK_OBJECT) && !(subTypeId & asTYPEID_OBJHANDLE) ) + { + // Real address of object + return reinterpret_cast(*(size_t*)buf); + } + else + { + // Primitive is just a raw data + return buf; + } +} + + +// Sort ascending +void CScriptArray::SortAsc() +{ + Sort(0, GetSize(), true); +} + +// Sort ascending +void CScriptArray::SortAsc(asUINT startAt, asUINT count) +{ + Sort(startAt, count, true); +} + +// Sort descending +void CScriptArray::SortDesc() +{ + Sort(0, GetSize(), false); +} + +// Sort descending +void CScriptArray::SortDesc(asUINT startAt, asUINT count) +{ + Sort(startAt, count, false); +} + + +// internal +void CScriptArray::Sort(asUINT startAt, asUINT count, bool asc) +{ + // Subtype isn't primitive and doesn't have opCmp + SArrayCache *cache = reinterpret_cast(objType->GetUserData(ARRAY_CACHE)); + if( subTypeId & ~asTYPEID_MASK_SEQNBR ) + { + if( !cache || cache->cmpFunc == 0 ) + { + asIScriptContext *ctx = asGetActiveContext(); + asITypeInfo* subType = objType->GetEngine()->GetTypeInfoById(subTypeId); + + // Throw an exception + if( ctx ) + { + char tmp[512]; + + if( cache && cache->cmpFuncReturnCode == asMULTIPLE_FUNCTIONS ) +#if defined(_MSC_VER) && _MSC_VER >= 1500 && !defined(__S3E__) + sprintf_s(tmp, 512, "Type '%s' has multiple matching opCmp methods", subType->GetName()); +#else + snprintf(tmp, 512, "Type '%s' has multiple matching opCmp methods", subType->GetName()); +#endif + else +#if defined(_MSC_VER) && _MSC_VER >= 1500 && !defined(__S3E__) + sprintf_s(tmp, 512, "Type '%s' does not have a matching opCmp method", subType->GetName()); +#else + snprintf(tmp, 512, "Type '%s' does not have a matching opCmp method", subType->GetName()); +#endif + + ctx->SetException(tmp); + } + + return; + } + } + + // No need to sort + if( count < 2 ) + { + return; + } + + int start = startAt; + int end = startAt + count; + + // Check if we could access invalid item while sorting + if( start >= (int)buffer->numElements || end > (int)buffer->numElements ) + { + asIScriptContext *ctx = asGetActiveContext(); + + // Throw an exception + if( ctx ) + { + ctx->SetException("Index out of bounds"); + } + + return; + } + + if( subTypeId & ~asTYPEID_MASK_SEQNBR ) + { + asIScriptContext *cmpContext = 0; + bool isNested = false; + + // Try to reuse the active context + cmpContext = asGetActiveContext(); + if( cmpContext ) + { + if( cmpContext->GetEngine() == objType->GetEngine() && cmpContext->PushState() >= 0 ) + isNested = true; + else + cmpContext = 0; + } + if( cmpContext == 0 ) + cmpContext = objType->GetEngine()->RequestContext(); + + // Do the sorting + struct { + bool asc; + asIScriptContext *cmpContext; + asIScriptFunction *cmpFunc; + bool operator()(void *a, void *b) const + { + if( !asc ) + { + // Swap items + void *TEMP = a; + a = b; + b = TEMP; + } + + int r = 0; + + // Allow sort to work even if the array contains null handles + if( a == 0 ) return true; + if( b == 0 ) return false; + + // Execute object opCmp + if( cmpFunc ) + { + // TODO: Add proper error handling + r = cmpContext->Prepare(cmpFunc); assert(r >= 0); + r = cmpContext->SetObject(a); assert(r >= 0); + r = cmpContext->SetArgObject(0, b); assert(r >= 0); + r = cmpContext->Execute(); + + if( r == asEXECUTION_FINISHED ) + { + return (int)cmpContext->GetReturnDWord() < 0; + } + } + + return false; + } + } customLess = {asc, cmpContext, cache ? cache->cmpFunc : 0}; + std::sort((void**)GetArrayItemPointer(start), (void**)GetArrayItemPointer(end), customLess); + + // Clean up + if( cmpContext ) + { + if( isNested ) + { + asEContextState state = cmpContext->GetState(); + cmpContext->PopState(); + if( state == asEXECUTION_ABORTED ) + cmpContext->Abort(); + } + else + objType->GetEngine()->ReturnContext(cmpContext); + } + } + else + { + // TODO: Use std::sort for primitive types too + + // Insertion sort + asBYTE tmp[16]; + for( int i = start + 1; i < end; i++ ) + { + Copy(tmp, GetArrayItemPointer(i)); + + int j = i - 1; + + while( j >= start && Less(GetDataPointer(tmp), At(j), asc) ) + { + Copy(GetArrayItemPointer(j + 1), GetArrayItemPointer(j)); + j--; + } + + Copy(GetArrayItemPointer(j + 1), tmp); + } + } +} + +// Sort with script callback for comparing elements +void CScriptArray::Sort(asIScriptFunction *func, asUINT startAt, asUINT count) +{ + // No need to sort + if (count < 2) + return; + + // Check if we could access invalid item while sorting + asUINT start = startAt; + asUINT end = asQWORD(startAt) + asQWORD(count) >= (asQWORD(1)<<32) ? 0xFFFFFFFF : startAt + count; + if (end > buffer->numElements) + end = buffer->numElements; + + if (start >= buffer->numElements) + { + asIScriptContext *ctx = asGetActiveContext(); + + // Throw an exception + if (ctx) + ctx->SetException("Index out of bounds"); + + return; + } + + asIScriptContext *cmpContext = 0; + bool isNested = false; + + // Try to reuse the active context + cmpContext = asGetActiveContext(); + if (cmpContext) + { + if (cmpContext->GetEngine() == objType->GetEngine() && cmpContext->PushState() >= 0) + isNested = true; + else + cmpContext = 0; + } + if (cmpContext == 0) + cmpContext = objType->GetEngine()->RequestContext(); + + // TODO: Security issue: If the array is accessed from the callback while the sort is going on the result may be unpredictable + // For example, the callback resizes the array in the middle of the sort + // Possible solution: set a lock flag on the array, and prohibit modifications while the lock flag is set + + // Bubble sort + // TODO: optimize: Use an efficient sort algorithm + for (asUINT i = start; i+1 < end; i++) + { + asUINT best = i; + for (asUINT j = i + 1; j < end; j++) + { + cmpContext->Prepare(func); + cmpContext->SetArgAddress(0, At(j)); + cmpContext->SetArgAddress(1, At(best)); + int r = cmpContext->Execute(); + if (r != asEXECUTION_FINISHED) + break; + if (*(bool*)(cmpContext->GetAddressOfReturnValue())) + best = j; + } + + // With Swap we guarantee that the array always sees all references + // if the GC calls the EnumReferences in the middle of the sorting + if( best != i ) + Swap(GetArrayItemPointer(i), GetArrayItemPointer(best)); + } + + if (cmpContext) + { + if (isNested) + { + asEContextState state = cmpContext->GetState(); + cmpContext->PopState(); + if (state == asEXECUTION_ABORTED) + cmpContext->Abort(); + } + else + objType->GetEngine()->ReturnContext(cmpContext); + } +} + +// internal +void CScriptArray::CopyBuffer(SArrayBuffer *dst, SArrayBuffer *src) +{ + asIScriptEngine *engine = objType->GetEngine(); + if( subTypeId & asTYPEID_OBJHANDLE ) + { + // Copy the references and increase the reference counters + if( dst->numElements > 0 && src->numElements > 0 ) + { + int count = dst->numElements > src->numElements ? src->numElements : dst->numElements; + + void **max = (void**)(dst->data + count * sizeof(void*)); + void **d = (void**)dst->data; + void **s = (void**)src->data; + + for( ; d < max; d++, s++ ) + { + void *tmp = *d; + *d = *s; + if( *d ) + engine->AddRefScriptObject(*d, objType->GetSubType()); + // Release the old ref after incrementing the new to avoid problem incase it is the same ref + if( tmp ) + engine->ReleaseScriptObject(tmp, objType->GetSubType()); + } + } + } + else + { + if( dst->numElements > 0 && src->numElements > 0 ) + { + int count = dst->numElements > src->numElements ? src->numElements : dst->numElements; + if( subTypeId & asTYPEID_MASK_OBJECT ) + { + // Call the assignment operator on all of the objects + void **max = (void**)(dst->data + count * sizeof(void*)); + void **d = (void**)dst->data; + void **s = (void**)src->data; + + asITypeInfo *subType = objType->GetSubType(); + if (subType->GetFlags() & asOBJ_ASHANDLE) + { + // For objects that should work as handles we must use the opHndlAssign method + // TODO: Must support alternative syntaxes as well + // TODO: Move the lookup of the opHndlAssign method to Precache() so it is only done once + string decl = string(subType->GetName()) + "& opHndlAssign(const " + string(subType->GetName()) + "&in)"; + asIScriptFunction *func = subType->GetMethodByDecl(decl.c_str()); + if (func) + { + // TODO: Reuse active context if existing + asIScriptContext* ctx = engine->RequestContext(); + for (; d < max; d++, s++) + { + ctx->Prepare(func); + ctx->SetObject(*d); + ctx->SetArgAddress(0, *s); + // TODO: Handle errors + ctx->Execute(); + } + engine->ReturnContext(ctx); + } + else + { + // opHndlAssign doesn't exist, so try ordinary value assign instead + for (; d < max; d++, s++) + engine->AssignScriptObject(*d, *s, subType); + } + } + else + for( ; d < max; d++, s++ ) + engine->AssignScriptObject(*d, *s, subType); + } + else + { + // Primitives are copied byte for byte + memcpy(dst->data, src->data, count*elementSize); + } + } + } +} + +// internal +// Precache some info +void CScriptArray::Precache() +{ + subTypeId = objType->GetSubTypeId(); + + // Check if it is an array of objects. Only for these do we need to cache anything + // Type ids for primitives and enums only has the sequence number part + if( !(subTypeId & ~asTYPEID_MASK_SEQNBR) ) + return; + + // The opCmp and opEquals methods are cached because the searching for the + // methods is quite time consuming if a lot of array objects are created. + + // First check if a cache already exists for this array type + SArrayCache *cache = reinterpret_cast(objType->GetUserData(ARRAY_CACHE)); + if( cache ) return; + + // We need to make sure the cache is created only once, even + // if multiple threads reach the same point at the same time + asAcquireExclusiveLock(); + + // Now that we got the lock, we need to check again to make sure the + // cache wasn't created while we were waiting for the lock + cache = reinterpret_cast(objType->GetUserData(ARRAY_CACHE)); + if( cache ) + { + asReleaseExclusiveLock(); + return; + } + + // Create the cache + cache = reinterpret_cast(userAlloc(sizeof(SArrayCache))); + if( !cache ) + { + asIScriptContext *ctx = asGetActiveContext(); + if( ctx ) + ctx->SetException("Out of memory"); + asReleaseExclusiveLock(); + return; + } + memset(cache, 0, sizeof(SArrayCache)); + + // If the sub type is a handle to const, then the methods must be const too + bool mustBeConst = (subTypeId & asTYPEID_HANDLETOCONST) ? true : false; + + asITypeInfo *subType = objType->GetEngine()->GetTypeInfoById(subTypeId); + if( subType ) + { + for( asUINT i = 0; i < subType->GetMethodCount(); i++ ) + { + asIScriptFunction *func = subType->GetMethodByIndex(i); + + if( func->GetParamCount() == 1 && (!mustBeConst || func->IsReadOnly()) ) + { + asDWORD flags = 0; + int returnTypeId = func->GetReturnTypeId(&flags); + + // The method must not return a reference + if( flags != asTM_NONE ) + continue; + + // opCmp returns an int and opEquals returns a bool + bool isCmp = false, isEq = false; + if( returnTypeId == asTYPEID_INT32 && strcmp(func->GetName(), "opCmp") == 0 ) + isCmp = true; + if( returnTypeId == asTYPEID_BOOL && strcmp(func->GetName(), "opEquals") == 0 ) + isEq = true; + + if( !isCmp && !isEq ) + continue; + + // The parameter must either be a reference to the subtype or a handle to the subtype + int paramTypeId; + func->GetParam(0, ¶mTypeId, &flags); + + if( (paramTypeId & ~(asTYPEID_OBJHANDLE|asTYPEID_HANDLETOCONST)) != (subTypeId & ~(asTYPEID_OBJHANDLE|asTYPEID_HANDLETOCONST)) ) + continue; + + if( (flags & asTM_INREF) ) + { + if( (paramTypeId & asTYPEID_OBJHANDLE) || (mustBeConst && !(flags & asTM_CONST)) ) + continue; + } + else if( paramTypeId & asTYPEID_OBJHANDLE ) + { + if( mustBeConst && !(paramTypeId & asTYPEID_HANDLETOCONST) ) + continue; + } + else + continue; + + if( isCmp ) + { + if( cache->cmpFunc || cache->cmpFuncReturnCode ) + { + cache->cmpFunc = 0; + cache->cmpFuncReturnCode = asMULTIPLE_FUNCTIONS; + } + else + cache->cmpFunc = func; + } + else if( isEq ) + { + if( cache->eqFunc || cache->eqFuncReturnCode ) + { + cache->eqFunc = 0; + cache->eqFuncReturnCode = asMULTIPLE_FUNCTIONS; + } + else + cache->eqFunc = func; + } + } + } + } + + if( cache->eqFunc == 0 && cache->eqFuncReturnCode == 0 ) + cache->eqFuncReturnCode = asNO_FUNCTION; + if( cache->cmpFunc == 0 && cache->cmpFuncReturnCode == 0 ) + cache->cmpFuncReturnCode = asNO_FUNCTION; + + // Set the user data only at the end so others that retrieve it will know it is complete + objType->SetUserData(cache, ARRAY_CACHE); + + asReleaseExclusiveLock(); +} + +// GC behaviour +void CScriptArray::EnumReferences(asIScriptEngine *engine) +{ + // TODO: If garbage collection can be done from a separate thread, then this method must be + // protected so that it doesn't get lost during the iteration if the array is modified + + // If the array is holding handles, then we need to notify the GC of them + if( subTypeId & asTYPEID_MASK_OBJECT ) + { + void **d = (void**)buffer->data; + + asITypeInfo *subType = engine->GetTypeInfoById(subTypeId); + if ((subType->GetFlags() & asOBJ_REF)) + { + // For reference types we need to notify the GC of each instance + for (asUINT n = 0; n < buffer->numElements; n++) + { + if (d[n]) + engine->GCEnumCallback(d[n]); + } + } + else if ((subType->GetFlags() & asOBJ_VALUE) && (subType->GetFlags() & asOBJ_GC)) + { + // For value types we need to forward the enum callback + // to the object so it can decide what to do + for (asUINT n = 0; n < buffer->numElements; n++) + { + if (d[n]) + engine->ForwardGCEnumReferences(d[n], subType); + } + } + } +} + +// GC behaviour +void CScriptArray::ReleaseAllHandles(asIScriptEngine *) +{ + // Resizing to zero will release everything + Resize(0); +} + +void CScriptArray::AddRef() const +{ + // Clear the GC flag then increase the counter + gcFlag = false; + asAtomicInc(refCount); +} + +void CScriptArray::Release() const +{ + // Clearing the GC flag then descrease the counter + gcFlag = false; + if( asAtomicDec(refCount) == 0 ) + { + // When reaching 0 no more references to this instance + // exists and the object should be destroyed + this->~CScriptArray(); + userFree(const_cast(this)); + } +} + +// GC behaviour +int CScriptArray::GetRefCount() +{ + return refCount; +} + +// GC behaviour +void CScriptArray::SetFlag() +{ + gcFlag = true; +} + +// GC behaviour +bool CScriptArray::GetFlag() +{ + return gcFlag; +} + +//-------------------------------------------- +// Generic calling conventions + +static void ScriptArrayFactory_Generic(asIScriptGeneric *gen) +{ + asITypeInfo *ti = *(asITypeInfo**)gen->GetAddressOfArg(0); + + *reinterpret_cast(gen->GetAddressOfReturnLocation()) = CScriptArray::Create(ti); +} + +static void ScriptArrayFactory2_Generic(asIScriptGeneric *gen) +{ + asITypeInfo *ti = *(asITypeInfo**)gen->GetAddressOfArg(0); + asUINT length = gen->GetArgDWord(1); + + *reinterpret_cast(gen->GetAddressOfReturnLocation()) = CScriptArray::Create(ti, length); +} + +static void ScriptArrayListFactory_Generic(asIScriptGeneric *gen) +{ + asITypeInfo *ti = *(asITypeInfo**)gen->GetAddressOfArg(0); + void *buf = gen->GetArgAddress(1); + + *reinterpret_cast(gen->GetAddressOfReturnLocation()) = CScriptArray::Create(ti, buf); +} + +static void ScriptArrayFactoryDefVal_Generic(asIScriptGeneric *gen) +{ + asITypeInfo *ti = *(asITypeInfo**)gen->GetAddressOfArg(0); + asUINT length = gen->GetArgDWord(1); + void *defVal = gen->GetArgAddress(2); + + *reinterpret_cast(gen->GetAddressOfReturnLocation()) = CScriptArray::Create(ti, length, defVal); +} + +static void ScriptArrayTemplateCallback_Generic(asIScriptGeneric *gen) +{ + asITypeInfo *ti = *(asITypeInfo**)gen->GetAddressOfArg(0); + bool *dontGarbageCollect = *(bool**)gen->GetAddressOfArg(1); + *reinterpret_cast(gen->GetAddressOfReturnLocation()) = ScriptArrayTemplateCallback(ti, *dontGarbageCollect); +} + +static void ScriptArrayAssignment_Generic(asIScriptGeneric *gen) +{ + CScriptArray *other = (CScriptArray*)gen->GetArgObject(0); + CScriptArray *self = (CScriptArray*)gen->GetObject(); + *self = *other; + gen->SetReturnObject(self); +} + +static void ScriptArrayEquals_Generic(asIScriptGeneric *gen) +{ + CScriptArray *other = (CScriptArray*)gen->GetArgObject(0); + CScriptArray *self = (CScriptArray*)gen->GetObject(); + gen->SetReturnByte(self->operator==(*other)); +} + +static void ScriptArrayFind_Generic(asIScriptGeneric *gen) +{ + void *value = gen->GetArgAddress(0); + CScriptArray *self = (CScriptArray*)gen->GetObject(); + gen->SetReturnDWord(self->Find(value)); +} + +static void ScriptArrayFind2_Generic(asIScriptGeneric *gen) +{ + asUINT index = gen->GetArgDWord(0); + void *value = gen->GetArgAddress(1); + CScriptArray *self = (CScriptArray*)gen->GetObject(); + gen->SetReturnDWord(self->Find(index, value)); +} + +static void ScriptArrayFindByRef_Generic(asIScriptGeneric *gen) +{ + void *value = gen->GetArgAddress(0); + CScriptArray *self = (CScriptArray*)gen->GetObject(); + gen->SetReturnDWord(self->FindByRef(value)); +} + +static void ScriptArrayFindByRef2_Generic(asIScriptGeneric *gen) +{ + asUINT index = gen->GetArgDWord(0); + void *value = gen->GetArgAddress(1); + CScriptArray *self = (CScriptArray*)gen->GetObject(); + gen->SetReturnDWord(self->FindByRef(index, value)); +} + +static void ScriptArrayAt_Generic(asIScriptGeneric *gen) +{ + asINT64 index = gen->GetArgQWord(0); + CScriptArray *self = (CScriptArray*)gen->GetObject(); + + gen->SetReturnAddress(self->At(index)); +} + +static void ScriptArrayInsertAt_Generic(asIScriptGeneric *gen) +{ + asUINT index = gen->GetArgDWord(0); + void *value = gen->GetArgAddress(1); + CScriptArray *self = (CScriptArray*)gen->GetObject(); + self->InsertAt(index, value); +} + +static void ScriptArrayInsertAtArray_Generic(asIScriptGeneric *gen) +{ + asUINT index = gen->GetArgDWord(0); + CScriptArray *array = (CScriptArray*)gen->GetArgAddress(1); + CScriptArray *self = (CScriptArray*)gen->GetObject(); + self->InsertAt(index, *array); +} + +static void ScriptArrayRemoveAt_Generic(asIScriptGeneric *gen) +{ + asUINT index = gen->GetArgDWord(0); + CScriptArray *self = (CScriptArray*)gen->GetObject(); + self->RemoveAt(index); +} + +static void ScriptArrayRemoveRange_Generic(asIScriptGeneric *gen) +{ + asUINT start = gen->GetArgDWord(0); + asUINT count = gen->GetArgDWord(1); + CScriptArray *self = (CScriptArray*)gen->GetObject(); + self->RemoveRange(start, count); +} + +static void ScriptArrayInsertLast_Generic(asIScriptGeneric *gen) +{ + void *value = gen->GetArgAddress(0); + CScriptArray *self = (CScriptArray*)gen->GetObject(); + self->InsertLast(value); +} + +static void ScriptArrayRemoveLast_Generic(asIScriptGeneric *gen) +{ + CScriptArray *self = (CScriptArray*)gen->GetObject(); + self->RemoveLast(); +} + +static void ScriptArrayLength_Generic(asIScriptGeneric *gen) +{ + CScriptArray *self = (CScriptArray*)gen->GetObject(); + + gen->SetReturnDWord(self->GetSize()); +} + +static void ScriptArrayResize_Generic(asIScriptGeneric *gen) +{ + asUINT size = gen->GetArgDWord(0); + CScriptArray *self = (CScriptArray*)gen->GetObject(); + + self->Resize(size); +} + +static void ScriptArrayReserve_Generic(asIScriptGeneric *gen) +{ + asUINT size = gen->GetArgDWord(0); + CScriptArray *self = (CScriptArray*)gen->GetObject(); + self->Reserve(size); +} + +static void ScriptArraySortAsc_Generic(asIScriptGeneric *gen) +{ + CScriptArray *self = (CScriptArray*)gen->GetObject(); + self->SortAsc(); +} + +static void ScriptArrayReverse_Generic(asIScriptGeneric *gen) +{ + CScriptArray *self = (CScriptArray*)gen->GetObject(); + self->Reverse(); +} + +static void ScriptArrayIsEmpty_Generic(asIScriptGeneric *gen) +{ + CScriptArray *self = (CScriptArray*)gen->GetObject(); + *reinterpret_cast(gen->GetAddressOfReturnLocation()) = self->IsEmpty(); +} + +static void ScriptArraySortAsc2_Generic(asIScriptGeneric *gen) +{ + asUINT index = gen->GetArgDWord(0); + asUINT count = gen->GetArgDWord(1); + CScriptArray *self = (CScriptArray*)gen->GetObject(); + self->SortAsc(index, count); +} + +static void ScriptArraySortDesc_Generic(asIScriptGeneric *gen) +{ + CScriptArray *self = (CScriptArray*)gen->GetObject(); + self->SortDesc(); +} + +static void ScriptArraySortDesc2_Generic(asIScriptGeneric *gen) +{ + asUINT index = gen->GetArgDWord(0); + asUINT count = gen->GetArgDWord(1); + CScriptArray *self = (CScriptArray*)gen->GetObject(); + self->SortDesc(index, count); +} + +static void ScriptArraySortCallback_Generic(asIScriptGeneric *gen) +{ + asIScriptFunction *callback = (asIScriptFunction*)gen->GetArgAddress(0); + asUINT startAt = gen->GetArgDWord(1); + asUINT count = gen->GetArgDWord(2); + CScriptArray *self = (CScriptArray*)gen->GetObject(); + self->Sort(callback, startAt, count); +} + +static void ScriptArrayAddRef_Generic(asIScriptGeneric *gen) +{ + CScriptArray *self = (CScriptArray*)gen->GetObject(); + self->AddRef(); +} + +static void ScriptArrayRelease_Generic(asIScriptGeneric *gen) +{ + CScriptArray *self = (CScriptArray*)gen->GetObject(); + self->Release(); +} + +static void ScriptArrayGetRefCount_Generic(asIScriptGeneric *gen) +{ + CScriptArray *self = (CScriptArray*)gen->GetObject(); + *reinterpret_cast(gen->GetAddressOfReturnLocation()) = self->GetRefCount(); +} + +static void ScriptArraySetFlag_Generic(asIScriptGeneric *gen) +{ + CScriptArray *self = (CScriptArray*)gen->GetObject(); + self->SetFlag(); +} + +static void ScriptArrayGetFlag_Generic(asIScriptGeneric *gen) +{ + CScriptArray *self = (CScriptArray*)gen->GetObject(); + *reinterpret_cast(gen->GetAddressOfReturnLocation()) = self->GetFlag(); +} + +static void ScriptArrayEnumReferences_Generic(asIScriptGeneric *gen) +{ + CScriptArray *self = (CScriptArray*)gen->GetObject(); + asIScriptEngine *engine = *(asIScriptEngine**)gen->GetAddressOfArg(0); + self->EnumReferences(engine); +} + +static void ScriptArrayReleaseAllHandles_Generic(asIScriptGeneric *gen) +{ + CScriptArray *self = (CScriptArray*)gen->GetObject(); + asIScriptEngine *engine = *(asIScriptEngine**)gen->GetAddressOfArg(0); + self->ReleaseAllHandles(engine); +} + +static void RegisterScriptArray_Generic(asIScriptEngine *engine) +{ + int r = 0; + UNUSED_VAR(r); + + engine->SetTypeInfoUserDataCleanupCallback(CleanupTypeInfoArrayCache, ARRAY_CACHE); + + r = engine->RegisterObjectType("array", 0, asOBJ_REF | asOBJ_GC | asOBJ_TEMPLATE); assert( r >= 0 ); + r = engine->RegisterObjectBehaviour("array", asBEHAVE_TEMPLATE_CALLBACK, "bool f(int&in, bool&out)", asFUNCTION(ScriptArrayTemplateCallback_Generic), asCALL_GENERIC); assert( r >= 0 ); + + r = engine->RegisterObjectBehaviour("array", asBEHAVE_FACTORY, "array@ f(int&in)", asFUNCTION(ScriptArrayFactory_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectBehaviour("array", asBEHAVE_FACTORY, "array@ f(int&in, uint length) explicit", asFUNCTION(ScriptArrayFactory2_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectBehaviour("array", asBEHAVE_FACTORY, "array@ f(int&in, uint length, const T &in value)", asFUNCTION(ScriptArrayFactoryDefVal_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectBehaviour("array", asBEHAVE_LIST_FACTORY, "array@ f(int&in, int&in) {repeat T}", asFUNCTION(ScriptArrayListFactory_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectBehaviour("array", asBEHAVE_ADDREF, "void f()", asFUNCTION(ScriptArrayAddRef_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectBehaviour("array", asBEHAVE_RELEASE, "void f()", asFUNCTION(ScriptArrayRelease_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "T &opIndex(int64 index)", asFUNCTION(ScriptArrayAt_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "const T &opIndex(int64 index) const", asFUNCTION(ScriptArrayAt_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "array &opAssign(const array&in)", asFUNCTION(ScriptArrayAssignment_Generic), asCALL_GENERIC); assert( r >= 0 ); + + r = engine->RegisterObjectMethod("array", "void insert_at(uint index, const T&in value)", asFUNCTION(ScriptArrayInsertAt_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "void insert_at(uint index, const array& arr)", asFUNCTION(ScriptArrayInsertAtArray_Generic), asCALL_GENERIC); assert(r >= 0); + r = engine->RegisterObjectMethod("array", "void insert_last(const T&in value)", asFUNCTION(ScriptArrayInsertLast_Generic), asCALL_GENERIC); assert(r >= 0); + r = engine->RegisterObjectMethod("array", "void remove_at(uint index)", asFUNCTION(ScriptArrayRemoveAt_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "void remove_last()", asFUNCTION(ScriptArrayRemoveLast_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "void remove_range(uint start, uint count)", asFUNCTION(ScriptArrayRemoveRange_Generic), asCALL_GENERIC); assert(r >= 0); +#if AS_USE_ACCESSORS != 1 + r = engine->RegisterObjectMethod("array", "uint length() const", asFUNCTION(ScriptArrayLength_Generic), asCALL_GENERIC); assert( r >= 0 ); +#endif + r = engine->RegisterObjectMethod("array", "void reserve(uint length)", asFUNCTION(ScriptArrayReserve_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "void resize(uint length)", asFUNCTION(ScriptArrayResize_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "void sort_ascending()", asFUNCTION(ScriptArraySortAsc_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "void sort_ascending(uint startAt, uint count)", asFUNCTION(ScriptArraySortAsc2_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "void sort_descending()", asFUNCTION(ScriptArraySortDesc_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "void sort_descending(uint startAt, uint count)", asFUNCTION(ScriptArraySortDesc2_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "void reverse()", asFUNCTION(ScriptArrayReverse_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "int find(const T&in if_handle_then_const value) const", asFUNCTION(ScriptArrayFind_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "int find(uint startAt, const T&in if_handle_then_const value) const", asFUNCTION(ScriptArrayFind2_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "int find_by_ref(const T&in if_handle_then_const value) const", asFUNCTION(ScriptArrayFindByRef_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "int find_by_ref(uint startAt, const T&in if_handle_then_const value) const", asFUNCTION(ScriptArrayFindByRef2_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "bool opEquals(const array&in) const", asFUNCTION(ScriptArrayEquals_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "bool is_empty() const", asFUNCTION(ScriptArrayIsEmpty_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterFuncdef("bool array::less(const T&in if_handle_then_const a, const T&in if_handle_then_const b)"); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "void sort(const less &in, uint startAt = 0, uint count = uint(-1))", asFUNCTION(ScriptArraySortCallback_Generic), asCALL_GENERIC); assert(r >= 0); +#if AS_USE_STLNAMES != 1 && AS_USE_ACCESSORS == 1 + r = engine->RegisterObjectMethod("array", "uint get_length() const property", asFUNCTION(ScriptArrayLength_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "void set_length(uint) property", asFUNCTION(ScriptArrayResize_Generic), asCALL_GENERIC); assert( r >= 0 ); +#endif + r = engine->RegisterObjectBehaviour("array", asBEHAVE_GETREFCOUNT, "int f()", asFUNCTION(ScriptArrayGetRefCount_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectBehaviour("array", asBEHAVE_SETGCFLAG, "void f()", asFUNCTION(ScriptArraySetFlag_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectBehaviour("array", asBEHAVE_GETGCFLAG, "bool f()", asFUNCTION(ScriptArrayGetFlag_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectBehaviour("array", asBEHAVE_ENUMREFS, "void f(int&in)", asFUNCTION(ScriptArrayEnumReferences_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectBehaviour("array", asBEHAVE_RELEASEREFS, "void f(int&in)", asFUNCTION(ScriptArrayReleaseAllHandles_Generic), asCALL_GENERIC); assert( r >= 0 ); +} + +END_AS_NAMESPACE diff --git a/ASAddon/src/scripthelper.cpp b/ASAddon/src/scripthelper.cpp index f34d3d77..9386eb34 100644 --- a/ASAddon/src/scripthelper.cpp +++ b/ASAddon/src/scripthelper.cpp @@ -1,1028 +1,1028 @@ -#include -#include "scripthelper.h" -#include -#include -#include -#include -#include -#include "aswrappedcall.h" - -using namespace std; - -BEGIN_AS_NAMESPACE - -int CompareRelation(asIScriptEngine *engine, void *lobj, void *robj, int typeId, int &result) -{ - // TODO: If a lot of script objects are going to be compared, e.g. when sorting an array, - // then the method id and context should be cached between calls. - - int retval = -1; - asIScriptFunction *func = 0; - - asITypeInfo *ti = engine->GetTypeInfoById(typeId); - if( ti ) - { - // Check if the object type has a compatible opCmp method - for( asUINT n = 0; n < ti->GetMethodCount(); n++ ) - { - asIScriptFunction *f = ti->GetMethodByIndex(n); - asDWORD flags; - if( strcmp(f->GetName(), "opCmp") == 0 && - f->GetReturnTypeId(&flags) == asTYPEID_INT32 && - flags == asTM_NONE && - f->GetParamCount() == 1 ) - { - int paramTypeId; - f->GetParam(0, ¶mTypeId, &flags); - - // The parameter must be an input reference of the same type - // If the reference is a inout reference, then it must also be read-only - if( !(flags & asTM_INREF) || typeId != paramTypeId || ((flags & asTM_OUTREF) && !(flags & asTM_CONST)) ) - break; - - // Found the method - func = f; - break; - } - } - } - - if( func ) - { - // Call the method - asIScriptContext *ctx = engine->CreateContext(); - ctx->Prepare(func); - ctx->SetObject(lobj); - ctx->SetArgAddress(0, robj); - int r = ctx->Execute(); - if( r == asEXECUTION_FINISHED ) - { - result = (int)ctx->GetReturnDWord(); - - // The comparison was successful - retval = 0; - } - ctx->Release(); - } - - return retval; -} - -int CompareEquality(asIScriptEngine *engine, void *lobj, void *robj, int typeId, bool &result) -{ - // TODO: If a lot of script objects are going to be compared, e.g. when searching for an - // entry in a set, then the method and context should be cached between calls. - - int retval = -1; - asIScriptFunction *func = 0; - - asITypeInfo *ti = engine->GetTypeInfoById(typeId); - if( ti ) - { - // Check if the object type has a compatible opEquals method - for( asUINT n = 0; n < ti->GetMethodCount(); n++ ) - { - asIScriptFunction *f = ti->GetMethodByIndex(n); - asDWORD flags; - if( strcmp(f->GetName(), "opEquals") == 0 && - f->GetReturnTypeId(&flags) == asTYPEID_BOOL && - flags == asTM_NONE && - f->GetParamCount() == 1 ) - { - int paramTypeId; - f->GetParam(0, ¶mTypeId, &flags); - - // The parameter must be an input reference of the same type - // If the reference is a inout reference, then it must also be read-only - if( !(flags & asTM_INREF) || typeId != paramTypeId || ((flags & asTM_OUTREF) && !(flags & asTM_CONST)) ) - break; - - // Found the method - func = f; - break; - } - } - } - - if( func ) - { - // Call the method - asIScriptContext *ctx = engine->CreateContext(); - ctx->Prepare(func); - ctx->SetObject(lobj); - ctx->SetArgAddress(0, robj); - int r = ctx->Execute(); - if( r == asEXECUTION_FINISHED ) - { - result = ctx->GetReturnByte() ? true : false; - - // The comparison was successful - retval = 0; - } - ctx->Release(); - } - else - { - // If the opEquals method doesn't exist, then we try with opCmp instead - int relation; - retval = CompareRelation(engine, lobj, robj, typeId, relation); - if( retval >= 0 ) - result = relation == 0 ? true : false; - } - - return retval; -} - -int ExecuteString(asIScriptEngine *engine, const char *code, asIScriptModule *mod, asIScriptContext *ctx) -{ - return ExecuteString(engine, code, 0, asTYPEID_VOID, mod, ctx); -} - -int ExecuteString(asIScriptEngine *engine, const char *code, void *ref, int refTypeId, asIScriptModule *mod, asIScriptContext *ctx) -{ - // Wrap the code in a function so that it can be compiled and executed - string funcCode = " ExecuteString() {\n"; - funcCode += code; - funcCode += "\n;}"; - - // Determine the return type based on the type of the ref arg - funcCode = engine->GetTypeDeclaration(refTypeId, true) + funcCode; - - // GetModule will free unused types, so to be on the safe side we'll hold on to a reference to the type - asITypeInfo *type = 0; - if( refTypeId & asTYPEID_MASK_OBJECT ) - { - type = engine->GetTypeInfoById(refTypeId); - if( type ) - type->AddRef(); - } - - // If no module was provided, get a dummy from the engine - asIScriptModule *execMod = mod ? mod : engine->GetModule("ExecuteString", asGM_ALWAYS_CREATE); - - // Now it's ok to release the type - if( type ) - type->Release(); - - // Compile the function that can be executed - asIScriptFunction *func = 0; - int r = execMod->CompileFunction("ExecuteString", funcCode.c_str(), -1, 0, &func); - if( r < 0 ) - return r; - - // If no context was provided, request a new one from the engine - asIScriptContext *execCtx = ctx ? ctx : engine->RequestContext(); - r = execCtx->Prepare(func); - if (r >= 0) - { - // Execute the function - r = execCtx->Execute(); - - // Unless the provided type was void retrieve it's value - if (ref != 0 && refTypeId != asTYPEID_VOID) - { - if (refTypeId & asTYPEID_OBJHANDLE) - { - // Expect the pointer to be null to start with - assert(*reinterpret_cast(ref) == 0); - *reinterpret_cast(ref) = *reinterpret_cast(execCtx->GetAddressOfReturnValue()); - engine->AddRefScriptObject(*reinterpret_cast(ref), engine->GetTypeInfoById(refTypeId)); - } - else if (refTypeId & asTYPEID_MASK_OBJECT) - { - // Use the registered assignment operator to do a value assign. - // This assumes that the ref is pointing to a valid object instance. - engine->AssignScriptObject(ref, execCtx->GetAddressOfReturnValue(), engine->GetTypeInfoById(refTypeId)); - } - else - { - // Copy the primitive value - memcpy(ref, execCtx->GetAddressOfReturnValue(), engine->GetSizeOfPrimitiveType(refTypeId)); - } - } - } - - // Clean up - func->Release(); - if( !ctx ) engine->ReturnContext(execCtx); - - return r; -} - -int WriteConfigToFile(asIScriptEngine *engine, const char *filename) -{ - ofstream strm; - strm.open(filename); - - return WriteConfigToStream(engine, strm); -} - -int WriteConfigToStream(asIScriptEngine *engine, ostream &strm) -{ - // A helper function for escaping quotes in default arguments - struct Escape - { - static string Quotes(const char *decl) - { - string str = decl; - size_t pos = 0; - for(;;) - { - // Find " characters - pos = str.find("\"",pos); - if( pos == string::npos ) - break; - - // Add a \ to escape them - str.insert(pos, "\\"); - pos += 2; - } - - return str; - } - }; - - int c, n; - - asDWORD currAccessMask = 0; - string currNamespace = ""; - engine->SetDefaultNamespace(""); - - // Export the engine version, just for info - strm << "// AngelScript " << asGetLibraryVersion() << "\n"; - strm << "// Lib options " << asGetLibraryOptions() << "\n"; - - // Export the relevant engine properties - strm << "// Engine properties\n"; - for( n = 0; n < asEP_LAST_PROPERTY; n++ ) - strm << "ep " << n << " " << engine->GetEngineProperty(asEEngineProp(n)) << "\n"; - - // Make sure the default array type is expanded to the template form - bool expandDefArrayToTempl = engine->GetEngineProperty(asEP_EXPAND_DEF_ARRAY_TO_TMPL) ? true : false; - engine->SetEngineProperty(asEP_EXPAND_DEF_ARRAY_TO_TMPL, true); - - // Write enum types and their values - strm << "\n// Enums\n"; - c = engine->GetEnumCount(); - for( n = 0; n < c; n++ ) - { - asITypeInfo *ti = engine->GetEnumByIndex(n); - asDWORD accessMask = ti->GetAccessMask(); - if( accessMask != currAccessMask ) - { - strm << "access " << hex << (unsigned int)(accessMask) << dec << "\n"; - currAccessMask = accessMask; - } - const char *nameSpace = ti->GetNamespace(); - if( nameSpace != currNamespace ) - { - strm << "namespace \"" << nameSpace << "\"\n"; - currNamespace = nameSpace; - engine->SetDefaultNamespace(currNamespace.c_str()); - } - const char *enumName = ti->GetName(); - strm << "enum " << enumName << "\n"; - for( asUINT m = 0; m < ti->GetEnumValueCount(); m++ ) - { - const char *valName; - int val; - valName = ti->GetEnumValueByIndex(m, &val); - strm << "enumval " << enumName << " " << valName << " " << val << "\n"; - } - } - - // Enumerate all types - strm << "\n// Types\n"; - - // Keep a list of the template types, as the methods for these need to be exported first - set templateTypes; - - c = engine->GetObjectTypeCount(); - for( n = 0; n < c; n++ ) - { - asITypeInfo *type = engine->GetObjectTypeByIndex(n); - asDWORD accessMask = type->GetAccessMask(); - if( accessMask != currAccessMask ) - { - strm << "access " << hex << (unsigned int)(accessMask) << dec << "\n"; - currAccessMask = accessMask; - } - const char *nameSpace = type->GetNamespace(); - if( nameSpace != currNamespace ) - { - strm << "namespace \"" << nameSpace << "\"\n"; - currNamespace = nameSpace; - engine->SetDefaultNamespace(currNamespace.c_str()); - } - if( type->GetFlags() & asOBJ_SCRIPT_OBJECT ) - { - // This should only be interfaces - assert( type->GetSize() == 0 ); - - strm << "intf " << type->GetName() << "\n"; - } - else - { - // Only the type flags are necessary. The application flags are application - // specific and doesn't matter to the offline compiler. The object size is also - // unnecessary for the offline compiler - strm << "objtype \"" << engine->GetTypeDeclaration(type->GetTypeId()) << "\" " << (unsigned int)(type->GetFlags() & asOBJ_MASK_VALID_FLAGS) << "\n"; - - // Store the template types (but not template instances) - if( (type->GetFlags() & asOBJ_TEMPLATE) && type->GetSubType() && (type->GetSubType()->GetFlags() & asOBJ_TEMPLATE_SUBTYPE) ) - templateTypes.insert(type); - } - } - - c = engine->GetTypedefCount(); - for( n = 0; n < c; n++ ) - { - asITypeInfo *ti = engine->GetTypedefByIndex(n); - const char *nameSpace = ti->GetNamespace(); - if( nameSpace != currNamespace ) - { - strm << "namespace \"" << nameSpace << "\"\n"; - currNamespace = nameSpace; - engine->SetDefaultNamespace(currNamespace.c_str()); - } - asDWORD accessMask = ti->GetAccessMask(); - if( accessMask != currAccessMask ) - { - strm << "access " << hex << (unsigned int)(accessMask) << dec << "\n"; - currAccessMask = accessMask; - } - strm << "typedef " << ti->GetName() << " \"" << engine->GetTypeDeclaration(ti->GetTypedefTypeId()) << "\"\n"; - } - - c = engine->GetFuncdefCount(); - for( n = 0; n < c; n++ ) - { - asITypeInfo *funcDef = engine->GetFuncdefByIndex(n); - asDWORD accessMask = funcDef->GetAccessMask(); - const char *nameSpace = funcDef->GetNamespace(); - // Child funcdefs do not have any namespace, as they belong to the parent object - if( nameSpace && nameSpace != currNamespace ) - { - strm << "namespace \"" << nameSpace << "\"\n"; - currNamespace = nameSpace; - engine->SetDefaultNamespace(currNamespace.c_str()); - } - if( accessMask != currAccessMask ) - { - strm << "access " << hex << (unsigned int)(accessMask) << dec << "\n"; - currAccessMask = accessMask; - } - strm << "funcdef \"" << funcDef->GetFuncdefSignature()->GetDeclaration(true, false, true) << "\"\n"; - } - - // A helper for writing object type members - struct TypeWriter - { - static void Write(asIScriptEngine *engine, ostream &strm, asITypeInfo *type, string &currNamespace, asDWORD &currAccessMask) - { - const char *nameSpace = type->GetNamespace(); - if( nameSpace != currNamespace ) - { - strm << "namespace \"" << nameSpace << "\"\n"; - currNamespace = nameSpace; - engine->SetDefaultNamespace(currNamespace.c_str()); - } - string typeDecl = engine->GetTypeDeclaration(type->GetTypeId()); - if( type->GetFlags() & asOBJ_SCRIPT_OBJECT ) - { - for( asUINT m = 0; m < type->GetMethodCount(); m++ ) - { - asIScriptFunction *func = type->GetMethodByIndex(m); - asDWORD accessMask = func->GetAccessMask(); - if( accessMask != currAccessMask ) - { - strm << "access " << hex << (unsigned int)(accessMask) << dec << "\n"; - currAccessMask = accessMask; - } - strm << "intfmthd " << typeDecl.c_str() << " \"" << Escape::Quotes(func->GetDeclaration(false, false, true)).c_str() << (func->IsProperty() ? " property" : "") << "\"\n"; - } - } - else - { - asUINT m; - for( m = 0; m < type->GetFactoryCount(); m++ ) - { - asIScriptFunction *func = type->GetFactoryByIndex(m); - asDWORD accessMask = func->GetAccessMask(); - if( accessMask != currAccessMask ) - { - strm << "access " << hex << (unsigned int)(accessMask) << dec << "\n"; - currAccessMask = accessMask; - } - strm << "objbeh \"" << typeDecl.c_str() << "\" " << asBEHAVE_FACTORY << " \"" << Escape::Quotes(func->GetDeclaration(false, false, true)).c_str() << "\"\n"; - } - for( m = 0; m < type->GetBehaviourCount(); m++ ) - { - asEBehaviours beh; - asIScriptFunction *func = type->GetBehaviourByIndex(m, &beh); - - if( beh == asBEHAVE_CONSTRUCT ) - // Prefix 'void' - strm << "objbeh \"" << typeDecl.c_str() << "\" " << beh << " \"void " << Escape::Quotes(func->GetDeclaration(false, false, true)).c_str() << "\"\n"; - else if( beh == asBEHAVE_DESTRUCT ) - // Prefix 'void' and remove ~ - strm << "objbeh \"" << typeDecl.c_str() << "\" " << beh << " \"void " << Escape::Quotes(func->GetDeclaration(false, false, true)).c_str()+1 << "\"\n"; - else - strm << "objbeh \"" << typeDecl.c_str() << "\" " << beh << " \"" << Escape::Quotes(func->GetDeclaration(false, false, true)).c_str() << "\"\n"; - } - for( m = 0; m < type->GetMethodCount(); m++ ) - { - asIScriptFunction *func = type->GetMethodByIndex(m); - asDWORD accessMask = func->GetAccessMask(); - if( accessMask != currAccessMask ) - { - strm << "access " << hex << (unsigned int)(accessMask) << dec << "\n"; - currAccessMask = accessMask; - } - strm << "objmthd \"" << typeDecl.c_str() << "\" \"" << Escape::Quotes(func->GetDeclaration(false, false, true)).c_str() << (func->IsProperty() ? " property" : "") << "\"\n"; - } - for( m = 0; m < type->GetPropertyCount(); m++ ) - { - asDWORD accessMask; - type->GetProperty(m, 0, 0, 0, 0, 0, 0, &accessMask); - if( accessMask != currAccessMask ) - { - strm << "access " << hex << (unsigned int)(accessMask) << dec << "\n"; - currAccessMask = accessMask; - } - strm << "objprop \"" << typeDecl.c_str() << "\" \"" << type->GetPropertyDeclaration(m) << "\""; - - // Save information about composite properties - int compositeOffset; - bool isCompositeIndirect; - type->GetProperty(m, 0, 0, 0, 0, 0, 0, 0, &compositeOffset, &isCompositeIndirect); - strm << " " << compositeOffset << " " << (isCompositeIndirect ? "1" : "0") << "\n"; - } - } - } - }; - - // Write the members of the template types, so they can be fully registered before any other type uses them - // TODO: Order the template types based on dependency to avoid failure if one type uses instances of another - strm << "\n// Template type members\n"; - for( set::iterator it = templateTypes.begin(); it != templateTypes.end(); ++it ) - { - asITypeInfo *type = *it; - TypeWriter::Write(engine, strm, type, currNamespace, currAccessMask); - } - - // Write the object types members - strm << "\n// Type members\n"; - - c = engine->GetObjectTypeCount(); - for( n = 0; n < c; n++ ) - { - asITypeInfo *type = engine->GetObjectTypeByIndex(n); - if( templateTypes.find(type) == templateTypes.end() ) - TypeWriter::Write(engine, strm, type, currNamespace, currAccessMask); - } - - // Write functions - strm << "\n// Functions\n"; - - c = engine->GetGlobalFunctionCount(); - for( n = 0; n < c; n++ ) - { - asIScriptFunction *func = engine->GetGlobalFunctionByIndex(n); - const char *nameSpace = func->GetNamespace(); - if( nameSpace != currNamespace ) - { - strm << "namespace \"" << nameSpace << "\"\n"; - currNamespace = nameSpace; - engine->SetDefaultNamespace(currNamespace.c_str()); - } - asDWORD accessMask = func->GetAccessMask(); - if( accessMask != currAccessMask ) - { - strm << "access " << hex << (unsigned int)(accessMask) << dec << "\n"; - currAccessMask = accessMask; - } - strm << "func \"" << Escape::Quotes(func->GetDeclaration(true, false, true)).c_str() << (func->IsProperty() ? " property" : "") << "\"\n"; - } - - // Write global properties - strm << "\n// Properties\n"; - - c = engine->GetGlobalPropertyCount(); - for( n = 0; n < c; n++ ) - { - const char *name; - int typeId; - bool isConst; - asDWORD accessMask; - const char *nameSpace; - engine->GetGlobalPropertyByIndex(n, &name, &nameSpace, &typeId, &isConst, 0, 0, &accessMask); - if( accessMask != currAccessMask ) - { - strm << "access " << hex << (unsigned int)(accessMask) << dec << "\n"; - currAccessMask = accessMask; - } - if( nameSpace != currNamespace ) - { - strm << "namespace \"" << nameSpace << "\"\n"; - currNamespace = nameSpace; - engine->SetDefaultNamespace(currNamespace.c_str()); - } - strm << "prop \"" << (isConst ? "const " : "") << engine->GetTypeDeclaration(typeId) << " " << name << "\"\n"; - } - - // Write string factory - strm << "\n// String factory\n"; - - // Reset the namespace for the string factory and default array type - if ("" != currNamespace) - { - strm << "namespace \"\"\n"; - currNamespace = ""; - engine->SetDefaultNamespace(""); - } - - asDWORD flags = 0; - int typeId = engine->GetStringFactory(&flags); - if( typeId > 0 ) - strm << "strfactory \"" << ((flags & asTM_CONST) ? "const " : "") << engine->GetTypeDeclaration(typeId) << ((flags & asTM_INOUTREF) ? "&" : "") << "\"\n"; - - // Write default array type - strm << "\n// Default array type\n"; - typeId = engine->GetDefaultArrayTypeId(); - if( typeId > 0 ) - strm << "defarray \"" << engine->GetTypeDeclaration(typeId) << "\"\n"; - - // Restore original settings - engine->SetEngineProperty(asEP_EXPAND_DEF_ARRAY_TO_TMPL, expandDefArrayToTempl); - - return 0; -} - -int ConfigEngineFromStream(asIScriptEngine *engine, istream &strm, const char *configFile, asIStringFactory *stringFactory) -{ - int r; - - // Some helper functions for parsing the configuration - struct in - { - static asETokenClass GetToken(asIScriptEngine *engine, string &token, const string &text, asUINT &pos) - { - asUINT len = 0; - asETokenClass t = engine->ParseToken(&text[pos], text.length() - pos, &len); - while( (t == asTC_WHITESPACE || t == asTC_COMMENT) && pos < text.length() ) - { - pos += len; - t = engine->ParseToken(&text[pos], text.length() - pos, &len); - } - - token.assign(&text[pos], len); - - pos += len; - - return t; - } - - static void ReplaceSlashQuote(string &str) - { - size_t pos = 0; - for(;;) - { - // Search for \" in the string - pos = str.find("\\\"", pos); - if( pos == string::npos ) - break; - - // Remove the \ character - str.erase(pos, 1); - } - } - - static asUINT GetLineNumber(const string &text, asUINT pos) - { - asUINT count = 1; - for( asUINT n = 0; n < pos; n++ ) - if( text[n] == '\n' ) - count++; - - return count; - } - }; - - // Since we are only going to compile the script and never actually execute it, - // we turn off the initialization of global variables, so that the compiler can - // just register dummy types and functions for the application interface. - r = engine->SetEngineProperty(asEP_INIT_GLOBAL_VARS_AFTER_BUILD, false); assert( r >= 0 ); - - // Read the entire file - char buffer[1000]; - string config; - do { - strm.getline(buffer, 1000); - config += buffer; - config += "\n"; - } while( !strm.eof() && strm.good() ); - - // Process the configuration file and register each entity - asUINT pos = 0; - while( pos < config.length() ) - { - string token; - // TODO: The position where the initial token is found should be stored for error messages - in::GetToken(engine, token, config, pos); - if( token == "ep" ) - { - string tmp; - in::GetToken(engine, tmp, config, pos); - - asEEngineProp ep = asEEngineProp(atol(tmp.c_str())); - - // Only set properties that affect the compiler - if( ep != asEP_COPY_SCRIPT_SECTIONS && - ep != asEP_MAX_STACK_SIZE && - ep != asEP_INIT_GLOBAL_VARS_AFTER_BUILD && - ep != asEP_EXPAND_DEF_ARRAY_TO_TMPL && - ep != asEP_AUTO_GARBAGE_COLLECT ) - { - // Get the value for the property - in::GetToken(engine, tmp, config, pos); - stringstream s(tmp); - asPWORD value; - - s >> value; - - engine->SetEngineProperty(ep, value); - } - } - else if( token == "namespace" ) - { - string ns; - in::GetToken(engine, ns, config, pos); - ns = ns.substr(1, ns.length() - 2); - - r = engine->SetDefaultNamespace(ns.c_str()); - if( r < 0 ) - { - engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to set namespace"); - return -1; - } - } - else if( token == "access" ) - { - string maskStr; - in::GetToken(engine, maskStr, config, pos); - asDWORD mask = strtoul(maskStr.c_str(), 0, 16); - engine->SetDefaultAccessMask(mask); - } - else if( token == "objtype" ) - { - string name, flags; - in::GetToken(engine, name, config, pos); - name = name.substr(1, name.length() - 2); - in::GetToken(engine, flags, config, pos); - - // The size of the value type doesn't matter, because the - // engine must adjust it anyway for different platforms - r = engine->RegisterObjectType(name.c_str(), (atol(flags.c_str()) & asOBJ_VALUE) ? 1 : 0, atol(flags.c_str())); - if( r < 0 ) - { - engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register object type"); - return -1; - } - } - else if( token == "objbeh" ) - { - string name, behaviour, decl; - in::GetToken(engine, name, config, pos); - name = name.substr(1, name.length() - 2); - in::GetToken(engine, behaviour, config, pos); - in::GetToken(engine, decl, config, pos); - decl = decl.substr(1, decl.length() - 2); - in::ReplaceSlashQuote(decl); - - // Remove the $ that the engine prefixes the behaviours with - size_t n = decl.find("$"); - if( n != string::npos ) - decl[n] = ' '; - - asEBehaviours behave = static_cast(atol(behaviour.c_str())); - if( behave == asBEHAVE_TEMPLATE_CALLBACK ) - { - // TODO: How can we let the compiler register this? Maybe through a plug-in system? Or maybe by implementing the callback as a script itself - engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_WARNING, "Cannot register template callback without the actual implementation"); - } - else - { - r = engine->RegisterObjectBehaviour(name.c_str(), behave, decl.c_str(), asFUNCTION(0), asCALL_GENERIC); - if( r < 0 ) - { - engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register behaviour"); - return -1; - } - } - } - else if( token == "objmthd" ) - { - string name, decl; - in::GetToken(engine, name, config, pos); - name = name.substr(1, name.length() - 2); - in::GetToken(engine, decl, config, pos); - decl = decl.substr(1, decl.length() - 2); - in::ReplaceSlashQuote(decl); - - r = engine->RegisterObjectMethod(name.c_str(), decl.c_str(), asFUNCTION(0), asCALL_GENERIC); - if( r < 0 ) - { - engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register object method"); - return -1; - } - } - else if( token == "objprop" ) - { - string name, decl, compositeOffset, isCompositeIndirect; - in::GetToken(engine, name, config, pos); - name = name.substr(1, name.length() - 2); - in::GetToken(engine, decl, config, pos); - decl = decl.substr(1, decl.length() - 2); - in::GetToken(engine, compositeOffset, config, pos); - in::GetToken(engine, isCompositeIndirect, config, pos); - - asITypeInfo *type = engine->GetTypeInfoById(engine->GetTypeIdByDecl(name.c_str())); - if( type == 0 ) - { - engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Type doesn't exist for property registration"); - return -1; - } - - // All properties must have different offsets in order to make them - // distinct, so we simply register them with an incremental offset - r = engine->RegisterObjectProperty(name.c_str(), decl.c_str(), type->GetPropertyCount(), compositeOffset != "0" ? type->GetPropertyCount() : 0, isCompositeIndirect != "0"); - if( r < 0 ) - { - engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register object property"); - return -1; - } - } - else if( token == "intf" ) - { - string name, size, flags; - in::GetToken(engine, name, config, pos); - - r = engine->RegisterInterface(name.c_str()); - if( r < 0 ) - { - engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register interface"); - return -1; - } - } - else if( token == "intfmthd" ) - { - string name, decl; - in::GetToken(engine, name, config, pos); - in::GetToken(engine, decl, config, pos); - decl = decl.substr(1, decl.length() - 2); - in::ReplaceSlashQuote(decl); - - r = engine->RegisterInterfaceMethod(name.c_str(), decl.c_str()); - if( r < 0 ) - { - engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register interface method"); - return -1; - } - } - else if( token == "func" ) - { - string decl; - in::GetToken(engine, decl, config, pos); - decl = decl.substr(1, decl.length() - 2); - in::ReplaceSlashQuote(decl); - - r = engine->RegisterGlobalFunction(decl.c_str(), asFUNCTION(0), asCALL_GENERIC); - if( r < 0 ) - { - engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register global function"); - return -1; - } - } - else if( token == "prop" ) - { - string decl; - in::GetToken(engine, decl, config, pos); - decl = decl.substr(1, decl.length() - 2); - - // All properties must have different offsets in order to make them - // distinct, so we simply register them with an incremental offset. - // The pointer must also be non-null so we add 1 to have a value. - r = engine->RegisterGlobalProperty(decl.c_str(), reinterpret_cast(asPWORD(engine->GetGlobalPropertyCount()+1))); - if( r < 0 ) - { - engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register global property"); - return -1; - } - } - else if( token == "strfactory" ) - { - string type; - in::GetToken(engine, type, config, pos); - type = type.substr(1, type.length() - 2); - - if (stringFactory == 0) - { - engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_WARNING, "Cannot register string factory without the actual implementation"); - return -1; - } - else - { - r = engine->RegisterStringFactory(type.c_str(), stringFactory); - if (r < 0) - { - engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register string factory"); - return -1; - } - } - } - else if( token == "defarray" ) - { - string type; - in::GetToken(engine, type, config, pos); - type = type.substr(1, type.length() - 2); - - r = engine->RegisterDefaultArrayType(type.c_str()); - if( r < 0 ) - { - engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register the default array type"); - return -1; - } - } - else if( token == "enum" ) - { - string type; - in::GetToken(engine, type, config, pos); - - r = engine->RegisterEnum(type.c_str()); - if( r < 0 ) - { - engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register enum type"); - return -1; - } - } - else if( token == "enumval" ) - { - string type, name, value; - in::GetToken(engine, type, config, pos); - in::GetToken(engine, name, config, pos); - in::GetToken(engine, value, config, pos); - - r = engine->RegisterEnumValue(type.c_str(), name.c_str(), atol(value.c_str())); - if( r < 0 ) - { - engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register enum value"); - return -1; - } - } - else if( token == "typedef" ) - { - string type, decl; - in::GetToken(engine, type, config, pos); - in::GetToken(engine, decl, config, pos); - decl = decl.substr(1, decl.length() - 2); - - r = engine->RegisterTypedef(type.c_str(), decl.c_str()); - if( r < 0 ) - { - engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register typedef"); - return -1; - } - } - else if( token == "funcdef" ) - { - string decl; - in::GetToken(engine, decl, config, pos); - decl = decl.substr(1, decl.length() - 2); - - r = engine->RegisterFuncdef(decl.c_str()); - if( r < 0 ) - { - engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register funcdef"); - return -1; - } - } - } - - return 0; -} - -string GetExceptionInfo(asIScriptContext *ctx, bool showStack) -{ - if( ctx->GetState() != asEXECUTION_EXCEPTION ) return ""; - - stringstream text; - - const asIScriptFunction *function = ctx->GetExceptionFunction(); - text << "in function: " << function->GetDeclaration() << "\n"; - if(function->GetScriptSectionName()) - text << "file: " << (function->GetScriptSectionName() ? function->GetScriptSectionName() : "") << "\n"; - if(ctx->GetExceptionLineNumber()) - text << "line: " << ctx->GetExceptionLineNumber() << "\n"; - text << "description: " << ctx->GetExceptionString() << "\n"; - - if( showStack && ctx->GetCallstackSize()>1) - { - text << "--- call stack ---\n"; - for( asUINT n = 1; n < ctx->GetCallstackSize(); n++ ) - { - function = ctx->GetFunction(n); - if( function ) - { - if( function->GetFuncType() == asFUNC_SCRIPT ) - { - text << (function->GetScriptSectionName() ? function->GetScriptSectionName() : "") << " (" << ctx->GetLineNumber(n) << "): " << function->GetDeclaration() << "\n"; - } - else - { - // The context is being reused by the application for a nested call - text << "{...application...}: " << function->GetDeclaration() << "\n"; - } - } - else - { - // The context is being reused by the script engine for a nested call - text << "{...script engine...}\n"; - } - } - } - - return text.str(); -} - -void ScriptThrow(const string &msg) -{ - asIScriptContext *ctx = asGetActiveContext(); - if (ctx) - ctx->SetException(msg.c_str()); -} - -string ScriptGetExceptionInfo() -{ - asIScriptContext *ctx = asGetActiveContext(); - if (!ctx) - return ""; - - const char *msg = ctx->GetExceptionString(); - if (msg == 0) - return ""; - - return string(msg); -} - -int ScriptGetExceptionLine() -{ - asIScriptContext *ctx = asGetActiveContext(); - if (!ctx) - return 0; - return ctx->GetExceptionLineNumber(); -} - -std::string ScriptGetExceptionFunctionDecl() -{ - asIScriptContext *ctx = asGetActiveContext(); - if (!ctx) - return ""; - asIScriptFunction* func=ctx->GetExceptionFunction(); - if(!func) return ""; - return std::string(func->GetDeclaration()); -} - -std::string ScriptGetExceptionModule() -{ - asIScriptContext *ctx = asGetActiveContext(); - if (!ctx) - return ""; - asIScriptFunction* func=ctx->GetExceptionFunction(); - if(!func) return ""; - return std::string(func->GetScriptSectionName()); -} - -void RegisterExceptionRoutines(asIScriptEngine *engine) -{ - int r; - - // The string type must be available - assert(engine->GetTypeInfoByDecl("string")); - - if (strstr(asGetLibraryOptions(), "AS_MAX_PORTABILITY") == 0) - { - r = engine->RegisterGlobalFunction("void throw(const string &in)", asFUNCTION(ScriptThrow), asCALL_CDECL); assert(r >= 0); - r = engine->RegisterGlobalFunction("string get_exception_info()", asFUNCTION(ScriptGetExceptionInfo), asCALL_CDECL); assert(r >= 0); - r = engine->RegisterGlobalFunction("int get_exception_line()", asFUNCTION(ScriptGetExceptionLine), asCALL_CDECL); assert(r >= 0); - r = engine->RegisterGlobalFunction("string get_exception_function()", asFUNCTION(ScriptGetExceptionFunctionDecl), asCALL_CDECL); assert(r >= 0); - r = engine->RegisterGlobalFunction("string get_exception_file()", asFUNCTION(ScriptGetExceptionModule), asCALL_CDECL); assert(r >= 0); - } - else - { - r = engine->RegisterGlobalFunction("void throw(const string &in)", WRAP_FN(ScriptThrow), asCALL_GENERIC); assert(r >= 0); - r = engine->RegisterGlobalFunction("string getExceptionInfo()", WRAP_FN(ScriptGetExceptionInfo), asCALL_GENERIC); assert(r >= 0); - } -} - -END_AS_NAMESPACE +#include +#include "scripthelper.h" +#include +#include +#include +#include +#include +#include "aswrappedcall.h" + +using namespace std; + +BEGIN_AS_NAMESPACE + +int CompareRelation(asIScriptEngine *engine, void *lobj, void *robj, int typeId, int &result) +{ + // TODO: If a lot of script objects are going to be compared, e.g. when sorting an array, + // then the method id and context should be cached between calls. + + int retval = -1; + asIScriptFunction *func = 0; + + asITypeInfo *ti = engine->GetTypeInfoById(typeId); + if( ti ) + { + // Check if the object type has a compatible opCmp method + for( asUINT n = 0; n < ti->GetMethodCount(); n++ ) + { + asIScriptFunction *f = ti->GetMethodByIndex(n); + asDWORD flags; + if( strcmp(f->GetName(), "opCmp") == 0 && + f->GetReturnTypeId(&flags) == asTYPEID_INT32 && + flags == asTM_NONE && + f->GetParamCount() == 1 ) + { + int paramTypeId; + f->GetParam(0, ¶mTypeId, &flags); + + // The parameter must be an input reference of the same type + // If the reference is a inout reference, then it must also be read-only + if( !(flags & asTM_INREF) || typeId != paramTypeId || ((flags & asTM_OUTREF) && !(flags & asTM_CONST)) ) + break; + + // Found the method + func = f; + break; + } + } + } + + if( func ) + { + // Call the method + asIScriptContext *ctx = engine->CreateContext(); + ctx->Prepare(func); + ctx->SetObject(lobj); + ctx->SetArgAddress(0, robj); + int r = ctx->Execute(); + if( r == asEXECUTION_FINISHED ) + { + result = (int)ctx->GetReturnDWord(); + + // The comparison was successful + retval = 0; + } + ctx->Release(); + } + + return retval; +} + +int CompareEquality(asIScriptEngine *engine, void *lobj, void *robj, int typeId, bool &result) +{ + // TODO: If a lot of script objects are going to be compared, e.g. when searching for an + // entry in a set, then the method and context should be cached between calls. + + int retval = -1; + asIScriptFunction *func = 0; + + asITypeInfo *ti = engine->GetTypeInfoById(typeId); + if( ti ) + { + // Check if the object type has a compatible opEquals method + for( asUINT n = 0; n < ti->GetMethodCount(); n++ ) + { + asIScriptFunction *f = ti->GetMethodByIndex(n); + asDWORD flags; + if( strcmp(f->GetName(), "opEquals") == 0 && + f->GetReturnTypeId(&flags) == asTYPEID_BOOL && + flags == asTM_NONE && + f->GetParamCount() == 1 ) + { + int paramTypeId; + f->GetParam(0, ¶mTypeId, &flags); + + // The parameter must be an input reference of the same type + // If the reference is a inout reference, then it must also be read-only + if( !(flags & asTM_INREF) || typeId != paramTypeId || ((flags & asTM_OUTREF) && !(flags & asTM_CONST)) ) + break; + + // Found the method + func = f; + break; + } + } + } + + if( func ) + { + // Call the method + asIScriptContext *ctx = engine->CreateContext(); + ctx->Prepare(func); + ctx->SetObject(lobj); + ctx->SetArgAddress(0, robj); + int r = ctx->Execute(); + if( r == asEXECUTION_FINISHED ) + { + result = ctx->GetReturnByte() ? true : false; + + // The comparison was successful + retval = 0; + } + ctx->Release(); + } + else + { + // If the opEquals method doesn't exist, then we try with opCmp instead + int relation; + retval = CompareRelation(engine, lobj, robj, typeId, relation); + if( retval >= 0 ) + result = relation == 0 ? true : false; + } + + return retval; +} + +int ExecuteString(asIScriptEngine *engine, const char *code, asIScriptModule *mod, asIScriptContext *ctx) +{ + return ExecuteString(engine, code, 0, asTYPEID_VOID, mod, ctx); +} + +int ExecuteString(asIScriptEngine *engine, const char *code, void *ref, int refTypeId, asIScriptModule *mod, asIScriptContext *ctx) +{ + // Wrap the code in a function so that it can be compiled and executed + string funcCode = " ExecuteString() {\n"; + funcCode += code; + funcCode += "\n;}"; + + // Determine the return type based on the type of the ref arg + funcCode = engine->GetTypeDeclaration(refTypeId, true) + funcCode; + + // GetModule will free unused types, so to be on the safe side we'll hold on to a reference to the type + asITypeInfo *type = 0; + if( refTypeId & asTYPEID_MASK_OBJECT ) + { + type = engine->GetTypeInfoById(refTypeId); + if( type ) + type->AddRef(); + } + + // If no module was provided, get a dummy from the engine + asIScriptModule *execMod = mod ? mod : engine->GetModule("ExecuteString", asGM_ALWAYS_CREATE); + + // Now it's ok to release the type + if( type ) + type->Release(); + + // Compile the function that can be executed + asIScriptFunction *func = 0; + int r = execMod->CompileFunction("ExecuteString", funcCode.c_str(), -1, 0, &func); + if( r < 0 ) + return r; + + // If no context was provided, request a new one from the engine + asIScriptContext *execCtx = ctx ? ctx : engine->RequestContext(); + r = execCtx->Prepare(func); + if (r >= 0) + { + // Execute the function + r = execCtx->Execute(); + + // Unless the provided type was void retrieve it's value + if (ref != 0 && refTypeId != asTYPEID_VOID) + { + if (refTypeId & asTYPEID_OBJHANDLE) + { + // Expect the pointer to be null to start with + assert(*reinterpret_cast(ref) == 0); + *reinterpret_cast(ref) = *reinterpret_cast(execCtx->GetAddressOfReturnValue()); + engine->AddRefScriptObject(*reinterpret_cast(ref), engine->GetTypeInfoById(refTypeId)); + } + else if (refTypeId & asTYPEID_MASK_OBJECT) + { + // Use the registered assignment operator to do a value assign. + // This assumes that the ref is pointing to a valid object instance. + engine->AssignScriptObject(ref, execCtx->GetAddressOfReturnValue(), engine->GetTypeInfoById(refTypeId)); + } + else + { + // Copy the primitive value + memcpy(ref, execCtx->GetAddressOfReturnValue(), engine->GetSizeOfPrimitiveType(refTypeId)); + } + } + } + + // Clean up + func->Release(); + if( !ctx ) engine->ReturnContext(execCtx); + + return r; +} + +int WriteConfigToFile(asIScriptEngine *engine, const char *filename) +{ + ofstream strm; + strm.open(filename); + + return WriteConfigToStream(engine, strm); +} + +int WriteConfigToStream(asIScriptEngine *engine, ostream &strm) +{ + // A helper function for escaping quotes in default arguments + struct Escape + { + static string Quotes(const char *decl) + { + string str = decl; + size_t pos = 0; + for(;;) + { + // Find " characters + pos = str.find("\"",pos); + if( pos == string::npos ) + break; + + // Add a \ to escape them + str.insert(pos, "\\"); + pos += 2; + } + + return str; + } + }; + + int c, n; + + asDWORD currAccessMask = 0; + string currNamespace = ""; + engine->SetDefaultNamespace(""); + + // Export the engine version, just for info + strm << "// AngelScript " << asGetLibraryVersion() << "\n"; + strm << "// Lib options " << asGetLibraryOptions() << "\n"; + + // Export the relevant engine properties + strm << "// Engine properties\n"; + for( n = 0; n < asEP_LAST_PROPERTY; n++ ) + strm << "ep " << n << " " << engine->GetEngineProperty(asEEngineProp(n)) << "\n"; + + // Make sure the default array type is expanded to the template form + bool expandDefArrayToTempl = engine->GetEngineProperty(asEP_EXPAND_DEF_ARRAY_TO_TMPL) ? true : false; + engine->SetEngineProperty(asEP_EXPAND_DEF_ARRAY_TO_TMPL, true); + + // Write enum types and their values + strm << "\n// Enums\n"; + c = engine->GetEnumCount(); + for( n = 0; n < c; n++ ) + { + asITypeInfo *ti = engine->GetEnumByIndex(n); + asDWORD accessMask = ti->GetAccessMask(); + if( accessMask != currAccessMask ) + { + strm << "access " << hex << (unsigned int)(accessMask) << dec << "\n"; + currAccessMask = accessMask; + } + const char *nameSpace = ti->GetNamespace(); + if( nameSpace != currNamespace ) + { + strm << "namespace \"" << nameSpace << "\"\n"; + currNamespace = nameSpace; + engine->SetDefaultNamespace(currNamespace.c_str()); + } + const char *enumName = ti->GetName(); + strm << "enum " << enumName << "\n"; + for( asUINT m = 0; m < ti->GetEnumValueCount(); m++ ) + { + const char *valName; + int val; + valName = ti->GetEnumValueByIndex(m, &val); + strm << "enumval " << enumName << " " << valName << " " << val << "\n"; + } + } + + // Enumerate all types + strm << "\n// Types\n"; + + // Keep a list of the template types, as the methods for these need to be exported first + set templateTypes; + + c = engine->GetObjectTypeCount(); + for( n = 0; n < c; n++ ) + { + asITypeInfo *type = engine->GetObjectTypeByIndex(n); + asDWORD accessMask = type->GetAccessMask(); + if( accessMask != currAccessMask ) + { + strm << "access " << hex << (unsigned int)(accessMask) << dec << "\n"; + currAccessMask = accessMask; + } + const char *nameSpace = type->GetNamespace(); + if( nameSpace != currNamespace ) + { + strm << "namespace \"" << nameSpace << "\"\n"; + currNamespace = nameSpace; + engine->SetDefaultNamespace(currNamespace.c_str()); + } + if( type->GetFlags() & asOBJ_SCRIPT_OBJECT ) + { + // This should only be interfaces + assert( type->GetSize() == 0 ); + + strm << "intf " << type->GetName() << "\n"; + } + else + { + // Only the type flags are necessary. The application flags are application + // specific and doesn't matter to the offline compiler. The object size is also + // unnecessary for the offline compiler + strm << "objtype \"" << engine->GetTypeDeclaration(type->GetTypeId()) << "\" " << (unsigned int)(type->GetFlags() & asOBJ_MASK_VALID_FLAGS) << "\n"; + + // Store the template types (but not template instances) + if( (type->GetFlags() & asOBJ_TEMPLATE) && type->GetSubType() && (type->GetSubType()->GetFlags() & asOBJ_TEMPLATE_SUBTYPE) ) + templateTypes.insert(type); + } + } + + c = engine->GetTypedefCount(); + for( n = 0; n < c; n++ ) + { + asITypeInfo *ti = engine->GetTypedefByIndex(n); + const char *nameSpace = ti->GetNamespace(); + if( nameSpace != currNamespace ) + { + strm << "namespace \"" << nameSpace << "\"\n"; + currNamespace = nameSpace; + engine->SetDefaultNamespace(currNamespace.c_str()); + } + asDWORD accessMask = ti->GetAccessMask(); + if( accessMask != currAccessMask ) + { + strm << "access " << hex << (unsigned int)(accessMask) << dec << "\n"; + currAccessMask = accessMask; + } + strm << "typedef " << ti->GetName() << " \"" << engine->GetTypeDeclaration(ti->GetTypedefTypeId()) << "\"\n"; + } + + c = engine->GetFuncdefCount(); + for( n = 0; n < c; n++ ) + { + asITypeInfo *funcDef = engine->GetFuncdefByIndex(n); + asDWORD accessMask = funcDef->GetAccessMask(); + const char *nameSpace = funcDef->GetNamespace(); + // Child funcdefs do not have any namespace, as they belong to the parent object + if( nameSpace && nameSpace != currNamespace ) + { + strm << "namespace \"" << nameSpace << "\"\n"; + currNamespace = nameSpace; + engine->SetDefaultNamespace(currNamespace.c_str()); + } + if( accessMask != currAccessMask ) + { + strm << "access " << hex << (unsigned int)(accessMask) << dec << "\n"; + currAccessMask = accessMask; + } + strm << "funcdef \"" << funcDef->GetFuncdefSignature()->GetDeclaration(true, false, true) << "\"\n"; + } + + // A helper for writing object type members + struct TypeWriter + { + static void Write(asIScriptEngine *engine, ostream &strm, asITypeInfo *type, string &currNamespace, asDWORD &currAccessMask) + { + const char *nameSpace = type->GetNamespace(); + if( nameSpace != currNamespace ) + { + strm << "namespace \"" << nameSpace << "\"\n"; + currNamespace = nameSpace; + engine->SetDefaultNamespace(currNamespace.c_str()); + } + string typeDecl = engine->GetTypeDeclaration(type->GetTypeId()); + if( type->GetFlags() & asOBJ_SCRIPT_OBJECT ) + { + for( asUINT m = 0; m < type->GetMethodCount(); m++ ) + { + asIScriptFunction *func = type->GetMethodByIndex(m); + asDWORD accessMask = func->GetAccessMask(); + if( accessMask != currAccessMask ) + { + strm << "access " << hex << (unsigned int)(accessMask) << dec << "\n"; + currAccessMask = accessMask; + } + strm << "intfmthd " << typeDecl.c_str() << " \"" << Escape::Quotes(func->GetDeclaration(false, false, true)).c_str() << (func->IsProperty() ? " property" : "") << "\"\n"; + } + } + else + { + asUINT m; + for( m = 0; m < type->GetFactoryCount(); m++ ) + { + asIScriptFunction *func = type->GetFactoryByIndex(m); + asDWORD accessMask = func->GetAccessMask(); + if( accessMask != currAccessMask ) + { + strm << "access " << hex << (unsigned int)(accessMask) << dec << "\n"; + currAccessMask = accessMask; + } + strm << "objbeh \"" << typeDecl.c_str() << "\" " << asBEHAVE_FACTORY << " \"" << Escape::Quotes(func->GetDeclaration(false, false, true)).c_str() << "\"\n"; + } + for( m = 0; m < type->GetBehaviourCount(); m++ ) + { + asEBehaviours beh; + asIScriptFunction *func = type->GetBehaviourByIndex(m, &beh); + + if( beh == asBEHAVE_CONSTRUCT ) + // Prefix 'void' + strm << "objbeh \"" << typeDecl.c_str() << "\" " << beh << " \"void " << Escape::Quotes(func->GetDeclaration(false, false, true)).c_str() << "\"\n"; + else if( beh == asBEHAVE_DESTRUCT ) + // Prefix 'void' and remove ~ + strm << "objbeh \"" << typeDecl.c_str() << "\" " << beh << " \"void " << Escape::Quotes(func->GetDeclaration(false, false, true)).c_str()+1 << "\"\n"; + else + strm << "objbeh \"" << typeDecl.c_str() << "\" " << beh << " \"" << Escape::Quotes(func->GetDeclaration(false, false, true)).c_str() << "\"\n"; + } + for( m = 0; m < type->GetMethodCount(); m++ ) + { + asIScriptFunction *func = type->GetMethodByIndex(m); + asDWORD accessMask = func->GetAccessMask(); + if( accessMask != currAccessMask ) + { + strm << "access " << hex << (unsigned int)(accessMask) << dec << "\n"; + currAccessMask = accessMask; + } + strm << "objmthd \"" << typeDecl.c_str() << "\" \"" << Escape::Quotes(func->GetDeclaration(false, false, true)).c_str() << (func->IsProperty() ? " property" : "") << "\"\n"; + } + for( m = 0; m < type->GetPropertyCount(); m++ ) + { + asDWORD accessMask; + type->GetProperty(m, 0, 0, 0, 0, 0, 0, &accessMask); + if( accessMask != currAccessMask ) + { + strm << "access " << hex << (unsigned int)(accessMask) << dec << "\n"; + currAccessMask = accessMask; + } + strm << "objprop \"" << typeDecl.c_str() << "\" \"" << type->GetPropertyDeclaration(m) << "\""; + + // Save information about composite properties + int compositeOffset; + bool isCompositeIndirect; + type->GetProperty(m, 0, 0, 0, 0, 0, 0, 0, &compositeOffset, &isCompositeIndirect); + strm << " " << compositeOffset << " " << (isCompositeIndirect ? "1" : "0") << "\n"; + } + } + } + }; + + // Write the members of the template types, so they can be fully registered before any other type uses them + // TODO: Order the template types based on dependency to avoid failure if one type uses instances of another + strm << "\n// Template type members\n"; + for( set::iterator it = templateTypes.begin(); it != templateTypes.end(); ++it ) + { + asITypeInfo *type = *it; + TypeWriter::Write(engine, strm, type, currNamespace, currAccessMask); + } + + // Write the object types members + strm << "\n// Type members\n"; + + c = engine->GetObjectTypeCount(); + for( n = 0; n < c; n++ ) + { + asITypeInfo *type = engine->GetObjectTypeByIndex(n); + if( templateTypes.find(type) == templateTypes.end() ) + TypeWriter::Write(engine, strm, type, currNamespace, currAccessMask); + } + + // Write functions + strm << "\n// Functions\n"; + + c = engine->GetGlobalFunctionCount(); + for( n = 0; n < c; n++ ) + { + asIScriptFunction *func = engine->GetGlobalFunctionByIndex(n); + const char *nameSpace = func->GetNamespace(); + if( nameSpace != currNamespace ) + { + strm << "namespace \"" << nameSpace << "\"\n"; + currNamespace = nameSpace; + engine->SetDefaultNamespace(currNamespace.c_str()); + } + asDWORD accessMask = func->GetAccessMask(); + if( accessMask != currAccessMask ) + { + strm << "access " << hex << (unsigned int)(accessMask) << dec << "\n"; + currAccessMask = accessMask; + } + strm << "func \"" << Escape::Quotes(func->GetDeclaration(true, false, true)).c_str() << (func->IsProperty() ? " property" : "") << "\"\n"; + } + + // Write global properties + strm << "\n// Properties\n"; + + c = engine->GetGlobalPropertyCount(); + for( n = 0; n < c; n++ ) + { + const char *name; + int typeId; + bool isConst; + asDWORD accessMask; + const char *nameSpace; + engine->GetGlobalPropertyByIndex(n, &name, &nameSpace, &typeId, &isConst, 0, 0, &accessMask); + if( accessMask != currAccessMask ) + { + strm << "access " << hex << (unsigned int)(accessMask) << dec << "\n"; + currAccessMask = accessMask; + } + if( nameSpace != currNamespace ) + { + strm << "namespace \"" << nameSpace << "\"\n"; + currNamespace = nameSpace; + engine->SetDefaultNamespace(currNamespace.c_str()); + } + strm << "prop \"" << (isConst ? "const " : "") << engine->GetTypeDeclaration(typeId) << " " << name << "\"\n"; + } + + // Write string factory + strm << "\n// String factory\n"; + + // Reset the namespace for the string factory and default array type + if ("" != currNamespace) + { + strm << "namespace \"\"\n"; + currNamespace = ""; + engine->SetDefaultNamespace(""); + } + + asDWORD flags = 0; + int typeId = engine->GetStringFactory(&flags); + if( typeId > 0 ) + strm << "strfactory \"" << ((flags & asTM_CONST) ? "const " : "") << engine->GetTypeDeclaration(typeId) << ((flags & asTM_INOUTREF) ? "&" : "") << "\"\n"; + + // Write default array type + strm << "\n// Default array type\n"; + typeId = engine->GetDefaultArrayTypeId(); + if( typeId > 0 ) + strm << "defarray \"" << engine->GetTypeDeclaration(typeId) << "\"\n"; + + // Restore original settings + engine->SetEngineProperty(asEP_EXPAND_DEF_ARRAY_TO_TMPL, expandDefArrayToTempl); + + return 0; +} + +int ConfigEngineFromStream(asIScriptEngine *engine, istream &strm, const char *configFile, asIStringFactory *stringFactory) +{ + int r; + + // Some helper functions for parsing the configuration + struct in + { + static asETokenClass GetToken(asIScriptEngine *engine, string &token, const string &text, asUINT &pos) + { + asUINT len = 0; + asETokenClass t = engine->ParseToken(&text[pos], text.length() - pos, &len); + while( (t == asTC_WHITESPACE || t == asTC_COMMENT) && pos < text.length() ) + { + pos += len; + t = engine->ParseToken(&text[pos], text.length() - pos, &len); + } + + token.assign(&text[pos], len); + + pos += len; + + return t; + } + + static void ReplaceSlashQuote(string &str) + { + size_t pos = 0; + for(;;) + { + // Search for \" in the string + pos = str.find("\\\"", pos); + if( pos == string::npos ) + break; + + // Remove the \ character + str.erase(pos, 1); + } + } + + static asUINT GetLineNumber(const string &text, asUINT pos) + { + asUINT count = 1; + for( asUINT n = 0; n < pos; n++ ) + if( text[n] == '\n' ) + count++; + + return count; + } + }; + + // Since we are only going to compile the script and never actually execute it, + // we turn off the initialization of global variables, so that the compiler can + // just register dummy types and functions for the application interface. + r = engine->SetEngineProperty(asEP_INIT_GLOBAL_VARS_AFTER_BUILD, false); assert( r >= 0 ); + + // Read the entire file + char buffer[1000]; + string config; + do { + strm.getline(buffer, 1000); + config += buffer; + config += "\n"; + } while( !strm.eof() && strm.good() ); + + // Process the configuration file and register each entity + asUINT pos = 0; + while( pos < config.length() ) + { + string token; + // TODO: The position where the initial token is found should be stored for error messages + in::GetToken(engine, token, config, pos); + if( token == "ep" ) + { + string tmp; + in::GetToken(engine, tmp, config, pos); + + asEEngineProp ep = asEEngineProp(atol(tmp.c_str())); + + // Only set properties that affect the compiler + if( ep != asEP_COPY_SCRIPT_SECTIONS && + ep != asEP_MAX_STACK_SIZE && + ep != asEP_INIT_GLOBAL_VARS_AFTER_BUILD && + ep != asEP_EXPAND_DEF_ARRAY_TO_TMPL && + ep != asEP_AUTO_GARBAGE_COLLECT ) + { + // Get the value for the property + in::GetToken(engine, tmp, config, pos); + stringstream s(tmp); + asPWORD value; + + s >> value; + + engine->SetEngineProperty(ep, value); + } + } + else if( token == "namespace" ) + { + string ns; + in::GetToken(engine, ns, config, pos); + ns = ns.substr(1, ns.length() - 2); + + r = engine->SetDefaultNamespace(ns.c_str()); + if( r < 0 ) + { + engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to set namespace"); + return -1; + } + } + else if( token == "access" ) + { + string maskStr; + in::GetToken(engine, maskStr, config, pos); + asDWORD mask = strtoul(maskStr.c_str(), 0, 16); + engine->SetDefaultAccessMask(mask); + } + else if( token == "objtype" ) + { + string name, flags; + in::GetToken(engine, name, config, pos); + name = name.substr(1, name.length() - 2); + in::GetToken(engine, flags, config, pos); + + // The size of the value type doesn't matter, because the + // engine must adjust it anyway for different platforms + r = engine->RegisterObjectType(name.c_str(), (atol(flags.c_str()) & asOBJ_VALUE) ? 1 : 0, atol(flags.c_str())); + if( r < 0 ) + { + engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register object type"); + return -1; + } + } + else if( token == "objbeh" ) + { + string name, behaviour, decl; + in::GetToken(engine, name, config, pos); + name = name.substr(1, name.length() - 2); + in::GetToken(engine, behaviour, config, pos); + in::GetToken(engine, decl, config, pos); + decl = decl.substr(1, decl.length() - 2); + in::ReplaceSlashQuote(decl); + + // Remove the $ that the engine prefixes the behaviours with + size_t n = decl.find("$"); + if( n != string::npos ) + decl[n] = ' '; + + asEBehaviours behave = static_cast(atol(behaviour.c_str())); + if( behave == asBEHAVE_TEMPLATE_CALLBACK ) + { + // TODO: How can we let the compiler register this? Maybe through a plug-in system? Or maybe by implementing the callback as a script itself + engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_WARNING, "Cannot register template callback without the actual implementation"); + } + else + { + r = engine->RegisterObjectBehaviour(name.c_str(), behave, decl.c_str(), asFUNCTION(0), asCALL_GENERIC); + if( r < 0 ) + { + engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register behaviour"); + return -1; + } + } + } + else if( token == "objmthd" ) + { + string name, decl; + in::GetToken(engine, name, config, pos); + name = name.substr(1, name.length() - 2); + in::GetToken(engine, decl, config, pos); + decl = decl.substr(1, decl.length() - 2); + in::ReplaceSlashQuote(decl); + + r = engine->RegisterObjectMethod(name.c_str(), decl.c_str(), asFUNCTION(0), asCALL_GENERIC); + if( r < 0 ) + { + engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register object method"); + return -1; + } + } + else if( token == "objprop" ) + { + string name, decl, compositeOffset, isCompositeIndirect; + in::GetToken(engine, name, config, pos); + name = name.substr(1, name.length() - 2); + in::GetToken(engine, decl, config, pos); + decl = decl.substr(1, decl.length() - 2); + in::GetToken(engine, compositeOffset, config, pos); + in::GetToken(engine, isCompositeIndirect, config, pos); + + asITypeInfo *type = engine->GetTypeInfoById(engine->GetTypeIdByDecl(name.c_str())); + if( type == 0 ) + { + engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Type doesn't exist for property registration"); + return -1; + } + + // All properties must have different offsets in order to make them + // distinct, so we simply register them with an incremental offset + r = engine->RegisterObjectProperty(name.c_str(), decl.c_str(), type->GetPropertyCount(), compositeOffset != "0" ? type->GetPropertyCount() : 0, isCompositeIndirect != "0"); + if( r < 0 ) + { + engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register object property"); + return -1; + } + } + else if( token == "intf" ) + { + string name, size, flags; + in::GetToken(engine, name, config, pos); + + r = engine->RegisterInterface(name.c_str()); + if( r < 0 ) + { + engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register interface"); + return -1; + } + } + else if( token == "intfmthd" ) + { + string name, decl; + in::GetToken(engine, name, config, pos); + in::GetToken(engine, decl, config, pos); + decl = decl.substr(1, decl.length() - 2); + in::ReplaceSlashQuote(decl); + + r = engine->RegisterInterfaceMethod(name.c_str(), decl.c_str()); + if( r < 0 ) + { + engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register interface method"); + return -1; + } + } + else if( token == "func" ) + { + string decl; + in::GetToken(engine, decl, config, pos); + decl = decl.substr(1, decl.length() - 2); + in::ReplaceSlashQuote(decl); + + r = engine->RegisterGlobalFunction(decl.c_str(), asFUNCTION(0), asCALL_GENERIC); + if( r < 0 ) + { + engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register global function"); + return -1; + } + } + else if( token == "prop" ) + { + string decl; + in::GetToken(engine, decl, config, pos); + decl = decl.substr(1, decl.length() - 2); + + // All properties must have different offsets in order to make them + // distinct, so we simply register them with an incremental offset. + // The pointer must also be non-null so we add 1 to have a value. + r = engine->RegisterGlobalProperty(decl.c_str(), reinterpret_cast(asPWORD(engine->GetGlobalPropertyCount()+1))); + if( r < 0 ) + { + engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register global property"); + return -1; + } + } + else if( token == "strfactory" ) + { + string type; + in::GetToken(engine, type, config, pos); + type = type.substr(1, type.length() - 2); + + if (stringFactory == 0) + { + engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_WARNING, "Cannot register string factory without the actual implementation"); + return -1; + } + else + { + r = engine->RegisterStringFactory(type.c_str(), stringFactory); + if (r < 0) + { + engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register string factory"); + return -1; + } + } + } + else if( token == "defarray" ) + { + string type; + in::GetToken(engine, type, config, pos); + type = type.substr(1, type.length() - 2); + + r = engine->RegisterDefaultArrayType(type.c_str()); + if( r < 0 ) + { + engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register the default array type"); + return -1; + } + } + else if( token == "enum" ) + { + string type; + in::GetToken(engine, type, config, pos); + + r = engine->RegisterEnum(type.c_str()); + if( r < 0 ) + { + engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register enum type"); + return -1; + } + } + else if( token == "enumval" ) + { + string type, name, value; + in::GetToken(engine, type, config, pos); + in::GetToken(engine, name, config, pos); + in::GetToken(engine, value, config, pos); + + r = engine->RegisterEnumValue(type.c_str(), name.c_str(), atol(value.c_str())); + if( r < 0 ) + { + engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register enum value"); + return -1; + } + } + else if( token == "typedef" ) + { + string type, decl; + in::GetToken(engine, type, config, pos); + in::GetToken(engine, decl, config, pos); + decl = decl.substr(1, decl.length() - 2); + + r = engine->RegisterTypedef(type.c_str(), decl.c_str()); + if( r < 0 ) + { + engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register typedef"); + return -1; + } + } + else if( token == "funcdef" ) + { + string decl; + in::GetToken(engine, decl, config, pos); + decl = decl.substr(1, decl.length() - 2); + + r = engine->RegisterFuncdef(decl.c_str()); + if( r < 0 ) + { + engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register funcdef"); + return -1; + } + } + } + + return 0; +} + +string GetExceptionInfo(asIScriptContext *ctx, bool showStack) +{ + if( ctx->GetState() != asEXECUTION_EXCEPTION ) return ""; + + stringstream text; + + const asIScriptFunction *function = ctx->GetExceptionFunction(); + text << "in function: " << function->GetDeclaration() << "\n"; + if(function->GetScriptSectionName()) + text << "file: " << (function->GetScriptSectionName() ? function->GetScriptSectionName() : "") << "\n"; + if(ctx->GetExceptionLineNumber()) + text << "line: " << ctx->GetExceptionLineNumber() << "\n"; + text << "description: " << ctx->GetExceptionString() << "\n"; + + if( showStack && ctx->GetCallstackSize()>1) + { + text << "--- call stack ---\n"; + for( asUINT n = 1; n < ctx->GetCallstackSize(); n++ ) + { + function = ctx->GetFunction(n); + if( function ) + { + if( function->GetFuncType() == asFUNC_SCRIPT ) + { + text << (function->GetScriptSectionName() ? function->GetScriptSectionName() : "") << " (" << ctx->GetLineNumber(n) << "): " << function->GetDeclaration() << "\n"; + } + else + { + // The context is being reused by the application for a nested call + text << "{...application...}: " << function->GetDeclaration() << "\n"; + } + } + else + { + // The context is being reused by the script engine for a nested call + text << "{...script engine...}\n"; + } + } + } + + return text.str(); +} + +void ScriptThrow(const string &msg) +{ + asIScriptContext *ctx = asGetActiveContext(); + if (ctx) + ctx->SetException(msg.c_str()); +} + +string ScriptGetExceptionInfo() +{ + asIScriptContext *ctx = asGetActiveContext(); + if (!ctx) + return ""; + + const char *msg = ctx->GetExceptionString(); + if (msg == 0) + return ""; + + return string(msg); +} + +int ScriptGetExceptionLine() +{ + asIScriptContext *ctx = asGetActiveContext(); + if (!ctx) + return 0; + return ctx->GetExceptionLineNumber(); +} + +std::string ScriptGetExceptionFunctionDecl() +{ + asIScriptContext *ctx = asGetActiveContext(); + if (!ctx) + return ""; + asIScriptFunction* func=ctx->GetExceptionFunction(); + if(!func) return ""; + return std::string(func->GetDeclaration()); +} + +std::string ScriptGetExceptionModule() +{ + asIScriptContext *ctx = asGetActiveContext(); + if (!ctx) + return ""; + asIScriptFunction* func=ctx->GetExceptionFunction(); + if(!func) return ""; + return std::string(func->GetScriptSectionName()); +} + +void RegisterExceptionRoutines(asIScriptEngine *engine) +{ + int r; + + // The string type must be available + assert(engine->GetTypeInfoByDecl("string")); + + if (strstr(asGetLibraryOptions(), "AS_MAX_PORTABILITY") == 0) + { + r = engine->RegisterGlobalFunction("void throw(const string &in)", asFUNCTION(ScriptThrow), asCALL_CDECL); assert(r >= 0); + r = engine->RegisterGlobalFunction("string get_exception_info()", asFUNCTION(ScriptGetExceptionInfo), asCALL_CDECL); assert(r >= 0); + r = engine->RegisterGlobalFunction("int get_exception_line()", asFUNCTION(ScriptGetExceptionLine), asCALL_CDECL); assert(r >= 0); + r = engine->RegisterGlobalFunction("string get_exception_function()", asFUNCTION(ScriptGetExceptionFunctionDecl), asCALL_CDECL); assert(r >= 0); + r = engine->RegisterGlobalFunction("string get_exception_file()", asFUNCTION(ScriptGetExceptionModule), asCALL_CDECL); assert(r >= 0); + } + else + { + r = engine->RegisterGlobalFunction("void throw(const string &in)", WRAP_FN(ScriptThrow), asCALL_GENERIC); assert(r >= 0); + r = engine->RegisterGlobalFunction("string getExceptionInfo()", WRAP_FN(ScriptGetExceptionInfo), asCALL_GENERIC); assert(r >= 0); + } +} + +END_AS_NAMESPACE diff --git a/build/build_linux.sh b/build/build_linux.sh index 6c8b1999..23e78df8 100644 --- a/build/build_linux.sh +++ b/build/build_linux.sh @@ -1,155 +1,155 @@ -#!/bin/bash - -function setup_angelscript { - echo Installing Angelscript... - git clone --depth 1 https://github.com/codecat/angelscript-mirror||true - cd "angelscript-mirror/sdk/angelscript/projects/gnuc" - make -j$(nproc) - sudo make install - cd ../../../../.. - echo Angelscript installed. -} - -function setup_bullet { - echo Installing bullet3... - sudo apt install python3-dev -y - git clone --depth 1 https://github.com/bulletphysics/bullet3||true - cd bullet3 - ./build_cmake_pybullet_double.sh - cd build_cmake - sudo cmake --install . - cd ../.. -} - -function setup_enet { - echo Installing enet... - git clone --depth 1 https://github.com/lsalzman/enet||true - cd enet - autoreconf -vfi - ./configure - make -j$(nproc) - sudo make install - cd .. - echo Enet installed. -} - -function setup_libgit2 { - echo Installing libgit2... - curl -s -O -L https://github.com/libgit2/libgit2/archive/refs/tags/v1.8.1.tar.gz - tar -xzf v1.8.1.tar.gz - cd libgit2-1.8.1 - mkdir -p build - cd build - cmake .. -DBUILD_TESTS=OFF -DUSE_ICONV=OFF -DBUILD_CLI=OFF -DCMAKE_BUILD_TYPE=Release - cmake --build . - sudo cmake --install . - cd ../.. - rm v1.8.1.tar.gz - echo libgit2 installed. -} - -function setup_libplist { - echo Installing libplist... - curl -s -O -L https://github.com/libimobiledevice/libplist/releases/download/2.6.0/libplist-2.6.0.tar.bz2 - tar -xf libplist-2.6.0.tar.bz2 - cd libplist-2.6.0 - ./configure --without-cython - make - sudo make install - cd .. - rm libplist-2.6.0.tar.bz2 - echo libplist installed. -} - -function setup_poco { - echo Installing poco... - curl -s -O https://pocoproject.org/releases/poco-1.13.3/poco-1.13.3-all.tar.gz - tar -xzf poco-1.13.3-all.tar.gz - cd poco-1.13.3-all - mkdir -p cmake_build - cd cmake_build - export CFLAGS="-fPIC -DPOCO_UTIL_NO_XMLCONFIGURATION" - export CXXFLAGS="-fPIC -DPOCO_UTIL_NO_XMLCONFIGURATION" - cmake .. -DENABLE_TESTS=OFF -DENABLE_SAMPLES=OFF -DCMAKE_BUILD_TYPE=MinSizeRel -DENABLE_PAGECOMPILER=OFF -DENABLE_PAGECOMPILER_FILE2PAGE=OFF -DENABLE_ACTIVERECORD=OFF -DENABLE_ACTIVERECORD_COMPILER=OFF -DENABLE_MONGODB=OFF -DBUILD_SHARED_LIBS=OFF - cmake --build . - sudo cmake --install . - cd ../.. - rm poco-1.13.3-all.tar.gz - echo poco installed. -} - -function setup_sdl { - echo Installing SDL... - # Install SDL this way to get many SDL deps. It is too old so we remove SDL itself and build from source, however. - sudo apt install libssl-dev libcurl4-openssl-dev libopus-dev libsdl2-dev -y - sudo apt remove libsdl2-dev -y - git clone https://github.com/libsdl-org/SDL||true - mkdir -p SDL/build - cd SDL/build - git checkout 4e09e58f62e95a66125dae9ddd3e302603819ffd - cmake -DCMAKE_BUILD_TYPE=MinSizeRel -DSDL_SHARED=OFF -DSDL_STATIC=ON -DSDL_TEST_LIBRARY=OFF .. - cmake --build . --config MinSizeRel - sudo make install - cd ../.. - echo SDL installed. -} - -function setup_nvgt { - echo Building NVGT... - - if [[ "$IS_CI" != 1 ]]; then - # Assumed to not be running on CI; NVGT should be cloned outside of deps first. - echo Not running on CI. - cd .. - - git clone --depth 1 https://github.com/samtupy/nvgt||true - cd nvgt - - else - echo Running on CI. - cd .. - fi - - echo Downloading lindev... - wget https://nvgt.gg/lindev.tar.gz - mkdir -p lindev - cd lindev - tar -xvf ../lindev.tar.gz - cd .. - rm lindev.tar.gz - if ! which scons &> /dev/null; then - export PIP_BREAK_SYSTEM_PACKAGES=1 - pip3 install --user scons - fi - scons -s no_upx=0 - echo NVGT built. -} - -function main { - sudo apt update -y - set -e - mkdir -p deps - cd deps - - # Insure required packages are installed for building. - sudo apt install build-essential gcc g++ make cmake autoconf libtool python3 python3-pip libsystemd-dev libspeechd-dev -y - - setup_angelscript - setup_bullet - setup_enet - setup_libgit2 - setup_libplist - setup_poco - setup_sdl - setup_nvgt - echo Success! - exit 0 -} - -if [[ "$1" == "ci" ]]; then - export IS_CI=1 -else - export IS_CI=0 -fi - -main +#!/bin/bash + +function setup_angelscript { + echo Installing Angelscript... + git clone --depth 1 https://github.com/codecat/angelscript-mirror||true + cd "angelscript-mirror/sdk/angelscript/projects/gnuc" + make -j$(nproc) + sudo make install + cd ../../../../.. + echo Angelscript installed. +} + +function setup_bullet { + echo Installing bullet3... + sudo apt install python3-dev -y + git clone --depth 1 https://github.com/bulletphysics/bullet3||true + cd bullet3 + ./build_cmake_pybullet_double.sh + cd build_cmake + sudo cmake --install . + cd ../.. +} + +function setup_enet { + echo Installing enet... + git clone --depth 1 https://github.com/lsalzman/enet||true + cd enet + autoreconf -vfi + ./configure + make -j$(nproc) + sudo make install + cd .. + echo Enet installed. +} + +function setup_libgit2 { + echo Installing libgit2... + curl -s -O -L https://github.com/libgit2/libgit2/archive/refs/tags/v1.8.1.tar.gz + tar -xzf v1.8.1.tar.gz + cd libgit2-1.8.1 + mkdir -p build + cd build + cmake .. -DBUILD_TESTS=OFF -DUSE_ICONV=OFF -DBUILD_CLI=OFF -DCMAKE_BUILD_TYPE=Release + cmake --build . + sudo cmake --install . + cd ../.. + rm v1.8.1.tar.gz + echo libgit2 installed. +} + +function setup_libplist { + echo Installing libplist... + curl -s -O -L https://github.com/libimobiledevice/libplist/releases/download/2.6.0/libplist-2.6.0.tar.bz2 + tar -xf libplist-2.6.0.tar.bz2 + cd libplist-2.6.0 + ./configure --without-cython + make + sudo make install + cd .. + rm libplist-2.6.0.tar.bz2 + echo libplist installed. +} + +function setup_poco { + echo Installing poco... + curl -s -O https://pocoproject.org/releases/poco-1.13.3/poco-1.13.3-all.tar.gz + tar -xzf poco-1.13.3-all.tar.gz + cd poco-1.13.3-all + mkdir -p cmake_build + cd cmake_build + export CFLAGS="-fPIC -DPOCO_UTIL_NO_XMLCONFIGURATION" + export CXXFLAGS="-fPIC -DPOCO_UTIL_NO_XMLCONFIGURATION" + cmake .. -DENABLE_TESTS=OFF -DENABLE_SAMPLES=OFF -DCMAKE_BUILD_TYPE=MinSizeRel -DENABLE_PAGECOMPILER=OFF -DENABLE_PAGECOMPILER_FILE2PAGE=OFF -DENABLE_ACTIVERECORD=OFF -DENABLE_ACTIVERECORD_COMPILER=OFF -DENABLE_MONGODB=OFF -DBUILD_SHARED_LIBS=OFF + cmake --build . + sudo cmake --install . + cd ../.. + rm poco-1.13.3-all.tar.gz + echo poco installed. +} + +function setup_sdl { + echo Installing SDL... + # Install SDL this way to get many SDL deps. It is too old so we remove SDL itself and build from source, however. + sudo apt install libssl-dev libcurl4-openssl-dev libopus-dev libsdl2-dev -y + sudo apt remove libsdl2-dev -y + git clone https://github.com/libsdl-org/SDL||true + mkdir -p SDL/build + cd SDL/build + git checkout 4e09e58f62e95a66125dae9ddd3e302603819ffd + cmake -DCMAKE_BUILD_TYPE=MinSizeRel -DSDL_SHARED=OFF -DSDL_STATIC=ON -DSDL_TEST_LIBRARY=OFF .. + cmake --build . --config MinSizeRel + sudo make install + cd ../.. + echo SDL installed. +} + +function setup_nvgt { + echo Building NVGT... + + if [[ "$IS_CI" != 1 ]]; then + # Assumed to not be running on CI; NVGT should be cloned outside of deps first. + echo Not running on CI. + cd .. + + git clone --depth 1 https://github.com/samtupy/nvgt||true + cd nvgt + + else + echo Running on CI. + cd .. + fi + + echo Downloading lindev... + wget https://nvgt.gg/lindev.tar.gz + mkdir -p lindev + cd lindev + tar -xvf ../lindev.tar.gz + cd .. + rm lindev.tar.gz + if ! which scons &> /dev/null; then + export PIP_BREAK_SYSTEM_PACKAGES=1 + pip3 install --user scons + fi + scons -s no_upx=0 + echo NVGT built. +} + +function main { + sudo apt update -y + set -e + mkdir -p deps + cd deps + + # Insure required packages are installed for building. + sudo apt install build-essential gcc g++ make cmake autoconf libtool python3 python3-pip libsystemd-dev libspeechd-dev -y + + setup_angelscript + setup_bullet + setup_enet + setup_libgit2 + setup_libplist + setup_poco + setup_sdl + setup_nvgt + echo Success! + exit 0 +} + +if [[ "$1" == "ci" ]]; then + export IS_CI=1 +else + export IS_CI=0 +fi + +main diff --git a/build/build_macos.sh b/build/build_macos.sh index 0a56aeb6..0a008288 100644 --- a/build/build_macos.sh +++ b/build/build_macos.sh @@ -1,118 +1,118 @@ -#!/bin/zsh - -function setup_homebrew { - brew install autoconf automake libtool openssl libgit2 bullet upx libplist -} - -function setup_angelscript { - echo Installing Angelscript... - git clone --depth 1 https://github.com/codecat/angelscript-mirror||true - cd "angelscript-mirror/sdk/angelscript/projects/cmake" - mkdir -p build - cd build - cmake .. - cmake --build . - sudo cmake --install . - cd ../../../../../.. - echo Angelscript installed. -} - -function setup_enet { - echo Installing enet... - git clone --depth 1 https://github.com/lsalzman/enet||true - cd enet - autoreconf -vfi - ./configure - make -j$(nsysctl -n hw.ncpu) - sudo make install - cd .. - echo Enet installed. -} - -function setup_libgit2 { - curl -s -O -L https://github.com/libgit2/libgit2/archive/refs/tags/v1.8.1.tar.gz - tar -xzf v1.8.1.tar.gz - cd libgit2-1.8.1 - mkdir -p build - cd build - cmake .. -DBUILD_TESTS=OFF -DUSE_ICONV=OFF -DBUILD_CLI=OFF -DCMAKE_BUILD_TYPE=Release - cmake --build . - sudo cmake --install . - cd ../.. - rm v1.8.1.tar.gz -} - -function setup_poco { - curl -s -O https://pocoproject.org/releases/poco-1.13.3/poco-1.13.3-all.tar.gz - tar -xzf poco-1.13.3-all.tar.gz - cd poco-1.13.3-all - mkdir -p cmake_build - cd cmake_build - cmake .. -DENABLE_TESTS=OFF -DENABLE_SAMPLES=OFF -DCMAKE_BUILD_TYPE=MinSizeRel -DCMAKE_OSX_ARCHITECTURES="arm64;x86_64" -DENABLE_PAGECOMPILER=OFF -DENABLE_PAGECOMPILER_FILE2PAGE=OFF -DENABLE_ACTIVERECORD=OFF -DENABLE_ACTIVERECORD_COMPILER=OFF -DENABLE_MONGODB=OFF -DBUILD_SHARED_LIBS=OFF -DCMAKE_CXX_FLAGS=-DPOCO_UTIL_NO_XMLCONFIGURATION - cmake --build . - sudo cmake --install . - cd ../.. - rm poco-1.13.3-all.tar.gz -} - -function setup_sdl { - echo Installing SDL... - git clone https://github.com/libsdl-org/SDL||true - mkdir -p SDL/build - cd SDL/build - git checkout 4e09e58f62e95a66125dae9ddd3e302603819ffd - cmake -DCMAKE_BUILD_TYPE=MinSizeRel -DSDL_SHARED=OFF -DSDL_STATIC=ON -DSDL_TEST_LIBRARY=OFF -DCMAKE_OSX_ARCHITECTURES="arm64;x86_64" .. - cmake --build . --config MinSizeRel - sudo make install - cd ../.. - echo SDL installed. -} -function setup_nvgt { - echo Building NVGT... - - if [[ "$IS_CI" != 1 ]]; then - # Assumed to not be running on CI; NVGT should be cloned outside of deps first. - echo Not running on CI. - cd .. - - git clone --depth 1 https://github.com/samtupy/nvgt||true - cd nvgt - - else - echo Running on CI. - cd .. - fi - - echo Downloading macosdev... - curl -s -O https://nvgt.gg/macosdev.tar.gz - mkdir -p macosdev - cd macosdev - tar -xvf ../macosdev.tar.gz - cd .. - rm macosdev.tar.gz - scons -s - echo NVGT built. -} - -function main { - set -e - mkdir -p deps - cd deps - setup_homebrew - setup_angelscript - setup_enet - #setup_libgit2 - setup_poco - setup_sdl - setup_nvgt - echo Success! - exit 0 -} - -if [[ "$1" == "ci" ]]; then - export IS_CI=1 -else - export IS_CI=0 -fi - -main +#!/bin/zsh + +function setup_homebrew { + brew install autoconf automake libtool openssl libgit2 bullet upx libplist +} + +function setup_angelscript { + echo Installing Angelscript... + git clone --depth 1 https://github.com/codecat/angelscript-mirror||true + cd "angelscript-mirror/sdk/angelscript/projects/cmake" + mkdir -p build + cd build + cmake .. + cmake --build . + sudo cmake --install . + cd ../../../../../.. + echo Angelscript installed. +} + +function setup_enet { + echo Installing enet... + git clone --depth 1 https://github.com/lsalzman/enet||true + cd enet + autoreconf -vfi + ./configure + make -j$(nsysctl -n hw.ncpu) + sudo make install + cd .. + echo Enet installed. +} + +function setup_libgit2 { + curl -s -O -L https://github.com/libgit2/libgit2/archive/refs/tags/v1.8.1.tar.gz + tar -xzf v1.8.1.tar.gz + cd libgit2-1.8.1 + mkdir -p build + cd build + cmake .. -DBUILD_TESTS=OFF -DUSE_ICONV=OFF -DBUILD_CLI=OFF -DCMAKE_BUILD_TYPE=Release + cmake --build . + sudo cmake --install . + cd ../.. + rm v1.8.1.tar.gz +} + +function setup_poco { + curl -s -O https://pocoproject.org/releases/poco-1.13.3/poco-1.13.3-all.tar.gz + tar -xzf poco-1.13.3-all.tar.gz + cd poco-1.13.3-all + mkdir -p cmake_build + cd cmake_build + cmake .. -DENABLE_TESTS=OFF -DENABLE_SAMPLES=OFF -DCMAKE_BUILD_TYPE=MinSizeRel -DCMAKE_OSX_ARCHITECTURES="arm64;x86_64" -DENABLE_PAGECOMPILER=OFF -DENABLE_PAGECOMPILER_FILE2PAGE=OFF -DENABLE_ACTIVERECORD=OFF -DENABLE_ACTIVERECORD_COMPILER=OFF -DENABLE_MONGODB=OFF -DBUILD_SHARED_LIBS=OFF -DCMAKE_CXX_FLAGS=-DPOCO_UTIL_NO_XMLCONFIGURATION + cmake --build . + sudo cmake --install . + cd ../.. + rm poco-1.13.3-all.tar.gz +} + +function setup_sdl { + echo Installing SDL... + git clone https://github.com/libsdl-org/SDL||true + mkdir -p SDL/build + cd SDL/build + git checkout 4e09e58f62e95a66125dae9ddd3e302603819ffd + cmake -DCMAKE_BUILD_TYPE=MinSizeRel -DSDL_SHARED=OFF -DSDL_STATIC=ON -DSDL_TEST_LIBRARY=OFF -DCMAKE_OSX_ARCHITECTURES="arm64;x86_64" .. + cmake --build . --config MinSizeRel + sudo make install + cd ../.. + echo SDL installed. +} +function setup_nvgt { + echo Building NVGT... + + if [[ "$IS_CI" != 1 ]]; then + # Assumed to not be running on CI; NVGT should be cloned outside of deps first. + echo Not running on CI. + cd .. + + git clone --depth 1 https://github.com/samtupy/nvgt||true + cd nvgt + + else + echo Running on CI. + cd .. + fi + + echo Downloading macosdev... + curl -s -O https://nvgt.gg/macosdev.tar.gz + mkdir -p macosdev + cd macosdev + tar -xvf ../macosdev.tar.gz + cd .. + rm macosdev.tar.gz + scons -s + echo NVGT built. +} + +function main { + set -e + mkdir -p deps + cd deps + setup_homebrew + setup_angelscript + setup_enet + #setup_libgit2 + setup_poco + setup_sdl + setup_nvgt + echo Success! + exit 0 +} + +if [[ "$1" == "ci" ]]; then + export IS_CI=1 +else + export IS_CI=0 +fi + +main diff --git a/doc/src/advanced/Plugin Creation.md b/doc/src/advanced/Plugin Creation.md index 4b37d1a9..c3e4ff71 100644 --- a/doc/src/advanced/Plugin Creation.md +++ b/doc/src/advanced/Plugin Creation.md @@ -1,187 +1,187 @@ -# Plugin Creation -Does NVGT not provide the function you need, and do you know a bit of c++? If so, perhaps NVGT plugins are exactly what you're looking for! - -This document will describe all there is to know about creating nvgt plugins, both dynamically and statically. - -## What is a plugin in the context of NVGT? -An NVGT plugin, in it's most basic form, is simply a module of code that is executed during the script loading part of the engine initialization process, one which can extend the functionality of NVGT by directly gaining access to and registering functions with it's internal Angelscript engine. - -Plugins are not just limited to functions, but classes, enums, funcdefs and anything else one could register normally using the Angelscript scripting library. - -## Types of plugin builds -A plugin can either be a shared library (.dll / .dylib / .so) that gets loaded when needed, or a static library (.lib / .a) that is linked directly into a custom build of NVGT. Both methods have different advantages and disadvantages. - -Dynamically loaded plugins, those built into a shared library, are easier to get working with NVGT because it's far easier to create such a plugin without at all altering NVGT's build process or adding things to it. You could use your own build system and your own environment, so long as the proper ABI is exposed to NVGT in the end and an up-to-date version of Angelscript is used within your plugin. However, a smart player may figure out how to replace your plugin dll with some sort of malicious copy, your dll plugin could be duplicated and reused in other projects, you'll have an extra dll file to release with your game distribution etc. - -Static plugins on the other hand, while a bit tougher to build, are certainly more rewarding in the end. From plugin code being packaged directly into your binary to a smaller distribution size because of no duplicated crt/other code in a dll to direct integration with NVGT's build system, there are several advantages that can be observed when choosing to create a static plugin. - -If one chooses to follow every step of the existing NVGT plugin creation process that is used internally by engine developers, you can set up your plugin such that it can easily be built either dynamically or statically depending on the end-user's preference. - -## The basic idea -In short, the idea here stems from a pretty simple base. The user creates a .cpp file that includes the nvgt_plugin.h header that can do any magic heavy lifting needed, then the user just defines an entry point using a macro declared in nvgt_plugin.h. This entry point receives a pointer to the asIScriptEngine instance used by NVGT, which the plugin developer can do anything they please with from registering custom functions to installing some sort of custom profiler. The entry point can return true or false to indicate to NVGT whether the plugin was able to successfully initialize. - -This plugin entry point always takes one argument, which is a structure of data passed to it by NVGT. The structure contains the Angelscript engine pointer as well as pointers to several other Angelscript functions that may be useful, and may be expanded with pointers to other useful interfaces from NVGT as well. One just simply needs to call a function provided by nvgt_plugin.h called prepare_plugin passing to it a pointer to the aforementioned structure before their own plugin initialization code begins to execute. - -To link a static plugin with the engine assuming the nvgt's build script knows about the static library file, one need only add a line such as static_plugin(\) to the nvgt_config.h file where \ should be replaced with the name of your plugin. - -## small example plugin -``` -#include -#include "../../src/nvgt_plugin.h" - -void do_test() { - MessageBoxA(0, "It works, this function is being called from within the context of an NVGT plugin!", "success", 0); -} - -plugin_main(nvgt_plugin_shared* shared) { - prepare_plugin(shared); - shared->script_engine->RegisterGlobalFunction("void do_test()", asFUNCTION(do_test), asCALL_CDECL); - return true; -} -``` - -### picking it apart -We shall forgo any general comments or teaching about the c++ language itself here, but instead will just focus on the bits of code that specifically involve the plugin interface. - -The first thing that you probably noticed was this include directive which includes "../../src/nvgt_plugin.h". Why there? While this will be described later, the gist is that NVGT's build setup already has some infrastructure set up to build plugins. NVGT's github repository has a plugin folder, and in there are folders for each plugin. This example is using such a structure. We will talk more in detail about this later, but for now it is enough to know that nvgt_plugin.h does not include anything else in nvgt's source tree, and can be safely copy pasted where ever you feel is best for your particular project (though we do recommend building plugins with NVGT's workspace). - -The next oddity here, why doesn't the plugin_main function declaration include a return type? This is because it is a macro defined in nvgt_plugin.h. It is required because the name of the entry point will internally change based on whether you are compiling your plugin statically or dynamically. If you are building your plugin as a shared library, the function that ends up exporting is called nvgt_plugin. However since one of course cannot link 2 static libraries with the same symbol names in each to a final executable, the entry point for a static plugin ends up being called nvgt_plugin_\ where \ is replaced with the value of the NVGT_PLUGIN_STATIC preprocessor define (set at plugin build time). In the future even dynamic libraries may possibly contain the plugin name in their entry point function signatures such that more than one plugin could be loaded from one dll file, but for now we instead recommend simply registering functions from multiple plugins in one common entry point if you really want to do that. - -Finally, remember to call prepare_plugin(shared) as the first thing in your plugin, and note that if your entry point does not return true, this indicates an error condition and your plugin is not loaded. - -## NVGT's plugin building infrastructure -As mentioned a couple of times above, NVGT's official repository already contains the infrastructure required to build plugins and integrate them with NVGT's existing build system, complete with the ability to exclude some of your more private plugins from being picked up by the repository. While it is not required that one use this setup and in fact one may not want to if they have a better workspace set up for themselves, we certainly recommend it especially if you are making a plugin that you may want to share with the NVGT community. - -### The plugin directory -In nvgt's main repository, the plugin directory contains all publicly available plugins. Either if you have downloaded NVGT's repository outside of version control (such as a public release artifact) or if you intend to contribute your plugin to the community by submitting a pull request, you can feel free to use this directory as well. - -Here, each directory is typically one plugin. It is not required that this be the case, other directories that are not plugins can also exist here, however any directory within the plugin folder that contains a file called _SConscript will automatically be considered as a plugin by the SConstruct script that builds NVGT. - -The source code in these plugins can be arranged any way you like, as it is the _SConscript file you provide that instructs the system how to build your plugin. - -An example _SConscript file for such a plugin might look like this: -``` -# Import the SCons environment we are using -Import("env") - -# Create the shared version of our plugin if the user has not disabled this feature. -if ARGUMENTS.get("no_shared_plugins", "0") == "0": - env.SharedLibrary("#release/lib/test_plugin", ["test.cpp"], libs = ["user32"]) - -# If we want to make a static version along side our shared one, we need to specifically rebuild the object file containing the plugin's entry point with a different name so that SCons can maintain a proper dependency graph. Note the NVGT_PLUGIN_STATIC define. -static = env.Object("test_plugin_static", "test.cpp", CPPDEFINES = [("NVGT_PLUGIN_STATIC", "test_plugin")]) -# now actually build the static library, reusing the same variable from above for fewer declarations. -static = env.StaticLibrary("#build/lib/test_plugin", [static]) - -# Tell NVGT's SConstruct script that the static version of this plugin needs symbols from the user32 library. -static = [static, "user32"] - -# What is being returned to NVGT's SConstruct in the end is a list of additional static library objects that should be linked. -Return("static") -``` - -Note that while the above example returns the user32 library back to NVGT's build script, it should be noted that most system libraries are already linked into nvgt's builds. The example exists to show how an extra static library would be passed to NVGT from a plugin if required, but this should only be done either as a reaction to a linker error or if you know for sure that your plugin requires a dependency that is not automatically linked to NVGT, examples in the git2, curl or sqlite3 plugins. - -### the user directory -NVGT's github repository also contains another root folder called user. This is a private scratchpad directory that exists so that a user can add plugins or any other code to NVGT that they do not want included in the repository. - -First, the repository's .gitignore file ignores everything in here accept for readme.md, meaning that you can do anything you like here with the peace of mind that you won't accidentally commit your private encryption plugin to the public repository when you try contributing a bugfix to the engine. - -Second, if a _SConscript file is present in this directory, NVGT's main build script will execute it, providing 2 environments to it via SCons exports. The nvgt_env environment is what is used to directly build NVGT, for example if you need any extra static libraries linked to nvgt.exe or the stubs, you'd add one by importing the nvgt_env variable and appending the library you want to link with to the environment's LIBS construction variable. - -Last but not least, if a file called nvgt_config.h is present in the user folder, this will also be loaded in place of the nvgt_config.h in the repo's src directory. - -You can do whatever you want within this user directory, choosing to either follow or ignore any conventions you wish. Below is an example of a working setup that employs the user directory, but keep in mind that you can set up your user directory any way you wish and don't necessarily have to follow the example exactly. - -#### user directory example -The following setup is used for Survive the Wild development. That game requires a couple of proprietary plugins to work, such as a private encryption layer. - -In this case, what was set up was a second github repository that exists within the user directory. It's not a good idea to make a github repository out of the root user folder itself because git will not appreciate this, but instead a folder should be created within the user directory that could contain a subrepository. We'll call it nvgt_user. - -The first step is to create some jumper scripts that allow the user folder to know about the nvgt_user repository contained inside it. - -user/nvgt_config.h: -``` -#include "nvgt_user/nvgt_config.h" -``` -and - -user/_SConscript: -``` -Import(["plugin_env", "nvgt_env"]) -SConscript("nvgt_user/_SConscript", exports=["plugin_env", "nvgt_env"]) -``` - -Now, user/nvgt_user/nvgt_config.h and user/nvgt_user/_SConscript will be loaded as they should be, respectively. - -In the nvgt_user folder itself we have _SConscript, nvgt_plugin.h, and some folders containing private plugins as well as an unimportant folder called setup we'll describe near the end of the example. - -nvgt_config.h contains the custom encryption routines / static plugin configuration that is used to build the version of NVGT used for Survive the Wild. - -The user/nvgt_user/_SConscript file looks something like this: -``` -Import("plugin_env", "nvgt_env") - -SConscript("plugname1/_SConscript", variant_dir = "#build/obj_plugin/plugname1", duplicate = 0, exports = ["plugin_env", "nvgt_env"]) -SConscript("plugname2/_SConscript", variant_dir = "#build/obj_plugin/plugname2", duplicate = 0, exports = ["plugin_env", "nvgt_env"]) -# nvgt_user/nvgt_config.h statically links with the git2 plugin, lets delay load that dll on windows so that users won't get errors if it's not found. -if nvgt_env["PLATFORM"] == "win32": - nvgt_env.Append(LINKFLAGS = ["/delayload:git2.dll"]) -``` -And finally an _SConscript file for nvgt_user/plugname\* might look something like this: -``` -Import(["plugin_env", "nvgt_env"]) - -static = plugin_env.StaticLibrary("#build/lib/plugname2", ["code.cpp"], CPPDEFINES = [("NVGT_PLUGIN_STATIC", "plugname2")], LIBS = ["somelib"]) -nvgt_env.Append(LIBS = [static, "somelib"]) -``` -As you can see, the decision regarding the custom plugins used for Survive the Wild is to simply not support building them as shared libraries, as that will never be needed from the context of that game. - -The only other item in the private nvgt_user repository used for Survive the Wild is a folder called setup, and it's nothing but a tiny all be it useful convenience mechanism. The setup folder simply contains copies of the user/_SConscript and user/nvgt_config.h files that were described at the beginning of this example, meaning that if nvgt's repository ever needs to be cloned from scratch to continue STW development (such as on a new workstation), the following commands can be executed without worrying about creating the extra files that are outside of the nvgt_user repository in the root of the user folder: - -```bash -git clone https://github.com/samtupy/nvgt -cd nvgt/user -git clone https://github.com/samtupy/nvgt_user -cp nvgt_user/setup/* . -``` - -And with that, nvgt is ready for the private development of STW all with the custom plugins still being safely in version control! So long as the cwd is outside of the nvgt_user directory the root nvgt repository is effected, and once inside the nvgt_user directory, that much smaller repository is the only thing that will be touched by git commands. - -Remember, you should use this example as a possible idea as to how you could potentially make use of NVGT's user directory, not as a guide you must follow exactly. Feel free to create your own entirely different workspace in here or if you want, forgo use of the user directory entirely. - -### Angelscript addon shims -When creating a shared/dynamic plugin made up of more than 1 cpp file, you must `#define NVGT_PLUGIN_INCLUDE` and then `#include "nvgt_plugin.h"` before you `#include `. This is to make sure that any additional units you build will use the manually imported Angelscript symbols that were passed to the plugin from NVGT, a multiple defined symbol error might appear otherwise or if your code compiles, 2 different versions of the Angelscript functions could be used which would be very unsafe. To make it easier to include Angelscript addons into your plugins, little shims are provided for the common addons in the nvgt repository in the ASAddon/plugin directory. You can simply compile ASAddon/plugin/scriptarray.cpp, for example, and the scriptarray plugin will safely be included into your plugin. nvgt_plugin.h should still be included before scriptarray.h in any of your plugin source files, however. - -When compiling a static plugin, you do not need to bother linking with these addon shims, because in that case your plugin's static library will be linked with NVGT when NVGT is next recompiled, and NVGT already contains working addons. - -## plugin dll loading -A common sanario is that you may wish to make a plugin that then loads another shared library. This happens already in nvgt, particularly with the git2 plugin. The plugin itself is called git2nvgt, but it loads git2.dll. This calls for some consideration. - -NVGT applications generally put their libraries in a lib folder to reduce clutter in the applications main directory. This isn't required, and in fact if you want to move all shared objects out of the lib folder and put them along side your game executable, you actually tend to avoid the small issue mentioned here. The problem is that sometimes, we need to explicitly tell the operating system about the lib directory at runtime (right now true on windows) in order for it to find the shared libraries there. - -For shared/dynamic plugins, this isn't really an issue. NVGT has launched, informed the system of the lib directory, and loaded your plugin in that order meaning that any dll your plugin imports can already be located and imported just fine. - -With static plugins, on the other hand, we must work around the fact that the operating system will generally try loading all libraries that the program uses before any of that program's code executes, and will show the user an error message and abort the program if it can't find one. Returning to the git2 example from earlier, if you were to ship this plugin with your game, a file would exist called lib/git2.dll on windows. When the static library git2nvgt.lib is created, it will add git2.dll to it's import table but with no knowledge of the lib folder at that point. When the custom build of NVGT which includes this plugin tries to run, an error message will appear because git2.dll can't be found, on account of NVGT never being able to tell the system about the lib directory before the operating system evaluates nvgt's import table and tries to load the libraries found within. - -The solution, at least on windows, is delayed dll loading. It is demonstrated above in the user directory example, but it's easy to gloss over considering it's level of importants. If you add the linkflag `/delayload:libname.dll` to your static plugin's build script, now NVGT's code is allowed to execute meaning it can tell the system about the lib directory, and then the dll will load the first time a function is called from it. On MacOS/clang, there is the linkflag -weak_library which does something similar, used like `-weak_library /path/to/library.dylib`. - -Delay loading does has the disadvantage that the app tends to crash if the dll is not present when it is needed rather than giving the user a nice error message, but you can work around that by manually loading the dll with the LoadLibrary function on windows / similar facilities on other platforms then immediately unloading it just to see if the system will be able to find it, and you can choose to show an error message in that case if you wish. - -## cross platform considerations -If you are only building plugins for projects that are intended to run on one platform, this section may be safely skipped. However if your game runs on multiple platforms and if you intend to introduce custom plugins, you probably don't want to miss this. - -There are a couple of things that should be considered when creating plugins intended to run on all platforms, but only one really big one. In short, it is important that a cross platform plugin's registered Angelscript interface looks exactly the same on all platforms, even if your plugin doesn't support some functionality on one platform. For example if your plugin has functions foo and bar but the bar function only works on windows, it is important to register an empty bar function for any non-windows builds of your plugin rather than excluding the bar function from the angelscript registration of such a plugin build entirely. This is especially true if you intend to, for example, cross compile your application with the windows version of NVGT to run on a linux platform. - -The reasoning is that Angelscript may sometimes store indexes or offsets to internal functions or engine registrations in compiled bytecode rather than the names of them. This makes sense and allows for much smaller/faster compiled programs, but what it does mean is that NVGT's registered interface must appear exactly the same both when compiling and when running a script. Maybe your plugin with foo and bar functions get registered into the engine as functions 500 and 501, then maybe the user loads a plugin after that with boo and bas functions that get registered as functions 502 and 503. Say the user makes a call to the bas function at index 503. Well, if the foo bar plugin doesn't include a bar function on linux builds of it, now we can compile the script on windows and observe that the function call to bas at index 503 is successful. But if I run that compiled code on linux, since the bar function is not registered (as it only works on windows), the bas function is now at index 502 instead of 503 where the bytecode is instructing the program to call a function. Oh no, program panic, invalid bytecode! The solution is to instead register an empty version of the bar function on non-windows builds of such a plugin that does nothing. - -## Angelscript registration -Hopefully this document has helped you gather the knowledge required to start making some great plugins! The last pressing question we'll end with is "how does one register things with NVGT's Angelscript engine?" The angelscript engine is a variable in the nvgt_plugin_shared structure passed to your plugins entry point, it's called script_engine. - -The best reference for how to register things with Angelscript is the Angelscript documentation itself, and as such, the following are just a couple of useful links from there which should help get you on the right track: -* [registering the application interface](https://www.angelcode.com/angelscript/sdk/docs/manual/doc_register_api_topic.html) -* [registering a function](https://www.angelcode.com/angelscript/sdk/docs/manual/doc_register_func.html) -* [registering global properties](https://www.angelcode.com/angelscript/sdk/docs/manual/doc_register_prop.html) -* [registering an object type](https://www.angelcode.com/angelscript/sdk/docs/manual/doc_register_type.html) - -Good luck creating NVGT plugins, and feel free to share some of them to the community if you deem them worthy! +# Plugin Creation +Does NVGT not provide the function you need, and do you know a bit of c++? If so, perhaps NVGT plugins are exactly what you're looking for! + +This document will describe all there is to know about creating nvgt plugins, both dynamically and statically. + +## What is a plugin in the context of NVGT? +An NVGT plugin, in it's most basic form, is simply a module of code that is executed during the script loading part of the engine initialization process, one which can extend the functionality of NVGT by directly gaining access to and registering functions with it's internal Angelscript engine. + +Plugins are not just limited to functions, but classes, enums, funcdefs and anything else one could register normally using the Angelscript scripting library. + +## Types of plugin builds +A plugin can either be a shared library (.dll / .dylib / .so) that gets loaded when needed, or a static library (.lib / .a) that is linked directly into a custom build of NVGT. Both methods have different advantages and disadvantages. + +Dynamically loaded plugins, those built into a shared library, are easier to get working with NVGT because it's far easier to create such a plugin without at all altering NVGT's build process or adding things to it. You could use your own build system and your own environment, so long as the proper ABI is exposed to NVGT in the end and an up-to-date version of Angelscript is used within your plugin. However, a smart player may figure out how to replace your plugin dll with some sort of malicious copy, your dll plugin could be duplicated and reused in other projects, you'll have an extra dll file to release with your game distribution etc. + +Static plugins on the other hand, while a bit tougher to build, are certainly more rewarding in the end. From plugin code being packaged directly into your binary to a smaller distribution size because of no duplicated crt/other code in a dll to direct integration with NVGT's build system, there are several advantages that can be observed when choosing to create a static plugin. + +If one chooses to follow every step of the existing NVGT plugin creation process that is used internally by engine developers, you can set up your plugin such that it can easily be built either dynamically or statically depending on the end-user's preference. + +## The basic idea +In short, the idea here stems from a pretty simple base. The user creates a .cpp file that includes the nvgt_plugin.h header that can do any magic heavy lifting needed, then the user just defines an entry point using a macro declared in nvgt_plugin.h. This entry point receives a pointer to the asIScriptEngine instance used by NVGT, which the plugin developer can do anything they please with from registering custom functions to installing some sort of custom profiler. The entry point can return true or false to indicate to NVGT whether the plugin was able to successfully initialize. + +This plugin entry point always takes one argument, which is a structure of data passed to it by NVGT. The structure contains the Angelscript engine pointer as well as pointers to several other Angelscript functions that may be useful, and may be expanded with pointers to other useful interfaces from NVGT as well. One just simply needs to call a function provided by nvgt_plugin.h called prepare_plugin passing to it a pointer to the aforementioned structure before their own plugin initialization code begins to execute. + +To link a static plugin with the engine assuming the nvgt's build script knows about the static library file, one need only add a line such as static_plugin(\) to the nvgt_config.h file where \ should be replaced with the name of your plugin. + +## small example plugin +``` +#include +#include "../../src/nvgt_plugin.h" + +void do_test() { + MessageBoxA(0, "It works, this function is being called from within the context of an NVGT plugin!", "success", 0); +} + +plugin_main(nvgt_plugin_shared* shared) { + prepare_plugin(shared); + shared->script_engine->RegisterGlobalFunction("void do_test()", asFUNCTION(do_test), asCALL_CDECL); + return true; +} +``` + +### picking it apart +We shall forgo any general comments or teaching about the c++ language itself here, but instead will just focus on the bits of code that specifically involve the plugin interface. + +The first thing that you probably noticed was this include directive which includes "../../src/nvgt_plugin.h". Why there? While this will be described later, the gist is that NVGT's build setup already has some infrastructure set up to build plugins. NVGT's github repository has a plugin folder, and in there are folders for each plugin. This example is using such a structure. We will talk more in detail about this later, but for now it is enough to know that nvgt_plugin.h does not include anything else in nvgt's source tree, and can be safely copy pasted where ever you feel is best for your particular project (though we do recommend building plugins with NVGT's workspace). + +The next oddity here, why doesn't the plugin_main function declaration include a return type? This is because it is a macro defined in nvgt_plugin.h. It is required because the name of the entry point will internally change based on whether you are compiling your plugin statically or dynamically. If you are building your plugin as a shared library, the function that ends up exporting is called nvgt_plugin. However since one of course cannot link 2 static libraries with the same symbol names in each to a final executable, the entry point for a static plugin ends up being called nvgt_plugin_\ where \ is replaced with the value of the NVGT_PLUGIN_STATIC preprocessor define (set at plugin build time). In the future even dynamic libraries may possibly contain the plugin name in their entry point function signatures such that more than one plugin could be loaded from one dll file, but for now we instead recommend simply registering functions from multiple plugins in one common entry point if you really want to do that. + +Finally, remember to call prepare_plugin(shared) as the first thing in your plugin, and note that if your entry point does not return true, this indicates an error condition and your plugin is not loaded. + +## NVGT's plugin building infrastructure +As mentioned a couple of times above, NVGT's official repository already contains the infrastructure required to build plugins and integrate them with NVGT's existing build system, complete with the ability to exclude some of your more private plugins from being picked up by the repository. While it is not required that one use this setup and in fact one may not want to if they have a better workspace set up for themselves, we certainly recommend it especially if you are making a plugin that you may want to share with the NVGT community. + +### The plugin directory +In nvgt's main repository, the plugin directory contains all publicly available plugins. Either if you have downloaded NVGT's repository outside of version control (such as a public release artifact) or if you intend to contribute your plugin to the community by submitting a pull request, you can feel free to use this directory as well. + +Here, each directory is typically one plugin. It is not required that this be the case, other directories that are not plugins can also exist here, however any directory within the plugin folder that contains a file called _SConscript will automatically be considered as a plugin by the SConstruct script that builds NVGT. + +The source code in these plugins can be arranged any way you like, as it is the _SConscript file you provide that instructs the system how to build your plugin. + +An example _SConscript file for such a plugin might look like this: +``` +# Import the SCons environment we are using +Import("env") + +# Create the shared version of our plugin if the user has not disabled this feature. +if ARGUMENTS.get("no_shared_plugins", "0") == "0": + env.SharedLibrary("#release/lib/test_plugin", ["test.cpp"], libs = ["user32"]) + +# If we want to make a static version along side our shared one, we need to specifically rebuild the object file containing the plugin's entry point with a different name so that SCons can maintain a proper dependency graph. Note the NVGT_PLUGIN_STATIC define. +static = env.Object("test_plugin_static", "test.cpp", CPPDEFINES = [("NVGT_PLUGIN_STATIC", "test_plugin")]) +# now actually build the static library, reusing the same variable from above for fewer declarations. +static = env.StaticLibrary("#build/lib/test_plugin", [static]) + +# Tell NVGT's SConstruct script that the static version of this plugin needs symbols from the user32 library. +static = [static, "user32"] + +# What is being returned to NVGT's SConstruct in the end is a list of additional static library objects that should be linked. +Return("static") +``` + +Note that while the above example returns the user32 library back to NVGT's build script, it should be noted that most system libraries are already linked into nvgt's builds. The example exists to show how an extra static library would be passed to NVGT from a plugin if required, but this should only be done either as a reaction to a linker error or if you know for sure that your plugin requires a dependency that is not automatically linked to NVGT, examples in the git2, curl or sqlite3 plugins. + +### the user directory +NVGT's github repository also contains another root folder called user. This is a private scratchpad directory that exists so that a user can add plugins or any other code to NVGT that they do not want included in the repository. + +First, the repository's .gitignore file ignores everything in here accept for readme.md, meaning that you can do anything you like here with the peace of mind that you won't accidentally commit your private encryption plugin to the public repository when you try contributing a bugfix to the engine. + +Second, if a _SConscript file is present in this directory, NVGT's main build script will execute it, providing 2 environments to it via SCons exports. The nvgt_env environment is what is used to directly build NVGT, for example if you need any extra static libraries linked to nvgt.exe or the stubs, you'd add one by importing the nvgt_env variable and appending the library you want to link with to the environment's LIBS construction variable. + +Last but not least, if a file called nvgt_config.h is present in the user folder, this will also be loaded in place of the nvgt_config.h in the repo's src directory. + +You can do whatever you want within this user directory, choosing to either follow or ignore any conventions you wish. Below is an example of a working setup that employs the user directory, but keep in mind that you can set up your user directory any way you wish and don't necessarily have to follow the example exactly. + +#### user directory example +The following setup is used for Survive the Wild development. That game requires a couple of proprietary plugins to work, such as a private encryption layer. + +In this case, what was set up was a second github repository that exists within the user directory. It's not a good idea to make a github repository out of the root user folder itself because git will not appreciate this, but instead a folder should be created within the user directory that could contain a subrepository. We'll call it nvgt_user. + +The first step is to create some jumper scripts that allow the user folder to know about the nvgt_user repository contained inside it. + +user/nvgt_config.h: +``` +#include "nvgt_user/nvgt_config.h" +``` +and + +user/_SConscript: +``` +Import(["plugin_env", "nvgt_env"]) +SConscript("nvgt_user/_SConscript", exports=["plugin_env", "nvgt_env"]) +``` + +Now, user/nvgt_user/nvgt_config.h and user/nvgt_user/_SConscript will be loaded as they should be, respectively. + +In the nvgt_user folder itself we have _SConscript, nvgt_plugin.h, and some folders containing private plugins as well as an unimportant folder called setup we'll describe near the end of the example. + +nvgt_config.h contains the custom encryption routines / static plugin configuration that is used to build the version of NVGT used for Survive the Wild. + +The user/nvgt_user/_SConscript file looks something like this: +``` +Import("plugin_env", "nvgt_env") + +SConscript("plugname1/_SConscript", variant_dir = "#build/obj_plugin/plugname1", duplicate = 0, exports = ["plugin_env", "nvgt_env"]) +SConscript("plugname2/_SConscript", variant_dir = "#build/obj_plugin/plugname2", duplicate = 0, exports = ["plugin_env", "nvgt_env"]) +# nvgt_user/nvgt_config.h statically links with the git2 plugin, lets delay load that dll on windows so that users won't get errors if it's not found. +if nvgt_env["PLATFORM"] == "win32": + nvgt_env.Append(LINKFLAGS = ["/delayload:git2.dll"]) +``` +And finally an _SConscript file for nvgt_user/plugname\* might look something like this: +``` +Import(["plugin_env", "nvgt_env"]) + +static = plugin_env.StaticLibrary("#build/lib/plugname2", ["code.cpp"], CPPDEFINES = [("NVGT_PLUGIN_STATIC", "plugname2")], LIBS = ["somelib"]) +nvgt_env.Append(LIBS = [static, "somelib"]) +``` +As you can see, the decision regarding the custom plugins used for Survive the Wild is to simply not support building them as shared libraries, as that will never be needed from the context of that game. + +The only other item in the private nvgt_user repository used for Survive the Wild is a folder called setup, and it's nothing but a tiny all be it useful convenience mechanism. The setup folder simply contains copies of the user/_SConscript and user/nvgt_config.h files that were described at the beginning of this example, meaning that if nvgt's repository ever needs to be cloned from scratch to continue STW development (such as on a new workstation), the following commands can be executed without worrying about creating the extra files that are outside of the nvgt_user repository in the root of the user folder: + +```bash +git clone https://github.com/samtupy/nvgt +cd nvgt/user +git clone https://github.com/samtupy/nvgt_user +cp nvgt_user/setup/* . +``` + +And with that, nvgt is ready for the private development of STW all with the custom plugins still being safely in version control! So long as the cwd is outside of the nvgt_user directory the root nvgt repository is effected, and once inside the nvgt_user directory, that much smaller repository is the only thing that will be touched by git commands. + +Remember, you should use this example as a possible idea as to how you could potentially make use of NVGT's user directory, not as a guide you must follow exactly. Feel free to create your own entirely different workspace in here or if you want, forgo use of the user directory entirely. + +### Angelscript addon shims +When creating a shared/dynamic plugin made up of more than 1 cpp file, you must `#define NVGT_PLUGIN_INCLUDE` and then `#include "nvgt_plugin.h"` before you `#include `. This is to make sure that any additional units you build will use the manually imported Angelscript symbols that were passed to the plugin from NVGT, a multiple defined symbol error might appear otherwise or if your code compiles, 2 different versions of the Angelscript functions could be used which would be very unsafe. To make it easier to include Angelscript addons into your plugins, little shims are provided for the common addons in the nvgt repository in the ASAddon/plugin directory. You can simply compile ASAddon/plugin/scriptarray.cpp, for example, and the scriptarray plugin will safely be included into your plugin. nvgt_plugin.h should still be included before scriptarray.h in any of your plugin source files, however. + +When compiling a static plugin, you do not need to bother linking with these addon shims, because in that case your plugin's static library will be linked with NVGT when NVGT is next recompiled, and NVGT already contains working addons. + +## plugin dll loading +A common sanario is that you may wish to make a plugin that then loads another shared library. This happens already in nvgt, particularly with the git2 plugin. The plugin itself is called git2nvgt, but it loads git2.dll. This calls for some consideration. + +NVGT applications generally put their libraries in a lib folder to reduce clutter in the applications main directory. This isn't required, and in fact if you want to move all shared objects out of the lib folder and put them along side your game executable, you actually tend to avoid the small issue mentioned here. The problem is that sometimes, we need to explicitly tell the operating system about the lib directory at runtime (right now true on windows) in order for it to find the shared libraries there. + +For shared/dynamic plugins, this isn't really an issue. NVGT has launched, informed the system of the lib directory, and loaded your plugin in that order meaning that any dll your plugin imports can already be located and imported just fine. + +With static plugins, on the other hand, we must work around the fact that the operating system will generally try loading all libraries that the program uses before any of that program's code executes, and will show the user an error message and abort the program if it can't find one. Returning to the git2 example from earlier, if you were to ship this plugin with your game, a file would exist called lib/git2.dll on windows. When the static library git2nvgt.lib is created, it will add git2.dll to it's import table but with no knowledge of the lib folder at that point. When the custom build of NVGT which includes this plugin tries to run, an error message will appear because git2.dll can't be found, on account of NVGT never being able to tell the system about the lib directory before the operating system evaluates nvgt's import table and tries to load the libraries found within. + +The solution, at least on windows, is delayed dll loading. It is demonstrated above in the user directory example, but it's easy to gloss over considering it's level of importants. If you add the linkflag `/delayload:libname.dll` to your static plugin's build script, now NVGT's code is allowed to execute meaning it can tell the system about the lib directory, and then the dll will load the first time a function is called from it. On MacOS/clang, there is the linkflag -weak_library which does something similar, used like `-weak_library /path/to/library.dylib`. + +Delay loading does has the disadvantage that the app tends to crash if the dll is not present when it is needed rather than giving the user a nice error message, but you can work around that by manually loading the dll with the LoadLibrary function on windows / similar facilities on other platforms then immediately unloading it just to see if the system will be able to find it, and you can choose to show an error message in that case if you wish. + +## cross platform considerations +If you are only building plugins for projects that are intended to run on one platform, this section may be safely skipped. However if your game runs on multiple platforms and if you intend to introduce custom plugins, you probably don't want to miss this. + +There are a couple of things that should be considered when creating plugins intended to run on all platforms, but only one really big one. In short, it is important that a cross platform plugin's registered Angelscript interface looks exactly the same on all platforms, even if your plugin doesn't support some functionality on one platform. For example if your plugin has functions foo and bar but the bar function only works on windows, it is important to register an empty bar function for any non-windows builds of your plugin rather than excluding the bar function from the angelscript registration of such a plugin build entirely. This is especially true if you intend to, for example, cross compile your application with the windows version of NVGT to run on a linux platform. + +The reasoning is that Angelscript may sometimes store indexes or offsets to internal functions or engine registrations in compiled bytecode rather than the names of them. This makes sense and allows for much smaller/faster compiled programs, but what it does mean is that NVGT's registered interface must appear exactly the same both when compiling and when running a script. Maybe your plugin with foo and bar functions get registered into the engine as functions 500 and 501, then maybe the user loads a plugin after that with boo and bas functions that get registered as functions 502 and 503. Say the user makes a call to the bas function at index 503. Well, if the foo bar plugin doesn't include a bar function on linux builds of it, now we can compile the script on windows and observe that the function call to bas at index 503 is successful. But if I run that compiled code on linux, since the bar function is not registered (as it only works on windows), the bas function is now at index 502 instead of 503 where the bytecode is instructing the program to call a function. Oh no, program panic, invalid bytecode! The solution is to instead register an empty version of the bar function on non-windows builds of such a plugin that does nothing. + +## Angelscript registration +Hopefully this document has helped you gather the knowledge required to start making some great plugins! The last pressing question we'll end with is "how does one register things with NVGT's Angelscript engine?" The angelscript engine is a variable in the nvgt_plugin_shared structure passed to your plugins entry point, it's called script_engine. + +The best reference for how to register things with Angelscript is the Angelscript documentation itself, and as such, the following are just a couple of useful links from there which should help get you on the right track: +* [registering the application interface](https://www.angelcode.com/angelscript/sdk/docs/manual/doc_register_api_topic.html) +* [registering a function](https://www.angelcode.com/angelscript/sdk/docs/manual/doc_register_func.html) +* [registering global properties](https://www.angelcode.com/angelscript/sdk/docs/manual/doc_register_prop.html) +* [registering an object type](https://www.angelcode.com/angelscript/sdk/docs/manual/doc_register_type.html) + +Good luck creating NVGT plugins, and feel free to share some of them to the community if you deem them worthy! diff --git a/jni/src/main/java/org/libsdl/app/SDLSurface.java b/jni/src/main/java/org/libsdl/app/SDLSurface.java index 0b6e4c59..283e3a5a 100644 --- a/jni/src/main/java/org/libsdl/app/SDLSurface.java +++ b/jni/src/main/java/org/libsdl/app/SDLSurface.java @@ -1,453 +1,453 @@ -package org.libsdl.app; - - -import android.content.Context; -import android.content.pm.ActivityInfo; -import android.graphics.Insets; -import android.hardware.Sensor; -import android.hardware.SensorEvent; -import android.hardware.SensorEventListener; -import android.hardware.SensorManager; -import android.os.Build; -import android.util.DisplayMetrics; -import android.util.Log; -import android.view.Display; -import android.view.InputDevice; -import android.view.KeyEvent; -import android.view.MotionEvent; -import android.view.accessibility.AccessibilityManager; -import android.view.Surface; -import android.view.SurfaceHolder; -import android.view.SurfaceView; -import android.view.View; -import android.view.WindowInsets; -import android.view.WindowManager; - - -/** - SDLSurface. This is what we draw on, so we need to know when it's created - in order to do anything useful. - - Because of this, that's where we set up the SDL thread -*/ -public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback, - View.OnApplyWindowInsetsListener, View.OnKeyListener, View.OnHoverListener, View.OnTouchListener, SensorEventListener { - - // Sensors - protected SensorManager mSensorManager; - protected Display mDisplay; - - // Keep track of the surface size to normalize touch events - protected float mWidth, mHeight; - - // Is SurfaceView ready for rendering - public boolean mIsSurfaceReady; - - // Accessibility manager - protected AccessibilityManager mAccessibilityManager; - - // Startup - public SDLSurface(Context context) { - super(context); - getHolder().addCallback(this); - - setFocusable(true); - setFocusableInTouchMode(true); - requestFocus(); - setOnApplyWindowInsetsListener(this); - setOnKeyListener(this); - setOnTouchListener(this); - setOnHoverListener(this); - - mDisplay = ((WindowManager)context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); - mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE); - mAccessibilityManager = (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE); - - setOnGenericMotionListener(SDLActivity.getMotionListener()); - - // Some arbitrary defaults to avoid a potential division by zero - mWidth = 1.0f; - mHeight = 1.0f; - - mIsSurfaceReady = false; - } - - public void handlePause() { - enableSensor(Sensor.TYPE_ACCELEROMETER, false); - } - - public void handleResume() { - setFocusable(true); - setFocusableInTouchMode(true); - requestFocus(); - setOnApplyWindowInsetsListener(this); - setOnKeyListener(this); - setOnTouchListener(this); - enableSensor(Sensor.TYPE_ACCELEROMETER, true); - } - - public Surface getNativeSurface() { - return getHolder().getSurface(); - } - - // Called when we have a valid drawing surface - @Override - public void surfaceCreated(SurfaceHolder holder) { - Log.v("SDL", "surfaceCreated()"); - SDLActivity.onNativeSurfaceCreated(); - } - - // Called when we lose the surface - @Override - public void surfaceDestroyed(SurfaceHolder holder) { - Log.v("SDL", "surfaceDestroyed()"); - - // Transition to pause, if needed - SDLActivity.mNextNativeState = SDLActivity.NativeState.PAUSED; - SDLActivity.handleNativeState(); - - mIsSurfaceReady = false; - SDLActivity.onNativeSurfaceDestroyed(); - } - - // Called when the surface is resized - @Override - public void surfaceChanged(SurfaceHolder holder, - int format, int width, int height) { - Log.v("SDL", "surfaceChanged()"); - - if (SDLActivity.mSingleton == null) { - return; - } - - mWidth = width; - mHeight = height; - int nDeviceWidth = width; - int nDeviceHeight = height; - float density = 1.0f; - try - { - if (Build.VERSION.SDK_INT >= 17 /* Android 4.2 (JELLY_BEAN_MR1) */) { - DisplayMetrics realMetrics = new DisplayMetrics(); - mDisplay.getRealMetrics( realMetrics ); - nDeviceWidth = realMetrics.widthPixels; - nDeviceHeight = realMetrics.heightPixels; - // Use densityDpi instead of density to more closely match what the UI scale is - density = (float)realMetrics.densityDpi / 160.0f; - } - } catch(Exception ignored) { - } - - synchronized(SDLActivity.getContext()) { - // In case we're waiting on a size change after going fullscreen, send a notification. - SDLActivity.getContext().notifyAll(); - } - - Log.v("SDL", "Window size: " + width + "x" + height); - Log.v("SDL", "Device size: " + nDeviceWidth + "x" + nDeviceHeight); - SDLActivity.nativeSetScreenResolution(width, height, nDeviceWidth, nDeviceHeight, density, mDisplay.getRefreshRate()); - SDLActivity.onNativeResize(); - - // Prevent a screen distortion glitch, - // for instance when the device is in Landscape and a Portrait App is resumed. - boolean skip = false; - int requestedOrientation = SDLActivity.mSingleton.getRequestedOrientation(); - - if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT) { - if (mWidth > mHeight) { - skip = true; - } - } else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE) { - if (mWidth < mHeight) { - skip = true; - } - } - - // Special Patch for Square Resolution: Black Berry Passport - if (skip) { - double min = Math.min(mWidth, mHeight); - double max = Math.max(mWidth, mHeight); - - if (max / min < 1.20) { - Log.v("SDL", "Don't skip on such aspect-ratio. Could be a square resolution."); - skip = false; - } - } - - // Don't skip if we might be multi-window or have popup dialogs - if (skip) { - if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) { - skip = false; - } - } - - if (skip) { - Log.v("SDL", "Skip .. Surface is not ready."); - mIsSurfaceReady = false; - return; - } - - /* If the surface has been previously destroyed by onNativeSurfaceDestroyed, recreate it here */ - SDLActivity.onNativeSurfaceChanged(); - - /* Surface is ready */ - mIsSurfaceReady = true; - - SDLActivity.mNextNativeState = SDLActivity.NativeState.RESUMED; - SDLActivity.handleNativeState(); - } - - // Window inset - @Override - public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) { - if (Build.VERSION.SDK_INT >= 30 /* Android 11 (R) */) { - Insets combined = insets.getInsets(WindowInsets.Type.systemBars() | - WindowInsets.Type.systemGestures() | - WindowInsets.Type.mandatorySystemGestures() | - WindowInsets.Type.tappableElement() | - WindowInsets.Type.displayCutout()); - - SDLActivity.onNativeInsetsChanged(combined.left, combined.right, combined.top, combined.bottom); - } - - // Pass these to any child views in case they need them - return insets; - } - - // Key events - @Override - public boolean onKey(View v, int keyCode, KeyEvent event) { - return SDLActivity.handleKeyEvent(v, keyCode, event, null); - } - - private float getNormalizedX(float x) - { - if (mWidth <= 1) { - return 0.5f; - } else { - return (x / (mWidth - 1)); - } - } - - private float getNormalizedY(float y) - { - if (mHeight <= 1) { - return 0.5f; - } else { - return (y / (mHeight - 1)); - } - } - - // Touch events - @Override - public boolean onHover(View v, MotionEvent event) { - if (mAccessibilityManager.isTouchExplorationEnabled()) { - return onTouch(v, event); - } else { - return super.onHoverEvent(event); - } - } - @Override - public boolean onTouch(View v, MotionEvent event) { - /* Ref: http://developer.android.com/training/gestures/multi.html */ - int touchDevId = event.getDeviceId(); - final int pointerCount = event.getPointerCount(); - int action = event.getActionMasked(); - int pointerFingerId; - int i = -1; - float x,y,p; - - // Convert hover events to touch events if we've received them. Should we only do this if accessibility manager? - if (action == MotionEvent.ACTION_HOVER_ENTER) action = MotionEvent.ACTION_DOWN; - else if (action == MotionEvent.ACTION_HOVER_MOVE) action = MotionEvent.ACTION_MOVE; - else if (action == MotionEvent.ACTION_HOVER_EXIT) action = MotionEvent.ACTION_UP; - - // 12290 = Samsung DeX mode desktop mouse - // 12290 = 0x3002 = 0x2002 | 0x1002 = SOURCE_MOUSE | SOURCE_TOUCHSCREEN - // 0x2 = SOURCE_CLASS_POINTER - if (event.getSource() == InputDevice.SOURCE_MOUSE || event.getSource() == (InputDevice.SOURCE_MOUSE | InputDevice.SOURCE_TOUCHSCREEN)) { - int mouseButton = 1; - try { - Object object = event.getClass().getMethod("getButtonState").invoke(event); - if (object != null) { - mouseButton = (Integer) object; - } - } catch(Exception ignored) { - } - - // We need to check if we're in relative mouse mode and get the axis offset rather than the x/y values - // if we are. We'll leverage our existing mouse motion listener - SDLGenericMotionListener_API12 motionListener = SDLActivity.getMotionListener(); - x = motionListener.getEventX(event); - y = motionListener.getEventY(event); - - SDLActivity.onNativeMouse(mouseButton, action, x, y, motionListener.inRelativeMode()); - } else { - switch(action) { - case MotionEvent.ACTION_MOVE: - for (i = 0; i < pointerCount; i++) { - pointerFingerId = event.getPointerId(i); - x = getNormalizedX(event.getX(i)); - y = getNormalizedY(event.getY(i)); - p = event.getPressure(i); - if (p > 1.0f) { - // may be larger than 1.0f on some devices - // see the documentation of getPressure(i) - p = 1.0f; - } - SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p); - } - break; - - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_DOWN: - // Primary pointer up/down, the index is always zero - i = 0; - /* fallthrough */ - case MotionEvent.ACTION_POINTER_UP: - case MotionEvent.ACTION_POINTER_DOWN: - // Non primary pointer up/down - if (i == -1) { - i = event.getActionIndex(); - } - - pointerFingerId = event.getPointerId(i); - x = getNormalizedX(event.getX(i)); - y = getNormalizedY(event.getY(i)); - p = event.getPressure(i); - if (p > 1.0f) { - // may be larger than 1.0f on some devices - // see the documentation of getPressure(i) - p = 1.0f; - } - SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p); - break; - - case MotionEvent.ACTION_CANCEL: - for (i = 0; i < pointerCount; i++) { - pointerFingerId = event.getPointerId(i); - x = getNormalizedX(event.getX(i)); - y = getNormalizedY(event.getY(i)); - p = event.getPressure(i); - if (p > 1.0f) { - // may be larger than 1.0f on some devices - // see the documentation of getPressure(i) - p = 1.0f; - } - SDLActivity.onNativeTouch(touchDevId, pointerFingerId, MotionEvent.ACTION_UP, x, y, p); - } - break; - - default: - break; - } - } - - return true; - } - - // Sensor events - public void enableSensor(int sensortype, boolean enabled) { - // TODO: This uses getDefaultSensor - what if we have >1 accels? - if (enabled) { - mSensorManager.registerListener(this, - mSensorManager.getDefaultSensor(sensortype), - SensorManager.SENSOR_DELAY_GAME, null); - } else { - mSensorManager.unregisterListener(this, - mSensorManager.getDefaultSensor(sensortype)); - } - } - - @Override - public void onAccuracyChanged(Sensor sensor, int accuracy) { - // TODO - } - - @Override - public void onSensorChanged(SensorEvent event) { - if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) { - - // Since we may have an orientation set, we won't receive onConfigurationChanged events. - // We thus should check here. - int newRotation; - - float x, y; - switch (mDisplay.getRotation()) { - case Surface.ROTATION_0: - default: - x = event.values[0]; - y = event.values[1]; - newRotation = 0; - break; - case Surface.ROTATION_90: - x = -event.values[1]; - y = event.values[0]; - newRotation = 90; - break; - case Surface.ROTATION_180: - x = -event.values[0]; - y = -event.values[1]; - newRotation = 180; - break; - case Surface.ROTATION_270: - x = event.values[1]; - y = -event.values[0]; - newRotation = 270; - break; - } - - if (newRotation != SDLActivity.mCurrentRotation) { - SDLActivity.mCurrentRotation = newRotation; - SDLActivity.onNativeRotationChanged(newRotation); - } - - SDLActivity.onNativeAccel(-x / SensorManager.GRAVITY_EARTH, - y / SensorManager.GRAVITY_EARTH, - event.values[2] / SensorManager.GRAVITY_EARTH); - - - } - } - - // Captured pointer events for API 26. - public boolean onCapturedPointerEvent(MotionEvent event) - { - int action = event.getActionMasked(); - - float x, y; - switch (action) { - case MotionEvent.ACTION_SCROLL: - x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0); - y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0); - SDLActivity.onNativeMouse(0, action, x, y, false); - return true; - - case MotionEvent.ACTION_HOVER_MOVE: - case MotionEvent.ACTION_MOVE: - x = event.getX(0); - y = event.getY(0); - SDLActivity.onNativeMouse(0, action, x, y, true); - return true; - - case MotionEvent.ACTION_BUTTON_PRESS: - case MotionEvent.ACTION_BUTTON_RELEASE: - - // Change our action value to what SDL's code expects. - if (action == MotionEvent.ACTION_BUTTON_PRESS) { - action = MotionEvent.ACTION_DOWN; - } else { /* MotionEvent.ACTION_BUTTON_RELEASE */ - action = MotionEvent.ACTION_UP; - } - - x = event.getX(0); - y = event.getY(0); - int button = event.getButtonState(); - - SDLActivity.onNativeMouse(button, action, x, y, true); - return true; - } - - return false; - } -} +package org.libsdl.app; + + +import android.content.Context; +import android.content.pm.ActivityInfo; +import android.graphics.Insets; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.os.Build; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.Display; +import android.view.InputDevice; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.accessibility.AccessibilityManager; +import android.view.Surface; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.View; +import android.view.WindowInsets; +import android.view.WindowManager; + + +/** + SDLSurface. This is what we draw on, so we need to know when it's created + in order to do anything useful. + + Because of this, that's where we set up the SDL thread +*/ +public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback, + View.OnApplyWindowInsetsListener, View.OnKeyListener, View.OnHoverListener, View.OnTouchListener, SensorEventListener { + + // Sensors + protected SensorManager mSensorManager; + protected Display mDisplay; + + // Keep track of the surface size to normalize touch events + protected float mWidth, mHeight; + + // Is SurfaceView ready for rendering + public boolean mIsSurfaceReady; + + // Accessibility manager + protected AccessibilityManager mAccessibilityManager; + + // Startup + public SDLSurface(Context context) { + super(context); + getHolder().addCallback(this); + + setFocusable(true); + setFocusableInTouchMode(true); + requestFocus(); + setOnApplyWindowInsetsListener(this); + setOnKeyListener(this); + setOnTouchListener(this); + setOnHoverListener(this); + + mDisplay = ((WindowManager)context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); + mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE); + mAccessibilityManager = (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE); + + setOnGenericMotionListener(SDLActivity.getMotionListener()); + + // Some arbitrary defaults to avoid a potential division by zero + mWidth = 1.0f; + mHeight = 1.0f; + + mIsSurfaceReady = false; + } + + public void handlePause() { + enableSensor(Sensor.TYPE_ACCELEROMETER, false); + } + + public void handleResume() { + setFocusable(true); + setFocusableInTouchMode(true); + requestFocus(); + setOnApplyWindowInsetsListener(this); + setOnKeyListener(this); + setOnTouchListener(this); + enableSensor(Sensor.TYPE_ACCELEROMETER, true); + } + + public Surface getNativeSurface() { + return getHolder().getSurface(); + } + + // Called when we have a valid drawing surface + @Override + public void surfaceCreated(SurfaceHolder holder) { + Log.v("SDL", "surfaceCreated()"); + SDLActivity.onNativeSurfaceCreated(); + } + + // Called when we lose the surface + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + Log.v("SDL", "surfaceDestroyed()"); + + // Transition to pause, if needed + SDLActivity.mNextNativeState = SDLActivity.NativeState.PAUSED; + SDLActivity.handleNativeState(); + + mIsSurfaceReady = false; + SDLActivity.onNativeSurfaceDestroyed(); + } + + // Called when the surface is resized + @Override + public void surfaceChanged(SurfaceHolder holder, + int format, int width, int height) { + Log.v("SDL", "surfaceChanged()"); + + if (SDLActivity.mSingleton == null) { + return; + } + + mWidth = width; + mHeight = height; + int nDeviceWidth = width; + int nDeviceHeight = height; + float density = 1.0f; + try + { + if (Build.VERSION.SDK_INT >= 17 /* Android 4.2 (JELLY_BEAN_MR1) */) { + DisplayMetrics realMetrics = new DisplayMetrics(); + mDisplay.getRealMetrics( realMetrics ); + nDeviceWidth = realMetrics.widthPixels; + nDeviceHeight = realMetrics.heightPixels; + // Use densityDpi instead of density to more closely match what the UI scale is + density = (float)realMetrics.densityDpi / 160.0f; + } + } catch(Exception ignored) { + } + + synchronized(SDLActivity.getContext()) { + // In case we're waiting on a size change after going fullscreen, send a notification. + SDLActivity.getContext().notifyAll(); + } + + Log.v("SDL", "Window size: " + width + "x" + height); + Log.v("SDL", "Device size: " + nDeviceWidth + "x" + nDeviceHeight); + SDLActivity.nativeSetScreenResolution(width, height, nDeviceWidth, nDeviceHeight, density, mDisplay.getRefreshRate()); + SDLActivity.onNativeResize(); + + // Prevent a screen distortion glitch, + // for instance when the device is in Landscape and a Portrait App is resumed. + boolean skip = false; + int requestedOrientation = SDLActivity.mSingleton.getRequestedOrientation(); + + if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT) { + if (mWidth > mHeight) { + skip = true; + } + } else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE) { + if (mWidth < mHeight) { + skip = true; + } + } + + // Special Patch for Square Resolution: Black Berry Passport + if (skip) { + double min = Math.min(mWidth, mHeight); + double max = Math.max(mWidth, mHeight); + + if (max / min < 1.20) { + Log.v("SDL", "Don't skip on such aspect-ratio. Could be a square resolution."); + skip = false; + } + } + + // Don't skip if we might be multi-window or have popup dialogs + if (skip) { + if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) { + skip = false; + } + } + + if (skip) { + Log.v("SDL", "Skip .. Surface is not ready."); + mIsSurfaceReady = false; + return; + } + + /* If the surface has been previously destroyed by onNativeSurfaceDestroyed, recreate it here */ + SDLActivity.onNativeSurfaceChanged(); + + /* Surface is ready */ + mIsSurfaceReady = true; + + SDLActivity.mNextNativeState = SDLActivity.NativeState.RESUMED; + SDLActivity.handleNativeState(); + } + + // Window inset + @Override + public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) { + if (Build.VERSION.SDK_INT >= 30 /* Android 11 (R) */) { + Insets combined = insets.getInsets(WindowInsets.Type.systemBars() | + WindowInsets.Type.systemGestures() | + WindowInsets.Type.mandatorySystemGestures() | + WindowInsets.Type.tappableElement() | + WindowInsets.Type.displayCutout()); + + SDLActivity.onNativeInsetsChanged(combined.left, combined.right, combined.top, combined.bottom); + } + + // Pass these to any child views in case they need them + return insets; + } + + // Key events + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + return SDLActivity.handleKeyEvent(v, keyCode, event, null); + } + + private float getNormalizedX(float x) + { + if (mWidth <= 1) { + return 0.5f; + } else { + return (x / (mWidth - 1)); + } + } + + private float getNormalizedY(float y) + { + if (mHeight <= 1) { + return 0.5f; + } else { + return (y / (mHeight - 1)); + } + } + + // Touch events + @Override + public boolean onHover(View v, MotionEvent event) { + if (mAccessibilityManager.isTouchExplorationEnabled()) { + return onTouch(v, event); + } else { + return super.onHoverEvent(event); + } + } + @Override + public boolean onTouch(View v, MotionEvent event) { + /* Ref: http://developer.android.com/training/gestures/multi.html */ + int touchDevId = event.getDeviceId(); + final int pointerCount = event.getPointerCount(); + int action = event.getActionMasked(); + int pointerFingerId; + int i = -1; + float x,y,p; + + // Convert hover events to touch events if we've received them. Should we only do this if accessibility manager? + if (action == MotionEvent.ACTION_HOVER_ENTER) action = MotionEvent.ACTION_DOWN; + else if (action == MotionEvent.ACTION_HOVER_MOVE) action = MotionEvent.ACTION_MOVE; + else if (action == MotionEvent.ACTION_HOVER_EXIT) action = MotionEvent.ACTION_UP; + + // 12290 = Samsung DeX mode desktop mouse + // 12290 = 0x3002 = 0x2002 | 0x1002 = SOURCE_MOUSE | SOURCE_TOUCHSCREEN + // 0x2 = SOURCE_CLASS_POINTER + if (event.getSource() == InputDevice.SOURCE_MOUSE || event.getSource() == (InputDevice.SOURCE_MOUSE | InputDevice.SOURCE_TOUCHSCREEN)) { + int mouseButton = 1; + try { + Object object = event.getClass().getMethod("getButtonState").invoke(event); + if (object != null) { + mouseButton = (Integer) object; + } + } catch(Exception ignored) { + } + + // We need to check if we're in relative mouse mode and get the axis offset rather than the x/y values + // if we are. We'll leverage our existing mouse motion listener + SDLGenericMotionListener_API12 motionListener = SDLActivity.getMotionListener(); + x = motionListener.getEventX(event); + y = motionListener.getEventY(event); + + SDLActivity.onNativeMouse(mouseButton, action, x, y, motionListener.inRelativeMode()); + } else { + switch(action) { + case MotionEvent.ACTION_MOVE: + for (i = 0; i < pointerCount; i++) { + pointerFingerId = event.getPointerId(i); + x = getNormalizedX(event.getX(i)); + y = getNormalizedY(event.getY(i)); + p = event.getPressure(i); + if (p > 1.0f) { + // may be larger than 1.0f on some devices + // see the documentation of getPressure(i) + p = 1.0f; + } + SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p); + } + break; + + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_DOWN: + // Primary pointer up/down, the index is always zero + i = 0; + /* fallthrough */ + case MotionEvent.ACTION_POINTER_UP: + case MotionEvent.ACTION_POINTER_DOWN: + // Non primary pointer up/down + if (i == -1) { + i = event.getActionIndex(); + } + + pointerFingerId = event.getPointerId(i); + x = getNormalizedX(event.getX(i)); + y = getNormalizedY(event.getY(i)); + p = event.getPressure(i); + if (p > 1.0f) { + // may be larger than 1.0f on some devices + // see the documentation of getPressure(i) + p = 1.0f; + } + SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p); + break; + + case MotionEvent.ACTION_CANCEL: + for (i = 0; i < pointerCount; i++) { + pointerFingerId = event.getPointerId(i); + x = getNormalizedX(event.getX(i)); + y = getNormalizedY(event.getY(i)); + p = event.getPressure(i); + if (p > 1.0f) { + // may be larger than 1.0f on some devices + // see the documentation of getPressure(i) + p = 1.0f; + } + SDLActivity.onNativeTouch(touchDevId, pointerFingerId, MotionEvent.ACTION_UP, x, y, p); + } + break; + + default: + break; + } + } + + return true; + } + + // Sensor events + public void enableSensor(int sensortype, boolean enabled) { + // TODO: This uses getDefaultSensor - what if we have >1 accels? + if (enabled) { + mSensorManager.registerListener(this, + mSensorManager.getDefaultSensor(sensortype), + SensorManager.SENSOR_DELAY_GAME, null); + } else { + mSensorManager.unregisterListener(this, + mSensorManager.getDefaultSensor(sensortype)); + } + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { + // TODO + } + + @Override + public void onSensorChanged(SensorEvent event) { + if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) { + + // Since we may have an orientation set, we won't receive onConfigurationChanged events. + // We thus should check here. + int newRotation; + + float x, y; + switch (mDisplay.getRotation()) { + case Surface.ROTATION_0: + default: + x = event.values[0]; + y = event.values[1]; + newRotation = 0; + break; + case Surface.ROTATION_90: + x = -event.values[1]; + y = event.values[0]; + newRotation = 90; + break; + case Surface.ROTATION_180: + x = -event.values[0]; + y = -event.values[1]; + newRotation = 180; + break; + case Surface.ROTATION_270: + x = event.values[1]; + y = -event.values[0]; + newRotation = 270; + break; + } + + if (newRotation != SDLActivity.mCurrentRotation) { + SDLActivity.mCurrentRotation = newRotation; + SDLActivity.onNativeRotationChanged(newRotation); + } + + SDLActivity.onNativeAccel(-x / SensorManager.GRAVITY_EARTH, + y / SensorManager.GRAVITY_EARTH, + event.values[2] / SensorManager.GRAVITY_EARTH); + + + } + } + + // Captured pointer events for API 26. + public boolean onCapturedPointerEvent(MotionEvent event) + { + int action = event.getActionMasked(); + + float x, y; + switch (action) { + case MotionEvent.ACTION_SCROLL: + x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0); + y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0); + SDLActivity.onNativeMouse(0, action, x, y, false); + return true; + + case MotionEvent.ACTION_HOVER_MOVE: + case MotionEvent.ACTION_MOVE: + x = event.getX(0); + y = event.getY(0); + SDLActivity.onNativeMouse(0, action, x, y, true); + return true; + + case MotionEvent.ACTION_BUTTON_PRESS: + case MotionEvent.ACTION_BUTTON_RELEASE: + + // Change our action value to what SDL's code expects. + if (action == MotionEvent.ACTION_BUTTON_PRESS) { + action = MotionEvent.ACTION_DOWN; + } else { /* MotionEvent.ACTION_BUTTON_RELEASE */ + action = MotionEvent.ACTION_UP; + } + + x = event.getX(0); + y = event.getY(0); + int button = event.getButtonState(); + + SDLActivity.onNativeMouse(button, action, x, y, true); + return true; + } + + return false; + } +} diff --git a/plugin/curl/_SConscript b/plugin/curl/_SConscript index 3d77c523..3c8bdf14 100644 --- a/plugin/curl/_SConscript +++ b/plugin/curl/_SConscript @@ -1,15 +1,15 @@ -Import("env") - -if env["PLATFORM"] == "linux": - # At least on my Ubuntu installations, the curl headers end up in what might be a somewhat nonstandard place, at any rate it doesn't hurt to add the edgecase here. - env.Append(CPPPATH = ["/usr/include/x86_64-linux-gnu"]) -scriptarray = env.SharedObject("scriptarray", "#ASAddon/plugin/scriptarray.cpp", LIBPATH = []) -winlibs = "" -if env["PLATFORM"] == "win32": - winlibs = "kernel32 user32 crypt32 advapi32 iphlpapi netapi32 uuid wldap32 ws2_32 normaliz" -if ARGUMENTS.get("no_shared_plugins", "0") == "0": - env.SharedLibrary("#release/lib/nvgt_curl", ["internet.cpp", scriptarray], LIBS = Split(winlibs) + ["libcurl", "PocoFoundationMT" if env["PLATFORM"] == "win32" else "PocoFoundation"], CPPDEFINES = ["CURL_STATICLIB"]) -static = env.Object("internet_static", "internet.cpp", CPPDEFINES = [("NVGT_PLUGIN_STATIC", "nvgt_curl"), "CURL_STATICLIB"]) -static = env.StaticLibrary("#build/lib/nvgt_curl", [static], LIBS = ["libcurl"]) -static = [static, "libcurl"] -Return("static") +Import("env") + +if env["PLATFORM"] == "linux": + # At least on my Ubuntu installations, the curl headers end up in what might be a somewhat nonstandard place, at any rate it doesn't hurt to add the edgecase here. + env.Append(CPPPATH = ["/usr/include/x86_64-linux-gnu"]) +scriptarray = env.SharedObject("scriptarray", "#ASAddon/plugin/scriptarray.cpp", LIBPATH = []) +winlibs = "" +if env["PLATFORM"] == "win32": + winlibs = "kernel32 user32 crypt32 advapi32 iphlpapi netapi32 uuid wldap32 ws2_32 normaliz" +if ARGUMENTS.get("no_shared_plugins", "0") == "0": + env.SharedLibrary("#release/lib/nvgt_curl", ["internet.cpp", scriptarray], LIBS = Split(winlibs) + ["libcurl", "PocoFoundationMT" if env["PLATFORM"] == "win32" else "PocoFoundation"], CPPDEFINES = ["CURL_STATICLIB"]) +static = env.Object("internet_static", "internet.cpp", CPPDEFINES = [("NVGT_PLUGIN_STATIC", "nvgt_curl"), "CURL_STATICLIB"]) +static = env.StaticLibrary("#build/lib/nvgt_curl", [static], LIBS = ["libcurl"]) +static = [static, "libcurl"] +Return("static") diff --git a/plugin/git/_SConscript b/plugin/git/_SConscript index 8443ba21..d3b71d87 100644 --- a/plugin/git/_SConscript +++ b/plugin/git/_SConscript @@ -1,12 +1,12 @@ -Import(["env", "nvgt_env"]) - -scriptarray = env.SharedObject("scriptarray", "#ASAddon/plugin/scriptarray.cpp") -if ARGUMENTS.get("no_shared_plugins", "0") == "0": - env.SharedLibrary("#release/lib/git2nvgt", ["git.cpp", scriptarray], LIBS = ["git2"]) -static = env.Object("git_static", "git.cpp", CPPDEFINES = [("NVGT_PLUGIN_STATIC", "git2nvgt")]) -static = env.StaticLibrary("#build/lib/git2nvgt", [static]) -if env["PLATFORM"] == "darwin": - nvgt_env.Append(LINKFLAGS = ["-weak_library", "/opt/homebrew/lib/libgit2.dylib"]) -else: - static = [static, "git2"] -Return("static") +Import(["env", "nvgt_env"]) + +scriptarray = env.SharedObject("scriptarray", "#ASAddon/plugin/scriptarray.cpp") +if ARGUMENTS.get("no_shared_plugins", "0") == "0": + env.SharedLibrary("#release/lib/git2nvgt", ["git.cpp", scriptarray], LIBS = ["git2"]) +static = env.Object("git_static", "git.cpp", CPPDEFINES = [("NVGT_PLUGIN_STATIC", "git2nvgt")]) +static = env.StaticLibrary("#build/lib/git2nvgt", [static]) +if env["PLATFORM"] == "darwin": + nvgt_env.Append(LINKFLAGS = ["-weak_library", "/opt/homebrew/lib/libgit2.dylib"]) +else: + static = [static, "git2"] +Return("static") diff --git a/plugin/git/git.cpp b/plugin/git/git.cpp index a03bfa78..64f7fcf9 100644 --- a/plugin/git/git.cpp +++ b/plugin/git/git.cpp @@ -1,450 +1,450 @@ -/* git.cpp - libgit2 wrapper plugin code - * - * NVGT - NonVisual Gaming Toolkit - * Copyright (c) 2022-2024 Sam Tupy - * https://nvgt.gg - * This software is provided "as-is", without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. - * Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: - * 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. - * 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. - * 3. This notice may not be removed or altered from any source distribution. -*/ - -#include "../../src/nvgt_plugin.h" -#include "git.h" - -static bool libgit2_inited = false; -static asIScriptEngine* g_ScriptEngine = NULL; - -int nvgt_git_default_match_callback(const char* path, const char* matched, void* payload) { - nvgt_git_repository* repo = (nvgt_git_repository*)payload; - if (!repo->match_callback) return 0; - asIScriptContext* ACtx = asGetActiveContext(); - bool new_context = !ACtx || ACtx->PushState() < 0; - asIScriptContext* ctx = new_context ? g_ScriptEngine->RequestContext() : ACtx; - if (ctx->Prepare(repo->match_callback)) { - if (new_context) g_ScriptEngine->ReturnContext(ctx); - else ctx->PopState(); - return GIT_EUSER; - } - std::string path_str(path, strlen(path)); - std::string matched_str(matched, strlen(path)); - ctx->SetArgObject(1, repo); - ctx->SetArgObject(2, &path_str); - ctx->SetArgObject(3, &repo->match_callback_payload); - int ret = GIT_EUSER; - if (ctx->Execute() == asEXECUTION_FINISHED) - ret = ctx->GetReturnDWord(); - if (new_context) g_ScriptEngine->ReturnContext(ctx); - else ctx->PopState(); - return ret; -} -int nvgt_git_changed_match_callback(const char* path, const char* matched, void* payload) { - git_repository* repo = (git_repository*)payload; - unsigned int status; - if (git_status_file(&status, repo, path) < 0) return -1; - if ((status & GIT_STATUS_WT_MODIFIED) || (status & GIT_STATUS_WT_NEW)) return 0; - return 1; -} -inline git_strarray as_strarray2git_strarray(CScriptArray* as_array) { - git_strarray strarray; - strarray.count = as_array->GetSize(); - strarray.strings = (char**)malloc(strarray.count * sizeof(char*)); - for (int i = 0; i < strarray.count; i++) - strarray.strings[i] = (char*)(*(std::string*)as_array->At(i)).c_str(); - return strarray; -} - - -nvgt_git_repository::nvgt_git_repository() : repo(NULL), index(NULL), ref_count(1) { - if (!libgit2_inited) { - git_libgit2_init(); - libgit2_inited = true; - } -} -void nvgt_git_repository::add_ref() { - asAtomicInc(ref_count); -} -void nvgt_git_repository::release() { - if (asAtomicDec(ref_count) < 1) { - close(); - delete this; - } -} -int nvgt_git_repository::open(const std::string& path) { - if (repo) return GIT_EEXISTS; - int ret = git_repository_open(&repo, path.c_str()); - if (ret == GIT_OK) - git_repository_index(&index, repo); - return ret; -} -int nvgt_git_repository::create(const std::string& path) { - if (repo) return GIT_EEXISTS; - int ret = git_repository_init(&repo, path.c_str(), false); - if (ret == GIT_OK) - git_repository_index(&index, repo); - return ret; -} -bool nvgt_git_repository::close() { - if (!index && !repo) return false; - if (index) git_index_free(index); - if (repo) git_repository_free(repo); - index = NULL; - repo = NULL; - return true; -} -int nvgt_git_repository::add(const std::string& path) { - if (!index) return GIT_ERROR; - return git_index_add_bypath(index, path.c_str()); -} -int nvgt_git_repository::add_all(CScriptArray* paths, int flags) { - if (!index || !paths) return GIT_ERROR; - git_strarray paths_list = as_strarray2git_strarray(paths); - int ret = git_index_add_all(index, &paths_list, flags, nvgt_git_changed_match_callback, repo); - free(paths_list.strings); - return ret; -} -int nvgt_git_repository::add_all_cb(CScriptArray* paths, int flags, asIScriptFunction* match_callback, const std::string& match_callback_payload) { - if (!index || !paths) return GIT_ERROR; - git_strarray paths_list = as_strarray2git_strarray(paths); - this->match_callback = match_callback; - this->match_callback_payload = match_callback_payload; - int ret = git_index_add_all(index, &paths_list, flags, this->match_callback ? nvgt_git_default_match_callback : NULL, this); - match_callback->Release(); - this->match_callback = NULL; - free(paths_list.strings); - return ret; -} -int nvgt_git_repository::remove(const std::string& path) { - if (!index) return GIT_ERROR; - return git_index_remove_bypath(index, path.c_str()); -} -int nvgt_git_repository::remove_all(CScriptArray* paths) { - if (!index || !paths) return GIT_ERROR; - git_strarray paths_list = as_strarray2git_strarray(paths); - int ret = git_index_remove_all(index, &paths_list, nvgt_git_changed_match_callback, repo); - free(paths_list.strings); - return ret; -} -int nvgt_git_repository::remove_all_cb(CScriptArray* paths, asIScriptFunction* match_callback, const std::string& match_callback_payload) { - if (!index || !paths) return GIT_ERROR; - git_strarray paths_list = as_strarray2git_strarray(paths); - this->match_callback = match_callback; - this->match_callback_payload = match_callback_payload; - int ret = git_index_remove_all(index, &paths_list, this->match_callback ? nvgt_git_default_match_callback : NULL, this); - match_callback->Release(); - this->match_callback = NULL; - free(paths_list.strings); - return ret; -} -int nvgt_git_repository::update_all(CScriptArray* paths) { - if (!index || !paths) return GIT_ERROR; - git_strarray paths_list = as_strarray2git_strarray(paths); - int ret = git_index_update_all(index, &paths_list, nvgt_git_changed_match_callback, repo); - free(paths_list.strings); - return ret; -} -int nvgt_git_repository::update_all_cb(CScriptArray* paths, asIScriptFunction* match_callback, const std::string& match_callback_payload) { - if (!index || !paths) return GIT_ERROR; - git_strarray paths_list = as_strarray2git_strarray(paths); - this->match_callback = match_callback; - this->match_callback_payload = match_callback_payload; - int ret = git_index_update_all(index, &paths_list, this->match_callback ? nvgt_git_default_match_callback : NULL, this); - match_callback->Release(); - this->match_callback = NULL; - free(paths_list.strings); - return ret; -} -nvgt_git_repository_commit* nvgt_git_repository::commit(const std::string& author, const std::string& author_email, const std::string& committer, const std::string& committer_email, const std::string& message, const std::string& commit_ref) { - git_oid commit_oid, tree_oid; - git_tree* tree; - git_object* parent = NULL; - git_reference* ref = NULL; - git_signature* signature_author; - git_signature* signature_committer; - int r = git_revparse_ext(&parent, &ref, repo, commit_ref.c_str()); - if (r != GIT_OK && r != GIT_ENOTFOUND) return NULL; - if (git_index_write_tree(&tree_oid, index)) { - git_object_free(parent); - git_reference_free(ref); - return NULL; - } - if (git_index_write(index)) { - git_object_free(parent); - git_reference_free(ref); - return NULL; - } - if (git_tree_lookup(&tree, repo, &tree_oid)) { - git_object_free(parent); - git_reference_free(ref); - return NULL; - } - if (git_signature_now(&signature_author, author.c_str(), author_email.c_str())) { - git_tree_free(tree); - git_object_free(parent); - git_reference_free(ref); - return NULL; - } - if (git_signature_now(&signature_committer, committer.c_str(), committer_email.c_str())) { - git_signature_free(signature_author); - git_tree_free(tree); - git_object_free(parent); - git_reference_free(ref); - return NULL; - } - git_buf clean_message = GIT_BUF_INIT; - git_message_prettify(&clean_message, message.c_str(), false, 0); - r = git_commit_create_v(&commit_oid, repo, commit_ref.c_str(), signature_author, signature_committer, NULL, clean_message.ptr ? clean_message.ptr : message.c_str(), tree, parent ? 1 : 0, parent); - git_commit* commit; - nvgt_git_repository_commit* ret = NULL; - if (!r && !git_commit_lookup(&commit, repo, &commit_oid)) ret = new nvgt_git_repository_commit(commit); - git_signature_free(signature_committer); - git_signature_free(signature_author); - git_tree_free(tree); - git_object_free(parent); - git_reference_free(ref); - if (clean_message.ptr) git_buf_dispose(&clean_message); - return ret; -} -std::string nvgt_git_repository::commit_diff(nvgt_git_repository_commit* commit1, nvgt_git_repository_commit* commit2, unsigned int context_lines, unsigned int interhunk_lines, unsigned int flags, unsigned int format, CScriptArray* pathspec, const std::string& old_prefix, const std::string& new_prefix) { - if (!commit1 || !commit2) return ""; - git_tree* tree1 = NULL, * tree2 = NULL; - if (commit1 && git_commit_tree(&tree1, commit1->commit)) { - if (pathspec) pathspec->Release(); - return ""; - } - if (commit2 && git_commit_tree(&tree2, commit2->commit)) { - if (pathspec) pathspec->Release(); - return ""; - } - git_diff* diff = NULL; - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - if (pathspec && pathspec->GetSize() > 0) - opts.pathspec = as_strarray2git_strarray(pathspec); - opts.flags = flags; - opts.context_lines = context_lines; - opts.interhunk_lines = interhunk_lines; - opts.old_prefix = old_prefix.c_str(); - opts.new_prefix = new_prefix.c_str(); - if (git_diff_tree_to_tree(&diff, repo, tree1, tree2, &opts)) { - if (tree1) git_tree_free(tree1); - git_tree_free(tree2); - if (opts.pathspec.strings) free(opts.pathspec.strings); - return ""; - } - git_buf output = GIT_BUF_INIT; - std::string ret; - if (!git_diff_to_buf(&output, diff, (git_diff_format_t)format)) - ret = std::string(output.ptr, output.size); - git_buf_dispose(&output); - if (tree1) git_tree_free(tree1); - git_tree_free(tree2); - if (opts.pathspec.strings) free(opts.pathspec.strings); - return ret; -} -nvgt_git_repository_commit* nvgt_git_repository::commit_lookup(const std::string& id) { - git_oid oid; - if (git_oid_fromstr(&oid, id.c_str())) return NULL; - git_commit* c; - if (git_commit_lookup(&c, repo, &oid)) return NULL; - return new nvgt_git_repository_commit(c); -} -nvgt_git_repository_commit_iterator* nvgt_git_repository::commit_iterate(CScriptArray* path_filter, const std::string& author_filter, const std::string& message_filter, git_time_t min_time_filter, git_time_t max_time_filter, unsigned int start, unsigned int count) { - git_revwalk* w = NULL; - if (git_revwalk_new(&w, repo)) return NULL; - git_revwalk_push_head(w); - return new nvgt_git_repository_commit_iterator(w, path_filter, author_filter, message_filter, min_time_filter, max_time_filter, start, count); -} -nvgt_git_repository_entry* nvgt_git_repository::get_entry(unsigned int n) { - if (!index) return NULL; - const git_index_entry* entry = git_index_get_byindex(index, n); - if (!entry) return NULL; - return new nvgt_git_repository_entry(entry); -} -nvgt_git_repository_entry* nvgt_git_repository::find_entry(const std::string& path) { - if (!index) return NULL; - size_t pos; - if (git_index_find(&pos, index, path.c_str()) != GIT_OK) return NULL; - return get_entry(pos); -} -int nvgt_git_repository::get_entry_count() { - if (!index) return GIT_ENOTFOUND; - return git_index_entrycount(index); -} -int nvgt_git_repository::get_is_empty() { - if (!repo) return GIT_ENOTFOUND; - return git_repository_is_empty(repo); -} -const std::string nvgt_git_repository::get_path() { - if (!repo) return ""; - const char* p = git_repository_path(repo); - return std::string(p, strlen(p)); -} -const std::string nvgt_git_repository::get_workdir() { - if (!repo) return ""; - const char* p = git_repository_workdir(repo); - return std::string(p, strlen(p)); -} -nvgt_git_repository* new_git_repository() { - return new nvgt_git_repository(); -} - -void nvgt_git_repository_commit::get_signatures() { - if (committer != "") return; - const git_signature* sig_committer = git_commit_committer(commit); - if (sig_committer != NULL) { - committer = sig_committer->name; - committer_email = sig_committer->email; - } - const git_signature* sig_author = git_commit_author(commit); - if (sig_author != NULL) { - author = sig_author->name; - author_email = sig_author->email; - } -} -nvgt_git_repository_commit* nvgt_git_repository_commit::get_parent(int idx) { - git_commit* c = NULL; - if (git_commit_parent(&c, commit, idx) != GIT_OK) return NULL; - return new nvgt_git_repository_commit(c); -} -bool nvgt_git_repository_commit_iterator::next() { - git_oid c_oid; - git_commit* c = NULL; - bool found_commit = false; - while (git_revwalk_next(&c_oid, walker) == GIT_OK) { - if (git_commit_lookup(&c, git_revwalk_repository(walker), &c_oid) != GIT_OK) return false; - if (dopts.pathspec.count) { - int parents = git_commit_parentcount(c); - git_tree* tree1, * tree2; - if (parents == 0) { - if (git_commit_tree(&tree2, c)) continue; - bool skip = false; - skip = git_pathspec_match_tree(NULL, tree2, GIT_PATHSPEC_NO_MATCH_ERROR, pspec); - git_tree_free(tree2); - if (skip) continue; - } else { - int unmatched = parents; - for (int i = 0; i < parents; i++) { - git_commit* parent; - if (git_commit_parent(&parent, c, i)) continue; - git_diff* diff; - if (git_commit_tree(&tree1, parent)) continue; - if (git_commit_tree(&tree2, c)) continue; - if (git_diff_tree_to_tree(&diff, git_commit_owner(c), tree1, tree2, &dopts)) continue; - int deltas = git_diff_num_deltas(diff); - git_diff_free(diff); - git_tree_free(tree1); - git_tree_free(tree2); - git_commit_free(parent); - if (deltas > 0) unmatched--; - } - if (unmatched > 0) continue; - } - } - const git_signature* sig = git_commit_author(c); - bool author_match = true; - if ((min_time_filter > 0 || max_time_filter > 0 || author_filter != "") && !sig) continue; - if (min_time_filter > 0 && sig->when.time < min_time_filter || max_time_filter > 0 && max_time_filter > min_time_filter && sig->when.time > max_time_filter) continue; - if (author_filter != "" && strstr(sig->name, author_filter.c_str()) == NULL && strstr(sig->email, author_filter.c_str()) == NULL) author_match = false; - sig = git_commit_committer(c); - if (!sig && author_filter != "") continue; - if (!author_match && author_filter != "" && strstr(sig->name, author_filter.c_str()) == NULL && strstr(sig->email, author_filter.c_str()) == NULL) continue; - if (message_filter != "" && strstr(git_commit_message(c), message_filter.c_str()) == NULL) continue; - current_entry++; - if (current_entry < start) continue; - if (count > 0 && inserted_entries + 1 > count) continue; - inserted_entries++; - found_commit = true; - break; - } - if (!found_commit) return false; - if (commit) commit->release(); - commit = new nvgt_git_repository_commit(c); - return true; -} - -int git_last_error_class() { - const git_error* e = git_error_last(); - if (!e) return GIT_ERROR_NONE; - return e->klass; -} -const std::string git_last_error_text() { - const git_error* e = git_error_last(); - if (!e) return ""; - return std::string(e->message); -} - - -void RegisterGit(asIScriptEngine* engine) { - engine->SetDefaultAccessMask(NVGT_SUBSYSTEM_GIT); - engine->RegisterObjectType("git_repository", 0, asOBJ_REF); - engine->RegisterFuncdef("int git_repository_match_callback(git_repository@ repo, const string&in path, const string&in user_data)"); - engine->RegisterObjectType("git_repository_entry", 0, asOBJ_REF); - engine->RegisterObjectType("git_repository_commit", 0, asOBJ_REF); - engine->RegisterObjectType("git_repository_commit_iterator", 0, asOBJ_REF); - - engine->RegisterObjectBehaviour("git_repository", asBEHAVE_FACTORY, "git_repository@r()", asFUNCTION(new_git_repository), asCALL_CDECL); - engine->RegisterObjectBehaviour("git_repository", asBEHAVE_ADDREF, "void f()", asMETHOD(nvgt_git_repository, add_ref), asCALL_THISCALL); - engine->RegisterObjectBehaviour("git_repository", asBEHAVE_RELEASE, "void f()", asMETHOD(nvgt_git_repository, release), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository", "int open(const string&in path)", asMETHOD(nvgt_git_repository, open), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository", "int create(const string&in path)", asMETHOD(nvgt_git_repository, create), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository", "bool close()", asMETHOD(nvgt_git_repository, close), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository", "int add(const string&in path)", asMETHOD(nvgt_git_repository, add), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository", "int add_all(string[]@ paths, int flags = 0)", asMETHOD(nvgt_git_repository, add_all), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository", "int add_all(string[]@ paths, int flags, git_repository_match_callback@ callback, const string&in callback_data = \"\")", asMETHOD(nvgt_git_repository, add_all_cb), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository", "int update_all(string[]@ paths)", asMETHOD(nvgt_git_repository, update_all), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository", "int update_all(string[]@ paths, git_repository_match_callback@ callback, const string&in callback_data = \"\")", asMETHOD(nvgt_git_repository, update_all_cb), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository", "int remove(const string&in path)", asMETHOD(nvgt_git_repository, remove), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository", "int remove_all(string[]@ paths)", asMETHOD(nvgt_git_repository, remove_all), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository", "int remove_all(string[]@ paths, git_repository_match_callback@ callback, const string&in callback_data = \"\")", asMETHOD(nvgt_git_repository, remove_all_cb), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository", "git_repository_commit@ commit(const string&in author, const string&in author_email, const string&in message, const string&in ref=\"HEAD\")", asMETHOD(nvgt_git_repository, commit_simple), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository", "git_repository_commit@ commit(const string&in author, const string&in author_email, const string&in committer, const string&in committer_email, const string&in message, const string&in ref=\"HEAD\")", asMETHOD(nvgt_git_repository, commit), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository", "string commit_diff(git_repository_commit@+ commit1, git_repository_commit@+ commit2, uint context_lines=3, uint interhunk_lines=0, uint flags=0, uint format=1, string[]@+ pathspec={}, const string&in old_prefix=\"a\", const string&in new_prefix=\"b\")", asMETHOD(nvgt_git_repository, commit_diff), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository", "git_repository_commit@ commit_lookup(const string&in oid)", asMETHOD(nvgt_git_repository, commit_lookup), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository", "git_repository_commit_iterator@ commit_iterate(string[]@ path_filter={}, const string&in author_filter='', const string&in message_filter='', uint64 min_time_filter=0, uint64 max_time_filter=0, uint start=0, uint count=0)", asMETHOD(nvgt_git_repository, commit_iterate), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository", "git_repository_entry@ get_entry(uint index)", asMETHOD(nvgt_git_repository, get_entry), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository", "int get_entry_count() property", asMETHOD(nvgt_git_repository, get_entry_count), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository", "int get_is_empty() property", asMETHOD(nvgt_git_repository, get_is_empty), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository", "string get_path() property", asMETHOD(nvgt_git_repository, get_path), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository", "string get_workdir() property", asMETHOD(nvgt_git_repository, get_workdir), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository", "bool get_active() property", asMETHOD(nvgt_git_repository, get_active), asCALL_THISCALL); - - engine->RegisterObjectBehaviour("git_repository_entry", asBEHAVE_ADDREF, "void f()", asMETHOD(nvgt_git_repository_entry, add_ref), asCALL_THISCALL); - engine->RegisterObjectBehaviour("git_repository_entry", asBEHAVE_RELEASE, "void f()", asMETHOD(nvgt_git_repository_entry, release), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository_entry", "uint get_ctime() property", asMETHOD(nvgt_git_repository_entry, get_ctime), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository_entry", "uint get_mtime() property", asMETHOD(nvgt_git_repository_entry, get_mtime), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository_entry", "uint get_file_size() property", asMETHOD(nvgt_git_repository_entry, get_file_size), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository_entry", "string get_oid() property", asMETHOD(nvgt_git_repository_entry, get_oid), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository_entry", "string get_path() property", asMETHOD(nvgt_git_repository_entry, get_path), asCALL_THISCALL); - - engine->RegisterObjectBehaviour("git_repository_commit", asBEHAVE_ADDREF, "void f()", asMETHOD(nvgt_git_repository_commit, add_ref), asCALL_THISCALL); - engine->RegisterObjectBehaviour("git_repository_commit", asBEHAVE_RELEASE, "void f()", asMETHOD(nvgt_git_repository_commit, release), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository_commit", "uint get_time() property", asMETHOD(nvgt_git_repository_commit, get_time), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository_commit", "int get_parent_count() property", asMETHOD(nvgt_git_repository_commit, get_parent_count), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository_commit", "git_repository_commit@ get_parent(uint)", asMETHOD(nvgt_git_repository_commit, get_parent), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository_commit", "const string get_id() property", asMETHOD(nvgt_git_repository_commit, get_id), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository_commit", "const string get_message() property", asMETHOD(nvgt_git_repository_commit, get_message), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository_commit", "string get_summary() property", asMETHOD(nvgt_git_repository_commit, get_summary), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository_commit", "const string get_body() property", asMETHOD(nvgt_git_repository_commit, get_body), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository_commit", "const string get_raw_header() property", asMETHOD(nvgt_git_repository_commit, get_raw_header), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository_commit", "const string get_committer() property", asMETHOD(nvgt_git_repository_commit, get_committer), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository_commit", "const string get_committer_email() property", asMETHOD(nvgt_git_repository_commit, get_committer_email), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository_commit", "const string get_author() property", asMETHOD(nvgt_git_repository_commit, get_author), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository_commit", "const string get_author_email() property", asMETHOD(nvgt_git_repository_commit, get_author_email), asCALL_THISCALL); - - engine->RegisterObjectBehaviour("git_repository_commit_iterator", asBEHAVE_ADDREF, "void f()", asMETHOD(nvgt_git_repository_commit_iterator, add_ref), asCALL_THISCALL); - engine->RegisterObjectBehaviour("git_repository_commit_iterator", asBEHAVE_RELEASE, "void f()", asMETHOD(nvgt_git_repository_commit_iterator, release), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository_commit_iterator", "git_repository_commit@ get_commit() property", asMETHOD(nvgt_git_repository_commit_iterator, get_commit), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository_commit_iterator", "git_repository_commit@ opImplCast()", asMETHOD(nvgt_git_repository_commit_iterator, get_commit), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository_commit_iterator", "bool opPostInc()", asMETHOD(nvgt_git_repository_commit_iterator, next), asCALL_THISCALL); - - engine->RegisterGlobalFunction("int git_last_error_class()", asFUNCTION(git_last_error_class), asCALL_CDECL); - engine->RegisterGlobalFunction("string git_last_error_text()", asFUNCTION(git_last_error_text), asCALL_CDECL); -} - -plugin_main(nvgt_plugin_shared* shared) { - prepare_plugin(shared); - g_ScriptEngine = shared->script_engine; - RegisterGit(shared->script_engine); - return true; -} +/* git.cpp - libgit2 wrapper plugin code + * + * NVGT - NonVisual Gaming Toolkit + * Copyright (c) 2022-2024 Sam Tupy + * https://nvgt.gg + * This software is provided "as-is", without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. + * Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: + * 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. +*/ + +#include "../../src/nvgt_plugin.h" +#include "git.h" + +static bool libgit2_inited = false; +static asIScriptEngine* g_ScriptEngine = NULL; + +int nvgt_git_default_match_callback(const char* path, const char* matched, void* payload) { + nvgt_git_repository* repo = (nvgt_git_repository*)payload; + if (!repo->match_callback) return 0; + asIScriptContext* ACtx = asGetActiveContext(); + bool new_context = !ACtx || ACtx->PushState() < 0; + asIScriptContext* ctx = new_context ? g_ScriptEngine->RequestContext() : ACtx; + if (ctx->Prepare(repo->match_callback)) { + if (new_context) g_ScriptEngine->ReturnContext(ctx); + else ctx->PopState(); + return GIT_EUSER; + } + std::string path_str(path, strlen(path)); + std::string matched_str(matched, strlen(path)); + ctx->SetArgObject(1, repo); + ctx->SetArgObject(2, &path_str); + ctx->SetArgObject(3, &repo->match_callback_payload); + int ret = GIT_EUSER; + if (ctx->Execute() == asEXECUTION_FINISHED) + ret = ctx->GetReturnDWord(); + if (new_context) g_ScriptEngine->ReturnContext(ctx); + else ctx->PopState(); + return ret; +} +int nvgt_git_changed_match_callback(const char* path, const char* matched, void* payload) { + git_repository* repo = (git_repository*)payload; + unsigned int status; + if (git_status_file(&status, repo, path) < 0) return -1; + if ((status & GIT_STATUS_WT_MODIFIED) || (status & GIT_STATUS_WT_NEW)) return 0; + return 1; +} +inline git_strarray as_strarray2git_strarray(CScriptArray* as_array) { + git_strarray strarray; + strarray.count = as_array->GetSize(); + strarray.strings = (char**)malloc(strarray.count * sizeof(char*)); + for (int i = 0; i < strarray.count; i++) + strarray.strings[i] = (char*)(*(std::string*)as_array->At(i)).c_str(); + return strarray; +} + + +nvgt_git_repository::nvgt_git_repository() : repo(NULL), index(NULL), ref_count(1) { + if (!libgit2_inited) { + git_libgit2_init(); + libgit2_inited = true; + } +} +void nvgt_git_repository::add_ref() { + asAtomicInc(ref_count); +} +void nvgt_git_repository::release() { + if (asAtomicDec(ref_count) < 1) { + close(); + delete this; + } +} +int nvgt_git_repository::open(const std::string& path) { + if (repo) return GIT_EEXISTS; + int ret = git_repository_open(&repo, path.c_str()); + if (ret == GIT_OK) + git_repository_index(&index, repo); + return ret; +} +int nvgt_git_repository::create(const std::string& path) { + if (repo) return GIT_EEXISTS; + int ret = git_repository_init(&repo, path.c_str(), false); + if (ret == GIT_OK) + git_repository_index(&index, repo); + return ret; +} +bool nvgt_git_repository::close() { + if (!index && !repo) return false; + if (index) git_index_free(index); + if (repo) git_repository_free(repo); + index = NULL; + repo = NULL; + return true; +} +int nvgt_git_repository::add(const std::string& path) { + if (!index) return GIT_ERROR; + return git_index_add_bypath(index, path.c_str()); +} +int nvgt_git_repository::add_all(CScriptArray* paths, int flags) { + if (!index || !paths) return GIT_ERROR; + git_strarray paths_list = as_strarray2git_strarray(paths); + int ret = git_index_add_all(index, &paths_list, flags, nvgt_git_changed_match_callback, repo); + free(paths_list.strings); + return ret; +} +int nvgt_git_repository::add_all_cb(CScriptArray* paths, int flags, asIScriptFunction* match_callback, const std::string& match_callback_payload) { + if (!index || !paths) return GIT_ERROR; + git_strarray paths_list = as_strarray2git_strarray(paths); + this->match_callback = match_callback; + this->match_callback_payload = match_callback_payload; + int ret = git_index_add_all(index, &paths_list, flags, this->match_callback ? nvgt_git_default_match_callback : NULL, this); + match_callback->Release(); + this->match_callback = NULL; + free(paths_list.strings); + return ret; +} +int nvgt_git_repository::remove(const std::string& path) { + if (!index) return GIT_ERROR; + return git_index_remove_bypath(index, path.c_str()); +} +int nvgt_git_repository::remove_all(CScriptArray* paths) { + if (!index || !paths) return GIT_ERROR; + git_strarray paths_list = as_strarray2git_strarray(paths); + int ret = git_index_remove_all(index, &paths_list, nvgt_git_changed_match_callback, repo); + free(paths_list.strings); + return ret; +} +int nvgt_git_repository::remove_all_cb(CScriptArray* paths, asIScriptFunction* match_callback, const std::string& match_callback_payload) { + if (!index || !paths) return GIT_ERROR; + git_strarray paths_list = as_strarray2git_strarray(paths); + this->match_callback = match_callback; + this->match_callback_payload = match_callback_payload; + int ret = git_index_remove_all(index, &paths_list, this->match_callback ? nvgt_git_default_match_callback : NULL, this); + match_callback->Release(); + this->match_callback = NULL; + free(paths_list.strings); + return ret; +} +int nvgt_git_repository::update_all(CScriptArray* paths) { + if (!index || !paths) return GIT_ERROR; + git_strarray paths_list = as_strarray2git_strarray(paths); + int ret = git_index_update_all(index, &paths_list, nvgt_git_changed_match_callback, repo); + free(paths_list.strings); + return ret; +} +int nvgt_git_repository::update_all_cb(CScriptArray* paths, asIScriptFunction* match_callback, const std::string& match_callback_payload) { + if (!index || !paths) return GIT_ERROR; + git_strarray paths_list = as_strarray2git_strarray(paths); + this->match_callback = match_callback; + this->match_callback_payload = match_callback_payload; + int ret = git_index_update_all(index, &paths_list, this->match_callback ? nvgt_git_default_match_callback : NULL, this); + match_callback->Release(); + this->match_callback = NULL; + free(paths_list.strings); + return ret; +} +nvgt_git_repository_commit* nvgt_git_repository::commit(const std::string& author, const std::string& author_email, const std::string& committer, const std::string& committer_email, const std::string& message, const std::string& commit_ref) { + git_oid commit_oid, tree_oid; + git_tree* tree; + git_object* parent = NULL; + git_reference* ref = NULL; + git_signature* signature_author; + git_signature* signature_committer; + int r = git_revparse_ext(&parent, &ref, repo, commit_ref.c_str()); + if (r != GIT_OK && r != GIT_ENOTFOUND) return NULL; + if (git_index_write_tree(&tree_oid, index)) { + git_object_free(parent); + git_reference_free(ref); + return NULL; + } + if (git_index_write(index)) { + git_object_free(parent); + git_reference_free(ref); + return NULL; + } + if (git_tree_lookup(&tree, repo, &tree_oid)) { + git_object_free(parent); + git_reference_free(ref); + return NULL; + } + if (git_signature_now(&signature_author, author.c_str(), author_email.c_str())) { + git_tree_free(tree); + git_object_free(parent); + git_reference_free(ref); + return NULL; + } + if (git_signature_now(&signature_committer, committer.c_str(), committer_email.c_str())) { + git_signature_free(signature_author); + git_tree_free(tree); + git_object_free(parent); + git_reference_free(ref); + return NULL; + } + git_buf clean_message = GIT_BUF_INIT; + git_message_prettify(&clean_message, message.c_str(), false, 0); + r = git_commit_create_v(&commit_oid, repo, commit_ref.c_str(), signature_author, signature_committer, NULL, clean_message.ptr ? clean_message.ptr : message.c_str(), tree, parent ? 1 : 0, parent); + git_commit* commit; + nvgt_git_repository_commit* ret = NULL; + if (!r && !git_commit_lookup(&commit, repo, &commit_oid)) ret = new nvgt_git_repository_commit(commit); + git_signature_free(signature_committer); + git_signature_free(signature_author); + git_tree_free(tree); + git_object_free(parent); + git_reference_free(ref); + if (clean_message.ptr) git_buf_dispose(&clean_message); + return ret; +} +std::string nvgt_git_repository::commit_diff(nvgt_git_repository_commit* commit1, nvgt_git_repository_commit* commit2, unsigned int context_lines, unsigned int interhunk_lines, unsigned int flags, unsigned int format, CScriptArray* pathspec, const std::string& old_prefix, const std::string& new_prefix) { + if (!commit1 || !commit2) return ""; + git_tree* tree1 = NULL, * tree2 = NULL; + if (commit1 && git_commit_tree(&tree1, commit1->commit)) { + if (pathspec) pathspec->Release(); + return ""; + } + if (commit2 && git_commit_tree(&tree2, commit2->commit)) { + if (pathspec) pathspec->Release(); + return ""; + } + git_diff* diff = NULL; + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + if (pathspec && pathspec->GetSize() > 0) + opts.pathspec = as_strarray2git_strarray(pathspec); + opts.flags = flags; + opts.context_lines = context_lines; + opts.interhunk_lines = interhunk_lines; + opts.old_prefix = old_prefix.c_str(); + opts.new_prefix = new_prefix.c_str(); + if (git_diff_tree_to_tree(&diff, repo, tree1, tree2, &opts)) { + if (tree1) git_tree_free(tree1); + git_tree_free(tree2); + if (opts.pathspec.strings) free(opts.pathspec.strings); + return ""; + } + git_buf output = GIT_BUF_INIT; + std::string ret; + if (!git_diff_to_buf(&output, diff, (git_diff_format_t)format)) + ret = std::string(output.ptr, output.size); + git_buf_dispose(&output); + if (tree1) git_tree_free(tree1); + git_tree_free(tree2); + if (opts.pathspec.strings) free(opts.pathspec.strings); + return ret; +} +nvgt_git_repository_commit* nvgt_git_repository::commit_lookup(const std::string& id) { + git_oid oid; + if (git_oid_fromstr(&oid, id.c_str())) return NULL; + git_commit* c; + if (git_commit_lookup(&c, repo, &oid)) return NULL; + return new nvgt_git_repository_commit(c); +} +nvgt_git_repository_commit_iterator* nvgt_git_repository::commit_iterate(CScriptArray* path_filter, const std::string& author_filter, const std::string& message_filter, git_time_t min_time_filter, git_time_t max_time_filter, unsigned int start, unsigned int count) { + git_revwalk* w = NULL; + if (git_revwalk_new(&w, repo)) return NULL; + git_revwalk_push_head(w); + return new nvgt_git_repository_commit_iterator(w, path_filter, author_filter, message_filter, min_time_filter, max_time_filter, start, count); +} +nvgt_git_repository_entry* nvgt_git_repository::get_entry(unsigned int n) { + if (!index) return NULL; + const git_index_entry* entry = git_index_get_byindex(index, n); + if (!entry) return NULL; + return new nvgt_git_repository_entry(entry); +} +nvgt_git_repository_entry* nvgt_git_repository::find_entry(const std::string& path) { + if (!index) return NULL; + size_t pos; + if (git_index_find(&pos, index, path.c_str()) != GIT_OK) return NULL; + return get_entry(pos); +} +int nvgt_git_repository::get_entry_count() { + if (!index) return GIT_ENOTFOUND; + return git_index_entrycount(index); +} +int nvgt_git_repository::get_is_empty() { + if (!repo) return GIT_ENOTFOUND; + return git_repository_is_empty(repo); +} +const std::string nvgt_git_repository::get_path() { + if (!repo) return ""; + const char* p = git_repository_path(repo); + return std::string(p, strlen(p)); +} +const std::string nvgt_git_repository::get_workdir() { + if (!repo) return ""; + const char* p = git_repository_workdir(repo); + return std::string(p, strlen(p)); +} +nvgt_git_repository* new_git_repository() { + return new nvgt_git_repository(); +} + +void nvgt_git_repository_commit::get_signatures() { + if (committer != "") return; + const git_signature* sig_committer = git_commit_committer(commit); + if (sig_committer != NULL) { + committer = sig_committer->name; + committer_email = sig_committer->email; + } + const git_signature* sig_author = git_commit_author(commit); + if (sig_author != NULL) { + author = sig_author->name; + author_email = sig_author->email; + } +} +nvgt_git_repository_commit* nvgt_git_repository_commit::get_parent(int idx) { + git_commit* c = NULL; + if (git_commit_parent(&c, commit, idx) != GIT_OK) return NULL; + return new nvgt_git_repository_commit(c); +} +bool nvgt_git_repository_commit_iterator::next() { + git_oid c_oid; + git_commit* c = NULL; + bool found_commit = false; + while (git_revwalk_next(&c_oid, walker) == GIT_OK) { + if (git_commit_lookup(&c, git_revwalk_repository(walker), &c_oid) != GIT_OK) return false; + if (dopts.pathspec.count) { + int parents = git_commit_parentcount(c); + git_tree* tree1, * tree2; + if (parents == 0) { + if (git_commit_tree(&tree2, c)) continue; + bool skip = false; + skip = git_pathspec_match_tree(NULL, tree2, GIT_PATHSPEC_NO_MATCH_ERROR, pspec); + git_tree_free(tree2); + if (skip) continue; + } else { + int unmatched = parents; + for (int i = 0; i < parents; i++) { + git_commit* parent; + if (git_commit_parent(&parent, c, i)) continue; + git_diff* diff; + if (git_commit_tree(&tree1, parent)) continue; + if (git_commit_tree(&tree2, c)) continue; + if (git_diff_tree_to_tree(&diff, git_commit_owner(c), tree1, tree2, &dopts)) continue; + int deltas = git_diff_num_deltas(diff); + git_diff_free(diff); + git_tree_free(tree1); + git_tree_free(tree2); + git_commit_free(parent); + if (deltas > 0) unmatched--; + } + if (unmatched > 0) continue; + } + } + const git_signature* sig = git_commit_author(c); + bool author_match = true; + if ((min_time_filter > 0 || max_time_filter > 0 || author_filter != "") && !sig) continue; + if (min_time_filter > 0 && sig->when.time < min_time_filter || max_time_filter > 0 && max_time_filter > min_time_filter && sig->when.time > max_time_filter) continue; + if (author_filter != "" && strstr(sig->name, author_filter.c_str()) == NULL && strstr(sig->email, author_filter.c_str()) == NULL) author_match = false; + sig = git_commit_committer(c); + if (!sig && author_filter != "") continue; + if (!author_match && author_filter != "" && strstr(sig->name, author_filter.c_str()) == NULL && strstr(sig->email, author_filter.c_str()) == NULL) continue; + if (message_filter != "" && strstr(git_commit_message(c), message_filter.c_str()) == NULL) continue; + current_entry++; + if (current_entry < start) continue; + if (count > 0 && inserted_entries + 1 > count) continue; + inserted_entries++; + found_commit = true; + break; + } + if (!found_commit) return false; + if (commit) commit->release(); + commit = new nvgt_git_repository_commit(c); + return true; +} + +int git_last_error_class() { + const git_error* e = git_error_last(); + if (!e) return GIT_ERROR_NONE; + return e->klass; +} +const std::string git_last_error_text() { + const git_error* e = git_error_last(); + if (!e) return ""; + return std::string(e->message); +} + + +void RegisterGit(asIScriptEngine* engine) { + engine->SetDefaultAccessMask(NVGT_SUBSYSTEM_GIT); + engine->RegisterObjectType("git_repository", 0, asOBJ_REF); + engine->RegisterFuncdef("int git_repository_match_callback(git_repository@ repo, const string&in path, const string&in user_data)"); + engine->RegisterObjectType("git_repository_entry", 0, asOBJ_REF); + engine->RegisterObjectType("git_repository_commit", 0, asOBJ_REF); + engine->RegisterObjectType("git_repository_commit_iterator", 0, asOBJ_REF); + + engine->RegisterObjectBehaviour("git_repository", asBEHAVE_FACTORY, "git_repository@r()", asFUNCTION(new_git_repository), asCALL_CDECL); + engine->RegisterObjectBehaviour("git_repository", asBEHAVE_ADDREF, "void f()", asMETHOD(nvgt_git_repository, add_ref), asCALL_THISCALL); + engine->RegisterObjectBehaviour("git_repository", asBEHAVE_RELEASE, "void f()", asMETHOD(nvgt_git_repository, release), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository", "int open(const string&in path)", asMETHOD(nvgt_git_repository, open), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository", "int create(const string&in path)", asMETHOD(nvgt_git_repository, create), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository", "bool close()", asMETHOD(nvgt_git_repository, close), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository", "int add(const string&in path)", asMETHOD(nvgt_git_repository, add), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository", "int add_all(string[]@ paths, int flags = 0)", asMETHOD(nvgt_git_repository, add_all), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository", "int add_all(string[]@ paths, int flags, git_repository_match_callback@ callback, const string&in callback_data = \"\")", asMETHOD(nvgt_git_repository, add_all_cb), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository", "int update_all(string[]@ paths)", asMETHOD(nvgt_git_repository, update_all), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository", "int update_all(string[]@ paths, git_repository_match_callback@ callback, const string&in callback_data = \"\")", asMETHOD(nvgt_git_repository, update_all_cb), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository", "int remove(const string&in path)", asMETHOD(nvgt_git_repository, remove), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository", "int remove_all(string[]@ paths)", asMETHOD(nvgt_git_repository, remove_all), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository", "int remove_all(string[]@ paths, git_repository_match_callback@ callback, const string&in callback_data = \"\")", asMETHOD(nvgt_git_repository, remove_all_cb), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository", "git_repository_commit@ commit(const string&in author, const string&in author_email, const string&in message, const string&in ref=\"HEAD\")", asMETHOD(nvgt_git_repository, commit_simple), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository", "git_repository_commit@ commit(const string&in author, const string&in author_email, const string&in committer, const string&in committer_email, const string&in message, const string&in ref=\"HEAD\")", asMETHOD(nvgt_git_repository, commit), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository", "string commit_diff(git_repository_commit@+ commit1, git_repository_commit@+ commit2, uint context_lines=3, uint interhunk_lines=0, uint flags=0, uint format=1, string[]@+ pathspec={}, const string&in old_prefix=\"a\", const string&in new_prefix=\"b\")", asMETHOD(nvgt_git_repository, commit_diff), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository", "git_repository_commit@ commit_lookup(const string&in oid)", asMETHOD(nvgt_git_repository, commit_lookup), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository", "git_repository_commit_iterator@ commit_iterate(string[]@ path_filter={}, const string&in author_filter='', const string&in message_filter='', uint64 min_time_filter=0, uint64 max_time_filter=0, uint start=0, uint count=0)", asMETHOD(nvgt_git_repository, commit_iterate), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository", "git_repository_entry@ get_entry(uint index)", asMETHOD(nvgt_git_repository, get_entry), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository", "int get_entry_count() property", asMETHOD(nvgt_git_repository, get_entry_count), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository", "int get_is_empty() property", asMETHOD(nvgt_git_repository, get_is_empty), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository", "string get_path() property", asMETHOD(nvgt_git_repository, get_path), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository", "string get_workdir() property", asMETHOD(nvgt_git_repository, get_workdir), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository", "bool get_active() property", asMETHOD(nvgt_git_repository, get_active), asCALL_THISCALL); + + engine->RegisterObjectBehaviour("git_repository_entry", asBEHAVE_ADDREF, "void f()", asMETHOD(nvgt_git_repository_entry, add_ref), asCALL_THISCALL); + engine->RegisterObjectBehaviour("git_repository_entry", asBEHAVE_RELEASE, "void f()", asMETHOD(nvgt_git_repository_entry, release), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository_entry", "uint get_ctime() property", asMETHOD(nvgt_git_repository_entry, get_ctime), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository_entry", "uint get_mtime() property", asMETHOD(nvgt_git_repository_entry, get_mtime), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository_entry", "uint get_file_size() property", asMETHOD(nvgt_git_repository_entry, get_file_size), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository_entry", "string get_oid() property", asMETHOD(nvgt_git_repository_entry, get_oid), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository_entry", "string get_path() property", asMETHOD(nvgt_git_repository_entry, get_path), asCALL_THISCALL); + + engine->RegisterObjectBehaviour("git_repository_commit", asBEHAVE_ADDREF, "void f()", asMETHOD(nvgt_git_repository_commit, add_ref), asCALL_THISCALL); + engine->RegisterObjectBehaviour("git_repository_commit", asBEHAVE_RELEASE, "void f()", asMETHOD(nvgt_git_repository_commit, release), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository_commit", "uint get_time() property", asMETHOD(nvgt_git_repository_commit, get_time), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository_commit", "int get_parent_count() property", asMETHOD(nvgt_git_repository_commit, get_parent_count), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository_commit", "git_repository_commit@ get_parent(uint)", asMETHOD(nvgt_git_repository_commit, get_parent), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository_commit", "const string get_id() property", asMETHOD(nvgt_git_repository_commit, get_id), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository_commit", "const string get_message() property", asMETHOD(nvgt_git_repository_commit, get_message), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository_commit", "string get_summary() property", asMETHOD(nvgt_git_repository_commit, get_summary), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository_commit", "const string get_body() property", asMETHOD(nvgt_git_repository_commit, get_body), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository_commit", "const string get_raw_header() property", asMETHOD(nvgt_git_repository_commit, get_raw_header), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository_commit", "const string get_committer() property", asMETHOD(nvgt_git_repository_commit, get_committer), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository_commit", "const string get_committer_email() property", asMETHOD(nvgt_git_repository_commit, get_committer_email), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository_commit", "const string get_author() property", asMETHOD(nvgt_git_repository_commit, get_author), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository_commit", "const string get_author_email() property", asMETHOD(nvgt_git_repository_commit, get_author_email), asCALL_THISCALL); + + engine->RegisterObjectBehaviour("git_repository_commit_iterator", asBEHAVE_ADDREF, "void f()", asMETHOD(nvgt_git_repository_commit_iterator, add_ref), asCALL_THISCALL); + engine->RegisterObjectBehaviour("git_repository_commit_iterator", asBEHAVE_RELEASE, "void f()", asMETHOD(nvgt_git_repository_commit_iterator, release), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository_commit_iterator", "git_repository_commit@ get_commit() property", asMETHOD(nvgt_git_repository_commit_iterator, get_commit), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository_commit_iterator", "git_repository_commit@ opImplCast()", asMETHOD(nvgt_git_repository_commit_iterator, get_commit), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository_commit_iterator", "bool opPostInc()", asMETHOD(nvgt_git_repository_commit_iterator, next), asCALL_THISCALL); + + engine->RegisterGlobalFunction("int git_last_error_class()", asFUNCTION(git_last_error_class), asCALL_CDECL); + engine->RegisterGlobalFunction("string git_last_error_text()", asFUNCTION(git_last_error_text), asCALL_CDECL); +} + +plugin_main(nvgt_plugin_shared* shared) { + prepare_plugin(shared); + g_ScriptEngine = shared->script_engine; + RegisterGit(shared->script_engine); + return true; +} diff --git a/plugin/sqlite/_SConscript b/plugin/sqlite/_SConscript index 9af10e3a..07173f09 100644 --- a/plugin/sqlite/_SConscript +++ b/plugin/sqlite/_SConscript @@ -1,10 +1,10 @@ -Import("env") - -scriptarray = env.SharedObject("scriptarray", "#ASAddon/plugin/scriptarray.cpp") -sqlite_addons = env.SharedObject(Glob("*.c"), CPPDEFINES = ["SQLITE_CORE"]) -if ARGUMENTS.get("no_shared_plugins", "0") == "0": - env.SharedLibrary("#release/lib/nvgt_sqlite", ["nvgt_sqlite.cpp", "zlib_uncompr/uncompr.c", scriptarray, sqlite_addons], LIBS = ["PocoFoundationMT", "PocoDataSQLiteMT"] if env["PLATFORM"] == "win32" else ["PocoFoundation", "PocoDataSQLite"]) -static = env.Object("nvgt_sqlite_static", "nvgt_sqlite.cpp", CPPDEFINES = [("NVGT_PLUGIN_STATIC", "nvgt_sqlite")]) -static = env.StaticLibrary("#build/lib/nvgt_sqlite", [static, sqlite_addons]) -static = [static, "PocoDataSQLiteMT" if env["PLATFORM"] == "win32" else "PocoDataSQLite"] -Return("static") +Import("env") + +scriptarray = env.SharedObject("scriptarray", "#ASAddon/plugin/scriptarray.cpp") +sqlite_addons = env.SharedObject(Glob("*.c"), CPPDEFINES = ["SQLITE_CORE"]) +if ARGUMENTS.get("no_shared_plugins", "0") == "0": + env.SharedLibrary("#release/lib/nvgt_sqlite", ["nvgt_sqlite.cpp", "zlib_uncompr/uncompr.c", scriptarray, sqlite_addons], LIBS = ["PocoFoundationMT", "PocoDataSQLiteMT"] if env["PLATFORM"] == "win32" else ["PocoFoundation", "PocoDataSQLite"]) +static = env.Object("nvgt_sqlite_static", "nvgt_sqlite.cpp", CPPDEFINES = [("NVGT_PLUGIN_STATIC", "nvgt_sqlite")]) +static = env.StaticLibrary("#build/lib/nvgt_sqlite", [static, sqlite_addons]) +static = [static, "PocoDataSQLiteMT" if env["PLATFORM"] == "win32" else "PocoDataSQLite"] +Return("static") diff --git a/plugin/systemd_notify/_SConscript b/plugin/systemd_notify/_SConscript index e16803b5..c8276e9d 100644 --- a/plugin/systemd_notify/_SConscript +++ b/plugin/systemd_notify/_SConscript @@ -1,13 +1,13 @@ -Import("env") - -systemd_avail = False -conf = Configure(env, log_file = "#build/config.log") -if conf.CheckLib("systemd"): systemd_avail = True -env = conf.Finish() - -if ARGUMENTS.get("no_shared_plugins", "0") == "0": - env.SharedLibrary("#release/lib/systemd_notify", ["sd_notify.cpp"], LIBS = ["systemd"] if systemd_avail else []) -static = env.Object("sd_notify_static", "sd_notify.cpp", CPPDEFINES = [("NVGT_PLUGIN_STATIC", "systemd_notify")]) -static = env.StaticLibrary("#build/lib/systemd_notify", [static]) -if systemd_avail: static = [static, "systemd"] -Return("static") +Import("env") + +systemd_avail = False +conf = Configure(env, log_file = "#build/config.log") +if conf.CheckLib("systemd"): systemd_avail = True +env = conf.Finish() + +if ARGUMENTS.get("no_shared_plugins", "0") == "0": + env.SharedLibrary("#release/lib/systemd_notify", ["sd_notify.cpp"], LIBS = ["systemd"] if systemd_avail else []) +static = env.Object("sd_notify_static", "sd_notify.cpp", CPPDEFINES = [("NVGT_PLUGIN_STATIC", "systemd_notify")]) +static = env.StaticLibrary("#build/lib/systemd_notify", [static]) +if systemd_avail: static = [static, "systemd"] +Return("static") diff --git a/plugin/systemd_notify/sd_notify.cpp b/plugin/systemd_notify/sd_notify.cpp index 9cd5e47d..6af0e96f 100644 --- a/plugin/systemd_notify/sd_notify.cpp +++ b/plugin/systemd_notify/sd_notify.cpp @@ -1,20 +1,20 @@ -#include -#include "../../src/nvgt_plugin.h" -#if __has_include() -#include -#define systemd_available -#endif - -int systemd_notify(const std::string& state) { - #ifdef systemd_available - return sd_notify(0, state.c_str()); - #else - return 0; - #endif -} - -plugin_main(nvgt_plugin_shared* shared) { - prepare_plugin(shared); - shared->script_engine->RegisterGlobalFunction("int systemd_notify(const string&in state)", asFUNCTION(systemd_notify), asCALL_CDECL); - return true; -} +#include +#include "../../src/nvgt_plugin.h" +#if __has_include() +#include +#define systemd_available +#endif + +int systemd_notify(const std::string& state) { + #ifdef systemd_available + return sd_notify(0, state.c_str()); + #else + return 0; + #endif +} + +plugin_main(nvgt_plugin_shared* shared) { + prepare_plugin(shared); + shared->script_engine->RegisterGlobalFunction("int systemd_notify(const string&in state)", asFUNCTION(systemd_notify), asCALL_CDECL); + return true; +} diff --git a/test/interact/lowlevel_touch.nvgt b/test/interact/lowlevel_touch.nvgt index 2ed15807..1f842250 100644 --- a/test/interact/lowlevel_touch.nvgt +++ b/test/interact/lowlevel_touch.nvgt @@ -1,24 +1,24 @@ -// NonVisual Gaming Toolkit (NVGT) -// Copyright (C) 2022-2024 Sam Tupy -// License: zlib (see license.md in the root of the NVGT distribution) - -void main() { - show_window("touch test"); -tts_voice tts; - uint64[]@ devs = get_touch_devices(); - if (devs.length() < 1) { - alert("oops", "no touch devices available"); - return; - } - int prev_finger_count = 0; - tts.speak("ready", true); - while (!key_pressed(KEY_ESCAPE) and !key_pressed(KEY_AC_BACK)) { - wait(5); - touch_finger[]@ fingers = query_touch_device(); - if (fingers.length() != prev_finger_count) { - tts.speak(fingers.length() + " fingers", true); - if (fingers.length() > 0) tts.speak(round(fingers[-1].x, 2) + ", " + round(fingers[-1].y, 2)); - prev_finger_count = fingers.length(); - } - } -} +// NonVisual Gaming Toolkit (NVGT) +// Copyright (C) 2022-2024 Sam Tupy +// License: zlib (see license.md in the root of the NVGT distribution) + +void main() { + show_window("touch test"); +tts_voice tts; + uint64[]@ devs = get_touch_devices(); + if (devs.length() < 1) { + alert("oops", "no touch devices available"); + return; + } + int prev_finger_count = 0; + tts.speak("ready", true); + while (!key_pressed(KEY_ESCAPE) and !key_pressed(KEY_AC_BACK)) { + wait(5); + touch_finger[]@ fingers = query_touch_device(); + if (fingers.length() != prev_finger_count) { + tts.speak(fingers.length() + " fingers", true); + if (fingers.length() > 0) tts.speak(round(fingers[-1].x, 2) + ", " + round(fingers[-1].y, 2)); + prev_finger_count = fingers.length(); + } + } +} From 789700bb9d90fbc156f40ee8726f70511c2f4dcc Mon Sep 17 00:00:00 2001 From: Ethin Probst Date: Sun, 29 Sep 2024 10:14:52 -0500 Subject: [PATCH 2/4] Revert "Push because git asked me to" This reverts commit 14246c9a6aa5afc99fc3e67c76e96e0c5588a010. --- .gitignore | 100 +- ASAddon/include/scriptarray.h | 288 +- ASAddon/plugin/readme.md | 18 +- ASAddon/plugin/scriptany.cpp | 6 +- ASAddon/plugin/scriptarray.cpp | 6 +- ASAddon/plugin/scriptdictionary.cpp | 6 +- ASAddon/plugin/scriptgrid.cpp | 6 +- ASAddon/plugin/scriptmathcomplex.cpp | 6 +- ASAddon/src/scriptarray.cpp | 4540 ++++++++--------- ASAddon/src/scripthelper.cpp | 2056 ++++---- build/build_linux.sh | 310 +- build/build_macos.sh | 236 +- doc/src/advanced/Plugin Creation.md | 374 +- .../main/java/org/libsdl/app/SDLSurface.java | 906 ++-- plugin/curl/_SConscript | 30 +- plugin/git/_SConscript | 24 +- plugin/git/git.cpp | 900 ++-- plugin/sqlite/_SConscript | 20 +- plugin/systemd_notify/_SConscript | 26 +- plugin/systemd_notify/sd_notify.cpp | 40 +- test/interact/lowlevel_touch.nvgt | 48 +- 21 files changed, 4973 insertions(+), 4973 deletions(-) diff --git a/.gitignore b/.gitignore index 1e0971c2..cfc3ffbd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,50 +1,50 @@ -# build/binary artifacts -*.exe -*.dll -*.bin -*.obj -*.pdb -*.exp -*.lib -*.a -*.o -*.os -*.dylib -__pycache__ - -# other intermediet build -build/config.log -build/lastbuild -build/windev_path -src/version.cpp -.sconf_temp -.sconsign.dblite -*.orig -jni/.gradle -jni/build -install/nvgt_version.nsh -libs -obj -release/lib/3rd_party_licenses.html -release/lib_* - -# private user space and dependencies -user/** -!user/readme.md -jni/Custom.mk -droidev -iosdev -lindev -macosdev -windev -deps - -# website builds -web/public_html -web/src/docs - -# Files generated by operating systems that we don't want committed. -.DS_Store - -# Compilation databases; easy to generate -compile_commands.json +# build/binary artifacts +*.exe +*.dll +*.bin +*.obj +*.pdb +*.exp +*.lib +*.a +*.o +*.os +*.dylib +__pycache__ + +# other intermediet build +build/config.log +build/lastbuild +build/windev_path +src/version.cpp +.sconf_temp +.sconsign.dblite +*.orig +jni/.gradle +jni/build +install/nvgt_version.nsh +libs +obj +release/lib/3rd_party_licenses.html +release/lib_* + +# private user space and dependencies +user/** +!user/readme.md +jni/Custom.mk +droidev +iosdev +lindev +macosdev +windev +deps + +# website builds +web/public_html +web/src/docs + +# Files generated by operating systems that we don't want committed. +.DS_Store + +# Compilation databases; easy to generate +compile_commands.json diff --git a/ASAddon/include/scriptarray.h b/ASAddon/include/scriptarray.h index 90080c59..babfd3aa 100644 --- a/ASAddon/include/scriptarray.h +++ b/ASAddon/include/scriptarray.h @@ -1,144 +1,144 @@ -#ifndef SCRIPTARRAY_H -#define SCRIPTARRAY_H - -#ifndef ANGELSCRIPT_H -// Avoid having to inform include path if header is already include before -#include -#endif - -// Sometimes it may be desired to use the same method names as used by C++ STL. -// This may for example reduce time when converting code from script to C++ or -// back. -// -// 0 = off -// 1 = on -#ifndef AS_USE_STLNAMES -#define AS_USE_STLNAMES 0 -#endif - -// Some prefer to use property accessors to get/set the length of the array -// This option registers the accessors instead of the method length() -#ifndef AS_USE_ACCESSORS -#define AS_USE_ACCESSORS 0 -#endif - -BEGIN_AS_NAMESPACE - -struct SArrayBuffer; -struct SArrayCache; - -class CScriptArray -{ -public: - // Set the memory functions that should be used by all CScriptArrays - static void SetMemoryFunctions(asALLOCFUNC_t allocFunc, asFREEFUNC_t freeFunc); - - // Factory functions - static CScriptArray *Create(asITypeInfo *ot); - static CScriptArray *Create(asITypeInfo *ot, asUINT length); - static CScriptArray *Create(asITypeInfo *ot, asUINT length, void *defaultValue); - static CScriptArray *Create(asITypeInfo *ot, void *listBuffer); - - // Memory management - void AddRef() const; - void Release() const; - - // Type information - asITypeInfo *GetArrayObjectType() const; - int GetArrayTypeId() const; - int GetElementTypeId() const; - - // Get the current size - asUINT GetSize() const; - - // Returns true if the array is empty - bool IsEmpty() const; - - // Pre-allocates memory for elements - void Reserve(asUINT maxElements); - - // Resize the array - void Resize(asUINT numElements); - - // Get a pointer to an element. Returns 0 if out of bounds - void *At(asINT64 index); - const void *At(asINT64 index) const; - - // Set value of an element. - // The value arg should be a pointer to the value that will be copied to the element. - // Remember, if the array holds handles the value parameter should be the - // address of the handle. The refCount of the object will also be incremented - void SetValue(asINT64 index, void *value); - - // Copy the contents of one array to another (only if the types are the same) - CScriptArray &operator=(const CScriptArray&); - - // Compare two arrays - bool operator==(const CScriptArray &) const; - - // Array manipulation - void InsertAt(asUINT index, void *value); - void InsertAt(asUINT index, const CScriptArray &arr); - void InsertLast(void *value); - void InsertLast(const CScriptArray &arr); - void RemoveAt(asUINT index); - void RemoveLast(); - void RemoveRange(asUINT start, asUINT count); - void SortAsc(); - void SortDesc(); - void SortAsc(asUINT startAt, asUINT count); - void SortDesc(asUINT startAt, asUINT count); - void Sort(asUINT startAt, asUINT count, bool asc); - void Sort(asIScriptFunction *less, asUINT startAt, asUINT count); - void Reverse(); - int Find(void *value) const; - int Find(asUINT startAt, void *value) const; - int FindByRef(void *ref) const; - int FindByRef(asUINT startAt, void *ref) const; - - // Return the address of internal buffer for direct manipulation of elements - void *GetBuffer(); - - // GC methods - int GetRefCount(); - void SetFlag(); - bool GetFlag(); - void EnumReferences(asIScriptEngine *engine); - void ReleaseAllHandles(asIScriptEngine *engine); - -protected: - mutable int refCount; - mutable bool gcFlag; - asITypeInfo *objType; - SArrayBuffer *buffer; - int elementSize; - int subTypeId; - - // Constructors - CScriptArray(asITypeInfo *ot, void *initBuf); // Called from script when initialized with list - CScriptArray(asUINT length, asITypeInfo *ot); - CScriptArray(asUINT length, void *defVal, asITypeInfo *ot); - CScriptArray(const CScriptArray &other); - virtual ~CScriptArray(); - - bool Less(const void *a, const void *b, bool asc); - void *GetArrayItemPointer(int index); - void *GetDataPointer(void *buffer); - void Copy(void *dst, void *src); - void Swap(void *a, void *b); - void Precache(); - bool CheckMaxSize(asUINT numElements); - void Resize(int delta, asUINT at); - void CreateBuffer(SArrayBuffer **buf, asUINT numElements); - void DeleteBuffer(SArrayBuffer *buf); - void CopyBuffer(SArrayBuffer *dst, SArrayBuffer *src); - void Construct(SArrayBuffer *buf, asUINT start, asUINT end); - void Destruct(SArrayBuffer *buf, asUINT start, asUINT end); - bool Equals(const void *a, const void *b, asIScriptContext *ctx, SArrayCache *cache) const; -}; - -void RegisterScriptArray(asIScriptEngine *engine, bool defaultArray); - -END_AS_NAMESPACE - -#endif +#ifndef SCRIPTARRAY_H +#define SCRIPTARRAY_H + +#ifndef ANGELSCRIPT_H +// Avoid having to inform include path if header is already include before +#include +#endif + +// Sometimes it may be desired to use the same method names as used by C++ STL. +// This may for example reduce time when converting code from script to C++ or +// back. +// +// 0 = off +// 1 = on +#ifndef AS_USE_STLNAMES +#define AS_USE_STLNAMES 0 +#endif + +// Some prefer to use property accessors to get/set the length of the array +// This option registers the accessors instead of the method length() +#ifndef AS_USE_ACCESSORS +#define AS_USE_ACCESSORS 0 +#endif + +BEGIN_AS_NAMESPACE + +struct SArrayBuffer; +struct SArrayCache; + +class CScriptArray +{ +public: + // Set the memory functions that should be used by all CScriptArrays + static void SetMemoryFunctions(asALLOCFUNC_t allocFunc, asFREEFUNC_t freeFunc); + + // Factory functions + static CScriptArray *Create(asITypeInfo *ot); + static CScriptArray *Create(asITypeInfo *ot, asUINT length); + static CScriptArray *Create(asITypeInfo *ot, asUINT length, void *defaultValue); + static CScriptArray *Create(asITypeInfo *ot, void *listBuffer); + + // Memory management + void AddRef() const; + void Release() const; + + // Type information + asITypeInfo *GetArrayObjectType() const; + int GetArrayTypeId() const; + int GetElementTypeId() const; + + // Get the current size + asUINT GetSize() const; + + // Returns true if the array is empty + bool IsEmpty() const; + + // Pre-allocates memory for elements + void Reserve(asUINT maxElements); + + // Resize the array + void Resize(asUINT numElements); + + // Get a pointer to an element. Returns 0 if out of bounds + void *At(asINT64 index); + const void *At(asINT64 index) const; + + // Set value of an element. + // The value arg should be a pointer to the value that will be copied to the element. + // Remember, if the array holds handles the value parameter should be the + // address of the handle. The refCount of the object will also be incremented + void SetValue(asINT64 index, void *value); + + // Copy the contents of one array to another (only if the types are the same) + CScriptArray &operator=(const CScriptArray&); + + // Compare two arrays + bool operator==(const CScriptArray &) const; + + // Array manipulation + void InsertAt(asUINT index, void *value); + void InsertAt(asUINT index, const CScriptArray &arr); + void InsertLast(void *value); + void InsertLast(const CScriptArray &arr); + void RemoveAt(asUINT index); + void RemoveLast(); + void RemoveRange(asUINT start, asUINT count); + void SortAsc(); + void SortDesc(); + void SortAsc(asUINT startAt, asUINT count); + void SortDesc(asUINT startAt, asUINT count); + void Sort(asUINT startAt, asUINT count, bool asc); + void Sort(asIScriptFunction *less, asUINT startAt, asUINT count); + void Reverse(); + int Find(void *value) const; + int Find(asUINT startAt, void *value) const; + int FindByRef(void *ref) const; + int FindByRef(asUINT startAt, void *ref) const; + + // Return the address of internal buffer for direct manipulation of elements + void *GetBuffer(); + + // GC methods + int GetRefCount(); + void SetFlag(); + bool GetFlag(); + void EnumReferences(asIScriptEngine *engine); + void ReleaseAllHandles(asIScriptEngine *engine); + +protected: + mutable int refCount; + mutable bool gcFlag; + asITypeInfo *objType; + SArrayBuffer *buffer; + int elementSize; + int subTypeId; + + // Constructors + CScriptArray(asITypeInfo *ot, void *initBuf); // Called from script when initialized with list + CScriptArray(asUINT length, asITypeInfo *ot); + CScriptArray(asUINT length, void *defVal, asITypeInfo *ot); + CScriptArray(const CScriptArray &other); + virtual ~CScriptArray(); + + bool Less(const void *a, const void *b, bool asc); + void *GetArrayItemPointer(int index); + void *GetDataPointer(void *buffer); + void Copy(void *dst, void *src); + void Swap(void *a, void *b); + void Precache(); + bool CheckMaxSize(asUINT numElements); + void Resize(int delta, asUINT at); + void CreateBuffer(SArrayBuffer **buf, asUINT numElements); + void DeleteBuffer(SArrayBuffer *buf); + void CopyBuffer(SArrayBuffer *dst, SArrayBuffer *src); + void Construct(SArrayBuffer *buf, asUINT start, asUINT end); + void Destruct(SArrayBuffer *buf, asUINT start, asUINT end); + bool Equals(const void *a, const void *b, asIScriptContext *ctx, SArrayCache *cache) const; +}; + +void RegisterScriptArray(asIScriptEngine *engine, bool defaultArray); + +END_AS_NAMESPACE + +#endif diff --git a/ASAddon/plugin/readme.md b/ASAddon/plugin/readme.md index e615f9b4..9a413354 100644 --- a/ASAddon/plugin/readme.md +++ b/ASAddon/plugin/readme.md @@ -1,9 +1,9 @@ -# What is this? -During NVGT plugin development, particularly when making a shared rather than a static plugin, the following lines should exist at the top of any compilation unit that uses `` to insure that it uses the manually imported Angelscript symbols that were sent to the plugin from NVGT: - -``` -#define NVGT_PLUGIN_INCLUDE -#include "../../src/nvgt_plugin.h" -``` - -This directory contains shims that set this up for the common Angelscript addons, making them easier to include in a shared plugin (see ../plugin/curl, ../plugin/git etc). +# What is this? +During NVGT plugin development, particularly when making a shared rather than a static plugin, the following lines should exist at the top of any compilation unit that uses `` to insure that it uses the manually imported Angelscript symbols that were sent to the plugin from NVGT: + +``` +#define NVGT_PLUGIN_INCLUDE +#include "../../src/nvgt_plugin.h" +``` + +This directory contains shims that set this up for the common Angelscript addons, making them easier to include in a shared plugin (see ../plugin/curl, ../plugin/git etc). diff --git a/ASAddon/plugin/scriptany.cpp b/ASAddon/plugin/scriptany.cpp index df53f1b8..e535361c 100644 --- a/ASAddon/plugin/scriptany.cpp +++ b/ASAddon/plugin/scriptany.cpp @@ -1,3 +1,3 @@ -#define NVGT_PLUGIN_INCLUDE -#include "../../src/nvgt_plugin.h" -#include "../src/scriptany.cpp" +#define NVGT_PLUGIN_INCLUDE +#include "../../src/nvgt_plugin.h" +#include "../src/scriptany.cpp" diff --git a/ASAddon/plugin/scriptarray.cpp b/ASAddon/plugin/scriptarray.cpp index 1a64bbb0..d114c664 100644 --- a/ASAddon/plugin/scriptarray.cpp +++ b/ASAddon/plugin/scriptarray.cpp @@ -1,3 +1,3 @@ -#define NVGT_PLUGIN_INCLUDE -#include "../../src/nvgt_plugin.h" -#include "../src/scriptarray.cpp" +#define NVGT_PLUGIN_INCLUDE +#include "../../src/nvgt_plugin.h" +#include "../src/scriptarray.cpp" diff --git a/ASAddon/plugin/scriptdictionary.cpp b/ASAddon/plugin/scriptdictionary.cpp index 25634e63..4baa450e 100644 --- a/ASAddon/plugin/scriptdictionary.cpp +++ b/ASAddon/plugin/scriptdictionary.cpp @@ -1,3 +1,3 @@ -#define NVGT_PLUGIN_INCLUDE -#include "../../src/nvgt_plugin.h" -#include "../src/scriptdictionary.cpp" +#define NVGT_PLUGIN_INCLUDE +#include "../../src/nvgt_plugin.h" +#include "../src/scriptdictionary.cpp" diff --git a/ASAddon/plugin/scriptgrid.cpp b/ASAddon/plugin/scriptgrid.cpp index 216e1afe..4b6d63b2 100644 --- a/ASAddon/plugin/scriptgrid.cpp +++ b/ASAddon/plugin/scriptgrid.cpp @@ -1,3 +1,3 @@ -#define NVGT_PLUGIN_INCLUDE -#include "../../src/nvgt_plugin.h" -#include "../src/scriptgrid.cpp" +#define NVGT_PLUGIN_INCLUDE +#include "../../src/nvgt_plugin.h" +#include "../src/scriptgrid.cpp" diff --git a/ASAddon/plugin/scriptmathcomplex.cpp b/ASAddon/plugin/scriptmathcomplex.cpp index 571e61c2..4f8ed838 100644 --- a/ASAddon/plugin/scriptmathcomplex.cpp +++ b/ASAddon/plugin/scriptmathcomplex.cpp @@ -1,3 +1,3 @@ -#define NVGT_PLUGIN_INCLUDE -#include "../../src/nvgt_plugin.h" -#include "../src/scriptmathcomplex.cpp" +#define NVGT_PLUGIN_INCLUDE +#include "../../src/nvgt_plugin.h" +#include "../src/scriptmathcomplex.cpp" diff --git a/ASAddon/src/scriptarray.cpp b/ASAddon/src/scriptarray.cpp index 3f57e21b..15d5323f 100644 --- a/ASAddon/src/scriptarray.cpp +++ b/ASAddon/src/scriptarray.cpp @@ -1,2270 +1,2270 @@ -#include -#include -#include -#include -#include // sprintf -#include -#include // std::sort - -#include "scriptarray.h" - -using namespace std; - -BEGIN_AS_NAMESPACE - -// This macro is used to avoid warnings about unused variables. -// Usually where the variables are only used in debug mode. -#define UNUSED_VAR(x) (void)(x) - -// Set the default memory routines -// Use the angelscript engine's memory routines by default -static asALLOCFUNC_t userAlloc = asAllocMem; -static asFREEFUNC_t userFree = asFreeMem; - -// Allows the application to set which memory routines should be used by the array object -void CScriptArray::SetMemoryFunctions(asALLOCFUNC_t allocFunc, asFREEFUNC_t freeFunc) -{ - userAlloc = allocFunc; - userFree = freeFunc; -} - -static void RegisterScriptArray_Native(asIScriptEngine *engine); -static void RegisterScriptArray_Generic(asIScriptEngine *engine); - -struct SArrayBuffer -{ - asDWORD maxElements; - asDWORD numElements; - asBYTE data[1]; -}; - -struct SArrayCache -{ - asIScriptFunction *cmpFunc; - asIScriptFunction *eqFunc; - int cmpFuncReturnCode; // To allow better error message in case of multiple matches - int eqFuncReturnCode; -}; - -// We just define a number here that we assume nobody else is using for -// object type user data. The add-ons have reserved the numbers 1000 -// through 1999 for this purpose, so we should be fine. -const asPWORD ARRAY_CACHE = 1000; - -static void CleanupTypeInfoArrayCache(asITypeInfo *type) -{ - SArrayCache *cache = reinterpret_cast(type->GetUserData(ARRAY_CACHE)); - if( cache ) - { - cache->~SArrayCache(); - userFree(cache); - } -} - -CScriptArray* CScriptArray::Create(asITypeInfo *ti, asUINT length) -{ - // Allocate the memory - void *mem = userAlloc(sizeof(CScriptArray)); - if( mem == 0 ) - { - asIScriptContext *ctx = asGetActiveContext(); - if( ctx ) - ctx->SetException("Out of memory"); - - return 0; - } - - // Initialize the object - CScriptArray *a = new(mem) CScriptArray(length, ti); - - return a; -} - -CScriptArray* CScriptArray::Create(asITypeInfo *ti, void *initList) -{ - // Allocate the memory - void *mem = userAlloc(sizeof(CScriptArray)); - if( mem == 0 ) - { - asIScriptContext *ctx = asGetActiveContext(); - if( ctx ) - ctx->SetException("Out of memory"); - - return 0; - } - - // Initialize the object - CScriptArray *a = new(mem) CScriptArray(ti, initList); - - return a; -} - -CScriptArray* CScriptArray::Create(asITypeInfo *ti, asUINT length, void *defVal) -{ - // Allocate the memory - void *mem = userAlloc(sizeof(CScriptArray)); - if( mem == 0 ) - { - asIScriptContext *ctx = asGetActiveContext(); - if( ctx ) - ctx->SetException("Out of memory"); - - return 0; - } - - // Initialize the object - CScriptArray *a = new(mem) CScriptArray(length, defVal, ti); - - return a; -} - -CScriptArray* CScriptArray::Create(asITypeInfo *ti) -{ - return CScriptArray::Create(ti, asUINT(0)); -} - -// This optional callback is called when the template type is first used by the compiler. -// It allows the application to validate if the template can be instantiated for the requested -// subtype at compile time, instead of at runtime. The output argument dontGarbageCollect -// allow the callback to tell the engine if the template instance type shouldn't be garbage collected, -// i.e. no asOBJ_GC flag. -static bool ScriptArrayTemplateCallback(asITypeInfo *ti, bool &dontGarbageCollect) -{ - // Make sure the subtype can be instantiated with a default factory/constructor, - // otherwise we won't be able to instantiate the elements. - int typeId = ti->GetSubTypeId(); - if( typeId == asTYPEID_VOID ) - return false; - if( (typeId & asTYPEID_MASK_OBJECT) && !(typeId & asTYPEID_OBJHANDLE) ) - { - asITypeInfo *subtype = ti->GetEngine()->GetTypeInfoById(typeId); - asDWORD flags = subtype->GetFlags(); - if( (flags & asOBJ_VALUE) && !(flags & asOBJ_POD) ) - { - // Verify that there is a default constructor - bool found = false; - for( asUINT n = 0; n < subtype->GetBehaviourCount(); n++ ) - { - asEBehaviours beh; - asIScriptFunction *func = subtype->GetBehaviourByIndex(n, &beh); - if( beh != asBEHAVE_CONSTRUCT ) continue; - - if( func->GetParamCount() == 0 ) - { - // Found the default constructor - found = true; - break; - } - } - - if( !found ) - { - // There is no default constructor - // TODO: Should format the message to give the name of the subtype for better understanding - ti->GetEngine()->WriteMessage("array", 0, 0, asMSGTYPE_ERROR, "The subtype has no default constructor"); - return false; - } - } - else if( (flags & asOBJ_REF) ) - { - bool found = false; - - // If value assignment for ref type has been disabled then the array - // can be created if the type has a default factory function - if( !ti->GetEngine()->GetEngineProperty(asEP_DISALLOW_VALUE_ASSIGN_FOR_REF_TYPE) ) - { - // Verify that there is a default factory - for( asUINT n = 0; n < subtype->GetFactoryCount(); n++ ) - { - asIScriptFunction *func = subtype->GetFactoryByIndex(n); - if( func->GetParamCount() == 0 ) - { - // Found the default factory - found = true; - break; - } - } - } - - if( !found ) - { - // No default factory - char buffer[1024]; - snprintf(buffer, sizeof(buffer), "The subtype '%s' has no default factory", subtype ? subtype->GetEngine()->GetTypeDeclaration(subtype->GetTypeId()) : "UNKNOWN"); - ti->GetEngine()->WriteMessage("array", 0, 0, asMSGTYPE_ERROR, buffer); - return false; - } - } - - // If the object type is not garbage collected then the array also doesn't need to be - if( !(flags & asOBJ_GC) ) - dontGarbageCollect = true; - } - else if( !(typeId & asTYPEID_OBJHANDLE) ) - { - // Arrays with primitives cannot form circular references, - // thus there is no need to garbage collect them - dontGarbageCollect = true; - } - else - { - assert( typeId & asTYPEID_OBJHANDLE ); - - // It is not necessary to set the array as garbage collected for all handle types. - // If it is possible to determine that the handle cannot refer to an object type - // that can potentially form a circular reference with the array then it is not - // necessary to make the array garbage collected. - asITypeInfo *subtype = ti->GetEngine()->GetTypeInfoById(typeId); - asDWORD flags = subtype->GetFlags(); - if( !(flags & asOBJ_GC) ) - { - if( (flags & asOBJ_SCRIPT_OBJECT) ) - { - // Even if a script class is by itself not garbage collected, it is possible - // that classes that derive from it may be, so it is not possible to know - // that no circular reference can occur. - if( (flags & asOBJ_NOINHERIT) ) - { - // A script class declared as final cannot be inherited from, thus - // we can be certain that the object cannot be garbage collected. - dontGarbageCollect = true; - } - } - else - { - // For application registered classes we assume the application knows - // what it is doing and don't mark the array as garbage collected unless - // the type is also garbage collected. - dontGarbageCollect = true; - } - } - } - - // The type is ok - return true; -} - -// Registers the template array type -void RegisterScriptArray(asIScriptEngine *engine, bool defaultArray) -{ - if( strstr(asGetLibraryOptions(), "AS_MAX_PORTABILITY") == 0 ) - RegisterScriptArray_Native(engine); - else - RegisterScriptArray_Generic(engine); - - if( defaultArray ) - { - int r = engine->RegisterDefaultArrayType("array"); assert( r >= 0 ); - UNUSED_VAR(r); - } -} - -static void RegisterScriptArray_Native(asIScriptEngine *engine) -{ - int r = 0; - UNUSED_VAR(r); - - // Register the object type user data clean up - engine->SetTypeInfoUserDataCleanupCallback(CleanupTypeInfoArrayCache, ARRAY_CACHE); - - // Register the array type as a template - r = engine->RegisterObjectType("array", 0, asOBJ_REF | asOBJ_GC | asOBJ_TEMPLATE); assert( r >= 0 ); - - // Register a callback for validating the subtype before it is used - r = engine->RegisterObjectBehaviour("array", asBEHAVE_TEMPLATE_CALLBACK, "bool f(int&in, bool&out)", asFUNCTION(ScriptArrayTemplateCallback), asCALL_CDECL); assert( r >= 0 ); - - // Templates receive the object type as the first parameter. To the script writer this is hidden - r = engine->RegisterObjectBehaviour("array", asBEHAVE_FACTORY, "array@ f(int&in)", asFUNCTIONPR(CScriptArray::Create, (asITypeInfo*), CScriptArray*), asCALL_CDECL); assert( r >= 0 ); - r = engine->RegisterObjectBehaviour("array", asBEHAVE_FACTORY, "array@ f(int&in, uint length) explicit", asFUNCTIONPR(CScriptArray::Create, (asITypeInfo*, asUINT), CScriptArray*), asCALL_CDECL); assert( r >= 0 ); - r = engine->RegisterObjectBehaviour("array", asBEHAVE_FACTORY, "array@ f(int&in, uint length, const T &in value)", asFUNCTIONPR(CScriptArray::Create, (asITypeInfo*, asUINT, void *), CScriptArray*), asCALL_CDECL); assert( r >= 0 ); - - // Register the factory that will be used for initialization lists - r = engine->RegisterObjectBehaviour("array", asBEHAVE_LIST_FACTORY, "array@ f(int&in type, int&in list) {repeat T}", asFUNCTIONPR(CScriptArray::Create, (asITypeInfo*, void*), CScriptArray*), asCALL_CDECL); assert( r >= 0 ); - - // The memory management methods - r = engine->RegisterObjectBehaviour("array", asBEHAVE_ADDREF, "void f()", asMETHOD(CScriptArray,AddRef), asCALL_THISCALL); assert( r >= 0 ); - r = engine->RegisterObjectBehaviour("array", asBEHAVE_RELEASE, "void f()", asMETHOD(CScriptArray,Release), asCALL_THISCALL); assert( r >= 0 ); - - // The index operator returns the template subtype - r = engine->RegisterObjectMethod("array", "T &opIndex(int64 index)", asMETHODPR(CScriptArray, At, (asINT64), void*), asCALL_THISCALL); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "const T &opIndex(int64 index) const", asMETHODPR(CScriptArray, At, (asINT64) const, const void*), asCALL_THISCALL); assert( r >= 0 ); - - // The assignment operator - r = engine->RegisterObjectMethod("array", "array &opAssign(const array&in)", asMETHOD(CScriptArray, operator=), asCALL_THISCALL); assert( r >= 0 ); - - // Other methods - r = engine->RegisterObjectMethod("array", "void insert_at(uint index, const T&in value)", asMETHODPR(CScriptArray, InsertAt, (asUINT, void *), void), asCALL_THISCALL); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "void insert_at(uint index, const array& arr)", asMETHODPR(CScriptArray, InsertAt, (asUINT, const CScriptArray &), void), asCALL_THISCALL); assert(r >= 0); - r = engine->RegisterObjectMethod("array", "void insert_last(const T&in value)", asMETHODPR(CScriptArray, InsertLast, (void*), void), asCALL_THISCALL); assert(r >= 0); - r = engine->RegisterObjectMethod("array", "void insert_last(const array& arr)", asMETHODPR(CScriptArray, InsertLast, (const CScriptArray &), void), asCALL_THISCALL); assert(r >= 0); - r = engine->RegisterObjectMethod("array", "void remove_at(uint index)", asMETHOD(CScriptArray, RemoveAt), asCALL_THISCALL); assert(r >= 0); - r = engine->RegisterObjectMethod("array", "void remove_last()", asMETHOD(CScriptArray, RemoveLast), asCALL_THISCALL); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "void remove_range(uint start, uint count)", asMETHOD(CScriptArray, RemoveRange), asCALL_THISCALL); assert(r >= 0); - // TODO: Should length() and resize() be deprecated as the property accessors do the same thing? - // TODO: Register as size() for consistency with other types -#if AS_USE_ACCESSORS != 1 - r = engine->RegisterObjectMethod("array", "uint length() const", asMETHOD(CScriptArray, GetSize), asCALL_THISCALL); assert( r >= 0 ); -#endif - r = engine->RegisterObjectMethod("array", "void reserve(uint length)", asMETHOD(CScriptArray, Reserve), asCALL_THISCALL); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "void resize(uint length)", asMETHODPR(CScriptArray, Resize, (asUINT), void), asCALL_THISCALL); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "void sort_ascending()", asMETHODPR(CScriptArray, SortAsc, (), void), asCALL_THISCALL); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "void sort_ascending(uint startAt, uint count)", asMETHODPR(CScriptArray, SortAsc, (asUINT, asUINT), void), asCALL_THISCALL); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "void sort_descending()", asMETHODPR(CScriptArray, SortDesc, (), void), asCALL_THISCALL); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "void sort_descending(uint startAt, uint count)", asMETHODPR(CScriptArray, SortDesc, (asUINT, asUINT), void), asCALL_THISCALL); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "void reverse()", asMETHOD(CScriptArray, Reverse), asCALL_THISCALL); assert( r >= 0 ); - // The token 'if_handle_then_const' tells the engine that if the type T is a handle, then it should refer to a read-only object - r = engine->RegisterObjectMethod("array", "int find(const T&in if_handle_then_const value) const", asMETHODPR(CScriptArray, Find, (void*) const, int), asCALL_THISCALL); assert( r >= 0 ); - // TODO: It should be "int find(const T&in value, uint startAt = 0) const" - r = engine->RegisterObjectMethod("array", "int find(uint startAt, const T&in if_handle_then_const value) const", asMETHODPR(CScriptArray, Find, (asUINT, void*) const, int), asCALL_THISCALL); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "int find_by_ref(const T&in if_handle_then_const value) const", asMETHODPR(CScriptArray, FindByRef, (void*) const, int), asCALL_THISCALL); assert( r >= 0 ); - // TODO: It should be "int find_by_ref(const T&in value, uint startAt = 0) const" - r = engine->RegisterObjectMethod("array", "int find_by_ref(uint startAt, const T&in if_handle_then_const value) const", asMETHODPR(CScriptArray, FindByRef, (asUINT, void*) const, int), asCALL_THISCALL); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "bool opEquals(const array&in) const", asMETHOD(CScriptArray, operator==), asCALL_THISCALL); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "bool is_empty() const", asMETHOD(CScriptArray, IsEmpty), asCALL_THISCALL); assert( r >= 0 ); - - // Sort with callback for comparison - r = engine->RegisterFuncdef("bool array::less(const T&in if_handle_then_const a, const T&in if_handle_then_const b)"); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "void sort(const less &in, uint startAt = 0, uint count = uint(-1))", asMETHODPR(CScriptArray, Sort, (asIScriptFunction*, asUINT, asUINT), void), asCALL_THISCALL); assert(r >= 0); - -#if AS_USE_STLNAMES != 1 && AS_USE_ACCESSORS == 1 - // Register virtual properties - r = engine->RegisterObjectMethod("array", "uint get_length() const property", asMETHOD(CScriptArray, GetSize), asCALL_THISCALL); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "void set_length(uint) property", asMETHODPR(CScriptArray, Resize, (asUINT), void), asCALL_THISCALL); assert( r >= 0 ); -#endif - - // Register GC behaviours in case the array needs to be garbage collected - r = engine->RegisterObjectBehaviour("array", asBEHAVE_GETREFCOUNT, "int f()", asMETHOD(CScriptArray, GetRefCount), asCALL_THISCALL); assert( r >= 0 ); - r = engine->RegisterObjectBehaviour("array", asBEHAVE_SETGCFLAG, "void f()", asMETHOD(CScriptArray, SetFlag), asCALL_THISCALL); assert( r >= 0 ); - r = engine->RegisterObjectBehaviour("array", asBEHAVE_GETGCFLAG, "bool f()", asMETHOD(CScriptArray, GetFlag), asCALL_THISCALL); assert( r >= 0 ); - r = engine->RegisterObjectBehaviour("array", asBEHAVE_ENUMREFS, "void f(int&in)", asMETHOD(CScriptArray, EnumReferences), asCALL_THISCALL); assert( r >= 0 ); - r = engine->RegisterObjectBehaviour("array", asBEHAVE_RELEASEREFS, "void f(int&in)", asMETHOD(CScriptArray, ReleaseAllHandles), asCALL_THISCALL); assert( r >= 0 ); - -#if AS_USE_STLNAMES == 1 - // Same as length - r = engine->RegisterObjectMethod("array", "uint size() const", asMETHOD(CScriptArray, GetSize), asCALL_THISCALL); assert( r >= 0 ); - // Same as isEmpty - r = engine->RegisterObjectMethod("array", "bool empty() const", asMETHOD(CScriptArray, IsEmpty), asCALL_THISCALL); assert( r >= 0 ); - // Same as insertLast - r = engine->RegisterObjectMethod("array", "void push_back(const T&in)", asMETHODPR(CScriptArray, InsertLast, (void*), void), asCALL_THISCALL); assert( r >= 0 ); - // Same as removeLast - r = engine->RegisterObjectMethod("array", "void pop_back()", asMETHOD(CScriptArray, RemoveLast), asCALL_THISCALL); assert( r >= 0 ); - // Same as insertAt - r = engine->RegisterObjectMethod("array", "void insert(uint index, const T&in value)", asMETHODPR(CScriptArray, InsertAt, (asUINT, void *), void), asCALL_THISCALL); assert(r >= 0); - r = engine->RegisterObjectMethod("array", "void insert(uint index, const array& arr)", asMETHODPR(CScriptArray, InsertAt, (asUINT, const CScriptArray &), void), asCALL_THISCALL); assert(r >= 0); - // Same as removeAt - r = engine->RegisterObjectMethod("array", "void erase(uint)", asMETHOD(CScriptArray, RemoveAt), asCALL_THISCALL); assert( r >= 0 ); -#endif -} - -CScriptArray &CScriptArray::operator=(const CScriptArray &other) -{ - // Only perform the copy if the array types are the same - if( &other != this && - other.GetArrayObjectType() == GetArrayObjectType() ) - { - // Make sure the arrays are of the same size - Resize(other.buffer->numElements); - - // Copy the value of each element - CopyBuffer(buffer, other.buffer); - } - - return *this; -} - -CScriptArray::CScriptArray(asITypeInfo *ti, void *buf) -{ - // The object type should be the template instance of the array - assert( ti && string(ti->GetName()) == "array" ); - - refCount = 1; - gcFlag = false; - objType = ti; - objType->AddRef(); - buffer = 0; - - Precache(); - - asIScriptEngine *engine = ti->GetEngine(); - - // Determine element size - if( subTypeId & asTYPEID_MASK_OBJECT ) - elementSize = sizeof(asPWORD); - else - elementSize = engine->GetSizeOfPrimitiveType(subTypeId); - - // Determine the initial size from the buffer - asUINT length = *(asUINT*)buf; - - // Make sure the array size isn't too large for us to handle - if( !CheckMaxSize(length) ) - { - // Don't continue with the initialization - return; - } - - // Copy the values of the array elements from the buffer - if( (ti->GetSubTypeId() & asTYPEID_MASK_OBJECT) == 0 ) - { - CreateBuffer(&buffer, length); - - // Copy the values of the primitive type into the internal buffer - if( length > 0 ) - memcpy(At(0), (((asUINT*)buf)+1), length * elementSize); - } - else if( ti->GetSubTypeId() & asTYPEID_OBJHANDLE ) - { - CreateBuffer(&buffer, length); - - // Copy the handles into the internal buffer - if( length > 0 ) - memcpy(At(0), (((asUINT*)buf)+1), length * elementSize); - - // With object handles it is safe to clear the memory in the received buffer - // instead of increasing the ref count. It will save time both by avoiding the - // call the increase ref, and also relieve the engine from having to release - // its references too - memset((((asUINT*)buf)+1), 0, length * elementSize); - } - else if( ti->GetSubType()->GetFlags() & asOBJ_REF ) - { - // Only allocate the buffer, but not the objects - subTypeId |= asTYPEID_OBJHANDLE; - CreateBuffer(&buffer, length); - if (!buffer) - throw std::bad_alloc(); - subTypeId &= ~asTYPEID_OBJHANDLE; - - // Copy the handles into the internal buffer - if( length > 0 ) - memcpy(buffer->data, (((asUINT*)buf)+1), length * elementSize); - - // For ref types we can do the same as for handles, as they are - // implicitly stored as handles. - memset((((asUINT*)buf)+1), 0, length * elementSize); - } - else - { - // TODO: Optimize by calling the copy constructor of the object instead of - // constructing with the default constructor and then assigning the value - // TODO: With C++11 ideally we should be calling the move constructor, instead - // of the copy constructor as the engine will just discard the objects in the - // buffer afterwards. - CreateBuffer(&buffer, length); - - // For value types we need to call the opAssign for each individual object - for( asUINT n = 0; n < length; n++ ) - { - void *obj = At(n); - asBYTE *srcObj = (asBYTE*)buf; - srcObj += 4 + n*ti->GetSubType()->GetSize(); - engine->AssignScriptObject(obj, srcObj, ti->GetSubType()); - } - } - - // Notify the GC of the successful creation - if( objType->GetFlags() & asOBJ_GC ) - objType->GetEngine()->NotifyGarbageCollectorOfNewObject(this, objType); -} - -CScriptArray::CScriptArray(asUINT length, asITypeInfo *ti) -{ - // The object type should be the template instance of the array - assert( ti && string(ti->GetName()) == "array" ); - - refCount = 1; - gcFlag = false; - objType = ti; - objType->AddRef(); - buffer = 0; - - Precache(); - - // Determine element size - if( subTypeId & asTYPEID_MASK_OBJECT ) - elementSize = sizeof(asPWORD); - else - elementSize = objType->GetEngine()->GetSizeOfPrimitiveType(subTypeId); - - // Make sure the array size isn't too large for us to handle - if( !CheckMaxSize(length) ) - { - // Don't continue with the initialization - return; - } - - CreateBuffer(&buffer, length); - - // Notify the GC of the successful creation - if( objType->GetFlags() & asOBJ_GC ) - objType->GetEngine()->NotifyGarbageCollectorOfNewObject(this, objType); -} - -CScriptArray::CScriptArray(const CScriptArray &other) -{ - refCount = 1; - gcFlag = false; - objType = other.objType; - objType->AddRef(); - buffer = 0; - - Precache(); - - elementSize = other.elementSize; - - if( objType->GetFlags() & asOBJ_GC ) - objType->GetEngine()->NotifyGarbageCollectorOfNewObject(this, objType); - - CreateBuffer(&buffer, 0); - - // Copy the content - *this = other; -} - -CScriptArray::CScriptArray(asUINT length, void *defVal, asITypeInfo *ti) -{ - // The object type should be the template instance of the array - assert( ti && string(ti->GetName()) == "array" ); - - refCount = 1; - gcFlag = false; - objType = ti; - objType->AddRef(); - buffer = 0; - - Precache(); - - // Determine element size - if( subTypeId & asTYPEID_MASK_OBJECT ) - elementSize = sizeof(asPWORD); - else - elementSize = objType->GetEngine()->GetSizeOfPrimitiveType(subTypeId); - - // Make sure the array size isn't too large for us to handle - if( !CheckMaxSize(length) ) - { - // Don't continue with the initialization - return; - } - - CreateBuffer(&buffer, length); - - // Notify the GC of the successful creation - if( objType->GetFlags() & asOBJ_GC ) - objType->GetEngine()->NotifyGarbageCollectorOfNewObject(this, objType); - - // Initialize the elements with the default value - for( asUINT n = 0; n < GetSize(); n++ ) - SetValue(n, defVal); -} - -void CScriptArray::SetValue(asINT64 index, void *value) -{ - // At() will take care of the out-of-bounds checking, though - // if called from the application then nothing will be done - void *ptr = At(index); - if( ptr == 0 ) return; - - if ((subTypeId & ~asTYPEID_MASK_SEQNBR) && !(subTypeId & asTYPEID_OBJHANDLE)) - { - asITypeInfo *subType = objType->GetSubType(); - if (subType->GetFlags() & asOBJ_ASHANDLE) - { - // For objects that should work as handles we must use the opHndlAssign method - // TODO: Must support alternative syntaxes as well - // TODO: Move the lookup of the opHndlAssign method to Precache() so it is only done once - string decl = string(subType->GetName()) + "& opHndlAssign(const " + string(subType->GetName()) + "&in)"; - asIScriptFunction* func = subType->GetMethodByDecl(decl.c_str()); - if (func) - { - // TODO: Reuse active context if existing - asIScriptEngine* engine = objType->GetEngine(); - asIScriptContext* ctx = engine->RequestContext(); - ctx->Prepare(func); - ctx->SetObject(ptr); - ctx->SetArgAddress(0, value); - // TODO: Handle errors - ctx->Execute(); - engine->ReturnContext(ctx); - } - else - { - // opHndlAssign doesn't exist, so try ordinary value assign instead - objType->GetEngine()->AssignScriptObject(ptr, value, subType); - } - } - else - objType->GetEngine()->AssignScriptObject(ptr, value, subType); - } - else if( subTypeId & asTYPEID_OBJHANDLE ) - { - void *tmp = *(void**)ptr; - *(void**)ptr = *(void**)value; - objType->GetEngine()->AddRefScriptObject(*(void**)value, objType->GetSubType()); - if( tmp ) - objType->GetEngine()->ReleaseScriptObject(tmp, objType->GetSubType()); - } - else if( subTypeId == asTYPEID_BOOL || - subTypeId == asTYPEID_INT8 || - subTypeId == asTYPEID_UINT8 ) - *(char*)ptr = *(char*)value; - else if( subTypeId == asTYPEID_INT16 || - subTypeId == asTYPEID_UINT16 ) - *(short*)ptr = *(short*)value; - else if( subTypeId == asTYPEID_INT32 || - subTypeId == asTYPEID_UINT32 || - subTypeId == asTYPEID_FLOAT || - subTypeId > asTYPEID_DOUBLE ) // enums have a type id larger than doubles - *(int*)ptr = *(int*)value; - else if( subTypeId == asTYPEID_INT64 || - subTypeId == asTYPEID_UINT64 || - subTypeId == asTYPEID_DOUBLE ) - *(double*)ptr = *(double*)value; -} - -CScriptArray::~CScriptArray() -{ - if( buffer ) - { - DeleteBuffer(buffer); - buffer = 0; - } - if( objType ) objType->Release(); -} - -asUINT CScriptArray::GetSize() const -{ - return buffer->numElements; -} - -bool CScriptArray::IsEmpty() const -{ - return buffer->numElements == 0; -} - -void CScriptArray::Reserve(asUINT maxElements) -{ - if( maxElements <= buffer->maxElements ) - return; - - if( !CheckMaxSize(maxElements) ) - return; - - // Allocate memory for the buffer - SArrayBuffer *newBuffer = reinterpret_cast(userAlloc(sizeof(SArrayBuffer)-1 + elementSize*maxElements)); - if( newBuffer ) - { - newBuffer->numElements = buffer->numElements; - newBuffer->maxElements = maxElements; - } - else - { - // Out of memory - asIScriptContext *ctx = asGetActiveContext(); - if( ctx ) - ctx->SetException("Out of memory"); - return; - } - - // As objects in arrays of objects are not stored inline, it is safe to use memcpy here - // since we're just copying the pointers to objects and not the actual objects. - memcpy(newBuffer->data, buffer->data, buffer->numElements*elementSize); - - // Release the old buffer - userFree(buffer); - - buffer = newBuffer; -} - -void CScriptArray::Resize(asUINT numElements) -{ - if( !CheckMaxSize(numElements) ) - return; - - Resize((int)numElements - (int)buffer->numElements, (asUINT)-1); -} - -void CScriptArray::RemoveRange(asUINT start, asUINT count) -{ - if (count == 0) - return; - - if( buffer == 0 || start > buffer->numElements ) - { - // If this is called from a script we raise a script exception - asIScriptContext *ctx = asGetActiveContext(); - if (ctx) - ctx->SetException("Index out of bounds"); - return; - } - - // Cap count to the end of the array - if (start + count > buffer->numElements) - count = buffer->numElements - start; - - // Destroy the elements that are being removed - Destruct(buffer, start, start + count); - - // Compact the elements - // As objects in arrays of objects are not stored inline, it is safe to use memmove here - // since we're just copying the pointers to objects and not the actual objects. - memmove(buffer->data + start*elementSize, buffer->data + (start + count)*elementSize, (buffer->numElements - start - count)*elementSize); - buffer->numElements -= count; -} - -// Internal -void CScriptArray::Resize(int delta, asUINT at) -{ - if( delta < 0 ) - { - if( -delta > (int)buffer->numElements ) - delta = -(int)buffer->numElements; - if( at > buffer->numElements + delta ) - at = buffer->numElements + delta; - } - else if( delta > 0 ) - { - // Make sure the array size isn't too large for us to handle - if( delta > 0 && !CheckMaxSize(buffer->numElements + delta) ) - return; - - if( at > buffer->numElements ) - at = buffer->numElements; - } - - if( delta == 0 ) return; - - if( buffer->maxElements < buffer->numElements + delta ) - { - // Allocate memory for the buffer - SArrayBuffer *newBuffer = reinterpret_cast(userAlloc(sizeof(SArrayBuffer)-1 + elementSize*(buffer->numElements + delta))); - if( newBuffer ) - { - newBuffer->numElements = buffer->numElements + delta; - newBuffer->maxElements = newBuffer->numElements; - } - else - { - // Out of memory - asIScriptContext *ctx = asGetActiveContext(); - if( ctx ) - ctx->SetException("Out of memory"); - return; - } - - // As objects in arrays of objects are not stored inline, it is safe to use memcpy here - // since we're just copying the pointers to objects and not the actual objects. - memcpy(newBuffer->data, buffer->data, at*elementSize); - if( at < buffer->numElements ) - memcpy(newBuffer->data + (at+delta)*elementSize, buffer->data + at*elementSize, (buffer->numElements-at)*elementSize); - - // Initialize the new elements with default values - Construct(newBuffer, at, at+delta); - - // Release the old buffer - userFree(buffer); - - buffer = newBuffer; - } - else if( delta < 0 ) - { - Destruct(buffer, at, at-delta); - // As objects in arrays of objects are not stored inline, it is safe to use memmove here - // since we're just copying the pointers to objects and not the actual objects. - memmove(buffer->data + at*elementSize, buffer->data + (at-delta)*elementSize, (buffer->numElements - (at-delta))*elementSize); - buffer->numElements += delta; - } - else - { - // As objects in arrays of objects are not stored inline, it is safe to use memmove here - // since we're just copying the pointers to objects and not the actual objects. - memmove(buffer->data + (at+delta)*elementSize, buffer->data + at*elementSize, (buffer->numElements - at)*elementSize); - Construct(buffer, at, at+delta); - buffer->numElements += delta; - } -} - -// internal -bool CScriptArray::CheckMaxSize(asUINT numElements) -{ - // This code makes sure the size of the buffer that is allocated - // for the array doesn't overflow and becomes smaller than requested - - asUINT maxSize = 0xFFFFFFFFul - sizeof(SArrayBuffer) + 1; - if( elementSize > 0 ) - maxSize /= elementSize; - - if( numElements > maxSize ) - { - asIScriptContext *ctx = asGetActiveContext(); - if( ctx ) - ctx->SetException("Too large array size"); - - return false; - } - - // OK - return true; -} - -asITypeInfo *CScriptArray::GetArrayObjectType() const -{ - return objType; -} - -int CScriptArray::GetArrayTypeId() const -{ - return objType->GetTypeId(); -} - -int CScriptArray::GetElementTypeId() const -{ - return subTypeId; -} - -void CScriptArray::InsertAt(asUINT index, void *value) -{ - if( index > buffer->numElements ) - { - // If this is called from a script we raise a script exception - asIScriptContext *ctx = asGetActiveContext(); - if( ctx ) - ctx->SetException("Index out of bounds"); - return; - } - - // Make room for the new element - Resize(1, index); - - // Set the value of the new element - SetValue(index, value); -} - -void CScriptArray::InsertAt(asUINT index, const CScriptArray &arr) -{ - if (index > buffer->numElements) - { - asIScriptContext *ctx = asGetActiveContext(); - if (ctx) - ctx->SetException("Index out of bounds"); - return; - } - - if (objType != arr.objType) - { - // This shouldn't really be possible to happen when - // called from a script, but let's check for it anyway - asIScriptContext *ctx = asGetActiveContext(); - if (ctx) - ctx->SetException("Mismatching array types"); - return; - } - - asUINT elements = arr.GetSize(); - Resize(elements, index); - if (&arr != this) - { - for (asUINT n = 0; n < arr.GetSize(); n++) - { - // This const cast is allowed, since we know the - // value will only be used to make a copy of it - void *value = const_cast(arr.At(n)); - SetValue(index + n, value); - } - } - else - { - // The array that is being inserted is the same as this one. - // So we should iterate over the elements before the index, - // and then the elements after - for (asUINT n = 0; n < index; n++) - { - // This const cast is allowed, since we know the - // value will only be used to make a copy of it - void *value = const_cast(arr.At(n)); - SetValue(index + n, value); - } - - for (asUINT n = index + elements, m = 0; n < arr.GetSize(); n++, m++) - { - // This const cast is allowed, since we know the - // value will only be used to make a copy of it - void *value = const_cast(arr.At(n)); - SetValue(index + index + m, value); - } - } -} - -void CScriptArray::InsertLast(void *value) -{ - InsertAt(buffer->numElements, value); -} - -void CScriptArray::InsertLast(const CScriptArray &arr) -{ - InsertAt(buffer->numElements, arr); -} - -void CScriptArray::RemoveAt(asUINT index) -{ - if( index >= buffer->numElements ) - { - // If this is called from a script we raise a script exception - asIScriptContext *ctx = asGetActiveContext(); - if( ctx ) - ctx->SetException("Index out of bounds"); - return; - } - - // Remove the element - Resize(-1, index); -} - -void CScriptArray::RemoveLast() -{ - RemoveAt(buffer->numElements-1); -} - -// Return a pointer to the array element. Returns 0 if the index is out of bounds -const void *CScriptArray::At(asINT64 index) const -{ - if( index < 0) index = buffer->numElements + index; - if( buffer == 0 || index < 0 || index >= buffer->numElements ) - { - // If this is called from a script we raise a script exception - asIScriptContext *ctx = asGetActiveContext(); - if( ctx ) - ctx->SetException("Index out of bounds"); - return 0; - } - - if( (subTypeId & asTYPEID_MASK_OBJECT) && !(subTypeId & asTYPEID_OBJHANDLE) ) - return *(void**)(buffer->data + elementSize*index); - else - return buffer->data + elementSize*index; -} -void *CScriptArray::At(asINT64 index) -{ - return const_cast(const_cast(this)->At(index)); -} - -void *CScriptArray::GetBuffer() -{ - return buffer->data; -} - - -// internal -void CScriptArray::CreateBuffer(SArrayBuffer **buf, asUINT numElements) -{ - *buf = reinterpret_cast(userAlloc(sizeof(SArrayBuffer)-1+elementSize*numElements)); - - if( *buf ) - { - (*buf)->numElements = numElements; - (*buf)->maxElements = numElements; - Construct(*buf, 0, numElements); - } - else - { - // Oops, out of memory - asIScriptContext *ctx = asGetActiveContext(); - if( ctx ) - ctx->SetException("Out of memory"); - } -} - -// internal -void CScriptArray::DeleteBuffer(SArrayBuffer *buf) -{ - Destruct(buf, 0, buf->numElements); - - // Free the buffer - userFree(buf); -} - -// internal -void CScriptArray::Construct(SArrayBuffer *buf, asUINT start, asUINT end) -{ - if( (subTypeId & asTYPEID_MASK_OBJECT) && !(subTypeId & asTYPEID_OBJHANDLE) ) - { - // Create an object using the default constructor/factory for each element - void **max = (void**)(buf->data + end * sizeof(void*)); - void **d = (void**)(buf->data + start * sizeof(void*)); - - asIScriptEngine *engine = objType->GetEngine(); - asITypeInfo *subType = objType->GetSubType(); - - for( ; d < max; d++ ) - { - *d = (void*)engine->CreateScriptObject(subType); - if( *d == 0 ) - { - // Set the remaining entries to null so the destructor - // won't attempt to destroy invalid objects later - memset(d, 0, sizeof(void*)*(max-d)); - - // There is no need to set an exception on the context, - // as CreateScriptObject has already done that - return; - } - } - } - else - { - // Set all elements to zero whether they are handles or primitives - void *d = (void*)(buf->data + start * elementSize); - memset(d, 0, (end-start)*elementSize); - } -} - -// internal -void CScriptArray::Destruct(SArrayBuffer *buf, asUINT start, asUINT end) -{ - if( subTypeId & asTYPEID_MASK_OBJECT ) - { - asIScriptEngine *engine = objType->GetEngine(); - - void **max = (void**)(buf->data + end * sizeof(void*)); - void **d = (void**)(buf->data + start * sizeof(void*)); - - for( ; d < max; d++ ) - { - if( *d ) - engine->ReleaseScriptObject(*d, objType->GetSubType()); - } - } -} - - -// internal -bool CScriptArray::Less(const void *a, const void *b, bool asc) -{ - if( !asc ) - { - // Swap items - const void *TEMP = a; - a = b; - b = TEMP; - } - - if( !(subTypeId & ~asTYPEID_MASK_SEQNBR) ) - { - // Simple compare of values - switch( subTypeId ) - { - #define COMPARE(T) *((T*)a) < *((T*)b) - case asTYPEID_BOOL: return COMPARE(bool); - case asTYPEID_INT8: return COMPARE(asINT8); - case asTYPEID_INT16: return COMPARE(asINT16); - case asTYPEID_INT32: return COMPARE(asINT32); - case asTYPEID_INT64: return COMPARE(asINT64); - case asTYPEID_UINT8: return COMPARE(asBYTE); - case asTYPEID_UINT16: return COMPARE(asWORD); - case asTYPEID_UINT32: return COMPARE(asDWORD); - case asTYPEID_UINT64: return COMPARE(asQWORD); - case asTYPEID_FLOAT: return COMPARE(float); - case asTYPEID_DOUBLE: return COMPARE(double); - default: return COMPARE(signed int); // All enums fall in this case. TODO: update this when enums can have different sizes and types - #undef COMPARE - } - } - - return false; -} - -void CScriptArray::Reverse() -{ - asUINT size = GetSize(); - - if( size >= 2 ) - { - asBYTE TEMP[16]; - - for( asUINT i = 0; i < size / 2; i++ ) - { - Copy(TEMP, GetArrayItemPointer(i)); - Copy(GetArrayItemPointer(i), GetArrayItemPointer(size - i - 1)); - Copy(GetArrayItemPointer(size - i - 1), TEMP); - } - } -} - -bool CScriptArray::operator==(const CScriptArray &other) const -{ - if( objType != other.objType ) - return false; - - if( GetSize() != other.GetSize() ) - return false; - - asIScriptContext *cmpContext = 0; - bool isNested = false; - - if( subTypeId & ~asTYPEID_MASK_SEQNBR ) - { - // Try to reuse the active context - cmpContext = asGetActiveContext(); - if( cmpContext ) - { - if( cmpContext->GetEngine() == objType->GetEngine() && cmpContext->PushState() >= 0 ) - isNested = true; - else - cmpContext = 0; - } - if( cmpContext == 0 ) - { - // TODO: Ideally this context would be retrieved from a pool, so we don't have to - // create a new one everytime. We could keep a context with the array object - // but that would consume a lot of resources as each context is quite heavy. - cmpContext = objType->GetEngine()->CreateContext(); - } - } - - // Check if all elements are equal - bool isEqual = true; - SArrayCache *cache = reinterpret_cast(objType->GetUserData(ARRAY_CACHE)); - for( asUINT n = 0; n < GetSize(); n++ ) - if( !Equals(At(n), other.At(n), cmpContext, cache) ) - { - isEqual = false; - break; - } - - if( cmpContext ) - { - if( isNested ) - { - asEContextState state = cmpContext->GetState(); - cmpContext->PopState(); - if( state == asEXECUTION_ABORTED ) - cmpContext->Abort(); - } - else - cmpContext->Release(); - } - - return isEqual; -} - -// internal -bool CScriptArray::Equals(const void *a, const void *b, asIScriptContext *ctx, SArrayCache *cache) const -{ - if( !(subTypeId & ~asTYPEID_MASK_SEQNBR) ) - { - // Simple compare of values - switch( subTypeId ) - { - #define COMPARE(T) *((T*)a) == *((T*)b) - case asTYPEID_BOOL: return COMPARE(bool); - case asTYPEID_INT8: return COMPARE(asINT8); - case asTYPEID_INT16: return COMPARE(asINT16); - case asTYPEID_INT32: return COMPARE(asINT32); - case asTYPEID_INT64: return COMPARE(asINT64); - case asTYPEID_UINT8: return COMPARE(asBYTE); - case asTYPEID_UINT16: return COMPARE(asWORD); - case asTYPEID_UINT32: return COMPARE(asDWORD); - case asTYPEID_UINT64: return COMPARE(asQWORD); - case asTYPEID_FLOAT: return COMPARE(float); - case asTYPEID_DOUBLE: return COMPARE(double); - default: return COMPARE(signed int); // All enums fall here. TODO: update this when enums can have different sizes and types - #undef COMPARE - } - } - else - { - int r = 0; - - if( subTypeId & asTYPEID_OBJHANDLE ) - { - // Allow the find to work even if the array contains null handles - if( *(void**)a == *(void**)b ) return true; - } - - // Execute object opEquals if available - if( cache && cache->eqFunc ) - { - // TODO: Add proper error handling - r = ctx->Prepare(cache->eqFunc); assert(r >= 0); - - if( subTypeId & asTYPEID_OBJHANDLE ) - { - r = ctx->SetObject(*((void**)a)); assert(r >= 0); - r = ctx->SetArgObject(0, *((void**)b)); assert(r >= 0); - } - else - { - r = ctx->SetObject((void*)a); assert(r >= 0); - r = ctx->SetArgObject(0, (void*)b); assert(r >= 0); - } - - r = ctx->Execute(); - - if( r == asEXECUTION_FINISHED ) - return ctx->GetReturnByte() != 0; - - return false; - } - - // Execute object opCmp if available - if( cache && cache->cmpFunc ) - { - // TODO: Add proper error handling - r = ctx->Prepare(cache->cmpFunc); assert(r >= 0); - - if( subTypeId & asTYPEID_OBJHANDLE ) - { - r = ctx->SetObject(*((void**)a)); assert(r >= 0); - r = ctx->SetArgObject(0, *((void**)b)); assert(r >= 0); - } - else - { - r = ctx->SetObject((void*)a); assert(r >= 0); - r = ctx->SetArgObject(0, (void*)b); assert(r >= 0); - } - - r = ctx->Execute(); - - if( r == asEXECUTION_FINISHED ) - return (int)ctx->GetReturnDWord() == 0; - - return false; - } - } - - return false; -} - -int CScriptArray::FindByRef(void *ref) const -{ - return FindByRef(0, ref); -} - -int CScriptArray::FindByRef(asUINT startAt, void *ref) const -{ - // Find the matching element by its reference - asUINT size = GetSize(); - if( subTypeId & asTYPEID_OBJHANDLE ) - { - // Dereference the pointer - ref = *(void**)ref; - for( asUINT i = startAt; i < size; i++ ) - { - if( *(void**)At(i) == ref ) - return i; - } - } - else - { - // Compare the reference directly - for( asUINT i = startAt; i < size; i++ ) - { - if( At(i) == ref ) - return i; - } - } - - return -1; -} - -int CScriptArray::Find(void *value) const -{ - return Find(0, value); -} - -int CScriptArray::Find(asUINT startAt, void *value) const -{ - // Check if the subtype really supports find() - // TODO: Can't this be done at compile time too by the template callback - SArrayCache *cache = 0; - if( subTypeId & ~asTYPEID_MASK_SEQNBR ) - { - cache = reinterpret_cast(objType->GetUserData(ARRAY_CACHE)); - if( !cache || (cache->cmpFunc == 0 && cache->eqFunc == 0) ) - { - asIScriptContext *ctx = asGetActiveContext(); - asITypeInfo* subType = objType->GetEngine()->GetTypeInfoById(subTypeId); - - // Throw an exception - if( ctx ) - { - char tmp[512]; - - if( cache && cache->eqFuncReturnCode == asMULTIPLE_FUNCTIONS ) -#if defined(_MSC_VER) && _MSC_VER >= 1500 && !defined(__S3E__) - sprintf_s(tmp, 512, "Type '%s' has multiple matching opEquals or opCmp methods", subType->GetName()); -#else - snprintf(tmp, 512, "Type '%s' has multiple matching opEquals or opCmp methods", subType->GetName()); -#endif - else -#if defined(_MSC_VER) && _MSC_VER >= 1500 && !defined(__S3E__) - sprintf_s(tmp, 512, "Type '%s' does not have a matching opEquals or opCmp method", subType->GetName()); -#else - snprintf(tmp, 512, "Type '%s' does not have a matching opEquals or opCmp method", subType->GetName()); -#endif - ctx->SetException(tmp); - } - - return -1; - } - } - - asIScriptContext *cmpContext = 0; - bool isNested = false; - - if( subTypeId & ~asTYPEID_MASK_SEQNBR ) - { - // Try to reuse the active context - cmpContext = asGetActiveContext(); - if( cmpContext ) - { - if( cmpContext->GetEngine() == objType->GetEngine() && cmpContext->PushState() >= 0 ) - isNested = true; - else - cmpContext = 0; - } - if( cmpContext == 0 ) - { - // TODO: Ideally this context would be retrieved from a pool, so we don't have to - // create a new one everytime. We could keep a context with the array object - // but that would consume a lot of resources as each context is quite heavy. - cmpContext = objType->GetEngine()->CreateContext(); - } - } - - // Find the matching element - int ret = -1; - asUINT size = GetSize(); - - for( asUINT i = startAt; i < size; i++ ) - { - // value passed by reference - if( Equals(At(i), value, cmpContext, cache) ) - { - ret = (int)i; - break; - } - } - - if( cmpContext ) - { - if( isNested ) - { - asEContextState state = cmpContext->GetState(); - cmpContext->PopState(); - if( state == asEXECUTION_ABORTED ) - cmpContext->Abort(); - } - else - cmpContext->Release(); - } - - return ret; -} - - - -// internal -// Copy object handle or primitive value -// Even in arrays of objects the objects are allocated on -// the heap and the array stores the pointers to the objects -void CScriptArray::Copy(void *dst, void *src) -{ - memcpy(dst, src, elementSize); -} - - -// internal -// Swap two elements -// Even in arrays of objects the objects are allocated on -// the heap and the array stores the pointers to the objects. -void CScriptArray::Swap(void* a, void* b) -{ - asBYTE tmp[16]; - Copy(tmp, a); - Copy(a, b); - Copy(b, tmp); -} - - -// internal -// Return pointer to array item (object handle or primitive value) -void *CScriptArray::GetArrayItemPointer(int index) -{ - return buffer->data + index * elementSize; -} - -// internal -// Return pointer to data in buffer (object or primitive) -void *CScriptArray::GetDataPointer(void *buf) -{ - if ((subTypeId & asTYPEID_MASK_OBJECT) && !(subTypeId & asTYPEID_OBJHANDLE) ) - { - // Real address of object - return reinterpret_cast(*(size_t*)buf); - } - else - { - // Primitive is just a raw data - return buf; - } -} - - -// Sort ascending -void CScriptArray::SortAsc() -{ - Sort(0, GetSize(), true); -} - -// Sort ascending -void CScriptArray::SortAsc(asUINT startAt, asUINT count) -{ - Sort(startAt, count, true); -} - -// Sort descending -void CScriptArray::SortDesc() -{ - Sort(0, GetSize(), false); -} - -// Sort descending -void CScriptArray::SortDesc(asUINT startAt, asUINT count) -{ - Sort(startAt, count, false); -} - - -// internal -void CScriptArray::Sort(asUINT startAt, asUINT count, bool asc) -{ - // Subtype isn't primitive and doesn't have opCmp - SArrayCache *cache = reinterpret_cast(objType->GetUserData(ARRAY_CACHE)); - if( subTypeId & ~asTYPEID_MASK_SEQNBR ) - { - if( !cache || cache->cmpFunc == 0 ) - { - asIScriptContext *ctx = asGetActiveContext(); - asITypeInfo* subType = objType->GetEngine()->GetTypeInfoById(subTypeId); - - // Throw an exception - if( ctx ) - { - char tmp[512]; - - if( cache && cache->cmpFuncReturnCode == asMULTIPLE_FUNCTIONS ) -#if defined(_MSC_VER) && _MSC_VER >= 1500 && !defined(__S3E__) - sprintf_s(tmp, 512, "Type '%s' has multiple matching opCmp methods", subType->GetName()); -#else - snprintf(tmp, 512, "Type '%s' has multiple matching opCmp methods", subType->GetName()); -#endif - else -#if defined(_MSC_VER) && _MSC_VER >= 1500 && !defined(__S3E__) - sprintf_s(tmp, 512, "Type '%s' does not have a matching opCmp method", subType->GetName()); -#else - snprintf(tmp, 512, "Type '%s' does not have a matching opCmp method", subType->GetName()); -#endif - - ctx->SetException(tmp); - } - - return; - } - } - - // No need to sort - if( count < 2 ) - { - return; - } - - int start = startAt; - int end = startAt + count; - - // Check if we could access invalid item while sorting - if( start >= (int)buffer->numElements || end > (int)buffer->numElements ) - { - asIScriptContext *ctx = asGetActiveContext(); - - // Throw an exception - if( ctx ) - { - ctx->SetException("Index out of bounds"); - } - - return; - } - - if( subTypeId & ~asTYPEID_MASK_SEQNBR ) - { - asIScriptContext *cmpContext = 0; - bool isNested = false; - - // Try to reuse the active context - cmpContext = asGetActiveContext(); - if( cmpContext ) - { - if( cmpContext->GetEngine() == objType->GetEngine() && cmpContext->PushState() >= 0 ) - isNested = true; - else - cmpContext = 0; - } - if( cmpContext == 0 ) - cmpContext = objType->GetEngine()->RequestContext(); - - // Do the sorting - struct { - bool asc; - asIScriptContext *cmpContext; - asIScriptFunction *cmpFunc; - bool operator()(void *a, void *b) const - { - if( !asc ) - { - // Swap items - void *TEMP = a; - a = b; - b = TEMP; - } - - int r = 0; - - // Allow sort to work even if the array contains null handles - if( a == 0 ) return true; - if( b == 0 ) return false; - - // Execute object opCmp - if( cmpFunc ) - { - // TODO: Add proper error handling - r = cmpContext->Prepare(cmpFunc); assert(r >= 0); - r = cmpContext->SetObject(a); assert(r >= 0); - r = cmpContext->SetArgObject(0, b); assert(r >= 0); - r = cmpContext->Execute(); - - if( r == asEXECUTION_FINISHED ) - { - return (int)cmpContext->GetReturnDWord() < 0; - } - } - - return false; - } - } customLess = {asc, cmpContext, cache ? cache->cmpFunc : 0}; - std::sort((void**)GetArrayItemPointer(start), (void**)GetArrayItemPointer(end), customLess); - - // Clean up - if( cmpContext ) - { - if( isNested ) - { - asEContextState state = cmpContext->GetState(); - cmpContext->PopState(); - if( state == asEXECUTION_ABORTED ) - cmpContext->Abort(); - } - else - objType->GetEngine()->ReturnContext(cmpContext); - } - } - else - { - // TODO: Use std::sort for primitive types too - - // Insertion sort - asBYTE tmp[16]; - for( int i = start + 1; i < end; i++ ) - { - Copy(tmp, GetArrayItemPointer(i)); - - int j = i - 1; - - while( j >= start && Less(GetDataPointer(tmp), At(j), asc) ) - { - Copy(GetArrayItemPointer(j + 1), GetArrayItemPointer(j)); - j--; - } - - Copy(GetArrayItemPointer(j + 1), tmp); - } - } -} - -// Sort with script callback for comparing elements -void CScriptArray::Sort(asIScriptFunction *func, asUINT startAt, asUINT count) -{ - // No need to sort - if (count < 2) - return; - - // Check if we could access invalid item while sorting - asUINT start = startAt; - asUINT end = asQWORD(startAt) + asQWORD(count) >= (asQWORD(1)<<32) ? 0xFFFFFFFF : startAt + count; - if (end > buffer->numElements) - end = buffer->numElements; - - if (start >= buffer->numElements) - { - asIScriptContext *ctx = asGetActiveContext(); - - // Throw an exception - if (ctx) - ctx->SetException("Index out of bounds"); - - return; - } - - asIScriptContext *cmpContext = 0; - bool isNested = false; - - // Try to reuse the active context - cmpContext = asGetActiveContext(); - if (cmpContext) - { - if (cmpContext->GetEngine() == objType->GetEngine() && cmpContext->PushState() >= 0) - isNested = true; - else - cmpContext = 0; - } - if (cmpContext == 0) - cmpContext = objType->GetEngine()->RequestContext(); - - // TODO: Security issue: If the array is accessed from the callback while the sort is going on the result may be unpredictable - // For example, the callback resizes the array in the middle of the sort - // Possible solution: set a lock flag on the array, and prohibit modifications while the lock flag is set - - // Bubble sort - // TODO: optimize: Use an efficient sort algorithm - for (asUINT i = start; i+1 < end; i++) - { - asUINT best = i; - for (asUINT j = i + 1; j < end; j++) - { - cmpContext->Prepare(func); - cmpContext->SetArgAddress(0, At(j)); - cmpContext->SetArgAddress(1, At(best)); - int r = cmpContext->Execute(); - if (r != asEXECUTION_FINISHED) - break; - if (*(bool*)(cmpContext->GetAddressOfReturnValue())) - best = j; - } - - // With Swap we guarantee that the array always sees all references - // if the GC calls the EnumReferences in the middle of the sorting - if( best != i ) - Swap(GetArrayItemPointer(i), GetArrayItemPointer(best)); - } - - if (cmpContext) - { - if (isNested) - { - asEContextState state = cmpContext->GetState(); - cmpContext->PopState(); - if (state == asEXECUTION_ABORTED) - cmpContext->Abort(); - } - else - objType->GetEngine()->ReturnContext(cmpContext); - } -} - -// internal -void CScriptArray::CopyBuffer(SArrayBuffer *dst, SArrayBuffer *src) -{ - asIScriptEngine *engine = objType->GetEngine(); - if( subTypeId & asTYPEID_OBJHANDLE ) - { - // Copy the references and increase the reference counters - if( dst->numElements > 0 && src->numElements > 0 ) - { - int count = dst->numElements > src->numElements ? src->numElements : dst->numElements; - - void **max = (void**)(dst->data + count * sizeof(void*)); - void **d = (void**)dst->data; - void **s = (void**)src->data; - - for( ; d < max; d++, s++ ) - { - void *tmp = *d; - *d = *s; - if( *d ) - engine->AddRefScriptObject(*d, objType->GetSubType()); - // Release the old ref after incrementing the new to avoid problem incase it is the same ref - if( tmp ) - engine->ReleaseScriptObject(tmp, objType->GetSubType()); - } - } - } - else - { - if( dst->numElements > 0 && src->numElements > 0 ) - { - int count = dst->numElements > src->numElements ? src->numElements : dst->numElements; - if( subTypeId & asTYPEID_MASK_OBJECT ) - { - // Call the assignment operator on all of the objects - void **max = (void**)(dst->data + count * sizeof(void*)); - void **d = (void**)dst->data; - void **s = (void**)src->data; - - asITypeInfo *subType = objType->GetSubType(); - if (subType->GetFlags() & asOBJ_ASHANDLE) - { - // For objects that should work as handles we must use the opHndlAssign method - // TODO: Must support alternative syntaxes as well - // TODO: Move the lookup of the opHndlAssign method to Precache() so it is only done once - string decl = string(subType->GetName()) + "& opHndlAssign(const " + string(subType->GetName()) + "&in)"; - asIScriptFunction *func = subType->GetMethodByDecl(decl.c_str()); - if (func) - { - // TODO: Reuse active context if existing - asIScriptContext* ctx = engine->RequestContext(); - for (; d < max; d++, s++) - { - ctx->Prepare(func); - ctx->SetObject(*d); - ctx->SetArgAddress(0, *s); - // TODO: Handle errors - ctx->Execute(); - } - engine->ReturnContext(ctx); - } - else - { - // opHndlAssign doesn't exist, so try ordinary value assign instead - for (; d < max; d++, s++) - engine->AssignScriptObject(*d, *s, subType); - } - } - else - for( ; d < max; d++, s++ ) - engine->AssignScriptObject(*d, *s, subType); - } - else - { - // Primitives are copied byte for byte - memcpy(dst->data, src->data, count*elementSize); - } - } - } -} - -// internal -// Precache some info -void CScriptArray::Precache() -{ - subTypeId = objType->GetSubTypeId(); - - // Check if it is an array of objects. Only for these do we need to cache anything - // Type ids for primitives and enums only has the sequence number part - if( !(subTypeId & ~asTYPEID_MASK_SEQNBR) ) - return; - - // The opCmp and opEquals methods are cached because the searching for the - // methods is quite time consuming if a lot of array objects are created. - - // First check if a cache already exists for this array type - SArrayCache *cache = reinterpret_cast(objType->GetUserData(ARRAY_CACHE)); - if( cache ) return; - - // We need to make sure the cache is created only once, even - // if multiple threads reach the same point at the same time - asAcquireExclusiveLock(); - - // Now that we got the lock, we need to check again to make sure the - // cache wasn't created while we were waiting for the lock - cache = reinterpret_cast(objType->GetUserData(ARRAY_CACHE)); - if( cache ) - { - asReleaseExclusiveLock(); - return; - } - - // Create the cache - cache = reinterpret_cast(userAlloc(sizeof(SArrayCache))); - if( !cache ) - { - asIScriptContext *ctx = asGetActiveContext(); - if( ctx ) - ctx->SetException("Out of memory"); - asReleaseExclusiveLock(); - return; - } - memset(cache, 0, sizeof(SArrayCache)); - - // If the sub type is a handle to const, then the methods must be const too - bool mustBeConst = (subTypeId & asTYPEID_HANDLETOCONST) ? true : false; - - asITypeInfo *subType = objType->GetEngine()->GetTypeInfoById(subTypeId); - if( subType ) - { - for( asUINT i = 0; i < subType->GetMethodCount(); i++ ) - { - asIScriptFunction *func = subType->GetMethodByIndex(i); - - if( func->GetParamCount() == 1 && (!mustBeConst || func->IsReadOnly()) ) - { - asDWORD flags = 0; - int returnTypeId = func->GetReturnTypeId(&flags); - - // The method must not return a reference - if( flags != asTM_NONE ) - continue; - - // opCmp returns an int and opEquals returns a bool - bool isCmp = false, isEq = false; - if( returnTypeId == asTYPEID_INT32 && strcmp(func->GetName(), "opCmp") == 0 ) - isCmp = true; - if( returnTypeId == asTYPEID_BOOL && strcmp(func->GetName(), "opEquals") == 0 ) - isEq = true; - - if( !isCmp && !isEq ) - continue; - - // The parameter must either be a reference to the subtype or a handle to the subtype - int paramTypeId; - func->GetParam(0, ¶mTypeId, &flags); - - if( (paramTypeId & ~(asTYPEID_OBJHANDLE|asTYPEID_HANDLETOCONST)) != (subTypeId & ~(asTYPEID_OBJHANDLE|asTYPEID_HANDLETOCONST)) ) - continue; - - if( (flags & asTM_INREF) ) - { - if( (paramTypeId & asTYPEID_OBJHANDLE) || (mustBeConst && !(flags & asTM_CONST)) ) - continue; - } - else if( paramTypeId & asTYPEID_OBJHANDLE ) - { - if( mustBeConst && !(paramTypeId & asTYPEID_HANDLETOCONST) ) - continue; - } - else - continue; - - if( isCmp ) - { - if( cache->cmpFunc || cache->cmpFuncReturnCode ) - { - cache->cmpFunc = 0; - cache->cmpFuncReturnCode = asMULTIPLE_FUNCTIONS; - } - else - cache->cmpFunc = func; - } - else if( isEq ) - { - if( cache->eqFunc || cache->eqFuncReturnCode ) - { - cache->eqFunc = 0; - cache->eqFuncReturnCode = asMULTIPLE_FUNCTIONS; - } - else - cache->eqFunc = func; - } - } - } - } - - if( cache->eqFunc == 0 && cache->eqFuncReturnCode == 0 ) - cache->eqFuncReturnCode = asNO_FUNCTION; - if( cache->cmpFunc == 0 && cache->cmpFuncReturnCode == 0 ) - cache->cmpFuncReturnCode = asNO_FUNCTION; - - // Set the user data only at the end so others that retrieve it will know it is complete - objType->SetUserData(cache, ARRAY_CACHE); - - asReleaseExclusiveLock(); -} - -// GC behaviour -void CScriptArray::EnumReferences(asIScriptEngine *engine) -{ - // TODO: If garbage collection can be done from a separate thread, then this method must be - // protected so that it doesn't get lost during the iteration if the array is modified - - // If the array is holding handles, then we need to notify the GC of them - if( subTypeId & asTYPEID_MASK_OBJECT ) - { - void **d = (void**)buffer->data; - - asITypeInfo *subType = engine->GetTypeInfoById(subTypeId); - if ((subType->GetFlags() & asOBJ_REF)) - { - // For reference types we need to notify the GC of each instance - for (asUINT n = 0; n < buffer->numElements; n++) - { - if (d[n]) - engine->GCEnumCallback(d[n]); - } - } - else if ((subType->GetFlags() & asOBJ_VALUE) && (subType->GetFlags() & asOBJ_GC)) - { - // For value types we need to forward the enum callback - // to the object so it can decide what to do - for (asUINT n = 0; n < buffer->numElements; n++) - { - if (d[n]) - engine->ForwardGCEnumReferences(d[n], subType); - } - } - } -} - -// GC behaviour -void CScriptArray::ReleaseAllHandles(asIScriptEngine *) -{ - // Resizing to zero will release everything - Resize(0); -} - -void CScriptArray::AddRef() const -{ - // Clear the GC flag then increase the counter - gcFlag = false; - asAtomicInc(refCount); -} - -void CScriptArray::Release() const -{ - // Clearing the GC flag then descrease the counter - gcFlag = false; - if( asAtomicDec(refCount) == 0 ) - { - // When reaching 0 no more references to this instance - // exists and the object should be destroyed - this->~CScriptArray(); - userFree(const_cast(this)); - } -} - -// GC behaviour -int CScriptArray::GetRefCount() -{ - return refCount; -} - -// GC behaviour -void CScriptArray::SetFlag() -{ - gcFlag = true; -} - -// GC behaviour -bool CScriptArray::GetFlag() -{ - return gcFlag; -} - -//-------------------------------------------- -// Generic calling conventions - -static void ScriptArrayFactory_Generic(asIScriptGeneric *gen) -{ - asITypeInfo *ti = *(asITypeInfo**)gen->GetAddressOfArg(0); - - *reinterpret_cast(gen->GetAddressOfReturnLocation()) = CScriptArray::Create(ti); -} - -static void ScriptArrayFactory2_Generic(asIScriptGeneric *gen) -{ - asITypeInfo *ti = *(asITypeInfo**)gen->GetAddressOfArg(0); - asUINT length = gen->GetArgDWord(1); - - *reinterpret_cast(gen->GetAddressOfReturnLocation()) = CScriptArray::Create(ti, length); -} - -static void ScriptArrayListFactory_Generic(asIScriptGeneric *gen) -{ - asITypeInfo *ti = *(asITypeInfo**)gen->GetAddressOfArg(0); - void *buf = gen->GetArgAddress(1); - - *reinterpret_cast(gen->GetAddressOfReturnLocation()) = CScriptArray::Create(ti, buf); -} - -static void ScriptArrayFactoryDefVal_Generic(asIScriptGeneric *gen) -{ - asITypeInfo *ti = *(asITypeInfo**)gen->GetAddressOfArg(0); - asUINT length = gen->GetArgDWord(1); - void *defVal = gen->GetArgAddress(2); - - *reinterpret_cast(gen->GetAddressOfReturnLocation()) = CScriptArray::Create(ti, length, defVal); -} - -static void ScriptArrayTemplateCallback_Generic(asIScriptGeneric *gen) -{ - asITypeInfo *ti = *(asITypeInfo**)gen->GetAddressOfArg(0); - bool *dontGarbageCollect = *(bool**)gen->GetAddressOfArg(1); - *reinterpret_cast(gen->GetAddressOfReturnLocation()) = ScriptArrayTemplateCallback(ti, *dontGarbageCollect); -} - -static void ScriptArrayAssignment_Generic(asIScriptGeneric *gen) -{ - CScriptArray *other = (CScriptArray*)gen->GetArgObject(0); - CScriptArray *self = (CScriptArray*)gen->GetObject(); - *self = *other; - gen->SetReturnObject(self); -} - -static void ScriptArrayEquals_Generic(asIScriptGeneric *gen) -{ - CScriptArray *other = (CScriptArray*)gen->GetArgObject(0); - CScriptArray *self = (CScriptArray*)gen->GetObject(); - gen->SetReturnByte(self->operator==(*other)); -} - -static void ScriptArrayFind_Generic(asIScriptGeneric *gen) -{ - void *value = gen->GetArgAddress(0); - CScriptArray *self = (CScriptArray*)gen->GetObject(); - gen->SetReturnDWord(self->Find(value)); -} - -static void ScriptArrayFind2_Generic(asIScriptGeneric *gen) -{ - asUINT index = gen->GetArgDWord(0); - void *value = gen->GetArgAddress(1); - CScriptArray *self = (CScriptArray*)gen->GetObject(); - gen->SetReturnDWord(self->Find(index, value)); -} - -static void ScriptArrayFindByRef_Generic(asIScriptGeneric *gen) -{ - void *value = gen->GetArgAddress(0); - CScriptArray *self = (CScriptArray*)gen->GetObject(); - gen->SetReturnDWord(self->FindByRef(value)); -} - -static void ScriptArrayFindByRef2_Generic(asIScriptGeneric *gen) -{ - asUINT index = gen->GetArgDWord(0); - void *value = gen->GetArgAddress(1); - CScriptArray *self = (CScriptArray*)gen->GetObject(); - gen->SetReturnDWord(self->FindByRef(index, value)); -} - -static void ScriptArrayAt_Generic(asIScriptGeneric *gen) -{ - asINT64 index = gen->GetArgQWord(0); - CScriptArray *self = (CScriptArray*)gen->GetObject(); - - gen->SetReturnAddress(self->At(index)); -} - -static void ScriptArrayInsertAt_Generic(asIScriptGeneric *gen) -{ - asUINT index = gen->GetArgDWord(0); - void *value = gen->GetArgAddress(1); - CScriptArray *self = (CScriptArray*)gen->GetObject(); - self->InsertAt(index, value); -} - -static void ScriptArrayInsertAtArray_Generic(asIScriptGeneric *gen) -{ - asUINT index = gen->GetArgDWord(0); - CScriptArray *array = (CScriptArray*)gen->GetArgAddress(1); - CScriptArray *self = (CScriptArray*)gen->GetObject(); - self->InsertAt(index, *array); -} - -static void ScriptArrayRemoveAt_Generic(asIScriptGeneric *gen) -{ - asUINT index = gen->GetArgDWord(0); - CScriptArray *self = (CScriptArray*)gen->GetObject(); - self->RemoveAt(index); -} - -static void ScriptArrayRemoveRange_Generic(asIScriptGeneric *gen) -{ - asUINT start = gen->GetArgDWord(0); - asUINT count = gen->GetArgDWord(1); - CScriptArray *self = (CScriptArray*)gen->GetObject(); - self->RemoveRange(start, count); -} - -static void ScriptArrayInsertLast_Generic(asIScriptGeneric *gen) -{ - void *value = gen->GetArgAddress(0); - CScriptArray *self = (CScriptArray*)gen->GetObject(); - self->InsertLast(value); -} - -static void ScriptArrayRemoveLast_Generic(asIScriptGeneric *gen) -{ - CScriptArray *self = (CScriptArray*)gen->GetObject(); - self->RemoveLast(); -} - -static void ScriptArrayLength_Generic(asIScriptGeneric *gen) -{ - CScriptArray *self = (CScriptArray*)gen->GetObject(); - - gen->SetReturnDWord(self->GetSize()); -} - -static void ScriptArrayResize_Generic(asIScriptGeneric *gen) -{ - asUINT size = gen->GetArgDWord(0); - CScriptArray *self = (CScriptArray*)gen->GetObject(); - - self->Resize(size); -} - -static void ScriptArrayReserve_Generic(asIScriptGeneric *gen) -{ - asUINT size = gen->GetArgDWord(0); - CScriptArray *self = (CScriptArray*)gen->GetObject(); - self->Reserve(size); -} - -static void ScriptArraySortAsc_Generic(asIScriptGeneric *gen) -{ - CScriptArray *self = (CScriptArray*)gen->GetObject(); - self->SortAsc(); -} - -static void ScriptArrayReverse_Generic(asIScriptGeneric *gen) -{ - CScriptArray *self = (CScriptArray*)gen->GetObject(); - self->Reverse(); -} - -static void ScriptArrayIsEmpty_Generic(asIScriptGeneric *gen) -{ - CScriptArray *self = (CScriptArray*)gen->GetObject(); - *reinterpret_cast(gen->GetAddressOfReturnLocation()) = self->IsEmpty(); -} - -static void ScriptArraySortAsc2_Generic(asIScriptGeneric *gen) -{ - asUINT index = gen->GetArgDWord(0); - asUINT count = gen->GetArgDWord(1); - CScriptArray *self = (CScriptArray*)gen->GetObject(); - self->SortAsc(index, count); -} - -static void ScriptArraySortDesc_Generic(asIScriptGeneric *gen) -{ - CScriptArray *self = (CScriptArray*)gen->GetObject(); - self->SortDesc(); -} - -static void ScriptArraySortDesc2_Generic(asIScriptGeneric *gen) -{ - asUINT index = gen->GetArgDWord(0); - asUINT count = gen->GetArgDWord(1); - CScriptArray *self = (CScriptArray*)gen->GetObject(); - self->SortDesc(index, count); -} - -static void ScriptArraySortCallback_Generic(asIScriptGeneric *gen) -{ - asIScriptFunction *callback = (asIScriptFunction*)gen->GetArgAddress(0); - asUINT startAt = gen->GetArgDWord(1); - asUINT count = gen->GetArgDWord(2); - CScriptArray *self = (CScriptArray*)gen->GetObject(); - self->Sort(callback, startAt, count); -} - -static void ScriptArrayAddRef_Generic(asIScriptGeneric *gen) -{ - CScriptArray *self = (CScriptArray*)gen->GetObject(); - self->AddRef(); -} - -static void ScriptArrayRelease_Generic(asIScriptGeneric *gen) -{ - CScriptArray *self = (CScriptArray*)gen->GetObject(); - self->Release(); -} - -static void ScriptArrayGetRefCount_Generic(asIScriptGeneric *gen) -{ - CScriptArray *self = (CScriptArray*)gen->GetObject(); - *reinterpret_cast(gen->GetAddressOfReturnLocation()) = self->GetRefCount(); -} - -static void ScriptArraySetFlag_Generic(asIScriptGeneric *gen) -{ - CScriptArray *self = (CScriptArray*)gen->GetObject(); - self->SetFlag(); -} - -static void ScriptArrayGetFlag_Generic(asIScriptGeneric *gen) -{ - CScriptArray *self = (CScriptArray*)gen->GetObject(); - *reinterpret_cast(gen->GetAddressOfReturnLocation()) = self->GetFlag(); -} - -static void ScriptArrayEnumReferences_Generic(asIScriptGeneric *gen) -{ - CScriptArray *self = (CScriptArray*)gen->GetObject(); - asIScriptEngine *engine = *(asIScriptEngine**)gen->GetAddressOfArg(0); - self->EnumReferences(engine); -} - -static void ScriptArrayReleaseAllHandles_Generic(asIScriptGeneric *gen) -{ - CScriptArray *self = (CScriptArray*)gen->GetObject(); - asIScriptEngine *engine = *(asIScriptEngine**)gen->GetAddressOfArg(0); - self->ReleaseAllHandles(engine); -} - -static void RegisterScriptArray_Generic(asIScriptEngine *engine) -{ - int r = 0; - UNUSED_VAR(r); - - engine->SetTypeInfoUserDataCleanupCallback(CleanupTypeInfoArrayCache, ARRAY_CACHE); - - r = engine->RegisterObjectType("array", 0, asOBJ_REF | asOBJ_GC | asOBJ_TEMPLATE); assert( r >= 0 ); - r = engine->RegisterObjectBehaviour("array", asBEHAVE_TEMPLATE_CALLBACK, "bool f(int&in, bool&out)", asFUNCTION(ScriptArrayTemplateCallback_Generic), asCALL_GENERIC); assert( r >= 0 ); - - r = engine->RegisterObjectBehaviour("array", asBEHAVE_FACTORY, "array@ f(int&in)", asFUNCTION(ScriptArrayFactory_Generic), asCALL_GENERIC); assert( r >= 0 ); - r = engine->RegisterObjectBehaviour("array", asBEHAVE_FACTORY, "array@ f(int&in, uint length) explicit", asFUNCTION(ScriptArrayFactory2_Generic), asCALL_GENERIC); assert( r >= 0 ); - r = engine->RegisterObjectBehaviour("array", asBEHAVE_FACTORY, "array@ f(int&in, uint length, const T &in value)", asFUNCTION(ScriptArrayFactoryDefVal_Generic), asCALL_GENERIC); assert( r >= 0 ); - r = engine->RegisterObjectBehaviour("array", asBEHAVE_LIST_FACTORY, "array@ f(int&in, int&in) {repeat T}", asFUNCTION(ScriptArrayListFactory_Generic), asCALL_GENERIC); assert( r >= 0 ); - r = engine->RegisterObjectBehaviour("array", asBEHAVE_ADDREF, "void f()", asFUNCTION(ScriptArrayAddRef_Generic), asCALL_GENERIC); assert( r >= 0 ); - r = engine->RegisterObjectBehaviour("array", asBEHAVE_RELEASE, "void f()", asFUNCTION(ScriptArrayRelease_Generic), asCALL_GENERIC); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "T &opIndex(int64 index)", asFUNCTION(ScriptArrayAt_Generic), asCALL_GENERIC); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "const T &opIndex(int64 index) const", asFUNCTION(ScriptArrayAt_Generic), asCALL_GENERIC); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "array &opAssign(const array&in)", asFUNCTION(ScriptArrayAssignment_Generic), asCALL_GENERIC); assert( r >= 0 ); - - r = engine->RegisterObjectMethod("array", "void insert_at(uint index, const T&in value)", asFUNCTION(ScriptArrayInsertAt_Generic), asCALL_GENERIC); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "void insert_at(uint index, const array& arr)", asFUNCTION(ScriptArrayInsertAtArray_Generic), asCALL_GENERIC); assert(r >= 0); - r = engine->RegisterObjectMethod("array", "void insert_last(const T&in value)", asFUNCTION(ScriptArrayInsertLast_Generic), asCALL_GENERIC); assert(r >= 0); - r = engine->RegisterObjectMethod("array", "void remove_at(uint index)", asFUNCTION(ScriptArrayRemoveAt_Generic), asCALL_GENERIC); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "void remove_last()", asFUNCTION(ScriptArrayRemoveLast_Generic), asCALL_GENERIC); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "void remove_range(uint start, uint count)", asFUNCTION(ScriptArrayRemoveRange_Generic), asCALL_GENERIC); assert(r >= 0); -#if AS_USE_ACCESSORS != 1 - r = engine->RegisterObjectMethod("array", "uint length() const", asFUNCTION(ScriptArrayLength_Generic), asCALL_GENERIC); assert( r >= 0 ); -#endif - r = engine->RegisterObjectMethod("array", "void reserve(uint length)", asFUNCTION(ScriptArrayReserve_Generic), asCALL_GENERIC); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "void resize(uint length)", asFUNCTION(ScriptArrayResize_Generic), asCALL_GENERIC); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "void sort_ascending()", asFUNCTION(ScriptArraySortAsc_Generic), asCALL_GENERIC); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "void sort_ascending(uint startAt, uint count)", asFUNCTION(ScriptArraySortAsc2_Generic), asCALL_GENERIC); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "void sort_descending()", asFUNCTION(ScriptArraySortDesc_Generic), asCALL_GENERIC); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "void sort_descending(uint startAt, uint count)", asFUNCTION(ScriptArraySortDesc2_Generic), asCALL_GENERIC); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "void reverse()", asFUNCTION(ScriptArrayReverse_Generic), asCALL_GENERIC); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "int find(const T&in if_handle_then_const value) const", asFUNCTION(ScriptArrayFind_Generic), asCALL_GENERIC); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "int find(uint startAt, const T&in if_handle_then_const value) const", asFUNCTION(ScriptArrayFind2_Generic), asCALL_GENERIC); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "int find_by_ref(const T&in if_handle_then_const value) const", asFUNCTION(ScriptArrayFindByRef_Generic), asCALL_GENERIC); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "int find_by_ref(uint startAt, const T&in if_handle_then_const value) const", asFUNCTION(ScriptArrayFindByRef2_Generic), asCALL_GENERIC); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "bool opEquals(const array&in) const", asFUNCTION(ScriptArrayEquals_Generic), asCALL_GENERIC); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "bool is_empty() const", asFUNCTION(ScriptArrayIsEmpty_Generic), asCALL_GENERIC); assert( r >= 0 ); - r = engine->RegisterFuncdef("bool array::less(const T&in if_handle_then_const a, const T&in if_handle_then_const b)"); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "void sort(const less &in, uint startAt = 0, uint count = uint(-1))", asFUNCTION(ScriptArraySortCallback_Generic), asCALL_GENERIC); assert(r >= 0); -#if AS_USE_STLNAMES != 1 && AS_USE_ACCESSORS == 1 - r = engine->RegisterObjectMethod("array", "uint get_length() const property", asFUNCTION(ScriptArrayLength_Generic), asCALL_GENERIC); assert( r >= 0 ); - r = engine->RegisterObjectMethod("array", "void set_length(uint) property", asFUNCTION(ScriptArrayResize_Generic), asCALL_GENERIC); assert( r >= 0 ); -#endif - r = engine->RegisterObjectBehaviour("array", asBEHAVE_GETREFCOUNT, "int f()", asFUNCTION(ScriptArrayGetRefCount_Generic), asCALL_GENERIC); assert( r >= 0 ); - r = engine->RegisterObjectBehaviour("array", asBEHAVE_SETGCFLAG, "void f()", asFUNCTION(ScriptArraySetFlag_Generic), asCALL_GENERIC); assert( r >= 0 ); - r = engine->RegisterObjectBehaviour("array", asBEHAVE_GETGCFLAG, "bool f()", asFUNCTION(ScriptArrayGetFlag_Generic), asCALL_GENERIC); assert( r >= 0 ); - r = engine->RegisterObjectBehaviour("array", asBEHAVE_ENUMREFS, "void f(int&in)", asFUNCTION(ScriptArrayEnumReferences_Generic), asCALL_GENERIC); assert( r >= 0 ); - r = engine->RegisterObjectBehaviour("array", asBEHAVE_RELEASEREFS, "void f(int&in)", asFUNCTION(ScriptArrayReleaseAllHandles_Generic), asCALL_GENERIC); assert( r >= 0 ); -} - -END_AS_NAMESPACE +#include +#include +#include +#include +#include // sprintf +#include +#include // std::sort + +#include "scriptarray.h" + +using namespace std; + +BEGIN_AS_NAMESPACE + +// This macro is used to avoid warnings about unused variables. +// Usually where the variables are only used in debug mode. +#define UNUSED_VAR(x) (void)(x) + +// Set the default memory routines +// Use the angelscript engine's memory routines by default +static asALLOCFUNC_t userAlloc = asAllocMem; +static asFREEFUNC_t userFree = asFreeMem; + +// Allows the application to set which memory routines should be used by the array object +void CScriptArray::SetMemoryFunctions(asALLOCFUNC_t allocFunc, asFREEFUNC_t freeFunc) +{ + userAlloc = allocFunc; + userFree = freeFunc; +} + +static void RegisterScriptArray_Native(asIScriptEngine *engine); +static void RegisterScriptArray_Generic(asIScriptEngine *engine); + +struct SArrayBuffer +{ + asDWORD maxElements; + asDWORD numElements; + asBYTE data[1]; +}; + +struct SArrayCache +{ + asIScriptFunction *cmpFunc; + asIScriptFunction *eqFunc; + int cmpFuncReturnCode; // To allow better error message in case of multiple matches + int eqFuncReturnCode; +}; + +// We just define a number here that we assume nobody else is using for +// object type user data. The add-ons have reserved the numbers 1000 +// through 1999 for this purpose, so we should be fine. +const asPWORD ARRAY_CACHE = 1000; + +static void CleanupTypeInfoArrayCache(asITypeInfo *type) +{ + SArrayCache *cache = reinterpret_cast(type->GetUserData(ARRAY_CACHE)); + if( cache ) + { + cache->~SArrayCache(); + userFree(cache); + } +} + +CScriptArray* CScriptArray::Create(asITypeInfo *ti, asUINT length) +{ + // Allocate the memory + void *mem = userAlloc(sizeof(CScriptArray)); + if( mem == 0 ) + { + asIScriptContext *ctx = asGetActiveContext(); + if( ctx ) + ctx->SetException("Out of memory"); + + return 0; + } + + // Initialize the object + CScriptArray *a = new(mem) CScriptArray(length, ti); + + return a; +} + +CScriptArray* CScriptArray::Create(asITypeInfo *ti, void *initList) +{ + // Allocate the memory + void *mem = userAlloc(sizeof(CScriptArray)); + if( mem == 0 ) + { + asIScriptContext *ctx = asGetActiveContext(); + if( ctx ) + ctx->SetException("Out of memory"); + + return 0; + } + + // Initialize the object + CScriptArray *a = new(mem) CScriptArray(ti, initList); + + return a; +} + +CScriptArray* CScriptArray::Create(asITypeInfo *ti, asUINT length, void *defVal) +{ + // Allocate the memory + void *mem = userAlloc(sizeof(CScriptArray)); + if( mem == 0 ) + { + asIScriptContext *ctx = asGetActiveContext(); + if( ctx ) + ctx->SetException("Out of memory"); + + return 0; + } + + // Initialize the object + CScriptArray *a = new(mem) CScriptArray(length, defVal, ti); + + return a; +} + +CScriptArray* CScriptArray::Create(asITypeInfo *ti) +{ + return CScriptArray::Create(ti, asUINT(0)); +} + +// This optional callback is called when the template type is first used by the compiler. +// It allows the application to validate if the template can be instantiated for the requested +// subtype at compile time, instead of at runtime. The output argument dontGarbageCollect +// allow the callback to tell the engine if the template instance type shouldn't be garbage collected, +// i.e. no asOBJ_GC flag. +static bool ScriptArrayTemplateCallback(asITypeInfo *ti, bool &dontGarbageCollect) +{ + // Make sure the subtype can be instantiated with a default factory/constructor, + // otherwise we won't be able to instantiate the elements. + int typeId = ti->GetSubTypeId(); + if( typeId == asTYPEID_VOID ) + return false; + if( (typeId & asTYPEID_MASK_OBJECT) && !(typeId & asTYPEID_OBJHANDLE) ) + { + asITypeInfo *subtype = ti->GetEngine()->GetTypeInfoById(typeId); + asDWORD flags = subtype->GetFlags(); + if( (flags & asOBJ_VALUE) && !(flags & asOBJ_POD) ) + { + // Verify that there is a default constructor + bool found = false; + for( asUINT n = 0; n < subtype->GetBehaviourCount(); n++ ) + { + asEBehaviours beh; + asIScriptFunction *func = subtype->GetBehaviourByIndex(n, &beh); + if( beh != asBEHAVE_CONSTRUCT ) continue; + + if( func->GetParamCount() == 0 ) + { + // Found the default constructor + found = true; + break; + } + } + + if( !found ) + { + // There is no default constructor + // TODO: Should format the message to give the name of the subtype for better understanding + ti->GetEngine()->WriteMessage("array", 0, 0, asMSGTYPE_ERROR, "The subtype has no default constructor"); + return false; + } + } + else if( (flags & asOBJ_REF) ) + { + bool found = false; + + // If value assignment for ref type has been disabled then the array + // can be created if the type has a default factory function + if( !ti->GetEngine()->GetEngineProperty(asEP_DISALLOW_VALUE_ASSIGN_FOR_REF_TYPE) ) + { + // Verify that there is a default factory + for( asUINT n = 0; n < subtype->GetFactoryCount(); n++ ) + { + asIScriptFunction *func = subtype->GetFactoryByIndex(n); + if( func->GetParamCount() == 0 ) + { + // Found the default factory + found = true; + break; + } + } + } + + if( !found ) + { + // No default factory + char buffer[1024]; + snprintf(buffer, sizeof(buffer), "The subtype '%s' has no default factory", subtype ? subtype->GetEngine()->GetTypeDeclaration(subtype->GetTypeId()) : "UNKNOWN"); + ti->GetEngine()->WriteMessage("array", 0, 0, asMSGTYPE_ERROR, buffer); + return false; + } + } + + // If the object type is not garbage collected then the array also doesn't need to be + if( !(flags & asOBJ_GC) ) + dontGarbageCollect = true; + } + else if( !(typeId & asTYPEID_OBJHANDLE) ) + { + // Arrays with primitives cannot form circular references, + // thus there is no need to garbage collect them + dontGarbageCollect = true; + } + else + { + assert( typeId & asTYPEID_OBJHANDLE ); + + // It is not necessary to set the array as garbage collected for all handle types. + // If it is possible to determine that the handle cannot refer to an object type + // that can potentially form a circular reference with the array then it is not + // necessary to make the array garbage collected. + asITypeInfo *subtype = ti->GetEngine()->GetTypeInfoById(typeId); + asDWORD flags = subtype->GetFlags(); + if( !(flags & asOBJ_GC) ) + { + if( (flags & asOBJ_SCRIPT_OBJECT) ) + { + // Even if a script class is by itself not garbage collected, it is possible + // that classes that derive from it may be, so it is not possible to know + // that no circular reference can occur. + if( (flags & asOBJ_NOINHERIT) ) + { + // A script class declared as final cannot be inherited from, thus + // we can be certain that the object cannot be garbage collected. + dontGarbageCollect = true; + } + } + else + { + // For application registered classes we assume the application knows + // what it is doing and don't mark the array as garbage collected unless + // the type is also garbage collected. + dontGarbageCollect = true; + } + } + } + + // The type is ok + return true; +} + +// Registers the template array type +void RegisterScriptArray(asIScriptEngine *engine, bool defaultArray) +{ + if( strstr(asGetLibraryOptions(), "AS_MAX_PORTABILITY") == 0 ) + RegisterScriptArray_Native(engine); + else + RegisterScriptArray_Generic(engine); + + if( defaultArray ) + { + int r = engine->RegisterDefaultArrayType("array"); assert( r >= 0 ); + UNUSED_VAR(r); + } +} + +static void RegisterScriptArray_Native(asIScriptEngine *engine) +{ + int r = 0; + UNUSED_VAR(r); + + // Register the object type user data clean up + engine->SetTypeInfoUserDataCleanupCallback(CleanupTypeInfoArrayCache, ARRAY_CACHE); + + // Register the array type as a template + r = engine->RegisterObjectType("array", 0, asOBJ_REF | asOBJ_GC | asOBJ_TEMPLATE); assert( r >= 0 ); + + // Register a callback for validating the subtype before it is used + r = engine->RegisterObjectBehaviour("array", asBEHAVE_TEMPLATE_CALLBACK, "bool f(int&in, bool&out)", asFUNCTION(ScriptArrayTemplateCallback), asCALL_CDECL); assert( r >= 0 ); + + // Templates receive the object type as the first parameter. To the script writer this is hidden + r = engine->RegisterObjectBehaviour("array", asBEHAVE_FACTORY, "array@ f(int&in)", asFUNCTIONPR(CScriptArray::Create, (asITypeInfo*), CScriptArray*), asCALL_CDECL); assert( r >= 0 ); + r = engine->RegisterObjectBehaviour("array", asBEHAVE_FACTORY, "array@ f(int&in, uint length) explicit", asFUNCTIONPR(CScriptArray::Create, (asITypeInfo*, asUINT), CScriptArray*), asCALL_CDECL); assert( r >= 0 ); + r = engine->RegisterObjectBehaviour("array", asBEHAVE_FACTORY, "array@ f(int&in, uint length, const T &in value)", asFUNCTIONPR(CScriptArray::Create, (asITypeInfo*, asUINT, void *), CScriptArray*), asCALL_CDECL); assert( r >= 0 ); + + // Register the factory that will be used for initialization lists + r = engine->RegisterObjectBehaviour("array", asBEHAVE_LIST_FACTORY, "array@ f(int&in type, int&in list) {repeat T}", asFUNCTIONPR(CScriptArray::Create, (asITypeInfo*, void*), CScriptArray*), asCALL_CDECL); assert( r >= 0 ); + + // The memory management methods + r = engine->RegisterObjectBehaviour("array", asBEHAVE_ADDREF, "void f()", asMETHOD(CScriptArray,AddRef), asCALL_THISCALL); assert( r >= 0 ); + r = engine->RegisterObjectBehaviour("array", asBEHAVE_RELEASE, "void f()", asMETHOD(CScriptArray,Release), asCALL_THISCALL); assert( r >= 0 ); + + // The index operator returns the template subtype + r = engine->RegisterObjectMethod("array", "T &opIndex(int64 index)", asMETHODPR(CScriptArray, At, (asINT64), void*), asCALL_THISCALL); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "const T &opIndex(int64 index) const", asMETHODPR(CScriptArray, At, (asINT64) const, const void*), asCALL_THISCALL); assert( r >= 0 ); + + // The assignment operator + r = engine->RegisterObjectMethod("array", "array &opAssign(const array&in)", asMETHOD(CScriptArray, operator=), asCALL_THISCALL); assert( r >= 0 ); + + // Other methods + r = engine->RegisterObjectMethod("array", "void insert_at(uint index, const T&in value)", asMETHODPR(CScriptArray, InsertAt, (asUINT, void *), void), asCALL_THISCALL); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "void insert_at(uint index, const array& arr)", asMETHODPR(CScriptArray, InsertAt, (asUINT, const CScriptArray &), void), asCALL_THISCALL); assert(r >= 0); + r = engine->RegisterObjectMethod("array", "void insert_last(const T&in value)", asMETHODPR(CScriptArray, InsertLast, (void*), void), asCALL_THISCALL); assert(r >= 0); + r = engine->RegisterObjectMethod("array", "void insert_last(const array& arr)", asMETHODPR(CScriptArray, InsertLast, (const CScriptArray &), void), asCALL_THISCALL); assert(r >= 0); + r = engine->RegisterObjectMethod("array", "void remove_at(uint index)", asMETHOD(CScriptArray, RemoveAt), asCALL_THISCALL); assert(r >= 0); + r = engine->RegisterObjectMethod("array", "void remove_last()", asMETHOD(CScriptArray, RemoveLast), asCALL_THISCALL); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "void remove_range(uint start, uint count)", asMETHOD(CScriptArray, RemoveRange), asCALL_THISCALL); assert(r >= 0); + // TODO: Should length() and resize() be deprecated as the property accessors do the same thing? + // TODO: Register as size() for consistency with other types +#if AS_USE_ACCESSORS != 1 + r = engine->RegisterObjectMethod("array", "uint length() const", asMETHOD(CScriptArray, GetSize), asCALL_THISCALL); assert( r >= 0 ); +#endif + r = engine->RegisterObjectMethod("array", "void reserve(uint length)", asMETHOD(CScriptArray, Reserve), asCALL_THISCALL); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "void resize(uint length)", asMETHODPR(CScriptArray, Resize, (asUINT), void), asCALL_THISCALL); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "void sort_ascending()", asMETHODPR(CScriptArray, SortAsc, (), void), asCALL_THISCALL); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "void sort_ascending(uint startAt, uint count)", asMETHODPR(CScriptArray, SortAsc, (asUINT, asUINT), void), asCALL_THISCALL); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "void sort_descending()", asMETHODPR(CScriptArray, SortDesc, (), void), asCALL_THISCALL); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "void sort_descending(uint startAt, uint count)", asMETHODPR(CScriptArray, SortDesc, (asUINT, asUINT), void), asCALL_THISCALL); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "void reverse()", asMETHOD(CScriptArray, Reverse), asCALL_THISCALL); assert( r >= 0 ); + // The token 'if_handle_then_const' tells the engine that if the type T is a handle, then it should refer to a read-only object + r = engine->RegisterObjectMethod("array", "int find(const T&in if_handle_then_const value) const", asMETHODPR(CScriptArray, Find, (void*) const, int), asCALL_THISCALL); assert( r >= 0 ); + // TODO: It should be "int find(const T&in value, uint startAt = 0) const" + r = engine->RegisterObjectMethod("array", "int find(uint startAt, const T&in if_handle_then_const value) const", asMETHODPR(CScriptArray, Find, (asUINT, void*) const, int), asCALL_THISCALL); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "int find_by_ref(const T&in if_handle_then_const value) const", asMETHODPR(CScriptArray, FindByRef, (void*) const, int), asCALL_THISCALL); assert( r >= 0 ); + // TODO: It should be "int find_by_ref(const T&in value, uint startAt = 0) const" + r = engine->RegisterObjectMethod("array", "int find_by_ref(uint startAt, const T&in if_handle_then_const value) const", asMETHODPR(CScriptArray, FindByRef, (asUINT, void*) const, int), asCALL_THISCALL); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "bool opEquals(const array&in) const", asMETHOD(CScriptArray, operator==), asCALL_THISCALL); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "bool is_empty() const", asMETHOD(CScriptArray, IsEmpty), asCALL_THISCALL); assert( r >= 0 ); + + // Sort with callback for comparison + r = engine->RegisterFuncdef("bool array::less(const T&in if_handle_then_const a, const T&in if_handle_then_const b)"); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "void sort(const less &in, uint startAt = 0, uint count = uint(-1))", asMETHODPR(CScriptArray, Sort, (asIScriptFunction*, asUINT, asUINT), void), asCALL_THISCALL); assert(r >= 0); + +#if AS_USE_STLNAMES != 1 && AS_USE_ACCESSORS == 1 + // Register virtual properties + r = engine->RegisterObjectMethod("array", "uint get_length() const property", asMETHOD(CScriptArray, GetSize), asCALL_THISCALL); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "void set_length(uint) property", asMETHODPR(CScriptArray, Resize, (asUINT), void), asCALL_THISCALL); assert( r >= 0 ); +#endif + + // Register GC behaviours in case the array needs to be garbage collected + r = engine->RegisterObjectBehaviour("array", asBEHAVE_GETREFCOUNT, "int f()", asMETHOD(CScriptArray, GetRefCount), asCALL_THISCALL); assert( r >= 0 ); + r = engine->RegisterObjectBehaviour("array", asBEHAVE_SETGCFLAG, "void f()", asMETHOD(CScriptArray, SetFlag), asCALL_THISCALL); assert( r >= 0 ); + r = engine->RegisterObjectBehaviour("array", asBEHAVE_GETGCFLAG, "bool f()", asMETHOD(CScriptArray, GetFlag), asCALL_THISCALL); assert( r >= 0 ); + r = engine->RegisterObjectBehaviour("array", asBEHAVE_ENUMREFS, "void f(int&in)", asMETHOD(CScriptArray, EnumReferences), asCALL_THISCALL); assert( r >= 0 ); + r = engine->RegisterObjectBehaviour("array", asBEHAVE_RELEASEREFS, "void f(int&in)", asMETHOD(CScriptArray, ReleaseAllHandles), asCALL_THISCALL); assert( r >= 0 ); + +#if AS_USE_STLNAMES == 1 + // Same as length + r = engine->RegisterObjectMethod("array", "uint size() const", asMETHOD(CScriptArray, GetSize), asCALL_THISCALL); assert( r >= 0 ); + // Same as isEmpty + r = engine->RegisterObjectMethod("array", "bool empty() const", asMETHOD(CScriptArray, IsEmpty), asCALL_THISCALL); assert( r >= 0 ); + // Same as insertLast + r = engine->RegisterObjectMethod("array", "void push_back(const T&in)", asMETHODPR(CScriptArray, InsertLast, (void*), void), asCALL_THISCALL); assert( r >= 0 ); + // Same as removeLast + r = engine->RegisterObjectMethod("array", "void pop_back()", asMETHOD(CScriptArray, RemoveLast), asCALL_THISCALL); assert( r >= 0 ); + // Same as insertAt + r = engine->RegisterObjectMethod("array", "void insert(uint index, const T&in value)", asMETHODPR(CScriptArray, InsertAt, (asUINT, void *), void), asCALL_THISCALL); assert(r >= 0); + r = engine->RegisterObjectMethod("array", "void insert(uint index, const array& arr)", asMETHODPR(CScriptArray, InsertAt, (asUINT, const CScriptArray &), void), asCALL_THISCALL); assert(r >= 0); + // Same as removeAt + r = engine->RegisterObjectMethod("array", "void erase(uint)", asMETHOD(CScriptArray, RemoveAt), asCALL_THISCALL); assert( r >= 0 ); +#endif +} + +CScriptArray &CScriptArray::operator=(const CScriptArray &other) +{ + // Only perform the copy if the array types are the same + if( &other != this && + other.GetArrayObjectType() == GetArrayObjectType() ) + { + // Make sure the arrays are of the same size + Resize(other.buffer->numElements); + + // Copy the value of each element + CopyBuffer(buffer, other.buffer); + } + + return *this; +} + +CScriptArray::CScriptArray(asITypeInfo *ti, void *buf) +{ + // The object type should be the template instance of the array + assert( ti && string(ti->GetName()) == "array" ); + + refCount = 1; + gcFlag = false; + objType = ti; + objType->AddRef(); + buffer = 0; + + Precache(); + + asIScriptEngine *engine = ti->GetEngine(); + + // Determine element size + if( subTypeId & asTYPEID_MASK_OBJECT ) + elementSize = sizeof(asPWORD); + else + elementSize = engine->GetSizeOfPrimitiveType(subTypeId); + + // Determine the initial size from the buffer + asUINT length = *(asUINT*)buf; + + // Make sure the array size isn't too large for us to handle + if( !CheckMaxSize(length) ) + { + // Don't continue with the initialization + return; + } + + // Copy the values of the array elements from the buffer + if( (ti->GetSubTypeId() & asTYPEID_MASK_OBJECT) == 0 ) + { + CreateBuffer(&buffer, length); + + // Copy the values of the primitive type into the internal buffer + if( length > 0 ) + memcpy(At(0), (((asUINT*)buf)+1), length * elementSize); + } + else if( ti->GetSubTypeId() & asTYPEID_OBJHANDLE ) + { + CreateBuffer(&buffer, length); + + // Copy the handles into the internal buffer + if( length > 0 ) + memcpy(At(0), (((asUINT*)buf)+1), length * elementSize); + + // With object handles it is safe to clear the memory in the received buffer + // instead of increasing the ref count. It will save time both by avoiding the + // call the increase ref, and also relieve the engine from having to release + // its references too + memset((((asUINT*)buf)+1), 0, length * elementSize); + } + else if( ti->GetSubType()->GetFlags() & asOBJ_REF ) + { + // Only allocate the buffer, but not the objects + subTypeId |= asTYPEID_OBJHANDLE; + CreateBuffer(&buffer, length); + if (!buffer) + throw std::bad_alloc(); + subTypeId &= ~asTYPEID_OBJHANDLE; + + // Copy the handles into the internal buffer + if( length > 0 ) + memcpy(buffer->data, (((asUINT*)buf)+1), length * elementSize); + + // For ref types we can do the same as for handles, as they are + // implicitly stored as handles. + memset((((asUINT*)buf)+1), 0, length * elementSize); + } + else + { + // TODO: Optimize by calling the copy constructor of the object instead of + // constructing with the default constructor and then assigning the value + // TODO: With C++11 ideally we should be calling the move constructor, instead + // of the copy constructor as the engine will just discard the objects in the + // buffer afterwards. + CreateBuffer(&buffer, length); + + // For value types we need to call the opAssign for each individual object + for( asUINT n = 0; n < length; n++ ) + { + void *obj = At(n); + asBYTE *srcObj = (asBYTE*)buf; + srcObj += 4 + n*ti->GetSubType()->GetSize(); + engine->AssignScriptObject(obj, srcObj, ti->GetSubType()); + } + } + + // Notify the GC of the successful creation + if( objType->GetFlags() & asOBJ_GC ) + objType->GetEngine()->NotifyGarbageCollectorOfNewObject(this, objType); +} + +CScriptArray::CScriptArray(asUINT length, asITypeInfo *ti) +{ + // The object type should be the template instance of the array + assert( ti && string(ti->GetName()) == "array" ); + + refCount = 1; + gcFlag = false; + objType = ti; + objType->AddRef(); + buffer = 0; + + Precache(); + + // Determine element size + if( subTypeId & asTYPEID_MASK_OBJECT ) + elementSize = sizeof(asPWORD); + else + elementSize = objType->GetEngine()->GetSizeOfPrimitiveType(subTypeId); + + // Make sure the array size isn't too large for us to handle + if( !CheckMaxSize(length) ) + { + // Don't continue with the initialization + return; + } + + CreateBuffer(&buffer, length); + + // Notify the GC of the successful creation + if( objType->GetFlags() & asOBJ_GC ) + objType->GetEngine()->NotifyGarbageCollectorOfNewObject(this, objType); +} + +CScriptArray::CScriptArray(const CScriptArray &other) +{ + refCount = 1; + gcFlag = false; + objType = other.objType; + objType->AddRef(); + buffer = 0; + + Precache(); + + elementSize = other.elementSize; + + if( objType->GetFlags() & asOBJ_GC ) + objType->GetEngine()->NotifyGarbageCollectorOfNewObject(this, objType); + + CreateBuffer(&buffer, 0); + + // Copy the content + *this = other; +} + +CScriptArray::CScriptArray(asUINT length, void *defVal, asITypeInfo *ti) +{ + // The object type should be the template instance of the array + assert( ti && string(ti->GetName()) == "array" ); + + refCount = 1; + gcFlag = false; + objType = ti; + objType->AddRef(); + buffer = 0; + + Precache(); + + // Determine element size + if( subTypeId & asTYPEID_MASK_OBJECT ) + elementSize = sizeof(asPWORD); + else + elementSize = objType->GetEngine()->GetSizeOfPrimitiveType(subTypeId); + + // Make sure the array size isn't too large for us to handle + if( !CheckMaxSize(length) ) + { + // Don't continue with the initialization + return; + } + + CreateBuffer(&buffer, length); + + // Notify the GC of the successful creation + if( objType->GetFlags() & asOBJ_GC ) + objType->GetEngine()->NotifyGarbageCollectorOfNewObject(this, objType); + + // Initialize the elements with the default value + for( asUINT n = 0; n < GetSize(); n++ ) + SetValue(n, defVal); +} + +void CScriptArray::SetValue(asINT64 index, void *value) +{ + // At() will take care of the out-of-bounds checking, though + // if called from the application then nothing will be done + void *ptr = At(index); + if( ptr == 0 ) return; + + if ((subTypeId & ~asTYPEID_MASK_SEQNBR) && !(subTypeId & asTYPEID_OBJHANDLE)) + { + asITypeInfo *subType = objType->GetSubType(); + if (subType->GetFlags() & asOBJ_ASHANDLE) + { + // For objects that should work as handles we must use the opHndlAssign method + // TODO: Must support alternative syntaxes as well + // TODO: Move the lookup of the opHndlAssign method to Precache() so it is only done once + string decl = string(subType->GetName()) + "& opHndlAssign(const " + string(subType->GetName()) + "&in)"; + asIScriptFunction* func = subType->GetMethodByDecl(decl.c_str()); + if (func) + { + // TODO: Reuse active context if existing + asIScriptEngine* engine = objType->GetEngine(); + asIScriptContext* ctx = engine->RequestContext(); + ctx->Prepare(func); + ctx->SetObject(ptr); + ctx->SetArgAddress(0, value); + // TODO: Handle errors + ctx->Execute(); + engine->ReturnContext(ctx); + } + else + { + // opHndlAssign doesn't exist, so try ordinary value assign instead + objType->GetEngine()->AssignScriptObject(ptr, value, subType); + } + } + else + objType->GetEngine()->AssignScriptObject(ptr, value, subType); + } + else if( subTypeId & asTYPEID_OBJHANDLE ) + { + void *tmp = *(void**)ptr; + *(void**)ptr = *(void**)value; + objType->GetEngine()->AddRefScriptObject(*(void**)value, objType->GetSubType()); + if( tmp ) + objType->GetEngine()->ReleaseScriptObject(tmp, objType->GetSubType()); + } + else if( subTypeId == asTYPEID_BOOL || + subTypeId == asTYPEID_INT8 || + subTypeId == asTYPEID_UINT8 ) + *(char*)ptr = *(char*)value; + else if( subTypeId == asTYPEID_INT16 || + subTypeId == asTYPEID_UINT16 ) + *(short*)ptr = *(short*)value; + else if( subTypeId == asTYPEID_INT32 || + subTypeId == asTYPEID_UINT32 || + subTypeId == asTYPEID_FLOAT || + subTypeId > asTYPEID_DOUBLE ) // enums have a type id larger than doubles + *(int*)ptr = *(int*)value; + else if( subTypeId == asTYPEID_INT64 || + subTypeId == asTYPEID_UINT64 || + subTypeId == asTYPEID_DOUBLE ) + *(double*)ptr = *(double*)value; +} + +CScriptArray::~CScriptArray() +{ + if( buffer ) + { + DeleteBuffer(buffer); + buffer = 0; + } + if( objType ) objType->Release(); +} + +asUINT CScriptArray::GetSize() const +{ + return buffer->numElements; +} + +bool CScriptArray::IsEmpty() const +{ + return buffer->numElements == 0; +} + +void CScriptArray::Reserve(asUINT maxElements) +{ + if( maxElements <= buffer->maxElements ) + return; + + if( !CheckMaxSize(maxElements) ) + return; + + // Allocate memory for the buffer + SArrayBuffer *newBuffer = reinterpret_cast(userAlloc(sizeof(SArrayBuffer)-1 + elementSize*maxElements)); + if( newBuffer ) + { + newBuffer->numElements = buffer->numElements; + newBuffer->maxElements = maxElements; + } + else + { + // Out of memory + asIScriptContext *ctx = asGetActiveContext(); + if( ctx ) + ctx->SetException("Out of memory"); + return; + } + + // As objects in arrays of objects are not stored inline, it is safe to use memcpy here + // since we're just copying the pointers to objects and not the actual objects. + memcpy(newBuffer->data, buffer->data, buffer->numElements*elementSize); + + // Release the old buffer + userFree(buffer); + + buffer = newBuffer; +} + +void CScriptArray::Resize(asUINT numElements) +{ + if( !CheckMaxSize(numElements) ) + return; + + Resize((int)numElements - (int)buffer->numElements, (asUINT)-1); +} + +void CScriptArray::RemoveRange(asUINT start, asUINT count) +{ + if (count == 0) + return; + + if( buffer == 0 || start > buffer->numElements ) + { + // If this is called from a script we raise a script exception + asIScriptContext *ctx = asGetActiveContext(); + if (ctx) + ctx->SetException("Index out of bounds"); + return; + } + + // Cap count to the end of the array + if (start + count > buffer->numElements) + count = buffer->numElements - start; + + // Destroy the elements that are being removed + Destruct(buffer, start, start + count); + + // Compact the elements + // As objects in arrays of objects are not stored inline, it is safe to use memmove here + // since we're just copying the pointers to objects and not the actual objects. + memmove(buffer->data + start*elementSize, buffer->data + (start + count)*elementSize, (buffer->numElements - start - count)*elementSize); + buffer->numElements -= count; +} + +// Internal +void CScriptArray::Resize(int delta, asUINT at) +{ + if( delta < 0 ) + { + if( -delta > (int)buffer->numElements ) + delta = -(int)buffer->numElements; + if( at > buffer->numElements + delta ) + at = buffer->numElements + delta; + } + else if( delta > 0 ) + { + // Make sure the array size isn't too large for us to handle + if( delta > 0 && !CheckMaxSize(buffer->numElements + delta) ) + return; + + if( at > buffer->numElements ) + at = buffer->numElements; + } + + if( delta == 0 ) return; + + if( buffer->maxElements < buffer->numElements + delta ) + { + // Allocate memory for the buffer + SArrayBuffer *newBuffer = reinterpret_cast(userAlloc(sizeof(SArrayBuffer)-1 + elementSize*(buffer->numElements + delta))); + if( newBuffer ) + { + newBuffer->numElements = buffer->numElements + delta; + newBuffer->maxElements = newBuffer->numElements; + } + else + { + // Out of memory + asIScriptContext *ctx = asGetActiveContext(); + if( ctx ) + ctx->SetException("Out of memory"); + return; + } + + // As objects in arrays of objects are not stored inline, it is safe to use memcpy here + // since we're just copying the pointers to objects and not the actual objects. + memcpy(newBuffer->data, buffer->data, at*elementSize); + if( at < buffer->numElements ) + memcpy(newBuffer->data + (at+delta)*elementSize, buffer->data + at*elementSize, (buffer->numElements-at)*elementSize); + + // Initialize the new elements with default values + Construct(newBuffer, at, at+delta); + + // Release the old buffer + userFree(buffer); + + buffer = newBuffer; + } + else if( delta < 0 ) + { + Destruct(buffer, at, at-delta); + // As objects in arrays of objects are not stored inline, it is safe to use memmove here + // since we're just copying the pointers to objects and not the actual objects. + memmove(buffer->data + at*elementSize, buffer->data + (at-delta)*elementSize, (buffer->numElements - (at-delta))*elementSize); + buffer->numElements += delta; + } + else + { + // As objects in arrays of objects are not stored inline, it is safe to use memmove here + // since we're just copying the pointers to objects and not the actual objects. + memmove(buffer->data + (at+delta)*elementSize, buffer->data + at*elementSize, (buffer->numElements - at)*elementSize); + Construct(buffer, at, at+delta); + buffer->numElements += delta; + } +} + +// internal +bool CScriptArray::CheckMaxSize(asUINT numElements) +{ + // This code makes sure the size of the buffer that is allocated + // for the array doesn't overflow and becomes smaller than requested + + asUINT maxSize = 0xFFFFFFFFul - sizeof(SArrayBuffer) + 1; + if( elementSize > 0 ) + maxSize /= elementSize; + + if( numElements > maxSize ) + { + asIScriptContext *ctx = asGetActiveContext(); + if( ctx ) + ctx->SetException("Too large array size"); + + return false; + } + + // OK + return true; +} + +asITypeInfo *CScriptArray::GetArrayObjectType() const +{ + return objType; +} + +int CScriptArray::GetArrayTypeId() const +{ + return objType->GetTypeId(); +} + +int CScriptArray::GetElementTypeId() const +{ + return subTypeId; +} + +void CScriptArray::InsertAt(asUINT index, void *value) +{ + if( index > buffer->numElements ) + { + // If this is called from a script we raise a script exception + asIScriptContext *ctx = asGetActiveContext(); + if( ctx ) + ctx->SetException("Index out of bounds"); + return; + } + + // Make room for the new element + Resize(1, index); + + // Set the value of the new element + SetValue(index, value); +} + +void CScriptArray::InsertAt(asUINT index, const CScriptArray &arr) +{ + if (index > buffer->numElements) + { + asIScriptContext *ctx = asGetActiveContext(); + if (ctx) + ctx->SetException("Index out of bounds"); + return; + } + + if (objType != arr.objType) + { + // This shouldn't really be possible to happen when + // called from a script, but let's check for it anyway + asIScriptContext *ctx = asGetActiveContext(); + if (ctx) + ctx->SetException("Mismatching array types"); + return; + } + + asUINT elements = arr.GetSize(); + Resize(elements, index); + if (&arr != this) + { + for (asUINT n = 0; n < arr.GetSize(); n++) + { + // This const cast is allowed, since we know the + // value will only be used to make a copy of it + void *value = const_cast(arr.At(n)); + SetValue(index + n, value); + } + } + else + { + // The array that is being inserted is the same as this one. + // So we should iterate over the elements before the index, + // and then the elements after + for (asUINT n = 0; n < index; n++) + { + // This const cast is allowed, since we know the + // value will only be used to make a copy of it + void *value = const_cast(arr.At(n)); + SetValue(index + n, value); + } + + for (asUINT n = index + elements, m = 0; n < arr.GetSize(); n++, m++) + { + // This const cast is allowed, since we know the + // value will only be used to make a copy of it + void *value = const_cast(arr.At(n)); + SetValue(index + index + m, value); + } + } +} + +void CScriptArray::InsertLast(void *value) +{ + InsertAt(buffer->numElements, value); +} + +void CScriptArray::InsertLast(const CScriptArray &arr) +{ + InsertAt(buffer->numElements, arr); +} + +void CScriptArray::RemoveAt(asUINT index) +{ + if( index >= buffer->numElements ) + { + // If this is called from a script we raise a script exception + asIScriptContext *ctx = asGetActiveContext(); + if( ctx ) + ctx->SetException("Index out of bounds"); + return; + } + + // Remove the element + Resize(-1, index); +} + +void CScriptArray::RemoveLast() +{ + RemoveAt(buffer->numElements-1); +} + +// Return a pointer to the array element. Returns 0 if the index is out of bounds +const void *CScriptArray::At(asINT64 index) const +{ + if( index < 0) index = buffer->numElements + index; + if( buffer == 0 || index < 0 || index >= buffer->numElements ) + { + // If this is called from a script we raise a script exception + asIScriptContext *ctx = asGetActiveContext(); + if( ctx ) + ctx->SetException("Index out of bounds"); + return 0; + } + + if( (subTypeId & asTYPEID_MASK_OBJECT) && !(subTypeId & asTYPEID_OBJHANDLE) ) + return *(void**)(buffer->data + elementSize*index); + else + return buffer->data + elementSize*index; +} +void *CScriptArray::At(asINT64 index) +{ + return const_cast(const_cast(this)->At(index)); +} + +void *CScriptArray::GetBuffer() +{ + return buffer->data; +} + + +// internal +void CScriptArray::CreateBuffer(SArrayBuffer **buf, asUINT numElements) +{ + *buf = reinterpret_cast(userAlloc(sizeof(SArrayBuffer)-1+elementSize*numElements)); + + if( *buf ) + { + (*buf)->numElements = numElements; + (*buf)->maxElements = numElements; + Construct(*buf, 0, numElements); + } + else + { + // Oops, out of memory + asIScriptContext *ctx = asGetActiveContext(); + if( ctx ) + ctx->SetException("Out of memory"); + } +} + +// internal +void CScriptArray::DeleteBuffer(SArrayBuffer *buf) +{ + Destruct(buf, 0, buf->numElements); + + // Free the buffer + userFree(buf); +} + +// internal +void CScriptArray::Construct(SArrayBuffer *buf, asUINT start, asUINT end) +{ + if( (subTypeId & asTYPEID_MASK_OBJECT) && !(subTypeId & asTYPEID_OBJHANDLE) ) + { + // Create an object using the default constructor/factory for each element + void **max = (void**)(buf->data + end * sizeof(void*)); + void **d = (void**)(buf->data + start * sizeof(void*)); + + asIScriptEngine *engine = objType->GetEngine(); + asITypeInfo *subType = objType->GetSubType(); + + for( ; d < max; d++ ) + { + *d = (void*)engine->CreateScriptObject(subType); + if( *d == 0 ) + { + // Set the remaining entries to null so the destructor + // won't attempt to destroy invalid objects later + memset(d, 0, sizeof(void*)*(max-d)); + + // There is no need to set an exception on the context, + // as CreateScriptObject has already done that + return; + } + } + } + else + { + // Set all elements to zero whether they are handles or primitives + void *d = (void*)(buf->data + start * elementSize); + memset(d, 0, (end-start)*elementSize); + } +} + +// internal +void CScriptArray::Destruct(SArrayBuffer *buf, asUINT start, asUINT end) +{ + if( subTypeId & asTYPEID_MASK_OBJECT ) + { + asIScriptEngine *engine = objType->GetEngine(); + + void **max = (void**)(buf->data + end * sizeof(void*)); + void **d = (void**)(buf->data + start * sizeof(void*)); + + for( ; d < max; d++ ) + { + if( *d ) + engine->ReleaseScriptObject(*d, objType->GetSubType()); + } + } +} + + +// internal +bool CScriptArray::Less(const void *a, const void *b, bool asc) +{ + if( !asc ) + { + // Swap items + const void *TEMP = a; + a = b; + b = TEMP; + } + + if( !(subTypeId & ~asTYPEID_MASK_SEQNBR) ) + { + // Simple compare of values + switch( subTypeId ) + { + #define COMPARE(T) *((T*)a) < *((T*)b) + case asTYPEID_BOOL: return COMPARE(bool); + case asTYPEID_INT8: return COMPARE(asINT8); + case asTYPEID_INT16: return COMPARE(asINT16); + case asTYPEID_INT32: return COMPARE(asINT32); + case asTYPEID_INT64: return COMPARE(asINT64); + case asTYPEID_UINT8: return COMPARE(asBYTE); + case asTYPEID_UINT16: return COMPARE(asWORD); + case asTYPEID_UINT32: return COMPARE(asDWORD); + case asTYPEID_UINT64: return COMPARE(asQWORD); + case asTYPEID_FLOAT: return COMPARE(float); + case asTYPEID_DOUBLE: return COMPARE(double); + default: return COMPARE(signed int); // All enums fall in this case. TODO: update this when enums can have different sizes and types + #undef COMPARE + } + } + + return false; +} + +void CScriptArray::Reverse() +{ + asUINT size = GetSize(); + + if( size >= 2 ) + { + asBYTE TEMP[16]; + + for( asUINT i = 0; i < size / 2; i++ ) + { + Copy(TEMP, GetArrayItemPointer(i)); + Copy(GetArrayItemPointer(i), GetArrayItemPointer(size - i - 1)); + Copy(GetArrayItemPointer(size - i - 1), TEMP); + } + } +} + +bool CScriptArray::operator==(const CScriptArray &other) const +{ + if( objType != other.objType ) + return false; + + if( GetSize() != other.GetSize() ) + return false; + + asIScriptContext *cmpContext = 0; + bool isNested = false; + + if( subTypeId & ~asTYPEID_MASK_SEQNBR ) + { + // Try to reuse the active context + cmpContext = asGetActiveContext(); + if( cmpContext ) + { + if( cmpContext->GetEngine() == objType->GetEngine() && cmpContext->PushState() >= 0 ) + isNested = true; + else + cmpContext = 0; + } + if( cmpContext == 0 ) + { + // TODO: Ideally this context would be retrieved from a pool, so we don't have to + // create a new one everytime. We could keep a context with the array object + // but that would consume a lot of resources as each context is quite heavy. + cmpContext = objType->GetEngine()->CreateContext(); + } + } + + // Check if all elements are equal + bool isEqual = true; + SArrayCache *cache = reinterpret_cast(objType->GetUserData(ARRAY_CACHE)); + for( asUINT n = 0; n < GetSize(); n++ ) + if( !Equals(At(n), other.At(n), cmpContext, cache) ) + { + isEqual = false; + break; + } + + if( cmpContext ) + { + if( isNested ) + { + asEContextState state = cmpContext->GetState(); + cmpContext->PopState(); + if( state == asEXECUTION_ABORTED ) + cmpContext->Abort(); + } + else + cmpContext->Release(); + } + + return isEqual; +} + +// internal +bool CScriptArray::Equals(const void *a, const void *b, asIScriptContext *ctx, SArrayCache *cache) const +{ + if( !(subTypeId & ~asTYPEID_MASK_SEQNBR) ) + { + // Simple compare of values + switch( subTypeId ) + { + #define COMPARE(T) *((T*)a) == *((T*)b) + case asTYPEID_BOOL: return COMPARE(bool); + case asTYPEID_INT8: return COMPARE(asINT8); + case asTYPEID_INT16: return COMPARE(asINT16); + case asTYPEID_INT32: return COMPARE(asINT32); + case asTYPEID_INT64: return COMPARE(asINT64); + case asTYPEID_UINT8: return COMPARE(asBYTE); + case asTYPEID_UINT16: return COMPARE(asWORD); + case asTYPEID_UINT32: return COMPARE(asDWORD); + case asTYPEID_UINT64: return COMPARE(asQWORD); + case asTYPEID_FLOAT: return COMPARE(float); + case asTYPEID_DOUBLE: return COMPARE(double); + default: return COMPARE(signed int); // All enums fall here. TODO: update this when enums can have different sizes and types + #undef COMPARE + } + } + else + { + int r = 0; + + if( subTypeId & asTYPEID_OBJHANDLE ) + { + // Allow the find to work even if the array contains null handles + if( *(void**)a == *(void**)b ) return true; + } + + // Execute object opEquals if available + if( cache && cache->eqFunc ) + { + // TODO: Add proper error handling + r = ctx->Prepare(cache->eqFunc); assert(r >= 0); + + if( subTypeId & asTYPEID_OBJHANDLE ) + { + r = ctx->SetObject(*((void**)a)); assert(r >= 0); + r = ctx->SetArgObject(0, *((void**)b)); assert(r >= 0); + } + else + { + r = ctx->SetObject((void*)a); assert(r >= 0); + r = ctx->SetArgObject(0, (void*)b); assert(r >= 0); + } + + r = ctx->Execute(); + + if( r == asEXECUTION_FINISHED ) + return ctx->GetReturnByte() != 0; + + return false; + } + + // Execute object opCmp if available + if( cache && cache->cmpFunc ) + { + // TODO: Add proper error handling + r = ctx->Prepare(cache->cmpFunc); assert(r >= 0); + + if( subTypeId & asTYPEID_OBJHANDLE ) + { + r = ctx->SetObject(*((void**)a)); assert(r >= 0); + r = ctx->SetArgObject(0, *((void**)b)); assert(r >= 0); + } + else + { + r = ctx->SetObject((void*)a); assert(r >= 0); + r = ctx->SetArgObject(0, (void*)b); assert(r >= 0); + } + + r = ctx->Execute(); + + if( r == asEXECUTION_FINISHED ) + return (int)ctx->GetReturnDWord() == 0; + + return false; + } + } + + return false; +} + +int CScriptArray::FindByRef(void *ref) const +{ + return FindByRef(0, ref); +} + +int CScriptArray::FindByRef(asUINT startAt, void *ref) const +{ + // Find the matching element by its reference + asUINT size = GetSize(); + if( subTypeId & asTYPEID_OBJHANDLE ) + { + // Dereference the pointer + ref = *(void**)ref; + for( asUINT i = startAt; i < size; i++ ) + { + if( *(void**)At(i) == ref ) + return i; + } + } + else + { + // Compare the reference directly + for( asUINT i = startAt; i < size; i++ ) + { + if( At(i) == ref ) + return i; + } + } + + return -1; +} + +int CScriptArray::Find(void *value) const +{ + return Find(0, value); +} + +int CScriptArray::Find(asUINT startAt, void *value) const +{ + // Check if the subtype really supports find() + // TODO: Can't this be done at compile time too by the template callback + SArrayCache *cache = 0; + if( subTypeId & ~asTYPEID_MASK_SEQNBR ) + { + cache = reinterpret_cast(objType->GetUserData(ARRAY_CACHE)); + if( !cache || (cache->cmpFunc == 0 && cache->eqFunc == 0) ) + { + asIScriptContext *ctx = asGetActiveContext(); + asITypeInfo* subType = objType->GetEngine()->GetTypeInfoById(subTypeId); + + // Throw an exception + if( ctx ) + { + char tmp[512]; + + if( cache && cache->eqFuncReturnCode == asMULTIPLE_FUNCTIONS ) +#if defined(_MSC_VER) && _MSC_VER >= 1500 && !defined(__S3E__) + sprintf_s(tmp, 512, "Type '%s' has multiple matching opEquals or opCmp methods", subType->GetName()); +#else + snprintf(tmp, 512, "Type '%s' has multiple matching opEquals or opCmp methods", subType->GetName()); +#endif + else +#if defined(_MSC_VER) && _MSC_VER >= 1500 && !defined(__S3E__) + sprintf_s(tmp, 512, "Type '%s' does not have a matching opEquals or opCmp method", subType->GetName()); +#else + snprintf(tmp, 512, "Type '%s' does not have a matching opEquals or opCmp method", subType->GetName()); +#endif + ctx->SetException(tmp); + } + + return -1; + } + } + + asIScriptContext *cmpContext = 0; + bool isNested = false; + + if( subTypeId & ~asTYPEID_MASK_SEQNBR ) + { + // Try to reuse the active context + cmpContext = asGetActiveContext(); + if( cmpContext ) + { + if( cmpContext->GetEngine() == objType->GetEngine() && cmpContext->PushState() >= 0 ) + isNested = true; + else + cmpContext = 0; + } + if( cmpContext == 0 ) + { + // TODO: Ideally this context would be retrieved from a pool, so we don't have to + // create a new one everytime. We could keep a context with the array object + // but that would consume a lot of resources as each context is quite heavy. + cmpContext = objType->GetEngine()->CreateContext(); + } + } + + // Find the matching element + int ret = -1; + asUINT size = GetSize(); + + for( asUINT i = startAt; i < size; i++ ) + { + // value passed by reference + if( Equals(At(i), value, cmpContext, cache) ) + { + ret = (int)i; + break; + } + } + + if( cmpContext ) + { + if( isNested ) + { + asEContextState state = cmpContext->GetState(); + cmpContext->PopState(); + if( state == asEXECUTION_ABORTED ) + cmpContext->Abort(); + } + else + cmpContext->Release(); + } + + return ret; +} + + + +// internal +// Copy object handle or primitive value +// Even in arrays of objects the objects are allocated on +// the heap and the array stores the pointers to the objects +void CScriptArray::Copy(void *dst, void *src) +{ + memcpy(dst, src, elementSize); +} + + +// internal +// Swap two elements +// Even in arrays of objects the objects are allocated on +// the heap and the array stores the pointers to the objects. +void CScriptArray::Swap(void* a, void* b) +{ + asBYTE tmp[16]; + Copy(tmp, a); + Copy(a, b); + Copy(b, tmp); +} + + +// internal +// Return pointer to array item (object handle or primitive value) +void *CScriptArray::GetArrayItemPointer(int index) +{ + return buffer->data + index * elementSize; +} + +// internal +// Return pointer to data in buffer (object or primitive) +void *CScriptArray::GetDataPointer(void *buf) +{ + if ((subTypeId & asTYPEID_MASK_OBJECT) && !(subTypeId & asTYPEID_OBJHANDLE) ) + { + // Real address of object + return reinterpret_cast(*(size_t*)buf); + } + else + { + // Primitive is just a raw data + return buf; + } +} + + +// Sort ascending +void CScriptArray::SortAsc() +{ + Sort(0, GetSize(), true); +} + +// Sort ascending +void CScriptArray::SortAsc(asUINT startAt, asUINT count) +{ + Sort(startAt, count, true); +} + +// Sort descending +void CScriptArray::SortDesc() +{ + Sort(0, GetSize(), false); +} + +// Sort descending +void CScriptArray::SortDesc(asUINT startAt, asUINT count) +{ + Sort(startAt, count, false); +} + + +// internal +void CScriptArray::Sort(asUINT startAt, asUINT count, bool asc) +{ + // Subtype isn't primitive and doesn't have opCmp + SArrayCache *cache = reinterpret_cast(objType->GetUserData(ARRAY_CACHE)); + if( subTypeId & ~asTYPEID_MASK_SEQNBR ) + { + if( !cache || cache->cmpFunc == 0 ) + { + asIScriptContext *ctx = asGetActiveContext(); + asITypeInfo* subType = objType->GetEngine()->GetTypeInfoById(subTypeId); + + // Throw an exception + if( ctx ) + { + char tmp[512]; + + if( cache && cache->cmpFuncReturnCode == asMULTIPLE_FUNCTIONS ) +#if defined(_MSC_VER) && _MSC_VER >= 1500 && !defined(__S3E__) + sprintf_s(tmp, 512, "Type '%s' has multiple matching opCmp methods", subType->GetName()); +#else + snprintf(tmp, 512, "Type '%s' has multiple matching opCmp methods", subType->GetName()); +#endif + else +#if defined(_MSC_VER) && _MSC_VER >= 1500 && !defined(__S3E__) + sprintf_s(tmp, 512, "Type '%s' does not have a matching opCmp method", subType->GetName()); +#else + snprintf(tmp, 512, "Type '%s' does not have a matching opCmp method", subType->GetName()); +#endif + + ctx->SetException(tmp); + } + + return; + } + } + + // No need to sort + if( count < 2 ) + { + return; + } + + int start = startAt; + int end = startAt + count; + + // Check if we could access invalid item while sorting + if( start >= (int)buffer->numElements || end > (int)buffer->numElements ) + { + asIScriptContext *ctx = asGetActiveContext(); + + // Throw an exception + if( ctx ) + { + ctx->SetException("Index out of bounds"); + } + + return; + } + + if( subTypeId & ~asTYPEID_MASK_SEQNBR ) + { + asIScriptContext *cmpContext = 0; + bool isNested = false; + + // Try to reuse the active context + cmpContext = asGetActiveContext(); + if( cmpContext ) + { + if( cmpContext->GetEngine() == objType->GetEngine() && cmpContext->PushState() >= 0 ) + isNested = true; + else + cmpContext = 0; + } + if( cmpContext == 0 ) + cmpContext = objType->GetEngine()->RequestContext(); + + // Do the sorting + struct { + bool asc; + asIScriptContext *cmpContext; + asIScriptFunction *cmpFunc; + bool operator()(void *a, void *b) const + { + if( !asc ) + { + // Swap items + void *TEMP = a; + a = b; + b = TEMP; + } + + int r = 0; + + // Allow sort to work even if the array contains null handles + if( a == 0 ) return true; + if( b == 0 ) return false; + + // Execute object opCmp + if( cmpFunc ) + { + // TODO: Add proper error handling + r = cmpContext->Prepare(cmpFunc); assert(r >= 0); + r = cmpContext->SetObject(a); assert(r >= 0); + r = cmpContext->SetArgObject(0, b); assert(r >= 0); + r = cmpContext->Execute(); + + if( r == asEXECUTION_FINISHED ) + { + return (int)cmpContext->GetReturnDWord() < 0; + } + } + + return false; + } + } customLess = {asc, cmpContext, cache ? cache->cmpFunc : 0}; + std::sort((void**)GetArrayItemPointer(start), (void**)GetArrayItemPointer(end), customLess); + + // Clean up + if( cmpContext ) + { + if( isNested ) + { + asEContextState state = cmpContext->GetState(); + cmpContext->PopState(); + if( state == asEXECUTION_ABORTED ) + cmpContext->Abort(); + } + else + objType->GetEngine()->ReturnContext(cmpContext); + } + } + else + { + // TODO: Use std::sort for primitive types too + + // Insertion sort + asBYTE tmp[16]; + for( int i = start + 1; i < end; i++ ) + { + Copy(tmp, GetArrayItemPointer(i)); + + int j = i - 1; + + while( j >= start && Less(GetDataPointer(tmp), At(j), asc) ) + { + Copy(GetArrayItemPointer(j + 1), GetArrayItemPointer(j)); + j--; + } + + Copy(GetArrayItemPointer(j + 1), tmp); + } + } +} + +// Sort with script callback for comparing elements +void CScriptArray::Sort(asIScriptFunction *func, asUINT startAt, asUINT count) +{ + // No need to sort + if (count < 2) + return; + + // Check if we could access invalid item while sorting + asUINT start = startAt; + asUINT end = asQWORD(startAt) + asQWORD(count) >= (asQWORD(1)<<32) ? 0xFFFFFFFF : startAt + count; + if (end > buffer->numElements) + end = buffer->numElements; + + if (start >= buffer->numElements) + { + asIScriptContext *ctx = asGetActiveContext(); + + // Throw an exception + if (ctx) + ctx->SetException("Index out of bounds"); + + return; + } + + asIScriptContext *cmpContext = 0; + bool isNested = false; + + // Try to reuse the active context + cmpContext = asGetActiveContext(); + if (cmpContext) + { + if (cmpContext->GetEngine() == objType->GetEngine() && cmpContext->PushState() >= 0) + isNested = true; + else + cmpContext = 0; + } + if (cmpContext == 0) + cmpContext = objType->GetEngine()->RequestContext(); + + // TODO: Security issue: If the array is accessed from the callback while the sort is going on the result may be unpredictable + // For example, the callback resizes the array in the middle of the sort + // Possible solution: set a lock flag on the array, and prohibit modifications while the lock flag is set + + // Bubble sort + // TODO: optimize: Use an efficient sort algorithm + for (asUINT i = start; i+1 < end; i++) + { + asUINT best = i; + for (asUINT j = i + 1; j < end; j++) + { + cmpContext->Prepare(func); + cmpContext->SetArgAddress(0, At(j)); + cmpContext->SetArgAddress(1, At(best)); + int r = cmpContext->Execute(); + if (r != asEXECUTION_FINISHED) + break; + if (*(bool*)(cmpContext->GetAddressOfReturnValue())) + best = j; + } + + // With Swap we guarantee that the array always sees all references + // if the GC calls the EnumReferences in the middle of the sorting + if( best != i ) + Swap(GetArrayItemPointer(i), GetArrayItemPointer(best)); + } + + if (cmpContext) + { + if (isNested) + { + asEContextState state = cmpContext->GetState(); + cmpContext->PopState(); + if (state == asEXECUTION_ABORTED) + cmpContext->Abort(); + } + else + objType->GetEngine()->ReturnContext(cmpContext); + } +} + +// internal +void CScriptArray::CopyBuffer(SArrayBuffer *dst, SArrayBuffer *src) +{ + asIScriptEngine *engine = objType->GetEngine(); + if( subTypeId & asTYPEID_OBJHANDLE ) + { + // Copy the references and increase the reference counters + if( dst->numElements > 0 && src->numElements > 0 ) + { + int count = dst->numElements > src->numElements ? src->numElements : dst->numElements; + + void **max = (void**)(dst->data + count * sizeof(void*)); + void **d = (void**)dst->data; + void **s = (void**)src->data; + + for( ; d < max; d++, s++ ) + { + void *tmp = *d; + *d = *s; + if( *d ) + engine->AddRefScriptObject(*d, objType->GetSubType()); + // Release the old ref after incrementing the new to avoid problem incase it is the same ref + if( tmp ) + engine->ReleaseScriptObject(tmp, objType->GetSubType()); + } + } + } + else + { + if( dst->numElements > 0 && src->numElements > 0 ) + { + int count = dst->numElements > src->numElements ? src->numElements : dst->numElements; + if( subTypeId & asTYPEID_MASK_OBJECT ) + { + // Call the assignment operator on all of the objects + void **max = (void**)(dst->data + count * sizeof(void*)); + void **d = (void**)dst->data; + void **s = (void**)src->data; + + asITypeInfo *subType = objType->GetSubType(); + if (subType->GetFlags() & asOBJ_ASHANDLE) + { + // For objects that should work as handles we must use the opHndlAssign method + // TODO: Must support alternative syntaxes as well + // TODO: Move the lookup of the opHndlAssign method to Precache() so it is only done once + string decl = string(subType->GetName()) + "& opHndlAssign(const " + string(subType->GetName()) + "&in)"; + asIScriptFunction *func = subType->GetMethodByDecl(decl.c_str()); + if (func) + { + // TODO: Reuse active context if existing + asIScriptContext* ctx = engine->RequestContext(); + for (; d < max; d++, s++) + { + ctx->Prepare(func); + ctx->SetObject(*d); + ctx->SetArgAddress(0, *s); + // TODO: Handle errors + ctx->Execute(); + } + engine->ReturnContext(ctx); + } + else + { + // opHndlAssign doesn't exist, so try ordinary value assign instead + for (; d < max; d++, s++) + engine->AssignScriptObject(*d, *s, subType); + } + } + else + for( ; d < max; d++, s++ ) + engine->AssignScriptObject(*d, *s, subType); + } + else + { + // Primitives are copied byte for byte + memcpy(dst->data, src->data, count*elementSize); + } + } + } +} + +// internal +// Precache some info +void CScriptArray::Precache() +{ + subTypeId = objType->GetSubTypeId(); + + // Check if it is an array of objects. Only for these do we need to cache anything + // Type ids for primitives and enums only has the sequence number part + if( !(subTypeId & ~asTYPEID_MASK_SEQNBR) ) + return; + + // The opCmp and opEquals methods are cached because the searching for the + // methods is quite time consuming if a lot of array objects are created. + + // First check if a cache already exists for this array type + SArrayCache *cache = reinterpret_cast(objType->GetUserData(ARRAY_CACHE)); + if( cache ) return; + + // We need to make sure the cache is created only once, even + // if multiple threads reach the same point at the same time + asAcquireExclusiveLock(); + + // Now that we got the lock, we need to check again to make sure the + // cache wasn't created while we were waiting for the lock + cache = reinterpret_cast(objType->GetUserData(ARRAY_CACHE)); + if( cache ) + { + asReleaseExclusiveLock(); + return; + } + + // Create the cache + cache = reinterpret_cast(userAlloc(sizeof(SArrayCache))); + if( !cache ) + { + asIScriptContext *ctx = asGetActiveContext(); + if( ctx ) + ctx->SetException("Out of memory"); + asReleaseExclusiveLock(); + return; + } + memset(cache, 0, sizeof(SArrayCache)); + + // If the sub type is a handle to const, then the methods must be const too + bool mustBeConst = (subTypeId & asTYPEID_HANDLETOCONST) ? true : false; + + asITypeInfo *subType = objType->GetEngine()->GetTypeInfoById(subTypeId); + if( subType ) + { + for( asUINT i = 0; i < subType->GetMethodCount(); i++ ) + { + asIScriptFunction *func = subType->GetMethodByIndex(i); + + if( func->GetParamCount() == 1 && (!mustBeConst || func->IsReadOnly()) ) + { + asDWORD flags = 0; + int returnTypeId = func->GetReturnTypeId(&flags); + + // The method must not return a reference + if( flags != asTM_NONE ) + continue; + + // opCmp returns an int and opEquals returns a bool + bool isCmp = false, isEq = false; + if( returnTypeId == asTYPEID_INT32 && strcmp(func->GetName(), "opCmp") == 0 ) + isCmp = true; + if( returnTypeId == asTYPEID_BOOL && strcmp(func->GetName(), "opEquals") == 0 ) + isEq = true; + + if( !isCmp && !isEq ) + continue; + + // The parameter must either be a reference to the subtype or a handle to the subtype + int paramTypeId; + func->GetParam(0, ¶mTypeId, &flags); + + if( (paramTypeId & ~(asTYPEID_OBJHANDLE|asTYPEID_HANDLETOCONST)) != (subTypeId & ~(asTYPEID_OBJHANDLE|asTYPEID_HANDLETOCONST)) ) + continue; + + if( (flags & asTM_INREF) ) + { + if( (paramTypeId & asTYPEID_OBJHANDLE) || (mustBeConst && !(flags & asTM_CONST)) ) + continue; + } + else if( paramTypeId & asTYPEID_OBJHANDLE ) + { + if( mustBeConst && !(paramTypeId & asTYPEID_HANDLETOCONST) ) + continue; + } + else + continue; + + if( isCmp ) + { + if( cache->cmpFunc || cache->cmpFuncReturnCode ) + { + cache->cmpFunc = 0; + cache->cmpFuncReturnCode = asMULTIPLE_FUNCTIONS; + } + else + cache->cmpFunc = func; + } + else if( isEq ) + { + if( cache->eqFunc || cache->eqFuncReturnCode ) + { + cache->eqFunc = 0; + cache->eqFuncReturnCode = asMULTIPLE_FUNCTIONS; + } + else + cache->eqFunc = func; + } + } + } + } + + if( cache->eqFunc == 0 && cache->eqFuncReturnCode == 0 ) + cache->eqFuncReturnCode = asNO_FUNCTION; + if( cache->cmpFunc == 0 && cache->cmpFuncReturnCode == 0 ) + cache->cmpFuncReturnCode = asNO_FUNCTION; + + // Set the user data only at the end so others that retrieve it will know it is complete + objType->SetUserData(cache, ARRAY_CACHE); + + asReleaseExclusiveLock(); +} + +// GC behaviour +void CScriptArray::EnumReferences(asIScriptEngine *engine) +{ + // TODO: If garbage collection can be done from a separate thread, then this method must be + // protected so that it doesn't get lost during the iteration if the array is modified + + // If the array is holding handles, then we need to notify the GC of them + if( subTypeId & asTYPEID_MASK_OBJECT ) + { + void **d = (void**)buffer->data; + + asITypeInfo *subType = engine->GetTypeInfoById(subTypeId); + if ((subType->GetFlags() & asOBJ_REF)) + { + // For reference types we need to notify the GC of each instance + for (asUINT n = 0; n < buffer->numElements; n++) + { + if (d[n]) + engine->GCEnumCallback(d[n]); + } + } + else if ((subType->GetFlags() & asOBJ_VALUE) && (subType->GetFlags() & asOBJ_GC)) + { + // For value types we need to forward the enum callback + // to the object so it can decide what to do + for (asUINT n = 0; n < buffer->numElements; n++) + { + if (d[n]) + engine->ForwardGCEnumReferences(d[n], subType); + } + } + } +} + +// GC behaviour +void CScriptArray::ReleaseAllHandles(asIScriptEngine *) +{ + // Resizing to zero will release everything + Resize(0); +} + +void CScriptArray::AddRef() const +{ + // Clear the GC flag then increase the counter + gcFlag = false; + asAtomicInc(refCount); +} + +void CScriptArray::Release() const +{ + // Clearing the GC flag then descrease the counter + gcFlag = false; + if( asAtomicDec(refCount) == 0 ) + { + // When reaching 0 no more references to this instance + // exists and the object should be destroyed + this->~CScriptArray(); + userFree(const_cast(this)); + } +} + +// GC behaviour +int CScriptArray::GetRefCount() +{ + return refCount; +} + +// GC behaviour +void CScriptArray::SetFlag() +{ + gcFlag = true; +} + +// GC behaviour +bool CScriptArray::GetFlag() +{ + return gcFlag; +} + +//-------------------------------------------- +// Generic calling conventions + +static void ScriptArrayFactory_Generic(asIScriptGeneric *gen) +{ + asITypeInfo *ti = *(asITypeInfo**)gen->GetAddressOfArg(0); + + *reinterpret_cast(gen->GetAddressOfReturnLocation()) = CScriptArray::Create(ti); +} + +static void ScriptArrayFactory2_Generic(asIScriptGeneric *gen) +{ + asITypeInfo *ti = *(asITypeInfo**)gen->GetAddressOfArg(0); + asUINT length = gen->GetArgDWord(1); + + *reinterpret_cast(gen->GetAddressOfReturnLocation()) = CScriptArray::Create(ti, length); +} + +static void ScriptArrayListFactory_Generic(asIScriptGeneric *gen) +{ + asITypeInfo *ti = *(asITypeInfo**)gen->GetAddressOfArg(0); + void *buf = gen->GetArgAddress(1); + + *reinterpret_cast(gen->GetAddressOfReturnLocation()) = CScriptArray::Create(ti, buf); +} + +static void ScriptArrayFactoryDefVal_Generic(asIScriptGeneric *gen) +{ + asITypeInfo *ti = *(asITypeInfo**)gen->GetAddressOfArg(0); + asUINT length = gen->GetArgDWord(1); + void *defVal = gen->GetArgAddress(2); + + *reinterpret_cast(gen->GetAddressOfReturnLocation()) = CScriptArray::Create(ti, length, defVal); +} + +static void ScriptArrayTemplateCallback_Generic(asIScriptGeneric *gen) +{ + asITypeInfo *ti = *(asITypeInfo**)gen->GetAddressOfArg(0); + bool *dontGarbageCollect = *(bool**)gen->GetAddressOfArg(1); + *reinterpret_cast(gen->GetAddressOfReturnLocation()) = ScriptArrayTemplateCallback(ti, *dontGarbageCollect); +} + +static void ScriptArrayAssignment_Generic(asIScriptGeneric *gen) +{ + CScriptArray *other = (CScriptArray*)gen->GetArgObject(0); + CScriptArray *self = (CScriptArray*)gen->GetObject(); + *self = *other; + gen->SetReturnObject(self); +} + +static void ScriptArrayEquals_Generic(asIScriptGeneric *gen) +{ + CScriptArray *other = (CScriptArray*)gen->GetArgObject(0); + CScriptArray *self = (CScriptArray*)gen->GetObject(); + gen->SetReturnByte(self->operator==(*other)); +} + +static void ScriptArrayFind_Generic(asIScriptGeneric *gen) +{ + void *value = gen->GetArgAddress(0); + CScriptArray *self = (CScriptArray*)gen->GetObject(); + gen->SetReturnDWord(self->Find(value)); +} + +static void ScriptArrayFind2_Generic(asIScriptGeneric *gen) +{ + asUINT index = gen->GetArgDWord(0); + void *value = gen->GetArgAddress(1); + CScriptArray *self = (CScriptArray*)gen->GetObject(); + gen->SetReturnDWord(self->Find(index, value)); +} + +static void ScriptArrayFindByRef_Generic(asIScriptGeneric *gen) +{ + void *value = gen->GetArgAddress(0); + CScriptArray *self = (CScriptArray*)gen->GetObject(); + gen->SetReturnDWord(self->FindByRef(value)); +} + +static void ScriptArrayFindByRef2_Generic(asIScriptGeneric *gen) +{ + asUINT index = gen->GetArgDWord(0); + void *value = gen->GetArgAddress(1); + CScriptArray *self = (CScriptArray*)gen->GetObject(); + gen->SetReturnDWord(self->FindByRef(index, value)); +} + +static void ScriptArrayAt_Generic(asIScriptGeneric *gen) +{ + asINT64 index = gen->GetArgQWord(0); + CScriptArray *self = (CScriptArray*)gen->GetObject(); + + gen->SetReturnAddress(self->At(index)); +} + +static void ScriptArrayInsertAt_Generic(asIScriptGeneric *gen) +{ + asUINT index = gen->GetArgDWord(0); + void *value = gen->GetArgAddress(1); + CScriptArray *self = (CScriptArray*)gen->GetObject(); + self->InsertAt(index, value); +} + +static void ScriptArrayInsertAtArray_Generic(asIScriptGeneric *gen) +{ + asUINT index = gen->GetArgDWord(0); + CScriptArray *array = (CScriptArray*)gen->GetArgAddress(1); + CScriptArray *self = (CScriptArray*)gen->GetObject(); + self->InsertAt(index, *array); +} + +static void ScriptArrayRemoveAt_Generic(asIScriptGeneric *gen) +{ + asUINT index = gen->GetArgDWord(0); + CScriptArray *self = (CScriptArray*)gen->GetObject(); + self->RemoveAt(index); +} + +static void ScriptArrayRemoveRange_Generic(asIScriptGeneric *gen) +{ + asUINT start = gen->GetArgDWord(0); + asUINT count = gen->GetArgDWord(1); + CScriptArray *self = (CScriptArray*)gen->GetObject(); + self->RemoveRange(start, count); +} + +static void ScriptArrayInsertLast_Generic(asIScriptGeneric *gen) +{ + void *value = gen->GetArgAddress(0); + CScriptArray *self = (CScriptArray*)gen->GetObject(); + self->InsertLast(value); +} + +static void ScriptArrayRemoveLast_Generic(asIScriptGeneric *gen) +{ + CScriptArray *self = (CScriptArray*)gen->GetObject(); + self->RemoveLast(); +} + +static void ScriptArrayLength_Generic(asIScriptGeneric *gen) +{ + CScriptArray *self = (CScriptArray*)gen->GetObject(); + + gen->SetReturnDWord(self->GetSize()); +} + +static void ScriptArrayResize_Generic(asIScriptGeneric *gen) +{ + asUINT size = gen->GetArgDWord(0); + CScriptArray *self = (CScriptArray*)gen->GetObject(); + + self->Resize(size); +} + +static void ScriptArrayReserve_Generic(asIScriptGeneric *gen) +{ + asUINT size = gen->GetArgDWord(0); + CScriptArray *self = (CScriptArray*)gen->GetObject(); + self->Reserve(size); +} + +static void ScriptArraySortAsc_Generic(asIScriptGeneric *gen) +{ + CScriptArray *self = (CScriptArray*)gen->GetObject(); + self->SortAsc(); +} + +static void ScriptArrayReverse_Generic(asIScriptGeneric *gen) +{ + CScriptArray *self = (CScriptArray*)gen->GetObject(); + self->Reverse(); +} + +static void ScriptArrayIsEmpty_Generic(asIScriptGeneric *gen) +{ + CScriptArray *self = (CScriptArray*)gen->GetObject(); + *reinterpret_cast(gen->GetAddressOfReturnLocation()) = self->IsEmpty(); +} + +static void ScriptArraySortAsc2_Generic(asIScriptGeneric *gen) +{ + asUINT index = gen->GetArgDWord(0); + asUINT count = gen->GetArgDWord(1); + CScriptArray *self = (CScriptArray*)gen->GetObject(); + self->SortAsc(index, count); +} + +static void ScriptArraySortDesc_Generic(asIScriptGeneric *gen) +{ + CScriptArray *self = (CScriptArray*)gen->GetObject(); + self->SortDesc(); +} + +static void ScriptArraySortDesc2_Generic(asIScriptGeneric *gen) +{ + asUINT index = gen->GetArgDWord(0); + asUINT count = gen->GetArgDWord(1); + CScriptArray *self = (CScriptArray*)gen->GetObject(); + self->SortDesc(index, count); +} + +static void ScriptArraySortCallback_Generic(asIScriptGeneric *gen) +{ + asIScriptFunction *callback = (asIScriptFunction*)gen->GetArgAddress(0); + asUINT startAt = gen->GetArgDWord(1); + asUINT count = gen->GetArgDWord(2); + CScriptArray *self = (CScriptArray*)gen->GetObject(); + self->Sort(callback, startAt, count); +} + +static void ScriptArrayAddRef_Generic(asIScriptGeneric *gen) +{ + CScriptArray *self = (CScriptArray*)gen->GetObject(); + self->AddRef(); +} + +static void ScriptArrayRelease_Generic(asIScriptGeneric *gen) +{ + CScriptArray *self = (CScriptArray*)gen->GetObject(); + self->Release(); +} + +static void ScriptArrayGetRefCount_Generic(asIScriptGeneric *gen) +{ + CScriptArray *self = (CScriptArray*)gen->GetObject(); + *reinterpret_cast(gen->GetAddressOfReturnLocation()) = self->GetRefCount(); +} + +static void ScriptArraySetFlag_Generic(asIScriptGeneric *gen) +{ + CScriptArray *self = (CScriptArray*)gen->GetObject(); + self->SetFlag(); +} + +static void ScriptArrayGetFlag_Generic(asIScriptGeneric *gen) +{ + CScriptArray *self = (CScriptArray*)gen->GetObject(); + *reinterpret_cast(gen->GetAddressOfReturnLocation()) = self->GetFlag(); +} + +static void ScriptArrayEnumReferences_Generic(asIScriptGeneric *gen) +{ + CScriptArray *self = (CScriptArray*)gen->GetObject(); + asIScriptEngine *engine = *(asIScriptEngine**)gen->GetAddressOfArg(0); + self->EnumReferences(engine); +} + +static void ScriptArrayReleaseAllHandles_Generic(asIScriptGeneric *gen) +{ + CScriptArray *self = (CScriptArray*)gen->GetObject(); + asIScriptEngine *engine = *(asIScriptEngine**)gen->GetAddressOfArg(0); + self->ReleaseAllHandles(engine); +} + +static void RegisterScriptArray_Generic(asIScriptEngine *engine) +{ + int r = 0; + UNUSED_VAR(r); + + engine->SetTypeInfoUserDataCleanupCallback(CleanupTypeInfoArrayCache, ARRAY_CACHE); + + r = engine->RegisterObjectType("array", 0, asOBJ_REF | asOBJ_GC | asOBJ_TEMPLATE); assert( r >= 0 ); + r = engine->RegisterObjectBehaviour("array", asBEHAVE_TEMPLATE_CALLBACK, "bool f(int&in, bool&out)", asFUNCTION(ScriptArrayTemplateCallback_Generic), asCALL_GENERIC); assert( r >= 0 ); + + r = engine->RegisterObjectBehaviour("array", asBEHAVE_FACTORY, "array@ f(int&in)", asFUNCTION(ScriptArrayFactory_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectBehaviour("array", asBEHAVE_FACTORY, "array@ f(int&in, uint length) explicit", asFUNCTION(ScriptArrayFactory2_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectBehaviour("array", asBEHAVE_FACTORY, "array@ f(int&in, uint length, const T &in value)", asFUNCTION(ScriptArrayFactoryDefVal_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectBehaviour("array", asBEHAVE_LIST_FACTORY, "array@ f(int&in, int&in) {repeat T}", asFUNCTION(ScriptArrayListFactory_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectBehaviour("array", asBEHAVE_ADDREF, "void f()", asFUNCTION(ScriptArrayAddRef_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectBehaviour("array", asBEHAVE_RELEASE, "void f()", asFUNCTION(ScriptArrayRelease_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "T &opIndex(int64 index)", asFUNCTION(ScriptArrayAt_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "const T &opIndex(int64 index) const", asFUNCTION(ScriptArrayAt_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "array &opAssign(const array&in)", asFUNCTION(ScriptArrayAssignment_Generic), asCALL_GENERIC); assert( r >= 0 ); + + r = engine->RegisterObjectMethod("array", "void insert_at(uint index, const T&in value)", asFUNCTION(ScriptArrayInsertAt_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "void insert_at(uint index, const array& arr)", asFUNCTION(ScriptArrayInsertAtArray_Generic), asCALL_GENERIC); assert(r >= 0); + r = engine->RegisterObjectMethod("array", "void insert_last(const T&in value)", asFUNCTION(ScriptArrayInsertLast_Generic), asCALL_GENERIC); assert(r >= 0); + r = engine->RegisterObjectMethod("array", "void remove_at(uint index)", asFUNCTION(ScriptArrayRemoveAt_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "void remove_last()", asFUNCTION(ScriptArrayRemoveLast_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "void remove_range(uint start, uint count)", asFUNCTION(ScriptArrayRemoveRange_Generic), asCALL_GENERIC); assert(r >= 0); +#if AS_USE_ACCESSORS != 1 + r = engine->RegisterObjectMethod("array", "uint length() const", asFUNCTION(ScriptArrayLength_Generic), asCALL_GENERIC); assert( r >= 0 ); +#endif + r = engine->RegisterObjectMethod("array", "void reserve(uint length)", asFUNCTION(ScriptArrayReserve_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "void resize(uint length)", asFUNCTION(ScriptArrayResize_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "void sort_ascending()", asFUNCTION(ScriptArraySortAsc_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "void sort_ascending(uint startAt, uint count)", asFUNCTION(ScriptArraySortAsc2_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "void sort_descending()", asFUNCTION(ScriptArraySortDesc_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "void sort_descending(uint startAt, uint count)", asFUNCTION(ScriptArraySortDesc2_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "void reverse()", asFUNCTION(ScriptArrayReverse_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "int find(const T&in if_handle_then_const value) const", asFUNCTION(ScriptArrayFind_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "int find(uint startAt, const T&in if_handle_then_const value) const", asFUNCTION(ScriptArrayFind2_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "int find_by_ref(const T&in if_handle_then_const value) const", asFUNCTION(ScriptArrayFindByRef_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "int find_by_ref(uint startAt, const T&in if_handle_then_const value) const", asFUNCTION(ScriptArrayFindByRef2_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "bool opEquals(const array&in) const", asFUNCTION(ScriptArrayEquals_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "bool is_empty() const", asFUNCTION(ScriptArrayIsEmpty_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterFuncdef("bool array::less(const T&in if_handle_then_const a, const T&in if_handle_then_const b)"); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "void sort(const less &in, uint startAt = 0, uint count = uint(-1))", asFUNCTION(ScriptArraySortCallback_Generic), asCALL_GENERIC); assert(r >= 0); +#if AS_USE_STLNAMES != 1 && AS_USE_ACCESSORS == 1 + r = engine->RegisterObjectMethod("array", "uint get_length() const property", asFUNCTION(ScriptArrayLength_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "void set_length(uint) property", asFUNCTION(ScriptArrayResize_Generic), asCALL_GENERIC); assert( r >= 0 ); +#endif + r = engine->RegisterObjectBehaviour("array", asBEHAVE_GETREFCOUNT, "int f()", asFUNCTION(ScriptArrayGetRefCount_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectBehaviour("array", asBEHAVE_SETGCFLAG, "void f()", asFUNCTION(ScriptArraySetFlag_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectBehaviour("array", asBEHAVE_GETGCFLAG, "bool f()", asFUNCTION(ScriptArrayGetFlag_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectBehaviour("array", asBEHAVE_ENUMREFS, "void f(int&in)", asFUNCTION(ScriptArrayEnumReferences_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectBehaviour("array", asBEHAVE_RELEASEREFS, "void f(int&in)", asFUNCTION(ScriptArrayReleaseAllHandles_Generic), asCALL_GENERIC); assert( r >= 0 ); +} + +END_AS_NAMESPACE diff --git a/ASAddon/src/scripthelper.cpp b/ASAddon/src/scripthelper.cpp index 9386eb34..f34d3d77 100644 --- a/ASAddon/src/scripthelper.cpp +++ b/ASAddon/src/scripthelper.cpp @@ -1,1028 +1,1028 @@ -#include -#include "scripthelper.h" -#include -#include -#include -#include -#include -#include "aswrappedcall.h" - -using namespace std; - -BEGIN_AS_NAMESPACE - -int CompareRelation(asIScriptEngine *engine, void *lobj, void *robj, int typeId, int &result) -{ - // TODO: If a lot of script objects are going to be compared, e.g. when sorting an array, - // then the method id and context should be cached between calls. - - int retval = -1; - asIScriptFunction *func = 0; - - asITypeInfo *ti = engine->GetTypeInfoById(typeId); - if( ti ) - { - // Check if the object type has a compatible opCmp method - for( asUINT n = 0; n < ti->GetMethodCount(); n++ ) - { - asIScriptFunction *f = ti->GetMethodByIndex(n); - asDWORD flags; - if( strcmp(f->GetName(), "opCmp") == 0 && - f->GetReturnTypeId(&flags) == asTYPEID_INT32 && - flags == asTM_NONE && - f->GetParamCount() == 1 ) - { - int paramTypeId; - f->GetParam(0, ¶mTypeId, &flags); - - // The parameter must be an input reference of the same type - // If the reference is a inout reference, then it must also be read-only - if( !(flags & asTM_INREF) || typeId != paramTypeId || ((flags & asTM_OUTREF) && !(flags & asTM_CONST)) ) - break; - - // Found the method - func = f; - break; - } - } - } - - if( func ) - { - // Call the method - asIScriptContext *ctx = engine->CreateContext(); - ctx->Prepare(func); - ctx->SetObject(lobj); - ctx->SetArgAddress(0, robj); - int r = ctx->Execute(); - if( r == asEXECUTION_FINISHED ) - { - result = (int)ctx->GetReturnDWord(); - - // The comparison was successful - retval = 0; - } - ctx->Release(); - } - - return retval; -} - -int CompareEquality(asIScriptEngine *engine, void *lobj, void *robj, int typeId, bool &result) -{ - // TODO: If a lot of script objects are going to be compared, e.g. when searching for an - // entry in a set, then the method and context should be cached between calls. - - int retval = -1; - asIScriptFunction *func = 0; - - asITypeInfo *ti = engine->GetTypeInfoById(typeId); - if( ti ) - { - // Check if the object type has a compatible opEquals method - for( asUINT n = 0; n < ti->GetMethodCount(); n++ ) - { - asIScriptFunction *f = ti->GetMethodByIndex(n); - asDWORD flags; - if( strcmp(f->GetName(), "opEquals") == 0 && - f->GetReturnTypeId(&flags) == asTYPEID_BOOL && - flags == asTM_NONE && - f->GetParamCount() == 1 ) - { - int paramTypeId; - f->GetParam(0, ¶mTypeId, &flags); - - // The parameter must be an input reference of the same type - // If the reference is a inout reference, then it must also be read-only - if( !(flags & asTM_INREF) || typeId != paramTypeId || ((flags & asTM_OUTREF) && !(flags & asTM_CONST)) ) - break; - - // Found the method - func = f; - break; - } - } - } - - if( func ) - { - // Call the method - asIScriptContext *ctx = engine->CreateContext(); - ctx->Prepare(func); - ctx->SetObject(lobj); - ctx->SetArgAddress(0, robj); - int r = ctx->Execute(); - if( r == asEXECUTION_FINISHED ) - { - result = ctx->GetReturnByte() ? true : false; - - // The comparison was successful - retval = 0; - } - ctx->Release(); - } - else - { - // If the opEquals method doesn't exist, then we try with opCmp instead - int relation; - retval = CompareRelation(engine, lobj, robj, typeId, relation); - if( retval >= 0 ) - result = relation == 0 ? true : false; - } - - return retval; -} - -int ExecuteString(asIScriptEngine *engine, const char *code, asIScriptModule *mod, asIScriptContext *ctx) -{ - return ExecuteString(engine, code, 0, asTYPEID_VOID, mod, ctx); -} - -int ExecuteString(asIScriptEngine *engine, const char *code, void *ref, int refTypeId, asIScriptModule *mod, asIScriptContext *ctx) -{ - // Wrap the code in a function so that it can be compiled and executed - string funcCode = " ExecuteString() {\n"; - funcCode += code; - funcCode += "\n;}"; - - // Determine the return type based on the type of the ref arg - funcCode = engine->GetTypeDeclaration(refTypeId, true) + funcCode; - - // GetModule will free unused types, so to be on the safe side we'll hold on to a reference to the type - asITypeInfo *type = 0; - if( refTypeId & asTYPEID_MASK_OBJECT ) - { - type = engine->GetTypeInfoById(refTypeId); - if( type ) - type->AddRef(); - } - - // If no module was provided, get a dummy from the engine - asIScriptModule *execMod = mod ? mod : engine->GetModule("ExecuteString", asGM_ALWAYS_CREATE); - - // Now it's ok to release the type - if( type ) - type->Release(); - - // Compile the function that can be executed - asIScriptFunction *func = 0; - int r = execMod->CompileFunction("ExecuteString", funcCode.c_str(), -1, 0, &func); - if( r < 0 ) - return r; - - // If no context was provided, request a new one from the engine - asIScriptContext *execCtx = ctx ? ctx : engine->RequestContext(); - r = execCtx->Prepare(func); - if (r >= 0) - { - // Execute the function - r = execCtx->Execute(); - - // Unless the provided type was void retrieve it's value - if (ref != 0 && refTypeId != asTYPEID_VOID) - { - if (refTypeId & asTYPEID_OBJHANDLE) - { - // Expect the pointer to be null to start with - assert(*reinterpret_cast(ref) == 0); - *reinterpret_cast(ref) = *reinterpret_cast(execCtx->GetAddressOfReturnValue()); - engine->AddRefScriptObject(*reinterpret_cast(ref), engine->GetTypeInfoById(refTypeId)); - } - else if (refTypeId & asTYPEID_MASK_OBJECT) - { - // Use the registered assignment operator to do a value assign. - // This assumes that the ref is pointing to a valid object instance. - engine->AssignScriptObject(ref, execCtx->GetAddressOfReturnValue(), engine->GetTypeInfoById(refTypeId)); - } - else - { - // Copy the primitive value - memcpy(ref, execCtx->GetAddressOfReturnValue(), engine->GetSizeOfPrimitiveType(refTypeId)); - } - } - } - - // Clean up - func->Release(); - if( !ctx ) engine->ReturnContext(execCtx); - - return r; -} - -int WriteConfigToFile(asIScriptEngine *engine, const char *filename) -{ - ofstream strm; - strm.open(filename); - - return WriteConfigToStream(engine, strm); -} - -int WriteConfigToStream(asIScriptEngine *engine, ostream &strm) -{ - // A helper function for escaping quotes in default arguments - struct Escape - { - static string Quotes(const char *decl) - { - string str = decl; - size_t pos = 0; - for(;;) - { - // Find " characters - pos = str.find("\"",pos); - if( pos == string::npos ) - break; - - // Add a \ to escape them - str.insert(pos, "\\"); - pos += 2; - } - - return str; - } - }; - - int c, n; - - asDWORD currAccessMask = 0; - string currNamespace = ""; - engine->SetDefaultNamespace(""); - - // Export the engine version, just for info - strm << "// AngelScript " << asGetLibraryVersion() << "\n"; - strm << "// Lib options " << asGetLibraryOptions() << "\n"; - - // Export the relevant engine properties - strm << "// Engine properties\n"; - for( n = 0; n < asEP_LAST_PROPERTY; n++ ) - strm << "ep " << n << " " << engine->GetEngineProperty(asEEngineProp(n)) << "\n"; - - // Make sure the default array type is expanded to the template form - bool expandDefArrayToTempl = engine->GetEngineProperty(asEP_EXPAND_DEF_ARRAY_TO_TMPL) ? true : false; - engine->SetEngineProperty(asEP_EXPAND_DEF_ARRAY_TO_TMPL, true); - - // Write enum types and their values - strm << "\n// Enums\n"; - c = engine->GetEnumCount(); - for( n = 0; n < c; n++ ) - { - asITypeInfo *ti = engine->GetEnumByIndex(n); - asDWORD accessMask = ti->GetAccessMask(); - if( accessMask != currAccessMask ) - { - strm << "access " << hex << (unsigned int)(accessMask) << dec << "\n"; - currAccessMask = accessMask; - } - const char *nameSpace = ti->GetNamespace(); - if( nameSpace != currNamespace ) - { - strm << "namespace \"" << nameSpace << "\"\n"; - currNamespace = nameSpace; - engine->SetDefaultNamespace(currNamespace.c_str()); - } - const char *enumName = ti->GetName(); - strm << "enum " << enumName << "\n"; - for( asUINT m = 0; m < ti->GetEnumValueCount(); m++ ) - { - const char *valName; - int val; - valName = ti->GetEnumValueByIndex(m, &val); - strm << "enumval " << enumName << " " << valName << " " << val << "\n"; - } - } - - // Enumerate all types - strm << "\n// Types\n"; - - // Keep a list of the template types, as the methods for these need to be exported first - set templateTypes; - - c = engine->GetObjectTypeCount(); - for( n = 0; n < c; n++ ) - { - asITypeInfo *type = engine->GetObjectTypeByIndex(n); - asDWORD accessMask = type->GetAccessMask(); - if( accessMask != currAccessMask ) - { - strm << "access " << hex << (unsigned int)(accessMask) << dec << "\n"; - currAccessMask = accessMask; - } - const char *nameSpace = type->GetNamespace(); - if( nameSpace != currNamespace ) - { - strm << "namespace \"" << nameSpace << "\"\n"; - currNamespace = nameSpace; - engine->SetDefaultNamespace(currNamespace.c_str()); - } - if( type->GetFlags() & asOBJ_SCRIPT_OBJECT ) - { - // This should only be interfaces - assert( type->GetSize() == 0 ); - - strm << "intf " << type->GetName() << "\n"; - } - else - { - // Only the type flags are necessary. The application flags are application - // specific and doesn't matter to the offline compiler. The object size is also - // unnecessary for the offline compiler - strm << "objtype \"" << engine->GetTypeDeclaration(type->GetTypeId()) << "\" " << (unsigned int)(type->GetFlags() & asOBJ_MASK_VALID_FLAGS) << "\n"; - - // Store the template types (but not template instances) - if( (type->GetFlags() & asOBJ_TEMPLATE) && type->GetSubType() && (type->GetSubType()->GetFlags() & asOBJ_TEMPLATE_SUBTYPE) ) - templateTypes.insert(type); - } - } - - c = engine->GetTypedefCount(); - for( n = 0; n < c; n++ ) - { - asITypeInfo *ti = engine->GetTypedefByIndex(n); - const char *nameSpace = ti->GetNamespace(); - if( nameSpace != currNamespace ) - { - strm << "namespace \"" << nameSpace << "\"\n"; - currNamespace = nameSpace; - engine->SetDefaultNamespace(currNamespace.c_str()); - } - asDWORD accessMask = ti->GetAccessMask(); - if( accessMask != currAccessMask ) - { - strm << "access " << hex << (unsigned int)(accessMask) << dec << "\n"; - currAccessMask = accessMask; - } - strm << "typedef " << ti->GetName() << " \"" << engine->GetTypeDeclaration(ti->GetTypedefTypeId()) << "\"\n"; - } - - c = engine->GetFuncdefCount(); - for( n = 0; n < c; n++ ) - { - asITypeInfo *funcDef = engine->GetFuncdefByIndex(n); - asDWORD accessMask = funcDef->GetAccessMask(); - const char *nameSpace = funcDef->GetNamespace(); - // Child funcdefs do not have any namespace, as they belong to the parent object - if( nameSpace && nameSpace != currNamespace ) - { - strm << "namespace \"" << nameSpace << "\"\n"; - currNamespace = nameSpace; - engine->SetDefaultNamespace(currNamespace.c_str()); - } - if( accessMask != currAccessMask ) - { - strm << "access " << hex << (unsigned int)(accessMask) << dec << "\n"; - currAccessMask = accessMask; - } - strm << "funcdef \"" << funcDef->GetFuncdefSignature()->GetDeclaration(true, false, true) << "\"\n"; - } - - // A helper for writing object type members - struct TypeWriter - { - static void Write(asIScriptEngine *engine, ostream &strm, asITypeInfo *type, string &currNamespace, asDWORD &currAccessMask) - { - const char *nameSpace = type->GetNamespace(); - if( nameSpace != currNamespace ) - { - strm << "namespace \"" << nameSpace << "\"\n"; - currNamespace = nameSpace; - engine->SetDefaultNamespace(currNamespace.c_str()); - } - string typeDecl = engine->GetTypeDeclaration(type->GetTypeId()); - if( type->GetFlags() & asOBJ_SCRIPT_OBJECT ) - { - for( asUINT m = 0; m < type->GetMethodCount(); m++ ) - { - asIScriptFunction *func = type->GetMethodByIndex(m); - asDWORD accessMask = func->GetAccessMask(); - if( accessMask != currAccessMask ) - { - strm << "access " << hex << (unsigned int)(accessMask) << dec << "\n"; - currAccessMask = accessMask; - } - strm << "intfmthd " << typeDecl.c_str() << " \"" << Escape::Quotes(func->GetDeclaration(false, false, true)).c_str() << (func->IsProperty() ? " property" : "") << "\"\n"; - } - } - else - { - asUINT m; - for( m = 0; m < type->GetFactoryCount(); m++ ) - { - asIScriptFunction *func = type->GetFactoryByIndex(m); - asDWORD accessMask = func->GetAccessMask(); - if( accessMask != currAccessMask ) - { - strm << "access " << hex << (unsigned int)(accessMask) << dec << "\n"; - currAccessMask = accessMask; - } - strm << "objbeh \"" << typeDecl.c_str() << "\" " << asBEHAVE_FACTORY << " \"" << Escape::Quotes(func->GetDeclaration(false, false, true)).c_str() << "\"\n"; - } - for( m = 0; m < type->GetBehaviourCount(); m++ ) - { - asEBehaviours beh; - asIScriptFunction *func = type->GetBehaviourByIndex(m, &beh); - - if( beh == asBEHAVE_CONSTRUCT ) - // Prefix 'void' - strm << "objbeh \"" << typeDecl.c_str() << "\" " << beh << " \"void " << Escape::Quotes(func->GetDeclaration(false, false, true)).c_str() << "\"\n"; - else if( beh == asBEHAVE_DESTRUCT ) - // Prefix 'void' and remove ~ - strm << "objbeh \"" << typeDecl.c_str() << "\" " << beh << " \"void " << Escape::Quotes(func->GetDeclaration(false, false, true)).c_str()+1 << "\"\n"; - else - strm << "objbeh \"" << typeDecl.c_str() << "\" " << beh << " \"" << Escape::Quotes(func->GetDeclaration(false, false, true)).c_str() << "\"\n"; - } - for( m = 0; m < type->GetMethodCount(); m++ ) - { - asIScriptFunction *func = type->GetMethodByIndex(m); - asDWORD accessMask = func->GetAccessMask(); - if( accessMask != currAccessMask ) - { - strm << "access " << hex << (unsigned int)(accessMask) << dec << "\n"; - currAccessMask = accessMask; - } - strm << "objmthd \"" << typeDecl.c_str() << "\" \"" << Escape::Quotes(func->GetDeclaration(false, false, true)).c_str() << (func->IsProperty() ? " property" : "") << "\"\n"; - } - for( m = 0; m < type->GetPropertyCount(); m++ ) - { - asDWORD accessMask; - type->GetProperty(m, 0, 0, 0, 0, 0, 0, &accessMask); - if( accessMask != currAccessMask ) - { - strm << "access " << hex << (unsigned int)(accessMask) << dec << "\n"; - currAccessMask = accessMask; - } - strm << "objprop \"" << typeDecl.c_str() << "\" \"" << type->GetPropertyDeclaration(m) << "\""; - - // Save information about composite properties - int compositeOffset; - bool isCompositeIndirect; - type->GetProperty(m, 0, 0, 0, 0, 0, 0, 0, &compositeOffset, &isCompositeIndirect); - strm << " " << compositeOffset << " " << (isCompositeIndirect ? "1" : "0") << "\n"; - } - } - } - }; - - // Write the members of the template types, so they can be fully registered before any other type uses them - // TODO: Order the template types based on dependency to avoid failure if one type uses instances of another - strm << "\n// Template type members\n"; - for( set::iterator it = templateTypes.begin(); it != templateTypes.end(); ++it ) - { - asITypeInfo *type = *it; - TypeWriter::Write(engine, strm, type, currNamespace, currAccessMask); - } - - // Write the object types members - strm << "\n// Type members\n"; - - c = engine->GetObjectTypeCount(); - for( n = 0; n < c; n++ ) - { - asITypeInfo *type = engine->GetObjectTypeByIndex(n); - if( templateTypes.find(type) == templateTypes.end() ) - TypeWriter::Write(engine, strm, type, currNamespace, currAccessMask); - } - - // Write functions - strm << "\n// Functions\n"; - - c = engine->GetGlobalFunctionCount(); - for( n = 0; n < c; n++ ) - { - asIScriptFunction *func = engine->GetGlobalFunctionByIndex(n); - const char *nameSpace = func->GetNamespace(); - if( nameSpace != currNamespace ) - { - strm << "namespace \"" << nameSpace << "\"\n"; - currNamespace = nameSpace; - engine->SetDefaultNamespace(currNamespace.c_str()); - } - asDWORD accessMask = func->GetAccessMask(); - if( accessMask != currAccessMask ) - { - strm << "access " << hex << (unsigned int)(accessMask) << dec << "\n"; - currAccessMask = accessMask; - } - strm << "func \"" << Escape::Quotes(func->GetDeclaration(true, false, true)).c_str() << (func->IsProperty() ? " property" : "") << "\"\n"; - } - - // Write global properties - strm << "\n// Properties\n"; - - c = engine->GetGlobalPropertyCount(); - for( n = 0; n < c; n++ ) - { - const char *name; - int typeId; - bool isConst; - asDWORD accessMask; - const char *nameSpace; - engine->GetGlobalPropertyByIndex(n, &name, &nameSpace, &typeId, &isConst, 0, 0, &accessMask); - if( accessMask != currAccessMask ) - { - strm << "access " << hex << (unsigned int)(accessMask) << dec << "\n"; - currAccessMask = accessMask; - } - if( nameSpace != currNamespace ) - { - strm << "namespace \"" << nameSpace << "\"\n"; - currNamespace = nameSpace; - engine->SetDefaultNamespace(currNamespace.c_str()); - } - strm << "prop \"" << (isConst ? "const " : "") << engine->GetTypeDeclaration(typeId) << " " << name << "\"\n"; - } - - // Write string factory - strm << "\n// String factory\n"; - - // Reset the namespace for the string factory and default array type - if ("" != currNamespace) - { - strm << "namespace \"\"\n"; - currNamespace = ""; - engine->SetDefaultNamespace(""); - } - - asDWORD flags = 0; - int typeId = engine->GetStringFactory(&flags); - if( typeId > 0 ) - strm << "strfactory \"" << ((flags & asTM_CONST) ? "const " : "") << engine->GetTypeDeclaration(typeId) << ((flags & asTM_INOUTREF) ? "&" : "") << "\"\n"; - - // Write default array type - strm << "\n// Default array type\n"; - typeId = engine->GetDefaultArrayTypeId(); - if( typeId > 0 ) - strm << "defarray \"" << engine->GetTypeDeclaration(typeId) << "\"\n"; - - // Restore original settings - engine->SetEngineProperty(asEP_EXPAND_DEF_ARRAY_TO_TMPL, expandDefArrayToTempl); - - return 0; -} - -int ConfigEngineFromStream(asIScriptEngine *engine, istream &strm, const char *configFile, asIStringFactory *stringFactory) -{ - int r; - - // Some helper functions for parsing the configuration - struct in - { - static asETokenClass GetToken(asIScriptEngine *engine, string &token, const string &text, asUINT &pos) - { - asUINT len = 0; - asETokenClass t = engine->ParseToken(&text[pos], text.length() - pos, &len); - while( (t == asTC_WHITESPACE || t == asTC_COMMENT) && pos < text.length() ) - { - pos += len; - t = engine->ParseToken(&text[pos], text.length() - pos, &len); - } - - token.assign(&text[pos], len); - - pos += len; - - return t; - } - - static void ReplaceSlashQuote(string &str) - { - size_t pos = 0; - for(;;) - { - // Search for \" in the string - pos = str.find("\\\"", pos); - if( pos == string::npos ) - break; - - // Remove the \ character - str.erase(pos, 1); - } - } - - static asUINT GetLineNumber(const string &text, asUINT pos) - { - asUINT count = 1; - for( asUINT n = 0; n < pos; n++ ) - if( text[n] == '\n' ) - count++; - - return count; - } - }; - - // Since we are only going to compile the script and never actually execute it, - // we turn off the initialization of global variables, so that the compiler can - // just register dummy types and functions for the application interface. - r = engine->SetEngineProperty(asEP_INIT_GLOBAL_VARS_AFTER_BUILD, false); assert( r >= 0 ); - - // Read the entire file - char buffer[1000]; - string config; - do { - strm.getline(buffer, 1000); - config += buffer; - config += "\n"; - } while( !strm.eof() && strm.good() ); - - // Process the configuration file and register each entity - asUINT pos = 0; - while( pos < config.length() ) - { - string token; - // TODO: The position where the initial token is found should be stored for error messages - in::GetToken(engine, token, config, pos); - if( token == "ep" ) - { - string tmp; - in::GetToken(engine, tmp, config, pos); - - asEEngineProp ep = asEEngineProp(atol(tmp.c_str())); - - // Only set properties that affect the compiler - if( ep != asEP_COPY_SCRIPT_SECTIONS && - ep != asEP_MAX_STACK_SIZE && - ep != asEP_INIT_GLOBAL_VARS_AFTER_BUILD && - ep != asEP_EXPAND_DEF_ARRAY_TO_TMPL && - ep != asEP_AUTO_GARBAGE_COLLECT ) - { - // Get the value for the property - in::GetToken(engine, tmp, config, pos); - stringstream s(tmp); - asPWORD value; - - s >> value; - - engine->SetEngineProperty(ep, value); - } - } - else if( token == "namespace" ) - { - string ns; - in::GetToken(engine, ns, config, pos); - ns = ns.substr(1, ns.length() - 2); - - r = engine->SetDefaultNamespace(ns.c_str()); - if( r < 0 ) - { - engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to set namespace"); - return -1; - } - } - else if( token == "access" ) - { - string maskStr; - in::GetToken(engine, maskStr, config, pos); - asDWORD mask = strtoul(maskStr.c_str(), 0, 16); - engine->SetDefaultAccessMask(mask); - } - else if( token == "objtype" ) - { - string name, flags; - in::GetToken(engine, name, config, pos); - name = name.substr(1, name.length() - 2); - in::GetToken(engine, flags, config, pos); - - // The size of the value type doesn't matter, because the - // engine must adjust it anyway for different platforms - r = engine->RegisterObjectType(name.c_str(), (atol(flags.c_str()) & asOBJ_VALUE) ? 1 : 0, atol(flags.c_str())); - if( r < 0 ) - { - engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register object type"); - return -1; - } - } - else if( token == "objbeh" ) - { - string name, behaviour, decl; - in::GetToken(engine, name, config, pos); - name = name.substr(1, name.length() - 2); - in::GetToken(engine, behaviour, config, pos); - in::GetToken(engine, decl, config, pos); - decl = decl.substr(1, decl.length() - 2); - in::ReplaceSlashQuote(decl); - - // Remove the $ that the engine prefixes the behaviours with - size_t n = decl.find("$"); - if( n != string::npos ) - decl[n] = ' '; - - asEBehaviours behave = static_cast(atol(behaviour.c_str())); - if( behave == asBEHAVE_TEMPLATE_CALLBACK ) - { - // TODO: How can we let the compiler register this? Maybe through a plug-in system? Or maybe by implementing the callback as a script itself - engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_WARNING, "Cannot register template callback without the actual implementation"); - } - else - { - r = engine->RegisterObjectBehaviour(name.c_str(), behave, decl.c_str(), asFUNCTION(0), asCALL_GENERIC); - if( r < 0 ) - { - engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register behaviour"); - return -1; - } - } - } - else if( token == "objmthd" ) - { - string name, decl; - in::GetToken(engine, name, config, pos); - name = name.substr(1, name.length() - 2); - in::GetToken(engine, decl, config, pos); - decl = decl.substr(1, decl.length() - 2); - in::ReplaceSlashQuote(decl); - - r = engine->RegisterObjectMethod(name.c_str(), decl.c_str(), asFUNCTION(0), asCALL_GENERIC); - if( r < 0 ) - { - engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register object method"); - return -1; - } - } - else if( token == "objprop" ) - { - string name, decl, compositeOffset, isCompositeIndirect; - in::GetToken(engine, name, config, pos); - name = name.substr(1, name.length() - 2); - in::GetToken(engine, decl, config, pos); - decl = decl.substr(1, decl.length() - 2); - in::GetToken(engine, compositeOffset, config, pos); - in::GetToken(engine, isCompositeIndirect, config, pos); - - asITypeInfo *type = engine->GetTypeInfoById(engine->GetTypeIdByDecl(name.c_str())); - if( type == 0 ) - { - engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Type doesn't exist for property registration"); - return -1; - } - - // All properties must have different offsets in order to make them - // distinct, so we simply register them with an incremental offset - r = engine->RegisterObjectProperty(name.c_str(), decl.c_str(), type->GetPropertyCount(), compositeOffset != "0" ? type->GetPropertyCount() : 0, isCompositeIndirect != "0"); - if( r < 0 ) - { - engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register object property"); - return -1; - } - } - else if( token == "intf" ) - { - string name, size, flags; - in::GetToken(engine, name, config, pos); - - r = engine->RegisterInterface(name.c_str()); - if( r < 0 ) - { - engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register interface"); - return -1; - } - } - else if( token == "intfmthd" ) - { - string name, decl; - in::GetToken(engine, name, config, pos); - in::GetToken(engine, decl, config, pos); - decl = decl.substr(1, decl.length() - 2); - in::ReplaceSlashQuote(decl); - - r = engine->RegisterInterfaceMethod(name.c_str(), decl.c_str()); - if( r < 0 ) - { - engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register interface method"); - return -1; - } - } - else if( token == "func" ) - { - string decl; - in::GetToken(engine, decl, config, pos); - decl = decl.substr(1, decl.length() - 2); - in::ReplaceSlashQuote(decl); - - r = engine->RegisterGlobalFunction(decl.c_str(), asFUNCTION(0), asCALL_GENERIC); - if( r < 0 ) - { - engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register global function"); - return -1; - } - } - else if( token == "prop" ) - { - string decl; - in::GetToken(engine, decl, config, pos); - decl = decl.substr(1, decl.length() - 2); - - // All properties must have different offsets in order to make them - // distinct, so we simply register them with an incremental offset. - // The pointer must also be non-null so we add 1 to have a value. - r = engine->RegisterGlobalProperty(decl.c_str(), reinterpret_cast(asPWORD(engine->GetGlobalPropertyCount()+1))); - if( r < 0 ) - { - engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register global property"); - return -1; - } - } - else if( token == "strfactory" ) - { - string type; - in::GetToken(engine, type, config, pos); - type = type.substr(1, type.length() - 2); - - if (stringFactory == 0) - { - engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_WARNING, "Cannot register string factory without the actual implementation"); - return -1; - } - else - { - r = engine->RegisterStringFactory(type.c_str(), stringFactory); - if (r < 0) - { - engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register string factory"); - return -1; - } - } - } - else if( token == "defarray" ) - { - string type; - in::GetToken(engine, type, config, pos); - type = type.substr(1, type.length() - 2); - - r = engine->RegisterDefaultArrayType(type.c_str()); - if( r < 0 ) - { - engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register the default array type"); - return -1; - } - } - else if( token == "enum" ) - { - string type; - in::GetToken(engine, type, config, pos); - - r = engine->RegisterEnum(type.c_str()); - if( r < 0 ) - { - engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register enum type"); - return -1; - } - } - else if( token == "enumval" ) - { - string type, name, value; - in::GetToken(engine, type, config, pos); - in::GetToken(engine, name, config, pos); - in::GetToken(engine, value, config, pos); - - r = engine->RegisterEnumValue(type.c_str(), name.c_str(), atol(value.c_str())); - if( r < 0 ) - { - engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register enum value"); - return -1; - } - } - else if( token == "typedef" ) - { - string type, decl; - in::GetToken(engine, type, config, pos); - in::GetToken(engine, decl, config, pos); - decl = decl.substr(1, decl.length() - 2); - - r = engine->RegisterTypedef(type.c_str(), decl.c_str()); - if( r < 0 ) - { - engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register typedef"); - return -1; - } - } - else if( token == "funcdef" ) - { - string decl; - in::GetToken(engine, decl, config, pos); - decl = decl.substr(1, decl.length() - 2); - - r = engine->RegisterFuncdef(decl.c_str()); - if( r < 0 ) - { - engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register funcdef"); - return -1; - } - } - } - - return 0; -} - -string GetExceptionInfo(asIScriptContext *ctx, bool showStack) -{ - if( ctx->GetState() != asEXECUTION_EXCEPTION ) return ""; - - stringstream text; - - const asIScriptFunction *function = ctx->GetExceptionFunction(); - text << "in function: " << function->GetDeclaration() << "\n"; - if(function->GetScriptSectionName()) - text << "file: " << (function->GetScriptSectionName() ? function->GetScriptSectionName() : "") << "\n"; - if(ctx->GetExceptionLineNumber()) - text << "line: " << ctx->GetExceptionLineNumber() << "\n"; - text << "description: " << ctx->GetExceptionString() << "\n"; - - if( showStack && ctx->GetCallstackSize()>1) - { - text << "--- call stack ---\n"; - for( asUINT n = 1; n < ctx->GetCallstackSize(); n++ ) - { - function = ctx->GetFunction(n); - if( function ) - { - if( function->GetFuncType() == asFUNC_SCRIPT ) - { - text << (function->GetScriptSectionName() ? function->GetScriptSectionName() : "") << " (" << ctx->GetLineNumber(n) << "): " << function->GetDeclaration() << "\n"; - } - else - { - // The context is being reused by the application for a nested call - text << "{...application...}: " << function->GetDeclaration() << "\n"; - } - } - else - { - // The context is being reused by the script engine for a nested call - text << "{...script engine...}\n"; - } - } - } - - return text.str(); -} - -void ScriptThrow(const string &msg) -{ - asIScriptContext *ctx = asGetActiveContext(); - if (ctx) - ctx->SetException(msg.c_str()); -} - -string ScriptGetExceptionInfo() -{ - asIScriptContext *ctx = asGetActiveContext(); - if (!ctx) - return ""; - - const char *msg = ctx->GetExceptionString(); - if (msg == 0) - return ""; - - return string(msg); -} - -int ScriptGetExceptionLine() -{ - asIScriptContext *ctx = asGetActiveContext(); - if (!ctx) - return 0; - return ctx->GetExceptionLineNumber(); -} - -std::string ScriptGetExceptionFunctionDecl() -{ - asIScriptContext *ctx = asGetActiveContext(); - if (!ctx) - return ""; - asIScriptFunction* func=ctx->GetExceptionFunction(); - if(!func) return ""; - return std::string(func->GetDeclaration()); -} - -std::string ScriptGetExceptionModule() -{ - asIScriptContext *ctx = asGetActiveContext(); - if (!ctx) - return ""; - asIScriptFunction* func=ctx->GetExceptionFunction(); - if(!func) return ""; - return std::string(func->GetScriptSectionName()); -} - -void RegisterExceptionRoutines(asIScriptEngine *engine) -{ - int r; - - // The string type must be available - assert(engine->GetTypeInfoByDecl("string")); - - if (strstr(asGetLibraryOptions(), "AS_MAX_PORTABILITY") == 0) - { - r = engine->RegisterGlobalFunction("void throw(const string &in)", asFUNCTION(ScriptThrow), asCALL_CDECL); assert(r >= 0); - r = engine->RegisterGlobalFunction("string get_exception_info()", asFUNCTION(ScriptGetExceptionInfo), asCALL_CDECL); assert(r >= 0); - r = engine->RegisterGlobalFunction("int get_exception_line()", asFUNCTION(ScriptGetExceptionLine), asCALL_CDECL); assert(r >= 0); - r = engine->RegisterGlobalFunction("string get_exception_function()", asFUNCTION(ScriptGetExceptionFunctionDecl), asCALL_CDECL); assert(r >= 0); - r = engine->RegisterGlobalFunction("string get_exception_file()", asFUNCTION(ScriptGetExceptionModule), asCALL_CDECL); assert(r >= 0); - } - else - { - r = engine->RegisterGlobalFunction("void throw(const string &in)", WRAP_FN(ScriptThrow), asCALL_GENERIC); assert(r >= 0); - r = engine->RegisterGlobalFunction("string getExceptionInfo()", WRAP_FN(ScriptGetExceptionInfo), asCALL_GENERIC); assert(r >= 0); - } -} - -END_AS_NAMESPACE +#include +#include "scripthelper.h" +#include +#include +#include +#include +#include +#include "aswrappedcall.h" + +using namespace std; + +BEGIN_AS_NAMESPACE + +int CompareRelation(asIScriptEngine *engine, void *lobj, void *robj, int typeId, int &result) +{ + // TODO: If a lot of script objects are going to be compared, e.g. when sorting an array, + // then the method id and context should be cached between calls. + + int retval = -1; + asIScriptFunction *func = 0; + + asITypeInfo *ti = engine->GetTypeInfoById(typeId); + if( ti ) + { + // Check if the object type has a compatible opCmp method + for( asUINT n = 0; n < ti->GetMethodCount(); n++ ) + { + asIScriptFunction *f = ti->GetMethodByIndex(n); + asDWORD flags; + if( strcmp(f->GetName(), "opCmp") == 0 && + f->GetReturnTypeId(&flags) == asTYPEID_INT32 && + flags == asTM_NONE && + f->GetParamCount() == 1 ) + { + int paramTypeId; + f->GetParam(0, ¶mTypeId, &flags); + + // The parameter must be an input reference of the same type + // If the reference is a inout reference, then it must also be read-only + if( !(flags & asTM_INREF) || typeId != paramTypeId || ((flags & asTM_OUTREF) && !(flags & asTM_CONST)) ) + break; + + // Found the method + func = f; + break; + } + } + } + + if( func ) + { + // Call the method + asIScriptContext *ctx = engine->CreateContext(); + ctx->Prepare(func); + ctx->SetObject(lobj); + ctx->SetArgAddress(0, robj); + int r = ctx->Execute(); + if( r == asEXECUTION_FINISHED ) + { + result = (int)ctx->GetReturnDWord(); + + // The comparison was successful + retval = 0; + } + ctx->Release(); + } + + return retval; +} + +int CompareEquality(asIScriptEngine *engine, void *lobj, void *robj, int typeId, bool &result) +{ + // TODO: If a lot of script objects are going to be compared, e.g. when searching for an + // entry in a set, then the method and context should be cached between calls. + + int retval = -1; + asIScriptFunction *func = 0; + + asITypeInfo *ti = engine->GetTypeInfoById(typeId); + if( ti ) + { + // Check if the object type has a compatible opEquals method + for( asUINT n = 0; n < ti->GetMethodCount(); n++ ) + { + asIScriptFunction *f = ti->GetMethodByIndex(n); + asDWORD flags; + if( strcmp(f->GetName(), "opEquals") == 0 && + f->GetReturnTypeId(&flags) == asTYPEID_BOOL && + flags == asTM_NONE && + f->GetParamCount() == 1 ) + { + int paramTypeId; + f->GetParam(0, ¶mTypeId, &flags); + + // The parameter must be an input reference of the same type + // If the reference is a inout reference, then it must also be read-only + if( !(flags & asTM_INREF) || typeId != paramTypeId || ((flags & asTM_OUTREF) && !(flags & asTM_CONST)) ) + break; + + // Found the method + func = f; + break; + } + } + } + + if( func ) + { + // Call the method + asIScriptContext *ctx = engine->CreateContext(); + ctx->Prepare(func); + ctx->SetObject(lobj); + ctx->SetArgAddress(0, robj); + int r = ctx->Execute(); + if( r == asEXECUTION_FINISHED ) + { + result = ctx->GetReturnByte() ? true : false; + + // The comparison was successful + retval = 0; + } + ctx->Release(); + } + else + { + // If the opEquals method doesn't exist, then we try with opCmp instead + int relation; + retval = CompareRelation(engine, lobj, robj, typeId, relation); + if( retval >= 0 ) + result = relation == 0 ? true : false; + } + + return retval; +} + +int ExecuteString(asIScriptEngine *engine, const char *code, asIScriptModule *mod, asIScriptContext *ctx) +{ + return ExecuteString(engine, code, 0, asTYPEID_VOID, mod, ctx); +} + +int ExecuteString(asIScriptEngine *engine, const char *code, void *ref, int refTypeId, asIScriptModule *mod, asIScriptContext *ctx) +{ + // Wrap the code in a function so that it can be compiled and executed + string funcCode = " ExecuteString() {\n"; + funcCode += code; + funcCode += "\n;}"; + + // Determine the return type based on the type of the ref arg + funcCode = engine->GetTypeDeclaration(refTypeId, true) + funcCode; + + // GetModule will free unused types, so to be on the safe side we'll hold on to a reference to the type + asITypeInfo *type = 0; + if( refTypeId & asTYPEID_MASK_OBJECT ) + { + type = engine->GetTypeInfoById(refTypeId); + if( type ) + type->AddRef(); + } + + // If no module was provided, get a dummy from the engine + asIScriptModule *execMod = mod ? mod : engine->GetModule("ExecuteString", asGM_ALWAYS_CREATE); + + // Now it's ok to release the type + if( type ) + type->Release(); + + // Compile the function that can be executed + asIScriptFunction *func = 0; + int r = execMod->CompileFunction("ExecuteString", funcCode.c_str(), -1, 0, &func); + if( r < 0 ) + return r; + + // If no context was provided, request a new one from the engine + asIScriptContext *execCtx = ctx ? ctx : engine->RequestContext(); + r = execCtx->Prepare(func); + if (r >= 0) + { + // Execute the function + r = execCtx->Execute(); + + // Unless the provided type was void retrieve it's value + if (ref != 0 && refTypeId != asTYPEID_VOID) + { + if (refTypeId & asTYPEID_OBJHANDLE) + { + // Expect the pointer to be null to start with + assert(*reinterpret_cast(ref) == 0); + *reinterpret_cast(ref) = *reinterpret_cast(execCtx->GetAddressOfReturnValue()); + engine->AddRefScriptObject(*reinterpret_cast(ref), engine->GetTypeInfoById(refTypeId)); + } + else if (refTypeId & asTYPEID_MASK_OBJECT) + { + // Use the registered assignment operator to do a value assign. + // This assumes that the ref is pointing to a valid object instance. + engine->AssignScriptObject(ref, execCtx->GetAddressOfReturnValue(), engine->GetTypeInfoById(refTypeId)); + } + else + { + // Copy the primitive value + memcpy(ref, execCtx->GetAddressOfReturnValue(), engine->GetSizeOfPrimitiveType(refTypeId)); + } + } + } + + // Clean up + func->Release(); + if( !ctx ) engine->ReturnContext(execCtx); + + return r; +} + +int WriteConfigToFile(asIScriptEngine *engine, const char *filename) +{ + ofstream strm; + strm.open(filename); + + return WriteConfigToStream(engine, strm); +} + +int WriteConfigToStream(asIScriptEngine *engine, ostream &strm) +{ + // A helper function for escaping quotes in default arguments + struct Escape + { + static string Quotes(const char *decl) + { + string str = decl; + size_t pos = 0; + for(;;) + { + // Find " characters + pos = str.find("\"",pos); + if( pos == string::npos ) + break; + + // Add a \ to escape them + str.insert(pos, "\\"); + pos += 2; + } + + return str; + } + }; + + int c, n; + + asDWORD currAccessMask = 0; + string currNamespace = ""; + engine->SetDefaultNamespace(""); + + // Export the engine version, just for info + strm << "// AngelScript " << asGetLibraryVersion() << "\n"; + strm << "// Lib options " << asGetLibraryOptions() << "\n"; + + // Export the relevant engine properties + strm << "// Engine properties\n"; + for( n = 0; n < asEP_LAST_PROPERTY; n++ ) + strm << "ep " << n << " " << engine->GetEngineProperty(asEEngineProp(n)) << "\n"; + + // Make sure the default array type is expanded to the template form + bool expandDefArrayToTempl = engine->GetEngineProperty(asEP_EXPAND_DEF_ARRAY_TO_TMPL) ? true : false; + engine->SetEngineProperty(asEP_EXPAND_DEF_ARRAY_TO_TMPL, true); + + // Write enum types and their values + strm << "\n// Enums\n"; + c = engine->GetEnumCount(); + for( n = 0; n < c; n++ ) + { + asITypeInfo *ti = engine->GetEnumByIndex(n); + asDWORD accessMask = ti->GetAccessMask(); + if( accessMask != currAccessMask ) + { + strm << "access " << hex << (unsigned int)(accessMask) << dec << "\n"; + currAccessMask = accessMask; + } + const char *nameSpace = ti->GetNamespace(); + if( nameSpace != currNamespace ) + { + strm << "namespace \"" << nameSpace << "\"\n"; + currNamespace = nameSpace; + engine->SetDefaultNamespace(currNamespace.c_str()); + } + const char *enumName = ti->GetName(); + strm << "enum " << enumName << "\n"; + for( asUINT m = 0; m < ti->GetEnumValueCount(); m++ ) + { + const char *valName; + int val; + valName = ti->GetEnumValueByIndex(m, &val); + strm << "enumval " << enumName << " " << valName << " " << val << "\n"; + } + } + + // Enumerate all types + strm << "\n// Types\n"; + + // Keep a list of the template types, as the methods for these need to be exported first + set templateTypes; + + c = engine->GetObjectTypeCount(); + for( n = 0; n < c; n++ ) + { + asITypeInfo *type = engine->GetObjectTypeByIndex(n); + asDWORD accessMask = type->GetAccessMask(); + if( accessMask != currAccessMask ) + { + strm << "access " << hex << (unsigned int)(accessMask) << dec << "\n"; + currAccessMask = accessMask; + } + const char *nameSpace = type->GetNamespace(); + if( nameSpace != currNamespace ) + { + strm << "namespace \"" << nameSpace << "\"\n"; + currNamespace = nameSpace; + engine->SetDefaultNamespace(currNamespace.c_str()); + } + if( type->GetFlags() & asOBJ_SCRIPT_OBJECT ) + { + // This should only be interfaces + assert( type->GetSize() == 0 ); + + strm << "intf " << type->GetName() << "\n"; + } + else + { + // Only the type flags are necessary. The application flags are application + // specific and doesn't matter to the offline compiler. The object size is also + // unnecessary for the offline compiler + strm << "objtype \"" << engine->GetTypeDeclaration(type->GetTypeId()) << "\" " << (unsigned int)(type->GetFlags() & asOBJ_MASK_VALID_FLAGS) << "\n"; + + // Store the template types (but not template instances) + if( (type->GetFlags() & asOBJ_TEMPLATE) && type->GetSubType() && (type->GetSubType()->GetFlags() & asOBJ_TEMPLATE_SUBTYPE) ) + templateTypes.insert(type); + } + } + + c = engine->GetTypedefCount(); + for( n = 0; n < c; n++ ) + { + asITypeInfo *ti = engine->GetTypedefByIndex(n); + const char *nameSpace = ti->GetNamespace(); + if( nameSpace != currNamespace ) + { + strm << "namespace \"" << nameSpace << "\"\n"; + currNamespace = nameSpace; + engine->SetDefaultNamespace(currNamespace.c_str()); + } + asDWORD accessMask = ti->GetAccessMask(); + if( accessMask != currAccessMask ) + { + strm << "access " << hex << (unsigned int)(accessMask) << dec << "\n"; + currAccessMask = accessMask; + } + strm << "typedef " << ti->GetName() << " \"" << engine->GetTypeDeclaration(ti->GetTypedefTypeId()) << "\"\n"; + } + + c = engine->GetFuncdefCount(); + for( n = 0; n < c; n++ ) + { + asITypeInfo *funcDef = engine->GetFuncdefByIndex(n); + asDWORD accessMask = funcDef->GetAccessMask(); + const char *nameSpace = funcDef->GetNamespace(); + // Child funcdefs do not have any namespace, as they belong to the parent object + if( nameSpace && nameSpace != currNamespace ) + { + strm << "namespace \"" << nameSpace << "\"\n"; + currNamespace = nameSpace; + engine->SetDefaultNamespace(currNamespace.c_str()); + } + if( accessMask != currAccessMask ) + { + strm << "access " << hex << (unsigned int)(accessMask) << dec << "\n"; + currAccessMask = accessMask; + } + strm << "funcdef \"" << funcDef->GetFuncdefSignature()->GetDeclaration(true, false, true) << "\"\n"; + } + + // A helper for writing object type members + struct TypeWriter + { + static void Write(asIScriptEngine *engine, ostream &strm, asITypeInfo *type, string &currNamespace, asDWORD &currAccessMask) + { + const char *nameSpace = type->GetNamespace(); + if( nameSpace != currNamespace ) + { + strm << "namespace \"" << nameSpace << "\"\n"; + currNamespace = nameSpace; + engine->SetDefaultNamespace(currNamespace.c_str()); + } + string typeDecl = engine->GetTypeDeclaration(type->GetTypeId()); + if( type->GetFlags() & asOBJ_SCRIPT_OBJECT ) + { + for( asUINT m = 0; m < type->GetMethodCount(); m++ ) + { + asIScriptFunction *func = type->GetMethodByIndex(m); + asDWORD accessMask = func->GetAccessMask(); + if( accessMask != currAccessMask ) + { + strm << "access " << hex << (unsigned int)(accessMask) << dec << "\n"; + currAccessMask = accessMask; + } + strm << "intfmthd " << typeDecl.c_str() << " \"" << Escape::Quotes(func->GetDeclaration(false, false, true)).c_str() << (func->IsProperty() ? " property" : "") << "\"\n"; + } + } + else + { + asUINT m; + for( m = 0; m < type->GetFactoryCount(); m++ ) + { + asIScriptFunction *func = type->GetFactoryByIndex(m); + asDWORD accessMask = func->GetAccessMask(); + if( accessMask != currAccessMask ) + { + strm << "access " << hex << (unsigned int)(accessMask) << dec << "\n"; + currAccessMask = accessMask; + } + strm << "objbeh \"" << typeDecl.c_str() << "\" " << asBEHAVE_FACTORY << " \"" << Escape::Quotes(func->GetDeclaration(false, false, true)).c_str() << "\"\n"; + } + for( m = 0; m < type->GetBehaviourCount(); m++ ) + { + asEBehaviours beh; + asIScriptFunction *func = type->GetBehaviourByIndex(m, &beh); + + if( beh == asBEHAVE_CONSTRUCT ) + // Prefix 'void' + strm << "objbeh \"" << typeDecl.c_str() << "\" " << beh << " \"void " << Escape::Quotes(func->GetDeclaration(false, false, true)).c_str() << "\"\n"; + else if( beh == asBEHAVE_DESTRUCT ) + // Prefix 'void' and remove ~ + strm << "objbeh \"" << typeDecl.c_str() << "\" " << beh << " \"void " << Escape::Quotes(func->GetDeclaration(false, false, true)).c_str()+1 << "\"\n"; + else + strm << "objbeh \"" << typeDecl.c_str() << "\" " << beh << " \"" << Escape::Quotes(func->GetDeclaration(false, false, true)).c_str() << "\"\n"; + } + for( m = 0; m < type->GetMethodCount(); m++ ) + { + asIScriptFunction *func = type->GetMethodByIndex(m); + asDWORD accessMask = func->GetAccessMask(); + if( accessMask != currAccessMask ) + { + strm << "access " << hex << (unsigned int)(accessMask) << dec << "\n"; + currAccessMask = accessMask; + } + strm << "objmthd \"" << typeDecl.c_str() << "\" \"" << Escape::Quotes(func->GetDeclaration(false, false, true)).c_str() << (func->IsProperty() ? " property" : "") << "\"\n"; + } + for( m = 0; m < type->GetPropertyCount(); m++ ) + { + asDWORD accessMask; + type->GetProperty(m, 0, 0, 0, 0, 0, 0, &accessMask); + if( accessMask != currAccessMask ) + { + strm << "access " << hex << (unsigned int)(accessMask) << dec << "\n"; + currAccessMask = accessMask; + } + strm << "objprop \"" << typeDecl.c_str() << "\" \"" << type->GetPropertyDeclaration(m) << "\""; + + // Save information about composite properties + int compositeOffset; + bool isCompositeIndirect; + type->GetProperty(m, 0, 0, 0, 0, 0, 0, 0, &compositeOffset, &isCompositeIndirect); + strm << " " << compositeOffset << " " << (isCompositeIndirect ? "1" : "0") << "\n"; + } + } + } + }; + + // Write the members of the template types, so they can be fully registered before any other type uses them + // TODO: Order the template types based on dependency to avoid failure if one type uses instances of another + strm << "\n// Template type members\n"; + for( set::iterator it = templateTypes.begin(); it != templateTypes.end(); ++it ) + { + asITypeInfo *type = *it; + TypeWriter::Write(engine, strm, type, currNamespace, currAccessMask); + } + + // Write the object types members + strm << "\n// Type members\n"; + + c = engine->GetObjectTypeCount(); + for( n = 0; n < c; n++ ) + { + asITypeInfo *type = engine->GetObjectTypeByIndex(n); + if( templateTypes.find(type) == templateTypes.end() ) + TypeWriter::Write(engine, strm, type, currNamespace, currAccessMask); + } + + // Write functions + strm << "\n// Functions\n"; + + c = engine->GetGlobalFunctionCount(); + for( n = 0; n < c; n++ ) + { + asIScriptFunction *func = engine->GetGlobalFunctionByIndex(n); + const char *nameSpace = func->GetNamespace(); + if( nameSpace != currNamespace ) + { + strm << "namespace \"" << nameSpace << "\"\n"; + currNamespace = nameSpace; + engine->SetDefaultNamespace(currNamespace.c_str()); + } + asDWORD accessMask = func->GetAccessMask(); + if( accessMask != currAccessMask ) + { + strm << "access " << hex << (unsigned int)(accessMask) << dec << "\n"; + currAccessMask = accessMask; + } + strm << "func \"" << Escape::Quotes(func->GetDeclaration(true, false, true)).c_str() << (func->IsProperty() ? " property" : "") << "\"\n"; + } + + // Write global properties + strm << "\n// Properties\n"; + + c = engine->GetGlobalPropertyCount(); + for( n = 0; n < c; n++ ) + { + const char *name; + int typeId; + bool isConst; + asDWORD accessMask; + const char *nameSpace; + engine->GetGlobalPropertyByIndex(n, &name, &nameSpace, &typeId, &isConst, 0, 0, &accessMask); + if( accessMask != currAccessMask ) + { + strm << "access " << hex << (unsigned int)(accessMask) << dec << "\n"; + currAccessMask = accessMask; + } + if( nameSpace != currNamespace ) + { + strm << "namespace \"" << nameSpace << "\"\n"; + currNamespace = nameSpace; + engine->SetDefaultNamespace(currNamespace.c_str()); + } + strm << "prop \"" << (isConst ? "const " : "") << engine->GetTypeDeclaration(typeId) << " " << name << "\"\n"; + } + + // Write string factory + strm << "\n// String factory\n"; + + // Reset the namespace for the string factory and default array type + if ("" != currNamespace) + { + strm << "namespace \"\"\n"; + currNamespace = ""; + engine->SetDefaultNamespace(""); + } + + asDWORD flags = 0; + int typeId = engine->GetStringFactory(&flags); + if( typeId > 0 ) + strm << "strfactory \"" << ((flags & asTM_CONST) ? "const " : "") << engine->GetTypeDeclaration(typeId) << ((flags & asTM_INOUTREF) ? "&" : "") << "\"\n"; + + // Write default array type + strm << "\n// Default array type\n"; + typeId = engine->GetDefaultArrayTypeId(); + if( typeId > 0 ) + strm << "defarray \"" << engine->GetTypeDeclaration(typeId) << "\"\n"; + + // Restore original settings + engine->SetEngineProperty(asEP_EXPAND_DEF_ARRAY_TO_TMPL, expandDefArrayToTempl); + + return 0; +} + +int ConfigEngineFromStream(asIScriptEngine *engine, istream &strm, const char *configFile, asIStringFactory *stringFactory) +{ + int r; + + // Some helper functions for parsing the configuration + struct in + { + static asETokenClass GetToken(asIScriptEngine *engine, string &token, const string &text, asUINT &pos) + { + asUINT len = 0; + asETokenClass t = engine->ParseToken(&text[pos], text.length() - pos, &len); + while( (t == asTC_WHITESPACE || t == asTC_COMMENT) && pos < text.length() ) + { + pos += len; + t = engine->ParseToken(&text[pos], text.length() - pos, &len); + } + + token.assign(&text[pos], len); + + pos += len; + + return t; + } + + static void ReplaceSlashQuote(string &str) + { + size_t pos = 0; + for(;;) + { + // Search for \" in the string + pos = str.find("\\\"", pos); + if( pos == string::npos ) + break; + + // Remove the \ character + str.erase(pos, 1); + } + } + + static asUINT GetLineNumber(const string &text, asUINT pos) + { + asUINT count = 1; + for( asUINT n = 0; n < pos; n++ ) + if( text[n] == '\n' ) + count++; + + return count; + } + }; + + // Since we are only going to compile the script and never actually execute it, + // we turn off the initialization of global variables, so that the compiler can + // just register dummy types and functions for the application interface. + r = engine->SetEngineProperty(asEP_INIT_GLOBAL_VARS_AFTER_BUILD, false); assert( r >= 0 ); + + // Read the entire file + char buffer[1000]; + string config; + do { + strm.getline(buffer, 1000); + config += buffer; + config += "\n"; + } while( !strm.eof() && strm.good() ); + + // Process the configuration file and register each entity + asUINT pos = 0; + while( pos < config.length() ) + { + string token; + // TODO: The position where the initial token is found should be stored for error messages + in::GetToken(engine, token, config, pos); + if( token == "ep" ) + { + string tmp; + in::GetToken(engine, tmp, config, pos); + + asEEngineProp ep = asEEngineProp(atol(tmp.c_str())); + + // Only set properties that affect the compiler + if( ep != asEP_COPY_SCRIPT_SECTIONS && + ep != asEP_MAX_STACK_SIZE && + ep != asEP_INIT_GLOBAL_VARS_AFTER_BUILD && + ep != asEP_EXPAND_DEF_ARRAY_TO_TMPL && + ep != asEP_AUTO_GARBAGE_COLLECT ) + { + // Get the value for the property + in::GetToken(engine, tmp, config, pos); + stringstream s(tmp); + asPWORD value; + + s >> value; + + engine->SetEngineProperty(ep, value); + } + } + else if( token == "namespace" ) + { + string ns; + in::GetToken(engine, ns, config, pos); + ns = ns.substr(1, ns.length() - 2); + + r = engine->SetDefaultNamespace(ns.c_str()); + if( r < 0 ) + { + engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to set namespace"); + return -1; + } + } + else if( token == "access" ) + { + string maskStr; + in::GetToken(engine, maskStr, config, pos); + asDWORD mask = strtoul(maskStr.c_str(), 0, 16); + engine->SetDefaultAccessMask(mask); + } + else if( token == "objtype" ) + { + string name, flags; + in::GetToken(engine, name, config, pos); + name = name.substr(1, name.length() - 2); + in::GetToken(engine, flags, config, pos); + + // The size of the value type doesn't matter, because the + // engine must adjust it anyway for different platforms + r = engine->RegisterObjectType(name.c_str(), (atol(flags.c_str()) & asOBJ_VALUE) ? 1 : 0, atol(flags.c_str())); + if( r < 0 ) + { + engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register object type"); + return -1; + } + } + else if( token == "objbeh" ) + { + string name, behaviour, decl; + in::GetToken(engine, name, config, pos); + name = name.substr(1, name.length() - 2); + in::GetToken(engine, behaviour, config, pos); + in::GetToken(engine, decl, config, pos); + decl = decl.substr(1, decl.length() - 2); + in::ReplaceSlashQuote(decl); + + // Remove the $ that the engine prefixes the behaviours with + size_t n = decl.find("$"); + if( n != string::npos ) + decl[n] = ' '; + + asEBehaviours behave = static_cast(atol(behaviour.c_str())); + if( behave == asBEHAVE_TEMPLATE_CALLBACK ) + { + // TODO: How can we let the compiler register this? Maybe through a plug-in system? Or maybe by implementing the callback as a script itself + engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_WARNING, "Cannot register template callback without the actual implementation"); + } + else + { + r = engine->RegisterObjectBehaviour(name.c_str(), behave, decl.c_str(), asFUNCTION(0), asCALL_GENERIC); + if( r < 0 ) + { + engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register behaviour"); + return -1; + } + } + } + else if( token == "objmthd" ) + { + string name, decl; + in::GetToken(engine, name, config, pos); + name = name.substr(1, name.length() - 2); + in::GetToken(engine, decl, config, pos); + decl = decl.substr(1, decl.length() - 2); + in::ReplaceSlashQuote(decl); + + r = engine->RegisterObjectMethod(name.c_str(), decl.c_str(), asFUNCTION(0), asCALL_GENERIC); + if( r < 0 ) + { + engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register object method"); + return -1; + } + } + else if( token == "objprop" ) + { + string name, decl, compositeOffset, isCompositeIndirect; + in::GetToken(engine, name, config, pos); + name = name.substr(1, name.length() - 2); + in::GetToken(engine, decl, config, pos); + decl = decl.substr(1, decl.length() - 2); + in::GetToken(engine, compositeOffset, config, pos); + in::GetToken(engine, isCompositeIndirect, config, pos); + + asITypeInfo *type = engine->GetTypeInfoById(engine->GetTypeIdByDecl(name.c_str())); + if( type == 0 ) + { + engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Type doesn't exist for property registration"); + return -1; + } + + // All properties must have different offsets in order to make them + // distinct, so we simply register them with an incremental offset + r = engine->RegisterObjectProperty(name.c_str(), decl.c_str(), type->GetPropertyCount(), compositeOffset != "0" ? type->GetPropertyCount() : 0, isCompositeIndirect != "0"); + if( r < 0 ) + { + engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register object property"); + return -1; + } + } + else if( token == "intf" ) + { + string name, size, flags; + in::GetToken(engine, name, config, pos); + + r = engine->RegisterInterface(name.c_str()); + if( r < 0 ) + { + engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register interface"); + return -1; + } + } + else if( token == "intfmthd" ) + { + string name, decl; + in::GetToken(engine, name, config, pos); + in::GetToken(engine, decl, config, pos); + decl = decl.substr(1, decl.length() - 2); + in::ReplaceSlashQuote(decl); + + r = engine->RegisterInterfaceMethod(name.c_str(), decl.c_str()); + if( r < 0 ) + { + engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register interface method"); + return -1; + } + } + else if( token == "func" ) + { + string decl; + in::GetToken(engine, decl, config, pos); + decl = decl.substr(1, decl.length() - 2); + in::ReplaceSlashQuote(decl); + + r = engine->RegisterGlobalFunction(decl.c_str(), asFUNCTION(0), asCALL_GENERIC); + if( r < 0 ) + { + engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register global function"); + return -1; + } + } + else if( token == "prop" ) + { + string decl; + in::GetToken(engine, decl, config, pos); + decl = decl.substr(1, decl.length() - 2); + + // All properties must have different offsets in order to make them + // distinct, so we simply register them with an incremental offset. + // The pointer must also be non-null so we add 1 to have a value. + r = engine->RegisterGlobalProperty(decl.c_str(), reinterpret_cast(asPWORD(engine->GetGlobalPropertyCount()+1))); + if( r < 0 ) + { + engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register global property"); + return -1; + } + } + else if( token == "strfactory" ) + { + string type; + in::GetToken(engine, type, config, pos); + type = type.substr(1, type.length() - 2); + + if (stringFactory == 0) + { + engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_WARNING, "Cannot register string factory without the actual implementation"); + return -1; + } + else + { + r = engine->RegisterStringFactory(type.c_str(), stringFactory); + if (r < 0) + { + engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register string factory"); + return -1; + } + } + } + else if( token == "defarray" ) + { + string type; + in::GetToken(engine, type, config, pos); + type = type.substr(1, type.length() - 2); + + r = engine->RegisterDefaultArrayType(type.c_str()); + if( r < 0 ) + { + engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register the default array type"); + return -1; + } + } + else if( token == "enum" ) + { + string type; + in::GetToken(engine, type, config, pos); + + r = engine->RegisterEnum(type.c_str()); + if( r < 0 ) + { + engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register enum type"); + return -1; + } + } + else if( token == "enumval" ) + { + string type, name, value; + in::GetToken(engine, type, config, pos); + in::GetToken(engine, name, config, pos); + in::GetToken(engine, value, config, pos); + + r = engine->RegisterEnumValue(type.c_str(), name.c_str(), atol(value.c_str())); + if( r < 0 ) + { + engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register enum value"); + return -1; + } + } + else if( token == "typedef" ) + { + string type, decl; + in::GetToken(engine, type, config, pos); + in::GetToken(engine, decl, config, pos); + decl = decl.substr(1, decl.length() - 2); + + r = engine->RegisterTypedef(type.c_str(), decl.c_str()); + if( r < 0 ) + { + engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register typedef"); + return -1; + } + } + else if( token == "funcdef" ) + { + string decl; + in::GetToken(engine, decl, config, pos); + decl = decl.substr(1, decl.length() - 2); + + r = engine->RegisterFuncdef(decl.c_str()); + if( r < 0 ) + { + engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register funcdef"); + return -1; + } + } + } + + return 0; +} + +string GetExceptionInfo(asIScriptContext *ctx, bool showStack) +{ + if( ctx->GetState() != asEXECUTION_EXCEPTION ) return ""; + + stringstream text; + + const asIScriptFunction *function = ctx->GetExceptionFunction(); + text << "in function: " << function->GetDeclaration() << "\n"; + if(function->GetScriptSectionName()) + text << "file: " << (function->GetScriptSectionName() ? function->GetScriptSectionName() : "") << "\n"; + if(ctx->GetExceptionLineNumber()) + text << "line: " << ctx->GetExceptionLineNumber() << "\n"; + text << "description: " << ctx->GetExceptionString() << "\n"; + + if( showStack && ctx->GetCallstackSize()>1) + { + text << "--- call stack ---\n"; + for( asUINT n = 1; n < ctx->GetCallstackSize(); n++ ) + { + function = ctx->GetFunction(n); + if( function ) + { + if( function->GetFuncType() == asFUNC_SCRIPT ) + { + text << (function->GetScriptSectionName() ? function->GetScriptSectionName() : "") << " (" << ctx->GetLineNumber(n) << "): " << function->GetDeclaration() << "\n"; + } + else + { + // The context is being reused by the application for a nested call + text << "{...application...}: " << function->GetDeclaration() << "\n"; + } + } + else + { + // The context is being reused by the script engine for a nested call + text << "{...script engine...}\n"; + } + } + } + + return text.str(); +} + +void ScriptThrow(const string &msg) +{ + asIScriptContext *ctx = asGetActiveContext(); + if (ctx) + ctx->SetException(msg.c_str()); +} + +string ScriptGetExceptionInfo() +{ + asIScriptContext *ctx = asGetActiveContext(); + if (!ctx) + return ""; + + const char *msg = ctx->GetExceptionString(); + if (msg == 0) + return ""; + + return string(msg); +} + +int ScriptGetExceptionLine() +{ + asIScriptContext *ctx = asGetActiveContext(); + if (!ctx) + return 0; + return ctx->GetExceptionLineNumber(); +} + +std::string ScriptGetExceptionFunctionDecl() +{ + asIScriptContext *ctx = asGetActiveContext(); + if (!ctx) + return ""; + asIScriptFunction* func=ctx->GetExceptionFunction(); + if(!func) return ""; + return std::string(func->GetDeclaration()); +} + +std::string ScriptGetExceptionModule() +{ + asIScriptContext *ctx = asGetActiveContext(); + if (!ctx) + return ""; + asIScriptFunction* func=ctx->GetExceptionFunction(); + if(!func) return ""; + return std::string(func->GetScriptSectionName()); +} + +void RegisterExceptionRoutines(asIScriptEngine *engine) +{ + int r; + + // The string type must be available + assert(engine->GetTypeInfoByDecl("string")); + + if (strstr(asGetLibraryOptions(), "AS_MAX_PORTABILITY") == 0) + { + r = engine->RegisterGlobalFunction("void throw(const string &in)", asFUNCTION(ScriptThrow), asCALL_CDECL); assert(r >= 0); + r = engine->RegisterGlobalFunction("string get_exception_info()", asFUNCTION(ScriptGetExceptionInfo), asCALL_CDECL); assert(r >= 0); + r = engine->RegisterGlobalFunction("int get_exception_line()", asFUNCTION(ScriptGetExceptionLine), asCALL_CDECL); assert(r >= 0); + r = engine->RegisterGlobalFunction("string get_exception_function()", asFUNCTION(ScriptGetExceptionFunctionDecl), asCALL_CDECL); assert(r >= 0); + r = engine->RegisterGlobalFunction("string get_exception_file()", asFUNCTION(ScriptGetExceptionModule), asCALL_CDECL); assert(r >= 0); + } + else + { + r = engine->RegisterGlobalFunction("void throw(const string &in)", WRAP_FN(ScriptThrow), asCALL_GENERIC); assert(r >= 0); + r = engine->RegisterGlobalFunction("string getExceptionInfo()", WRAP_FN(ScriptGetExceptionInfo), asCALL_GENERIC); assert(r >= 0); + } +} + +END_AS_NAMESPACE diff --git a/build/build_linux.sh b/build/build_linux.sh index 23e78df8..6c8b1999 100644 --- a/build/build_linux.sh +++ b/build/build_linux.sh @@ -1,155 +1,155 @@ -#!/bin/bash - -function setup_angelscript { - echo Installing Angelscript... - git clone --depth 1 https://github.com/codecat/angelscript-mirror||true - cd "angelscript-mirror/sdk/angelscript/projects/gnuc" - make -j$(nproc) - sudo make install - cd ../../../../.. - echo Angelscript installed. -} - -function setup_bullet { - echo Installing bullet3... - sudo apt install python3-dev -y - git clone --depth 1 https://github.com/bulletphysics/bullet3||true - cd bullet3 - ./build_cmake_pybullet_double.sh - cd build_cmake - sudo cmake --install . - cd ../.. -} - -function setup_enet { - echo Installing enet... - git clone --depth 1 https://github.com/lsalzman/enet||true - cd enet - autoreconf -vfi - ./configure - make -j$(nproc) - sudo make install - cd .. - echo Enet installed. -} - -function setup_libgit2 { - echo Installing libgit2... - curl -s -O -L https://github.com/libgit2/libgit2/archive/refs/tags/v1.8.1.tar.gz - tar -xzf v1.8.1.tar.gz - cd libgit2-1.8.1 - mkdir -p build - cd build - cmake .. -DBUILD_TESTS=OFF -DUSE_ICONV=OFF -DBUILD_CLI=OFF -DCMAKE_BUILD_TYPE=Release - cmake --build . - sudo cmake --install . - cd ../.. - rm v1.8.1.tar.gz - echo libgit2 installed. -} - -function setup_libplist { - echo Installing libplist... - curl -s -O -L https://github.com/libimobiledevice/libplist/releases/download/2.6.0/libplist-2.6.0.tar.bz2 - tar -xf libplist-2.6.0.tar.bz2 - cd libplist-2.6.0 - ./configure --without-cython - make - sudo make install - cd .. - rm libplist-2.6.0.tar.bz2 - echo libplist installed. -} - -function setup_poco { - echo Installing poco... - curl -s -O https://pocoproject.org/releases/poco-1.13.3/poco-1.13.3-all.tar.gz - tar -xzf poco-1.13.3-all.tar.gz - cd poco-1.13.3-all - mkdir -p cmake_build - cd cmake_build - export CFLAGS="-fPIC -DPOCO_UTIL_NO_XMLCONFIGURATION" - export CXXFLAGS="-fPIC -DPOCO_UTIL_NO_XMLCONFIGURATION" - cmake .. -DENABLE_TESTS=OFF -DENABLE_SAMPLES=OFF -DCMAKE_BUILD_TYPE=MinSizeRel -DENABLE_PAGECOMPILER=OFF -DENABLE_PAGECOMPILER_FILE2PAGE=OFF -DENABLE_ACTIVERECORD=OFF -DENABLE_ACTIVERECORD_COMPILER=OFF -DENABLE_MONGODB=OFF -DBUILD_SHARED_LIBS=OFF - cmake --build . - sudo cmake --install . - cd ../.. - rm poco-1.13.3-all.tar.gz - echo poco installed. -} - -function setup_sdl { - echo Installing SDL... - # Install SDL this way to get many SDL deps. It is too old so we remove SDL itself and build from source, however. - sudo apt install libssl-dev libcurl4-openssl-dev libopus-dev libsdl2-dev -y - sudo apt remove libsdl2-dev -y - git clone https://github.com/libsdl-org/SDL||true - mkdir -p SDL/build - cd SDL/build - git checkout 4e09e58f62e95a66125dae9ddd3e302603819ffd - cmake -DCMAKE_BUILD_TYPE=MinSizeRel -DSDL_SHARED=OFF -DSDL_STATIC=ON -DSDL_TEST_LIBRARY=OFF .. - cmake --build . --config MinSizeRel - sudo make install - cd ../.. - echo SDL installed. -} - -function setup_nvgt { - echo Building NVGT... - - if [[ "$IS_CI" != 1 ]]; then - # Assumed to not be running on CI; NVGT should be cloned outside of deps first. - echo Not running on CI. - cd .. - - git clone --depth 1 https://github.com/samtupy/nvgt||true - cd nvgt - - else - echo Running on CI. - cd .. - fi - - echo Downloading lindev... - wget https://nvgt.gg/lindev.tar.gz - mkdir -p lindev - cd lindev - tar -xvf ../lindev.tar.gz - cd .. - rm lindev.tar.gz - if ! which scons &> /dev/null; then - export PIP_BREAK_SYSTEM_PACKAGES=1 - pip3 install --user scons - fi - scons -s no_upx=0 - echo NVGT built. -} - -function main { - sudo apt update -y - set -e - mkdir -p deps - cd deps - - # Insure required packages are installed for building. - sudo apt install build-essential gcc g++ make cmake autoconf libtool python3 python3-pip libsystemd-dev libspeechd-dev -y - - setup_angelscript - setup_bullet - setup_enet - setup_libgit2 - setup_libplist - setup_poco - setup_sdl - setup_nvgt - echo Success! - exit 0 -} - -if [[ "$1" == "ci" ]]; then - export IS_CI=1 -else - export IS_CI=0 -fi - -main +#!/bin/bash + +function setup_angelscript { + echo Installing Angelscript... + git clone --depth 1 https://github.com/codecat/angelscript-mirror||true + cd "angelscript-mirror/sdk/angelscript/projects/gnuc" + make -j$(nproc) + sudo make install + cd ../../../../.. + echo Angelscript installed. +} + +function setup_bullet { + echo Installing bullet3... + sudo apt install python3-dev -y + git clone --depth 1 https://github.com/bulletphysics/bullet3||true + cd bullet3 + ./build_cmake_pybullet_double.sh + cd build_cmake + sudo cmake --install . + cd ../.. +} + +function setup_enet { + echo Installing enet... + git clone --depth 1 https://github.com/lsalzman/enet||true + cd enet + autoreconf -vfi + ./configure + make -j$(nproc) + sudo make install + cd .. + echo Enet installed. +} + +function setup_libgit2 { + echo Installing libgit2... + curl -s -O -L https://github.com/libgit2/libgit2/archive/refs/tags/v1.8.1.tar.gz + tar -xzf v1.8.1.tar.gz + cd libgit2-1.8.1 + mkdir -p build + cd build + cmake .. -DBUILD_TESTS=OFF -DUSE_ICONV=OFF -DBUILD_CLI=OFF -DCMAKE_BUILD_TYPE=Release + cmake --build . + sudo cmake --install . + cd ../.. + rm v1.8.1.tar.gz + echo libgit2 installed. +} + +function setup_libplist { + echo Installing libplist... + curl -s -O -L https://github.com/libimobiledevice/libplist/releases/download/2.6.0/libplist-2.6.0.tar.bz2 + tar -xf libplist-2.6.0.tar.bz2 + cd libplist-2.6.0 + ./configure --without-cython + make + sudo make install + cd .. + rm libplist-2.6.0.tar.bz2 + echo libplist installed. +} + +function setup_poco { + echo Installing poco... + curl -s -O https://pocoproject.org/releases/poco-1.13.3/poco-1.13.3-all.tar.gz + tar -xzf poco-1.13.3-all.tar.gz + cd poco-1.13.3-all + mkdir -p cmake_build + cd cmake_build + export CFLAGS="-fPIC -DPOCO_UTIL_NO_XMLCONFIGURATION" + export CXXFLAGS="-fPIC -DPOCO_UTIL_NO_XMLCONFIGURATION" + cmake .. -DENABLE_TESTS=OFF -DENABLE_SAMPLES=OFF -DCMAKE_BUILD_TYPE=MinSizeRel -DENABLE_PAGECOMPILER=OFF -DENABLE_PAGECOMPILER_FILE2PAGE=OFF -DENABLE_ACTIVERECORD=OFF -DENABLE_ACTIVERECORD_COMPILER=OFF -DENABLE_MONGODB=OFF -DBUILD_SHARED_LIBS=OFF + cmake --build . + sudo cmake --install . + cd ../.. + rm poco-1.13.3-all.tar.gz + echo poco installed. +} + +function setup_sdl { + echo Installing SDL... + # Install SDL this way to get many SDL deps. It is too old so we remove SDL itself and build from source, however. + sudo apt install libssl-dev libcurl4-openssl-dev libopus-dev libsdl2-dev -y + sudo apt remove libsdl2-dev -y + git clone https://github.com/libsdl-org/SDL||true + mkdir -p SDL/build + cd SDL/build + git checkout 4e09e58f62e95a66125dae9ddd3e302603819ffd + cmake -DCMAKE_BUILD_TYPE=MinSizeRel -DSDL_SHARED=OFF -DSDL_STATIC=ON -DSDL_TEST_LIBRARY=OFF .. + cmake --build . --config MinSizeRel + sudo make install + cd ../.. + echo SDL installed. +} + +function setup_nvgt { + echo Building NVGT... + + if [[ "$IS_CI" != 1 ]]; then + # Assumed to not be running on CI; NVGT should be cloned outside of deps first. + echo Not running on CI. + cd .. + + git clone --depth 1 https://github.com/samtupy/nvgt||true + cd nvgt + + else + echo Running on CI. + cd .. + fi + + echo Downloading lindev... + wget https://nvgt.gg/lindev.tar.gz + mkdir -p lindev + cd lindev + tar -xvf ../lindev.tar.gz + cd .. + rm lindev.tar.gz + if ! which scons &> /dev/null; then + export PIP_BREAK_SYSTEM_PACKAGES=1 + pip3 install --user scons + fi + scons -s no_upx=0 + echo NVGT built. +} + +function main { + sudo apt update -y + set -e + mkdir -p deps + cd deps + + # Insure required packages are installed for building. + sudo apt install build-essential gcc g++ make cmake autoconf libtool python3 python3-pip libsystemd-dev libspeechd-dev -y + + setup_angelscript + setup_bullet + setup_enet + setup_libgit2 + setup_libplist + setup_poco + setup_sdl + setup_nvgt + echo Success! + exit 0 +} + +if [[ "$1" == "ci" ]]; then + export IS_CI=1 +else + export IS_CI=0 +fi + +main diff --git a/build/build_macos.sh b/build/build_macos.sh index 0a008288..0a56aeb6 100644 --- a/build/build_macos.sh +++ b/build/build_macos.sh @@ -1,118 +1,118 @@ -#!/bin/zsh - -function setup_homebrew { - brew install autoconf automake libtool openssl libgit2 bullet upx libplist -} - -function setup_angelscript { - echo Installing Angelscript... - git clone --depth 1 https://github.com/codecat/angelscript-mirror||true - cd "angelscript-mirror/sdk/angelscript/projects/cmake" - mkdir -p build - cd build - cmake .. - cmake --build . - sudo cmake --install . - cd ../../../../../.. - echo Angelscript installed. -} - -function setup_enet { - echo Installing enet... - git clone --depth 1 https://github.com/lsalzman/enet||true - cd enet - autoreconf -vfi - ./configure - make -j$(nsysctl -n hw.ncpu) - sudo make install - cd .. - echo Enet installed. -} - -function setup_libgit2 { - curl -s -O -L https://github.com/libgit2/libgit2/archive/refs/tags/v1.8.1.tar.gz - tar -xzf v1.8.1.tar.gz - cd libgit2-1.8.1 - mkdir -p build - cd build - cmake .. -DBUILD_TESTS=OFF -DUSE_ICONV=OFF -DBUILD_CLI=OFF -DCMAKE_BUILD_TYPE=Release - cmake --build . - sudo cmake --install . - cd ../.. - rm v1.8.1.tar.gz -} - -function setup_poco { - curl -s -O https://pocoproject.org/releases/poco-1.13.3/poco-1.13.3-all.tar.gz - tar -xzf poco-1.13.3-all.tar.gz - cd poco-1.13.3-all - mkdir -p cmake_build - cd cmake_build - cmake .. -DENABLE_TESTS=OFF -DENABLE_SAMPLES=OFF -DCMAKE_BUILD_TYPE=MinSizeRel -DCMAKE_OSX_ARCHITECTURES="arm64;x86_64" -DENABLE_PAGECOMPILER=OFF -DENABLE_PAGECOMPILER_FILE2PAGE=OFF -DENABLE_ACTIVERECORD=OFF -DENABLE_ACTIVERECORD_COMPILER=OFF -DENABLE_MONGODB=OFF -DBUILD_SHARED_LIBS=OFF -DCMAKE_CXX_FLAGS=-DPOCO_UTIL_NO_XMLCONFIGURATION - cmake --build . - sudo cmake --install . - cd ../.. - rm poco-1.13.3-all.tar.gz -} - -function setup_sdl { - echo Installing SDL... - git clone https://github.com/libsdl-org/SDL||true - mkdir -p SDL/build - cd SDL/build - git checkout 4e09e58f62e95a66125dae9ddd3e302603819ffd - cmake -DCMAKE_BUILD_TYPE=MinSizeRel -DSDL_SHARED=OFF -DSDL_STATIC=ON -DSDL_TEST_LIBRARY=OFF -DCMAKE_OSX_ARCHITECTURES="arm64;x86_64" .. - cmake --build . --config MinSizeRel - sudo make install - cd ../.. - echo SDL installed. -} -function setup_nvgt { - echo Building NVGT... - - if [[ "$IS_CI" != 1 ]]; then - # Assumed to not be running on CI; NVGT should be cloned outside of deps first. - echo Not running on CI. - cd .. - - git clone --depth 1 https://github.com/samtupy/nvgt||true - cd nvgt - - else - echo Running on CI. - cd .. - fi - - echo Downloading macosdev... - curl -s -O https://nvgt.gg/macosdev.tar.gz - mkdir -p macosdev - cd macosdev - tar -xvf ../macosdev.tar.gz - cd .. - rm macosdev.tar.gz - scons -s - echo NVGT built. -} - -function main { - set -e - mkdir -p deps - cd deps - setup_homebrew - setup_angelscript - setup_enet - #setup_libgit2 - setup_poco - setup_sdl - setup_nvgt - echo Success! - exit 0 -} - -if [[ "$1" == "ci" ]]; then - export IS_CI=1 -else - export IS_CI=0 -fi - -main +#!/bin/zsh + +function setup_homebrew { + brew install autoconf automake libtool openssl libgit2 bullet upx libplist +} + +function setup_angelscript { + echo Installing Angelscript... + git clone --depth 1 https://github.com/codecat/angelscript-mirror||true + cd "angelscript-mirror/sdk/angelscript/projects/cmake" + mkdir -p build + cd build + cmake .. + cmake --build . + sudo cmake --install . + cd ../../../../../.. + echo Angelscript installed. +} + +function setup_enet { + echo Installing enet... + git clone --depth 1 https://github.com/lsalzman/enet||true + cd enet + autoreconf -vfi + ./configure + make -j$(nsysctl -n hw.ncpu) + sudo make install + cd .. + echo Enet installed. +} + +function setup_libgit2 { + curl -s -O -L https://github.com/libgit2/libgit2/archive/refs/tags/v1.8.1.tar.gz + tar -xzf v1.8.1.tar.gz + cd libgit2-1.8.1 + mkdir -p build + cd build + cmake .. -DBUILD_TESTS=OFF -DUSE_ICONV=OFF -DBUILD_CLI=OFF -DCMAKE_BUILD_TYPE=Release + cmake --build . + sudo cmake --install . + cd ../.. + rm v1.8.1.tar.gz +} + +function setup_poco { + curl -s -O https://pocoproject.org/releases/poco-1.13.3/poco-1.13.3-all.tar.gz + tar -xzf poco-1.13.3-all.tar.gz + cd poco-1.13.3-all + mkdir -p cmake_build + cd cmake_build + cmake .. -DENABLE_TESTS=OFF -DENABLE_SAMPLES=OFF -DCMAKE_BUILD_TYPE=MinSizeRel -DCMAKE_OSX_ARCHITECTURES="arm64;x86_64" -DENABLE_PAGECOMPILER=OFF -DENABLE_PAGECOMPILER_FILE2PAGE=OFF -DENABLE_ACTIVERECORD=OFF -DENABLE_ACTIVERECORD_COMPILER=OFF -DENABLE_MONGODB=OFF -DBUILD_SHARED_LIBS=OFF -DCMAKE_CXX_FLAGS=-DPOCO_UTIL_NO_XMLCONFIGURATION + cmake --build . + sudo cmake --install . + cd ../.. + rm poco-1.13.3-all.tar.gz +} + +function setup_sdl { + echo Installing SDL... + git clone https://github.com/libsdl-org/SDL||true + mkdir -p SDL/build + cd SDL/build + git checkout 4e09e58f62e95a66125dae9ddd3e302603819ffd + cmake -DCMAKE_BUILD_TYPE=MinSizeRel -DSDL_SHARED=OFF -DSDL_STATIC=ON -DSDL_TEST_LIBRARY=OFF -DCMAKE_OSX_ARCHITECTURES="arm64;x86_64" .. + cmake --build . --config MinSizeRel + sudo make install + cd ../.. + echo SDL installed. +} +function setup_nvgt { + echo Building NVGT... + + if [[ "$IS_CI" != 1 ]]; then + # Assumed to not be running on CI; NVGT should be cloned outside of deps first. + echo Not running on CI. + cd .. + + git clone --depth 1 https://github.com/samtupy/nvgt||true + cd nvgt + + else + echo Running on CI. + cd .. + fi + + echo Downloading macosdev... + curl -s -O https://nvgt.gg/macosdev.tar.gz + mkdir -p macosdev + cd macosdev + tar -xvf ../macosdev.tar.gz + cd .. + rm macosdev.tar.gz + scons -s + echo NVGT built. +} + +function main { + set -e + mkdir -p deps + cd deps + setup_homebrew + setup_angelscript + setup_enet + #setup_libgit2 + setup_poco + setup_sdl + setup_nvgt + echo Success! + exit 0 +} + +if [[ "$1" == "ci" ]]; then + export IS_CI=1 +else + export IS_CI=0 +fi + +main diff --git a/doc/src/advanced/Plugin Creation.md b/doc/src/advanced/Plugin Creation.md index c3e4ff71..4b37d1a9 100644 --- a/doc/src/advanced/Plugin Creation.md +++ b/doc/src/advanced/Plugin Creation.md @@ -1,187 +1,187 @@ -# Plugin Creation -Does NVGT not provide the function you need, and do you know a bit of c++? If so, perhaps NVGT plugins are exactly what you're looking for! - -This document will describe all there is to know about creating nvgt plugins, both dynamically and statically. - -## What is a plugin in the context of NVGT? -An NVGT plugin, in it's most basic form, is simply a module of code that is executed during the script loading part of the engine initialization process, one which can extend the functionality of NVGT by directly gaining access to and registering functions with it's internal Angelscript engine. - -Plugins are not just limited to functions, but classes, enums, funcdefs and anything else one could register normally using the Angelscript scripting library. - -## Types of plugin builds -A plugin can either be a shared library (.dll / .dylib / .so) that gets loaded when needed, or a static library (.lib / .a) that is linked directly into a custom build of NVGT. Both methods have different advantages and disadvantages. - -Dynamically loaded plugins, those built into a shared library, are easier to get working with NVGT because it's far easier to create such a plugin without at all altering NVGT's build process or adding things to it. You could use your own build system and your own environment, so long as the proper ABI is exposed to NVGT in the end and an up-to-date version of Angelscript is used within your plugin. However, a smart player may figure out how to replace your plugin dll with some sort of malicious copy, your dll plugin could be duplicated and reused in other projects, you'll have an extra dll file to release with your game distribution etc. - -Static plugins on the other hand, while a bit tougher to build, are certainly more rewarding in the end. From plugin code being packaged directly into your binary to a smaller distribution size because of no duplicated crt/other code in a dll to direct integration with NVGT's build system, there are several advantages that can be observed when choosing to create a static plugin. - -If one chooses to follow every step of the existing NVGT plugin creation process that is used internally by engine developers, you can set up your plugin such that it can easily be built either dynamically or statically depending on the end-user's preference. - -## The basic idea -In short, the idea here stems from a pretty simple base. The user creates a .cpp file that includes the nvgt_plugin.h header that can do any magic heavy lifting needed, then the user just defines an entry point using a macro declared in nvgt_plugin.h. This entry point receives a pointer to the asIScriptEngine instance used by NVGT, which the plugin developer can do anything they please with from registering custom functions to installing some sort of custom profiler. The entry point can return true or false to indicate to NVGT whether the plugin was able to successfully initialize. - -This plugin entry point always takes one argument, which is a structure of data passed to it by NVGT. The structure contains the Angelscript engine pointer as well as pointers to several other Angelscript functions that may be useful, and may be expanded with pointers to other useful interfaces from NVGT as well. One just simply needs to call a function provided by nvgt_plugin.h called prepare_plugin passing to it a pointer to the aforementioned structure before their own plugin initialization code begins to execute. - -To link a static plugin with the engine assuming the nvgt's build script knows about the static library file, one need only add a line such as static_plugin(\) to the nvgt_config.h file where \ should be replaced with the name of your plugin. - -## small example plugin -``` -#include -#include "../../src/nvgt_plugin.h" - -void do_test() { - MessageBoxA(0, "It works, this function is being called from within the context of an NVGT plugin!", "success", 0); -} - -plugin_main(nvgt_plugin_shared* shared) { - prepare_plugin(shared); - shared->script_engine->RegisterGlobalFunction("void do_test()", asFUNCTION(do_test), asCALL_CDECL); - return true; -} -``` - -### picking it apart -We shall forgo any general comments or teaching about the c++ language itself here, but instead will just focus on the bits of code that specifically involve the plugin interface. - -The first thing that you probably noticed was this include directive which includes "../../src/nvgt_plugin.h". Why there? While this will be described later, the gist is that NVGT's build setup already has some infrastructure set up to build plugins. NVGT's github repository has a plugin folder, and in there are folders for each plugin. This example is using such a structure. We will talk more in detail about this later, but for now it is enough to know that nvgt_plugin.h does not include anything else in nvgt's source tree, and can be safely copy pasted where ever you feel is best for your particular project (though we do recommend building plugins with NVGT's workspace). - -The next oddity here, why doesn't the plugin_main function declaration include a return type? This is because it is a macro defined in nvgt_plugin.h. It is required because the name of the entry point will internally change based on whether you are compiling your plugin statically or dynamically. If you are building your plugin as a shared library, the function that ends up exporting is called nvgt_plugin. However since one of course cannot link 2 static libraries with the same symbol names in each to a final executable, the entry point for a static plugin ends up being called nvgt_plugin_\ where \ is replaced with the value of the NVGT_PLUGIN_STATIC preprocessor define (set at plugin build time). In the future even dynamic libraries may possibly contain the plugin name in their entry point function signatures such that more than one plugin could be loaded from one dll file, but for now we instead recommend simply registering functions from multiple plugins in one common entry point if you really want to do that. - -Finally, remember to call prepare_plugin(shared) as the first thing in your plugin, and note that if your entry point does not return true, this indicates an error condition and your plugin is not loaded. - -## NVGT's plugin building infrastructure -As mentioned a couple of times above, NVGT's official repository already contains the infrastructure required to build plugins and integrate them with NVGT's existing build system, complete with the ability to exclude some of your more private plugins from being picked up by the repository. While it is not required that one use this setup and in fact one may not want to if they have a better workspace set up for themselves, we certainly recommend it especially if you are making a plugin that you may want to share with the NVGT community. - -### The plugin directory -In nvgt's main repository, the plugin directory contains all publicly available plugins. Either if you have downloaded NVGT's repository outside of version control (such as a public release artifact) or if you intend to contribute your plugin to the community by submitting a pull request, you can feel free to use this directory as well. - -Here, each directory is typically one plugin. It is not required that this be the case, other directories that are not plugins can also exist here, however any directory within the plugin folder that contains a file called _SConscript will automatically be considered as a plugin by the SConstruct script that builds NVGT. - -The source code in these plugins can be arranged any way you like, as it is the _SConscript file you provide that instructs the system how to build your plugin. - -An example _SConscript file for such a plugin might look like this: -``` -# Import the SCons environment we are using -Import("env") - -# Create the shared version of our plugin if the user has not disabled this feature. -if ARGUMENTS.get("no_shared_plugins", "0") == "0": - env.SharedLibrary("#release/lib/test_plugin", ["test.cpp"], libs = ["user32"]) - -# If we want to make a static version along side our shared one, we need to specifically rebuild the object file containing the plugin's entry point with a different name so that SCons can maintain a proper dependency graph. Note the NVGT_PLUGIN_STATIC define. -static = env.Object("test_plugin_static", "test.cpp", CPPDEFINES = [("NVGT_PLUGIN_STATIC", "test_plugin")]) -# now actually build the static library, reusing the same variable from above for fewer declarations. -static = env.StaticLibrary("#build/lib/test_plugin", [static]) - -# Tell NVGT's SConstruct script that the static version of this plugin needs symbols from the user32 library. -static = [static, "user32"] - -# What is being returned to NVGT's SConstruct in the end is a list of additional static library objects that should be linked. -Return("static") -``` - -Note that while the above example returns the user32 library back to NVGT's build script, it should be noted that most system libraries are already linked into nvgt's builds. The example exists to show how an extra static library would be passed to NVGT from a plugin if required, but this should only be done either as a reaction to a linker error or if you know for sure that your plugin requires a dependency that is not automatically linked to NVGT, examples in the git2, curl or sqlite3 plugins. - -### the user directory -NVGT's github repository also contains another root folder called user. This is a private scratchpad directory that exists so that a user can add plugins or any other code to NVGT that they do not want included in the repository. - -First, the repository's .gitignore file ignores everything in here accept for readme.md, meaning that you can do anything you like here with the peace of mind that you won't accidentally commit your private encryption plugin to the public repository when you try contributing a bugfix to the engine. - -Second, if a _SConscript file is present in this directory, NVGT's main build script will execute it, providing 2 environments to it via SCons exports. The nvgt_env environment is what is used to directly build NVGT, for example if you need any extra static libraries linked to nvgt.exe or the stubs, you'd add one by importing the nvgt_env variable and appending the library you want to link with to the environment's LIBS construction variable. - -Last but not least, if a file called nvgt_config.h is present in the user folder, this will also be loaded in place of the nvgt_config.h in the repo's src directory. - -You can do whatever you want within this user directory, choosing to either follow or ignore any conventions you wish. Below is an example of a working setup that employs the user directory, but keep in mind that you can set up your user directory any way you wish and don't necessarily have to follow the example exactly. - -#### user directory example -The following setup is used for Survive the Wild development. That game requires a couple of proprietary plugins to work, such as a private encryption layer. - -In this case, what was set up was a second github repository that exists within the user directory. It's not a good idea to make a github repository out of the root user folder itself because git will not appreciate this, but instead a folder should be created within the user directory that could contain a subrepository. We'll call it nvgt_user. - -The first step is to create some jumper scripts that allow the user folder to know about the nvgt_user repository contained inside it. - -user/nvgt_config.h: -``` -#include "nvgt_user/nvgt_config.h" -``` -and - -user/_SConscript: -``` -Import(["plugin_env", "nvgt_env"]) -SConscript("nvgt_user/_SConscript", exports=["plugin_env", "nvgt_env"]) -``` - -Now, user/nvgt_user/nvgt_config.h and user/nvgt_user/_SConscript will be loaded as they should be, respectively. - -In the nvgt_user folder itself we have _SConscript, nvgt_plugin.h, and some folders containing private plugins as well as an unimportant folder called setup we'll describe near the end of the example. - -nvgt_config.h contains the custom encryption routines / static plugin configuration that is used to build the version of NVGT used for Survive the Wild. - -The user/nvgt_user/_SConscript file looks something like this: -``` -Import("plugin_env", "nvgt_env") - -SConscript("plugname1/_SConscript", variant_dir = "#build/obj_plugin/plugname1", duplicate = 0, exports = ["plugin_env", "nvgt_env"]) -SConscript("plugname2/_SConscript", variant_dir = "#build/obj_plugin/plugname2", duplicate = 0, exports = ["plugin_env", "nvgt_env"]) -# nvgt_user/nvgt_config.h statically links with the git2 plugin, lets delay load that dll on windows so that users won't get errors if it's not found. -if nvgt_env["PLATFORM"] == "win32": - nvgt_env.Append(LINKFLAGS = ["/delayload:git2.dll"]) -``` -And finally an _SConscript file for nvgt_user/plugname\* might look something like this: -``` -Import(["plugin_env", "nvgt_env"]) - -static = plugin_env.StaticLibrary("#build/lib/plugname2", ["code.cpp"], CPPDEFINES = [("NVGT_PLUGIN_STATIC", "plugname2")], LIBS = ["somelib"]) -nvgt_env.Append(LIBS = [static, "somelib"]) -``` -As you can see, the decision regarding the custom plugins used for Survive the Wild is to simply not support building them as shared libraries, as that will never be needed from the context of that game. - -The only other item in the private nvgt_user repository used for Survive the Wild is a folder called setup, and it's nothing but a tiny all be it useful convenience mechanism. The setup folder simply contains copies of the user/_SConscript and user/nvgt_config.h files that were described at the beginning of this example, meaning that if nvgt's repository ever needs to be cloned from scratch to continue STW development (such as on a new workstation), the following commands can be executed without worrying about creating the extra files that are outside of the nvgt_user repository in the root of the user folder: - -```bash -git clone https://github.com/samtupy/nvgt -cd nvgt/user -git clone https://github.com/samtupy/nvgt_user -cp nvgt_user/setup/* . -``` - -And with that, nvgt is ready for the private development of STW all with the custom plugins still being safely in version control! So long as the cwd is outside of the nvgt_user directory the root nvgt repository is effected, and once inside the nvgt_user directory, that much smaller repository is the only thing that will be touched by git commands. - -Remember, you should use this example as a possible idea as to how you could potentially make use of NVGT's user directory, not as a guide you must follow exactly. Feel free to create your own entirely different workspace in here or if you want, forgo use of the user directory entirely. - -### Angelscript addon shims -When creating a shared/dynamic plugin made up of more than 1 cpp file, you must `#define NVGT_PLUGIN_INCLUDE` and then `#include "nvgt_plugin.h"` before you `#include `. This is to make sure that any additional units you build will use the manually imported Angelscript symbols that were passed to the plugin from NVGT, a multiple defined symbol error might appear otherwise or if your code compiles, 2 different versions of the Angelscript functions could be used which would be very unsafe. To make it easier to include Angelscript addons into your plugins, little shims are provided for the common addons in the nvgt repository in the ASAddon/plugin directory. You can simply compile ASAddon/plugin/scriptarray.cpp, for example, and the scriptarray plugin will safely be included into your plugin. nvgt_plugin.h should still be included before scriptarray.h in any of your plugin source files, however. - -When compiling a static plugin, you do not need to bother linking with these addon shims, because in that case your plugin's static library will be linked with NVGT when NVGT is next recompiled, and NVGT already contains working addons. - -## plugin dll loading -A common sanario is that you may wish to make a plugin that then loads another shared library. This happens already in nvgt, particularly with the git2 plugin. The plugin itself is called git2nvgt, but it loads git2.dll. This calls for some consideration. - -NVGT applications generally put their libraries in a lib folder to reduce clutter in the applications main directory. This isn't required, and in fact if you want to move all shared objects out of the lib folder and put them along side your game executable, you actually tend to avoid the small issue mentioned here. The problem is that sometimes, we need to explicitly tell the operating system about the lib directory at runtime (right now true on windows) in order for it to find the shared libraries there. - -For shared/dynamic plugins, this isn't really an issue. NVGT has launched, informed the system of the lib directory, and loaded your plugin in that order meaning that any dll your plugin imports can already be located and imported just fine. - -With static plugins, on the other hand, we must work around the fact that the operating system will generally try loading all libraries that the program uses before any of that program's code executes, and will show the user an error message and abort the program if it can't find one. Returning to the git2 example from earlier, if you were to ship this plugin with your game, a file would exist called lib/git2.dll on windows. When the static library git2nvgt.lib is created, it will add git2.dll to it's import table but with no knowledge of the lib folder at that point. When the custom build of NVGT which includes this plugin tries to run, an error message will appear because git2.dll can't be found, on account of NVGT never being able to tell the system about the lib directory before the operating system evaluates nvgt's import table and tries to load the libraries found within. - -The solution, at least on windows, is delayed dll loading. It is demonstrated above in the user directory example, but it's easy to gloss over considering it's level of importants. If you add the linkflag `/delayload:libname.dll` to your static plugin's build script, now NVGT's code is allowed to execute meaning it can tell the system about the lib directory, and then the dll will load the first time a function is called from it. On MacOS/clang, there is the linkflag -weak_library which does something similar, used like `-weak_library /path/to/library.dylib`. - -Delay loading does has the disadvantage that the app tends to crash if the dll is not present when it is needed rather than giving the user a nice error message, but you can work around that by manually loading the dll with the LoadLibrary function on windows / similar facilities on other platforms then immediately unloading it just to see if the system will be able to find it, and you can choose to show an error message in that case if you wish. - -## cross platform considerations -If you are only building plugins for projects that are intended to run on one platform, this section may be safely skipped. However if your game runs on multiple platforms and if you intend to introduce custom plugins, you probably don't want to miss this. - -There are a couple of things that should be considered when creating plugins intended to run on all platforms, but only one really big one. In short, it is important that a cross platform plugin's registered Angelscript interface looks exactly the same on all platforms, even if your plugin doesn't support some functionality on one platform. For example if your plugin has functions foo and bar but the bar function only works on windows, it is important to register an empty bar function for any non-windows builds of your plugin rather than excluding the bar function from the angelscript registration of such a plugin build entirely. This is especially true if you intend to, for example, cross compile your application with the windows version of NVGT to run on a linux platform. - -The reasoning is that Angelscript may sometimes store indexes or offsets to internal functions or engine registrations in compiled bytecode rather than the names of them. This makes sense and allows for much smaller/faster compiled programs, but what it does mean is that NVGT's registered interface must appear exactly the same both when compiling and when running a script. Maybe your plugin with foo and bar functions get registered into the engine as functions 500 and 501, then maybe the user loads a plugin after that with boo and bas functions that get registered as functions 502 and 503. Say the user makes a call to the bas function at index 503. Well, if the foo bar plugin doesn't include a bar function on linux builds of it, now we can compile the script on windows and observe that the function call to bas at index 503 is successful. But if I run that compiled code on linux, since the bar function is not registered (as it only works on windows), the bas function is now at index 502 instead of 503 where the bytecode is instructing the program to call a function. Oh no, program panic, invalid bytecode! The solution is to instead register an empty version of the bar function on non-windows builds of such a plugin that does nothing. - -## Angelscript registration -Hopefully this document has helped you gather the knowledge required to start making some great plugins! The last pressing question we'll end with is "how does one register things with NVGT's Angelscript engine?" The angelscript engine is a variable in the nvgt_plugin_shared structure passed to your plugins entry point, it's called script_engine. - -The best reference for how to register things with Angelscript is the Angelscript documentation itself, and as such, the following are just a couple of useful links from there which should help get you on the right track: -* [registering the application interface](https://www.angelcode.com/angelscript/sdk/docs/manual/doc_register_api_topic.html) -* [registering a function](https://www.angelcode.com/angelscript/sdk/docs/manual/doc_register_func.html) -* [registering global properties](https://www.angelcode.com/angelscript/sdk/docs/manual/doc_register_prop.html) -* [registering an object type](https://www.angelcode.com/angelscript/sdk/docs/manual/doc_register_type.html) - -Good luck creating NVGT plugins, and feel free to share some of them to the community if you deem them worthy! +# Plugin Creation +Does NVGT not provide the function you need, and do you know a bit of c++? If so, perhaps NVGT plugins are exactly what you're looking for! + +This document will describe all there is to know about creating nvgt plugins, both dynamically and statically. + +## What is a plugin in the context of NVGT? +An NVGT plugin, in it's most basic form, is simply a module of code that is executed during the script loading part of the engine initialization process, one which can extend the functionality of NVGT by directly gaining access to and registering functions with it's internal Angelscript engine. + +Plugins are not just limited to functions, but classes, enums, funcdefs and anything else one could register normally using the Angelscript scripting library. + +## Types of plugin builds +A plugin can either be a shared library (.dll / .dylib / .so) that gets loaded when needed, or a static library (.lib / .a) that is linked directly into a custom build of NVGT. Both methods have different advantages and disadvantages. + +Dynamically loaded plugins, those built into a shared library, are easier to get working with NVGT because it's far easier to create such a plugin without at all altering NVGT's build process or adding things to it. You could use your own build system and your own environment, so long as the proper ABI is exposed to NVGT in the end and an up-to-date version of Angelscript is used within your plugin. However, a smart player may figure out how to replace your plugin dll with some sort of malicious copy, your dll plugin could be duplicated and reused in other projects, you'll have an extra dll file to release with your game distribution etc. + +Static plugins on the other hand, while a bit tougher to build, are certainly more rewarding in the end. From plugin code being packaged directly into your binary to a smaller distribution size because of no duplicated crt/other code in a dll to direct integration with NVGT's build system, there are several advantages that can be observed when choosing to create a static plugin. + +If one chooses to follow every step of the existing NVGT plugin creation process that is used internally by engine developers, you can set up your plugin such that it can easily be built either dynamically or statically depending on the end-user's preference. + +## The basic idea +In short, the idea here stems from a pretty simple base. The user creates a .cpp file that includes the nvgt_plugin.h header that can do any magic heavy lifting needed, then the user just defines an entry point using a macro declared in nvgt_plugin.h. This entry point receives a pointer to the asIScriptEngine instance used by NVGT, which the plugin developer can do anything they please with from registering custom functions to installing some sort of custom profiler. The entry point can return true or false to indicate to NVGT whether the plugin was able to successfully initialize. + +This plugin entry point always takes one argument, which is a structure of data passed to it by NVGT. The structure contains the Angelscript engine pointer as well as pointers to several other Angelscript functions that may be useful, and may be expanded with pointers to other useful interfaces from NVGT as well. One just simply needs to call a function provided by nvgt_plugin.h called prepare_plugin passing to it a pointer to the aforementioned structure before their own plugin initialization code begins to execute. + +To link a static plugin with the engine assuming the nvgt's build script knows about the static library file, one need only add a line such as static_plugin(\) to the nvgt_config.h file where \ should be replaced with the name of your plugin. + +## small example plugin +``` +#include +#include "../../src/nvgt_plugin.h" + +void do_test() { + MessageBoxA(0, "It works, this function is being called from within the context of an NVGT plugin!", "success", 0); +} + +plugin_main(nvgt_plugin_shared* shared) { + prepare_plugin(shared); + shared->script_engine->RegisterGlobalFunction("void do_test()", asFUNCTION(do_test), asCALL_CDECL); + return true; +} +``` + +### picking it apart +We shall forgo any general comments or teaching about the c++ language itself here, but instead will just focus on the bits of code that specifically involve the plugin interface. + +The first thing that you probably noticed was this include directive which includes "../../src/nvgt_plugin.h". Why there? While this will be described later, the gist is that NVGT's build setup already has some infrastructure set up to build plugins. NVGT's github repository has a plugin folder, and in there are folders for each plugin. This example is using such a structure. We will talk more in detail about this later, but for now it is enough to know that nvgt_plugin.h does not include anything else in nvgt's source tree, and can be safely copy pasted where ever you feel is best for your particular project (though we do recommend building plugins with NVGT's workspace). + +The next oddity here, why doesn't the plugin_main function declaration include a return type? This is because it is a macro defined in nvgt_plugin.h. It is required because the name of the entry point will internally change based on whether you are compiling your plugin statically or dynamically. If you are building your plugin as a shared library, the function that ends up exporting is called nvgt_plugin. However since one of course cannot link 2 static libraries with the same symbol names in each to a final executable, the entry point for a static plugin ends up being called nvgt_plugin_\ where \ is replaced with the value of the NVGT_PLUGIN_STATIC preprocessor define (set at plugin build time). In the future even dynamic libraries may possibly contain the plugin name in their entry point function signatures such that more than one plugin could be loaded from one dll file, but for now we instead recommend simply registering functions from multiple plugins in one common entry point if you really want to do that. + +Finally, remember to call prepare_plugin(shared) as the first thing in your plugin, and note that if your entry point does not return true, this indicates an error condition and your plugin is not loaded. + +## NVGT's plugin building infrastructure +As mentioned a couple of times above, NVGT's official repository already contains the infrastructure required to build plugins and integrate them with NVGT's existing build system, complete with the ability to exclude some of your more private plugins from being picked up by the repository. While it is not required that one use this setup and in fact one may not want to if they have a better workspace set up for themselves, we certainly recommend it especially if you are making a plugin that you may want to share with the NVGT community. + +### The plugin directory +In nvgt's main repository, the plugin directory contains all publicly available plugins. Either if you have downloaded NVGT's repository outside of version control (such as a public release artifact) or if you intend to contribute your plugin to the community by submitting a pull request, you can feel free to use this directory as well. + +Here, each directory is typically one plugin. It is not required that this be the case, other directories that are not plugins can also exist here, however any directory within the plugin folder that contains a file called _SConscript will automatically be considered as a plugin by the SConstruct script that builds NVGT. + +The source code in these plugins can be arranged any way you like, as it is the _SConscript file you provide that instructs the system how to build your plugin. + +An example _SConscript file for such a plugin might look like this: +``` +# Import the SCons environment we are using +Import("env") + +# Create the shared version of our plugin if the user has not disabled this feature. +if ARGUMENTS.get("no_shared_plugins", "0") == "0": + env.SharedLibrary("#release/lib/test_plugin", ["test.cpp"], libs = ["user32"]) + +# If we want to make a static version along side our shared one, we need to specifically rebuild the object file containing the plugin's entry point with a different name so that SCons can maintain a proper dependency graph. Note the NVGT_PLUGIN_STATIC define. +static = env.Object("test_plugin_static", "test.cpp", CPPDEFINES = [("NVGT_PLUGIN_STATIC", "test_plugin")]) +# now actually build the static library, reusing the same variable from above for fewer declarations. +static = env.StaticLibrary("#build/lib/test_plugin", [static]) + +# Tell NVGT's SConstruct script that the static version of this plugin needs symbols from the user32 library. +static = [static, "user32"] + +# What is being returned to NVGT's SConstruct in the end is a list of additional static library objects that should be linked. +Return("static") +``` + +Note that while the above example returns the user32 library back to NVGT's build script, it should be noted that most system libraries are already linked into nvgt's builds. The example exists to show how an extra static library would be passed to NVGT from a plugin if required, but this should only be done either as a reaction to a linker error or if you know for sure that your plugin requires a dependency that is not automatically linked to NVGT, examples in the git2, curl or sqlite3 plugins. + +### the user directory +NVGT's github repository also contains another root folder called user. This is a private scratchpad directory that exists so that a user can add plugins or any other code to NVGT that they do not want included in the repository. + +First, the repository's .gitignore file ignores everything in here accept for readme.md, meaning that you can do anything you like here with the peace of mind that you won't accidentally commit your private encryption plugin to the public repository when you try contributing a bugfix to the engine. + +Second, if a _SConscript file is present in this directory, NVGT's main build script will execute it, providing 2 environments to it via SCons exports. The nvgt_env environment is what is used to directly build NVGT, for example if you need any extra static libraries linked to nvgt.exe or the stubs, you'd add one by importing the nvgt_env variable and appending the library you want to link with to the environment's LIBS construction variable. + +Last but not least, if a file called nvgt_config.h is present in the user folder, this will also be loaded in place of the nvgt_config.h in the repo's src directory. + +You can do whatever you want within this user directory, choosing to either follow or ignore any conventions you wish. Below is an example of a working setup that employs the user directory, but keep in mind that you can set up your user directory any way you wish and don't necessarily have to follow the example exactly. + +#### user directory example +The following setup is used for Survive the Wild development. That game requires a couple of proprietary plugins to work, such as a private encryption layer. + +In this case, what was set up was a second github repository that exists within the user directory. It's not a good idea to make a github repository out of the root user folder itself because git will not appreciate this, but instead a folder should be created within the user directory that could contain a subrepository. We'll call it nvgt_user. + +The first step is to create some jumper scripts that allow the user folder to know about the nvgt_user repository contained inside it. + +user/nvgt_config.h: +``` +#include "nvgt_user/nvgt_config.h" +``` +and + +user/_SConscript: +``` +Import(["plugin_env", "nvgt_env"]) +SConscript("nvgt_user/_SConscript", exports=["plugin_env", "nvgt_env"]) +``` + +Now, user/nvgt_user/nvgt_config.h and user/nvgt_user/_SConscript will be loaded as they should be, respectively. + +In the nvgt_user folder itself we have _SConscript, nvgt_plugin.h, and some folders containing private plugins as well as an unimportant folder called setup we'll describe near the end of the example. + +nvgt_config.h contains the custom encryption routines / static plugin configuration that is used to build the version of NVGT used for Survive the Wild. + +The user/nvgt_user/_SConscript file looks something like this: +``` +Import("plugin_env", "nvgt_env") + +SConscript("plugname1/_SConscript", variant_dir = "#build/obj_plugin/plugname1", duplicate = 0, exports = ["plugin_env", "nvgt_env"]) +SConscript("plugname2/_SConscript", variant_dir = "#build/obj_plugin/plugname2", duplicate = 0, exports = ["plugin_env", "nvgt_env"]) +# nvgt_user/nvgt_config.h statically links with the git2 plugin, lets delay load that dll on windows so that users won't get errors if it's not found. +if nvgt_env["PLATFORM"] == "win32": + nvgt_env.Append(LINKFLAGS = ["/delayload:git2.dll"]) +``` +And finally an _SConscript file for nvgt_user/plugname\* might look something like this: +``` +Import(["plugin_env", "nvgt_env"]) + +static = plugin_env.StaticLibrary("#build/lib/plugname2", ["code.cpp"], CPPDEFINES = [("NVGT_PLUGIN_STATIC", "plugname2")], LIBS = ["somelib"]) +nvgt_env.Append(LIBS = [static, "somelib"]) +``` +As you can see, the decision regarding the custom plugins used for Survive the Wild is to simply not support building them as shared libraries, as that will never be needed from the context of that game. + +The only other item in the private nvgt_user repository used for Survive the Wild is a folder called setup, and it's nothing but a tiny all be it useful convenience mechanism. The setup folder simply contains copies of the user/_SConscript and user/nvgt_config.h files that were described at the beginning of this example, meaning that if nvgt's repository ever needs to be cloned from scratch to continue STW development (such as on a new workstation), the following commands can be executed without worrying about creating the extra files that are outside of the nvgt_user repository in the root of the user folder: + +```bash +git clone https://github.com/samtupy/nvgt +cd nvgt/user +git clone https://github.com/samtupy/nvgt_user +cp nvgt_user/setup/* . +``` + +And with that, nvgt is ready for the private development of STW all with the custom plugins still being safely in version control! So long as the cwd is outside of the nvgt_user directory the root nvgt repository is effected, and once inside the nvgt_user directory, that much smaller repository is the only thing that will be touched by git commands. + +Remember, you should use this example as a possible idea as to how you could potentially make use of NVGT's user directory, not as a guide you must follow exactly. Feel free to create your own entirely different workspace in here or if you want, forgo use of the user directory entirely. + +### Angelscript addon shims +When creating a shared/dynamic plugin made up of more than 1 cpp file, you must `#define NVGT_PLUGIN_INCLUDE` and then `#include "nvgt_plugin.h"` before you `#include `. This is to make sure that any additional units you build will use the manually imported Angelscript symbols that were passed to the plugin from NVGT, a multiple defined symbol error might appear otherwise or if your code compiles, 2 different versions of the Angelscript functions could be used which would be very unsafe. To make it easier to include Angelscript addons into your plugins, little shims are provided for the common addons in the nvgt repository in the ASAddon/plugin directory. You can simply compile ASAddon/plugin/scriptarray.cpp, for example, and the scriptarray plugin will safely be included into your plugin. nvgt_plugin.h should still be included before scriptarray.h in any of your plugin source files, however. + +When compiling a static plugin, you do not need to bother linking with these addon shims, because in that case your plugin's static library will be linked with NVGT when NVGT is next recompiled, and NVGT already contains working addons. + +## plugin dll loading +A common sanario is that you may wish to make a plugin that then loads another shared library. This happens already in nvgt, particularly with the git2 plugin. The plugin itself is called git2nvgt, but it loads git2.dll. This calls for some consideration. + +NVGT applications generally put their libraries in a lib folder to reduce clutter in the applications main directory. This isn't required, and in fact if you want to move all shared objects out of the lib folder and put them along side your game executable, you actually tend to avoid the small issue mentioned here. The problem is that sometimes, we need to explicitly tell the operating system about the lib directory at runtime (right now true on windows) in order for it to find the shared libraries there. + +For shared/dynamic plugins, this isn't really an issue. NVGT has launched, informed the system of the lib directory, and loaded your plugin in that order meaning that any dll your plugin imports can already be located and imported just fine. + +With static plugins, on the other hand, we must work around the fact that the operating system will generally try loading all libraries that the program uses before any of that program's code executes, and will show the user an error message and abort the program if it can't find one. Returning to the git2 example from earlier, if you were to ship this plugin with your game, a file would exist called lib/git2.dll on windows. When the static library git2nvgt.lib is created, it will add git2.dll to it's import table but with no knowledge of the lib folder at that point. When the custom build of NVGT which includes this plugin tries to run, an error message will appear because git2.dll can't be found, on account of NVGT never being able to tell the system about the lib directory before the operating system evaluates nvgt's import table and tries to load the libraries found within. + +The solution, at least on windows, is delayed dll loading. It is demonstrated above in the user directory example, but it's easy to gloss over considering it's level of importants. If you add the linkflag `/delayload:libname.dll` to your static plugin's build script, now NVGT's code is allowed to execute meaning it can tell the system about the lib directory, and then the dll will load the first time a function is called from it. On MacOS/clang, there is the linkflag -weak_library which does something similar, used like `-weak_library /path/to/library.dylib`. + +Delay loading does has the disadvantage that the app tends to crash if the dll is not present when it is needed rather than giving the user a nice error message, but you can work around that by manually loading the dll with the LoadLibrary function on windows / similar facilities on other platforms then immediately unloading it just to see if the system will be able to find it, and you can choose to show an error message in that case if you wish. + +## cross platform considerations +If you are only building plugins for projects that are intended to run on one platform, this section may be safely skipped. However if your game runs on multiple platforms and if you intend to introduce custom plugins, you probably don't want to miss this. + +There are a couple of things that should be considered when creating plugins intended to run on all platforms, but only one really big one. In short, it is important that a cross platform plugin's registered Angelscript interface looks exactly the same on all platforms, even if your plugin doesn't support some functionality on one platform. For example if your plugin has functions foo and bar but the bar function only works on windows, it is important to register an empty bar function for any non-windows builds of your plugin rather than excluding the bar function from the angelscript registration of such a plugin build entirely. This is especially true if you intend to, for example, cross compile your application with the windows version of NVGT to run on a linux platform. + +The reasoning is that Angelscript may sometimes store indexes or offsets to internal functions or engine registrations in compiled bytecode rather than the names of them. This makes sense and allows for much smaller/faster compiled programs, but what it does mean is that NVGT's registered interface must appear exactly the same both when compiling and when running a script. Maybe your plugin with foo and bar functions get registered into the engine as functions 500 and 501, then maybe the user loads a plugin after that with boo and bas functions that get registered as functions 502 and 503. Say the user makes a call to the bas function at index 503. Well, if the foo bar plugin doesn't include a bar function on linux builds of it, now we can compile the script on windows and observe that the function call to bas at index 503 is successful. But if I run that compiled code on linux, since the bar function is not registered (as it only works on windows), the bas function is now at index 502 instead of 503 where the bytecode is instructing the program to call a function. Oh no, program panic, invalid bytecode! The solution is to instead register an empty version of the bar function on non-windows builds of such a plugin that does nothing. + +## Angelscript registration +Hopefully this document has helped you gather the knowledge required to start making some great plugins! The last pressing question we'll end with is "how does one register things with NVGT's Angelscript engine?" The angelscript engine is a variable in the nvgt_plugin_shared structure passed to your plugins entry point, it's called script_engine. + +The best reference for how to register things with Angelscript is the Angelscript documentation itself, and as such, the following are just a couple of useful links from there which should help get you on the right track: +* [registering the application interface](https://www.angelcode.com/angelscript/sdk/docs/manual/doc_register_api_topic.html) +* [registering a function](https://www.angelcode.com/angelscript/sdk/docs/manual/doc_register_func.html) +* [registering global properties](https://www.angelcode.com/angelscript/sdk/docs/manual/doc_register_prop.html) +* [registering an object type](https://www.angelcode.com/angelscript/sdk/docs/manual/doc_register_type.html) + +Good luck creating NVGT plugins, and feel free to share some of them to the community if you deem them worthy! diff --git a/jni/src/main/java/org/libsdl/app/SDLSurface.java b/jni/src/main/java/org/libsdl/app/SDLSurface.java index 283e3a5a..0b6e4c59 100644 --- a/jni/src/main/java/org/libsdl/app/SDLSurface.java +++ b/jni/src/main/java/org/libsdl/app/SDLSurface.java @@ -1,453 +1,453 @@ -package org.libsdl.app; - - -import android.content.Context; -import android.content.pm.ActivityInfo; -import android.graphics.Insets; -import android.hardware.Sensor; -import android.hardware.SensorEvent; -import android.hardware.SensorEventListener; -import android.hardware.SensorManager; -import android.os.Build; -import android.util.DisplayMetrics; -import android.util.Log; -import android.view.Display; -import android.view.InputDevice; -import android.view.KeyEvent; -import android.view.MotionEvent; -import android.view.accessibility.AccessibilityManager; -import android.view.Surface; -import android.view.SurfaceHolder; -import android.view.SurfaceView; -import android.view.View; -import android.view.WindowInsets; -import android.view.WindowManager; - - -/** - SDLSurface. This is what we draw on, so we need to know when it's created - in order to do anything useful. - - Because of this, that's where we set up the SDL thread -*/ -public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback, - View.OnApplyWindowInsetsListener, View.OnKeyListener, View.OnHoverListener, View.OnTouchListener, SensorEventListener { - - // Sensors - protected SensorManager mSensorManager; - protected Display mDisplay; - - // Keep track of the surface size to normalize touch events - protected float mWidth, mHeight; - - // Is SurfaceView ready for rendering - public boolean mIsSurfaceReady; - - // Accessibility manager - protected AccessibilityManager mAccessibilityManager; - - // Startup - public SDLSurface(Context context) { - super(context); - getHolder().addCallback(this); - - setFocusable(true); - setFocusableInTouchMode(true); - requestFocus(); - setOnApplyWindowInsetsListener(this); - setOnKeyListener(this); - setOnTouchListener(this); - setOnHoverListener(this); - - mDisplay = ((WindowManager)context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); - mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE); - mAccessibilityManager = (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE); - - setOnGenericMotionListener(SDLActivity.getMotionListener()); - - // Some arbitrary defaults to avoid a potential division by zero - mWidth = 1.0f; - mHeight = 1.0f; - - mIsSurfaceReady = false; - } - - public void handlePause() { - enableSensor(Sensor.TYPE_ACCELEROMETER, false); - } - - public void handleResume() { - setFocusable(true); - setFocusableInTouchMode(true); - requestFocus(); - setOnApplyWindowInsetsListener(this); - setOnKeyListener(this); - setOnTouchListener(this); - enableSensor(Sensor.TYPE_ACCELEROMETER, true); - } - - public Surface getNativeSurface() { - return getHolder().getSurface(); - } - - // Called when we have a valid drawing surface - @Override - public void surfaceCreated(SurfaceHolder holder) { - Log.v("SDL", "surfaceCreated()"); - SDLActivity.onNativeSurfaceCreated(); - } - - // Called when we lose the surface - @Override - public void surfaceDestroyed(SurfaceHolder holder) { - Log.v("SDL", "surfaceDestroyed()"); - - // Transition to pause, if needed - SDLActivity.mNextNativeState = SDLActivity.NativeState.PAUSED; - SDLActivity.handleNativeState(); - - mIsSurfaceReady = false; - SDLActivity.onNativeSurfaceDestroyed(); - } - - // Called when the surface is resized - @Override - public void surfaceChanged(SurfaceHolder holder, - int format, int width, int height) { - Log.v("SDL", "surfaceChanged()"); - - if (SDLActivity.mSingleton == null) { - return; - } - - mWidth = width; - mHeight = height; - int nDeviceWidth = width; - int nDeviceHeight = height; - float density = 1.0f; - try - { - if (Build.VERSION.SDK_INT >= 17 /* Android 4.2 (JELLY_BEAN_MR1) */) { - DisplayMetrics realMetrics = new DisplayMetrics(); - mDisplay.getRealMetrics( realMetrics ); - nDeviceWidth = realMetrics.widthPixels; - nDeviceHeight = realMetrics.heightPixels; - // Use densityDpi instead of density to more closely match what the UI scale is - density = (float)realMetrics.densityDpi / 160.0f; - } - } catch(Exception ignored) { - } - - synchronized(SDLActivity.getContext()) { - // In case we're waiting on a size change after going fullscreen, send a notification. - SDLActivity.getContext().notifyAll(); - } - - Log.v("SDL", "Window size: " + width + "x" + height); - Log.v("SDL", "Device size: " + nDeviceWidth + "x" + nDeviceHeight); - SDLActivity.nativeSetScreenResolution(width, height, nDeviceWidth, nDeviceHeight, density, mDisplay.getRefreshRate()); - SDLActivity.onNativeResize(); - - // Prevent a screen distortion glitch, - // for instance when the device is in Landscape and a Portrait App is resumed. - boolean skip = false; - int requestedOrientation = SDLActivity.mSingleton.getRequestedOrientation(); - - if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT) { - if (mWidth > mHeight) { - skip = true; - } - } else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE) { - if (mWidth < mHeight) { - skip = true; - } - } - - // Special Patch for Square Resolution: Black Berry Passport - if (skip) { - double min = Math.min(mWidth, mHeight); - double max = Math.max(mWidth, mHeight); - - if (max / min < 1.20) { - Log.v("SDL", "Don't skip on such aspect-ratio. Could be a square resolution."); - skip = false; - } - } - - // Don't skip if we might be multi-window or have popup dialogs - if (skip) { - if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) { - skip = false; - } - } - - if (skip) { - Log.v("SDL", "Skip .. Surface is not ready."); - mIsSurfaceReady = false; - return; - } - - /* If the surface has been previously destroyed by onNativeSurfaceDestroyed, recreate it here */ - SDLActivity.onNativeSurfaceChanged(); - - /* Surface is ready */ - mIsSurfaceReady = true; - - SDLActivity.mNextNativeState = SDLActivity.NativeState.RESUMED; - SDLActivity.handleNativeState(); - } - - // Window inset - @Override - public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) { - if (Build.VERSION.SDK_INT >= 30 /* Android 11 (R) */) { - Insets combined = insets.getInsets(WindowInsets.Type.systemBars() | - WindowInsets.Type.systemGestures() | - WindowInsets.Type.mandatorySystemGestures() | - WindowInsets.Type.tappableElement() | - WindowInsets.Type.displayCutout()); - - SDLActivity.onNativeInsetsChanged(combined.left, combined.right, combined.top, combined.bottom); - } - - // Pass these to any child views in case they need them - return insets; - } - - // Key events - @Override - public boolean onKey(View v, int keyCode, KeyEvent event) { - return SDLActivity.handleKeyEvent(v, keyCode, event, null); - } - - private float getNormalizedX(float x) - { - if (mWidth <= 1) { - return 0.5f; - } else { - return (x / (mWidth - 1)); - } - } - - private float getNormalizedY(float y) - { - if (mHeight <= 1) { - return 0.5f; - } else { - return (y / (mHeight - 1)); - } - } - - // Touch events - @Override - public boolean onHover(View v, MotionEvent event) { - if (mAccessibilityManager.isTouchExplorationEnabled()) { - return onTouch(v, event); - } else { - return super.onHoverEvent(event); - } - } - @Override - public boolean onTouch(View v, MotionEvent event) { - /* Ref: http://developer.android.com/training/gestures/multi.html */ - int touchDevId = event.getDeviceId(); - final int pointerCount = event.getPointerCount(); - int action = event.getActionMasked(); - int pointerFingerId; - int i = -1; - float x,y,p; - - // Convert hover events to touch events if we've received them. Should we only do this if accessibility manager? - if (action == MotionEvent.ACTION_HOVER_ENTER) action = MotionEvent.ACTION_DOWN; - else if (action == MotionEvent.ACTION_HOVER_MOVE) action = MotionEvent.ACTION_MOVE; - else if (action == MotionEvent.ACTION_HOVER_EXIT) action = MotionEvent.ACTION_UP; - - // 12290 = Samsung DeX mode desktop mouse - // 12290 = 0x3002 = 0x2002 | 0x1002 = SOURCE_MOUSE | SOURCE_TOUCHSCREEN - // 0x2 = SOURCE_CLASS_POINTER - if (event.getSource() == InputDevice.SOURCE_MOUSE || event.getSource() == (InputDevice.SOURCE_MOUSE | InputDevice.SOURCE_TOUCHSCREEN)) { - int mouseButton = 1; - try { - Object object = event.getClass().getMethod("getButtonState").invoke(event); - if (object != null) { - mouseButton = (Integer) object; - } - } catch(Exception ignored) { - } - - // We need to check if we're in relative mouse mode and get the axis offset rather than the x/y values - // if we are. We'll leverage our existing mouse motion listener - SDLGenericMotionListener_API12 motionListener = SDLActivity.getMotionListener(); - x = motionListener.getEventX(event); - y = motionListener.getEventY(event); - - SDLActivity.onNativeMouse(mouseButton, action, x, y, motionListener.inRelativeMode()); - } else { - switch(action) { - case MotionEvent.ACTION_MOVE: - for (i = 0; i < pointerCount; i++) { - pointerFingerId = event.getPointerId(i); - x = getNormalizedX(event.getX(i)); - y = getNormalizedY(event.getY(i)); - p = event.getPressure(i); - if (p > 1.0f) { - // may be larger than 1.0f on some devices - // see the documentation of getPressure(i) - p = 1.0f; - } - SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p); - } - break; - - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_DOWN: - // Primary pointer up/down, the index is always zero - i = 0; - /* fallthrough */ - case MotionEvent.ACTION_POINTER_UP: - case MotionEvent.ACTION_POINTER_DOWN: - // Non primary pointer up/down - if (i == -1) { - i = event.getActionIndex(); - } - - pointerFingerId = event.getPointerId(i); - x = getNormalizedX(event.getX(i)); - y = getNormalizedY(event.getY(i)); - p = event.getPressure(i); - if (p > 1.0f) { - // may be larger than 1.0f on some devices - // see the documentation of getPressure(i) - p = 1.0f; - } - SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p); - break; - - case MotionEvent.ACTION_CANCEL: - for (i = 0; i < pointerCount; i++) { - pointerFingerId = event.getPointerId(i); - x = getNormalizedX(event.getX(i)); - y = getNormalizedY(event.getY(i)); - p = event.getPressure(i); - if (p > 1.0f) { - // may be larger than 1.0f on some devices - // see the documentation of getPressure(i) - p = 1.0f; - } - SDLActivity.onNativeTouch(touchDevId, pointerFingerId, MotionEvent.ACTION_UP, x, y, p); - } - break; - - default: - break; - } - } - - return true; - } - - // Sensor events - public void enableSensor(int sensortype, boolean enabled) { - // TODO: This uses getDefaultSensor - what if we have >1 accels? - if (enabled) { - mSensorManager.registerListener(this, - mSensorManager.getDefaultSensor(sensortype), - SensorManager.SENSOR_DELAY_GAME, null); - } else { - mSensorManager.unregisterListener(this, - mSensorManager.getDefaultSensor(sensortype)); - } - } - - @Override - public void onAccuracyChanged(Sensor sensor, int accuracy) { - // TODO - } - - @Override - public void onSensorChanged(SensorEvent event) { - if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) { - - // Since we may have an orientation set, we won't receive onConfigurationChanged events. - // We thus should check here. - int newRotation; - - float x, y; - switch (mDisplay.getRotation()) { - case Surface.ROTATION_0: - default: - x = event.values[0]; - y = event.values[1]; - newRotation = 0; - break; - case Surface.ROTATION_90: - x = -event.values[1]; - y = event.values[0]; - newRotation = 90; - break; - case Surface.ROTATION_180: - x = -event.values[0]; - y = -event.values[1]; - newRotation = 180; - break; - case Surface.ROTATION_270: - x = event.values[1]; - y = -event.values[0]; - newRotation = 270; - break; - } - - if (newRotation != SDLActivity.mCurrentRotation) { - SDLActivity.mCurrentRotation = newRotation; - SDLActivity.onNativeRotationChanged(newRotation); - } - - SDLActivity.onNativeAccel(-x / SensorManager.GRAVITY_EARTH, - y / SensorManager.GRAVITY_EARTH, - event.values[2] / SensorManager.GRAVITY_EARTH); - - - } - } - - // Captured pointer events for API 26. - public boolean onCapturedPointerEvent(MotionEvent event) - { - int action = event.getActionMasked(); - - float x, y; - switch (action) { - case MotionEvent.ACTION_SCROLL: - x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0); - y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0); - SDLActivity.onNativeMouse(0, action, x, y, false); - return true; - - case MotionEvent.ACTION_HOVER_MOVE: - case MotionEvent.ACTION_MOVE: - x = event.getX(0); - y = event.getY(0); - SDLActivity.onNativeMouse(0, action, x, y, true); - return true; - - case MotionEvent.ACTION_BUTTON_PRESS: - case MotionEvent.ACTION_BUTTON_RELEASE: - - // Change our action value to what SDL's code expects. - if (action == MotionEvent.ACTION_BUTTON_PRESS) { - action = MotionEvent.ACTION_DOWN; - } else { /* MotionEvent.ACTION_BUTTON_RELEASE */ - action = MotionEvent.ACTION_UP; - } - - x = event.getX(0); - y = event.getY(0); - int button = event.getButtonState(); - - SDLActivity.onNativeMouse(button, action, x, y, true); - return true; - } - - return false; - } -} +package org.libsdl.app; + + +import android.content.Context; +import android.content.pm.ActivityInfo; +import android.graphics.Insets; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.os.Build; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.Display; +import android.view.InputDevice; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.accessibility.AccessibilityManager; +import android.view.Surface; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.View; +import android.view.WindowInsets; +import android.view.WindowManager; + + +/** + SDLSurface. This is what we draw on, so we need to know when it's created + in order to do anything useful. + + Because of this, that's where we set up the SDL thread +*/ +public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback, + View.OnApplyWindowInsetsListener, View.OnKeyListener, View.OnHoverListener, View.OnTouchListener, SensorEventListener { + + // Sensors + protected SensorManager mSensorManager; + protected Display mDisplay; + + // Keep track of the surface size to normalize touch events + protected float mWidth, mHeight; + + // Is SurfaceView ready for rendering + public boolean mIsSurfaceReady; + + // Accessibility manager + protected AccessibilityManager mAccessibilityManager; + + // Startup + public SDLSurface(Context context) { + super(context); + getHolder().addCallback(this); + + setFocusable(true); + setFocusableInTouchMode(true); + requestFocus(); + setOnApplyWindowInsetsListener(this); + setOnKeyListener(this); + setOnTouchListener(this); + setOnHoverListener(this); + + mDisplay = ((WindowManager)context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); + mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE); + mAccessibilityManager = (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE); + + setOnGenericMotionListener(SDLActivity.getMotionListener()); + + // Some arbitrary defaults to avoid a potential division by zero + mWidth = 1.0f; + mHeight = 1.0f; + + mIsSurfaceReady = false; + } + + public void handlePause() { + enableSensor(Sensor.TYPE_ACCELEROMETER, false); + } + + public void handleResume() { + setFocusable(true); + setFocusableInTouchMode(true); + requestFocus(); + setOnApplyWindowInsetsListener(this); + setOnKeyListener(this); + setOnTouchListener(this); + enableSensor(Sensor.TYPE_ACCELEROMETER, true); + } + + public Surface getNativeSurface() { + return getHolder().getSurface(); + } + + // Called when we have a valid drawing surface + @Override + public void surfaceCreated(SurfaceHolder holder) { + Log.v("SDL", "surfaceCreated()"); + SDLActivity.onNativeSurfaceCreated(); + } + + // Called when we lose the surface + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + Log.v("SDL", "surfaceDestroyed()"); + + // Transition to pause, if needed + SDLActivity.mNextNativeState = SDLActivity.NativeState.PAUSED; + SDLActivity.handleNativeState(); + + mIsSurfaceReady = false; + SDLActivity.onNativeSurfaceDestroyed(); + } + + // Called when the surface is resized + @Override + public void surfaceChanged(SurfaceHolder holder, + int format, int width, int height) { + Log.v("SDL", "surfaceChanged()"); + + if (SDLActivity.mSingleton == null) { + return; + } + + mWidth = width; + mHeight = height; + int nDeviceWidth = width; + int nDeviceHeight = height; + float density = 1.0f; + try + { + if (Build.VERSION.SDK_INT >= 17 /* Android 4.2 (JELLY_BEAN_MR1) */) { + DisplayMetrics realMetrics = new DisplayMetrics(); + mDisplay.getRealMetrics( realMetrics ); + nDeviceWidth = realMetrics.widthPixels; + nDeviceHeight = realMetrics.heightPixels; + // Use densityDpi instead of density to more closely match what the UI scale is + density = (float)realMetrics.densityDpi / 160.0f; + } + } catch(Exception ignored) { + } + + synchronized(SDLActivity.getContext()) { + // In case we're waiting on a size change after going fullscreen, send a notification. + SDLActivity.getContext().notifyAll(); + } + + Log.v("SDL", "Window size: " + width + "x" + height); + Log.v("SDL", "Device size: " + nDeviceWidth + "x" + nDeviceHeight); + SDLActivity.nativeSetScreenResolution(width, height, nDeviceWidth, nDeviceHeight, density, mDisplay.getRefreshRate()); + SDLActivity.onNativeResize(); + + // Prevent a screen distortion glitch, + // for instance when the device is in Landscape and a Portrait App is resumed. + boolean skip = false; + int requestedOrientation = SDLActivity.mSingleton.getRequestedOrientation(); + + if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT) { + if (mWidth > mHeight) { + skip = true; + } + } else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE) { + if (mWidth < mHeight) { + skip = true; + } + } + + // Special Patch for Square Resolution: Black Berry Passport + if (skip) { + double min = Math.min(mWidth, mHeight); + double max = Math.max(mWidth, mHeight); + + if (max / min < 1.20) { + Log.v("SDL", "Don't skip on such aspect-ratio. Could be a square resolution."); + skip = false; + } + } + + // Don't skip if we might be multi-window or have popup dialogs + if (skip) { + if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) { + skip = false; + } + } + + if (skip) { + Log.v("SDL", "Skip .. Surface is not ready."); + mIsSurfaceReady = false; + return; + } + + /* If the surface has been previously destroyed by onNativeSurfaceDestroyed, recreate it here */ + SDLActivity.onNativeSurfaceChanged(); + + /* Surface is ready */ + mIsSurfaceReady = true; + + SDLActivity.mNextNativeState = SDLActivity.NativeState.RESUMED; + SDLActivity.handleNativeState(); + } + + // Window inset + @Override + public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) { + if (Build.VERSION.SDK_INT >= 30 /* Android 11 (R) */) { + Insets combined = insets.getInsets(WindowInsets.Type.systemBars() | + WindowInsets.Type.systemGestures() | + WindowInsets.Type.mandatorySystemGestures() | + WindowInsets.Type.tappableElement() | + WindowInsets.Type.displayCutout()); + + SDLActivity.onNativeInsetsChanged(combined.left, combined.right, combined.top, combined.bottom); + } + + // Pass these to any child views in case they need them + return insets; + } + + // Key events + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + return SDLActivity.handleKeyEvent(v, keyCode, event, null); + } + + private float getNormalizedX(float x) + { + if (mWidth <= 1) { + return 0.5f; + } else { + return (x / (mWidth - 1)); + } + } + + private float getNormalizedY(float y) + { + if (mHeight <= 1) { + return 0.5f; + } else { + return (y / (mHeight - 1)); + } + } + + // Touch events + @Override + public boolean onHover(View v, MotionEvent event) { + if (mAccessibilityManager.isTouchExplorationEnabled()) { + return onTouch(v, event); + } else { + return super.onHoverEvent(event); + } + } + @Override + public boolean onTouch(View v, MotionEvent event) { + /* Ref: http://developer.android.com/training/gestures/multi.html */ + int touchDevId = event.getDeviceId(); + final int pointerCount = event.getPointerCount(); + int action = event.getActionMasked(); + int pointerFingerId; + int i = -1; + float x,y,p; + + // Convert hover events to touch events if we've received them. Should we only do this if accessibility manager? + if (action == MotionEvent.ACTION_HOVER_ENTER) action = MotionEvent.ACTION_DOWN; + else if (action == MotionEvent.ACTION_HOVER_MOVE) action = MotionEvent.ACTION_MOVE; + else if (action == MotionEvent.ACTION_HOVER_EXIT) action = MotionEvent.ACTION_UP; + + // 12290 = Samsung DeX mode desktop mouse + // 12290 = 0x3002 = 0x2002 | 0x1002 = SOURCE_MOUSE | SOURCE_TOUCHSCREEN + // 0x2 = SOURCE_CLASS_POINTER + if (event.getSource() == InputDevice.SOURCE_MOUSE || event.getSource() == (InputDevice.SOURCE_MOUSE | InputDevice.SOURCE_TOUCHSCREEN)) { + int mouseButton = 1; + try { + Object object = event.getClass().getMethod("getButtonState").invoke(event); + if (object != null) { + mouseButton = (Integer) object; + } + } catch(Exception ignored) { + } + + // We need to check if we're in relative mouse mode and get the axis offset rather than the x/y values + // if we are. We'll leverage our existing mouse motion listener + SDLGenericMotionListener_API12 motionListener = SDLActivity.getMotionListener(); + x = motionListener.getEventX(event); + y = motionListener.getEventY(event); + + SDLActivity.onNativeMouse(mouseButton, action, x, y, motionListener.inRelativeMode()); + } else { + switch(action) { + case MotionEvent.ACTION_MOVE: + for (i = 0; i < pointerCount; i++) { + pointerFingerId = event.getPointerId(i); + x = getNormalizedX(event.getX(i)); + y = getNormalizedY(event.getY(i)); + p = event.getPressure(i); + if (p > 1.0f) { + // may be larger than 1.0f on some devices + // see the documentation of getPressure(i) + p = 1.0f; + } + SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p); + } + break; + + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_DOWN: + // Primary pointer up/down, the index is always zero + i = 0; + /* fallthrough */ + case MotionEvent.ACTION_POINTER_UP: + case MotionEvent.ACTION_POINTER_DOWN: + // Non primary pointer up/down + if (i == -1) { + i = event.getActionIndex(); + } + + pointerFingerId = event.getPointerId(i); + x = getNormalizedX(event.getX(i)); + y = getNormalizedY(event.getY(i)); + p = event.getPressure(i); + if (p > 1.0f) { + // may be larger than 1.0f on some devices + // see the documentation of getPressure(i) + p = 1.0f; + } + SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p); + break; + + case MotionEvent.ACTION_CANCEL: + for (i = 0; i < pointerCount; i++) { + pointerFingerId = event.getPointerId(i); + x = getNormalizedX(event.getX(i)); + y = getNormalizedY(event.getY(i)); + p = event.getPressure(i); + if (p > 1.0f) { + // may be larger than 1.0f on some devices + // see the documentation of getPressure(i) + p = 1.0f; + } + SDLActivity.onNativeTouch(touchDevId, pointerFingerId, MotionEvent.ACTION_UP, x, y, p); + } + break; + + default: + break; + } + } + + return true; + } + + // Sensor events + public void enableSensor(int sensortype, boolean enabled) { + // TODO: This uses getDefaultSensor - what if we have >1 accels? + if (enabled) { + mSensorManager.registerListener(this, + mSensorManager.getDefaultSensor(sensortype), + SensorManager.SENSOR_DELAY_GAME, null); + } else { + mSensorManager.unregisterListener(this, + mSensorManager.getDefaultSensor(sensortype)); + } + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { + // TODO + } + + @Override + public void onSensorChanged(SensorEvent event) { + if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) { + + // Since we may have an orientation set, we won't receive onConfigurationChanged events. + // We thus should check here. + int newRotation; + + float x, y; + switch (mDisplay.getRotation()) { + case Surface.ROTATION_0: + default: + x = event.values[0]; + y = event.values[1]; + newRotation = 0; + break; + case Surface.ROTATION_90: + x = -event.values[1]; + y = event.values[0]; + newRotation = 90; + break; + case Surface.ROTATION_180: + x = -event.values[0]; + y = -event.values[1]; + newRotation = 180; + break; + case Surface.ROTATION_270: + x = event.values[1]; + y = -event.values[0]; + newRotation = 270; + break; + } + + if (newRotation != SDLActivity.mCurrentRotation) { + SDLActivity.mCurrentRotation = newRotation; + SDLActivity.onNativeRotationChanged(newRotation); + } + + SDLActivity.onNativeAccel(-x / SensorManager.GRAVITY_EARTH, + y / SensorManager.GRAVITY_EARTH, + event.values[2] / SensorManager.GRAVITY_EARTH); + + + } + } + + // Captured pointer events for API 26. + public boolean onCapturedPointerEvent(MotionEvent event) + { + int action = event.getActionMasked(); + + float x, y; + switch (action) { + case MotionEvent.ACTION_SCROLL: + x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0); + y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0); + SDLActivity.onNativeMouse(0, action, x, y, false); + return true; + + case MotionEvent.ACTION_HOVER_MOVE: + case MotionEvent.ACTION_MOVE: + x = event.getX(0); + y = event.getY(0); + SDLActivity.onNativeMouse(0, action, x, y, true); + return true; + + case MotionEvent.ACTION_BUTTON_PRESS: + case MotionEvent.ACTION_BUTTON_RELEASE: + + // Change our action value to what SDL's code expects. + if (action == MotionEvent.ACTION_BUTTON_PRESS) { + action = MotionEvent.ACTION_DOWN; + } else { /* MotionEvent.ACTION_BUTTON_RELEASE */ + action = MotionEvent.ACTION_UP; + } + + x = event.getX(0); + y = event.getY(0); + int button = event.getButtonState(); + + SDLActivity.onNativeMouse(button, action, x, y, true); + return true; + } + + return false; + } +} diff --git a/plugin/curl/_SConscript b/plugin/curl/_SConscript index 3c8bdf14..3d77c523 100644 --- a/plugin/curl/_SConscript +++ b/plugin/curl/_SConscript @@ -1,15 +1,15 @@ -Import("env") - -if env["PLATFORM"] == "linux": - # At least on my Ubuntu installations, the curl headers end up in what might be a somewhat nonstandard place, at any rate it doesn't hurt to add the edgecase here. - env.Append(CPPPATH = ["/usr/include/x86_64-linux-gnu"]) -scriptarray = env.SharedObject("scriptarray", "#ASAddon/plugin/scriptarray.cpp", LIBPATH = []) -winlibs = "" -if env["PLATFORM"] == "win32": - winlibs = "kernel32 user32 crypt32 advapi32 iphlpapi netapi32 uuid wldap32 ws2_32 normaliz" -if ARGUMENTS.get("no_shared_plugins", "0") == "0": - env.SharedLibrary("#release/lib/nvgt_curl", ["internet.cpp", scriptarray], LIBS = Split(winlibs) + ["libcurl", "PocoFoundationMT" if env["PLATFORM"] == "win32" else "PocoFoundation"], CPPDEFINES = ["CURL_STATICLIB"]) -static = env.Object("internet_static", "internet.cpp", CPPDEFINES = [("NVGT_PLUGIN_STATIC", "nvgt_curl"), "CURL_STATICLIB"]) -static = env.StaticLibrary("#build/lib/nvgt_curl", [static], LIBS = ["libcurl"]) -static = [static, "libcurl"] -Return("static") +Import("env") + +if env["PLATFORM"] == "linux": + # At least on my Ubuntu installations, the curl headers end up in what might be a somewhat nonstandard place, at any rate it doesn't hurt to add the edgecase here. + env.Append(CPPPATH = ["/usr/include/x86_64-linux-gnu"]) +scriptarray = env.SharedObject("scriptarray", "#ASAddon/plugin/scriptarray.cpp", LIBPATH = []) +winlibs = "" +if env["PLATFORM"] == "win32": + winlibs = "kernel32 user32 crypt32 advapi32 iphlpapi netapi32 uuid wldap32 ws2_32 normaliz" +if ARGUMENTS.get("no_shared_plugins", "0") == "0": + env.SharedLibrary("#release/lib/nvgt_curl", ["internet.cpp", scriptarray], LIBS = Split(winlibs) + ["libcurl", "PocoFoundationMT" if env["PLATFORM"] == "win32" else "PocoFoundation"], CPPDEFINES = ["CURL_STATICLIB"]) +static = env.Object("internet_static", "internet.cpp", CPPDEFINES = [("NVGT_PLUGIN_STATIC", "nvgt_curl"), "CURL_STATICLIB"]) +static = env.StaticLibrary("#build/lib/nvgt_curl", [static], LIBS = ["libcurl"]) +static = [static, "libcurl"] +Return("static") diff --git a/plugin/git/_SConscript b/plugin/git/_SConscript index d3b71d87..8443ba21 100644 --- a/plugin/git/_SConscript +++ b/plugin/git/_SConscript @@ -1,12 +1,12 @@ -Import(["env", "nvgt_env"]) - -scriptarray = env.SharedObject("scriptarray", "#ASAddon/plugin/scriptarray.cpp") -if ARGUMENTS.get("no_shared_plugins", "0") == "0": - env.SharedLibrary("#release/lib/git2nvgt", ["git.cpp", scriptarray], LIBS = ["git2"]) -static = env.Object("git_static", "git.cpp", CPPDEFINES = [("NVGT_PLUGIN_STATIC", "git2nvgt")]) -static = env.StaticLibrary("#build/lib/git2nvgt", [static]) -if env["PLATFORM"] == "darwin": - nvgt_env.Append(LINKFLAGS = ["-weak_library", "/opt/homebrew/lib/libgit2.dylib"]) -else: - static = [static, "git2"] -Return("static") +Import(["env", "nvgt_env"]) + +scriptarray = env.SharedObject("scriptarray", "#ASAddon/plugin/scriptarray.cpp") +if ARGUMENTS.get("no_shared_plugins", "0") == "0": + env.SharedLibrary("#release/lib/git2nvgt", ["git.cpp", scriptarray], LIBS = ["git2"]) +static = env.Object("git_static", "git.cpp", CPPDEFINES = [("NVGT_PLUGIN_STATIC", "git2nvgt")]) +static = env.StaticLibrary("#build/lib/git2nvgt", [static]) +if env["PLATFORM"] == "darwin": + nvgt_env.Append(LINKFLAGS = ["-weak_library", "/opt/homebrew/lib/libgit2.dylib"]) +else: + static = [static, "git2"] +Return("static") diff --git a/plugin/git/git.cpp b/plugin/git/git.cpp index 64f7fcf9..a03bfa78 100644 --- a/plugin/git/git.cpp +++ b/plugin/git/git.cpp @@ -1,450 +1,450 @@ -/* git.cpp - libgit2 wrapper plugin code - * - * NVGT - NonVisual Gaming Toolkit - * Copyright (c) 2022-2024 Sam Tupy - * https://nvgt.gg - * This software is provided "as-is", without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. - * Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: - * 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. - * 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. - * 3. This notice may not be removed or altered from any source distribution. -*/ - -#include "../../src/nvgt_plugin.h" -#include "git.h" - -static bool libgit2_inited = false; -static asIScriptEngine* g_ScriptEngine = NULL; - -int nvgt_git_default_match_callback(const char* path, const char* matched, void* payload) { - nvgt_git_repository* repo = (nvgt_git_repository*)payload; - if (!repo->match_callback) return 0; - asIScriptContext* ACtx = asGetActiveContext(); - bool new_context = !ACtx || ACtx->PushState() < 0; - asIScriptContext* ctx = new_context ? g_ScriptEngine->RequestContext() : ACtx; - if (ctx->Prepare(repo->match_callback)) { - if (new_context) g_ScriptEngine->ReturnContext(ctx); - else ctx->PopState(); - return GIT_EUSER; - } - std::string path_str(path, strlen(path)); - std::string matched_str(matched, strlen(path)); - ctx->SetArgObject(1, repo); - ctx->SetArgObject(2, &path_str); - ctx->SetArgObject(3, &repo->match_callback_payload); - int ret = GIT_EUSER; - if (ctx->Execute() == asEXECUTION_FINISHED) - ret = ctx->GetReturnDWord(); - if (new_context) g_ScriptEngine->ReturnContext(ctx); - else ctx->PopState(); - return ret; -} -int nvgt_git_changed_match_callback(const char* path, const char* matched, void* payload) { - git_repository* repo = (git_repository*)payload; - unsigned int status; - if (git_status_file(&status, repo, path) < 0) return -1; - if ((status & GIT_STATUS_WT_MODIFIED) || (status & GIT_STATUS_WT_NEW)) return 0; - return 1; -} -inline git_strarray as_strarray2git_strarray(CScriptArray* as_array) { - git_strarray strarray; - strarray.count = as_array->GetSize(); - strarray.strings = (char**)malloc(strarray.count * sizeof(char*)); - for (int i = 0; i < strarray.count; i++) - strarray.strings[i] = (char*)(*(std::string*)as_array->At(i)).c_str(); - return strarray; -} - - -nvgt_git_repository::nvgt_git_repository() : repo(NULL), index(NULL), ref_count(1) { - if (!libgit2_inited) { - git_libgit2_init(); - libgit2_inited = true; - } -} -void nvgt_git_repository::add_ref() { - asAtomicInc(ref_count); -} -void nvgt_git_repository::release() { - if (asAtomicDec(ref_count) < 1) { - close(); - delete this; - } -} -int nvgt_git_repository::open(const std::string& path) { - if (repo) return GIT_EEXISTS; - int ret = git_repository_open(&repo, path.c_str()); - if (ret == GIT_OK) - git_repository_index(&index, repo); - return ret; -} -int nvgt_git_repository::create(const std::string& path) { - if (repo) return GIT_EEXISTS; - int ret = git_repository_init(&repo, path.c_str(), false); - if (ret == GIT_OK) - git_repository_index(&index, repo); - return ret; -} -bool nvgt_git_repository::close() { - if (!index && !repo) return false; - if (index) git_index_free(index); - if (repo) git_repository_free(repo); - index = NULL; - repo = NULL; - return true; -} -int nvgt_git_repository::add(const std::string& path) { - if (!index) return GIT_ERROR; - return git_index_add_bypath(index, path.c_str()); -} -int nvgt_git_repository::add_all(CScriptArray* paths, int flags) { - if (!index || !paths) return GIT_ERROR; - git_strarray paths_list = as_strarray2git_strarray(paths); - int ret = git_index_add_all(index, &paths_list, flags, nvgt_git_changed_match_callback, repo); - free(paths_list.strings); - return ret; -} -int nvgt_git_repository::add_all_cb(CScriptArray* paths, int flags, asIScriptFunction* match_callback, const std::string& match_callback_payload) { - if (!index || !paths) return GIT_ERROR; - git_strarray paths_list = as_strarray2git_strarray(paths); - this->match_callback = match_callback; - this->match_callback_payload = match_callback_payload; - int ret = git_index_add_all(index, &paths_list, flags, this->match_callback ? nvgt_git_default_match_callback : NULL, this); - match_callback->Release(); - this->match_callback = NULL; - free(paths_list.strings); - return ret; -} -int nvgt_git_repository::remove(const std::string& path) { - if (!index) return GIT_ERROR; - return git_index_remove_bypath(index, path.c_str()); -} -int nvgt_git_repository::remove_all(CScriptArray* paths) { - if (!index || !paths) return GIT_ERROR; - git_strarray paths_list = as_strarray2git_strarray(paths); - int ret = git_index_remove_all(index, &paths_list, nvgt_git_changed_match_callback, repo); - free(paths_list.strings); - return ret; -} -int nvgt_git_repository::remove_all_cb(CScriptArray* paths, asIScriptFunction* match_callback, const std::string& match_callback_payload) { - if (!index || !paths) return GIT_ERROR; - git_strarray paths_list = as_strarray2git_strarray(paths); - this->match_callback = match_callback; - this->match_callback_payload = match_callback_payload; - int ret = git_index_remove_all(index, &paths_list, this->match_callback ? nvgt_git_default_match_callback : NULL, this); - match_callback->Release(); - this->match_callback = NULL; - free(paths_list.strings); - return ret; -} -int nvgt_git_repository::update_all(CScriptArray* paths) { - if (!index || !paths) return GIT_ERROR; - git_strarray paths_list = as_strarray2git_strarray(paths); - int ret = git_index_update_all(index, &paths_list, nvgt_git_changed_match_callback, repo); - free(paths_list.strings); - return ret; -} -int nvgt_git_repository::update_all_cb(CScriptArray* paths, asIScriptFunction* match_callback, const std::string& match_callback_payload) { - if (!index || !paths) return GIT_ERROR; - git_strarray paths_list = as_strarray2git_strarray(paths); - this->match_callback = match_callback; - this->match_callback_payload = match_callback_payload; - int ret = git_index_update_all(index, &paths_list, this->match_callback ? nvgt_git_default_match_callback : NULL, this); - match_callback->Release(); - this->match_callback = NULL; - free(paths_list.strings); - return ret; -} -nvgt_git_repository_commit* nvgt_git_repository::commit(const std::string& author, const std::string& author_email, const std::string& committer, const std::string& committer_email, const std::string& message, const std::string& commit_ref) { - git_oid commit_oid, tree_oid; - git_tree* tree; - git_object* parent = NULL; - git_reference* ref = NULL; - git_signature* signature_author; - git_signature* signature_committer; - int r = git_revparse_ext(&parent, &ref, repo, commit_ref.c_str()); - if (r != GIT_OK && r != GIT_ENOTFOUND) return NULL; - if (git_index_write_tree(&tree_oid, index)) { - git_object_free(parent); - git_reference_free(ref); - return NULL; - } - if (git_index_write(index)) { - git_object_free(parent); - git_reference_free(ref); - return NULL; - } - if (git_tree_lookup(&tree, repo, &tree_oid)) { - git_object_free(parent); - git_reference_free(ref); - return NULL; - } - if (git_signature_now(&signature_author, author.c_str(), author_email.c_str())) { - git_tree_free(tree); - git_object_free(parent); - git_reference_free(ref); - return NULL; - } - if (git_signature_now(&signature_committer, committer.c_str(), committer_email.c_str())) { - git_signature_free(signature_author); - git_tree_free(tree); - git_object_free(parent); - git_reference_free(ref); - return NULL; - } - git_buf clean_message = GIT_BUF_INIT; - git_message_prettify(&clean_message, message.c_str(), false, 0); - r = git_commit_create_v(&commit_oid, repo, commit_ref.c_str(), signature_author, signature_committer, NULL, clean_message.ptr ? clean_message.ptr : message.c_str(), tree, parent ? 1 : 0, parent); - git_commit* commit; - nvgt_git_repository_commit* ret = NULL; - if (!r && !git_commit_lookup(&commit, repo, &commit_oid)) ret = new nvgt_git_repository_commit(commit); - git_signature_free(signature_committer); - git_signature_free(signature_author); - git_tree_free(tree); - git_object_free(parent); - git_reference_free(ref); - if (clean_message.ptr) git_buf_dispose(&clean_message); - return ret; -} -std::string nvgt_git_repository::commit_diff(nvgt_git_repository_commit* commit1, nvgt_git_repository_commit* commit2, unsigned int context_lines, unsigned int interhunk_lines, unsigned int flags, unsigned int format, CScriptArray* pathspec, const std::string& old_prefix, const std::string& new_prefix) { - if (!commit1 || !commit2) return ""; - git_tree* tree1 = NULL, * tree2 = NULL; - if (commit1 && git_commit_tree(&tree1, commit1->commit)) { - if (pathspec) pathspec->Release(); - return ""; - } - if (commit2 && git_commit_tree(&tree2, commit2->commit)) { - if (pathspec) pathspec->Release(); - return ""; - } - git_diff* diff = NULL; - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - if (pathspec && pathspec->GetSize() > 0) - opts.pathspec = as_strarray2git_strarray(pathspec); - opts.flags = flags; - opts.context_lines = context_lines; - opts.interhunk_lines = interhunk_lines; - opts.old_prefix = old_prefix.c_str(); - opts.new_prefix = new_prefix.c_str(); - if (git_diff_tree_to_tree(&diff, repo, tree1, tree2, &opts)) { - if (tree1) git_tree_free(tree1); - git_tree_free(tree2); - if (opts.pathspec.strings) free(opts.pathspec.strings); - return ""; - } - git_buf output = GIT_BUF_INIT; - std::string ret; - if (!git_diff_to_buf(&output, diff, (git_diff_format_t)format)) - ret = std::string(output.ptr, output.size); - git_buf_dispose(&output); - if (tree1) git_tree_free(tree1); - git_tree_free(tree2); - if (opts.pathspec.strings) free(opts.pathspec.strings); - return ret; -} -nvgt_git_repository_commit* nvgt_git_repository::commit_lookup(const std::string& id) { - git_oid oid; - if (git_oid_fromstr(&oid, id.c_str())) return NULL; - git_commit* c; - if (git_commit_lookup(&c, repo, &oid)) return NULL; - return new nvgt_git_repository_commit(c); -} -nvgt_git_repository_commit_iterator* nvgt_git_repository::commit_iterate(CScriptArray* path_filter, const std::string& author_filter, const std::string& message_filter, git_time_t min_time_filter, git_time_t max_time_filter, unsigned int start, unsigned int count) { - git_revwalk* w = NULL; - if (git_revwalk_new(&w, repo)) return NULL; - git_revwalk_push_head(w); - return new nvgt_git_repository_commit_iterator(w, path_filter, author_filter, message_filter, min_time_filter, max_time_filter, start, count); -} -nvgt_git_repository_entry* nvgt_git_repository::get_entry(unsigned int n) { - if (!index) return NULL; - const git_index_entry* entry = git_index_get_byindex(index, n); - if (!entry) return NULL; - return new nvgt_git_repository_entry(entry); -} -nvgt_git_repository_entry* nvgt_git_repository::find_entry(const std::string& path) { - if (!index) return NULL; - size_t pos; - if (git_index_find(&pos, index, path.c_str()) != GIT_OK) return NULL; - return get_entry(pos); -} -int nvgt_git_repository::get_entry_count() { - if (!index) return GIT_ENOTFOUND; - return git_index_entrycount(index); -} -int nvgt_git_repository::get_is_empty() { - if (!repo) return GIT_ENOTFOUND; - return git_repository_is_empty(repo); -} -const std::string nvgt_git_repository::get_path() { - if (!repo) return ""; - const char* p = git_repository_path(repo); - return std::string(p, strlen(p)); -} -const std::string nvgt_git_repository::get_workdir() { - if (!repo) return ""; - const char* p = git_repository_workdir(repo); - return std::string(p, strlen(p)); -} -nvgt_git_repository* new_git_repository() { - return new nvgt_git_repository(); -} - -void nvgt_git_repository_commit::get_signatures() { - if (committer != "") return; - const git_signature* sig_committer = git_commit_committer(commit); - if (sig_committer != NULL) { - committer = sig_committer->name; - committer_email = sig_committer->email; - } - const git_signature* sig_author = git_commit_author(commit); - if (sig_author != NULL) { - author = sig_author->name; - author_email = sig_author->email; - } -} -nvgt_git_repository_commit* nvgt_git_repository_commit::get_parent(int idx) { - git_commit* c = NULL; - if (git_commit_parent(&c, commit, idx) != GIT_OK) return NULL; - return new nvgt_git_repository_commit(c); -} -bool nvgt_git_repository_commit_iterator::next() { - git_oid c_oid; - git_commit* c = NULL; - bool found_commit = false; - while (git_revwalk_next(&c_oid, walker) == GIT_OK) { - if (git_commit_lookup(&c, git_revwalk_repository(walker), &c_oid) != GIT_OK) return false; - if (dopts.pathspec.count) { - int parents = git_commit_parentcount(c); - git_tree* tree1, * tree2; - if (parents == 0) { - if (git_commit_tree(&tree2, c)) continue; - bool skip = false; - skip = git_pathspec_match_tree(NULL, tree2, GIT_PATHSPEC_NO_MATCH_ERROR, pspec); - git_tree_free(tree2); - if (skip) continue; - } else { - int unmatched = parents; - for (int i = 0; i < parents; i++) { - git_commit* parent; - if (git_commit_parent(&parent, c, i)) continue; - git_diff* diff; - if (git_commit_tree(&tree1, parent)) continue; - if (git_commit_tree(&tree2, c)) continue; - if (git_diff_tree_to_tree(&diff, git_commit_owner(c), tree1, tree2, &dopts)) continue; - int deltas = git_diff_num_deltas(diff); - git_diff_free(diff); - git_tree_free(tree1); - git_tree_free(tree2); - git_commit_free(parent); - if (deltas > 0) unmatched--; - } - if (unmatched > 0) continue; - } - } - const git_signature* sig = git_commit_author(c); - bool author_match = true; - if ((min_time_filter > 0 || max_time_filter > 0 || author_filter != "") && !sig) continue; - if (min_time_filter > 0 && sig->when.time < min_time_filter || max_time_filter > 0 && max_time_filter > min_time_filter && sig->when.time > max_time_filter) continue; - if (author_filter != "" && strstr(sig->name, author_filter.c_str()) == NULL && strstr(sig->email, author_filter.c_str()) == NULL) author_match = false; - sig = git_commit_committer(c); - if (!sig && author_filter != "") continue; - if (!author_match && author_filter != "" && strstr(sig->name, author_filter.c_str()) == NULL && strstr(sig->email, author_filter.c_str()) == NULL) continue; - if (message_filter != "" && strstr(git_commit_message(c), message_filter.c_str()) == NULL) continue; - current_entry++; - if (current_entry < start) continue; - if (count > 0 && inserted_entries + 1 > count) continue; - inserted_entries++; - found_commit = true; - break; - } - if (!found_commit) return false; - if (commit) commit->release(); - commit = new nvgt_git_repository_commit(c); - return true; -} - -int git_last_error_class() { - const git_error* e = git_error_last(); - if (!e) return GIT_ERROR_NONE; - return e->klass; -} -const std::string git_last_error_text() { - const git_error* e = git_error_last(); - if (!e) return ""; - return std::string(e->message); -} - - -void RegisterGit(asIScriptEngine* engine) { - engine->SetDefaultAccessMask(NVGT_SUBSYSTEM_GIT); - engine->RegisterObjectType("git_repository", 0, asOBJ_REF); - engine->RegisterFuncdef("int git_repository_match_callback(git_repository@ repo, const string&in path, const string&in user_data)"); - engine->RegisterObjectType("git_repository_entry", 0, asOBJ_REF); - engine->RegisterObjectType("git_repository_commit", 0, asOBJ_REF); - engine->RegisterObjectType("git_repository_commit_iterator", 0, asOBJ_REF); - - engine->RegisterObjectBehaviour("git_repository", asBEHAVE_FACTORY, "git_repository@r()", asFUNCTION(new_git_repository), asCALL_CDECL); - engine->RegisterObjectBehaviour("git_repository", asBEHAVE_ADDREF, "void f()", asMETHOD(nvgt_git_repository, add_ref), asCALL_THISCALL); - engine->RegisterObjectBehaviour("git_repository", asBEHAVE_RELEASE, "void f()", asMETHOD(nvgt_git_repository, release), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository", "int open(const string&in path)", asMETHOD(nvgt_git_repository, open), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository", "int create(const string&in path)", asMETHOD(nvgt_git_repository, create), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository", "bool close()", asMETHOD(nvgt_git_repository, close), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository", "int add(const string&in path)", asMETHOD(nvgt_git_repository, add), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository", "int add_all(string[]@ paths, int flags = 0)", asMETHOD(nvgt_git_repository, add_all), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository", "int add_all(string[]@ paths, int flags, git_repository_match_callback@ callback, const string&in callback_data = \"\")", asMETHOD(nvgt_git_repository, add_all_cb), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository", "int update_all(string[]@ paths)", asMETHOD(nvgt_git_repository, update_all), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository", "int update_all(string[]@ paths, git_repository_match_callback@ callback, const string&in callback_data = \"\")", asMETHOD(nvgt_git_repository, update_all_cb), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository", "int remove(const string&in path)", asMETHOD(nvgt_git_repository, remove), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository", "int remove_all(string[]@ paths)", asMETHOD(nvgt_git_repository, remove_all), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository", "int remove_all(string[]@ paths, git_repository_match_callback@ callback, const string&in callback_data = \"\")", asMETHOD(nvgt_git_repository, remove_all_cb), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository", "git_repository_commit@ commit(const string&in author, const string&in author_email, const string&in message, const string&in ref=\"HEAD\")", asMETHOD(nvgt_git_repository, commit_simple), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository", "git_repository_commit@ commit(const string&in author, const string&in author_email, const string&in committer, const string&in committer_email, const string&in message, const string&in ref=\"HEAD\")", asMETHOD(nvgt_git_repository, commit), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository", "string commit_diff(git_repository_commit@+ commit1, git_repository_commit@+ commit2, uint context_lines=3, uint interhunk_lines=0, uint flags=0, uint format=1, string[]@+ pathspec={}, const string&in old_prefix=\"a\", const string&in new_prefix=\"b\")", asMETHOD(nvgt_git_repository, commit_diff), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository", "git_repository_commit@ commit_lookup(const string&in oid)", asMETHOD(nvgt_git_repository, commit_lookup), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository", "git_repository_commit_iterator@ commit_iterate(string[]@ path_filter={}, const string&in author_filter='', const string&in message_filter='', uint64 min_time_filter=0, uint64 max_time_filter=0, uint start=0, uint count=0)", asMETHOD(nvgt_git_repository, commit_iterate), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository", "git_repository_entry@ get_entry(uint index)", asMETHOD(nvgt_git_repository, get_entry), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository", "int get_entry_count() property", asMETHOD(nvgt_git_repository, get_entry_count), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository", "int get_is_empty() property", asMETHOD(nvgt_git_repository, get_is_empty), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository", "string get_path() property", asMETHOD(nvgt_git_repository, get_path), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository", "string get_workdir() property", asMETHOD(nvgt_git_repository, get_workdir), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository", "bool get_active() property", asMETHOD(nvgt_git_repository, get_active), asCALL_THISCALL); - - engine->RegisterObjectBehaviour("git_repository_entry", asBEHAVE_ADDREF, "void f()", asMETHOD(nvgt_git_repository_entry, add_ref), asCALL_THISCALL); - engine->RegisterObjectBehaviour("git_repository_entry", asBEHAVE_RELEASE, "void f()", asMETHOD(nvgt_git_repository_entry, release), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository_entry", "uint get_ctime() property", asMETHOD(nvgt_git_repository_entry, get_ctime), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository_entry", "uint get_mtime() property", asMETHOD(nvgt_git_repository_entry, get_mtime), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository_entry", "uint get_file_size() property", asMETHOD(nvgt_git_repository_entry, get_file_size), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository_entry", "string get_oid() property", asMETHOD(nvgt_git_repository_entry, get_oid), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository_entry", "string get_path() property", asMETHOD(nvgt_git_repository_entry, get_path), asCALL_THISCALL); - - engine->RegisterObjectBehaviour("git_repository_commit", asBEHAVE_ADDREF, "void f()", asMETHOD(nvgt_git_repository_commit, add_ref), asCALL_THISCALL); - engine->RegisterObjectBehaviour("git_repository_commit", asBEHAVE_RELEASE, "void f()", asMETHOD(nvgt_git_repository_commit, release), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository_commit", "uint get_time() property", asMETHOD(nvgt_git_repository_commit, get_time), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository_commit", "int get_parent_count() property", asMETHOD(nvgt_git_repository_commit, get_parent_count), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository_commit", "git_repository_commit@ get_parent(uint)", asMETHOD(nvgt_git_repository_commit, get_parent), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository_commit", "const string get_id() property", asMETHOD(nvgt_git_repository_commit, get_id), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository_commit", "const string get_message() property", asMETHOD(nvgt_git_repository_commit, get_message), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository_commit", "string get_summary() property", asMETHOD(nvgt_git_repository_commit, get_summary), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository_commit", "const string get_body() property", asMETHOD(nvgt_git_repository_commit, get_body), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository_commit", "const string get_raw_header() property", asMETHOD(nvgt_git_repository_commit, get_raw_header), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository_commit", "const string get_committer() property", asMETHOD(nvgt_git_repository_commit, get_committer), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository_commit", "const string get_committer_email() property", asMETHOD(nvgt_git_repository_commit, get_committer_email), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository_commit", "const string get_author() property", asMETHOD(nvgt_git_repository_commit, get_author), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository_commit", "const string get_author_email() property", asMETHOD(nvgt_git_repository_commit, get_author_email), asCALL_THISCALL); - - engine->RegisterObjectBehaviour("git_repository_commit_iterator", asBEHAVE_ADDREF, "void f()", asMETHOD(nvgt_git_repository_commit_iterator, add_ref), asCALL_THISCALL); - engine->RegisterObjectBehaviour("git_repository_commit_iterator", asBEHAVE_RELEASE, "void f()", asMETHOD(nvgt_git_repository_commit_iterator, release), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository_commit_iterator", "git_repository_commit@ get_commit() property", asMETHOD(nvgt_git_repository_commit_iterator, get_commit), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository_commit_iterator", "git_repository_commit@ opImplCast()", asMETHOD(nvgt_git_repository_commit_iterator, get_commit), asCALL_THISCALL); - engine->RegisterObjectMethod("git_repository_commit_iterator", "bool opPostInc()", asMETHOD(nvgt_git_repository_commit_iterator, next), asCALL_THISCALL); - - engine->RegisterGlobalFunction("int git_last_error_class()", asFUNCTION(git_last_error_class), asCALL_CDECL); - engine->RegisterGlobalFunction("string git_last_error_text()", asFUNCTION(git_last_error_text), asCALL_CDECL); -} - -plugin_main(nvgt_plugin_shared* shared) { - prepare_plugin(shared); - g_ScriptEngine = shared->script_engine; - RegisterGit(shared->script_engine); - return true; -} +/* git.cpp - libgit2 wrapper plugin code + * + * NVGT - NonVisual Gaming Toolkit + * Copyright (c) 2022-2024 Sam Tupy + * https://nvgt.gg + * This software is provided "as-is", without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. + * Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: + * 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. +*/ + +#include "../../src/nvgt_plugin.h" +#include "git.h" + +static bool libgit2_inited = false; +static asIScriptEngine* g_ScriptEngine = NULL; + +int nvgt_git_default_match_callback(const char* path, const char* matched, void* payload) { + nvgt_git_repository* repo = (nvgt_git_repository*)payload; + if (!repo->match_callback) return 0; + asIScriptContext* ACtx = asGetActiveContext(); + bool new_context = !ACtx || ACtx->PushState() < 0; + asIScriptContext* ctx = new_context ? g_ScriptEngine->RequestContext() : ACtx; + if (ctx->Prepare(repo->match_callback)) { + if (new_context) g_ScriptEngine->ReturnContext(ctx); + else ctx->PopState(); + return GIT_EUSER; + } + std::string path_str(path, strlen(path)); + std::string matched_str(matched, strlen(path)); + ctx->SetArgObject(1, repo); + ctx->SetArgObject(2, &path_str); + ctx->SetArgObject(3, &repo->match_callback_payload); + int ret = GIT_EUSER; + if (ctx->Execute() == asEXECUTION_FINISHED) + ret = ctx->GetReturnDWord(); + if (new_context) g_ScriptEngine->ReturnContext(ctx); + else ctx->PopState(); + return ret; +} +int nvgt_git_changed_match_callback(const char* path, const char* matched, void* payload) { + git_repository* repo = (git_repository*)payload; + unsigned int status; + if (git_status_file(&status, repo, path) < 0) return -1; + if ((status & GIT_STATUS_WT_MODIFIED) || (status & GIT_STATUS_WT_NEW)) return 0; + return 1; +} +inline git_strarray as_strarray2git_strarray(CScriptArray* as_array) { + git_strarray strarray; + strarray.count = as_array->GetSize(); + strarray.strings = (char**)malloc(strarray.count * sizeof(char*)); + for (int i = 0; i < strarray.count; i++) + strarray.strings[i] = (char*)(*(std::string*)as_array->At(i)).c_str(); + return strarray; +} + + +nvgt_git_repository::nvgt_git_repository() : repo(NULL), index(NULL), ref_count(1) { + if (!libgit2_inited) { + git_libgit2_init(); + libgit2_inited = true; + } +} +void nvgt_git_repository::add_ref() { + asAtomicInc(ref_count); +} +void nvgt_git_repository::release() { + if (asAtomicDec(ref_count) < 1) { + close(); + delete this; + } +} +int nvgt_git_repository::open(const std::string& path) { + if (repo) return GIT_EEXISTS; + int ret = git_repository_open(&repo, path.c_str()); + if (ret == GIT_OK) + git_repository_index(&index, repo); + return ret; +} +int nvgt_git_repository::create(const std::string& path) { + if (repo) return GIT_EEXISTS; + int ret = git_repository_init(&repo, path.c_str(), false); + if (ret == GIT_OK) + git_repository_index(&index, repo); + return ret; +} +bool nvgt_git_repository::close() { + if (!index && !repo) return false; + if (index) git_index_free(index); + if (repo) git_repository_free(repo); + index = NULL; + repo = NULL; + return true; +} +int nvgt_git_repository::add(const std::string& path) { + if (!index) return GIT_ERROR; + return git_index_add_bypath(index, path.c_str()); +} +int nvgt_git_repository::add_all(CScriptArray* paths, int flags) { + if (!index || !paths) return GIT_ERROR; + git_strarray paths_list = as_strarray2git_strarray(paths); + int ret = git_index_add_all(index, &paths_list, flags, nvgt_git_changed_match_callback, repo); + free(paths_list.strings); + return ret; +} +int nvgt_git_repository::add_all_cb(CScriptArray* paths, int flags, asIScriptFunction* match_callback, const std::string& match_callback_payload) { + if (!index || !paths) return GIT_ERROR; + git_strarray paths_list = as_strarray2git_strarray(paths); + this->match_callback = match_callback; + this->match_callback_payload = match_callback_payload; + int ret = git_index_add_all(index, &paths_list, flags, this->match_callback ? nvgt_git_default_match_callback : NULL, this); + match_callback->Release(); + this->match_callback = NULL; + free(paths_list.strings); + return ret; +} +int nvgt_git_repository::remove(const std::string& path) { + if (!index) return GIT_ERROR; + return git_index_remove_bypath(index, path.c_str()); +} +int nvgt_git_repository::remove_all(CScriptArray* paths) { + if (!index || !paths) return GIT_ERROR; + git_strarray paths_list = as_strarray2git_strarray(paths); + int ret = git_index_remove_all(index, &paths_list, nvgt_git_changed_match_callback, repo); + free(paths_list.strings); + return ret; +} +int nvgt_git_repository::remove_all_cb(CScriptArray* paths, asIScriptFunction* match_callback, const std::string& match_callback_payload) { + if (!index || !paths) return GIT_ERROR; + git_strarray paths_list = as_strarray2git_strarray(paths); + this->match_callback = match_callback; + this->match_callback_payload = match_callback_payload; + int ret = git_index_remove_all(index, &paths_list, this->match_callback ? nvgt_git_default_match_callback : NULL, this); + match_callback->Release(); + this->match_callback = NULL; + free(paths_list.strings); + return ret; +} +int nvgt_git_repository::update_all(CScriptArray* paths) { + if (!index || !paths) return GIT_ERROR; + git_strarray paths_list = as_strarray2git_strarray(paths); + int ret = git_index_update_all(index, &paths_list, nvgt_git_changed_match_callback, repo); + free(paths_list.strings); + return ret; +} +int nvgt_git_repository::update_all_cb(CScriptArray* paths, asIScriptFunction* match_callback, const std::string& match_callback_payload) { + if (!index || !paths) return GIT_ERROR; + git_strarray paths_list = as_strarray2git_strarray(paths); + this->match_callback = match_callback; + this->match_callback_payload = match_callback_payload; + int ret = git_index_update_all(index, &paths_list, this->match_callback ? nvgt_git_default_match_callback : NULL, this); + match_callback->Release(); + this->match_callback = NULL; + free(paths_list.strings); + return ret; +} +nvgt_git_repository_commit* nvgt_git_repository::commit(const std::string& author, const std::string& author_email, const std::string& committer, const std::string& committer_email, const std::string& message, const std::string& commit_ref) { + git_oid commit_oid, tree_oid; + git_tree* tree; + git_object* parent = NULL; + git_reference* ref = NULL; + git_signature* signature_author; + git_signature* signature_committer; + int r = git_revparse_ext(&parent, &ref, repo, commit_ref.c_str()); + if (r != GIT_OK && r != GIT_ENOTFOUND) return NULL; + if (git_index_write_tree(&tree_oid, index)) { + git_object_free(parent); + git_reference_free(ref); + return NULL; + } + if (git_index_write(index)) { + git_object_free(parent); + git_reference_free(ref); + return NULL; + } + if (git_tree_lookup(&tree, repo, &tree_oid)) { + git_object_free(parent); + git_reference_free(ref); + return NULL; + } + if (git_signature_now(&signature_author, author.c_str(), author_email.c_str())) { + git_tree_free(tree); + git_object_free(parent); + git_reference_free(ref); + return NULL; + } + if (git_signature_now(&signature_committer, committer.c_str(), committer_email.c_str())) { + git_signature_free(signature_author); + git_tree_free(tree); + git_object_free(parent); + git_reference_free(ref); + return NULL; + } + git_buf clean_message = GIT_BUF_INIT; + git_message_prettify(&clean_message, message.c_str(), false, 0); + r = git_commit_create_v(&commit_oid, repo, commit_ref.c_str(), signature_author, signature_committer, NULL, clean_message.ptr ? clean_message.ptr : message.c_str(), tree, parent ? 1 : 0, parent); + git_commit* commit; + nvgt_git_repository_commit* ret = NULL; + if (!r && !git_commit_lookup(&commit, repo, &commit_oid)) ret = new nvgt_git_repository_commit(commit); + git_signature_free(signature_committer); + git_signature_free(signature_author); + git_tree_free(tree); + git_object_free(parent); + git_reference_free(ref); + if (clean_message.ptr) git_buf_dispose(&clean_message); + return ret; +} +std::string nvgt_git_repository::commit_diff(nvgt_git_repository_commit* commit1, nvgt_git_repository_commit* commit2, unsigned int context_lines, unsigned int interhunk_lines, unsigned int flags, unsigned int format, CScriptArray* pathspec, const std::string& old_prefix, const std::string& new_prefix) { + if (!commit1 || !commit2) return ""; + git_tree* tree1 = NULL, * tree2 = NULL; + if (commit1 && git_commit_tree(&tree1, commit1->commit)) { + if (pathspec) pathspec->Release(); + return ""; + } + if (commit2 && git_commit_tree(&tree2, commit2->commit)) { + if (pathspec) pathspec->Release(); + return ""; + } + git_diff* diff = NULL; + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + if (pathspec && pathspec->GetSize() > 0) + opts.pathspec = as_strarray2git_strarray(pathspec); + opts.flags = flags; + opts.context_lines = context_lines; + opts.interhunk_lines = interhunk_lines; + opts.old_prefix = old_prefix.c_str(); + opts.new_prefix = new_prefix.c_str(); + if (git_diff_tree_to_tree(&diff, repo, tree1, tree2, &opts)) { + if (tree1) git_tree_free(tree1); + git_tree_free(tree2); + if (opts.pathspec.strings) free(opts.pathspec.strings); + return ""; + } + git_buf output = GIT_BUF_INIT; + std::string ret; + if (!git_diff_to_buf(&output, diff, (git_diff_format_t)format)) + ret = std::string(output.ptr, output.size); + git_buf_dispose(&output); + if (tree1) git_tree_free(tree1); + git_tree_free(tree2); + if (opts.pathspec.strings) free(opts.pathspec.strings); + return ret; +} +nvgt_git_repository_commit* nvgt_git_repository::commit_lookup(const std::string& id) { + git_oid oid; + if (git_oid_fromstr(&oid, id.c_str())) return NULL; + git_commit* c; + if (git_commit_lookup(&c, repo, &oid)) return NULL; + return new nvgt_git_repository_commit(c); +} +nvgt_git_repository_commit_iterator* nvgt_git_repository::commit_iterate(CScriptArray* path_filter, const std::string& author_filter, const std::string& message_filter, git_time_t min_time_filter, git_time_t max_time_filter, unsigned int start, unsigned int count) { + git_revwalk* w = NULL; + if (git_revwalk_new(&w, repo)) return NULL; + git_revwalk_push_head(w); + return new nvgt_git_repository_commit_iterator(w, path_filter, author_filter, message_filter, min_time_filter, max_time_filter, start, count); +} +nvgt_git_repository_entry* nvgt_git_repository::get_entry(unsigned int n) { + if (!index) return NULL; + const git_index_entry* entry = git_index_get_byindex(index, n); + if (!entry) return NULL; + return new nvgt_git_repository_entry(entry); +} +nvgt_git_repository_entry* nvgt_git_repository::find_entry(const std::string& path) { + if (!index) return NULL; + size_t pos; + if (git_index_find(&pos, index, path.c_str()) != GIT_OK) return NULL; + return get_entry(pos); +} +int nvgt_git_repository::get_entry_count() { + if (!index) return GIT_ENOTFOUND; + return git_index_entrycount(index); +} +int nvgt_git_repository::get_is_empty() { + if (!repo) return GIT_ENOTFOUND; + return git_repository_is_empty(repo); +} +const std::string nvgt_git_repository::get_path() { + if (!repo) return ""; + const char* p = git_repository_path(repo); + return std::string(p, strlen(p)); +} +const std::string nvgt_git_repository::get_workdir() { + if (!repo) return ""; + const char* p = git_repository_workdir(repo); + return std::string(p, strlen(p)); +} +nvgt_git_repository* new_git_repository() { + return new nvgt_git_repository(); +} + +void nvgt_git_repository_commit::get_signatures() { + if (committer != "") return; + const git_signature* sig_committer = git_commit_committer(commit); + if (sig_committer != NULL) { + committer = sig_committer->name; + committer_email = sig_committer->email; + } + const git_signature* sig_author = git_commit_author(commit); + if (sig_author != NULL) { + author = sig_author->name; + author_email = sig_author->email; + } +} +nvgt_git_repository_commit* nvgt_git_repository_commit::get_parent(int idx) { + git_commit* c = NULL; + if (git_commit_parent(&c, commit, idx) != GIT_OK) return NULL; + return new nvgt_git_repository_commit(c); +} +bool nvgt_git_repository_commit_iterator::next() { + git_oid c_oid; + git_commit* c = NULL; + bool found_commit = false; + while (git_revwalk_next(&c_oid, walker) == GIT_OK) { + if (git_commit_lookup(&c, git_revwalk_repository(walker), &c_oid) != GIT_OK) return false; + if (dopts.pathspec.count) { + int parents = git_commit_parentcount(c); + git_tree* tree1, * tree2; + if (parents == 0) { + if (git_commit_tree(&tree2, c)) continue; + bool skip = false; + skip = git_pathspec_match_tree(NULL, tree2, GIT_PATHSPEC_NO_MATCH_ERROR, pspec); + git_tree_free(tree2); + if (skip) continue; + } else { + int unmatched = parents; + for (int i = 0; i < parents; i++) { + git_commit* parent; + if (git_commit_parent(&parent, c, i)) continue; + git_diff* diff; + if (git_commit_tree(&tree1, parent)) continue; + if (git_commit_tree(&tree2, c)) continue; + if (git_diff_tree_to_tree(&diff, git_commit_owner(c), tree1, tree2, &dopts)) continue; + int deltas = git_diff_num_deltas(diff); + git_diff_free(diff); + git_tree_free(tree1); + git_tree_free(tree2); + git_commit_free(parent); + if (deltas > 0) unmatched--; + } + if (unmatched > 0) continue; + } + } + const git_signature* sig = git_commit_author(c); + bool author_match = true; + if ((min_time_filter > 0 || max_time_filter > 0 || author_filter != "") && !sig) continue; + if (min_time_filter > 0 && sig->when.time < min_time_filter || max_time_filter > 0 && max_time_filter > min_time_filter && sig->when.time > max_time_filter) continue; + if (author_filter != "" && strstr(sig->name, author_filter.c_str()) == NULL && strstr(sig->email, author_filter.c_str()) == NULL) author_match = false; + sig = git_commit_committer(c); + if (!sig && author_filter != "") continue; + if (!author_match && author_filter != "" && strstr(sig->name, author_filter.c_str()) == NULL && strstr(sig->email, author_filter.c_str()) == NULL) continue; + if (message_filter != "" && strstr(git_commit_message(c), message_filter.c_str()) == NULL) continue; + current_entry++; + if (current_entry < start) continue; + if (count > 0 && inserted_entries + 1 > count) continue; + inserted_entries++; + found_commit = true; + break; + } + if (!found_commit) return false; + if (commit) commit->release(); + commit = new nvgt_git_repository_commit(c); + return true; +} + +int git_last_error_class() { + const git_error* e = git_error_last(); + if (!e) return GIT_ERROR_NONE; + return e->klass; +} +const std::string git_last_error_text() { + const git_error* e = git_error_last(); + if (!e) return ""; + return std::string(e->message); +} + + +void RegisterGit(asIScriptEngine* engine) { + engine->SetDefaultAccessMask(NVGT_SUBSYSTEM_GIT); + engine->RegisterObjectType("git_repository", 0, asOBJ_REF); + engine->RegisterFuncdef("int git_repository_match_callback(git_repository@ repo, const string&in path, const string&in user_data)"); + engine->RegisterObjectType("git_repository_entry", 0, asOBJ_REF); + engine->RegisterObjectType("git_repository_commit", 0, asOBJ_REF); + engine->RegisterObjectType("git_repository_commit_iterator", 0, asOBJ_REF); + + engine->RegisterObjectBehaviour("git_repository", asBEHAVE_FACTORY, "git_repository@r()", asFUNCTION(new_git_repository), asCALL_CDECL); + engine->RegisterObjectBehaviour("git_repository", asBEHAVE_ADDREF, "void f()", asMETHOD(nvgt_git_repository, add_ref), asCALL_THISCALL); + engine->RegisterObjectBehaviour("git_repository", asBEHAVE_RELEASE, "void f()", asMETHOD(nvgt_git_repository, release), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository", "int open(const string&in path)", asMETHOD(nvgt_git_repository, open), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository", "int create(const string&in path)", asMETHOD(nvgt_git_repository, create), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository", "bool close()", asMETHOD(nvgt_git_repository, close), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository", "int add(const string&in path)", asMETHOD(nvgt_git_repository, add), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository", "int add_all(string[]@ paths, int flags = 0)", asMETHOD(nvgt_git_repository, add_all), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository", "int add_all(string[]@ paths, int flags, git_repository_match_callback@ callback, const string&in callback_data = \"\")", asMETHOD(nvgt_git_repository, add_all_cb), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository", "int update_all(string[]@ paths)", asMETHOD(nvgt_git_repository, update_all), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository", "int update_all(string[]@ paths, git_repository_match_callback@ callback, const string&in callback_data = \"\")", asMETHOD(nvgt_git_repository, update_all_cb), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository", "int remove(const string&in path)", asMETHOD(nvgt_git_repository, remove), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository", "int remove_all(string[]@ paths)", asMETHOD(nvgt_git_repository, remove_all), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository", "int remove_all(string[]@ paths, git_repository_match_callback@ callback, const string&in callback_data = \"\")", asMETHOD(nvgt_git_repository, remove_all_cb), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository", "git_repository_commit@ commit(const string&in author, const string&in author_email, const string&in message, const string&in ref=\"HEAD\")", asMETHOD(nvgt_git_repository, commit_simple), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository", "git_repository_commit@ commit(const string&in author, const string&in author_email, const string&in committer, const string&in committer_email, const string&in message, const string&in ref=\"HEAD\")", asMETHOD(nvgt_git_repository, commit), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository", "string commit_diff(git_repository_commit@+ commit1, git_repository_commit@+ commit2, uint context_lines=3, uint interhunk_lines=0, uint flags=0, uint format=1, string[]@+ pathspec={}, const string&in old_prefix=\"a\", const string&in new_prefix=\"b\")", asMETHOD(nvgt_git_repository, commit_diff), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository", "git_repository_commit@ commit_lookup(const string&in oid)", asMETHOD(nvgt_git_repository, commit_lookup), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository", "git_repository_commit_iterator@ commit_iterate(string[]@ path_filter={}, const string&in author_filter='', const string&in message_filter='', uint64 min_time_filter=0, uint64 max_time_filter=0, uint start=0, uint count=0)", asMETHOD(nvgt_git_repository, commit_iterate), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository", "git_repository_entry@ get_entry(uint index)", asMETHOD(nvgt_git_repository, get_entry), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository", "int get_entry_count() property", asMETHOD(nvgt_git_repository, get_entry_count), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository", "int get_is_empty() property", asMETHOD(nvgt_git_repository, get_is_empty), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository", "string get_path() property", asMETHOD(nvgt_git_repository, get_path), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository", "string get_workdir() property", asMETHOD(nvgt_git_repository, get_workdir), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository", "bool get_active() property", asMETHOD(nvgt_git_repository, get_active), asCALL_THISCALL); + + engine->RegisterObjectBehaviour("git_repository_entry", asBEHAVE_ADDREF, "void f()", asMETHOD(nvgt_git_repository_entry, add_ref), asCALL_THISCALL); + engine->RegisterObjectBehaviour("git_repository_entry", asBEHAVE_RELEASE, "void f()", asMETHOD(nvgt_git_repository_entry, release), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository_entry", "uint get_ctime() property", asMETHOD(nvgt_git_repository_entry, get_ctime), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository_entry", "uint get_mtime() property", asMETHOD(nvgt_git_repository_entry, get_mtime), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository_entry", "uint get_file_size() property", asMETHOD(nvgt_git_repository_entry, get_file_size), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository_entry", "string get_oid() property", asMETHOD(nvgt_git_repository_entry, get_oid), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository_entry", "string get_path() property", asMETHOD(nvgt_git_repository_entry, get_path), asCALL_THISCALL); + + engine->RegisterObjectBehaviour("git_repository_commit", asBEHAVE_ADDREF, "void f()", asMETHOD(nvgt_git_repository_commit, add_ref), asCALL_THISCALL); + engine->RegisterObjectBehaviour("git_repository_commit", asBEHAVE_RELEASE, "void f()", asMETHOD(nvgt_git_repository_commit, release), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository_commit", "uint get_time() property", asMETHOD(nvgt_git_repository_commit, get_time), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository_commit", "int get_parent_count() property", asMETHOD(nvgt_git_repository_commit, get_parent_count), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository_commit", "git_repository_commit@ get_parent(uint)", asMETHOD(nvgt_git_repository_commit, get_parent), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository_commit", "const string get_id() property", asMETHOD(nvgt_git_repository_commit, get_id), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository_commit", "const string get_message() property", asMETHOD(nvgt_git_repository_commit, get_message), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository_commit", "string get_summary() property", asMETHOD(nvgt_git_repository_commit, get_summary), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository_commit", "const string get_body() property", asMETHOD(nvgt_git_repository_commit, get_body), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository_commit", "const string get_raw_header() property", asMETHOD(nvgt_git_repository_commit, get_raw_header), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository_commit", "const string get_committer() property", asMETHOD(nvgt_git_repository_commit, get_committer), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository_commit", "const string get_committer_email() property", asMETHOD(nvgt_git_repository_commit, get_committer_email), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository_commit", "const string get_author() property", asMETHOD(nvgt_git_repository_commit, get_author), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository_commit", "const string get_author_email() property", asMETHOD(nvgt_git_repository_commit, get_author_email), asCALL_THISCALL); + + engine->RegisterObjectBehaviour("git_repository_commit_iterator", asBEHAVE_ADDREF, "void f()", asMETHOD(nvgt_git_repository_commit_iterator, add_ref), asCALL_THISCALL); + engine->RegisterObjectBehaviour("git_repository_commit_iterator", asBEHAVE_RELEASE, "void f()", asMETHOD(nvgt_git_repository_commit_iterator, release), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository_commit_iterator", "git_repository_commit@ get_commit() property", asMETHOD(nvgt_git_repository_commit_iterator, get_commit), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository_commit_iterator", "git_repository_commit@ opImplCast()", asMETHOD(nvgt_git_repository_commit_iterator, get_commit), asCALL_THISCALL); + engine->RegisterObjectMethod("git_repository_commit_iterator", "bool opPostInc()", asMETHOD(nvgt_git_repository_commit_iterator, next), asCALL_THISCALL); + + engine->RegisterGlobalFunction("int git_last_error_class()", asFUNCTION(git_last_error_class), asCALL_CDECL); + engine->RegisterGlobalFunction("string git_last_error_text()", asFUNCTION(git_last_error_text), asCALL_CDECL); +} + +plugin_main(nvgt_plugin_shared* shared) { + prepare_plugin(shared); + g_ScriptEngine = shared->script_engine; + RegisterGit(shared->script_engine); + return true; +} diff --git a/plugin/sqlite/_SConscript b/plugin/sqlite/_SConscript index 07173f09..9af10e3a 100644 --- a/plugin/sqlite/_SConscript +++ b/plugin/sqlite/_SConscript @@ -1,10 +1,10 @@ -Import("env") - -scriptarray = env.SharedObject("scriptarray", "#ASAddon/plugin/scriptarray.cpp") -sqlite_addons = env.SharedObject(Glob("*.c"), CPPDEFINES = ["SQLITE_CORE"]) -if ARGUMENTS.get("no_shared_plugins", "0") == "0": - env.SharedLibrary("#release/lib/nvgt_sqlite", ["nvgt_sqlite.cpp", "zlib_uncompr/uncompr.c", scriptarray, sqlite_addons], LIBS = ["PocoFoundationMT", "PocoDataSQLiteMT"] if env["PLATFORM"] == "win32" else ["PocoFoundation", "PocoDataSQLite"]) -static = env.Object("nvgt_sqlite_static", "nvgt_sqlite.cpp", CPPDEFINES = [("NVGT_PLUGIN_STATIC", "nvgt_sqlite")]) -static = env.StaticLibrary("#build/lib/nvgt_sqlite", [static, sqlite_addons]) -static = [static, "PocoDataSQLiteMT" if env["PLATFORM"] == "win32" else "PocoDataSQLite"] -Return("static") +Import("env") + +scriptarray = env.SharedObject("scriptarray", "#ASAddon/plugin/scriptarray.cpp") +sqlite_addons = env.SharedObject(Glob("*.c"), CPPDEFINES = ["SQLITE_CORE"]) +if ARGUMENTS.get("no_shared_plugins", "0") == "0": + env.SharedLibrary("#release/lib/nvgt_sqlite", ["nvgt_sqlite.cpp", "zlib_uncompr/uncompr.c", scriptarray, sqlite_addons], LIBS = ["PocoFoundationMT", "PocoDataSQLiteMT"] if env["PLATFORM"] == "win32" else ["PocoFoundation", "PocoDataSQLite"]) +static = env.Object("nvgt_sqlite_static", "nvgt_sqlite.cpp", CPPDEFINES = [("NVGT_PLUGIN_STATIC", "nvgt_sqlite")]) +static = env.StaticLibrary("#build/lib/nvgt_sqlite", [static, sqlite_addons]) +static = [static, "PocoDataSQLiteMT" if env["PLATFORM"] == "win32" else "PocoDataSQLite"] +Return("static") diff --git a/plugin/systemd_notify/_SConscript b/plugin/systemd_notify/_SConscript index c8276e9d..e16803b5 100644 --- a/plugin/systemd_notify/_SConscript +++ b/plugin/systemd_notify/_SConscript @@ -1,13 +1,13 @@ -Import("env") - -systemd_avail = False -conf = Configure(env, log_file = "#build/config.log") -if conf.CheckLib("systemd"): systemd_avail = True -env = conf.Finish() - -if ARGUMENTS.get("no_shared_plugins", "0") == "0": - env.SharedLibrary("#release/lib/systemd_notify", ["sd_notify.cpp"], LIBS = ["systemd"] if systemd_avail else []) -static = env.Object("sd_notify_static", "sd_notify.cpp", CPPDEFINES = [("NVGT_PLUGIN_STATIC", "systemd_notify")]) -static = env.StaticLibrary("#build/lib/systemd_notify", [static]) -if systemd_avail: static = [static, "systemd"] -Return("static") +Import("env") + +systemd_avail = False +conf = Configure(env, log_file = "#build/config.log") +if conf.CheckLib("systemd"): systemd_avail = True +env = conf.Finish() + +if ARGUMENTS.get("no_shared_plugins", "0") == "0": + env.SharedLibrary("#release/lib/systemd_notify", ["sd_notify.cpp"], LIBS = ["systemd"] if systemd_avail else []) +static = env.Object("sd_notify_static", "sd_notify.cpp", CPPDEFINES = [("NVGT_PLUGIN_STATIC", "systemd_notify")]) +static = env.StaticLibrary("#build/lib/systemd_notify", [static]) +if systemd_avail: static = [static, "systemd"] +Return("static") diff --git a/plugin/systemd_notify/sd_notify.cpp b/plugin/systemd_notify/sd_notify.cpp index 6af0e96f..9cd5e47d 100644 --- a/plugin/systemd_notify/sd_notify.cpp +++ b/plugin/systemd_notify/sd_notify.cpp @@ -1,20 +1,20 @@ -#include -#include "../../src/nvgt_plugin.h" -#if __has_include() -#include -#define systemd_available -#endif - -int systemd_notify(const std::string& state) { - #ifdef systemd_available - return sd_notify(0, state.c_str()); - #else - return 0; - #endif -} - -plugin_main(nvgt_plugin_shared* shared) { - prepare_plugin(shared); - shared->script_engine->RegisterGlobalFunction("int systemd_notify(const string&in state)", asFUNCTION(systemd_notify), asCALL_CDECL); - return true; -} +#include +#include "../../src/nvgt_plugin.h" +#if __has_include() +#include +#define systemd_available +#endif + +int systemd_notify(const std::string& state) { + #ifdef systemd_available + return sd_notify(0, state.c_str()); + #else + return 0; + #endif +} + +plugin_main(nvgt_plugin_shared* shared) { + prepare_plugin(shared); + shared->script_engine->RegisterGlobalFunction("int systemd_notify(const string&in state)", asFUNCTION(systemd_notify), asCALL_CDECL); + return true; +} diff --git a/test/interact/lowlevel_touch.nvgt b/test/interact/lowlevel_touch.nvgt index 1f842250..2ed15807 100644 --- a/test/interact/lowlevel_touch.nvgt +++ b/test/interact/lowlevel_touch.nvgt @@ -1,24 +1,24 @@ -// NonVisual Gaming Toolkit (NVGT) -// Copyright (C) 2022-2024 Sam Tupy -// License: zlib (see license.md in the root of the NVGT distribution) - -void main() { - show_window("touch test"); -tts_voice tts; - uint64[]@ devs = get_touch_devices(); - if (devs.length() < 1) { - alert("oops", "no touch devices available"); - return; - } - int prev_finger_count = 0; - tts.speak("ready", true); - while (!key_pressed(KEY_ESCAPE) and !key_pressed(KEY_AC_BACK)) { - wait(5); - touch_finger[]@ fingers = query_touch_device(); - if (fingers.length() != prev_finger_count) { - tts.speak(fingers.length() + " fingers", true); - if (fingers.length() > 0) tts.speak(round(fingers[-1].x, 2) + ", " + round(fingers[-1].y, 2)); - prev_finger_count = fingers.length(); - } - } -} +// NonVisual Gaming Toolkit (NVGT) +// Copyright (C) 2022-2024 Sam Tupy +// License: zlib (see license.md in the root of the NVGT distribution) + +void main() { + show_window("touch test"); +tts_voice tts; + uint64[]@ devs = get_touch_devices(); + if (devs.length() < 1) { + alert("oops", "no touch devices available"); + return; + } + int prev_finger_count = 0; + tts.speak("ready", true); + while (!key_pressed(KEY_ESCAPE) and !key_pressed(KEY_AC_BACK)) { + wait(5); + touch_finger[]@ fingers = query_touch_device(); + if (fingers.length() != prev_finger_count) { + tts.speak(fingers.length() + " fingers", true); + if (fingers.length() > 0) tts.speak(round(fingers[-1].x, 2) + ", " + round(fingers[-1].y, 2)); + prev_finger_count = fingers.length(); + } + } +} From c05131c49fb917d5be8f1aa1f2168132da5b8f7f Mon Sep 17 00:00:00 2001 From: Ethin Probst Date: Fri, 4 Oct 2024 14:46:25 -0500 Subject: [PATCH 3/4] Initial draft of speech synthesis to files/memory --- jni/src/main/java/com/samtupy/nvgt/TTS.java | 62 +++++++++++++++++++++ src/tts.cpp | 52 ++++++++++++++++- src/tts.h | 2 +- 3 files changed, 113 insertions(+), 3 deletions(-) diff --git a/jni/src/main/java/com/samtupy/nvgt/TTS.java b/jni/src/main/java/com/samtupy/nvgt/TTS.java index d2468f15..06f67d40 100644 --- a/jni/src/main/java/com/samtupy/nvgt/TTS.java +++ b/jni/src/main/java/com/samtupy/nvgt/TTS.java @@ -21,6 +21,14 @@ import java.util.Set; import java.util.stream.Collectors; import org.libsdl.app.SDL; +import android.os.storage.StorageManager; +import android.os.ProxyFileDescriptorCallback; +import android.os.Handler; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.nio.file.Path; +import android.os.ParcelFileDescriptor; +import java.io.IOException; public class TTS { // First the static screen reader methods. @@ -205,4 +213,58 @@ public float getVolume() { public float getPan() { return ttsPan; } + + public boolean speakToFile(String filename, String text) throws IOException { + Path p = Path.of(filename); + Path actual_path; + if (p.isAbsolute()) { + actual_path = p; + } else { + actual_path = Path.of(SDL.getContext().getFilesDir().getAbsolutePath(), filename); + } + File f = actual_path.toFile(); + f.createNewFile(); + if (!f.canWrite()) return false; + Bundle params = new Bundle(); + params.putFloat(TextToSpeech.Engine.KEY_PARAM_VOLUME, ttsVolume); + params.putFloat(TextToSpeech.Engine.KEY_PARAM_PAN, ttsPan); + if (text.length() > tts.getMaxSpeechInputLength()) { + return false; + } + return tts.synthesizeToFile(text, params, f, null) == TextToSpeech.SUCCESS; + } + + public byte[] speakToMemory(String text) throws IOException { + // It is much simpler to use the storage manager interface instead of memfd_create here + // To do: investigate whether it would be worth it creating a separate looper so we do not potentially risk blocking the main one + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + Handler handler = Handler.createAsync(SDL.getContext().getMainLooper()); + Context context = SDL.getContext(); + StorageManager manager = (StorageManager)context.getSystemService(context.STORAGE_SERVICE); + ParcelFileDescriptor pfd = manager.openProxyFileDescriptor(ParcelFileDescriptor.MODE_WRITE_ONLY, new ProxyFileDescriptorCallback() { + public void onFsync () { } + + public long onGetSize () { + return stream.size(); + } + + public int onRead (long offset, int size, byte[] data) { + return 0; + } + + public void onRelease () { } + + public int onWrite (long offset, int size, byte[] data) { + stream.writeBytes(data); + return size; + } + }, handler); + Bundle params = new Bundle(); + params.putFloat(TextToSpeech.Engine.KEY_PARAM_VOLUME, ttsVolume); + params.putFloat(TextToSpeech.Engine.KEY_PARAM_PAN, ttsPan); + if (tts.synthesizeToFile(text, params, pfd, null) == TextToSpeech.SUCCESS) { + return stream.toByteArray(); + } + return null; + } } diff --git a/src/tts.cpp b/src/tts.cpp index 64f22580..85bf0496 100644 --- a/src/tts.cpp +++ b/src/tts.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #endif char* minitrim(char* data, unsigned long* bufsize, int bitrate, int channels) { @@ -106,7 +107,9 @@ void tts_voice::setup() { midGetPitch = env->GetMethodID(TTSClass, "getPitch", "()F"); midGetPan = env->GetMethodID(TTSClass, "getPan", "()F"); midGetVolume = env->GetMethodID(TTSClass, "getVolume", "()F"); - if (!midIsActive || !midIsSpeaking || !midSpeak || !midSilence || !midGetVoice || !midSetRate || !midSetPitch || !midSetPan || !midSetVolume || !midGetVoices || !midSetVoice || !midGetMaxSpeechInputLength || !midGetPitch || !midGetPan || !midGetRate || !midGetVolume) throw Poco::Exception("One or more methods on the TTS class could not be retrieved from JNI!"); + midSpeakToFile = env->GetMethodID(TTSClass, "speakToFile", "(Ljava/lang/String;Ljava/lang/String)Z"); + midSpeakToMemory = env->GetMethodID(TTSClass, "speakToMemory", "(Ljava/lang/String;)[B"); + if (!midIsActive || !midIsSpeaking || !midSpeak || !midSilence || !midGetVoice || !midSetRate || !midSetPitch || !midSetPan || !midSetVolume || !midGetVoices || !midSetVoice || !midGetMaxSpeechInputLength || !midGetPitch || !midGetPan || !midGetRate || !midGetVolume || !midSpeakToFile || !midSpeakToMemory) throw Poco::Exception("One or more methods on the TTS class could not be retrieved from JNI!"); if (!env->CallBooleanMethod(TTSObj, midIsActive)) throw Poco::Exception("TTS engine could not be initialized!"); voice_index = 1; #else @@ -237,7 +240,26 @@ bool tts_voice::speak_to_file(const std::string& filename, const std::string& te } #elif defined(__ANDROID__) else { - return false; + jstring jtext = env->NewStringUTF(text.c_str()); + jstring jfile = env->NewStringUTF(filename.c_str()); + auto res = env->CallBooleanMethod(TTSObj, midSpeakToFile, jfile, jtext); + if (auto exc = env->ExceptionOccurred(); exc) { + // Translate into C++ exception + jclass ThrowableClass = env->FindClass("java/lang/Throwable"); + if (!ThrowableClass) throw Poco::Exception("This JVM implementation is broken: could not translate Java exception!"); + jmethodID midGetMessage = env->GetMethodID(ThrowableClass, "getMessage", "()Ljava/lang/String;"); + if (!midGetMessage) throw Poco::Exception("This JVM implementation is broken: Throwable does not have getMessage method!"); + jstring message = static_cast(env->CallObjectMethod(exc, midGetMessage)); + const char* msg_chars = env->GetStringUTFChars(message, 0); + std::string msg(msg_chars); + env->ReleaseStringUTFChars(message, msg_chars); + env->DeleteLocalRef(jfile); + env->DeleteLocalRef(jtext); + throw std::runtime_error(msg); + } + env->DeleteLocalRef(jfile); + env->DeleteLocalRef(jtext); + return res; } #endif char* ptr = minitrim(data, &bufsize, bitrate, channels); @@ -254,6 +276,7 @@ std::string tts_voice::speak_to_memory(const std::string& text) { if (text.empty()) return ""; unsigned long bufsize; char* data; + std::string data_res; if (voice_index == builtin_index) { if (samprate != 48000 || bitrate != 16 || channels != 2) { samprate = 48000; @@ -284,6 +307,31 @@ std::string tts_voice::speak_to_memory(const std::string& text) { else { return ""; // Not implemented yet. } + #elif defined(__ANDROID__) + else { + jstring jtext = env->NewStringUTF(text.c_str()); + jarray jbytes = env->CallObjectMethod(TTSObj, midSpeakToMemory, jtext); + if (auto exc = env->ExceptionOccurred(); exc) { + // Translate into C++ exception + jclass ThrowableClass = env->FindClass("java/lang/Throwable"); + if (!ThrowableClass) throw Poco::Exception("This JVM implementation is broken: could not translate Java exception!"); + jmethodID midGetMessage = env->GetMethodID(ThrowableClass, "getMessage", "()Ljava/lang/String;"); + if (!midGetMessage) throw Poco::exception("This JVM implementation is broken: Throwable does not have getMessage method!"); + jstring message = env->CallObjectMethod(exc, midGetMessage); + const char* msg_chars = env->GetStringUTFChars(message, 0); + std::string msg(msg_chars); + env->ReleaseStringUTFChars(message, msg_chars); + throw std::runtime_error(msg); + } + env->DeleteLocalRef(jtext); + data_res.reserve(env->GetArrayLength(jbytes)); + jbyte* bytes = env->GetByteArrayElements(jbytes, NULL); + for (auto i = 0; i < env->GetArrayLength(jbytes); ++i) { + data_res.append(static_cast(bytes[i])); + } + env->ReleaseByteArrayElements(jbytes, bytes, JNI_ABORT); + data = data_res.c_str(); + } #endif if (!data) return ""; char* ptr = minitrim(data, &bufsize, bitrate, channels); diff --git a/src/tts.h b/src/tts.h index 059a21c7..64ecbe4a 100644 --- a/src/tts.h +++ b/src/tts.h @@ -32,7 +32,7 @@ class tts_voice { AVTTSVoice* inst; #elifdef __ANDROID__ jclass TTSClass; - jmethodID constructor, midIsActive, midIsSpeaking, midSpeak, midSilence, midGetVoice, midSetRate, midSetPitch, midSetPan, midSetVolume, midGetVoices, midSetVoice, midGetMaxSpeechInputLength, midGetPitch, midGetPan, midGetRate, midGetVolume; + jmethodID constructor, midIsActive, midIsSpeaking, midSpeak, midSilence, midGetVoice, midSetRate, midSetPitch, midSetPan, midSetVolume, midGetVoices, midSetVoice, midGetMaxSpeechInputLength, midGetPitch, midGetPan, midGetRate, midGetVolume, midSpeakToFile, midSpeakToMemory; JNIEnv* env; jobject TTSObj; #endif From eeecd777656f9e634e6e90c41d9d4bdc178c22f8 Mon Sep 17 00:00:00 2001 From: Ethin Probst Date: Mon, 7 Oct 2024 12:33:37 -0500 Subject: [PATCH 4/4] Do not use finalizers and manually destruct the TTS object --- jni/src/main/java/com/samtupy/nvgt/TTS.java | 3 +-- src/tts.cpp | 10 ++++++++-- src/tts.h | 2 +- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/jni/src/main/java/com/samtupy/nvgt/TTS.java b/jni/src/main/java/com/samtupy/nvgt/TTS.java index 06f67d40..792a978e 100644 --- a/jni/src/main/java/com/samtupy/nvgt/TTS.java +++ b/jni/src/main/java/com/samtupy/nvgt/TTS.java @@ -169,8 +169,7 @@ public void setVolume(float volume) { ttsVolume = volume; } - @Override - public void finalize() { + public void shutdown() { if (isActive()) { tts.shutdown(); } diff --git a/src/tts.cpp b/src/tts.cpp index 85bf0496..8d9db730 100644 --- a/src/tts.cpp +++ b/src/tts.cpp @@ -67,7 +67,12 @@ tts_voice::tts_voice(const std::string& builtin_voice_name) { audioout = 0; } tts_voice::~tts_voice() { - //destroy(); + #ifdef __ANDROID__ + // Manually clean up here + if (env != NULL && TTSObj != NULL && midShutdown != NULL) { + env->CallVoidMethod(TTSObj, midShutdown); + } + #endif } void tts_voice::setup() { #ifdef _WIN32 @@ -109,7 +114,8 @@ void tts_voice::setup() { midGetVolume = env->GetMethodID(TTSClass, "getVolume", "()F"); midSpeakToFile = env->GetMethodID(TTSClass, "speakToFile", "(Ljava/lang/String;Ljava/lang/String)Z"); midSpeakToMemory = env->GetMethodID(TTSClass, "speakToMemory", "(Ljava/lang/String;)[B"); - if (!midIsActive || !midIsSpeaking || !midSpeak || !midSilence || !midGetVoice || !midSetRate || !midSetPitch || !midSetPan || !midSetVolume || !midGetVoices || !midSetVoice || !midGetMaxSpeechInputLength || !midGetPitch || !midGetPan || !midGetRate || !midGetVolume || !midSpeakToFile || !midSpeakToMemory) throw Poco::Exception("One or more methods on the TTS class could not be retrieved from JNI!"); + midShutdown = env->GetMethodID(TTSClass, "shutdown", "()V"); + if (!midIsActive || !midIsSpeaking || !midSpeak || !midSilence || !midGetVoice || !midSetRate || !midSetPitch || !midSetPan || !midSetVolume || !midGetVoices || !midSetVoice || !midGetMaxSpeechInputLength || !midGetPitch || !midGetPan || !midGetRate || !midGetVolume || !midSpeakToFile || !midSpeakToMemory || !midShutdown) throw Poco::Exception("One or more methods on the TTS class could not be retrieved from JNI!"); if (!env->CallBooleanMethod(TTSObj, midIsActive)) throw Poco::Exception("TTS engine could not be initialized!"); voice_index = 1; #else diff --git a/src/tts.h b/src/tts.h index 64ecbe4a..83085a95 100644 --- a/src/tts.h +++ b/src/tts.h @@ -32,7 +32,7 @@ class tts_voice { AVTTSVoice* inst; #elifdef __ANDROID__ jclass TTSClass; - jmethodID constructor, midIsActive, midIsSpeaking, midSpeak, midSilence, midGetVoice, midSetRate, midSetPitch, midSetPan, midSetVolume, midGetVoices, midSetVoice, midGetMaxSpeechInputLength, midGetPitch, midGetPan, midGetRate, midGetVolume, midSpeakToFile, midSpeakToMemory; + jmethodID constructor, midIsActive, midIsSpeaking, midSpeak, midSilence, midGetVoice, midSetRate, midSetPitch, midSetPan, midSetVolume, midGetVoices, midSetVoice, midGetMaxSpeechInputLength, midGetPitch, midGetPan, midGetRate, midGetVolume, midSpeakToFile, midSpeakToMemory, midShutdown; JNIEnv* env; jobject TTSObj; #endif