From bf36103c115bf7b51505a9f2d1e7637ad477c737 Mon Sep 17 00:00:00 2001
From: Deukhoofd <deukhoofd@gmail.com>
Date: Sat, 11 Apr 2020 14:42:49 +0200
Subject: [PATCH] Support for saving compiled AngelScript to either file or
 RAM, so we can reuse it.

---
 .../AngelScript/AngelScripResolver.cpp        | 74 ++++++++++++++++---
 .../AngelScript/AngelScripResolver.hpp        | 19 ++++-
 .../AngelScript/AngelScriptTypeInfo.hpp       |  4 +
 .../ByteCodeHandling/FileByteCodeStream.hpp   | 25 +++++++
 .../ByteCodeHandling/MemoryByteCodeStream.hpp | 57 ++++++++++++++
 tests/ScriptTests/ScriptResolverTests.cpp     | 64 ++++++++++++++++
 6 files changed, 228 insertions(+), 15 deletions(-)
 create mode 100644 src/ScriptResolving/AngelScript/ByteCodeHandling/FileByteCodeStream.hpp
 create mode 100644 src/ScriptResolving/AngelScript/ByteCodeHandling/MemoryByteCodeStream.hpp

diff --git a/src/ScriptResolving/AngelScript/AngelScripResolver.cpp b/src/ScriptResolving/AngelScript/AngelScripResolver.cpp
index eaedf68..4da49e5 100644
--- a/src/ScriptResolving/AngelScript/AngelScripResolver.cpp
+++ b/src/ScriptResolving/AngelScript/AngelScripResolver.cpp
@@ -6,6 +6,8 @@
 #include "../../../extern/angelscript_addons/scripthandle/scripthandle.h"
 #include "../../../extern/angelscript_addons/scripthelper/scripthelper.h"
 #include "../../../extern/angelscript_addons/scriptstdstring/scriptstdstring.h"
+#include "ByteCodeHandling/FileByteCodeStream.hpp"
+#include "ByteCodeHandling/MemoryByteCodeStream.hpp"
 #include "TypeRegistry/BasicScriptClass.hpp"
 #include "TypeRegistry/Battling/RegisterBattleClass.hpp"
 #include "TypeRegistry/Battling/RegisterBattleLibrary.hpp"
@@ -25,26 +27,19 @@ CreatureLib::Battling::ScriptResolver* PkmnLib::Battling::BattleLibrary::CreateS
     return new AngelScripResolver();
 }
 
-static void TranslateException(asIScriptContext *ctx, void* /*userParam*/)
-{
-    try
-    {
+static void TranslateException(asIScriptContext* ctx, void* /*userParam*/) {
+    try {
         // Retrow the original exception so we can catch it again
         throw;
-    }
-    catch( std::exception &e )
-    {
+    } catch (std::exception& e) {
         // Tell the VM the type of exception that occurred
         ctx->SetException(e.what());
-    }
-    catch(...)
-    {
+    } catch (...) {
         // 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
     }
 }
 
-
 void AngelScripResolver::Initialize(CreatureLib::Battling::BattleLibrary* arg) {
     for (auto scriptCategory : ScriptCategoryHelper::GetValues()) {
         _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) {
-    Dictionary<uint32_t, AngelScriptTypeInfo*> innerDb;
+    Dictionary<ConstString, AngelScriptTypeInfo*> innerDb;
     if (!_typeDatabase.TryGet(category, innerDb)) {
         _typeDatabase.Insert(category, innerDb);
         return nullptr;
@@ -200,3 +195,58 @@ void AngelScripResolver::FinalizeModule() {
 void AngelScripResolver::CreateScript(const char* name, const char* 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;
+}
diff --git a/src/ScriptResolving/AngelScript/AngelScripResolver.hpp b/src/ScriptResolving/AngelScript/AngelScripResolver.hpp
index 67a084f..d2495de 100644
--- a/src/ScriptResolving/AngelScript/AngelScripResolver.hpp
+++ b/src/ScriptResolving/AngelScript/AngelScripResolver.hpp
@@ -2,8 +2,8 @@
 #define PKMNLIB_ANGELSCRIPRESOLVER_HPP
 
 #include <CreatureLib/Battling/ScriptHandling/ScriptResolver.hpp>
-#include "../../Battling/Library/BattleLibrary.hpp"
 #include "../../../extern/angelscript_addons/scriptbuilder/scriptbuilder.h"
+#include "../../Battling/Library/BattleLibrary.hpp"
 
 #define ANGELSCRIPT_DLL_LIBRARY_IMPORT
 #include <angelscript.h>
@@ -21,15 +21,17 @@ private:
 
     static void MessageCallback(const asSMessageInfo* msg, void* param);
     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 LoadByteCode(asIBinaryStream* stream,
+                      const Dictionary<ScriptCategory, Dictionary<ConstString, const char*>>& types);
 
 public:
     ~AngelScripResolver() override {
         delete _contextPool;
         for (const auto& category : _typeDatabase) {
-            for (const auto& type : category.second){
+            for (const auto& type : category.second) {
                 delete type.second;
             }
         }
@@ -42,5 +44,16 @@ public:
     void FinalizeModule();
 
     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
diff --git a/src/ScriptResolving/AngelScript/AngelScriptTypeInfo.hpp b/src/ScriptResolving/AngelScript/AngelScriptTypeInfo.hpp
index 3508fc8..cadab39 100644
--- a/src/ScriptResolving/AngelScript/AngelScriptTypeInfo.hpp
+++ b/src/ScriptResolving/AngelScript/AngelScriptTypeInfo.hpp
@@ -45,6 +45,10 @@ public:
 
     const ConstString& GetName() const noexcept { return _name; }
 
+    const char* GetDecl(){
+        return _type->GetName();
+    }
+
     asIScriptFunction* GetFunction(const ConstString& functionName) {
         asIScriptFunction* func;
         if (_functions.TryGet(functionName, func)) {
diff --git a/src/ScriptResolving/AngelScript/ByteCodeHandling/FileByteCodeStream.hpp b/src/ScriptResolving/AngelScript/ByteCodeHandling/FileByteCodeStream.hpp
new file mode 100644
index 0000000..cd723d5
--- /dev/null
+++ b/src/ScriptResolving/AngelScript/ByteCodeHandling/FileByteCodeStream.hpp
@@ -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
diff --git a/src/ScriptResolving/AngelScript/ByteCodeHandling/MemoryByteCodeStream.hpp b/src/ScriptResolving/AngelScript/ByteCodeHandling/MemoryByteCodeStream.hpp
new file mode 100644
index 0000000..277c46e
--- /dev/null
+++ b/src/ScriptResolving/AngelScript/ByteCodeHandling/MemoryByteCodeStream.hpp
@@ -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
diff --git a/tests/ScriptTests/ScriptResolverTests.cpp b/tests/ScriptTests/ScriptResolverTests.cpp
index 0ecdcb2..f53fd09 100644
--- a/tests/ScriptTests/ScriptResolverTests.cpp
+++ b/tests/ScriptTests/ScriptResolverTests.cpp
@@ -79,4 +79,68 @@ TEST_CASE("Build script resolver, create object, invoke addition method") {
     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
\ No newline at end of file