From bcc038b49d0a1cfd0f5e50d74f601d64031e101e Mon Sep 17 00:00:00 2001 From: Deukhoofd Date: Tue, 4 Feb 2020 19:34:30 +0100 Subject: [PATCH] Actual implementation of Angelscript hooks into battle library. --- conanfile.py | 2 +- .../scripthandle/scripthandle.cpp | 360 ++++++++++++++++++ .../scripthandle/scripthandle.h | 69 ++++ .../AngelScript/AngelScripResolver.cpp | 20 +- .../AngelScript/AngelScriptScript.hpp | 103 ++++- .../AngelScript/AngelScriptTypeInfo.hpp | 47 ++- .../TypeRegistry/BasicScriptClass.cpp | 15 + .../TypeRegistry/BasicScriptClass.hpp | 10 + .../Library/RegisterStaticLibraryTypes.cpp | 7 +- tests/ScriptTests/BaseScriptClassTests.cpp | 119 ++++++ tests/ScriptTests/ScriptResolverTests.cpp | 37 -- 11 files changed, 717 insertions(+), 72 deletions(-) create mode 100644 extern/angelscript_addons/scripthandle/scripthandle.cpp create mode 100644 extern/angelscript_addons/scripthandle/scripthandle.h create mode 100644 src/ScriptResolving/AngelScript/TypeRegistry/BasicScriptClass.cpp create mode 100644 src/ScriptResolving/AngelScript/TypeRegistry/BasicScriptClass.hpp create mode 100644 tests/ScriptTests/BaseScriptClassTests.cpp diff --git a/conanfile.py b/conanfile.py index 72679ce..5450826 100644 --- a/conanfile.py +++ b/conanfile.py @@ -40,7 +40,7 @@ class PkmnLibConan(ConanFile): self.options["AngelScript"].link_std_statically = True def requirements(self): - self.requires("CreatureLib/c3b573c7daa3f2815e063c9dabfaa5dbfe6dbb20@creaturelib/master") + self.requires("CreatureLib/cd7ddcf78ec0ee4ae645d1862f92ec23246af949@creaturelib/master") if self.options.script_handler == "angelscript": self.requires("AngelScript/2.34@AngelScript/Deukhoofd") else: diff --git a/extern/angelscript_addons/scripthandle/scripthandle.cpp b/extern/angelscript_addons/scripthandle/scripthandle.cpp new file mode 100644 index 0000000..df6cbbe --- /dev/null +++ b/extern/angelscript_addons/scripthandle/scripthandle.cpp @@ -0,0 +1,360 @@ +#include "scripthandle.h" +#include +#include +#include + +BEGIN_AS_NAMESPACE + +static void Construct(CScriptHandle *self) { new(self) CScriptHandle(); } +static void Construct(CScriptHandle *self, const CScriptHandle &o) { new(self) CScriptHandle(o); } +// This one is not static because it needs to be friend with the CScriptHandle class +void Construct(CScriptHandle *self, void *ref, int typeId) { new(self) CScriptHandle(ref, typeId); } +static void Destruct(CScriptHandle *self) { self->~CScriptHandle(); } + +CScriptHandle::CScriptHandle() +{ + m_ref = 0; + m_type = 0; +} + +CScriptHandle::CScriptHandle(const CScriptHandle &other) +{ + m_ref = other.m_ref; + m_type = other.m_type; + + AddRefHandle(); +} + +CScriptHandle::CScriptHandle(void *ref, asITypeInfo *type) +{ + m_ref = ref; + m_type = type; + + AddRefHandle(); +} + +// This constructor shouldn't be called from the application +// directly as it requires an active script context +CScriptHandle::CScriptHandle(void *ref, int typeId) +{ + m_ref = 0; + m_type = 0; + + Assign(ref, typeId); +} + +CScriptHandle::~CScriptHandle() +{ + ReleaseHandle(); +} + +void CScriptHandle::ReleaseHandle() +{ + if( m_ref && m_type ) + { + asIScriptEngine *engine = m_type->GetEngine(); + engine->ReleaseScriptObject(m_ref, m_type); + + engine->Release(); + + m_ref = 0; + m_type = 0; + } +} + +void CScriptHandle::AddRefHandle() +{ + if( m_ref && m_type ) + { + asIScriptEngine *engine = m_type->GetEngine(); + engine->AddRefScriptObject(m_ref, m_type); + + // Hold on to the engine so it isn't destroyed while + // a reference to a script object is still held + engine->AddRef(); + } +} + +CScriptHandle &CScriptHandle::operator =(const CScriptHandle &other) +{ + Set(other.m_ref, other.m_type); + + return *this; +} + +void CScriptHandle::Set(void *ref, asITypeInfo *type) +{ + if( m_ref == ref ) return; + + ReleaseHandle(); + + m_ref = ref; + m_type = type; + + AddRefHandle(); +} + +void *CScriptHandle::GetRef() +{ + return m_ref; +} + +asITypeInfo *CScriptHandle::GetType() const +{ + return m_type; +} + +int CScriptHandle::GetTypeId() const +{ + if( m_type == 0 ) return 0; + + return m_type->GetTypeId() | asTYPEID_OBJHANDLE; +} + +// This method shouldn't be called from the application +// directly as it requires an active script context +CScriptHandle &CScriptHandle::Assign(void *ref, int typeId) +{ + // When receiving a null handle we just clear our memory + if( typeId == 0 ) + { + Set(0, 0); + return *this; + } + + // Dereference received handles to get the object + if( typeId & asTYPEID_OBJHANDLE ) + { + // Store the actual reference + ref = *(void**)ref; + typeId &= ~asTYPEID_OBJHANDLE; + } + + // Get the object type + asIScriptContext *ctx = asGetActiveContext(); + asIScriptEngine *engine = ctx->GetEngine(); + asITypeInfo *type = engine->GetTypeInfoById(typeId); + + // If the argument is another CScriptHandle, we should copy the content instead + if( type && strcmp(type->GetName(), "ref") == 0 ) + { + CScriptHandle *r = (CScriptHandle*)ref; + ref = r->m_ref; + type = r->m_type; + } + + Set(ref, type); + + return *this; +} + +bool CScriptHandle::operator==(const CScriptHandle &o) const +{ + if( m_ref == o.m_ref && + m_type == o.m_type ) + return true; + + // TODO: If type is not the same, we should attempt to do a dynamic cast, + // which may change the pointer for application registered classes + + return false; +} + +bool CScriptHandle::operator!=(const CScriptHandle &o) const +{ + return !(*this == o); +} + +bool CScriptHandle::Equals(void *ref, int typeId) const +{ + // Null handles are received as reference to a null handle + if( typeId == 0 ) + ref = 0; + + // Dereference handles to get the object + if( typeId & asTYPEID_OBJHANDLE ) + { + // Compare the actual reference + ref = *(void**)ref; + typeId &= ~asTYPEID_OBJHANDLE; + } + + // TODO: If typeId is not the same, we should attempt to do a dynamic cast, + // which may change the pointer for application registered classes + + if( ref == m_ref ) return true; + + return false; +} + +// AngelScript: used as '@obj = cast(ref);' +void CScriptHandle::Cast(void **outRef, int typeId) +{ + // If we hold a null handle, then just return null + if( m_type == 0 ) + { + *outRef = 0; + return; + } + + // It is expected that the outRef is always a handle + assert( typeId & asTYPEID_OBJHANDLE ); + + // Compare the type id of the actual object + typeId &= ~asTYPEID_OBJHANDLE; + asIScriptEngine *engine = m_type->GetEngine(); + asITypeInfo *type = engine->GetTypeInfoById(typeId); + + *outRef = 0; + + // RefCastObject will increment the refCount of the returned object if successful + engine->RefCastObject(m_ref, m_type, type, outRef); +} + +void CScriptHandle::EnumReferences(asIScriptEngine *inEngine) +{ + // If we're holding a reference, we'll notify the garbage collector of it + if (m_ref) + inEngine->GCEnumCallback(m_ref); + + // The object type itself is also garbage collected + if( m_type) + inEngine->GCEnumCallback(m_type); +} + +void CScriptHandle::ReleaseReferences(asIScriptEngine *inEngine) +{ + // Simply clear the content to release the references + Set(0, 0); +} + +void RegisterScriptHandle_Native(asIScriptEngine *engine) +{ + int r; + +#if AS_CAN_USE_CPP11 + // With C++11 it is possible to use asGetTypeTraits to automatically determine the flags that represent the C++ class + r = engine->RegisterObjectType("ref", sizeof(CScriptHandle), asOBJ_VALUE | asOBJ_ASHANDLE | asOBJ_GC | asGetTypeTraits()); assert( r >= 0 ); +#else + r = engine->RegisterObjectType("ref", sizeof(CScriptHandle), asOBJ_VALUE | asOBJ_ASHANDLE | asOBJ_GC | asOBJ_APP_CLASS_CDAK); assert( r >= 0 ); +#endif + r = engine->RegisterObjectBehaviour("ref", asBEHAVE_CONSTRUCT, "void f()", asFUNCTIONPR(Construct, (CScriptHandle *), void), asCALL_CDECL_OBJFIRST); assert( r >= 0 ); + r = engine->RegisterObjectBehaviour("ref", asBEHAVE_CONSTRUCT, "void f(const ref &in)", asFUNCTIONPR(Construct, (CScriptHandle *, const CScriptHandle &), void), asCALL_CDECL_OBJFIRST); assert( r >= 0 ); + r = engine->RegisterObjectBehaviour("ref", asBEHAVE_CONSTRUCT, "void f(const ?&in)", asFUNCTIONPR(Construct, (CScriptHandle *, void *, int), void), asCALL_CDECL_OBJFIRST); assert( r >= 0 ); + r = engine->RegisterObjectBehaviour("ref", asBEHAVE_DESTRUCT, "void f()", asFUNCTIONPR(Destruct, (CScriptHandle *), void), asCALL_CDECL_OBJFIRST); assert( r >= 0 ); + r = engine->RegisterObjectBehaviour("ref", asBEHAVE_ENUMREFS, "void f(int&in)", asMETHOD(CScriptHandle,EnumReferences), asCALL_THISCALL); assert(r >= 0); + r = engine->RegisterObjectBehaviour("ref", asBEHAVE_RELEASEREFS, "void f(int&in)", asMETHOD(CScriptHandle, ReleaseReferences), asCALL_THISCALL); assert(r >= 0); + r = engine->RegisterObjectMethod("ref", "void opCast(?&out)", asMETHODPR(CScriptHandle, Cast, (void **, int), void), asCALL_THISCALL); assert( r >= 0 ); + r = engine->RegisterObjectMethod("ref", "ref &opHndlAssign(const ref &in)", asMETHOD(CScriptHandle, operator=), asCALL_THISCALL); assert( r >= 0 ); + r = engine->RegisterObjectMethod("ref", "ref &opHndlAssign(const ?&in)", asMETHOD(CScriptHandle, Assign), asCALL_THISCALL); assert( r >= 0 ); + r = engine->RegisterObjectMethod("ref", "bool opEquals(const ref &in) const", asMETHODPR(CScriptHandle, operator==, (const CScriptHandle &) const, bool), asCALL_THISCALL); assert( r >= 0 ); + r = engine->RegisterObjectMethod("ref", "bool opEquals(const ?&in) const", asMETHODPR(CScriptHandle, Equals, (void*, int) const, bool), asCALL_THISCALL); assert( r >= 0 ); +} + +void CScriptHandle_Construct_Generic(asIScriptGeneric *gen) +{ + CScriptHandle *self = reinterpret_cast(gen->GetObject()); + new(self) CScriptHandle(); +} + +void CScriptHandle_ConstructCopy_Generic(asIScriptGeneric *gen) +{ + CScriptHandle *other = reinterpret_cast(gen->GetArgAddress(0)); + CScriptHandle *self = reinterpret_cast(gen->GetObject()); + new(self) CScriptHandle(*other); +} + +void CScriptHandle_ConstructVar_Generic(asIScriptGeneric *gen) +{ + void *ref = gen->GetArgAddress(0); + int typeId = gen->GetArgTypeId(0); + CScriptHandle *self = reinterpret_cast(gen->GetObject()); + Construct(self, ref, typeId); +} + +void CScriptHandle_Destruct_Generic(asIScriptGeneric *gen) +{ + CScriptHandle *self = reinterpret_cast(gen->GetObject()); + self->~CScriptHandle(); +} + +void CScriptHandle_Cast_Generic(asIScriptGeneric *gen) +{ + void **ref = reinterpret_cast(gen->GetArgAddress(0)); + int typeId = gen->GetArgTypeId(0); + CScriptHandle *self = reinterpret_cast(gen->GetObject()); + self->Cast(ref, typeId); +} + +void CScriptHandle_Assign_Generic(asIScriptGeneric *gen) +{ + CScriptHandle *other = reinterpret_cast(gen->GetArgAddress(0)); + CScriptHandle *self = reinterpret_cast(gen->GetObject()); + *self = *other; + gen->SetReturnAddress(self); +} + +void CScriptHandle_AssignVar_Generic(asIScriptGeneric *gen) +{ + void *ref = gen->GetArgAddress(0); + int typeId = gen->GetArgTypeId(0); + CScriptHandle *self = reinterpret_cast(gen->GetObject()); + self->Assign(ref, typeId); + gen->SetReturnAddress(self); +} + +void CScriptHandle_Equals_Generic(asIScriptGeneric *gen) +{ + CScriptHandle *other = reinterpret_cast(gen->GetArgAddress(0)); + CScriptHandle *self = reinterpret_cast(gen->GetObject()); + gen->SetReturnByte(*self == *other); +} + +void CScriptHandle_EqualsVar_Generic(asIScriptGeneric *gen) +{ + void *ref = gen->GetArgAddress(0); + int typeId = gen->GetArgTypeId(0); + CScriptHandle *self = reinterpret_cast(gen->GetObject()); + gen->SetReturnByte(self->Equals(ref, typeId)); +} + +void CScriptHandle_EnumReferences_Generic(asIScriptGeneric *gen) +{ + CScriptHandle *self = reinterpret_cast(gen->GetObject()); + self->EnumReferences(gen->GetEngine()); +} + +void CScriptHandle_ReleaseReferences_Generic(asIScriptGeneric *gen) +{ + CScriptHandle *self = reinterpret_cast(gen->GetObject()); + self->ReleaseReferences(gen->GetEngine()); +} + +void RegisterScriptHandle_Generic(asIScriptEngine *engine) +{ + int r; + + r = engine->RegisterObjectType("ref", sizeof(CScriptHandle), asOBJ_VALUE | asOBJ_ASHANDLE | asOBJ_GC | asOBJ_APP_CLASS_CDAK); assert( r >= 0 ); + r = engine->RegisterObjectBehaviour("ref", asBEHAVE_CONSTRUCT, "void f()", asFUNCTION(CScriptHandle_Construct_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectBehaviour("ref", asBEHAVE_CONSTRUCT, "void f(const ref &in)", asFUNCTION(CScriptHandle_ConstructCopy_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectBehaviour("ref", asBEHAVE_CONSTRUCT, "void f(const ?&in)", asFUNCTION(CScriptHandle_ConstructVar_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectBehaviour("ref", asBEHAVE_DESTRUCT, "void f()", asFUNCTION(CScriptHandle_Destruct_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectBehaviour("ref", asBEHAVE_ENUMREFS, "void f(int&in)", asFUNCTION(CScriptHandle_EnumReferences_Generic), asCALL_GENERIC); assert(r >= 0); + r = engine->RegisterObjectBehaviour("ref", asBEHAVE_RELEASEREFS, "void f(int&in)", asFUNCTION(CScriptHandle_ReleaseReferences_Generic), asCALL_GENERIC); assert(r >= 0); + r = engine->RegisterObjectMethod("ref", "void opCast(?&out)", asFUNCTION(CScriptHandle_Cast_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("ref", "ref &opHndlAssign(const ref &in)", asFUNCTION(CScriptHandle_Assign_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("ref", "ref &opHndlAssign(const ?&in)", asFUNCTION(CScriptHandle_AssignVar_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("ref", "bool opEquals(const ref &in) const", asFUNCTION(CScriptHandle_Equals_Generic), asCALL_GENERIC); assert( r >= 0 ); + r = engine->RegisterObjectMethod("ref", "bool opEquals(const ?&in) const", asFUNCTION(CScriptHandle_EqualsVar_Generic), asCALL_GENERIC); assert( r >= 0 ); +} + +void RegisterScriptHandle(asIScriptEngine *engine) +{ + if( strstr(asGetLibraryOptions(), "AS_MAX_PORTABILITY") ) + RegisterScriptHandle_Generic(engine); + else + RegisterScriptHandle_Native(engine); +} + + +END_AS_NAMESPACE diff --git a/extern/angelscript_addons/scripthandle/scripthandle.h b/extern/angelscript_addons/scripthandle/scripthandle.h new file mode 100644 index 0000000..7adf1b7 --- /dev/null +++ b/extern/angelscript_addons/scripthandle/scripthandle.h @@ -0,0 +1,69 @@ +#ifndef SCRIPTHANDLE_H +#define SCRIPTHANDLE_H + +#ifndef ANGELSCRIPT_H +// Avoid having to inform include path if header is already include before +#include +#endif + + +BEGIN_AS_NAMESPACE + +class CScriptHandle +{ +public: + // Constructors + CScriptHandle(); + CScriptHandle(const CScriptHandle &other); + CScriptHandle(void *ref, asITypeInfo *type); + ~CScriptHandle(); + + // Copy the stored value from another any object + CScriptHandle &operator=(const CScriptHandle &other); + + // Set the reference + void Set(void *ref, asITypeInfo *type); + + // Compare equalness + bool operator==(const CScriptHandle &o) const; + bool operator!=(const CScriptHandle &o) const; + bool Equals(void *ref, int typeId) const; + + // Dynamic cast to desired handle type + void Cast(void **outRef, int typeId); + + // Returns the type of the reference held + asITypeInfo *GetType() const; + int GetTypeId() const; + + // Get the reference + void *GetRef(); + + // GC callback + void EnumReferences(asIScriptEngine *engine); + void ReleaseReferences(asIScriptEngine *engine); + +protected: + // These functions need to have access to protected + // members in order to call them from the script engine + friend void Construct(CScriptHandle *self, void *ref, int typeId); + friend void RegisterScriptHandle_Native(asIScriptEngine *engine); + friend void CScriptHandle_AssignVar_Generic(asIScriptGeneric *gen); + + void ReleaseHandle(); + void AddRefHandle(); + + // These shouldn't be called directly by the + // application as they requires an active context + CScriptHandle(void *ref, int typeId); + CScriptHandle &Assign(void *ref, int typeId); + + void *m_ref; + asITypeInfo *m_type; +}; + +void RegisterScriptHandle(asIScriptEngine *engine); + +END_AS_NAMESPACE + +#endif diff --git a/src/ScriptResolving/AngelScript/AngelScripResolver.cpp b/src/ScriptResolving/AngelScript/AngelScripResolver.cpp index 5a4f9b1..16e7210 100644 --- a/src/ScriptResolving/AngelScript/AngelScripResolver.cpp +++ b/src/ScriptResolving/AngelScript/AngelScripResolver.cpp @@ -2,8 +2,11 @@ #define AS_USE_ACCESSORS #include "../../../extern/angelscript_addons/scriptarray/scriptarray.h" #undef AS_USE_ACCESSORS +#include +#include "../../../extern/angelscript_addons/scripthandle/scripthandle.h" #include "../../../extern/angelscript_addons/scripthelper/scripthelper.h" #include "../../../extern/angelscript_addons/scriptstdstring/scriptstdstring.h" +#include "TypeRegistry/BasicScriptClass.hpp" #include "TypeRegistry/Battling/RegisterExecutingAttack.hpp" #include "TypeRegistry/Battling/RegisterPokemonClass.hpp" #include "TypeRegistry/Library/RegisterGrowthRateTypes.hpp" @@ -17,7 +20,8 @@ CreatureLib::Battling::ScriptResolver* PkmnLib::Battling::BattleLibrary::CreateS return new AngelScripResolver(); } -void AngelScripResolver::Initialize(CreatureLib::Battling::BattleLibrary* library) { +void AngelScripResolver::Initialize(CreatureLib::Battling::BattleLibrary* arg) { + auto library = (PkmnLib::Battling::BattleLibrary*)arg; _engine = asCreateScriptEngine(); int32_t r = _engine->SetMessageCallback(asFUNCTION(MessageCallback), nullptr, asCALL_CDECL); @@ -25,10 +29,13 @@ void AngelScripResolver::Initialize(CreatureLib::Battling::BattleLibrary* librar throw CreatureException("Registering message callback failed."); _engine->SetEngineProperty(asEP_DISALLOW_EMPTY_LIST_ELEMENTS, true); - _engine->SetEngineProperty(asEP_DISALLOW_VALUE_ASSIGN_FOR_REF_TYPE, true); + _engine->SetEngineProperty(asEP_DISALLOW_VALUE_ASSIGN_FOR_REF_TYPE, false); + _engine->SetEngineProperty(asEP_ALLOW_UNSAFE_REFERENCES, true); _engine->SetEngineProperty(asEP_ALWAYS_IMPL_DEFAULT_CONSTRUCT, true); _engine->SetEngineProperty(asEP_AUTO_GARBAGE_COLLECT, false); _engine->SetEngineProperty(asEP_REQUIRE_ENUM_SCOPE, true); + _engine->SetEngineProperty(asEP_PROPERTY_ACCESSOR_MODE, 2); + RegisterStdString(_engine); @@ -38,11 +45,15 @@ void AngelScripResolver::Initialize(CreatureLib::Battling::BattleLibrary* librar r = _engine->RegisterGlobalFunction("void print(const string &in)", asFUNCTION(Print), asCALL_CDECL); if (r < 0) throw CreatureException("Registering print function failed."); + RegisterScriptHandle(_engine); + + _mainModule = _engine->GetModule("pkmn", asGM_ALWAYS_CREATE); RegisterTypes(); RegisterExceptionRoutines(_engine); - _mainModule = _engine->GetModule("pkmn", asGM_ALWAYS_CREATE); + auto staticLib = library->GetStaticLib(); + _engine->RegisterGlobalProperty("const StaticLibrary@ StaticLib", &staticLib); _contextPool = new ContextPool(_engine); } @@ -59,6 +70,9 @@ void AngelScripResolver::RegisterTypes() { // Register battle types RegisterPokemonClass::Register(_engine); RegisterExecutingAttack::Register(_engine); + + // Register base script + BasicScriptClass::Register(_engine); } AngelScriptTypeInfo* AngelScripResolver::GetTypeInfo(const std::string& name) { diff --git a/src/ScriptResolving/AngelScript/AngelScriptScript.hpp b/src/ScriptResolving/AngelScript/AngelScriptScript.hpp index d3a9917..f26c95f 100644 --- a/src/ScriptResolving/AngelScript/AngelScriptScript.hpp +++ b/src/ScriptResolving/AngelScript/AngelScriptScript.hpp @@ -3,6 +3,7 @@ #include #define ANGELSCRIPT_DLL_LIBRARY_IMPORT #include +#include #include "AngelScriptTypeInfo.hpp" #include "ContextPool.hpp" @@ -19,25 +20,6 @@ public: ~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); @@ -46,6 +28,89 @@ public: } ContextPool* GetContextPool() { return _ctxPool; } + +#define CALLHOOK(name, setup) \ + auto s = _type->Get##name(); \ + if (!s.Exists) \ + return; \ + auto ctx = _ctxPool->RequestContext(); \ + ctx->Prepare(s.Function); \ + ctx->SetObject(_obj); \ + setup; \ + auto scriptResult = ctx->Execute(); \ + if (scriptResult != 0) { \ + throw CreatureException("Script didn't finish properly; message " + std::to_string(scriptResult)); \ + } \ + _ctxPool->ReturnContextToPool(ctx); + + void Stack() override { CALLHOOK(Stack, {}); } + + void OnBeforeTurn(const CreatureLib::Battling::BaseTurnChoice* choice) override { + throw NotImplementedException(); //TODO + } + + void ChangeAttack(CreatureLib::Battling::AttackTurnChoice* choice, std::string* outAttack) override { + throw NotImplementedException(); //TODO + } + + void PreventAttack(CreatureLib::Battling::ExecutingAttack* attack, bool* outResult) override { + CALLHOOK(PreventAttack, { + ctx->SetArgObject(0, (void*)attack); + ctx->SetArgAddress(1, outResult); + }) + } + + void FailAttack(CreatureLib::Battling::ExecutingAttack* attack, bool* outFailed) override { + CALLHOOK(FailAttack, { + ctx->SetArgObject(0, (void*)attack); + ctx->SetArgAddress(1, outFailed); + }) + } + + void StopBeforeAttack(CreatureLib::Battling::ExecutingAttack* attack, bool* outResult) override { + CALLHOOK(StopBeforeAttack, { + ctx->SetArgObject(0, (void*)attack); + ctx->SetArgAddress(1, outResult); + }) + } + + void OnBeforeAttack(CreatureLib::Battling::ExecutingAttack* attack) override { Script::OnBeforeAttack(attack); } + void FailIncomingAttack(CreatureLib::Battling::ExecutingAttack* attack, CreatureLib::Battling::Creature* target, + bool* outResult) override { + Script::FailIncomingAttack(attack, target, outResult); + } + void IsInvulnerable(CreatureLib::Battling::ExecutingAttack* attack, CreatureLib::Battling::Creature* target, + bool* outResult) override { + Script::IsInvulnerable(attack, target, outResult); + } + void OnAttackMiss(CreatureLib::Battling::ExecutingAttack* attack, + CreatureLib::Battling::Creature* target) override { + Script::OnAttackMiss(attack, target); + } + void ChangeAttackType(CreatureLib::Battling::ExecutingAttack* attack, CreatureLib::Battling::Creature* target, + uint8_t hitNumber, uint8_t* outType) override { + Script::ChangeAttackType(attack, target, hitNumber, outType); + } + void OnStatusMove(const CreatureLib::Battling::ExecutingAttack* attack, CreatureLib::Battling::Creature* target, + uint8_t hitNumber) override { + Script::OnStatusMove(attack, target, hitNumber); + } + void PreventSecondaryEffects(const CreatureLib::Battling::ExecutingAttack* attack, + CreatureLib::Battling::Creature* target, uint8_t hitNumber, bool* outResult) override { + Script::PreventSecondaryEffects(attack, target, hitNumber, outResult); + } + void OnSecondaryEffect(const CreatureLib::Battling::ExecutingAttack* attack, + CreatureLib::Battling::Creature* target, uint8_t hitNumber) override { + Script::OnSecondaryEffect(attack, target, hitNumber); + } + void OnAfterHits(const CreatureLib::Battling::ExecutingAttack* attack, + CreatureLib::Battling::Creature* target) override { + Script::OnAfterHits(attack, target); + } + void PreventSelfSwitch(const CreatureLib::Battling::SwitchTurnChoice* choice, bool* outResult) override { + Script::PreventSelfSwitch(choice, outResult); + } }; +#undef CALLHOOK #endif // PKMNLIB_ANGELSCRIPTSCRIPT_HPP diff --git a/src/ScriptResolving/AngelScript/AngelScriptTypeInfo.hpp b/src/ScriptResolving/AngelScript/AngelScriptTypeInfo.hpp index be3ee48..0aaf3e4 100644 --- a/src/ScriptResolving/AngelScript/AngelScriptTypeInfo.hpp +++ b/src/ScriptResolving/AngelScript/AngelScriptTypeInfo.hpp @@ -2,42 +2,59 @@ #define PKMNLIB_ANGELSCRIPTTYPEINFO_HPP #define ANGELSCRIPT_DLL_LIBRARY_IMPORT -#include -#include #include +#include +#include +#include class AngelScriptTypeInfo { private: asITypeInfo* _type = nullptr; std::unordered_map _functions; + struct FunctionInfo { + bool Exists = false; + asIScriptFunction* Function = nullptr; + }; + + FunctionInfo Initialize(const std::string& decl) { + auto val = _type->GetMethodByDecl(decl.c_str(), false); + if (val == nullptr){ + return FunctionInfo{.Exists = false, .Function = nullptr}; + } + if (!val->IsOverride()){ + return FunctionInfo{.Exists = false, .Function = nullptr}; + } + return FunctionInfo{.Exists = true, .Function = val}; + } + public: - explicit AngelScriptTypeInfo(asITypeInfo* type) : _type(type){} - ~AngelScriptTypeInfo(){ - for (const auto& f: _functions){ + explicit AngelScriptTypeInfo(asITypeInfo* type) : _type(type) {} + ~AngelScriptTypeInfo() { + for (const auto& f : _functions) { f.second->Release(); } _functions.clear(); } - asIScriptFunction* GetFunction(const std::string& functionName){ + asIScriptFunction* GetFunction(const std::string& functionName) { auto find = _functions.find(functionName); - if (find != _functions.end()){ + if (find != _functions.end()) { return find->second; } auto func = _type->GetMethodByName(functionName.c_str()); - if (func != nullptr){ + if (func != nullptr) { func->AddRef(); } _functions.insert({functionName, func}); return func; } - asIScriptObject* Instantiate(asIScriptContext* ctx){ + asIScriptObject* Instantiate(asIScriptContext* ctx) { auto factory = _type->GetFactoryByIndex(0); ctx->Prepare(factory); auto result = ctx->Execute(); - if (result != asEXECUTION_FINISHED){ + if (result != asEXECUTION_FINISHED) { throw CreatureException("Instantiation failed."); } asIScriptObject* obj = *(asIScriptObject**)ctx->GetAddressOfReturnValue(); @@ -45,6 +62,16 @@ public: return obj; } +#define SCRIPT_HOOK_FUNCTION(name, decl) \ +private: FunctionInfo __##name = Initialize(decl); \ +public: const FunctionInfo& Get##name() const { return __##name; } + +SCRIPT_HOOK_FUNCTION(Stack, "void Stack()"); +SCRIPT_HOOK_FUNCTION(PreventAttack, "void PreventAttack(ExecutingMove@ attack, bool& result)"); +SCRIPT_HOOK_FUNCTION(FailAttack, "void FailAttack(ExecutingMove@ attack, bool& result)"); +SCRIPT_HOOK_FUNCTION(StopBeforeAttack, "void StopBeforeAttack(ExecutingMove@ attack, bool& result)"); + }; +#undef SCRIPT_HOOK_FUNCTION #endif // PKMNLIB_ANGELSCRIPTTYPEINFO_HPP diff --git a/src/ScriptResolving/AngelScript/TypeRegistry/BasicScriptClass.cpp b/src/ScriptResolving/AngelScript/TypeRegistry/BasicScriptClass.cpp new file mode 100644 index 0000000..2150e19 --- /dev/null +++ b/src/ScriptResolving/AngelScript/TypeRegistry/BasicScriptClass.cpp @@ -0,0 +1,15 @@ +#include "BasicScriptClass.hpp" +#include + +void BasicScriptClass::Register(asIScriptEngine* engine) { + [[maybe_unused]] int r = engine->GetModuleByIndex(0)->AddScriptSection("PkmnScript", R"( +shared abstract class PkmnScript { + void Stack(){}; + void PreventAttack(ExecutingMove@ attack, bool& result){}; + void FailAttack(ExecutingMove@ attack, bool& result){}; + void StopBeforeAttack(ExecutingMove@ attack, bool& result){}; +} +)"); + assert(r >= 0); + +} diff --git a/src/ScriptResolving/AngelScript/TypeRegistry/BasicScriptClass.hpp b/src/ScriptResolving/AngelScript/TypeRegistry/BasicScriptClass.hpp new file mode 100644 index 0000000..4033748 --- /dev/null +++ b/src/ScriptResolving/AngelScript/TypeRegistry/BasicScriptClass.hpp @@ -0,0 +1,10 @@ +#ifndef PKMNLIB_BASICSCRIPTCLASS_HPP +#define PKMNLIB_BASICSCRIPTCLASS_HPP + +#include +class BasicScriptClass { +public: + static void Register(asIScriptEngine* engine); +}; + +#endif // PKMNLIB_BASICSCRIPTCLASS_HPP diff --git a/src/ScriptResolving/AngelScript/TypeRegistry/Library/RegisterStaticLibraryTypes.cpp b/src/ScriptResolving/AngelScript/TypeRegistry/Library/RegisterStaticLibraryTypes.cpp index 6ce9415..7935f64 100644 --- a/src/ScriptResolving/AngelScript/TypeRegistry/Library/RegisterStaticLibraryTypes.cpp +++ b/src/ScriptResolving/AngelScript/TypeRegistry/Library/RegisterStaticLibraryTypes.cpp @@ -10,10 +10,13 @@ void RegisterStaticLibraryTypes::Register(asIScriptEngine* engine) { void RegisterStaticLibraryTypes::RegisterLibrarySettingsType(asIScriptEngine* engine) { [[maybe_unused]] int r = engine->RegisterObjectType("LibrarySettings", 0, asOBJ_REF | asOBJ_NOCOUNT); r = engine->RegisterObjectMethod("LibrarySettings", "uint8 get_MaximalLevel() const property", - asMETHOD(CreatureLib::Library::LibrarySettings, GetMaximalLevel), asCALL_THISCALL); + asMETHOD(PkmnLib::Library::LibrarySettings, GetMaximalLevel), asCALL_THISCALL); assert(r >= 0); r = engine->RegisterObjectMethod("LibrarySettings", "uint8 get_MaximalMoves() const property", - asMETHOD(CreatureLib::Library::LibrarySettings, GetMaximalMoves), asCALL_THISCALL); + asMETHOD(PkmnLib::Library::LibrarySettings, GetMaximalMoves), asCALL_THISCALL); + assert(r >= 0); + r = engine->RegisterObjectMethod("LibrarySettings", "uint16 get_ShinyRate() const property", + asMETHOD(PkmnLib::Library::LibrarySettings, GetShinyRate), asCALL_THISCALL); assert(r >= 0); } diff --git a/tests/ScriptTests/BaseScriptClassTests.cpp b/tests/ScriptTests/BaseScriptClassTests.cpp new file mode 100644 index 0000000..184f61e --- /dev/null +++ b/tests/ScriptTests/BaseScriptClassTests.cpp @@ -0,0 +1,119 @@ +#ifdef TESTS_BUILD +#include "../../extern/catch.hpp" +#include "../../src/Battling/Pokemon/CreatePokemon.hpp" +#include "../../src/ScriptResolving/AngelScript/AngelScripResolver.hpp" +#include "../TestLibrary/TestLibrary.hpp" + +#define AS_CLASS(name, contents) { #name, "class " #name " : PkmnScript { " contents "}" } + +static std::unordered_map _scripts = std::unordered_map { + AS_CLASS(blankScript, ), + AS_CLASS( stackScript, "int value = 0; void Stack() override { value++; } int GetValue() { return value; }"), + {"doubleInheritanceScript", R"( +class doubleInheritanceScriptBase : PkmnScript { + int value = 0; + void Stack() override{ + value++; + } + + int GetValue(){ return value; } +} +class doubleInheritanceScript : doubleInheritanceScriptBase {} +)"}, + AS_CLASS( preventAttackScript, R"( +void PreventAttack(ExecutingMove@ attack, bool& result) override{ + result = !result; +})"), + AS_CLASS( stopBeforeAttackScript, R"( +void StopBeforeAttack(ExecutingMove@ attack, bool& result) override{ + result = !result; +})"), + +}; + +static const char* _testLoadFunc(const char* name) { return _scripts[name]; } + +static AngelScripResolver* _resolverCache = nullptr; +static AngelScripResolver* GetScriptResolver(PkmnLib::Battling::BattleLibrary* mainLib) { + if (_resolverCache == nullptr) { + _resolverCache = dynamic_cast(PkmnLib::Battling::BattleLibrary::CreateScriptResolver()); + _resolverCache->Initialize(mainLib); + _resolverCache->SetCreateFunction(&_testLoadFunc); + for (auto kv : _scripts) { + _resolverCache->CreateScript(kv.first); + } + _resolverCache->FinalizeModule(); + } + return _resolverCache; +} + +static AngelScriptScript* GetScript(PkmnLib::Battling::BattleLibrary* mainLib, const char* scriptName) { + auto lib = GetScriptResolver(mainLib); + auto s = lib->LoadScript(AngelScripResolver::ScriptCategory::Creature, scriptName); + auto script = dynamic_cast(s); + return script; +} + +TEST_CASE("Invoke non-implemented script function") { + auto mainLib = TestLibrary::GetLibrary(); + auto script = GetScript(mainLib, "blankScript"); + script->Stack(); + delete script; +} + +TEST_CASE("Invoke Stack script function") { + auto mainLib = TestLibrary::GetLibrary(); + auto script = GetScript(mainLib, "stackScript"); + for (int i = 1; i <= 10; i++) { + script->Stack(); + + auto ctxPool = script->GetContextPool(); + auto ctx = ctxPool->RequestContext(); + script->PrepareMethod("GetValue", ctx); + REQUIRE(ctx->Execute() == asEXECUTION_FINISHED); + REQUIRE(ctx->GetReturnDWord() == i); + ctxPool->ReturnContextToPool(ctx); + } + + delete script; +} + +TEST_CASE("Invoke Stack script function with implementation in base class") { + auto mainLib = TestLibrary::GetLibrary(); + auto script = GetScript(mainLib, "doubleInheritanceScript"); + for (int i = 1; i <= 10; i++) { + script->Stack(); + + auto ctxPool = script->GetContextPool(); + auto ctx = ctxPool->RequestContext(); + script->PrepareMethod("GetValue", ctx); + REQUIRE(ctx->Execute() == asEXECUTION_FINISHED); + REQUIRE(ctx->GetReturnDWord() == i); + ctxPool->ReturnContextToPool(ctx); + } + + delete script; +} + +TEST_CASE("Invoke preventAttackScript script function") { + auto mainLib = TestLibrary::GetLibrary(); + auto script = GetScript(mainLib, "preventAttackScript"); + bool b = false; + script->PreventAttack(nullptr, &b); + REQUIRE(b); + + delete script; +} + +TEST_CASE("Invoke StopBeforeAttack script function") { + auto mainLib = TestLibrary::GetLibrary(); + auto script = GetScript(mainLib, "stopBeforeAttackScript"); + bool b = false; + script->StopBeforeAttack(nullptr, &b); + REQUIRE(b); + + delete script; +} + + +#endif \ No newline at end of file diff --git a/tests/ScriptTests/ScriptResolverTests.cpp b/tests/ScriptTests/ScriptResolverTests.cpp index 3e5dce6..6e7aab3 100644 --- a/tests/ScriptTests/ScriptResolverTests.cpp +++ b/tests/ScriptTests/ScriptResolverTests.cpp @@ -89,41 +89,4 @@ TEST_CASE("Build script resolver, create object, invoke addition method") { 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