diff --git a/CMakeLists.txt b/CMakeLists.txt index 3e9e857..1043e36 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -62,8 +62,8 @@ SET(_TESTLINKS pkmnLib CreatureLibCore CreatureLibLibrary CreatureLibBattling) if (SCRIPT_PROVIDER STREQUAL "angelscript") message(STATUS "Creating angelscript implementation.") - file(GLOB_RECURSE ANGELSCRIPT_SRC_FILES "src/AngelScript/*.cpp" "src/AngelScript/*.hpp") - add_library(pkmnLib-angelscript SHARED ${ANGELSCRIPT_SRC_FILES}) + file(GLOB_RECURSE ANGELSCRIPT_SRC_FILES "src/AngelScript/*.cpp" "src/AngelScript/*.hpp" "extern/angelscript_addons/*.cpp" "extern/angelscript_addons/*.h") + add_library(pkmnLib-angelscript SHARED ${ANGELSCRIPT_SRC_FILES} ) SET(SCRIPT_PROVIDER_LIB_NAME "pkmnLib-angelscript") SET(_LINKS ${_LINKS} angelscript) endif() diff --git a/conanfile.py b/conanfile.py index be38c7a..f9d89db 100644 --- a/conanfile.py +++ b/conanfile.py @@ -13,7 +13,7 @@ class PkmnLibConan(ConanFile): exports_sources = "*" compiler = "clang" requires = \ - "CreatureLib/144d17d859e134c17cdb45c32f4a7e4f68eeb7a0@creaturelib/master", \ + "CreatureLib/779f0b08cfc7d2b19ee0bd27c61a28807172bc1f@creaturelib/master", \ "AngelScript/2.34@AngelScript/Deukhoofd" def build(self): diff --git a/extern/angelscript_addons/scriptarray/scriptarray.cpp b/extern/angelscript_addons/scriptarray/scriptarray.cpp new file mode 100644 index 0000000..d7ccda3 --- /dev/null +++ b/extern/angelscript_addons/scriptarray/scriptarray.cpp @@ -0,0 +1,2177 @@ +#include +#include +#include +#include +#include // sprintf +#include + +#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 + // 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 factory"); + 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(uint index)", asMETHODPR(CScriptArray, At, (asUINT), void*), asCALL_THISCALL); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "const T &opIndex(uint index) const", asMETHODPR(CScriptArray, At, (asUINT) 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 insertAt(uint index, const T&in value)", asMETHODPR(CScriptArray, InsertAt, (asUINT, void *), void), asCALL_THISCALL); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "void insertAt(uint index, const array& arr)", asMETHODPR(CScriptArray, InsertAt, (asUINT, const CScriptArray &), void), asCALL_THISCALL); assert(r >= 0); + r = engine->RegisterObjectMethod("array", "void insertLast(const T&in value)", asMETHOD(CScriptArray, InsertLast), asCALL_THISCALL); assert(r >= 0); + r = engine->RegisterObjectMethod("array", "void removeAt(uint index)", asMETHOD(CScriptArray, RemoveAt), asCALL_THISCALL); assert(r >= 0); + r = engine->RegisterObjectMethod("array", "void removeLast()", asMETHOD(CScriptArray, RemoveLast), asCALL_THISCALL); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "void removeRange(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 sortAsc()", asMETHODPR(CScriptArray, SortAsc, (), void), asCALL_THISCALL); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "void sortAsc(uint startAt, uint count)", asMETHODPR(CScriptArray, SortAsc, (asUINT, asUINT), void), asCALL_THISCALL); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "void sortDesc()", asMETHODPR(CScriptArray, SortDesc, (), void), asCALL_THISCALL); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "void sortDesc(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 findByRef(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 findByRef(const T&in value, uint startAt = 0) const" + r = engine->RegisterObjectMethod("array", "int findByRef(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 isEmpty() 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)"); + 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)", asMETHOD(CScriptArray, InsertLast), 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); + 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(asUINT 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) ) + objType->GetEngine()->AssignScriptObject(ptr, value, objType->GetSubType()); + 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::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(asUINT index) const +{ + if( buffer == 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(asUINT 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, asIScriptContext *ctx, SArrayCache *cache) +{ + 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(signed char); + case asTYPEID_UINT8: return COMPARE(unsigned char); + case asTYPEID_INT16: return COMPARE(signed short); + case asTYPEID_UINT16: return COMPARE(unsigned short); + case asTYPEID_INT32: return COMPARE(signed int); + case asTYPEID_UINT32: return COMPARE(unsigned int); + case asTYPEID_FLOAT: return COMPARE(float); + case asTYPEID_DOUBLE: return COMPARE(double); + default: return COMPARE(signed int); // All enums fall in this case + #undef COMPARE + } + } + else + { + int r = 0; + + if( subTypeId & asTYPEID_OBJHANDLE ) + { + // Allow sort to work even if the array contains null handles + if( *(void**)a == 0 ) return true; + if( *(void**)b == 0 ) return false; + } + + // Execute object opCmp + 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; +} + +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(signed char); + case asTYPEID_UINT8: return COMPARE(unsigned char); + case asTYPEID_INT16: return COMPARE(signed short); + case asTYPEID_UINT16: return COMPARE(unsigned short); + case asTYPEID_INT32: return COMPARE(signed int); + case asTYPEID_UINT32: return COMPARE(unsigned int); + case asTYPEID_FLOAT: return COMPARE(float); + case asTYPEID_DOUBLE: return COMPARE(double); + default: return COMPARE(signed int); // All enums fall here + #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 + sprintf(tmp, "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 + sprintf(tmp, "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 +// 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 + sprintf(tmp, "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 + sprintf(tmp, "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; + } + + asBYTE tmp[16]; + 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 ) + { + cmpContext = objType->GetEngine()->RequestContext(); + } + } + + // Insertion sort + 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, cmpContext, cache) ) + { + Copy(GetArrayItemPointer(j + 1), GetArrayItemPointer(j)); + j--; + } + + Copy(GetArrayItemPointer(j + 1), tmp); + } + + if( cmpContext ) + { + if( isNested ) + { + asEContextState state = cmpContext->GetState(); + cmpContext->PopState(); + if( state == asEXECUTION_ABORTED ) + cmpContext->Abort(); + } + else + objType->GetEngine()->ReturnContext(cmpContext); + } +} + +// 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; + } + + asBYTE tmp[16]; + 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(); + + // Insertion sort + for (asUINT i = start + 1; i < end; i++) + { + Copy(tmp, GetArrayItemPointer(i)); + + asUINT j = i - 1; + + while (j != 0xFFFFFFFF && j >= start ) + { + cmpContext->Prepare(func); + cmpContext->SetArgAddress(0, GetDataPointer(tmp)); + cmpContext->SetArgAddress(1, At(j)); + int r = cmpContext->Execute(); + if (r != asEXECUTION_FINISHED) + break; + if (*(bool*)(cmpContext->GetAddressOfReturnValue())) + { + Copy(GetArrayItemPointer(j + 1), GetArrayItemPointer(j)); + j--; + } + else + break; + } + + Copy(GetArrayItemPointer(j + 1), tmp); + } + + 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(); + 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) +{ + asUINT index = gen->GetArgDWord(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(uint index)", asFUNCTION(ScriptArrayAt_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "const T &opIndex(uint 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 insertAt(uint index, const T&in value)", asFUNCTION(ScriptArrayInsertAt_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "void insertAt(uint index, const array& arr)", asFUNCTION(ScriptArrayInsertAtArray_Generic), asCALL_GENERIC); assert(r >= 0); + r = engine->RegisterObjectMethod("array", "void insertLast(const T&in value)", asFUNCTION(ScriptArrayInsertLast_Generic), asCALL_GENERIC); assert(r >= 0); + r = engine->RegisterObjectMethod("array", "void removeAt(uint index)", asFUNCTION(ScriptArrayRemoveAt_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "void removeLast()", asFUNCTION(ScriptArrayRemoveLast_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "void removeRange(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 sortAsc()", asFUNCTION(ScriptArraySortAsc_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "void sortAsc(uint startAt, uint count)", asFUNCTION(ScriptArraySortAsc2_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "void sortDesc()", asFUNCTION(ScriptArraySortDesc_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "void sortDesc(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 findByRef(const T&in if_handle_then_const value) const", asFUNCTION(ScriptArrayFindByRef_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("array", "int findByRef(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 isEmpty() const", asFUNCTION(ScriptArrayIsEmpty_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterFuncdef("bool array::less(const T&in a, const T&in b)"); + 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/extern/angelscript_addons/scriptarray/scriptarray.h b/extern/angelscript_addons/scriptarray/scriptarray.h new file mode 100644 index 0000000..41b34dd --- /dev/null +++ b/extern/angelscript_addons/scriptarray/scriptarray.h @@ -0,0 +1,142 @@ +#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(asUINT index); + const void *At(asUINT 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(asUINT 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 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, asIScriptContext *ctx, SArrayCache *cache); + void *GetArrayItemPointer(int index); + void *GetDataPointer(void *buffer); + void Copy(void *dst, void *src); + 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/extern/angelscript_addons/scripthelper/scripthelper.cpp b/extern/angelscript_addons/scripthelper/scripthelper.cpp new file mode 100644 index 0000000..4af4fde --- /dev/null +++ b/extern/angelscript_addons/scripthelper/scripthelper.cpp @@ -0,0 +1,987 @@ +#include +#include "scripthelper.h" +#include +#include +#include +#include +#include + +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() << "\"\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)).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)).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)).c_str() << "\"\n"; + else if( beh == asBEHAVE_DESTRUCT ) + // Prefix 'void' and remove ~ + strm << "objbeh \"" << typeDecl.c_str() << "\" " << beh << " \"void " << Escape::Quotes(func->GetDeclaration(false)).c_str()+1 << "\"\n"; + else + strm << "objbeh \"" << typeDecl.c_str() << "\" " << beh << " \"" << Escape::Quotes(func->GetDeclaration(false)).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)).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()).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->GetStringFactoryReturnTypeId(&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 << "func: " << function->GetDeclaration() << "\n"; + text << "modl: " << (function->GetModuleName() ? function->GetModuleName() : "") << "\n"; + text << "sect: " << (function->GetScriptSectionName() ? function->GetScriptSectionName() : "") << "\n"; + text << "line: " << ctx->GetExceptionLineNumber() << "\n"; + text << "desc: " << ctx->GetExceptionString() << "\n"; + + if( showStack ) + { + 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); +} + +void RegisterExceptionRoutines(asIScriptEngine *engine) +{ + int r; + + // The string type must be available + assert(engine->GetTypeInfoByDecl("string")); + + r = engine->RegisterGlobalFunction("void throw(const string &in)", asFUNCTION(ScriptThrow), asCALL_CDECL); assert(r >= 0); + r = engine->RegisterGlobalFunction("string getExceptionInfo()", asFUNCTION(ScriptGetExceptionInfo), asCALL_CDECL); assert(r >= 0); +} + +END_AS_NAMESPACE diff --git a/extern/angelscript_addons/scripthelper/scripthelper.h b/extern/angelscript_addons/scripthelper/scripthelper.h new file mode 100644 index 0000000..dc3e0b8 --- /dev/null +++ b/extern/angelscript_addons/scripthelper/scripthelper.h @@ -0,0 +1,53 @@ +#ifndef SCRIPTHELPER_H +#define SCRIPTHELPER_H + +#include +#include + +#ifndef ANGELSCRIPT_H +// Avoid having to inform include path if header is already include before +#include +#endif + + +BEGIN_AS_NAMESPACE + +// Compare relation between two objects of the same type +int CompareRelation(asIScriptEngine *engine, void *lobj, void *robj, int typeId, int &result); + +// Compare equality between two objects of the same type +int CompareEquality(asIScriptEngine *engine, void *lobj, void *robj, int typeId, bool &result); + +// Compile and execute simple statements +// The module is optional. If given the statements can access the entities compiled in the module. +// The caller can optionally provide its own context, for example if a context should be reused. +int ExecuteString(asIScriptEngine *engine, const char *code, asIScriptModule *mod = 0, asIScriptContext *ctx = 0); + +// Compile and execute simple statements with option of return value +// The module is optional. If given the statements can access the entitites compiled in the module. +// The caller can optionally provide its own context, for example if a context should be reused. +int ExecuteString(asIScriptEngine *engine, const char *code, void *ret, int retTypeId, asIScriptModule *mod = 0, asIScriptContext *ctx = 0); + +// Write the registered application interface to a file for an offline compiler. +// The format is compatible with the offline compiler in /sdk/samples/asbuild/. +int WriteConfigToFile(asIScriptEngine *engine, const char *filename); + +// Write the registered application interface to a text stream. +int WriteConfigToStream(asIScriptEngine *engine, std::ostream &strm); + +// Loads an interface from a text stream and configures the engine with it. This will not +// set the correct function pointers, so it is not possible to use this engine to execute +// scripts, but it can be used to compile scripts and save the byte code. +int ConfigEngineFromStream(asIScriptEngine *engine, std::istream &strm, const char *nameOfStream = "config", asIStringFactory *stringFactory = 0); + +// Format the details of the script exception into a human readable text +std::string GetExceptionInfo(asIScriptContext *ctx, bool showStack = false); + +// Register the exception routines +// 'void throw(const string &msg)' +// 'string getExceptionInfo()' +void RegisterExceptionRoutines(asIScriptEngine *engine); + +END_AS_NAMESPACE + +#endif diff --git a/extern/angelscript_addons/scriptstdstring/scriptstdstring.cpp b/extern/angelscript_addons/scriptstdstring/scriptstdstring.cpp new file mode 100644 index 0000000..bcba250 --- /dev/null +++ b/extern/angelscript_addons/scriptstdstring/scriptstdstring.cpp @@ -0,0 +1,1364 @@ +#include "scriptstdstring.h" +#include // assert() +#include // std::stringstream +#include // strstr() +#include // sprintf() +#include // strtod() +#ifndef __psp2__ + #include // setlocale() +#endif + +using namespace std; + +// 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) + +#ifdef AS_CAN_USE_CPP11 +// The string factory doesn't need to keep a specific order in the +// cache, so the unordered_map is faster than the ordinary map +#include // std::unordered_map +BEGIN_AS_NAMESPACE +typedef unordered_map map_t; +END_AS_NAMESPACE +#else +#include // std::map +BEGIN_AS_NAMESPACE +typedef map map_t; +END_AS_NAMESPACE +#endif + +class CStdStringFactory : public asIStringFactory +{ +public: + CStdStringFactory() {} + ~CStdStringFactory() + { + // The script engine must release each string + // constant that it has requested + assert(stringCache.size() == 0); + } + + const void *GetStringConstant(const char *data, asUINT length) + { + // The string factory might be modified from multiple + // threads, so it is necessary to use a mutex. + asAcquireExclusiveLock(); + + string str(data, length); + map_t::iterator it = stringCache.find(str); + if (it != stringCache.end()) + it->second++; + else + it = stringCache.insert(map_t::value_type(str, 1)).first; + + asReleaseExclusiveLock(); + + return reinterpret_cast(&it->first); + } + + int ReleaseStringConstant(const void *str) + { + if (str == 0) + return asERROR; + + int ret = asSUCCESS; + + // The string factory might be modified from multiple + // threads, so it is necessary to use a mutex. + asAcquireExclusiveLock(); + + map_t::iterator it = stringCache.find(*reinterpret_cast(str)); + if (it == stringCache.end()) + ret = asERROR; + else + { + it->second--; + if (it->second == 0) + stringCache.erase(it); + } + + asReleaseExclusiveLock(); + + return ret; + } + + int GetRawStringData(const void *str, char *data, asUINT *length) const + { + if (str == 0) + return asERROR; + + if (length) + *length = (asUINT)reinterpret_cast(str)->length(); + + if (data) + memcpy(data, reinterpret_cast(str)->c_str(), reinterpret_cast(str)->length()); + + return asSUCCESS; + } + + // THe access to the string cache is protected with the common mutex provided by AngelScript + map_t stringCache; +}; + +static CStdStringFactory *stringFactory = 0; + +// TODO: Make this public so the application can also use the string +// factory and share the string constants if so desired, or to +// monitor the size of the string factory cache. +CStdStringFactory *GetStdStringFactorySingleton() +{ + if( stringFactory == 0 ) + { + // The following instance will be destroyed by the global + // CStdStringFactoryCleaner instance upon application shutdown + stringFactory = new CStdStringFactory(); + } + return stringFactory; +} + +class CStdStringFactoryCleaner +{ +public: + ~CStdStringFactoryCleaner() + { + if (stringFactory) + { + // Only delete the string factory if the stringCache is empty + // If it is not empty, it means that someone might still attempt + // to release string constants, so if we delete the string factory + // the application might crash. Not deleting the cache would + // lead to a memory leak, but since this is only happens when the + // application is shutting down anyway, it is not important. + if (stringFactory->stringCache.empty()) + { + delete stringFactory; + stringFactory = 0; + } + } + } +}; + +static CStdStringFactoryCleaner cleaner; + + +static void ConstructString(string *thisPointer) +{ + new(thisPointer) string(); +} + +static void CopyConstructString(const string &other, string *thisPointer) +{ + new(thisPointer) string(other); +} + +static void DestructString(string *thisPointer) +{ + thisPointer->~string(); +} + +static string &AddAssignStringToString(const string &str, string &dest) +{ + // We don't register the method directly because some compilers + // and standard libraries inline the definition, resulting in the + // linker being unable to find the declaration. + // Example: CLang/LLVM with XCode 4.3 on OSX 10.7 + dest += str; + return dest; +} + +// bool string::isEmpty() +// bool string::empty() // if AS_USE_STLNAMES == 1 +static bool StringIsEmpty(const string &str) +{ + // We don't register the method directly because some compilers + // and standard libraries inline the definition, resulting in the + // linker being unable to find the declaration + // Example: CLang/LLVM with XCode 4.3 on OSX 10.7 + return str.empty(); +} + +static string &AssignUInt64ToString(asQWORD i, string &dest) +{ + ostringstream stream; + stream << i; + dest = stream.str(); + return dest; +} + +static string &AddAssignUInt64ToString(asQWORD i, string &dest) +{ + ostringstream stream; + stream << i; + dest += stream.str(); + return dest; +} + +static string AddStringUInt64(const string &str, asQWORD i) +{ + ostringstream stream; + stream << i; + return str + stream.str(); +} + +static string AddInt64String(asINT64 i, const string &str) +{ + ostringstream stream; + stream << i; + return stream.str() + str; +} + +static string &AssignInt64ToString(asINT64 i, string &dest) +{ + ostringstream stream; + stream << i; + dest = stream.str(); + return dest; +} + +static string &AddAssignInt64ToString(asINT64 i, string &dest) +{ + ostringstream stream; + stream << i; + dest += stream.str(); + return dest; +} + +static string AddStringInt64(const string &str, asINT64 i) +{ + ostringstream stream; + stream << i; + return str + stream.str(); +} + +static string AddUInt64String(asQWORD i, const string &str) +{ + ostringstream stream; + stream << i; + return stream.str() + str; +} + +static string &AssignDoubleToString(double f, string &dest) +{ + ostringstream stream; + stream << f; + dest = stream.str(); + return dest; +} + +static string &AddAssignDoubleToString(double f, string &dest) +{ + ostringstream stream; + stream << f; + dest += stream.str(); + return dest; +} + +static string &AssignFloatToString(float f, string &dest) +{ + ostringstream stream; + stream << f; + dest = stream.str(); + return dest; +} + +static string &AddAssignFloatToString(float f, string &dest) +{ + ostringstream stream; + stream << f; + dest += stream.str(); + return dest; +} + +static string &AssignBoolToString(bool b, string &dest) +{ + ostringstream stream; + stream << (b ? "true" : "false"); + dest = stream.str(); + return dest; +} + +static string &AddAssignBoolToString(bool b, string &dest) +{ + ostringstream stream; + stream << (b ? "true" : "false"); + dest += stream.str(); + return dest; +} + +static string AddStringDouble(const string &str, double f) +{ + ostringstream stream; + stream << f; + return str + stream.str(); +} + +static string AddDoubleString(double f, const string &str) +{ + ostringstream stream; + stream << f; + return stream.str() + str; +} + +static string AddStringFloat(const string &str, float f) +{ + ostringstream stream; + stream << f; + return str + stream.str(); +} + +static string AddFloatString(float f, const string &str) +{ + ostringstream stream; + stream << f; + return stream.str() + str; +} + +static string AddStringBool(const string &str, bool b) +{ + ostringstream stream; + stream << (b ? "true" : "false"); + return str + stream.str(); +} + +static string AddBoolString(bool b, const string &str) +{ + ostringstream stream; + stream << (b ? "true" : "false"); + return stream.str() + str; +} + +static char *StringCharAt(unsigned int i, string &str) +{ + if( i >= str.size() ) + { + // Set a script exception + asIScriptContext *ctx = asGetActiveContext(); + ctx->SetException("Out of range"); + + // Return a null pointer + return 0; + } + + return &str[i]; +} + +// AngelScript signature: +// int string::opCmp(const string &in) const +static int StringCmp(const string &a, const string &b) +{ + int cmp = 0; + if( a < b ) cmp = -1; + else if( a > b ) cmp = 1; + return cmp; +} + +// This function returns the index of the first position where the substring +// exists in the input string. If the substring doesn't exist in the input +// string -1 is returned. +// +// AngelScript signature: +// int string::findFirst(const string &in sub, uint start = 0) const +static int StringFindFirst(const string &sub, asUINT start, const string &str) +{ + // We don't register the method directly because the argument types change between 32bit and 64bit platforms + return (int)str.find(sub, (size_t)(start < 0 ? string::npos : start)); +} + +// This function returns the index of the first position where the one of the bytes in substring +// exists in the input string. If the characters in the substring doesn't exist in the input +// string -1 is returned. +// +// AngelScript signature: +// int string::findFirstOf(const string &in sub, uint start = 0) const +static int StringFindFirstOf(const string &sub, asUINT start, const string &str) +{ + // We don't register the method directly because the argument types change between 32bit and 64bit platforms + return (int)str.find_first_of(sub, (size_t)(start < 0 ? string::npos : start)); +} + +// This function returns the index of the last position where the one of the bytes in substring +// exists in the input string. If the characters in the substring doesn't exist in the input +// string -1 is returned. +// +// AngelScript signature: +// int string::findLastOf(const string &in sub, uint start = -1) const +static int StringFindLastOf(const string &sub, asUINT start, const string &str) +{ + // We don't register the method directly because the argument types change between 32bit and 64bit platforms + return (int)str.find_last_of(sub, (size_t)(start < 0 ? string::npos : start)); +} + +// This function returns the index of the first position where a byte other than those in substring +// exists in the input string. If none is found -1 is returned. +// +// AngelScript signature: +// int string::findFirstNotOf(const string &in sub, uint start = 0) const +static int StringFindFirstNotOf(const string &sub, asUINT start, const string &str) +{ + // We don't register the method directly because the argument types change between 32bit and 64bit platforms + return (int)str.find_first_not_of(sub, (size_t)(start < 0 ? string::npos : start)); +} + +// This function returns the index of the last position where a byte other than those in substring +// exists in the input string. If none is found -1 is returned. +// +// AngelScript signature: +// int string::findLastNotOf(const string &in sub, uint start = -1) const +static int StringFindLastNotOf(const string &sub, asUINT start, const string &str) +{ + // We don't register the method directly because the argument types change between 32bit and 64bit platforms + return (int)str.find_last_not_of(sub, (size_t)(start < 0 ? string::npos : start)); +} + +// This function returns the index of the last position where the substring +// exists in the input string. If the substring doesn't exist in the input +// string -1 is returned. +// +// AngelScript signature: +// int string::findLast(const string &in sub, int start = -1) const +static int StringFindLast(const string &sub, int start, const string &str) +{ + // We don't register the method directly because the argument types change between 32bit and 64bit platforms + return (int)str.rfind(sub, (size_t)(start < 0 ? string::npos : start)); +} + +// AngelScript signature: +// void string::insert(uint pos, const string &in other) +static void StringInsert(unsigned int pos, const string &other, string &str) +{ + // We don't register the method directly because the argument types change between 32bit and 64bit platforms + str.insert(pos, other); +} + +// AngelScript signature: +// void string::erase(uint pos, int count = -1) +static void StringErase(unsigned int pos, int count, string &str) +{ + // We don't register the method directly because the argument types change between 32bit and 64bit platforms + str.erase(pos, (size_t)(count < 0 ? string::npos : count)); +} + + +// AngelScript signature: +// uint string::length() const +static asUINT StringLength(const string &str) +{ + // We don't register the method directly because the return type changes between 32bit and 64bit platforms + return (asUINT)str.length(); +} + + +// AngelScript signature: +// void string::resize(uint l) +static void StringResize(asUINT l, string &str) +{ + // We don't register the method directly because the argument types change between 32bit and 64bit platforms + str.resize(l); +} + +// AngelScript signature: +// string formatInt(int64 val, const string &in options, uint width) +static string formatInt(asINT64 value, const string &options, asUINT width) +{ + bool leftJustify = options.find("l") != string::npos; + bool padWithZero = options.find("0") != string::npos; + bool alwaysSign = options.find("+") != string::npos; + bool spaceOnSign = options.find(" ") != string::npos; + bool hexSmall = options.find("h") != string::npos; + bool hexLarge = options.find("H") != string::npos; + + string fmt = "%"; + if( leftJustify ) fmt += "-"; + if( alwaysSign ) fmt += "+"; + if( spaceOnSign ) fmt += " "; + if( padWithZero ) fmt += "0"; + +#ifdef _WIN32 + fmt += "*I64"; +#else +#ifdef _LP64 + fmt += "*l"; +#else + fmt += "*ll"; +#endif +#endif + + if( hexSmall ) fmt += "x"; + else if( hexLarge ) fmt += "X"; + else fmt += "d"; + + string buf; + buf.resize(width+30); +#if _MSC_VER >= 1400 && !defined(__S3E__) + // MSVC 8.0 / 2005 or newer + sprintf_s(&buf[0], buf.size(), fmt.c_str(), width, value); +#else + sprintf(&buf[0], fmt.c_str(), width, value); +#endif + buf.resize(strlen(&buf[0])); + + return buf; +} + +// AngelScript signature: +// string formatUInt(uint64 val, const string &in options, uint width) +static string formatUInt(asQWORD value, const string &options, asUINT width) +{ + bool leftJustify = options.find("l") != string::npos; + bool padWithZero = options.find("0") != string::npos; + bool alwaysSign = options.find("+") != string::npos; + bool spaceOnSign = options.find(" ") != string::npos; + bool hexSmall = options.find("h") != string::npos; + bool hexLarge = options.find("H") != string::npos; + + string fmt = "%"; + if( leftJustify ) fmt += "-"; + if( alwaysSign ) fmt += "+"; + if( spaceOnSign ) fmt += " "; + if( padWithZero ) fmt += "0"; + +#ifdef _WIN32 + fmt += "*I64"; +#else +#ifdef _LP64 + fmt += "*l"; +#else + fmt += "*ll"; +#endif +#endif + + if( hexSmall ) fmt += "x"; + else if( hexLarge ) fmt += "X"; + else fmt += "u"; + + string buf; + buf.resize(width+30); +#if _MSC_VER >= 1400 && !defined(__S3E__) + // MSVC 8.0 / 2005 or newer + sprintf_s(&buf[0], buf.size(), fmt.c_str(), width, value); +#else + sprintf(&buf[0], fmt.c_str(), width, value); +#endif + buf.resize(strlen(&buf[0])); + + return buf; +} + +// AngelScript signature: +// string formatFloat(double val, const string &in options, uint width, uint precision) +static string formatFloat(double value, const string &options, asUINT width, asUINT precision) +{ + bool leftJustify = options.find("l") != string::npos; + bool padWithZero = options.find("0") != string::npos; + bool alwaysSign = options.find("+") != string::npos; + bool spaceOnSign = options.find(" ") != string::npos; + bool expSmall = options.find("e") != string::npos; + bool expLarge = options.find("E") != string::npos; + + string fmt = "%"; + if( leftJustify ) fmt += "-"; + if( alwaysSign ) fmt += "+"; + if( spaceOnSign ) fmt += " "; + if( padWithZero ) fmt += "0"; + + fmt += "*.*"; + + if( expSmall ) fmt += "e"; + else if( expLarge ) fmt += "E"; + else fmt += "f"; + + string buf; + buf.resize(width+precision+50); +#if _MSC_VER >= 1400 && !defined(__S3E__) + // MSVC 8.0 / 2005 or newer + sprintf_s(&buf[0], buf.size(), fmt.c_str(), width, precision, value); +#else + sprintf(&buf[0], fmt.c_str(), width, precision, value); +#endif + buf.resize(strlen(&buf[0])); + + return buf; +} + +// AngelScript signature: +// int64 parseInt(const string &in val, uint base = 10, uint &out byteCount = 0) +static asINT64 parseInt(const string &val, asUINT base, asUINT *byteCount) +{ + // Only accept base 10 and 16 + if( base != 10 && base != 16 ) + { + if( byteCount ) *byteCount = 0; + return 0; + } + + const char *end = &val[0]; + + // Determine the sign + bool sign = false; + if( *end == '-' ) + { + sign = true; + end++; + } + else if( *end == '+' ) + end++; + + asINT64 res = 0; + if( base == 10 ) + { + while( *end >= '0' && *end <= '9' ) + { + res *= 10; + res += *end++ - '0'; + } + } + else if( base == 16 ) + { + while( (*end >= '0' && *end <= '9') || + (*end >= 'a' && *end <= 'f') || + (*end >= 'A' && *end <= 'F') ) + { + res *= 16; + if( *end >= '0' && *end <= '9' ) + res += *end++ - '0'; + else if( *end >= 'a' && *end <= 'f' ) + res += *end++ - 'a' + 10; + else if( *end >= 'A' && *end <= 'F' ) + res += *end++ - 'A' + 10; + } + } + + if( byteCount ) + *byteCount = asUINT(size_t(end - val.c_str())); + + if( sign ) + res = -res; + + return res; +} + +// AngelScript signature: +// uint64 parseUInt(const string &in val, uint base = 10, uint &out byteCount = 0) +static asQWORD parseUInt(const string &val, asUINT base, asUINT *byteCount) +{ + // Only accept base 10 and 16 + if (base != 10 && base != 16) + { + if (byteCount) *byteCount = 0; + return 0; + } + + const char *end = &val[0]; + + asQWORD res = 0; + if (base == 10) + { + while (*end >= '0' && *end <= '9') + { + res *= 10; + res += *end++ - '0'; + } + } + else if (base == 16) + { + while ((*end >= '0' && *end <= '9') || + (*end >= 'a' && *end <= 'f') || + (*end >= 'A' && *end <= 'F')) + { + res *= 16; + if (*end >= '0' && *end <= '9') + res += *end++ - '0'; + else if (*end >= 'a' && *end <= 'f') + res += *end++ - 'a' + 10; + else if (*end >= 'A' && *end <= 'F') + res += *end++ - 'A' + 10; + } + } + + if (byteCount) + *byteCount = asUINT(size_t(end - val.c_str())); + + return res; +} + +// AngelScript signature: +// double parseFloat(const string &in val, uint &out byteCount = 0) +double parseFloat(const string &val, asUINT *byteCount) +{ + char *end; + + // WinCE doesn't have setlocale. Some quick testing on my current platform + // still manages to parse the numbers such as "3.14" even if the decimal for the + // locale is ",". +#if !defined(_WIN32_WCE) && !defined(ANDROID) && !defined(__psp2__) + // Set the locale to C so that we are guaranteed to parse the float value correctly + char *tmp = setlocale(LC_NUMERIC, 0); + string orig = tmp ? tmp : "C"; + setlocale(LC_NUMERIC, "C"); +#endif + + double res = strtod(val.c_str(), &end); + +#if !defined(_WIN32_WCE) && !defined(ANDROID) && !defined(__psp2__) + // Restore the locale + setlocale(LC_NUMERIC, orig.c_str()); +#endif + + if( byteCount ) + *byteCount = asUINT(size_t(end - val.c_str())); + + return res; +} + +// This function returns a string containing the substring of the input string +// determined by the starting index and count of characters. +// +// AngelScript signature: +// string string::substr(uint start = 0, int count = -1) const +static string StringSubString(asUINT start, int count, const string &str) +{ + // Check for out-of-bounds + string ret; + if( start < str.length() && count != 0 ) + ret = str.substr(start, (size_t)(count < 0 ? string::npos : count)); + + return ret; +} + +// String equality comparison. +// Returns true iff lhs is equal to rhs. +// +// For some reason gcc 4.7 has difficulties resolving the +// asFUNCTIONPR(operator==, (const string &, const string &) +// makro, so this wrapper was introduced as work around. +static bool StringEquals(const std::string& lhs, const std::string& rhs) +{ + return lhs == rhs; +} + +void RegisterStdString_Native(asIScriptEngine *engine) +{ + int r = 0; + UNUSED_VAR(r); + + // Register the string type +#if AS_CAN_USE_CPP11 + // With C++11 it is possible to use asGetTypeTraits to automatically determine the correct flags to use + r = engine->RegisterObjectType("string", sizeof(string), asOBJ_VALUE | asGetTypeTraits()); assert( r >= 0 ); +#else + r = engine->RegisterObjectType("string", sizeof(string), asOBJ_VALUE | asOBJ_APP_CLASS_CDAK); assert( r >= 0 ); +#endif + + r = engine->RegisterStringFactory("string", GetStdStringFactorySingleton()); + + // Register the object operator overloads + r = engine->RegisterObjectBehaviour("string", asBEHAVE_CONSTRUCT, "void f()", asFUNCTION(ConstructString), asCALL_CDECL_OBJLAST); assert( r >= 0 ); + r = engine->RegisterObjectBehaviour("string", asBEHAVE_CONSTRUCT, "void f(const string &in)", asFUNCTION(CopyConstructString), asCALL_CDECL_OBJLAST); assert( r >= 0 ); + r = engine->RegisterObjectBehaviour("string", asBEHAVE_DESTRUCT, "void f()", asFUNCTION(DestructString), asCALL_CDECL_OBJLAST); assert( r >= 0 ); + r = engine->RegisterObjectMethod("string", "string &opAssign(const string &in)", asMETHODPR(string, operator =, (const string&), string&), asCALL_THISCALL); assert( r >= 0 ); + // Need to use a wrapper on Mac OS X 10.7/XCode 4.3 and CLang/LLVM, otherwise the linker fails + r = engine->RegisterObjectMethod("string", "string &opAddAssign(const string &in)", asFUNCTION(AddAssignStringToString), asCALL_CDECL_OBJLAST); assert( r >= 0 ); +// r = engine->RegisterObjectMethod("string", "string &opAddAssign(const string &in)", asMETHODPR(string, operator+=, (const string&), string&), asCALL_THISCALL); assert( r >= 0 ); + + // Need to use a wrapper for operator== otherwise gcc 4.7 fails to compile + r = engine->RegisterObjectMethod("string", "bool opEquals(const string &in) const", asFUNCTIONPR(StringEquals, (const string &, const string &), bool), asCALL_CDECL_OBJFIRST); assert( r >= 0 ); + r = engine->RegisterObjectMethod("string", "int opCmp(const string &in) const", asFUNCTION(StringCmp), asCALL_CDECL_OBJFIRST); assert( r >= 0 ); + r = engine->RegisterObjectMethod("string", "string opAdd(const string &in) const", asFUNCTIONPR(operator +, (const string &, const string &), string), asCALL_CDECL_OBJFIRST); assert( r >= 0 ); + + // The string length can be accessed through methods or through virtual property + // TODO: Register as size() for consistency with other types +#if AS_USE_ACCESSORS != 1 + r = engine->RegisterObjectMethod("string", "uint length() const", asFUNCTION(StringLength), asCALL_CDECL_OBJLAST); assert( r >= 0 ); +#endif + r = engine->RegisterObjectMethod("string", "void resize(uint)", asFUNCTION(StringResize), asCALL_CDECL_OBJLAST); assert( r >= 0 ); +#if AS_USE_STLNAMES != 1 && AS_USE_ACCESSORS == 1 + // Don't register these if STL names is used, as they conflict with the method size() + r = engine->RegisterObjectMethod("string", "uint get_length() const property", asFUNCTION(StringLength), asCALL_CDECL_OBJLAST); assert( r >= 0 ); + r = engine->RegisterObjectMethod("string", "void set_length(uint) property", asFUNCTION(StringResize), asCALL_CDECL_OBJLAST); assert( r >= 0 ); +#endif + // Need to use a wrapper on Mac OS X 10.7/XCode 4.3 and CLang/LLVM, otherwise the linker fails +// r = engine->RegisterObjectMethod("string", "bool isEmpty() const", asMETHOD(string, empty), asCALL_THISCALL); assert( r >= 0 ); + r = engine->RegisterObjectMethod("string", "bool isEmpty() const", asFUNCTION(StringIsEmpty), asCALL_CDECL_OBJLAST); assert( r >= 0 ); + + // Register the index operator, both as a mutator and as an inspector + // Note that we don't register the operator[] directly, as it doesn't do bounds checking + r = engine->RegisterObjectMethod("string", "uint8 &opIndex(uint)", asFUNCTION(StringCharAt), asCALL_CDECL_OBJLAST); assert( r >= 0 ); + r = engine->RegisterObjectMethod("string", "const uint8 &opIndex(uint) const", asFUNCTION(StringCharAt), asCALL_CDECL_OBJLAST); assert( r >= 0 ); + + // Automatic conversion from values + r = engine->RegisterObjectMethod("string", "string &opAssign(double)", asFUNCTION(AssignDoubleToString), asCALL_CDECL_OBJLAST); assert( r >= 0 ); + r = engine->RegisterObjectMethod("string", "string &opAddAssign(double)", asFUNCTION(AddAssignDoubleToString), asCALL_CDECL_OBJLAST); assert( r >= 0 ); + r = engine->RegisterObjectMethod("string", "string opAdd(double) const", asFUNCTION(AddStringDouble), asCALL_CDECL_OBJFIRST); assert( r >= 0 ); + r = engine->RegisterObjectMethod("string", "string opAdd_r(double) const", asFUNCTION(AddDoubleString), asCALL_CDECL_OBJLAST); assert( r >= 0 ); + + r = engine->RegisterObjectMethod("string", "string &opAssign(float)", asFUNCTION(AssignFloatToString), asCALL_CDECL_OBJLAST); assert( r >= 0 ); + r = engine->RegisterObjectMethod("string", "string &opAddAssign(float)", asFUNCTION(AddAssignFloatToString), asCALL_CDECL_OBJLAST); assert( r >= 0 ); + r = engine->RegisterObjectMethod("string", "string opAdd(float) const", asFUNCTION(AddStringFloat), asCALL_CDECL_OBJFIRST); assert( r >= 0 ); + r = engine->RegisterObjectMethod("string", "string opAdd_r(float) const", asFUNCTION(AddFloatString), asCALL_CDECL_OBJLAST); assert( r >= 0 ); + + r = engine->RegisterObjectMethod("string", "string &opAssign(int64)", asFUNCTION(AssignInt64ToString), asCALL_CDECL_OBJLAST); assert( r >= 0 ); + r = engine->RegisterObjectMethod("string", "string &opAddAssign(int64)", asFUNCTION(AddAssignInt64ToString), asCALL_CDECL_OBJLAST); assert( r >= 0 ); + r = engine->RegisterObjectMethod("string", "string opAdd(int64) const", asFUNCTION(AddStringInt64), asCALL_CDECL_OBJFIRST); assert( r >= 0 ); + r = engine->RegisterObjectMethod("string", "string opAdd_r(int64) const", asFUNCTION(AddInt64String), asCALL_CDECL_OBJLAST); assert( r >= 0 ); + + r = engine->RegisterObjectMethod("string", "string &opAssign(uint64)", asFUNCTION(AssignUInt64ToString), asCALL_CDECL_OBJLAST); assert( r >= 0 ); + r = engine->RegisterObjectMethod("string", "string &opAddAssign(uint64)", asFUNCTION(AddAssignUInt64ToString), asCALL_CDECL_OBJLAST); assert( r >= 0 ); + r = engine->RegisterObjectMethod("string", "string opAdd(uint64) const", asFUNCTION(AddStringUInt64), asCALL_CDECL_OBJFIRST); assert( r >= 0 ); + r = engine->RegisterObjectMethod("string", "string opAdd_r(uint64) const", asFUNCTION(AddUInt64String), asCALL_CDECL_OBJLAST); assert( r >= 0 ); + + r = engine->RegisterObjectMethod("string", "string &opAssign(bool)", asFUNCTION(AssignBoolToString), asCALL_CDECL_OBJLAST); assert( r >= 0 ); + r = engine->RegisterObjectMethod("string", "string &opAddAssign(bool)", asFUNCTION(AddAssignBoolToString), asCALL_CDECL_OBJLAST); assert( r >= 0 ); + r = engine->RegisterObjectMethod("string", "string opAdd(bool) const", asFUNCTION(AddStringBool), asCALL_CDECL_OBJFIRST); assert( r >= 0 ); + r = engine->RegisterObjectMethod("string", "string opAdd_r(bool) const", asFUNCTION(AddBoolString), asCALL_CDECL_OBJLAST); assert( r >= 0 ); + + // Utilities + r = engine->RegisterObjectMethod("string", "string substr(uint start = 0, int count = -1) const", asFUNCTION(StringSubString), asCALL_CDECL_OBJLAST); assert( r >= 0 ); + r = engine->RegisterObjectMethod("string", "int findFirst(const string &in, uint start = 0) const", asFUNCTION(StringFindFirst), asCALL_CDECL_OBJLAST); assert( r >= 0 ); + r = engine->RegisterObjectMethod("string", "int findFirstOf(const string &in, uint start = 0) const", asFUNCTION(StringFindFirstOf), asCALL_CDECL_OBJLAST); assert(r >= 0); + r = engine->RegisterObjectMethod("string", "int findFirstNotOf(const string &in, uint start = 0) const", asFUNCTION(StringFindFirstNotOf), asCALL_CDECL_OBJLAST); assert(r >= 0); + r = engine->RegisterObjectMethod("string", "int findLast(const string &in, int start = -1) const", asFUNCTION(StringFindLast), asCALL_CDECL_OBJLAST); assert( r >= 0 ); + r = engine->RegisterObjectMethod("string", "int findLastOf(const string &in, int start = -1) const", asFUNCTION(StringFindLastOf), asCALL_CDECL_OBJLAST); assert(r >= 0); + r = engine->RegisterObjectMethod("string", "int findLastNotOf(const string &in, int start = -1) const", asFUNCTION(StringFindLastNotOf), asCALL_CDECL_OBJLAST); assert(r >= 0); + r = engine->RegisterObjectMethod("string", "void insert(uint pos, const string &in other)", asFUNCTION(StringInsert), asCALL_CDECL_OBJLAST); assert(r >= 0); + r = engine->RegisterObjectMethod("string", "void erase(uint pos, int count = -1)", asFUNCTION(StringErase), asCALL_CDECL_OBJLAST); assert(r >= 0); + + + r = engine->RegisterGlobalFunction("string formatInt(int64 val, const string &in options = \"\", uint width = 0)", asFUNCTION(formatInt), asCALL_CDECL); assert(r >= 0); + r = engine->RegisterGlobalFunction("string formatUInt(uint64 val, const string &in options = \"\", uint width = 0)", asFUNCTION(formatUInt), asCALL_CDECL); assert(r >= 0); + r = engine->RegisterGlobalFunction("string formatFloat(double val, const string &in options = \"\", uint width = 0, uint precision = 0)", asFUNCTION(formatFloat), asCALL_CDECL); assert(r >= 0); + r = engine->RegisterGlobalFunction("int64 parseInt(const string &in, uint base = 10, uint &out byteCount = 0)", asFUNCTION(parseInt), asCALL_CDECL); assert(r >= 0); + r = engine->RegisterGlobalFunction("uint64 parseUInt(const string &in, uint base = 10, uint &out byteCount = 0)", asFUNCTION(parseUInt), asCALL_CDECL); assert(r >= 0); + r = engine->RegisterGlobalFunction("double parseFloat(const string &in, uint &out byteCount = 0)", asFUNCTION(parseFloat), asCALL_CDECL); assert(r >= 0); + +#if AS_USE_STLNAMES == 1 + // Same as length + r = engine->RegisterObjectMethod("string", "uint size() const", asFUNCTION(StringLength), asCALL_CDECL_OBJLAST); assert( r >= 0 ); + // Same as isEmpty + r = engine->RegisterObjectMethod("string", "bool empty() const", asFUNCTION(StringIsEmpty), asCALL_CDECL_OBJLAST); assert( r >= 0 ); + // Same as findFirst + r = engine->RegisterObjectMethod("string", "int find(const string &in, uint start = 0) const", asFUNCTION(StringFindFirst), asCALL_CDECL_OBJLAST); assert( r >= 0 ); + // Same as findLast + r = engine->RegisterObjectMethod("string", "int rfind(const string &in, int start = -1) const", asFUNCTION(StringFindLast), asCALL_CDECL_OBJLAST); assert( r >= 0 ); +#endif + + // TODO: Implement the following + // findAndReplace - replaces a text found in the string + // replaceRange - replaces a range of bytes in the string + // multiply/times/opMul/opMul_r - takes the string and multiplies it n times, e.g. "-".multiply(5) returns "-----" +} + +static void ConstructStringGeneric(asIScriptGeneric * gen) +{ + new (gen->GetObject()) string(); +} + +static void CopyConstructStringGeneric(asIScriptGeneric * gen) +{ + string * a = static_cast(gen->GetArgObject(0)); + new (gen->GetObject()) string(*a); +} + +static void DestructStringGeneric(asIScriptGeneric * gen) +{ + string * ptr = static_cast(gen->GetObject()); + ptr->~string(); +} + +static void AssignStringGeneric(asIScriptGeneric *gen) +{ + string * a = static_cast(gen->GetArgObject(0)); + string * self = static_cast(gen->GetObject()); + *self = *a; + gen->SetReturnAddress(self); +} + +static void AddAssignStringGeneric(asIScriptGeneric *gen) +{ + string * a = static_cast(gen->GetArgObject(0)); + string * self = static_cast(gen->GetObject()); + *self += *a; + gen->SetReturnAddress(self); +} + +static void StringEqualsGeneric(asIScriptGeneric * gen) +{ + string * a = static_cast(gen->GetObject()); + string * b = static_cast(gen->GetArgAddress(0)); + *(bool*)gen->GetAddressOfReturnLocation() = (*a == *b); +} + +static void StringCmpGeneric(asIScriptGeneric * gen) +{ + string * a = static_cast(gen->GetObject()); + string * b = static_cast(gen->GetArgAddress(0)); + + int cmp = 0; + if( *a < *b ) cmp = -1; + else if( *a > *b ) cmp = 1; + + *(int*)gen->GetAddressOfReturnLocation() = cmp; +} + +static void StringAddGeneric(asIScriptGeneric * gen) +{ + string * a = static_cast(gen->GetObject()); + string * b = static_cast(gen->GetArgAddress(0)); + string ret_val = *a + *b; + gen->SetReturnObject(&ret_val); +} + +static void StringLengthGeneric(asIScriptGeneric * gen) +{ + string * self = static_cast(gen->GetObject()); + *static_cast(gen->GetAddressOfReturnLocation()) = (asUINT)self->length(); +} + +static void StringIsEmptyGeneric(asIScriptGeneric * gen) +{ + string * self = reinterpret_cast(gen->GetObject()); + *reinterpret_cast(gen->GetAddressOfReturnLocation()) = StringIsEmpty(*self); +} + +static void StringResizeGeneric(asIScriptGeneric * gen) +{ + string * self = static_cast(gen->GetObject()); + self->resize(*static_cast(gen->GetAddressOfArg(0))); +} + +static void StringInsert_Generic(asIScriptGeneric *gen) +{ + string * self = static_cast(gen->GetObject()); + asUINT pos = gen->GetArgDWord(0); + string *other = reinterpret_cast(gen->GetArgAddress(1)); + StringInsert(pos, *other, *self); +} + +static void StringErase_Generic(asIScriptGeneric *gen) +{ + string * self = static_cast(gen->GetObject()); + asUINT pos = gen->GetArgDWord(0); + int count = int(gen->GetArgDWord(1)); + StringErase(pos, count, *self); +} + +static void StringFindFirst_Generic(asIScriptGeneric * gen) +{ + string *find = reinterpret_cast(gen->GetArgAddress(0)); + asUINT start = gen->GetArgDWord(1); + string *self = reinterpret_cast(gen->GetObject()); + *reinterpret_cast(gen->GetAddressOfReturnLocation()) = StringFindFirst(*find, start, *self); +} + +static void StringFindLast_Generic(asIScriptGeneric * gen) +{ + string *find = reinterpret_cast(gen->GetArgAddress(0)); + asUINT start = gen->GetArgDWord(1); + string *self = reinterpret_cast(gen->GetObject()); + *reinterpret_cast(gen->GetAddressOfReturnLocation()) = StringFindLast(*find, start, *self); +} + +static void StringFindFirstOf_Generic(asIScriptGeneric * gen) +{ + string *find = reinterpret_cast(gen->GetArgAddress(0)); + asUINT start = gen->GetArgDWord(1); + string *self = reinterpret_cast(gen->GetObject()); + *reinterpret_cast(gen->GetAddressOfReturnLocation()) = StringFindFirstOf(*find, start, *self); +} + +static void StringFindLastOf_Generic(asIScriptGeneric * gen) +{ + string *find = reinterpret_cast(gen->GetArgAddress(0)); + asUINT start = gen->GetArgDWord(1); + string *self = reinterpret_cast(gen->GetObject()); + *reinterpret_cast(gen->GetAddressOfReturnLocation()) = StringFindLastOf(*find, start, *self); +} + +static void StringFindFirstNotOf_Generic(asIScriptGeneric * gen) +{ + string *find = reinterpret_cast(gen->GetArgAddress(0)); + asUINT start = gen->GetArgDWord(1); + string *self = reinterpret_cast(gen->GetObject()); + *reinterpret_cast(gen->GetAddressOfReturnLocation()) = StringFindFirstNotOf(*find, start, *self); +} + +static void StringFindLastNotOf_Generic(asIScriptGeneric * gen) +{ + string *find = reinterpret_cast(gen->GetArgAddress(0)); + asUINT start = gen->GetArgDWord(1); + string *self = reinterpret_cast(gen->GetObject()); + *reinterpret_cast(gen->GetAddressOfReturnLocation()) = StringFindLastNotOf(*find, start, *self); +} + +static void formatInt_Generic(asIScriptGeneric * gen) +{ + asINT64 val = gen->GetArgQWord(0); + string *options = reinterpret_cast(gen->GetArgAddress(1)); + asUINT width = gen->GetArgDWord(2); + new(gen->GetAddressOfReturnLocation()) string(formatInt(val, *options, width)); +} + +static void formatUInt_Generic(asIScriptGeneric * gen) +{ + asQWORD val = gen->GetArgQWord(0); + string *options = reinterpret_cast(gen->GetArgAddress(1)); + asUINT width = gen->GetArgDWord(2); + new(gen->GetAddressOfReturnLocation()) string(formatUInt(val, *options, width)); +} + +static void formatFloat_Generic(asIScriptGeneric *gen) +{ + double val = gen->GetArgDouble(0); + string *options = reinterpret_cast(gen->GetArgAddress(1)); + asUINT width = gen->GetArgDWord(2); + asUINT precision = gen->GetArgDWord(3); + new(gen->GetAddressOfReturnLocation()) string(formatFloat(val, *options, width, precision)); +} + +static void parseInt_Generic(asIScriptGeneric *gen) +{ + string *str = reinterpret_cast(gen->GetArgAddress(0)); + asUINT base = gen->GetArgDWord(1); + asUINT *byteCount = reinterpret_cast(gen->GetArgAddress(2)); + gen->SetReturnQWord(parseInt(*str,base,byteCount)); +} + +static void parseUInt_Generic(asIScriptGeneric *gen) +{ + string *str = reinterpret_cast(gen->GetArgAddress(0)); + asUINT base = gen->GetArgDWord(1); + asUINT *byteCount = reinterpret_cast(gen->GetArgAddress(2)); + gen->SetReturnQWord(parseUInt(*str, base, byteCount)); +} + +static void parseFloat_Generic(asIScriptGeneric *gen) +{ + string *str = reinterpret_cast(gen->GetArgAddress(0)); + asUINT *byteCount = reinterpret_cast(gen->GetArgAddress(1)); + gen->SetReturnDouble(parseFloat(*str,byteCount)); +} + +static void StringCharAtGeneric(asIScriptGeneric * gen) +{ + unsigned int index = gen->GetArgDWord(0); + string * self = static_cast(gen->GetObject()); + + if (index >= self->size()) + { + // Set a script exception + asIScriptContext *ctx = asGetActiveContext(); + ctx->SetException("Out of range"); + + gen->SetReturnAddress(0); + } + else + { + gen->SetReturnAddress(&(self->operator [](index))); + } +} + +static void AssignInt2StringGeneric(asIScriptGeneric *gen) +{ + asINT64 *a = static_cast(gen->GetAddressOfArg(0)); + string *self = static_cast(gen->GetObject()); + std::stringstream sstr; + sstr << *a; + *self = sstr.str(); + gen->SetReturnAddress(self); +} + +static void AssignUInt2StringGeneric(asIScriptGeneric *gen) +{ + asQWORD *a = static_cast(gen->GetAddressOfArg(0)); + string *self = static_cast(gen->GetObject()); + std::stringstream sstr; + sstr << *a; + *self = sstr.str(); + gen->SetReturnAddress(self); +} + +static void AssignDouble2StringGeneric(asIScriptGeneric *gen) +{ + double *a = static_cast(gen->GetAddressOfArg(0)); + string *self = static_cast(gen->GetObject()); + std::stringstream sstr; + sstr << *a; + *self = sstr.str(); + gen->SetReturnAddress(self); +} + +static void AssignFloat2StringGeneric(asIScriptGeneric *gen) +{ + float *a = static_cast(gen->GetAddressOfArg(0)); + string *self = static_cast(gen->GetObject()); + std::stringstream sstr; + sstr << *a; + *self = sstr.str(); + gen->SetReturnAddress(self); +} + +static void AssignBool2StringGeneric(asIScriptGeneric *gen) +{ + bool *a = static_cast(gen->GetAddressOfArg(0)); + string *self = static_cast(gen->GetObject()); + std::stringstream sstr; + sstr << (*a ? "true" : "false"); + *self = sstr.str(); + gen->SetReturnAddress(self); +} + +static void AddAssignDouble2StringGeneric(asIScriptGeneric * gen) +{ + double * a = static_cast(gen->GetAddressOfArg(0)); + string * self = static_cast(gen->GetObject()); + std::stringstream sstr; + sstr << *a; + *self += sstr.str(); + gen->SetReturnAddress(self); +} + +static void AddAssignFloat2StringGeneric(asIScriptGeneric * gen) +{ + float * a = static_cast(gen->GetAddressOfArg(0)); + string * self = static_cast(gen->GetObject()); + std::stringstream sstr; + sstr << *a; + *self += sstr.str(); + gen->SetReturnAddress(self); +} + +static void AddAssignInt2StringGeneric(asIScriptGeneric * gen) +{ + asINT64 * a = static_cast(gen->GetAddressOfArg(0)); + string * self = static_cast(gen->GetObject()); + std::stringstream sstr; + sstr << *a; + *self += sstr.str(); + gen->SetReturnAddress(self); +} + +static void AddAssignUInt2StringGeneric(asIScriptGeneric * gen) +{ + asQWORD * a = static_cast(gen->GetAddressOfArg(0)); + string * self = static_cast(gen->GetObject()); + std::stringstream sstr; + sstr << *a; + *self += sstr.str(); + gen->SetReturnAddress(self); +} + +static void AddAssignBool2StringGeneric(asIScriptGeneric * gen) +{ + bool * a = static_cast(gen->GetAddressOfArg(0)); + string * self = static_cast(gen->GetObject()); + std::stringstream sstr; + sstr << (*a ? "true" : "false"); + *self += sstr.str(); + gen->SetReturnAddress(self); +} + +static void AddString2DoubleGeneric(asIScriptGeneric * gen) +{ + string * a = static_cast(gen->GetObject()); + double * b = static_cast(gen->GetAddressOfArg(0)); + std::stringstream sstr; + sstr << *a << *b; + std::string ret_val = sstr.str(); + gen->SetReturnObject(&ret_val); +} + +static void AddString2FloatGeneric(asIScriptGeneric * gen) +{ + string * a = static_cast(gen->GetObject()); + float * b = static_cast(gen->GetAddressOfArg(0)); + std::stringstream sstr; + sstr << *a << *b; + std::string ret_val = sstr.str(); + gen->SetReturnObject(&ret_val); +} + +static void AddString2IntGeneric(asIScriptGeneric * gen) +{ + string * a = static_cast(gen->GetObject()); + asINT64 * b = static_cast(gen->GetAddressOfArg(0)); + std::stringstream sstr; + sstr << *a << *b; + std::string ret_val = sstr.str(); + gen->SetReturnObject(&ret_val); +} + +static void AddString2UIntGeneric(asIScriptGeneric * gen) +{ + string * a = static_cast(gen->GetObject()); + asQWORD * b = static_cast(gen->GetAddressOfArg(0)); + std::stringstream sstr; + sstr << *a << *b; + std::string ret_val = sstr.str(); + gen->SetReturnObject(&ret_val); +} + +static void AddString2BoolGeneric(asIScriptGeneric * gen) +{ + string * a = static_cast(gen->GetObject()); + bool * b = static_cast(gen->GetAddressOfArg(0)); + std::stringstream sstr; + sstr << *a << (*b ? "true" : "false"); + std::string ret_val = sstr.str(); + gen->SetReturnObject(&ret_val); +} + +static void AddDouble2StringGeneric(asIScriptGeneric * gen) +{ + double* a = static_cast(gen->GetAddressOfArg(0)); + string * b = static_cast(gen->GetObject()); + std::stringstream sstr; + sstr << *a << *b; + std::string ret_val = sstr.str(); + gen->SetReturnObject(&ret_val); +} + +static void AddFloat2StringGeneric(asIScriptGeneric * gen) +{ + float* a = static_cast(gen->GetAddressOfArg(0)); + string * b = static_cast(gen->GetObject()); + std::stringstream sstr; + sstr << *a << *b; + std::string ret_val = sstr.str(); + gen->SetReturnObject(&ret_val); +} + +static void AddInt2StringGeneric(asIScriptGeneric * gen) +{ + asINT64* a = static_cast(gen->GetAddressOfArg(0)); + string * b = static_cast(gen->GetObject()); + std::stringstream sstr; + sstr << *a << *b; + std::string ret_val = sstr.str(); + gen->SetReturnObject(&ret_val); +} + +static void AddUInt2StringGeneric(asIScriptGeneric * gen) +{ + asQWORD* a = static_cast(gen->GetAddressOfArg(0)); + string * b = static_cast(gen->GetObject()); + std::stringstream sstr; + sstr << *a << *b; + std::string ret_val = sstr.str(); + gen->SetReturnObject(&ret_val); +} + +static void AddBool2StringGeneric(asIScriptGeneric * gen) +{ + bool* a = static_cast(gen->GetAddressOfArg(0)); + string * b = static_cast(gen->GetObject()); + std::stringstream sstr; + sstr << (*a ? "true" : "false") << *b; + std::string ret_val = sstr.str(); + gen->SetReturnObject(&ret_val); +} + +static void StringSubString_Generic(asIScriptGeneric *gen) +{ + // Get the arguments + string *str = (string*)gen->GetObject(); + asUINT start = *(int*)gen->GetAddressOfArg(0); + int count = *(int*)gen->GetAddressOfArg(1); + + // Return the substring + new(gen->GetAddressOfReturnLocation()) string(StringSubString(start, count, *str)); +} + +void RegisterStdString_Generic(asIScriptEngine *engine) +{ + int r = 0; + UNUSED_VAR(r); + + // Register the string type + r = engine->RegisterObjectType("string", sizeof(string), asOBJ_VALUE | asOBJ_APP_CLASS_CDAK); assert( r >= 0 ); + + r = engine->RegisterStringFactory("string", GetStdStringFactorySingleton()); + + // Register the object operator overloads + r = engine->RegisterObjectBehaviour("string", asBEHAVE_CONSTRUCT, "void f()", asFUNCTION(ConstructStringGeneric), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectBehaviour("string", asBEHAVE_CONSTRUCT, "void f(const string &in)", asFUNCTION(CopyConstructStringGeneric), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectBehaviour("string", asBEHAVE_DESTRUCT, "void f()", asFUNCTION(DestructStringGeneric), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("string", "string &opAssign(const string &in)", asFUNCTION(AssignStringGeneric), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("string", "string &opAddAssign(const string &in)", asFUNCTION(AddAssignStringGeneric), asCALL_GENERIC); assert( r >= 0 ); + + r = engine->RegisterObjectMethod("string", "bool opEquals(const string &in) const", asFUNCTION(StringEqualsGeneric), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("string", "int opCmp(const string &in) const", asFUNCTION(StringCmpGeneric), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("string", "string opAdd(const string &in) const", asFUNCTION(StringAddGeneric), asCALL_GENERIC); assert( r >= 0 ); + + // Register the object methods +#if AS_USE_ACCESSORS != 1 + r = engine->RegisterObjectMethod("string", "uint length() const", asFUNCTION(StringLengthGeneric), asCALL_GENERIC); assert( r >= 0 ); +#endif + r = engine->RegisterObjectMethod("string", "void resize(uint)", asFUNCTION(StringResizeGeneric), asCALL_GENERIC); assert( r >= 0 ); +#if AS_USE_STLNAMES != 1 && AS_USE_ACCESSORS == 1 + r = engine->RegisterObjectMethod("string", "uint get_length() const property", asFUNCTION(StringLengthGeneric), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("string", "void set_length(uint) property", asFUNCTION(StringResizeGeneric), asCALL_GENERIC); assert( r >= 0 ); +#endif + r = engine->RegisterObjectMethod("string", "bool isEmpty() const", asFUNCTION(StringIsEmptyGeneric), asCALL_GENERIC); assert( r >= 0 ); + + // Register the index operator, both as a mutator and as an inspector + r = engine->RegisterObjectMethod("string", "uint8 &opIndex(uint)", asFUNCTION(StringCharAtGeneric), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("string", "const uint8 &opIndex(uint) const", asFUNCTION(StringCharAtGeneric), asCALL_GENERIC); assert( r >= 0 ); + + // Automatic conversion from values + r = engine->RegisterObjectMethod("string", "string &opAssign(double)", asFUNCTION(AssignDouble2StringGeneric), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("string", "string &opAddAssign(double)", asFUNCTION(AddAssignDouble2StringGeneric), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("string", "string opAdd(double) const", asFUNCTION(AddString2DoubleGeneric), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("string", "string opAdd_r(double) const", asFUNCTION(AddDouble2StringGeneric), asCALL_GENERIC); assert( r >= 0 ); + + r = engine->RegisterObjectMethod("string", "string &opAssign(float)", asFUNCTION(AssignFloat2StringGeneric), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("string", "string &opAddAssign(float)", asFUNCTION(AddAssignFloat2StringGeneric), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("string", "string opAdd(float) const", asFUNCTION(AddString2FloatGeneric), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("string", "string opAdd_r(float) const", asFUNCTION(AddFloat2StringGeneric), asCALL_GENERIC); assert( r >= 0 ); + + r = engine->RegisterObjectMethod("string", "string &opAssign(int64)", asFUNCTION(AssignInt2StringGeneric), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("string", "string &opAddAssign(int64)", asFUNCTION(AddAssignInt2StringGeneric), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("string", "string opAdd(int64) const", asFUNCTION(AddString2IntGeneric), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("string", "string opAdd_r(int64) const", asFUNCTION(AddInt2StringGeneric), asCALL_GENERIC); assert( r >= 0 ); + + r = engine->RegisterObjectMethod("string", "string &opAssign(uint64)", asFUNCTION(AssignUInt2StringGeneric), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("string", "string &opAddAssign(uint64)", asFUNCTION(AddAssignUInt2StringGeneric), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("string", "string opAdd(uint64) const", asFUNCTION(AddString2UIntGeneric), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("string", "string opAdd_r(uint64) const", asFUNCTION(AddUInt2StringGeneric), asCALL_GENERIC); assert( r >= 0 ); + + r = engine->RegisterObjectMethod("string", "string &opAssign(bool)", asFUNCTION(AssignBool2StringGeneric), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("string", "string &opAddAssign(bool)", asFUNCTION(AddAssignBool2StringGeneric), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("string", "string opAdd(bool) const", asFUNCTION(AddString2BoolGeneric), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("string", "string opAdd_r(bool) const", asFUNCTION(AddBool2StringGeneric), asCALL_GENERIC); assert( r >= 0 ); + + r = engine->RegisterObjectMethod("string", "string substr(uint start = 0, int count = -1) const", asFUNCTION(StringSubString_Generic), asCALL_GENERIC); assert(r >= 0); + r = engine->RegisterObjectMethod("string", "int findFirst(const string &in, uint start = 0) const", asFUNCTION(StringFindFirst_Generic), asCALL_GENERIC); assert(r >= 0); + r = engine->RegisterObjectMethod("string", "int findFirstOf(const string &in, uint start = 0) const", asFUNCTION(StringFindFirstOf_Generic), asCALL_GENERIC); assert(r >= 0); + r = engine->RegisterObjectMethod("string", "int findFirstNotOf(const string &in, uint start = 0) const", asFUNCTION(StringFindFirstNotOf_Generic), asCALL_GENERIC); assert(r >= 0); + r = engine->RegisterObjectMethod("string", "int findLast(const string &in, int start = -1) const", asFUNCTION(StringFindLast_Generic), asCALL_GENERIC); assert(r >= 0); + r = engine->RegisterObjectMethod("string", "int findLastOf(const string &in, int start = -1) const", asFUNCTION(StringFindLastOf_Generic), asCALL_GENERIC); assert(r >= 0); + r = engine->RegisterObjectMethod("string", "int findLastNotOf(const string &in, int start = -1) const", asFUNCTION(StringFindLastNotOf_Generic), asCALL_GENERIC); assert(r >= 0); + r = engine->RegisterObjectMethod("string", "void insert(uint pos, const string &in other)", asFUNCTION(StringInsert_Generic), asCALL_GENERIC); assert(r >= 0); + r = engine->RegisterObjectMethod("string", "void erase(uint pos, int count = -1)", asFUNCTION(StringErase_Generic), asCALL_GENERIC); assert(r >= 0); + + + r = engine->RegisterGlobalFunction("string formatInt(int64 val, const string &in options = \"\", uint width = 0)", asFUNCTION(formatInt_Generic), asCALL_GENERIC); assert(r >= 0); + r = engine->RegisterGlobalFunction("string formatUInt(uint64 val, const string &in options = \"\", uint width = 0)", asFUNCTION(formatUInt_Generic), asCALL_GENERIC); assert(r >= 0); + r = engine->RegisterGlobalFunction("string formatFloat(double val, const string &in options = \"\", uint width = 0, uint precision = 0)", asFUNCTION(formatFloat_Generic), asCALL_GENERIC); assert(r >= 0); + r = engine->RegisterGlobalFunction("int64 parseInt(const string &in, uint base = 10, uint &out byteCount = 0)", asFUNCTION(parseInt_Generic), asCALL_GENERIC); assert(r >= 0); + r = engine->RegisterGlobalFunction("uint64 parseUInt(const string &in, uint base = 10, uint &out byteCount = 0)", asFUNCTION(parseUInt_Generic), asCALL_GENERIC); assert(r >= 0); + r = engine->RegisterGlobalFunction("double parseFloat(const string &in, uint &out byteCount = 0)", asFUNCTION(parseFloat_Generic), asCALL_GENERIC); assert(r >= 0); +} + +void RegisterStdString(asIScriptEngine * engine) +{ + if (strstr(asGetLibraryOptions(), "AS_MAX_PORTABILITY")) + RegisterStdString_Generic(engine); + else + RegisterStdString_Native(engine); +} + +END_AS_NAMESPACE + + + + diff --git a/extern/angelscript_addons/scriptstdstring/scriptstdstring.h b/extern/angelscript_addons/scriptstdstring/scriptstdstring.h new file mode 100644 index 0000000..0960beb --- /dev/null +++ b/extern/angelscript_addons/scriptstdstring/scriptstdstring.h @@ -0,0 +1,48 @@ +// +// Script std::string +// +// This function registers the std::string type with AngelScript to be used as the default string type. +// +// The string type is registered as a value type, thus may have performance issues if a lot of +// string operations are performed in the script. However, for relatively few operations, this should +// not cause any problem for most applications. +// + +#ifndef SCRIPTSTDSTRING_H +#define SCRIPTSTDSTRING_H + +#ifndef ANGELSCRIPT_H +// Avoid having to inform include path if header is already include before +#include +#endif + +#include + +//--------------------------- +// Compilation settings +// + +// 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 string +// This option registers the accessors instead of the method length() +#ifndef AS_USE_ACCESSORS +#define AS_USE_ACCESSORS 0 +#endif + +BEGIN_AS_NAMESPACE + +void RegisterStdString(asIScriptEngine *engine); +void RegisterStdStringUtils(asIScriptEngine *engine); + +END_AS_NAMESPACE + +#endif diff --git a/extern/angelscript_addons/scriptstdstring/scriptstdstring_utils.cpp b/extern/angelscript_addons/scriptstdstring/scriptstdstring_utils.cpp new file mode 100644 index 0000000..54662aa --- /dev/null +++ b/extern/angelscript_addons/scriptstdstring/scriptstdstring_utils.cpp @@ -0,0 +1,129 @@ +#include +#include "scriptstdstring.h" +#include "../scriptarray/scriptarray.h" +#include +#include + +using namespace std; + +BEGIN_AS_NAMESPACE + +// This function takes an input string and splits it into parts by looking +// for a specified delimiter. Example: +// +// string str = "A|B||D"; +// array@ array = str.split("|"); +// +// The resulting array has the following elements: +// +// {"A", "B", "", "D"} +// +// AngelScript signature: +// array@ string::split(const string &in delim) const +static CScriptArray *StringSplit(const string &delim, const string &str) +{ + // Obtain a pointer to the engine + asIScriptContext *ctx = asGetActiveContext(); + asIScriptEngine *engine = ctx->GetEngine(); + + // TODO: This should only be done once + // TODO: This assumes that CScriptArray was already registered + asITypeInfo *arrayType = engine->GetTypeInfoByDecl("array"); + + // Create the array object + CScriptArray *array = CScriptArray::Create(arrayType); + + // Find the existence of the delimiter in the input string + int pos = 0, prev = 0, count = 0; + while( (pos = (int)str.find(delim, prev)) != (int)string::npos ) + { + // Add the part to the array + array->Resize(array->GetSize()+1); + ((string*)array->At(count))->assign(&str[prev], pos-prev); + + // Find the next part + count++; + prev = pos + (int)delim.length(); + } + + // Add the remaining part + array->Resize(array->GetSize()+1); + ((string*)array->At(count))->assign(&str[prev]); + + return array; +} + +static void StringSplit_Generic(asIScriptGeneric *gen) +{ + // Get the arguments + string *str = (string*)gen->GetObject(); + string *delim = *(string**)gen->GetAddressOfArg(0); + + // Return the array by handle + *(CScriptArray**)gen->GetAddressOfReturnLocation() = StringSplit(*delim, *str); +} + + + +// This function takes as input an array of string handles as well as a +// delimiter and concatenates the array elements into one delimited string. +// Example: +// +// array array = {"A", "B", "", "D"}; +// string str = join(array, "|"); +// +// The resulting string is: +// +// "A|B||D" +// +// AngelScript signature: +// string join(const array &in array, const string &in delim) +static string StringJoin(const CScriptArray &array, const string &delim) +{ + // Create the new string + string str = ""; + if( array.GetSize() ) + { + int n; + for( n = 0; n < (int)array.GetSize() - 1; n++ ) + { + str += *(string*)array.At(n); + str += delim; + } + + // Add the last part + str += *(string*)array.At(n); + } + + return str; +} + +static void StringJoin_Generic(asIScriptGeneric *gen) +{ + // Get the arguments + CScriptArray *array = *(CScriptArray**)gen->GetAddressOfArg(0); + string *delim = *(string**)gen->GetAddressOfArg(1); + + // Return the string + new(gen->GetAddressOfReturnLocation()) string(StringJoin(*array, *delim)); +} + +// This is where the utility functions are registered. +// The string type must have been registered first. +void RegisterStdStringUtils(asIScriptEngine *engine) +{ + int r; + + if( strstr(asGetLibraryOptions(), "AS_MAX_PORTABILITY") ) + { + r = engine->RegisterObjectMethod("string", "array@ split(const string &in) const", asFUNCTION(StringSplit_Generic), asCALL_GENERIC); assert(r >= 0); + r = engine->RegisterGlobalFunction("string join(const array &in, const string &in)", asFUNCTION(StringJoin_Generic), asCALL_GENERIC); assert(r >= 0); + } + else + { + r = engine->RegisterObjectMethod("string", "array@ split(const string &in) const", asFUNCTION(StringSplit), asCALL_CDECL_OBJLAST); assert(r >= 0); + r = engine->RegisterGlobalFunction("string join(const array &in, const string &in)", asFUNCTION(StringJoin), asCALL_CDECL); assert(r >= 0); + } +} + +END_AS_NAMESPACE diff --git a/src/AngelScript/AngelScripResolver.cpp b/src/AngelScript/AngelScripResolver.cpp new file mode 100644 index 0000000..4c6f8cc --- /dev/null +++ b/src/AngelScript/AngelScripResolver.cpp @@ -0,0 +1,72 @@ +#include "AngelScripResolver.hpp" +#include "TypeRegistry/RegisterPokemonTypes.hpp" +#include "../../extern/angelscript_addons/scriptstdstring/scriptstdstring.h" +#include "../../extern/angelscript_addons/scripthelper/scripthelper.h" + +CreatureLib::Battling::ScriptResolver* PkmnLib::Battling::BattleLibrary::CreateScriptResolver(){ + return new AngelScripResolver(); +} +void AngelScripResolver::Initialize(CreatureLib::Battling::BattleLibrary* library) { + _engine = asCreateScriptEngine(); + + _engine->SetEngineProperty(asEP_DISALLOW_EMPTY_LIST_ELEMENTS, true); + _engine->SetEngineProperty(asEP_DISALLOW_VALUE_ASSIGN_FOR_REF_TYPE, true); + _engine->SetEngineProperty(asEP_ALLOW_IMPLICIT_HANDLE_TYPES, true); + _engine->SetEngineProperty(asEP_ALWAYS_IMPL_DEFAULT_CONSTRUCT, true); + _engine->SetEngineProperty(asEP_AUTO_GARBAGE_COLLECT, false); + + int32_t r = _engine->SetMessageCallback(asFUNCTION(MessageCallback), nullptr, asCALL_CDECL); + if (r < 0) + throw CreatureException("Registering message callback failed."); + + RegisterStdString(_engine); + r = _engine->RegisterGlobalFunction("void print(const string &in)", asFUNCTION(Print), asCALL_CDECL); + if (r < 0) throw CreatureException("Registering print function failed."); + + RegisterExceptionRoutines(_engine); + + RegisterPokemonTypes::Register(_engine); + + _mainModule = _engine->GetModule("pkmn", asGM_ALWAYS_CREATE); + + _contextPool = new ContextPool(_engine); +} +AngelScriptTypeInfo* AngelScripResolver::GetTypeInfo(const std::string& name) { + auto find = _types.find(name); + if (find != _types.end()){ + return find->second; + } + auto type = _mainModule->GetTypeInfoByDecl(name.c_str()); + if (type == nullptr){ + _types.insert({name, nullptr}); + return nullptr; + } + auto typeinfo = new AngelScriptTypeInfo(type); + _types.insert({name, typeinfo}); + return typeinfo; +} +void AngelScripResolver::MessageCallback(const asSMessageInfo* msg, void* param) { + const char* type = "ERR "; + if (msg->type == asMSGTYPE_WARNING) + type = "WARN"; + else if (msg->type == asMSGTYPE_INFORMATION) + type = "INFO"; + printf("%s (%d, %d) : %s : %s\n", msg->section, msg->row, msg->col, type, msg->message); +} +CreatureLib::Battling::Script* AngelScripResolver::LoadScript(ScriptCategory category, const std::string& scriptName) { + auto typeInfo = GetTypeInfo(scriptName); + if (typeInfo == nullptr) return nullptr; + auto ctx = _contextPool->RequestContext(); + auto obj = typeInfo->Instantiate(ctx); + _contextPool->ReturnContextToPool(ctx); + return new AngelScriptScript(scriptName, typeInfo, obj, _contextPool); +} +void AngelScripResolver::FinalizeModule() { + int r = _mainModule->Build(); + if (r < 0) + throw CreatureException("Building Script Module failed."); +} +void AngelScripResolver::CreateScript(const char* scriptName) { + auto scriptString = _loadFunc(scriptName); + _mainModule->AddScriptSection(scriptName, scriptString); +} diff --git a/src/AngelScript/AngelScripResolver.hpp b/src/AngelScript/AngelScripResolver.hpp new file mode 100644 index 0000000..3c6ee40 --- /dev/null +++ b/src/AngelScript/AngelScripResolver.hpp @@ -0,0 +1,46 @@ +#ifndef PKMNLIB_ANGELSCRIPRESOLVER_HPP +#define PKMNLIB_ANGELSCRIPRESOLVER_HPP + +#include +#include "../Battling/Library/BattleLibrary.hpp" + +#undef GetProperty +#include +#include +#include +#include "AngelScriptScript.hpp" +#include "AngelScriptTypeInfo.hpp" + +class AngelScripResolver : public CreatureLib::Battling::ScriptResolver { +private: + asIScriptEngine* _engine = nullptr; + asIScriptModule* _mainModule = nullptr; + ContextPool* _contextPool = nullptr; + const char* (*_loadFunc)(const char*) = nullptr; + std::unordered_map _types; + + static void MessageCallback(const asSMessageInfo* msg, void* param); + static void Print(const std::string& str) { std::cout << str << std::endl; } + AngelScriptTypeInfo* GetTypeInfo(const std::string& name); + +public: + ~AngelScripResolver() override { + delete _contextPool; + for (const auto& type : _types) { + delete type.second; + } + _types.clear(); + _engine->ShutDownAndRelease(); + } + + void Initialize(CreatureLib::Battling::BattleLibrary* library) override; + + void SetCreateFunction(const char* (*loadFunc)(const char* scriptName)) { _loadFunc = loadFunc; } + + void CreateScript(const char* scriptName); + + void FinalizeModule(); + + CreatureLib::Battling::Script* LoadScript(ScriptCategory category, const std::string& scriptName) override; +}; +#endif // PKMNLIB_ANGELSCRIPRESOLVER_HPP diff --git a/src/AngelScript/AngelScriptScript.cpp b/src/AngelScript/AngelScriptScript.cpp index 97d92e6..4afb7d5 100644 --- a/src/AngelScript/AngelScriptScript.cpp +++ b/src/AngelScript/AngelScriptScript.cpp @@ -1 +1,2 @@ +#undef GetProperty #include "AngelScriptScript.hpp" diff --git a/src/AngelScript/AngelScriptScript.hpp b/src/AngelScript/AngelScriptScript.hpp index f2b5a88..6504579 100644 --- a/src/AngelScript/AngelScriptScript.hpp +++ b/src/AngelScript/AngelScriptScript.hpp @@ -1,6 +1,55 @@ #ifndef PKMNLIB_ANGELSCRIPTSCRIPT_HPP #define PKMNLIB_ANGELSCRIPTSCRIPT_HPP +#include +#undef GetProperty +#include +#include "AngelScriptTypeInfo.hpp" +#include "ContextPool.hpp" -class AngelScriptScript {}; +class AngelScriptScript : public CreatureLib::Battling::Script { +private: + AngelScriptTypeInfo* _type = nullptr; + ContextPool* _ctxPool = nullptr; + + asIScriptObject* _obj = nullptr; + +public: + AngelScriptScript(const std::string& name, AngelScriptTypeInfo* type, asIScriptObject* obj, ContextPool* ctxPool) + : CreatureLib::Battling::Script(name), _type(type), _ctxPool(ctxPool), _obj(obj) { + } + + ~AngelScriptScript() override { _obj->Release(); } + + void InvokeMethod(const char* name) { + auto func = _type->GetFunction(name); + if (func == nullptr) return; + auto ctx = _ctxPool->RequestContext(); + ctx->Prepare(func); + ctx->SetObject(_obj); + auto result = ctx->Execute(); + if (result != asEXECUTION_FINISHED){ + if (result == asEXECUTION_EXCEPTION){ + throw CreatureException(ctx->GetExceptionString()); + } + throw CreatureException("Function failed."); + } + _ctxPool->ReturnContextToPool(ctx); + } + + asIScriptObject* GetScriptObject(){ + return _obj; + } + + asIScriptFunction* PrepareMethod(const char* name, asIScriptContext* ctx){ + auto func = _type->GetFunction(name); + ctx->Prepare(func); + ctx->SetObject(_obj); + return func; + } + + ContextPool* GetContextPool(){ + return _ctxPool; + } +}; #endif // PKMNLIB_ANGELSCRIPTSCRIPT_HPP diff --git a/src/AngelScript/AngelScriptTypeInfo.cpp b/src/AngelScript/AngelScriptTypeInfo.cpp new file mode 100644 index 0000000..f864f81 --- /dev/null +++ b/src/AngelScript/AngelScriptTypeInfo.cpp @@ -0,0 +1 @@ +#include "AngelScriptTypeInfo.hpp" diff --git a/src/AngelScript/AngelScriptTypeInfo.hpp b/src/AngelScript/AngelScriptTypeInfo.hpp new file mode 100644 index 0000000..6d15c9d --- /dev/null +++ b/src/AngelScript/AngelScriptTypeInfo.hpp @@ -0,0 +1,47 @@ +#ifndef PKMNLIB_ANGELSCRIPTTYPEINFO_HPP +#define PKMNLIB_ANGELSCRIPTTYPEINFO_HPP + +#include +#include +#include + +class AngelScriptTypeInfo { +private: + asITypeInfo* _type = nullptr; + std::unordered_map _functions; + +public: + explicit AngelScriptTypeInfo(asITypeInfo* type) : _type(type){} + ~AngelScriptTypeInfo(){ + for (const auto& f: _functions){ + f.second->Release(); + } + _functions.clear(); + } + + asIScriptFunction* GetFunction(const std::string& functionName){ + auto find = _functions.find(functionName); + if (find != _functions.end()){ + return find->second; + } + auto func = _type->GetMethodByName(functionName.c_str()); + func->AddRef(); + _functions.insert({functionName, func}); + return func; + } + + asIScriptObject* Instantiate(asIScriptContext* ctx){ + auto factory = _type->GetFactoryByIndex(0); + ctx->Prepare(factory); + auto result = ctx->Execute(); + if (result != asEXECUTION_FINISHED){ + throw CreatureException("Instantiation failed."); + } + asIScriptObject* obj = *(asIScriptObject**)ctx->GetAddressOfReturnValue(); + obj->AddRef(); + return obj; + } + +}; + +#endif // PKMNLIB_ANGELSCRIPTTYPEINFO_HPP diff --git a/src/AngelScript/ContextPool.cpp b/src/AngelScript/ContextPool.cpp new file mode 100644 index 0000000..f8049ab --- /dev/null +++ b/src/AngelScript/ContextPool.cpp @@ -0,0 +1 @@ +#include "ContextPool.hpp" diff --git a/src/AngelScript/ContextPool.hpp b/src/AngelScript/ContextPool.hpp new file mode 100644 index 0000000..6acbbf2 --- /dev/null +++ b/src/AngelScript/ContextPool.hpp @@ -0,0 +1,37 @@ +#ifndef PKMNLIB_CONTEXTPOOL_HPP +#define PKMNLIB_CONTEXTPOOL_HPP + +#include +#include + +class ContextPool { + std::vector _pool; + asIScriptEngine* _engine; + +public: + ContextPool(asIScriptEngine* engine) : _engine(engine) {} + + ~ContextPool(){ + for (auto ctx: _pool){ + ctx->Release(); + } + } + + asIScriptContext* RequestContext() { + // Get a context from the pool, or create a new + asIScriptContext* ctx = nullptr; + if (!_pool.empty()) { + ctx = *_pool.rbegin(); + _pool.pop_back(); + } else + ctx = _engine->CreateContext(); + return ctx; + } + + void ReturnContextToPool(asIScriptContext* ctx) { + _pool.push_back(ctx); + ctx->Unprepare(); + } +}; + +#endif // PKMNLIB_CONTEXTPOOL_HPP diff --git a/src/AngelScript/PokemonScriptResolver.cpp b/src/AngelScript/PokemonScriptResolver.cpp deleted file mode 100644 index 8721256..0000000 --- a/src/AngelScript/PokemonScriptResolver.cpp +++ /dev/null @@ -1,16 +0,0 @@ -#include "../Battling/Library/PokemonScriptResolver.hpp" -#define ANGELSCRIPT_DLL_LIBRARY_IMPORT -#define GetPropertyCache -#undef GetProperty -#include -#define GetProperty GetPropertyCache - -using namespace PkmnLib::Battling; - -void PokemonScriptResolver::Initialize(const PkmnLib::Battling::BattleLibrary* library){ -} - -CreatureLib::Battling::Script* PokemonScriptResolver::LoadScript(ScriptCategory category, const std::string& scriptName){ - return nullptr; -} - diff --git a/src/AngelScript/TypeRegistry/RegisterPokemonTypes.cpp b/src/AngelScript/TypeRegistry/RegisterPokemonTypes.cpp new file mode 100644 index 0000000..6f59b96 --- /dev/null +++ b/src/AngelScript/TypeRegistry/RegisterPokemonTypes.cpp @@ -0,0 +1,36 @@ +#include "RegisterPokemonTypes.hpp" +#include +#include "../../Library/Species/PokemonSpecies.hpp" + +void RegisterPokemonTypes::Register(asIScriptEngine* engine) { + RegisterGenderEnum(engine); + RegisterSpeciesType(engine); +} + +void RegisterPokemonTypes::RegisterGenderEnum(asIScriptEngine* engine) { + int r = engine->RegisterEnum("Gender"); assert(r >= 0); + r = engine->RegisterEnumValue("Gender", "Male", 0); assert(r >= 0); + r = engine->RegisterEnumValue("Gender", "Female", 1); assert(r >= 0); + r = engine->RegisterEnumValue("Gender", "Genderless", 2); assert(r >= 0); +} + +void RegisterPokemonTypes::RegisterSpeciesType(asIScriptEngine* engine) { + int r = engine->RegisterObjectType("Species", 0, asOBJ_REF | asOBJ_NOCOUNT); + assert(r >= 0); + r = engine->RegisterObjectMethod("Species", "const string& get_Name() const property", + asMETHOD(PkmnLib::Library::PokemonSpecies, GetName), asCALL_THISCALL); + assert(r >= 0); + r = engine->RegisterObjectMethod("Species", "uint16 get_Id() const property", + asMETHOD(PkmnLib::Library::PokemonSpecies, GetId), asCALL_THISCALL); + assert(r >= 0); + r = engine->RegisterObjectMethod("Species", "float get_GenderRate() const property", + asMETHOD(PkmnLib::Library::PokemonSpecies, GetGenderRate), asCALL_THISCALL); + assert(r >= 0); + r = engine->RegisterObjectMethod("Species", "uint8 get_CaptureRate() const property", + asMETHOD(PkmnLib::Library::PokemonSpecies, GetCaptureRate), asCALL_THISCALL); + assert(r >= 0); + r = engine->RegisterObjectMethod("Species", "Gender GetRandomGender() const", + asMETHOD(PkmnLib::Library::PokemonSpecies, GetRandomGender), asCALL_THISCALL); + assert(r >= 0); + +} diff --git a/src/AngelScript/TypeRegistry/RegisterPokemonTypes.hpp b/src/AngelScript/TypeRegistry/RegisterPokemonTypes.hpp new file mode 100644 index 0000000..5a38945 --- /dev/null +++ b/src/AngelScript/TypeRegistry/RegisterPokemonTypes.hpp @@ -0,0 +1,14 @@ +#ifndef PKMNLIB_REGISTERPOKEMONTYPES_HPP +#define PKMNLIB_REGISTERPOKEMONTYPES_HPP +#include + +class RegisterPokemonTypes { +public: + static void Register(asIScriptEngine* engine); + +private: + static void RegisterGenderEnum(asIScriptEngine* engine); + static void RegisterSpeciesType(asIScriptEngine* engine); +}; + +#endif // PKMNLIB_REGISTERPOKEMONTYPES_HPP diff --git a/src/Battling/Library/BattleLibrary.hpp b/src/Battling/Library/BattleLibrary.hpp index 393bac1..612c133 100644 --- a/src/Battling/Library/BattleLibrary.hpp +++ b/src/Battling/Library/BattleLibrary.hpp @@ -33,7 +33,8 @@ namespace PkmnLib::Battling { const Library::NatureLibrary* GetNatureLibrary() const { return GetStaticLib()->GetNatureLibrary(); } + + static CreatureLib::Battling::ScriptResolver* CreateScriptResolver(); }; } - #endif // PKMNLIB_BATTLELIBRARY_HPP diff --git a/src/Battling/Library/PokemonScriptResolver.hpp b/src/Battling/Library/PokemonScriptResolver.hpp deleted file mode 100644 index cda9ecb..0000000 --- a/src/Battling/Library/PokemonScriptResolver.hpp +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef PKMNLIB_POKEMONSCRIPTRESOLVER_HPP -#define PKMNLIB_POKEMONSCRIPTRESOLVER_HPP - -#include -#include "../../Battling/Library/BattleLibrary.hpp" -namespace PkmnLib::Battling { - class PokemonScriptResolver : public CreatureLib::Battling::ScriptResolver { - public: - ~PokemonScriptResolver() override = default; - void Initialize(const PkmnLib::Battling::BattleLibrary* library); - CreatureLib::Battling::Script* LoadScript(ScriptCategory category, const std::string& scriptName) override; - }; -} - -#endif // PKMNLIB_POKEMONSCRIPTRESOLVER_HPP diff --git a/tests/ScriptTests/ScriptResolverTests.cpp b/tests/ScriptTests/ScriptResolverTests.cpp new file mode 100644 index 0000000..beed455 --- /dev/null +++ b/tests/ScriptTests/ScriptResolverTests.cpp @@ -0,0 +1,130 @@ +#ifdef TESTS_BUILD +#include "../../extern/catch.hpp" +#include "../../src/AngelScript/AngelScripResolver.hpp" +#include "../TestLibrary/TestLibrary.hpp" + +static std::unordered_map _scripts = + std::unordered_map{ + {"testScript1", R"( +class testScript1 { + int add(int a, int b) { + return a + b; + } + + void testFunc2(Species &s){ + if (s.Name != "testSpecies2"){ + throw("err"); + } + } +} +)"} +}; + +static const char* _testLoadFunc(const char* name) { return _scripts[name]; } + +TEST_CASE("Get a script resolver, initialize it, then delete it") { + auto lib = PkmnLib::Battling::BattleLibrary::CreateScriptResolver(); + lib->Initialize(TestLibrary::GetLibrary()); + delete lib; +} + +TEST_CASE("Get a script resolver, set script load function, then delete it") { + auto lib = dynamic_cast(PkmnLib::Battling::BattleLibrary::CreateScriptResolver()); + lib->Initialize(TestLibrary::GetLibrary()); + lib->SetCreateFunction(&_testLoadFunc); + delete lib; +} + +TEST_CASE("Get a script resolver, set script load function, load script, then build module") { + auto lib = dynamic_cast(PkmnLib::Battling::BattleLibrary::CreateScriptResolver()); + lib->Initialize(TestLibrary::GetLibrary()); + lib->SetCreateFunction(&_testLoadFunc); + lib->CreateScript("testScript1"); + lib->FinalizeModule(); + delete lib; +} + +TEST_CASE("Build script resolver, then create object") { + auto lib = dynamic_cast(PkmnLib::Battling::BattleLibrary::CreateScriptResolver()); + lib->Initialize(TestLibrary::GetLibrary()); + lib->SetCreateFunction(&_testLoadFunc); + lib->CreateScript("testScript1"); + lib->FinalizeModule(); + + auto obj = lib->LoadScript(AngelScripResolver::ScriptCategory::Creature, "testScript1"); + + delete obj; + delete lib; +} + +TEST_CASE("Build script resolver, create object, invoke addition method") { + auto lib = dynamic_cast(PkmnLib::Battling::BattleLibrary::CreateScriptResolver()); + lib->Initialize(TestLibrary::GetLibrary()); + lib->SetCreateFunction(&_testLoadFunc); + lib->CreateScript("testScript1"); + lib->FinalizeModule(); + + auto obj = + dynamic_cast(lib->LoadScript(AngelScripResolver::ScriptCategory::Creature, "testScript1")); + auto ctxPool = obj->GetContextPool(); + auto ctx = ctxPool->RequestContext(); + + auto func = obj->PrepareMethod("add", ctx); + REQUIRE(func != nullptr); + + ctx->SetArgDWord(0, 5); + ctx->SetArgDWord(1, 100); + + auto result = ctx->Execute(); + if (result == asEXECUTION_EXCEPTION){ + FAIL(ctx->GetExceptionString()); + } + CHECK(result == asEXECUTION_FINISHED); + + auto returnValue = ctx->GetReturnDWord(); + REQUIRE(returnValue == 105); + + ctxPool->ReturnContextToPool(ctx); + delete obj; + delete lib; +} + + +TEST_CASE("Test whether species object was properly registered.") { + auto lib = dynamic_cast(PkmnLib::Battling::BattleLibrary::CreateScriptResolver()); + auto mainLib = TestLibrary::GetLibrary(); + lib->Initialize(mainLib); + lib->SetCreateFunction(&_testLoadFunc); + lib->CreateScript("testScript1"); + lib->FinalizeModule(); + auto species = mainLib->GetSpeciesLibrary()->GetPkmnSpecies("testSpecies2"); + + auto obj = + dynamic_cast(lib->LoadScript(AngelScripResolver::ScriptCategory::Creature, "testScript1")); + + auto ctxPool = obj->GetContextPool(); + auto ctx = ctxPool->RequestContext(); + + obj->PrepareMethod("testFunc2", ctx); + ctx->SetArgObject(0, const_cast(species)); + auto result = ctx->Execute(); + if (result == asEXECUTION_EXCEPTION){ + FAIL(ctx->GetExceptionString()); + } + ctx->Unprepare(); + + species = mainLib->GetSpeciesLibrary()->GetPkmnSpecies("testSpecies"); + + obj->PrepareMethod("testFunc2", ctx); + ctx->SetArgObject(0, const_cast(species)); + result = ctx->Execute(); + REQUIRE(result == asEXECUTION_EXCEPTION); + + ctxPool->ReturnContextToPool(ctx); + delete obj; + delete lib; +} + + + +#endif \ No newline at end of file diff --git a/tests/TestLibrary/TestLibrary.hpp b/tests/TestLibrary/TestLibrary.hpp index f4504d1..fc8a8f1 100644 --- a/tests/TestLibrary/TestLibrary.hpp +++ b/tests/TestLibrary/TestLibrary.hpp @@ -3,10 +3,10 @@ #include #include "../../src/Battling/Library/BattleLibrary.hpp" -#include "../../src/Battling/Library/PokemonScriptResolver.hpp" #include "../../src/Library/Moves/MoveLibrary.hpp" #include "../../src/Library/PokemonLibrary.hpp" #include "../../src/Library/Statistic.hpp" + class TestLibrary { private: static PkmnLib::Battling::BattleLibrary* _library; @@ -21,7 +21,7 @@ public: static PkmnLib::Battling::BattleLibrary* BuildLibrary() { auto statCalc = new PkmnLib::Battling::StatCalculator(); - auto scriptResolver = new PkmnLib::Battling::PokemonScriptResolver(); + auto scriptResolver = PkmnLib::Battling::BattleLibrary::CreateScriptResolver(); auto lib = new PkmnLib::Battling::BattleLibrary( BuildStaticLibrary(), statCalc, new CreatureLib::Battling::DamageLibrary(), new CreatureLib::Battling::ExperienceLibrary(), scriptResolver,