Support for saving compiled AngelScript to either file or RAM, so we can reuse it.
continuous-integration/drone/push Build is failing Details

This commit is contained in:
Deukhoofd 2020-04-11 14:42:49 +02:00
parent 0b045db811
commit bf36103c11
Signed by: Deukhoofd
GPG Key ID: ADF2E9256009EDCE
6 changed files with 228 additions and 15 deletions

View File

@ -6,6 +6,8 @@
#include "../../../extern/angelscript_addons/scripthandle/scripthandle.h" #include "../../../extern/angelscript_addons/scripthandle/scripthandle.h"
#include "../../../extern/angelscript_addons/scripthelper/scripthelper.h" #include "../../../extern/angelscript_addons/scripthelper/scripthelper.h"
#include "../../../extern/angelscript_addons/scriptstdstring/scriptstdstring.h" #include "../../../extern/angelscript_addons/scriptstdstring/scriptstdstring.h"
#include "ByteCodeHandling/FileByteCodeStream.hpp"
#include "ByteCodeHandling/MemoryByteCodeStream.hpp"
#include "TypeRegistry/BasicScriptClass.hpp" #include "TypeRegistry/BasicScriptClass.hpp"
#include "TypeRegistry/Battling/RegisterBattleClass.hpp" #include "TypeRegistry/Battling/RegisterBattleClass.hpp"
#include "TypeRegistry/Battling/RegisterBattleLibrary.hpp" #include "TypeRegistry/Battling/RegisterBattleLibrary.hpp"
@ -25,26 +27,19 @@ CreatureLib::Battling::ScriptResolver* PkmnLib::Battling::BattleLibrary::CreateS
return new AngelScripResolver(); return new AngelScripResolver();
} }
static void TranslateException(asIScriptContext *ctx, void* /*userParam*/) static void TranslateException(asIScriptContext* ctx, void* /*userParam*/) {
{ try {
try
{
// Retrow the original exception so we can catch it again // Retrow the original exception so we can catch it again
throw; throw;
} } catch (std::exception& e) {
catch( std::exception &e )
{
// Tell the VM the type of exception that occurred // Tell the VM the type of exception that occurred
ctx->SetException(e.what()); ctx->SetException(e.what());
} } catch (...) {
catch(...)
{
// The callback must not allow any exception to be thrown, but it is not necessary // The callback must not allow any exception to be thrown, but it is not necessary
// to explicitly set an exception string if the default exception string is sufficient // to explicitly set an exception string if the default exception string is sufficient
} }
} }
void AngelScripResolver::Initialize(CreatureLib::Battling::BattleLibrary* arg) { void AngelScripResolver::Initialize(CreatureLib::Battling::BattleLibrary* arg) {
for (auto scriptCategory : ScriptCategoryHelper::GetValues()) { for (auto scriptCategory : ScriptCategoryHelper::GetValues()) {
_typeDatabase.Insert(scriptCategory, {}); _typeDatabase.Insert(scriptCategory, {});
@ -125,7 +120,7 @@ void AngelScripResolver::MessageCallback(const asSMessageInfo* msg, void* param)
} }
CreatureLib::Battling::Script* AngelScripResolver::LoadScript(ScriptCategory category, const ConstString& scriptName) { CreatureLib::Battling::Script* AngelScripResolver::LoadScript(ScriptCategory category, const ConstString& scriptName) {
Dictionary<uint32_t, AngelScriptTypeInfo*> innerDb; Dictionary<ConstString, AngelScriptTypeInfo*> innerDb;
if (!_typeDatabase.TryGet(category, innerDb)) { if (!_typeDatabase.TryGet(category, innerDb)) {
_typeDatabase.Insert(category, innerDb); _typeDatabase.Insert(category, innerDb);
return nullptr; return nullptr;
@ -200,3 +195,58 @@ void AngelScripResolver::FinalizeModule() {
void AngelScripResolver::CreateScript(const char* name, const char* script) { void AngelScripResolver::CreateScript(const char* name, const char* script) {
_builder.AddSectionFromMemory(name, script); _builder.AddSectionFromMemory(name, script);
} }
void AngelScripResolver::WriteByteCodeToFile(const char* file, bool stripDebugInfo) {
FILE* wFile = fopen(file, "w");
auto stream = new FileByteCodeStream(wFile);
_mainModule->SaveByteCode(stream, stripDebugInfo);
fclose(wFile);
delete stream;
}
void AngelScripResolver::LoadByteCodeFromFile(
const char* file, const Dictionary<ScriptCategory, Dictionary<ConstString, const char*>>& types) {
FILE* rFile = fopen(file, "r");
auto stream = new FileByteCodeStream(rFile);
LoadByteCode(stream, types);
fclose(rFile);
delete stream;
//_typeDatabase = types;
}
uint8_t* AngelScripResolver::WriteByteCodeToMemory(size_t& size, bool stripDebugInfo) {
auto stream = new MemoryByteCodeStream();
auto result = _mainModule->SaveByteCode(stream, stripDebugInfo);
Assert(result == asSUCCESS);
auto arr = stream->GetOut();
size = stream->GetWrittenSize();
arr = static_cast<uint8_t*>(realloc(arr, size));
delete stream;
return arr;
}
void AngelScripResolver::LoadByteCodeFromMemory(
uint8_t* byte, size_t size, const Dictionary<ScriptCategory, Dictionary<ConstString, const char*>>& types) {
auto stream = new MemoryByteCodeStream(byte, size);
LoadByteCode(stream, types);
delete stream;
}
void AngelScripResolver::LoadByteCode(asIBinaryStream* stream,
const Dictionary<ScriptCategory, Dictionary<ConstString, const char*>>& types) {
int result = _mainModule->LoadByteCode(stream);
Assert(result == asSUCCESS);
auto typeCount = _mainModule->GetObjectTypeCount();
Dictionary<uint32_t, asITypeInfo*> objectTypes;
for (asUINT i = 0; i < typeCount; i++) {
auto t = _mainModule->GetObjectTypeByIndex(i);
objectTypes.Insert(ConstString::GetHash(t->GetName()), t);
}
Dictionary<ScriptCategory, Dictionary<ConstString, AngelScriptTypeInfo*>> typeDatabase;
for (auto& innerDb : types) {
Dictionary<ConstString, AngelScriptTypeInfo*> newInnerDb;
for (auto& val : innerDb.second) {
auto decl = val.second;
auto type = objectTypes[ConstString::GetHash(decl)];
newInnerDb.Insert(val.first, new AngelScriptTypeInfo(val.first, type));
}
typeDatabase.Insert(innerDb.first, newInnerDb);
}
_typeDatabase = typeDatabase;
}

View File

@ -2,8 +2,8 @@
#define PKMNLIB_ANGELSCRIPRESOLVER_HPP #define PKMNLIB_ANGELSCRIPRESOLVER_HPP
#include <CreatureLib/Battling/ScriptHandling/ScriptResolver.hpp> #include <CreatureLib/Battling/ScriptHandling/ScriptResolver.hpp>
#include "../../Battling/Library/BattleLibrary.hpp"
#include "../../../extern/angelscript_addons/scriptbuilder/scriptbuilder.h" #include "../../../extern/angelscript_addons/scriptbuilder/scriptbuilder.h"
#include "../../Battling/Library/BattleLibrary.hpp"
#define ANGELSCRIPT_DLL_LIBRARY_IMPORT #define ANGELSCRIPT_DLL_LIBRARY_IMPORT
#include <angelscript.h> #include <angelscript.h>
@ -21,15 +21,17 @@ private:
static void MessageCallback(const asSMessageInfo* msg, void* param); static void MessageCallback(const asSMessageInfo* msg, void* param);
static void Print(const std::string& str) { std::cout << str << std::endl; } static void Print(const std::string& str) { std::cout << str << std::endl; }
Dictionary<ScriptCategory, Dictionary<uint32_t, AngelScriptTypeInfo*>> _typeDatabase; Dictionary<ScriptCategory, Dictionary<ConstString, AngelScriptTypeInfo*>> _typeDatabase;
void RegisterTypes(); void RegisterTypes();
void LoadByteCode(asIBinaryStream* stream,
const Dictionary<ScriptCategory, Dictionary<ConstString, const char*>>& types);
public: public:
~AngelScripResolver() override { ~AngelScripResolver() override {
delete _contextPool; delete _contextPool;
for (const auto& category : _typeDatabase) { for (const auto& category : _typeDatabase) {
for (const auto& type : category.second){ for (const auto& type : category.second) {
delete type.second; delete type.second;
} }
} }
@ -42,5 +44,16 @@ public:
void FinalizeModule(); void FinalizeModule();
CreatureLib::Battling::Script* LoadScript(ScriptCategory category, const ConstString& scriptName) override; CreatureLib::Battling::Script* LoadScript(ScriptCategory category, const ConstString& scriptName) override;
void WriteByteCodeToFile(const char* file, bool stripDebugInfo = false);
void LoadByteCodeFromFile(const char* file,
const Dictionary<ScriptCategory, Dictionary<ConstString, const char*>>& types);
uint8_t* WriteByteCodeToMemory(size_t& size, bool stripDebugInfo = false);
void LoadByteCodeFromMemory(uint8_t*, size_t size,
const Dictionary<ScriptCategory, Dictionary<ConstString, const char*>>& types);
const Dictionary<ScriptCategory, Dictionary<ConstString, AngelScriptTypeInfo*>>& GetTypeDatabase() const noexcept {
return _typeDatabase;
}
}; };
#endif // PKMNLIB_ANGELSCRIPRESOLVER_HPP #endif // PKMNLIB_ANGELSCRIPRESOLVER_HPP

View File

@ -45,6 +45,10 @@ public:
const ConstString& GetName() const noexcept { return _name; } const ConstString& GetName() const noexcept { return _name; }
const char* GetDecl(){
return _type->GetName();
}
asIScriptFunction* GetFunction(const ConstString& functionName) { asIScriptFunction* GetFunction(const ConstString& functionName) {
asIScriptFunction* func; asIScriptFunction* func;
if (_functions.TryGet(functionName, func)) { if (_functions.TryGet(functionName, func)) {

View File

@ -0,0 +1,25 @@
#ifndef PKMNLIB_FILEBYTECODESTREAM_HPP
#define PKMNLIB_FILEBYTECODESTREAM_HPP
#include <angelscript.h>
#include <cstdio>
class FileByteCodeStream : public asIBinaryStream {
private:
FILE* _file;
public:
explicit FileByteCodeStream(FILE* file) : _file(file) {}
int Write(const void* ptr, asUINT size) {
if (size == 0)
return 0;
return fwrite(ptr, size, 1, _file);
}
int Read(void* ptr, asUINT size) {
if (size == 0)
return 0;
return fread(ptr, size, 1, _file);
}
};
#endif // PKMNLIB_FILEBYTECODESTREAM_HPP

View File

@ -0,0 +1,57 @@
#ifndef PKMNLIB_MEMORYBYTECODESTREAM_HPP
#define PKMNLIB_MEMORYBYTECODESTREAM_HPP
#include <angelscript.h>
#include <vector>
class MemoryByteCodeStream : public asIBinaryStream {
private:
uint8_t* _out;
size_t _index = 0;
size_t _size = 0;
size_t _capacity = 0;
#define MEM_STEPS 256
public:
MemoryByteCodeStream() : _out((uint8_t*)malloc(MEM_STEPS * sizeof(uint8_t) )), _capacity(MEM_STEPS){};
MemoryByteCodeStream(uint8_t* in, size_t size) : _out(in), _size(size) {}
uint8_t* GetOut() const { return _out; }
size_t GetWrittenSize() const { return _size; }
int Write(const void* ptr, asUINT size) final {
if (size == 0)
return 0;
auto initialSize = _size;
if (_size + size >= _capacity) {
_capacity += MEM_STEPS;
auto newLoc = realloc(_out, _capacity * sizeof(uint8_t));
if (newLoc == nullptr) {
throw CreatureException("Out of memory.");
}
_out = (uint8_t*)newLoc;
}
auto start = reinterpret_cast<const uint8_t*>(ptr);
for (auto index = 0; index < size; index++) {
_out[initialSize + index] = *(start + index);
}
_size += size;
return size;
}
int Read(void* ptr, asUINT size) final {
if (size == 0)
return 0;
auto start = reinterpret_cast<uint8_t*>(ptr);
auto toRead = size;
if (_index + toRead > _size) {
toRead = _size - _index;
}
for (auto index = 0; index < toRead; index++) {
*(start + index) = _out[_index + index];
}
_index += toRead;
return toRead;
}
};
#undef MEM_STEPS
#endif // PKMNLIB_MEMORYBYTECODESTREAM_HPP

View File

@ -79,4 +79,68 @@ TEST_CASE("Build script resolver, create object, invoke addition method") {
delete lib; delete lib;
} }
TEST_CASE("Get a script resolver, save the byte code to memory, create new script resolver from it") {
auto originLib = dynamic_cast<AngelScripResolver*>(PkmnLib::Battling::BattleLibrary::CreateScriptResolver());
originLib->Initialize(TestLibrary::GetLibrary());
originLib->CreateScript("testScript1" , _scripts["testScript1"]);
originLib->FinalizeModule();
size_t size;
auto byteCode = originLib->WriteByteCodeToMemory(size);
auto typeDatabase = originLib->GetTypeDatabase();
Dictionary<ScriptCategory, Dictionary<ConstString, const char*>> types;
for (auto& innerDb: typeDatabase){
Dictionary<ConstString, const char*> newInnerDb;
for (auto& kv : innerDb.second){
INFO(kv.second->GetDecl());
newInnerDb.Insert(kv.first, kv.second->GetDecl());
}
types.Insert(innerDb.first, newInnerDb);
}
auto newLib = dynamic_cast<AngelScripResolver*>(PkmnLib::Battling::BattleLibrary::CreateScriptResolver());
newLib->Initialize(TestLibrary::GetLibrary());
newLib->LoadByteCodeFromMemory(byteCode, size, types);
auto obj =
dynamic_cast<AngelScriptScript*>(newLib->LoadScript(ScriptCategory::Creature, "testScript1"_cnc));
REQUIRE(obj != nullptr);
delete obj;
delete originLib;
delete newLib;
free(byteCode);
}
TEST_CASE("Get a script resolver, save the byte code to file, create new script resolver from it") {
const char* TestFileName = "compiledAngelScriptTestFile.bin";
auto originLib = dynamic_cast<AngelScripResolver*>(PkmnLib::Battling::BattleLibrary::CreateScriptResolver());
originLib->Initialize(TestLibrary::GetLibrary());
originLib->CreateScript("testScript1" , _scripts["testScript1"]);
originLib->FinalizeModule();
originLib->WriteByteCodeToFile(TestFileName);
auto typeDatabase = originLib->GetTypeDatabase();
Dictionary<ScriptCategory, Dictionary<ConstString, const char*>> types;
for (auto& innerDb: typeDatabase){
Dictionary<ConstString, const char*> newInnerDb;
for (auto& kv : innerDb.second){
INFO(kv.second->GetDecl());
newInnerDb.Insert(kv.first, kv.second->GetDecl());
}
types.Insert(innerDb.first, newInnerDb);
}
auto newLib = dynamic_cast<AngelScripResolver*>(PkmnLib::Battling::BattleLibrary::CreateScriptResolver());
newLib->Initialize(TestLibrary::GetLibrary());
newLib->LoadByteCodeFromFile(TestFileName, types);
auto obj =
dynamic_cast<AngelScriptScript*>(newLib->LoadScript(ScriptCategory::Creature, "testScript1"_cnc));
REQUIRE(obj != nullptr);
delete obj;
delete originLib;
delete newLib;
remove(TestFileName);
}
#endif #endif