Initial work on WebAssembly script provider
continuous-integration/drone/push Build is failing Details

This commit is contained in:
Deukhoofd 2022-05-14 11:48:27 +02:00
parent c6775d7089
commit e32d655d80
Signed by: Deukhoofd
GPG Key ID: F63E044490819F6F
31 changed files with 995 additions and 19 deletions

View File

@ -73,10 +73,15 @@ if (SCRIPT_PROVIDER STREQUAL "angelscript")
WORKING_DIRECTORY ${Angelscript_SOURCE_DIR}/angelscript/projects/cmake)
endif ()
include_directories(${CMAKE_CURRENT_BINARY_DIR}/include)
include_directories(${Angelscript_SOURCE_DIR}/angelscript/include)
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 ()
include_directories(${CMAKE_CURRENT_BINARY_DIR}/include)
if (WINDOWS)
SET(CMAKE_SYSTEM_NAME Windows)
@ -160,13 +165,19 @@ if (SCRIPT_PROVIDER STREQUAL "angelscript")
set(_TESTLINKS ${_TESTLINKS} AngelscriptDebugger)
endif ()
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 ()
file(GLOB_RECURSE CORE_SRC_FILES ${FILE_SOURCE})
add_library(pkmnLib ${LIBTYPE} ${CORE_SRC_FILES})
# Enable all warnings, and make them error when occurring.
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
check_ipo_supported(RESULT IPO_SUPPORTED)
@ -180,12 +191,21 @@ SET(_TESTLINKS ${_TESTLINKS} pkmnLib CreatureLib Arbutils)
if (SCRIPT_PROVIDER STREQUAL "angelscript")
message(STATUS "Using Angelscript as script provider.")
target_link_directories(pkmnLib PUBLIC ${Angelscript_BINARY_DIR})
ADD_DEFINITIONS(-D ANGELSCRIPT=1)
SET(_LINKS ${_LINKS} angelscript)
SET(_TESTLINKS ${_TESTLINKS} angelscript)
if (ANGELSCRIPT_DEBUGGER)
ADD_DEFINITIONS(-D ANGELSCRIPT_DEBUGGER=1)
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 ()
# If we are building for Windows we need to set some specific variables.
@ -245,6 +265,8 @@ if (PKMNLIB_TESTS)
endif ()
endif ()
if (ANGELSCRIPT_DEBUGGER)
include_directories(extern/AngelscriptDebuggerServer/extern/asio-1.18.2/include)
endif ()
if (SCRIPT_PROVIDER STREQUAL "angelscript")
if (ANGELSCRIPT_DEBUGGER)
include_directories(extern/AngelscriptDebuggerServer/extern/asio-1.18.2/include)
endif ()
endif()

1
sanitizer_ignores Normal file
View File

@ -0,0 +1 @@
src:*/std_function.h

View File

@ -1,6 +1,7 @@
#ifndef PKMNLIB_SCRIPTRESOLVER_HPP
#define PKMNLIB_SCRIPTRESOLVER_HPP
#include <CreatureLib/Battling/ScriptHandling/ScriptResolver.hpp>
#include "../EvolutionScript.hpp"
namespace PkmnLib::Battling {

View File

@ -18,9 +18,19 @@ namespace PkmnLib::Library {
return {};
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 {
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>();
}
};
}

View File

@ -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));
}

View File

@ -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

View File

@ -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);
}

View File

@ -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

View File

