Initial work on WebAssembly script provider
continuous-integration/drone/push Build is failing
Details
continuous-integration/drone/push Build is failing
Details
This commit is contained in:
parent
c6775d7089
commit
e32d655d80
|
@ -73,10 +73,15 @@ if (SCRIPT_PROVIDER STREQUAL "angelscript")
|
||||||
WORKING_DIRECTORY ${Angelscript_SOURCE_DIR}/angelscript/projects/cmake)
|
WORKING_DIRECTORY ${Angelscript_SOURCE_DIR}/angelscript/projects/cmake)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
include_directories(${CMAKE_CURRENT_BINARY_DIR}/include)
|
|
||||||
include_directories(${Angelscript_SOURCE_DIR}/angelscript/include)
|
include_directories(${Angelscript_SOURCE_DIR}/angelscript/include)
|
||||||
include_directories(${Angelscript_SOURCE_DIR}/add_on)
|
include_directories(${Angelscript_SOURCE_DIR}/add_on)
|
||||||
|
elseif (SCRIPT_PROVIDER STREQUAL "wasm")
|
||||||
|
message(STATUS "Installing wasmer")
|
||||||
|
execute_process(COMMAND bash -c "curl https://get.wasmer.io -sSfL | WASMER_INSTALL_LOG=0 sh")
|
||||||
|
include_directories($ENV{WASMER_DIR}/include)
|
||||||
endif ()
|
endif ()
|
||||||
|
include_directories(${CMAKE_CURRENT_BINARY_DIR}/include)
|
||||||
|
|
||||||
|
|
||||||
if (WINDOWS)
|
if (WINDOWS)
|
||||||
SET(CMAKE_SYSTEM_NAME Windows)
|
SET(CMAKE_SYSTEM_NAME Windows)
|
||||||
|
@ -160,13 +165,19 @@ if (SCRIPT_PROVIDER STREQUAL "angelscript")
|
||||||
set(_TESTLINKS ${_TESTLINKS} AngelscriptDebugger)
|
set(_TESTLINKS ${_TESTLINKS} AngelscriptDebugger)
|
||||||
endif ()
|
endif ()
|
||||||
ADD_DEFINITIONS(-D AS_USE_ACCESSORS=1)
|
ADD_DEFINITIONS(-D AS_USE_ACCESSORS=1)
|
||||||
|
elseif(SCRIPT_PROVIDER STREQUAL "wasm")
|
||||||
|
SET(FILE_SOURCE ${FILE_SOURCE}
|
||||||
|
"src/ScriptResolving/WASM/*.cpp"
|
||||||
|
"src/ScriptResolving/WASM/*.hpp")
|
||||||
|
# FIXME: Add C Interface
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
file(GLOB_RECURSE CORE_SRC_FILES ${FILE_SOURCE})
|
file(GLOB_RECURSE CORE_SRC_FILES ${FILE_SOURCE})
|
||||||
add_library(pkmnLib ${LIBTYPE} ${CORE_SRC_FILES})
|
add_library(pkmnLib ${LIBTYPE} ${CORE_SRC_FILES})
|
||||||
# Enable all warnings, and make them error when occurring.
|
# Enable all warnings, and make them error when occurring.
|
||||||
target_compile_options(pkmnLib PRIVATE -Wall -Wextra -Werror -pedantic-errors)
|
target_compile_options(pkmnLib PRIVATE -Wall -Wextra -Werror -pedantic-errors)
|
||||||
target_link_directories(pkmnLib PUBLIC ${Angelscript_BINARY_DIR})
|
|
||||||
|
|
||||||
|
|
||||||
# If interprocedural optimization is available, apply it
|
# If interprocedural optimization is available, apply it
|
||||||
check_ipo_supported(RESULT IPO_SUPPORTED)
|
check_ipo_supported(RESULT IPO_SUPPORTED)
|
||||||
|
@ -180,12 +191,21 @@ SET(_TESTLINKS ${_TESTLINKS} pkmnLib CreatureLib Arbutils)
|
||||||
|
|
||||||
if (SCRIPT_PROVIDER STREQUAL "angelscript")
|
if (SCRIPT_PROVIDER STREQUAL "angelscript")
|
||||||
message(STATUS "Using Angelscript as script provider.")
|
message(STATUS "Using Angelscript as script provider.")
|
||||||
|
target_link_directories(pkmnLib PUBLIC ${Angelscript_BINARY_DIR})
|
||||||
|
|
||||||
ADD_DEFINITIONS(-D ANGELSCRIPT=1)
|
ADD_DEFINITIONS(-D ANGELSCRIPT=1)
|
||||||
SET(_LINKS ${_LINKS} angelscript)
|
SET(_LINKS ${_LINKS} angelscript)
|
||||||
SET(_TESTLINKS ${_TESTLINKS} angelscript)
|
SET(_TESTLINKS ${_TESTLINKS} angelscript)
|
||||||
if (ANGELSCRIPT_DEBUGGER)
|
if (ANGELSCRIPT_DEBUGGER)
|
||||||
ADD_DEFINITIONS(-D ANGELSCRIPT_DEBUGGER=1)
|
ADD_DEFINITIONS(-D ANGELSCRIPT_DEBUGGER=1)
|
||||||
endif ()
|
endif ()
|
||||||
|
elseif (SCRIPT_PROVIDER STREQUAL "wasm")
|
||||||
|
message(STATUS "Using WebAssembly as script provider.")
|
||||||
|
target_link_directories(pkmnLib PUBLIC $ENV{WASMER_DIR}/lib)
|
||||||
|
message($ENV{WASMER_DIR}/lib)
|
||||||
|
ADD_DEFINITIONS(-D WASM=1)
|
||||||
|
SET(_LINKS ${_LINKS} -Wl,-Bstatic wasmer -Wl,-Bdynamic)
|
||||||
|
SET(_TESTLINKS ${_TESTLINKS} -Wl,-Bstatic wasmer -Wl,-Bdynamic)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
# If we are building for Windows we need to set some specific variables.
|
# If we are building for Windows we need to set some specific variables.
|
||||||
|
@ -245,6 +265,8 @@ if (PKMNLIB_TESTS)
|
||||||
endif ()
|
endif ()
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
if (ANGELSCRIPT_DEBUGGER)
|
if (SCRIPT_PROVIDER STREQUAL "angelscript")
|
||||||
include_directories(extern/AngelscriptDebuggerServer/extern/asio-1.18.2/include)
|
if (ANGELSCRIPT_DEBUGGER)
|
||||||
endif ()
|
include_directories(extern/AngelscriptDebuggerServer/extern/asio-1.18.2/include)
|
||||||
|
endif ()
|
||||||
|
endif()
|
|
@ -0,0 +1 @@
|
||||||
|
src:*/std_function.h
|
|
@ -1,6 +1,7 @@
|
||||||
#ifndef PKMNLIB_SCRIPTRESOLVER_HPP
|
#ifndef PKMNLIB_SCRIPTRESOLVER_HPP
|
||||||
#define PKMNLIB_SCRIPTRESOLVER_HPP
|
#define PKMNLIB_SCRIPTRESOLVER_HPP
|
||||||
|
|
||||||
|
#include <CreatureLib/Battling/ScriptHandling/ScriptResolver.hpp>
|
||||||
#include "../EvolutionScript.hpp"
|
#include "../EvolutionScript.hpp"
|
||||||
|
|
||||||
namespace PkmnLib::Battling {
|
namespace PkmnLib::Battling {
|
||||||
|
|
|
@ -18,9 +18,19 @@ namespace PkmnLib::Library {
|
||||||
return {};
|
return {};
|
||||||
return res.value().ForceAs<const MoveData>();
|
return res.value().ForceAs<const MoveData>();
|
||||||
}
|
}
|
||||||
|
inline std::optional<ArbUt::BorrowedPtr<const MoveData>> TryGet(u32 hash) const {
|
||||||
|
auto res = CreatureLib::Library::AttackLibrary::TryGet(hash);
|
||||||
|
if (!res.has_value())
|
||||||
|
return {};
|
||||||
|
return res.value().ForceAs<const MoveData>();
|
||||||
|
}
|
||||||
|
|
||||||
inline ArbUt::BorrowedPtr<const MoveData> Get(const ArbUt::BasicStringView& name) const {
|
inline ArbUt::BorrowedPtr<const MoveData> Get(const ArbUt::BasicStringView& name) const {
|
||||||
return CreatureLib::Library::AttackLibrary::Get(name).As<const MoveData>();
|
return CreatureLib::Library::AttackLibrary::Get(name).As<const MoveData>();
|
||||||
}
|
}
|
||||||
|
inline ArbUt::BorrowedPtr<const MoveData> Get(u32 hash) const {
|
||||||
|
return CreatureLib::Library::AttackLibrary::Get(hash).As<const MoveData>();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,86 @@
|
||||||
|
#include "CoreMethods.hpp"
|
||||||
|
#include <cstring>
|
||||||
|
#include "../WebAssemblyScriptResolver.hpp"
|
||||||
|
#include "WasmHelperFile.hpp"
|
||||||
|
#include "wasm.h"
|
||||||
|
|
||||||
|
wasm_func_t* CreateErrorFunc(wasm_store_t* store, WebAssemblyScriptResolver* resolver) {
|
||||||
|
// This is probably the most horrific function we need to expose. As we do not want the scripting library to be
|
||||||
|
// responsible for string formatting for size reasons, we pass a lot of data separately. This includes several
|
||||||
|
// strings.
|
||||||
|
return WasmHelpers::CreateFunc<void, i32, i32, i32, i32, i32, i32>(
|
||||||
|
store, resolver, [](void* env, const wasm_val_vec_t* args, wasm_val_vec_t*) -> wasm_trap_t* {
|
||||||
|
auto msg = args->data[0].of.i32;
|
||||||
|
auto msg_len = args->data[1].of.i32;
|
||||||
|
auto file = args->data[2].of.i32;
|
||||||
|
auto file_len = args->data[3].of.i32;
|
||||||
|
auto line = args->data[4].of.i32;
|
||||||
|
auto position = args->data[5].of.i32;
|
||||||
|
|
||||||
|
auto* resolver = (WebAssemblyScriptResolver*)env;
|
||||||
|
auto* msgPointer = wasm_memory_data(resolver->GetMemory()) + msg;
|
||||||
|
auto* filePointer = wasm_memory_data(resolver->GetMemory()) + file;
|
||||||
|
|
||||||
|
auto msgString = std::string_view(msgPointer, msg_len);
|
||||||
|
auto fileString = std::string_view(filePointer, file_len);
|
||||||
|
|
||||||
|
std::stringstream fullMessage;
|
||||||
|
fullMessage << "WASM Error with message: " << msgString << std::endl
|
||||||
|
<< "in file: " << fileString << ". Line: " << line << ":" << position;
|
||||||
|
|
||||||
|
wasm_message_t message;
|
||||||
|
wasm_name_new_from_string_nt(&message, fullMessage.str().c_str());
|
||||||
|
wasm_trap_t* trap = wasm_trap_new(resolver->GetStore(), &message);
|
||||||
|
wasm_name_delete(&message);
|
||||||
|
return trap;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
wasm_func_t* CreatePrintFunc(wasm_store_t* store, WebAssemblyScriptResolver* resolver) {
|
||||||
|
return WasmHelpers::CreateFunc<void, i32, i32>(
|
||||||
|
store, resolver, [](void* env, const wasm_val_vec_t* args, wasm_val_vec_t*) -> wasm_trap_t* {
|
||||||
|
auto msg = args->data[0].of.i32;
|
||||||
|
auto msg_len = args->data[1].of.i32;
|
||||||
|
auto resolver = (WebAssemblyScriptResolver*)env;
|
||||||
|
auto* msgPointer = wasm_memory_data(resolver->GetMemory()) + msg;
|
||||||
|
auto msgString = std::string_view(msgPointer, msg_len);
|
||||||
|
std::cout << msgString << std::endl;
|
||||||
|
return nullptr;
|
||||||
|
});
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
wasm_func_t* ConstString_GetHash(wasm_store_t* store, WebAssemblyScriptResolver* resolver) {
|
||||||
|
return WasmHelpers::CreateFunc2<u32, const ArbUt::StringView*>(
|
||||||
|
store, resolver,
|
||||||
|
std::function<u32(WebAssemblyScriptResolver*, const ArbUt::StringView*)>(
|
||||||
|
[](WebAssemblyScriptResolver*, const ArbUt::StringView* sv) -> u32 { return sv->GetHash(); }));
|
||||||
|
}
|
||||||
|
|
||||||
|
wasm_func_t* ConstString_GetStr(wasm_store_t* store, WebAssemblyScriptResolver* resolver) {
|
||||||
|
return WasmHelpers::CreateFunc<i32, const ArbUt::StringView*>(
|
||||||
|
store, resolver, [](void* env, const wasm_val_vec_t* args, wasm_val_vec_t* returns) -> wasm_trap_t* {
|
||||||
|
auto resolver = (WebAssemblyScriptResolver*)env;
|
||||||
|
auto& constString = *(ArbUt::StringView*)args->data[0].of.i64;
|
||||||
|
|
||||||
|
// To allow webassembly to access the C String, we need to allocate it inside it's memory.
|
||||||
|
// Length + 1 to make room for the '\0'
|
||||||
|
auto stringPointer = resolver->AllocateMemory(constString.Length() + 1, std::alignment_of<const char>());
|
||||||
|
|
||||||
|
// After we have the pointer to the memory allocated for the string, copy the string with specified length
|
||||||
|
// to it, then suffix it with the null byte to end it.
|
||||||
|
strncpy(reinterpret_cast<char*>(stringPointer.first), constString.c_str(), constString.Length());
|
||||||
|
stringPointer.first[constString.Length()] = '\0';
|
||||||
|
|
||||||
|
returns->data[0] = WASM_I32_VAL(stringPointer.second);
|
||||||
|
return nullptr;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebAssemblyCoreMethods::Register(wasm_store_t* store, ArbUt::Dictionary<std::string, wasm_func_t*>& externs,
|
||||||
|
WebAssemblyScriptResolver* resolver) {
|
||||||
|
externs.Insert("_error", CreateErrorFunc(store, resolver));
|
||||||
|
externs.Insert("_print", CreatePrintFunc(store, resolver));
|
||||||
|
externs.Insert("arbutils_const_string_get_hash", ConstString_GetHash(store, resolver));
|
||||||
|
externs.Insert("arbutils_const_string_get_str", ConstString_GetStr(store, resolver));
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
#ifndef PKMNLIB_COREMETHODS_HPP
|
||||||
|
#define PKMNLIB_COREMETHODS_HPP
|
||||||
|
|
||||||
|
#include <Arbutils/Collections/Dictionary.hpp>
|
||||||
|
#include <wasm.h>
|
||||||
|
|
||||||
|
class WebAssemblyScriptResolver;
|
||||||
|
class WebAssemblyCoreMethods {
|
||||||
|
public:
|
||||||
|
static void Register(wasm_store_t* store, ArbUt::Dictionary<std::string, wasm_func_t*>& externs,
|
||||||
|
WebAssemblyScriptResolver* resolver);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // PKMNLIB_COREMETHODS_HPP
|
|
@ -0,0 +1,39 @@
|
||||||
|
#include "LibraryMethods.hpp"
|
||||||
|
#include <type_traits>
|
||||||
|
#include "../../../../Battling/Library/BattleLibrary.hpp"
|
||||||
|
#include "../../WebAssemblyScriptResolver.hpp"
|
||||||
|
#include "../WasmHelperFile.hpp"
|
||||||
|
#include "wasm.h"
|
||||||
|
|
||||||
|
wasm_func_t* MoveLibrary_GetMoveByHash(wasm_store_t* store) {
|
||||||
|
wasm_functype_t* type =
|
||||||
|
wasm_functype_new_2_1(wasm_valtype_new_i64(), wasm_valtype_new_i32(), wasm_valtype_new_i64());
|
||||||
|
auto* f = wasm_func_new(store, type, [](const wasm_val_vec_t* args, wasm_val_vec_t* returns) -> wasm_trap_t* {
|
||||||
|
auto moveLibrary = (PkmnLib::Library::MoveLibrary*)args->data[0].of.i64;
|
||||||
|
auto hash = (u32)args->data[1].of.i32;
|
||||||
|
auto opt = moveLibrary->TryGet(hash);
|
||||||
|
if (!opt.has_value()) {
|
||||||
|
returns->data[0] = WASM_I64_VAL(0);
|
||||||
|
} else{
|
||||||
|
returns->data[0] = WASM_I64_VAL(reinterpret_cast<i64>(moveLibrary->Get(hash).GetRaw()));
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
});
|
||||||
|
wasm_functype_delete(type);
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LibraryMethods::Register(wasm_store_t* store, ArbUt::Dictionary<std::string, wasm_func_t*>& externs,
|
||||||
|
WebAssemblyScriptResolver* resolver) {
|
||||||
|
REGISTER_GETTER("battling_battle_library_get_data_library", PkmnLib::Battling::BattleLibrary, GetStaticLib, store,
|
||||||
|
resolver);
|
||||||
|
REGISTER_GETTER("library_data_library_get_move_library", PkmnLib::Library::PokemonLibrary, GetMoveLibrary, store,
|
||||||
|
resolver);
|
||||||
|
externs.Insert("library_move_library_get_move_by_hash", MoveLibrary_GetMoveByHash(store));
|
||||||
|
|
||||||
|
REGISTER_GETTER("library_move_data_get_base_power", CreatureLib::Library::AttackData, GetBasePower, store,
|
||||||
|
resolver);
|
||||||
|
|
||||||
|
REGISTER_GETTER("library_move_data_get_name", CreatureLib::Library::AttackData, GetName, store,
|
||||||
|
resolver);
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
#ifndef PKMNLIB_LIBRARYMETHODS_H
|
||||||
|
#define PKMNLIB_LIBRARYMETHODS_H
|
||||||
|
#include <Arbutils/Collections/Dictionary.hpp>
|
||||||
|
#include <wasm.h>
|
||||||
|
|
||||||
|
class WebAssemblyScriptResolver;
|
||||||
|
class LibraryMethods {
|
||||||
|
public:
|
||||||
|
static void Register(wasm_store_t* store, ArbUt::Dictionary<std::string, wasm_func_t*>& externs,
|
||||||
|
WebAssemblyScriptResolver* resolver);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // PKMNLIB_LIBRARYMETHODS_H
|
|
@ -0,0 +1,197 @@
|
||||||
|
#ifndef PKMNLIB_HELPERFILE_H
|
||||||
|
#define PKMNLIB_HELPERFILE_H
|
||||||
|
#include <Arbutils/Memory/Memory.hpp>
|
||||||
|
#include <memory>
|
||||||
|
#include <sstream>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <wasm.h>
|
||||||
|
#include "../WebAssemblyScriptResolver.hpp"
|
||||||
|
#include "wasm.h"
|
||||||
|
|
||||||
|
template <typename Test, template <typename...> class Ref> struct is_specialization : std::false_type {};
|
||||||
|
template <template <typename...> class Ref, typename... Args>
|
||||||
|
struct is_specialization<Ref<Args...>, Ref> : std::true_type {};
|
||||||
|
template <template <typename...> class Ref, typename... Args>
|
||||||
|
struct is_specialization<const Ref<Args...>&, Ref> : std::true_type {};
|
||||||
|
template <template <typename...> class Ref, typename... Args>
|
||||||
|
struct is_specialization<Ref<Args...>&, Ref> : std::true_type {};
|
||||||
|
|
||||||
|
struct WasmHelpers {
|
||||||
|
public:
|
||||||
|
static wasm_trap_t* CreateTrapFromException(const ArbUt::Exception& e, const WebAssemblyScriptResolver* resolver) {
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << e.what() << std::endl;
|
||||||
|
ss << e.GetStacktrace() << std::endl;
|
||||||
|
|
||||||
|
wasm_message_t message;
|
||||||
|
wasm_name_new_from_string_nt(&message, ss.str().c_str());
|
||||||
|
wasm_trap_t* trap = wasm_trap_new(resolver->GetStore(), &message);
|
||||||
|
wasm_name_delete(&message);
|
||||||
|
return trap;
|
||||||
|
}
|
||||||
|
|
||||||
|
static wasm_trap_t* FromStdException(const std::exception& e, const WebAssemblyScriptResolver* resolver) {
|
||||||
|
wasm_message_t message;
|
||||||
|
wasm_name_new_from_string_nt(&message, e.what());
|
||||||
|
wasm_trap_t* trap = wasm_trap_new(resolver->GetStore(), &message);
|
||||||
|
wasm_name_delete(&message);
|
||||||
|
return trap;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename R, R (T::*Method)() const>
|
||||||
|
static wasm_func_t* RegisterGetter(wasm_store_t* store, WebAssemblyScriptResolver* resolver) {
|
||||||
|
wasm_functype_t* type = wasm_functype_new_1_1(wasm_valtype_new_i64(), GetValType<R>());
|
||||||
|
auto* f = wasm_func_new_with_env(
|
||||||
|
store, type,
|
||||||
|
[](void* env, const wasm_val_vec_t* args, wasm_val_vec_t* returns) -> wasm_trap_t* {
|
||||||
|
try {
|
||||||
|
auto obj = (const T*)args->data[0].of.i64;
|
||||||
|
returns->data[0] = ToVal<R>((obj->*Method)());
|
||||||
|
} catch (ArbUt::Exception& e) {
|
||||||
|
return CreateTrapFromException(e, (WebAssemblyScriptResolver*)env);
|
||||||
|
} catch (std::exception& e) {
|
||||||
|
return FromStdException(e, (WebAssemblyScriptResolver*)env);
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
},
|
||||||
|
resolver, nullptr);
|
||||||
|
wasm_functype_delete(type);
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class R, class... Args>
|
||||||
|
static wasm_func_t* CreateFunc(wasm_store_t* store, WebAssemblyScriptResolver* resolver,
|
||||||
|
wasm_func_callback_with_env_t func) {
|
||||||
|
auto funcType = GetFuncType<R, Args...>();
|
||||||
|
auto* f = wasm_func_new_with_env(store, funcType, func, resolver, nullptr);
|
||||||
|
wasm_functype_delete(funcType);
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline static T ConvertAllArguments(const wasm_val_vec_t* t, std::size_t& index,
|
||||||
|
WebAssemblyScriptResolver* resolver) {
|
||||||
|
return FromVal<T>(t->data[index++], resolver);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class R, class... Args>
|
||||||
|
static wasm_func_t* CreateFunc2(wasm_store_t* store, WebAssemblyScriptResolver* resolver,
|
||||||
|
std::function<R(WebAssemblyScriptResolver*, Args...)> func) {
|
||||||
|
auto funcType = GetFuncType<R, Args...>();
|
||||||
|
struct Env {
|
||||||
|
WebAssemblyScriptResolver* Resolver;
|
||||||
|
std::function<R(WebAssemblyScriptResolver*, Args...)> Func;
|
||||||
|
__attribute__((no_sanitize("address")))
|
||||||
|
~Env(){}
|
||||||
|
};
|
||||||
|
|
||||||
|
auto env = new Env{.Resolver = resolver, .Func = func};
|
||||||
|
auto* f = wasm_func_new_with_env(
|
||||||
|
store, funcType,
|
||||||
|
[](void* env, const wasm_val_vec_t* parameters, wasm_val_vec_t* results) -> wasm_trap_t* {
|
||||||
|
auto e = *(Env*)env;
|
||||||
|
size_t index = 0;
|
||||||
|
R result = e.Func(e.Resolver, ConvertAllArguments<Args>(parameters, index, e.Resolver)...);
|
||||||
|
results->data[0] = ToVal<R>(result);
|
||||||
|
return nullptr;
|
||||||
|
},
|
||||||
|
env,
|
||||||
|
[](void* env) __attribute__((no_sanitize("address"))) {
|
||||||
|
delete (Env*)env;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
wasm_functype_delete(funcType);
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
template <class T> inline static wasm_valtype_t* GetValType() {
|
||||||
|
if constexpr (std::is_pointer<T>() || is_specialization<T, ArbUt::BorrowedPtr>::value ||
|
||||||
|
is_specialization<T, ArbUt::OptionalBorrowedPtr>::value ||
|
||||||
|
is_specialization<T, std::unique_ptr>::value) {
|
||||||
|
return wasm_valtype_new_i64();
|
||||||
|
} else if constexpr (std::is_enum<T>() || std::is_integral<T>()) {
|
||||||
|
if constexpr (sizeof(T) > 4) {
|
||||||
|
return wasm_valtype_new_i64();
|
||||||
|
} else {
|
||||||
|
return wasm_valtype_new_i32();
|
||||||
|
}
|
||||||
|
} else if constexpr (std::is_same<T, const ArbUt::StringView&>()) {
|
||||||
|
return wasm_valtype_new_i64();
|
||||||
|
}
|
||||||
|
THROW("Unhandled value type: ", typeid(T).name());
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T> inline static wasm_val_t ToVal(const T& val) {
|
||||||
|
if constexpr (std::is_pointer<T>()) {
|
||||||
|
return WASM_I64_VAL(reinterpret_cast<i64>(val));
|
||||||
|
} else if constexpr (is_specialization<T, ArbUt::BorrowedPtr>::value) {
|
||||||
|
return WASM_I64_VAL(reinterpret_cast<i64>(val.GetRaw()));
|
||||||
|
} else if constexpr (is_specialization<T, ArbUt::OptionalBorrowedPtr>::value) {
|
||||||
|
return WASM_I64_VAL(reinterpret_cast<i64>(val.GetRaw()));
|
||||||
|
} else if constexpr (is_specialization<T, std::unique_ptr>::value) {
|
||||||
|
return WASM_I64_VAL(reinterpret_cast<i64>(val.get()));
|
||||||
|
} else if constexpr (std::is_enum<T>() || std::is_integral<T>()) {
|
||||||
|
if constexpr (sizeof(T) > 4) {
|
||||||
|
return WASM_I64_VAL((i64)val);
|
||||||
|
} else {
|
||||||
|
return WASM_I32_VAL((i32)val);
|
||||||
|
}
|
||||||
|
} else if constexpr (std::is_same<T, const ArbUt::StringView&>()) {
|
||||||
|
return WASM_I64_VAL(reinterpret_cast<i64>(&val));
|
||||||
|
}
|
||||||
|
THROW("Unhandled value type: ", typeid(T).name());
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T> inline static T FromVal(const wasm_val_t& val, WebAssemblyScriptResolver* resolver) {
|
||||||
|
if constexpr (std::is_pointer<T>()) {
|
||||||
|
auto v = reinterpret_cast<void*>(val.of.i64);
|
||||||
|
Ensure(resolver->ValidateLoadedPointer<std::remove_pointer<T>>(v));
|
||||||
|
return (T)v;
|
||||||
|
} else if constexpr (is_specialization<T, ArbUt::BorrowedPtr>::value) {
|
||||||
|
return dynamic_cast<T>(reinterpret_cast<void*>(val.of.i64));
|
||||||
|
} else if constexpr (is_specialization<T, ArbUt::OptionalBorrowedPtr>::value) {
|
||||||
|
return dynamic_cast<T>(reinterpret_cast<void*>(val.of.i64));
|
||||||
|
} else if constexpr (is_specialization<T, std::unique_ptr>::value) {
|
||||||
|
return dynamic_cast<T>(reinterpret_cast<void*>(val.of.i64));
|
||||||
|
} else if constexpr (std::is_enum<T>() || std::is_integral<T>()) {
|
||||||
|
if constexpr (sizeof(T) > 4) {
|
||||||
|
return reinterpret_cast<T>(val.of.i64);
|
||||||
|
} else {
|
||||||
|
return reinterpret_cast<T>(val.of.i32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
THROW("Unhandled value type: ", typeid(T).name());
|
||||||
|
}
|
||||||
|
|
||||||
|
template <size_t I, class... Args> inline static void AppendArgument(wasm_valtype_t* array[]) {
|
||||||
|
using type = typename std::tuple_element<I, std::tuple<Args...>>::type;
|
||||||
|
array[I] = GetValType<type>();
|
||||||
|
if constexpr (I + 1 < sizeof...(Args)) {
|
||||||
|
AppendArgument<I + 1, Args...>(array);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class R, class... Args> static wasm_functype_t* GetFuncType() {
|
||||||
|
wasm_valtype_t* ps[sizeof...(Args)];
|
||||||
|
AppendArgument<0, Args...>(ps);
|
||||||
|
|
||||||
|
wasm_valtype_vec_t params, results;
|
||||||
|
if constexpr (std::is_void<R>()) {
|
||||||
|
results = WASM_EMPTY_VEC;
|
||||||
|
} else {
|
||||||
|
wasm_valtype_t* rs[1] = {GetValType<R>()};
|
||||||
|
wasm_valtype_vec_new(&results, 1, rs);
|
||||||
|
}
|
||||||
|
wasm_valtype_vec_new(¶ms, sizeof...(Args), ps);
|
||||||
|
return wasm_functype_new(¶ms, &results);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#define REGISTER_GETTER(name, cType, cFunc, store, resolver) \
|
||||||
|
externs.Insert( \
|
||||||
|
name, \
|
||||||
|
WasmHelpers::RegisterGetter<cType, std::result_of<decltype (&cType::cFunc)(cType)>::type, &cType::cFunc>( \
|
||||||
|
store, resolver));
|
||||||
|
|
||||||
|
#endif // PKMNLIB_HELPERFILE_H
|
|
@ -0,0 +1,13 @@
|
||||||
|
#ifndef PKMNLIB_WASMEXTERNREF_HPP
|
||||||
|
#define PKMNLIB_WASMEXTERNREF_HPP
|
||||||
|
|
||||||
|
template <class T> class WasmExternRef {
|
||||||
|
public:
|
||||||
|
WasmExternRef(T* ptr) : _ptr(ptr) {}
|
||||||
|
|
||||||
|
[[nodiscard]] inline T* GetPtr() const noexcept { return _ptr; }
|
||||||
|
private:
|
||||||
|
T* _ptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // PKMNLIB_WASMEXTERNREF_HPP
|
|
@ -0,0 +1,40 @@
|
||||||
|
#include "WebAssemblyBattleScript.hpp"
|
||||||
|
#include "WebAssemblyFunctionCall.hpp"
|
||||||
|
#include "WebAssemblyScriptResolver.hpp"
|
||||||
|
|
||||||
|
WebAssemblyBattleScript::~WebAssemblyBattleScript() {
|
||||||
|
// Ensure the WebAssembly library can clear up its own junk
|
||||||
|
auto funcOpt = _resolver->GetFunction<1, 0>("destroy_script");
|
||||||
|
if (funcOpt.has_value()) {
|
||||||
|
auto& func = funcOpt.value();
|
||||||
|
func.Loadi32(0, _wasmPtr);
|
||||||
|
func.Call();
|
||||||
|
}
|
||||||
|
// Remove the backwards lookup to this script.
|
||||||
|
_resolver->RemoveRegisteredScript(_wasmPtr);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define WASM_CALL(capability, script_name, args_count, return_count, parameter_setup) \
|
||||||
|
if (!HasCapability(WebAssemblyScriptCapabilities::capability)) { \
|
||||||
|
return; \
|
||||||
|
} \
|
||||||
|
auto funcOpt = _resolver->GetFunction<args_count, return_count>(script_name); \
|
||||||
|
if (!funcOpt.has_value()) { \
|
||||||
|
return; \
|
||||||
|
} \
|
||||||
|
auto& func = funcOpt.value(); \
|
||||||
|
parameter_setup; \
|
||||||
|
func.Call();
|
||||||
|
|
||||||
|
void WebAssemblyBattleScript::OnInitialize(const CreatureLib::Battling::BattleLibrary* library,
|
||||||
|
const ArbUt::List<CreatureLib::Library::EffectParameter*>&) {
|
||||||
|
WASM_CALL(Initialize, "script_on_initialize", 2, 0, {
|
||||||
|
func.Loadi32(0, _wasmPtr);
|
||||||
|
func.LoadExternRef(1, library);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
CreatureLib::Battling::BattleScript* WebAssemblyBattleScript::Clone(const ArbUt::OptionalBorrowedPtr<void>&) {
|
||||||
|
// FIXME: Implement
|
||||||
|
return nullptr;
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
#ifndef PKMNLIB_WEBASSEMBLYBATTLESCRIPT_H
|
||||||
|
#define PKMNLIB_WEBASSEMBLYBATTLESCRIPT_H
|
||||||
|
|
||||||
|
#include "../../Battling/PkmnScript.hpp"
|
||||||
|
#include "WebAssemblyScriptCapabilities.hpp"
|
||||||
|
|
||||||
|
class WebAssemblyScriptResolver;
|
||||||
|
class WebAssemblyBattleScript : public PkmnLib::Battling::PkmnScript {
|
||||||
|
public:
|
||||||
|
WebAssemblyBattleScript(const ArbUt::OptionalBorrowedPtr<void>& owner, i32 wasm_ptr,
|
||||||
|
const std::unordered_set<WebAssemblyScriptCapabilities>* capabilities,
|
||||||
|
WebAssemblyScriptResolver* resolver, const ArbUt::StringView& scriptName)
|
||||||
|
: PkmnScript(owner), _resolver(resolver), _wasmPtr(wasm_ptr), _capabilities(capabilities),
|
||||||
|
_scriptName(scriptName) {}
|
||||||
|
~WebAssemblyBattleScript();
|
||||||
|
|
||||||
|
BattleScript* Clone(const ArbUt::OptionalBorrowedPtr<void>& owner) non_null override;
|
||||||
|
const ArbUt::StringView& GetName() const noexcept override { return _scriptName; }
|
||||||
|
|
||||||
|
public:
|
||||||
|
void OnInitialize(const CreatureLib::Battling::BattleLibrary* library,
|
||||||
|
const ArbUt::List<CreatureLib::Library::EffectParameter*>& parameters) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
[[nodiscard]] inline bool HasCapability(WebAssemblyScriptCapabilities capability) const noexcept {
|
||||||
|
return _capabilities->contains(capability);
|
||||||
|
}
|
||||||
|
|
||||||
|
WebAssemblyScriptResolver* _resolver;
|
||||||
|
i32 _wasmPtr;
|
||||||
|
const std::unordered_set<WebAssemblyScriptCapabilities>* _capabilities;
|
||||||
|
ArbUt::StringView _scriptName;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // PKMNLIB_WEBASSEMBLYBATTLESCRIPT_H
|
|
@ -0,0 +1,107 @@
|
||||||
|
#ifndef PKMNLIB_WEBASSEMBLYFUNCTIONCALL_H
|
||||||
|
#define PKMNLIB_WEBASSEMBLYFUNCTIONCALL_H
|
||||||
|
|
||||||
|
#include <Arbutils/Collections/List.hpp>
|
||||||
|
#include <Arbutils/Memory/Memory.hpp>
|
||||||
|
#include <wasm.h>
|
||||||
|
#include "wasm.h"
|
||||||
|
|
||||||
|
template <u32 argsCount, u32 returnsCount>
|
||||||
|
class WebAssemblyFunctionCall {
|
||||||
|
public:
|
||||||
|
WebAssemblyFunctionCall(const ArbUt::BorrowedPtr<wasm_func_t>& func) : _func(func) {}
|
||||||
|
|
||||||
|
NO_COPY_OR_MOVE(WebAssemblyFunctionCall)
|
||||||
|
|
||||||
|
void Call() {
|
||||||
|
wasm_val_vec_t args = {argsCount, _arguments.Data};
|
||||||
|
|
||||||
|
wasm_val_vec_t results;
|
||||||
|
if constexpr (returnsCount > 0) {
|
||||||
|
results = {returnsCount, _results.Data};
|
||||||
|
} else {
|
||||||
|
results = WASM_EMPTY_VEC;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* result = wasm_func_call(_func, &args, &results);
|
||||||
|
if (result != nullptr) {
|
||||||
|
wasm_message_t retrieved_message;
|
||||||
|
wasm_trap_message(result, &retrieved_message);
|
||||||
|
auto msg = std::string(retrieved_message.data, retrieved_message.size);
|
||||||
|
THROW(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void Loadi32(size_t index, i32 value) { _arguments.Data[index] = WASM_I32_VAL(value); }
|
||||||
|
inline void Loadi64(size_t index, i64 value) { _arguments.Data[index] = WASM_I64_VAL(value); }
|
||||||
|
inline void Loadf32(size_t index, f32 value) { _arguments.Data[index] = WASM_F32_VAL(value); }
|
||||||
|
inline void Loadf64(size_t index, f64 value) { _arguments.Data[index] = WASM_F64_VAL(value); }
|
||||||
|
|
||||||
|
template <class T> inline void LoadExternRef(size_t index, T* value) {
|
||||||
|
_arguments.Data[index] = WASM_I64_VAL(reinterpret_cast<i64>(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline i32 GetResultAsi32() const {
|
||||||
|
if constexpr (returnsCount > 0) {
|
||||||
|
Ensure(_results.Data[0].kind == WASM_I32);
|
||||||
|
return _results.Data[0].of.i32;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
[[nodiscard]] inline i64 GetResultAsi64() const {
|
||||||
|
if constexpr (returnsCount > 0) {
|
||||||
|
Ensure(_results.Data[0].kind == WASM_I64);
|
||||||
|
return _results.Data[0].of.i64;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
[[nodiscard]] inline f32 GetResultAsf32() const {
|
||||||
|
if constexpr (returnsCount > 0) {
|
||||||
|
Ensure(_results.Data[0].kind == WASM_F32);
|
||||||
|
return _results.Data[0].of.f32;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
[[nodiscard]] inline f64 GetResultAsf64() const {
|
||||||
|
if constexpr (returnsCount > 0) {
|
||||||
|
Ensure(_results.Data[0].kind == WASM_F64);
|
||||||
|
return _results.Data[0].of.f64;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T> [[nodiscard]] inline T* GetResultAsExternRef() const {
|
||||||
|
if constexpr (returnsCount > 0) {
|
||||||
|
Ensure(_results.Data[0].kind == WASM_ANYREF);
|
||||||
|
return reinterpret_cast<T*>(_results.Data[0].of.i64);
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const wasm_val_t* GetRawResults() const {
|
||||||
|
if constexpr (returnsCount > 0) {
|
||||||
|
return _results.Data;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
ArbUt::BorrowedPtr<wasm_func_t> _func;
|
||||||
|
|
||||||
|
struct Empty {};
|
||||||
|
template <u32 size> struct NonEmpty { wasm_val_t Data[size]; };
|
||||||
|
|
||||||
|
typedef typename std::conditional<argsCount == 0, Empty, NonEmpty<argsCount>>::type args_t;
|
||||||
|
typedef typename std::conditional<returnsCount == 0, Empty, NonEmpty<returnsCount>>::type result_t;
|
||||||
|
|
||||||
|
args_t _arguments;
|
||||||
|
result_t _results;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // PKMNLIB_WEBASSEMBLYFUNCTIONCALL_H
|
|
@ -0,0 +1,63 @@
|
||||||
|
#ifndef PKMNLIB_WEBASSEMBLYSCRIPTCAPABILITIES_H
|
||||||
|
#define PKMNLIB_WEBASSEMBLYSCRIPTCAPABILITIES_H
|
||||||
|
|
||||||
|
enum class WebAssemblyScriptCapabilities : u8 {
|
||||||
|
None = 0,
|
||||||
|
Initialize = 1,
|
||||||
|
OnStack,
|
||||||
|
OnRemove,
|
||||||
|
OnBeforeTurn,
|
||||||
|
ChangeAttack,
|
||||||
|
ModifyNumberOfHits,
|
||||||
|
PreventAttack,
|
||||||
|
FailAttack,
|
||||||
|
StopBeforeAttack,
|
||||||
|
OnBeforeAttack,
|
||||||
|
FailIncomingAttack,
|
||||||
|
IsInvulnerable,
|
||||||
|
OnAttackMiss,
|
||||||
|
ChangeAttackType,
|
||||||
|
ChangeEffectiveness,
|
||||||
|
BlockCritical,
|
||||||
|
OnIncomingHit,
|
||||||
|
OnFaintingOpponent,
|
||||||
|
PreventStatBoostChange,
|
||||||
|
ModifyStatBoostChange,
|
||||||
|
PreventSecondaryEffects,
|
||||||
|
OnSecondaryEffect,
|
||||||
|
OnAfterHits,
|
||||||
|
PreventSelfSwitch,
|
||||||
|
ModifyEffectChance,
|
||||||
|
ModifyIncomingEffectChance,
|
||||||
|
OverrideBasePower,
|
||||||
|
ChangeDamageStatsUser,
|
||||||
|
BypassDefensiveStat,
|
||||||
|
BypassOffensiveStat,
|
||||||
|
ModifyStatModifier,
|
||||||
|
ModifyDamageModifier,
|
||||||
|
OverrideDamage,
|
||||||
|
OverrideIncomingDamage,
|
||||||
|
ChangeSpeed,
|
||||||
|
ChangePriority,
|
||||||
|
OnFail,
|
||||||
|
OnOpponentFail,
|
||||||
|
PreventRunAway,
|
||||||
|
PreventOpponentRunAway,
|
||||||
|
PreventOpponentSwitch,
|
||||||
|
OnEndTurn,
|
||||||
|
OnDamage,
|
||||||
|
OnFaint,
|
||||||
|
OnAfterHeldItemConsume,
|
||||||
|
PreventIncomingCritical,
|
||||||
|
ModifyCriticalStage,
|
||||||
|
OverrideCriticalModifier,
|
||||||
|
OverrideSTABModifier,
|
||||||
|
ModifyExperienceGain,
|
||||||
|
DoesShareExperience,
|
||||||
|
BlockWeather,
|
||||||
|
OnSwitchIn,
|
||||||
|
ModifyOffensiveStatValue,
|
||||||
|
ModifyDefensiveStatValue,
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // PKMNLIB_WEBASSEMBLYSCRIPTCAPABILITIES_H
|
|
@ -0,0 +1,175 @@
|
||||||
|
#include "WebAssemblyScriptResolver.hpp"
|
||||||
|
#include <iostream>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <wasmer.h>
|
||||||
|
#include "InterfaceMethods/CoreMethods.hpp"
|
||||||
|
#include "InterfaceMethods/Library/LibraryMethods.hpp"
|
||||||
|
#include "WebAssemblyBattleScript.hpp"
|
||||||
|
#include "WebAssemblyFunctionCall.hpp"
|
||||||
|
#include "wasm.h"
|
||||||
|
|
||||||
|
PkmnLib::Battling::ScriptResolver* PkmnLib::Battling::BattleLibrary::CreateScriptResolver() {
|
||||||
|
return new WebAssemblyScriptResolver();
|
||||||
|
}
|
||||||
|
WebAssemblyScriptResolver::WebAssemblyScriptResolver() : _engine(wasm_engine_new()), _store(wasm_store_new(_engine)) {}
|
||||||
|
|
||||||
|
WebAssemblyScriptResolver::~WebAssemblyScriptResolver() {
|
||||||
|
wasm_extern_vec_delete(&_exports);
|
||||||
|
for (auto& import : _imports) {
|
||||||
|
wasm_func_delete(import.second);
|
||||||
|
}
|
||||||
|
if (_instance != nullptr) {
|
||||||
|
wasm_instance_delete(_instance);
|
||||||
|
}
|
||||||
|
if (_module != nullptr) {
|
||||||
|
wasm_module_delete(_module);
|
||||||
|
}
|
||||||
|
wasm_store_delete(_store);
|
||||||
|
wasm_engine_delete(_engine);
|
||||||
|
}
|
||||||
|
|
||||||
|
u8 WebAssemblyScriptResolver::LoadWasmFromFile(const std::string& path) {
|
||||||
|
auto file = fopen(path.c_str(), "rb");
|
||||||
|
if (!file) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
fseek(file, 0L, SEEK_END);
|
||||||
|
size_t file_size = ftell(file);
|
||||||
|
fseek(file, 0L, SEEK_SET);
|
||||||
|
wasm_byte_vec_t wasm_bytes;
|
||||||
|
wasm_byte_vec_new_uninitialized(&wasm_bytes, file_size);
|
||||||
|
if (fread(wasm_bytes.data, file_size, 1, file) != 1) {
|
||||||
|
wasm_byte_vec_delete(&wasm_bytes);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
fclose(file);
|
||||||
|
_module = wasm_module_new(_store, &wasm_bytes);
|
||||||
|
if (_module == nullptr) {
|
||||||
|
wasm_byte_vec_delete(&wasm_bytes);
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
wasm_byte_vec_delete(&wasm_bytes);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
u8 WebAssemblyScriptResolver::LoadWasmFromBytes(std::vector<u8> wasm_bytes) {
|
||||||
|
wasm_byte_vec_t data = {wasm_bytes.size(), (char*)wasm_bytes.data()};
|
||||||
|
_module = wasm_module_new(_store, &data);
|
||||||
|
if (_module == nullptr) {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
u8 WebAssemblyScriptResolver::LoadWatFromString(const std::string& data) {
|
||||||
|
wasm_byte_vec_t wat;
|
||||||
|
wasm_byte_vec_new(&wat, data.size(), data.c_str());
|
||||||
|
wasm_byte_vec_t wasm_bytes;
|
||||||
|
wat2wasm(&wat, &wasm_bytes);
|
||||||
|
wasm_byte_vec_delete(&wat);
|
||||||
|
_module = wasm_module_new(_store, &wasm_bytes);
|
||||||
|
if (_module == nullptr) {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
wasm_byte_vec_delete(&wasm_bytes);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebAssemblyScriptResolver::RegisterFunction() {}
|
||||||
|
|
||||||
|
void WebAssemblyScriptResolver::RegisterDefaultMethods() {
|
||||||
|
WebAssemblyCoreMethods::Register(_store, _imports, this);
|
||||||
|
LibraryMethods::Register(_store, _imports, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebAssemblyScriptResolver::Finalize() {
|
||||||
|
RegisterDefaultMethods();
|
||||||
|
auto imports = ArbUt::List<wasm_extern_t*>();
|
||||||
|
|
||||||
|
wasm_importtype_vec_t import_types;
|
||||||
|
wasm_module_imports(_module, &import_types);
|
||||||
|
for (size_t i = 0; i < import_types.size; ++i) {
|
||||||
|
auto importType = import_types.data[i];
|
||||||
|
auto nameWasm = wasm_importtype_name(importType);
|
||||||
|
auto name = std::string(nameWasm->data, nameWasm->size);
|
||||||
|
auto exportFunc = _imports.TryGet(name);
|
||||||
|
if (!exportFunc.has_value()) {
|
||||||
|
THROW("Missing imported WASM function: ", name);
|
||||||
|
}
|
||||||
|
imports.Append(wasm_func_as_extern(exportFunc.value()));
|
||||||
|
}
|
||||||
|
|
||||||
|
wasm_extern_vec_t import_object = {imports.Count(), const_cast<wasm_extern_t**>(imports.RawData())};
|
||||||
|
|
||||||
|
wasm_trap_t* trap = nullptr;
|
||||||
|
_instance = wasm_instance_new(_store, _module, &import_object, &trap);
|
||||||
|
wasm_importtype_vec_delete(&import_types);
|
||||||
|
if (_instance == nullptr) {
|
||||||
|
char* err = new char[wasmer_last_error_length()];
|
||||||
|
wasmer_last_error_message(err, wasmer_last_error_length());
|
||||||
|
std::cout << err << std::endl;
|
||||||
|
delete[] err;
|
||||||
|
}
|
||||||
|
|
||||||
|
EnsureNotNull(_instance);
|
||||||
|
|
||||||
|
wasm_exporttype_vec_t export_types;
|
||||||
|
wasm_module_exports(_module, &export_types);
|
||||||
|
wasm_instance_exports(_instance, &_exports);
|
||||||
|
for (size_t i = 0; i < export_types.size; ++i) {
|
||||||
|
auto t = wasm_externtype_kind(wasm_exporttype_type(export_types.data[i]));
|
||||||
|
if (t == WASM_EXTERN_FUNC) {
|
||||||
|
const auto* name = wasm_exporttype_name(export_types.data[i]);
|
||||||
|
|
||||||
|
_exportedFunctions.Insert(ArbUt::StringView(name->data, name->size), wasm_extern_as_func(_exports.data[i]));
|
||||||
|
} else if (t == WASM_EXTERN_MEMORY) {
|
||||||
|
_memory = wasm_extern_as_memory(_exports.data[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wasm_exporttype_vec_delete(&export_types);
|
||||||
|
if (_memory != nullptr) {
|
||||||
|
wasm_memory_grow(_memory, 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CreatureLib::Battling::BattleScript*
|
||||||
|
WebAssemblyScriptResolver::LoadScript(const ArbUt::OptionalBorrowedPtr<void>& owner, ScriptCategory category,
|
||||||
|
const ArbUt::StringView& scriptName) {
|
||||||
|
auto loadScriptOpt = GetFunction<2, 1>("load_script"_cnc);
|
||||||
|
if (!loadScriptOpt.has_value()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
auto& loadScriptFunc = loadScriptOpt.value();
|
||||||
|
loadScriptFunc.Loadi32(0, static_cast<i32>(category));
|
||||||
|
loadScriptFunc.LoadExternRef(1, &scriptName);
|
||||||
|
loadScriptFunc.Call();
|
||||||
|
auto result = loadScriptFunc.GetResultAsi32();
|
||||||
|
if (result == 0) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
auto key = std::pair<ScriptCategory, ArbUt::StringView>(category, scriptName);
|
||||||
|
auto findCapabilities = _scriptCapabilities.find(key);
|
||||||
|
std::unordered_set<WebAssemblyScriptCapabilities> capabilities;
|
||||||
|
if (findCapabilities != _scriptCapabilities.end()) {
|
||||||
|
capabilities = findCapabilities->second;
|
||||||
|
} else {
|
||||||
|
auto getCapabilitiesOpt = GetFunction<1,2>("get_script_capabilities"_cnc);
|
||||||
|
if (getCapabilitiesOpt.has_value()) {
|
||||||
|
auto& getCapabilitiesFunc = getCapabilitiesOpt.value();
|
||||||
|
getCapabilitiesFunc.Loadi32(0, result);
|
||||||
|
getCapabilitiesFunc.Call();
|
||||||
|
const auto* rawResult = getCapabilitiesFunc.GetRawResults();
|
||||||
|
auto ptr = (WebAssemblyScriptCapabilities*)(wasm_memory_data(_memory) + rawResult[0].of.i32);
|
||||||
|
auto end = (WebAssemblyScriptCapabilities*)(ptr + rawResult[1].of.i32);
|
||||||
|
auto vec = std::vector<WebAssemblyScriptCapabilities>(ptr, end);
|
||||||
|
for (auto capability: vec){
|
||||||
|
capabilities.insert(capability);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_scriptCapabilities[key] = capabilities;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto script = new WebAssemblyBattleScript(owner, result, &_scriptCapabilities[key], this, scriptName);
|
||||||
|
_loadedScripts.Insert(result, script);
|
||||||
|
return script;
|
||||||
|
}
|
|
@ -0,0 +1,92 @@
|
||||||
|
#ifndef PKMNLIB_WEBASSEMBLYSCRIPTRESOLVER_HPP
|
||||||
|
#define PKMNLIB_WEBASSEMBLYSCRIPTRESOLVER_HPP
|
||||||
|
|
||||||
|
#include <Arbutils/Collections/Dictionary.hpp>
|
||||||
|
#include <optional>
|
||||||
|
#include <utility>
|
||||||
|
#include <wasm.h>
|
||||||
|
#include "../../Battling/Library/ScriptResolver.hpp"
|
||||||
|
#include "WebAssemblyBattleScript.hpp"
|
||||||
|
#include "WebAssemblyFunctionCall.hpp"
|
||||||
|
#include "WebAssemblyScriptCapabilities.hpp"
|
||||||
|
|
||||||
|
class WebAssemblyScriptResolver : public PkmnLib::Battling::ScriptResolver {
|
||||||
|
public:
|
||||||
|
WebAssemblyScriptResolver();
|
||||||
|
~WebAssemblyScriptResolver();
|
||||||
|
|
||||||
|
u8 LoadWasmFromFile(const std::string& path);
|
||||||
|
u8 LoadWasmFromBytes(std::vector<u8>);
|
||||||
|
u8 LoadWatFromString(const std::string& data);
|
||||||
|
void RegisterFunction();
|
||||||
|
void Finalize();
|
||||||
|
|
||||||
|
template <u32 argsCount, u32 returnsCount>
|
||||||
|
inline std::optional<WebAssemblyFunctionCall<argsCount, returnsCount>>
|
||||||
|
GetFunction(const ArbUt::StringView& name) const {
|
||||||
|
auto res = _exportedFunctions.TryGet(name);
|
||||||
|
if (!res.has_value()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return std::make_optional<WebAssemblyFunctionCall<argsCount, returnsCount>>(
|
||||||
|
ArbUt::BorrowedPtr<wasm_func_t>(res.value()));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<u8*, i32> AllocateMemory(u32 size, u32 align) const {
|
||||||
|
auto funcOpt = GetFunction<2, 1>("allocate_mem");
|
||||||
|
auto& func = funcOpt.value();
|
||||||
|
func.Loadi32(0, size);
|
||||||
|
func.Loadi32(1, align);
|
||||||
|
func.Call();
|
||||||
|
auto memoryOffset = func.GetResultAsi32();
|
||||||
|
return std::make_pair(reinterpret_cast<u8*>(wasm_memory_data(_memory) + memoryOffset), memoryOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline wasm_memory_t* GetMemory() const noexcept { return _memory; }
|
||||||
|
|
||||||
|
CreatureLib::Battling::BattleScript* LoadScript(const ArbUt::OptionalBorrowedPtr<void>& owner,
|
||||||
|
ScriptCategory category,
|
||||||
|
const ArbUt::StringView& scriptName) nullable override;
|
||||||
|
|
||||||
|
[[nodiscard]] inline wasm_store_t* GetStore() const noexcept { return _store; }
|
||||||
|
|
||||||
|
inline void RemoveRegisteredScript(i32 wasmPtr) { _loadedScripts.Remove(wasmPtr); }
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline void MarkLoadedPointer(T* ptr){
|
||||||
|
_loadedPointers.Set((void*)ptr, typeid(T));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline bool ValidateLoadedPointer(void* ptr){
|
||||||
|
const auto& opt = _loadedPointers.TryGet(ptr);
|
||||||
|
return opt.has_value() && opt.value() == typeid(T);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
wasm_engine_t* _engine;
|
||||||
|
wasm_store_t* _store;
|
||||||
|
wasm_module_t* _module = nullptr;
|
||||||
|
wasm_instance_t* _instance = nullptr;
|
||||||
|
wasm_memory_t* _memory = nullptr;
|
||||||
|
ArbUt::Dictionary<std::string, wasm_func_t*> _imports;
|
||||||
|
wasm_extern_vec_t _exports = {0, nullptr};
|
||||||
|
ArbUt::Dictionary<ArbUt::StringView, wasm_func_t*> _exportedFunctions;
|
||||||
|
ArbUt::Dictionary<i32, WebAssemblyBattleScript*> _loadedScripts;
|
||||||
|
|
||||||
|
void RegisterDefaultMethods();
|
||||||
|
|
||||||
|
typedef std::pair<ScriptCategory, ArbUt::StringView> scriptCapabilitiesKey;
|
||||||
|
|
||||||
|
struct pair_hash {
|
||||||
|
template <class T1, class T2> std::size_t operator()(const std::pair<T1, T2>& pair) const {
|
||||||
|
return std::hash<T1>()(pair.first) ^ std::hash<T2>()(pair.second);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
std::unordered_map<scriptCapabilitiesKey, std::unordered_set<WebAssemblyScriptCapabilities>, pair_hash>
|
||||||
|
_scriptCapabilities;
|
||||||
|
|
||||||
|
ArbUt::Dictionary<void*, std::type_info> _loadedPointers;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // PKMNLIB_WEBASSEMBLYSCRIPTRESOLVER_HPP
|
|
@ -8,4 +8,9 @@ TEST_CASE("Able to build and destroy empty library") {
|
||||||
delete lib;
|
delete lib;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
REGISTER_EXCEPTION_TRANSLATOR(ArbUt::Exception& ex) {
|
||||||
|
return {(std::string(ex.what()) + "\n" + ex.GetStacktrace()).c_str()};
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
|
@ -1,4 +1,4 @@
|
||||||
#ifdef TESTS_BUILD
|
#if TESTS_BUILD and ANGELSCRIPT
|
||||||
#include <doctest.h>
|
#include <doctest.h>
|
||||||
#include "../../src/Battling/Pokemon/CreatePokemon.hpp"
|
#include "../../src/Battling/Pokemon/CreatePokemon.hpp"
|
||||||
#include "../../src/ScriptResolving/AngelScript/AngelScriptResolver.hpp"
|
#include "../../src/ScriptResolving/AngelScript/AngelScriptResolver.hpp"
|
|
@ -1,4 +1,4 @@
|
||||||
#ifdef TESTS_BUILD
|
#if TESTS_BUILD and ANGELSCRIPT
|
||||||
#include <doctest.h>
|
#include <doctest.h>
|
||||||
#include "../../src/Battling/Pokemon/CreatePokemon.hpp"
|
#include "../../src/Battling/Pokemon/CreatePokemon.hpp"
|
||||||
#include "../../src/ScriptResolving/AngelScript/AngelScriptResolver.hpp"
|
#include "../../src/ScriptResolving/AngelScript/AngelScriptResolver.hpp"
|
|
@ -1,7 +1,7 @@
|
||||||
#ifdef TESTS_BUILD
|
#ifdef TESTS_BUILD
|
||||||
#include <Arbutils/StringView.hpp>
|
#include "../../../src/ScriptResolving/AngelScript/AngelScriptMetadata.hpp"
|
||||||
#include <doctest.h>
|
#include "Arbutils/StringView.hpp"
|
||||||
#include "../../src/ScriptResolving/AngelScript/AngelScriptMetadata.hpp"
|
#include "doctest.h"
|
||||||
|
|
||||||
TEST_CASE("Metadata without parameters") {
|
TEST_CASE("Metadata without parameters") {
|
||||||
auto m = AngelscriptMetadata("Foo");
|
auto m = AngelscriptMetadata("Foo");
|
|
@ -1,4 +1,4 @@
|
||||||
#ifdef TESTS_BUILD
|
#if TESTS_BUILD and ANGELSCRIPT
|
||||||
#include <doctest.h>
|
#include <doctest.h>
|
||||||
#include "../../src/Battling/Pokemon/CreatePokemon.hpp"
|
#include "../../src/Battling/Pokemon/CreatePokemon.hpp"
|
||||||
#include "../../src/ScriptResolving/AngelScript/AngelScriptResolver.hpp"
|
#include "../../src/ScriptResolving/AngelScript/AngelScriptResolver.hpp"
|
|
@ -1,4 +1,4 @@
|
||||||
#ifdef TESTS_BUILD
|
#if TESTS_BUILD && ANGELSCRIPT
|
||||||
#include <doctest.h>
|
#include <doctest.h>
|
||||||
#include "../../src/ScriptResolving/AngelScript/AngelScriptResolver.hpp"
|
#include "../../src/ScriptResolving/AngelScript/AngelScriptResolver.hpp"
|
||||||
#include "../../src/ScriptResolving/AngelScript/ContextPool.hpp"
|
#include "../../src/ScriptResolving/AngelScript/ContextPool.hpp"
|
|
@ -1,4 +1,4 @@
|
||||||
#ifdef TESTS_BUILD
|
#if TESTS_BUILD and ANGELSCRIPT
|
||||||
#include <doctest.h>
|
#include <doctest.h>
|
||||||
#include "../../../../src/Battling/Battle/Battle.hpp"
|
#include "../../../../src/Battling/Battle/Battle.hpp"
|
||||||
#include "../../../../src/Battling/Pokemon/CreatePokemon.hpp"
|
#include "../../../../src/Battling/Pokemon/CreatePokemon.hpp"
|
|
@ -1,4 +1,4 @@
|
||||||
#ifdef TESTS_BUILD
|
#if TESTS_BUILD and ANGELSCRIPT
|
||||||
#include <doctest.h>
|
#include <doctest.h>
|
||||||
#include "../../../../src/Battling/Pokemon/CreatePokemon.hpp"
|
#include "../../../../src/Battling/Pokemon/CreatePokemon.hpp"
|
||||||
#include "../../../../src/ScriptResolving/AngelScript/AngelScriptResolver.hpp"
|
#include "../../../../src/ScriptResolving/AngelScript/AngelScriptResolver.hpp"
|
|
@ -1,4 +1,4 @@
|
||||||
#ifdef TESTS_BUILD
|
#if TESTS_BUILD and ANGELSCRIPT
|
||||||
#include <doctest.h>
|
#include <doctest.h>
|
||||||
#include "../../../../src/ScriptResolving/AngelScript/AngelScriptResolver.hpp"
|
#include "../../../../src/ScriptResolving/AngelScript/AngelScriptResolver.hpp"
|
||||||
#include "../../../../src/ScriptResolving/AngelScript/ContextPool.hpp"
|
#include "../../../../src/ScriptResolving/AngelScript/ContextPool.hpp"
|
|
@ -1,4 +1,4 @@
|
||||||
#ifdef TESTS_BUILD
|
#if TESTS_BUILD and ANGELSCRIPT
|
||||||
#include <doctest.h>
|
#include <doctest.h>
|
||||||
#include "../../../../src/ScriptResolving/AngelScript/AngelScriptResolver.hpp"
|
#include "../../../../src/ScriptResolving/AngelScript/AngelScriptResolver.hpp"
|
||||||
#include "../../../../src/ScriptResolving/AngelScript/ContextPool.hpp"
|
#include "../../../../src/ScriptResolving/AngelScript/ContextPool.hpp"
|
|
@ -1,4 +1,4 @@
|
||||||
#ifdef TESTS_BUILD
|
#if TESTS_BUILD and ANGELSCRIPT
|
||||||
#include <doctest.h>
|
#include <doctest.h>
|
||||||
#include "../../../../src/ScriptResolving/AngelScript/AngelScriptResolver.hpp"
|
#include "../../../../src/ScriptResolving/AngelScript/AngelScriptResolver.hpp"
|
||||||
#include "../../../../src/ScriptResolving/AngelScript/ContextPool.hpp"
|
#include "../../../../src/ScriptResolving/AngelScript/ContextPool.hpp"
|
|
@ -1,4 +1,4 @@
|
||||||
#ifdef TESTS_BUILD
|
#if TESTS_BUILD && ANGELSCRIPT
|
||||||
#include <doctest.h>
|
#include <doctest.h>
|
||||||
#include "../../../../src/ScriptResolving/AngelScript/AngelScriptResolver.hpp"
|
#include "../../../../src/ScriptResolving/AngelScript/AngelScriptResolver.hpp"
|
||||||
#include "../../../../src/ScriptResolving/AngelScript/ContextPool.hpp"
|
#include "../../../../src/ScriptResolving/AngelScript/ContextPool.hpp"
|
|
@ -1,4 +1,4 @@
|
||||||
#ifdef TESTS_BUILD
|
#if TESTS_BUILD and ANGELSCRIPT
|
||||||
#include <doctest.h>
|
#include <doctest.h>
|
||||||
#include "../../../../src/ScriptResolving/AngelScript/AngelScriptResolver.hpp"
|
#include "../../../../src/ScriptResolving/AngelScript/AngelScriptResolver.hpp"
|
||||||
#include "../../../../src/ScriptResolving/AngelScript/ContextPool.hpp"
|
#include "../../../../src/ScriptResolving/AngelScript/ContextPool.hpp"
|
|
@ -0,0 +1,63 @@
|
||||||
|
#if TESTS_BUILD && WASM
|
||||||
|
#include <doctest.h>
|
||||||
|
#include "../../TestLibrary/TestLibrary.hpp"
|
||||||
|
#include "../../src/ScriptResolving/WASM/WebAssemblyFunctionCall.hpp"
|
||||||
|
#include "../../src/ScriptResolving/WASM/WebAssemblyScriptResolver.hpp"
|
||||||
|
|
||||||
|
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, load simple wat, run function") {
|
||||||
|
auto lib = dynamic_cast<WebAssemblyScriptResolver*>(PkmnLib::Battling::BattleLibrary::CreateScriptResolver());
|
||||||
|
lib->Initialize(TestLibrary::GetLibrary());
|
||||||
|
auto res = lib->LoadWatFromString(R"(
|
||||||
|
(module
|
||||||
|
(type $add_one_t (func (param i32) (result i32)))
|
||||||
|
(func $add_one_f (type $add_one_t) (param $value i32) (result i32)
|
||||||
|
local.get $value
|
||||||
|
i32.const 1
|
||||||
|
i32.add)
|
||||||
|
(export "add_one" (func $add_one_f))
|
||||||
|
)
|
||||||
|
)");
|
||||||
|
REQUIRE_EQ(0, res);
|
||||||
|
lib->Finalize();
|
||||||
|
auto func_opt = lib->GetFunction<1, 1>("add_one");
|
||||||
|
REQUIRE(func_opt.has_value());
|
||||||
|
auto& func = func_opt.value();
|
||||||
|
func.Loadi32(0, 684);
|
||||||
|
func.Call();
|
||||||
|
REQUIRE_EQ(func.GetResultAsi32(), 685);
|
||||||
|
delete lib;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Get a script resolver, load a real wasm script") {
|
||||||
|
auto lib = dynamic_cast<WebAssemblyScriptResolver*>(PkmnLib::Battling::BattleLibrary::CreateScriptResolver());
|
||||||
|
lib->Initialize(TestLibrary::GetLibrary());
|
||||||
|
std::string file_path = __FILE__;
|
||||||
|
std::string dir_path = file_path.substr(0, file_path.rfind("/"));
|
||||||
|
auto res = lib->LoadWasmFromFile(dir_path + "/gen7_scripts_rs.wasm");
|
||||||
|
REQUIRE_EQ(0, res);
|
||||||
|
lib->Finalize();
|
||||||
|
delete lib;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Get a script resolver, load a real wasm script, load test script") {
|
||||||
|
ArbUt::ScopedPtr<WebAssemblyScriptResolver> lib =
|
||||||
|
dynamic_cast<WebAssemblyScriptResolver*>(PkmnLib::Battling::BattleLibrary::CreateScriptResolver());
|
||||||
|
lib->Initialize(TestLibrary::GetLibrary());
|
||||||
|
std::string file_path = __FILE__;
|
||||||
|
std::string dir_path = file_path.substr(0, file_path.rfind("/"));
|
||||||
|
auto res = lib->LoadWasmFromFile(dir_path + "/gen7_scripts_rs.wasm");
|
||||||
|
REQUIRE_EQ(0, res);
|
||||||
|
lib->Finalize();
|
||||||
|
auto script = lib->LoadScript(nullptr, ScriptCategory::Attack, "test"_cnc);
|
||||||
|
EnsureNotNull(script);
|
||||||
|
script->OnInitialize(TestLibrary::GetLibrary(), {});
|
||||||
|
delete script;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
Binary file not shown.
Loading…
Reference in New Issue