@ -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(&params, sizeof...(Args), ps);
return wasm_functype_new(&params, &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

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -8,4 +8,9 @@ TEST_CASE("Able to build and destroy empty library") {
delete lib;
}
REGISTER_EXCEPTION_TRANSLATOR(ArbUt::Exception& ex) {
return {(std::string(ex.what()) + "\n" + ex.GetStacktrace()).c_str()};
}
#endif

View File

@ -1,4 +1,4 @@
#ifdef TESTS_BUILD
#if TESTS_BUILD and ANGELSCRIPT
#include <doctest.h>
#include "../../src/Battling/Pokemon/CreatePokemon.hpp"
#include "../../src/ScriptResolving/AngelScript/AngelScriptResolver.hpp"

View File

@ -1,4 +1,4 @@
#ifdef TESTS_BUILD
#if TESTS_BUILD and ANGELSCRIPT
#include <doctest.h>
#include "../../src/Battling/Pokemon/CreatePokemon.hpp"
#include "../../src/ScriptResolving/AngelScript/AngelScriptResolver.hpp"

View File

@ -1,7 +1,7 @@
#ifdef TESTS_BUILD
#include <Arbutils/StringView.hpp>
#include <doctest.h>
#include "../../src/ScriptResolving/AngelScript/AngelScriptMetadata.hpp"
#include "../../../src/ScriptResolving/AngelScript/AngelScriptMetadata.hpp"
#include "Arbutils/StringView.hpp"
#include "doctest.h"
TEST_CASE("Metadata without parameters") {
auto m = AngelscriptMetadata("Foo");

View File

@ -1,4 +1,4 @@
#ifdef TESTS_BUILD
#if TESTS_BUILD and ANGELSCRIPT
#include <doctest.h>
#include "../../src/Battling/Pokemon/CreatePokemon.hpp"
#include "../../src/ScriptResolving/AngelScript/AngelScriptResolver.hpp"

View File

@ -1,4 +1,4 @@
#ifdef TESTS_BUILD
#if TESTS_BUILD && ANGELSCRIPT
#include <doctest.h>
#include "../../src/ScriptResolving/AngelScript/AngelScriptResolver.hpp"
#include "../../src/ScriptResolving/AngelScript/ContextPool.hpp"

View File

@ -1,4 +1,4 @@
#ifdef TESTS_BUILD
#if TESTS_BUILD and ANGELSCRIPT
#include <doctest.h>
#include "../../../../src/Battling/Battle/Battle.hpp"
#include "../../../../src/Battling/Pokemon/CreatePokemon.hpp"

View File

@ -1,4 +1,4 @@
#ifdef TESTS_BUILD
#if TESTS_BUILD and ANGELSCRIPT
#include <doctest.h>
#include "../../../../src/Battling/Pokemon/CreatePokemon.hpp"
#include "../../../../src/ScriptResolving/AngelScript/AngelScriptResolver.hpp"

View File

@ -1,4 +1,4 @@
#ifdef TESTS_BUILD
#if TESTS_BUILD and ANGELSCRIPT
#include <doctest.h>
#include "../../../../src/ScriptResolving/AngelScript/AngelScriptResolver.hpp"
#include "../../../../src/ScriptResolving/AngelScript/ContextPool.hpp"

View File

@ -1,4 +1,4 @@
#ifdef TESTS_BUILD
#if TESTS_BUILD and ANGELSCRIPT
#include <doctest.h>
#include "../../../../src/ScriptResolving/AngelScript/AngelScriptResolver.hpp"
#include "../../../../src/ScriptResolving/AngelScript/ContextPool.hpp"

View File

@ -1,4 +1,4 @@
#ifdef TESTS_BUILD
#if TESTS_BUILD and ANGELSCRIPT
#include <doctest.h>
#include "../../../../src/ScriptResolving/AngelScript/AngelScriptResolver.hpp"
#include "../../../../src/ScriptResolving/AngelScript/ContextPool.hpp"

View File

@ -1,4 +1,4 @@
#ifdef TESTS_BUILD
#if TESTS_BUILD && ANGELSCRIPT
#include <doctest.h>
#include "../../../../src/ScriptResolving/AngelScript/AngelScriptResolver.hpp"
#include "../../../../src/ScriptResolving/AngelScript/ContextPool.hpp"

View File

@ -1,4 +1,4 @@
#ifdef TESTS_BUILD
#if TESTS_BUILD and ANGELSCRIPT
#include <doctest.h>
#include "../../../../src/ScriptResolving/AngelScript/AngelScriptResolver.hpp"
#include "../../../../src/ScriptResolving/AngelScript/ContextPool.hpp"

View File

@ -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.