#pragma once #include "RE/B/BSScript_Array.h" #include "RE/B/BSScript_IVirtualMachine.h" #include "RE/B/BSScript_Internal_NativeFunctionBase.h" #include "RE/B/BSScript_Internal_Stack.h" #include "RE/B/BSScript_Object.h" #include "RE/B/BSScript_ObjectTypeInfo.h" #include "RE/B/BSScript_StackFrame.h" #include "RE/B/BSScript_Struct.h" #include "RE/B/BSScript_TypeInfo.h" #include "RE/E/ENUM_FORM_ID.h" #include "RE/G/GameScript.h" #include "RE/T/TESObjectREFR.h" namespace RE::BSScript { namespace detail { struct wrapper_accessor; template using decay_t = std::conditional_t< std::is_pointer_v, std::remove_cv_t>, std::remove_cvref_t>; template [[nodiscard]] std::optional GetTypeInfo(); template void PackVariable(Variable& a_var, T&& a_val); template [[nodiscard]] T UnpackVariable(const Variable& a_var); template [[nodiscard]] consteval auto make_structure_tag( REX::TStaticString a_lhs, REX::TStaticString a_rhs) noexcept -> REX::TStaticString { char buf[a_lhs.length() + 1 + a_rhs.length() + 1]{ '\0' }; std::copy_n(a_lhs.data(), a_lhs.length(), buf); buf[a_lhs.length()] = '#'; std::copy_n(a_rhs.data(), a_rhs.length(), buf + a_lhs.length() + 1); return { static_cast(buf) }; } } template < REX::TStaticString Object, REX::TStaticString Structure> class structure_wrapper { private: static constexpr REX::TStaticString _full = detail::make_structure_tag(Object, Structure); public: static constexpr std::string_view name{ _full.data(), _full.length() }; structure_wrapper() { if (!_proxy) { const auto game = GameVM::GetSingleton(); const auto vm = game ? game->GetVM() : nullptr; if (!vm || !vm->CreateStruct(name, _proxy) || !_proxy) { REX::ERROR("failed to create structure of type \"{}\"", name); assert(false); } } } template std::optional find(std::string_view a_name, bool a_quiet = false) const { if (_proxy && _proxy->type) { const auto& mappings = _proxy->type->varNameIndexMap; const auto it = mappings.find(a_name); if (it != mappings.end()) { const auto& var = _proxy->variables[it->second]; return detail::UnpackVariable(var); } } if (!a_quiet) { REX::ERROR("failed to find var \"{}\" on structure \"{}\"", a_name, name); } return std::nullopt; } template bool insert(std::string_view a_name, T&& a_val) { if (_proxy && _proxy->type) { auto& mappings = _proxy->type->varNameIndexMap; const auto it = mappings.find(a_name); if (it != mappings.end()) { auto& var = _proxy->variables[it->second]; detail::PackVariable(var, std::forward(a_val)); return true; } } REX::ERROR("failed to pack var \"{}\" on structure \"{}\"", a_name, name); return false; } protected: friend struct detail::wrapper_accessor; explicit structure_wrapper(BSTSmartPointer a_proxy) noexcept : _proxy(std::move(a_proxy)) { assert(_proxy != nullptr); } [[nodiscard]] BSTSmartPointer get_proxy() const& { return _proxy; } [[nodiscard]] BSTSmartPointer get_proxy() && { return std::move(_proxy); } private: BSTSmartPointer _proxy; }; template struct script_traits final { using is_array = std::false_type; using is_string = std::false_type; using is_nullable = std::false_type; }; template < class T, class Allocator> struct script_traits< std::vector> final { using is_array = std::true_type; }; template struct script_traits< std::basic_string_view> final { using is_string = std::true_type; }; template < class Traits, class Allocator> struct script_traits< std::basic_string> final { using is_string = std::true_type; }; template struct script_traits< std::optional> final { using is_nullable = std::true_type; }; namespace detail { template struct _is_bstsmartptr : std::false_type {}; template class R> struct _is_bstsmartptr> : std::true_type {}; template using is_bstsmartptr = _is_bstsmartptr>; template inline constexpr bool is_bstsmartptr_v = is_bstsmartptr::value; template struct _is_structure_wrapper : std::false_type {}; template struct _is_structure_wrapper> : std::true_type {}; template using is_structure_wrapper = _is_structure_wrapper>; template inline constexpr bool is_structure_wrapper_v = is_structure_wrapper::value; // clang-format off template concept invocable_r = requires(F&& a_func, Args&&... a_args) { { std::invoke(std::forward(a_func), std::forward(a_args)...) } -> std::same_as; }; // clang-format on template concept decays_to = std::same_as, To>; template concept static_tag = std::same_as; // clang-format off template concept object = std::derived_from, TESForm> && requires { std::remove_cv_t::FORM_ID; }; template concept eobject = std::derived_from, ActiveEffect>&& requires { std::remove_cv_t::FORM_ID; }; // clang-format on template concept cobject = std::same_as, GameScript::RefrOrInventoryObj>; template concept vmobject = std::same_as, Object>; template concept vmobject_ptr = is_bstsmartptr_v && vmobject::element_type>; template concept vmvariable = std::same_as, Variable>; template concept string = std::same_as< typename script_traits>::is_string, std::true_type> && std::convertible_to && std::constructible_from; template concept integral = (std::integral && !std::same_as, bool>) || std::is_enum_v; template concept signed_integral = (std::signed_integral && !std::same_as, bool>) || (std::is_enum_v && std::signed_integral>); template concept unsigned_integral = (std::unsigned_integral && !std::same_as, bool>) || (std::is_enum_v && std::unsigned_integral>); template concept floating_point = std::floating_point; // clang-format off template concept boolean = std::same_as, bool>; // clang-format on // clang-format off template concept array = std::same_as< typename script_traits>::is_array, std::true_type> && std::is_default_constructible_v && requires(T a_array, typename T::value_type a_value) { { a_array.begin() } -> std::same_as; { a_array.end() } -> std::same_as; { a_array.size() } -> std::same_as; a_array.push_back(static_cast(a_value)); }; // clang-format on template concept wrapper = is_structure_wrapper_v; template concept nullable = std::same_as< typename script_traits>::is_nullable, std::true_type> && std::is_default_constructible_v && ((array || wrapper)) && // requires(T a_nullable) { // clang-format off static_cast(a_nullable); { *static_cast(a_nullable) } -> decays_to; // clang-format on }; template concept valid_self = static_tag || ((std::is_lvalue_reference_v && (object> || vmobject>))) || cobject || eobject>; template concept valid_parameter = (std::is_pointer_v && (object> || (std::is_const_v> && vmvariable>))) || (!std::is_reference_v && (cobject || vmobject_ptr || string || integral || floating_point || boolean || array || wrapper || nullable)); template concept valid_return = valid_parameter || std::same_as; struct wrapper_accessor { template [[nodiscard]] static T construct(const Variable& a_var) // requires(is_structure_wrapper_v) { return T(get(a_var)); } template [[nodiscard]] static auto get_proxy(T&& a_wrapper) // requires(wrapper>) { return std::forward(a_wrapper).get_proxy(); } }; } template [[nodiscard]] constexpr std::uint32_t GetVMTypeID() noexcept { return static_cast(T::FORM_ID); } template [[nodiscard]] constexpr std::uint32_t GetVMTypeID() noexcept { return static_cast(T::FORM_ID); } template [[nodiscard]] constexpr std::uint32_t GetVMTypeID() noexcept { return GetVMTypeID(); } template [[nodiscard]] std::optional GetTypeInfo() // requires(std::same_as) { return TypeInfo::RawType::kNone; } template [[nodiscard]] std::optional GetTypeInfo() { return TypeInfo::RawType::kNone; } template [[nodiscard]] std::optional GetTypeInfo() { const auto game = GameVM::GetSingleton(); const auto vm = game ? game->GetVM() : nullptr; BSTSmartPointer typeInfo; if (!vm || !vm->GetScriptObjectType(GetVMTypeID(), typeInfo) || !typeInfo) { assert(false); REX::ERROR("failed to get type info for object"sv); return std::nullopt; } else { return typeInfo.get(); } } template [[nodiscard]] std::optional GetTypeInfo() { const auto game = GameVM::GetSingleton(); const auto vm = game ? game->GetVM() : nullptr; BSTSmartPointer typeInfo; if (!vm || !vm->GetScriptObjectType(GetVMTypeID(), typeInfo) || !typeInfo) { assert(false); REX::ERROR("failed to get type info for object"sv); return std::nullopt; } else { return typeInfo.get(); } } template [[nodiscard]] std::optional GetTypeInfo() { return GetTypeInfo(); } template [[nodiscard]] std::optional GetTypeInfo() { const auto game = GameVM::GetSingleton(); const auto vm = game ? game->GetVM() : nullptr; static REL::Relocation baseObjectName{ ID::BSScriptUtil::BaseObjectName }; BSTSmartPointer typeInfo; if (!vm || !vm->GetScriptObjectType(*baseObjectName, typeInfo) || !typeInfo) { assert(false); REX::ERROR("failed to get type info for vm object"sv); return std::nullopt; } else { return typeInfo.get(); } } template [[nodiscard]] std::optional GetTypeInfo() { return TypeInfo::RawType::kVar; } template [[nodiscard]] std::optional GetTypeInfo() { return TypeInfo::RawType::kString; } template [[nodiscard]] std::optional GetTypeInfo() { return TypeInfo::RawType::kInt; } template [[nodiscard]] std::optional GetTypeInfo() { return TypeInfo::RawType::kFloat; } template [[nodiscard]] std::optional GetTypeInfo() { return TypeInfo::RawType::kBool; } template [[nodiscard]] std::optional GetTypeInfo() { using value_type = detail::decay_t::value_type>; auto typeInfo = detail::GetTypeInfo(); if (typeInfo) { typeInfo->SetArray(true); } return typeInfo; } template [[nodiscard]] std::optional GetTypeInfo() { const auto game = GameVM::GetSingleton(); const auto vm = game ? game->GetVM() : nullptr; if constexpr (detail::is_structure_wrapper_v) { BSTSmartPointer typeInfo; if (!vm || !vm->GetScriptStructType(T::name, typeInfo) || !typeInfo) { assert(false); REX::ERROR("failed to get type info for structure"sv); return std::nullopt; } else { return typeInfo.get(); } } else { static_assert(false && sizeof(T)); } } template [[nodiscard]] std::optional GetTypeInfo() { using value_type = typename std::remove_cv_t::value_type; return detail::GetTypeInfo(); } namespace detail { template __forceinline std::optional GetTypeInfo() { return BSScript::GetTypeInfo(); } } template void PackVariable(Variable& a_var, const volatile T* a_val) { if (!a_val) { a_var = nullptr; return; } const auto success = [&]() { const auto game = GameVM::GetSingleton(); const auto vm = game ? game->GetVM() : nullptr; BSTSmartPointer typeInfo; if (!vm || !vm->GetScriptObjectType(GetVMTypeID(), typeInfo) || !typeInfo) { return false; } const auto& handles = vm->GetObjectHandlePolicy(); const auto handle = handles.GetHandleForObject( GetVMTypeID(), const_cast( static_cast(a_val))); if (handle == handles.EmptyHandle()) { return false; } BSTSmartPointer object; if (!vm->FindBoundObject(handle, typeInfo->name.c_str(), false, object, false) && vm->CreateObject(typeInfo->name, object) && object) { auto& binding = vm->GetObjectBindPolicy(); binding.BindObject(object, handle); } if (!object) { return false; } a_var = std::move(object); return true; }(); if (!success) { assert(false); REX::ERROR("failed to pack variable"sv); a_var = nullptr; } } template void PackVariable(Variable& a_var, T a_val) { const auto success = [&]() { if (a_val.Reference()) { detail::PackVariable(a_var, a_val.Reference()); return true; } else if (!a_val.Container() || !a_val.UniqueID()) { return false; } const auto& container = *a_val.Container(); const auto uniqueID = a_val.UniqueID(); const auto typeInfo = GetTypeInfo(); if (!typeInfo || !typeInfo->IsObject()) { return false; } const auto& objInfo = static_cast(*typeInfo->data.complexTypeInfo); const auto game = GameVM::GetSingleton(); const auto vm = game ? game->GetVM() : nullptr; if (!vm) { return false; } const auto handle = GameScript::HandlePolicy::GetHandleForInventoryID(uniqueID, container.GetFormID()); BSTSmartPointer object; if (!vm->FindBoundObject(handle, objInfo.name.c_str(), false, object, false) && vm->CreateObject(objInfo.name, object)) { GameScript::BindCObject(object, a_val, *vm); } if (!object) { return false; } a_var = std::move(object); return true; }(); if (!success) { assert(false); REX::ERROR("failed to pack cobject"sv); a_var = nullptr; } } template void PackVariable(Variable& a_var, T a_val) { a_var = a_val ? std::move(a_val) : nullptr; } template void PackVariable(Variable& a_var, const volatile T* a_val) { a_var = a_val ? const_cast(a_val) : nullptr; } template void PackVariable(Variable& a_var, T&& a_val) // requires(detail::string>) { a_var = BSFixedString(std::forward(a_val)); } template void PackVariable(Variable& a_var, T a_val) { a_var = static_cast(a_val); } template void PackVariable(Variable& a_var, T a_val) { a_var = static_cast(a_val); } template void PackVariable(Variable& a_var, T a_val) { a_var = static_cast(a_val); } template void PackVariable(Variable& a_var, T a_val) { a_var = static_cast(a_val); } template void PackVariable(Variable& a_var, T&& a_val) // requires(detail::array>) { using value_type = detail::decay_t::value_type>; using reference_type = std::conditional_t< std::is_lvalue_reference_v, typename std::remove_cvref_t::value_type&, typename std::remove_cvref_t::value_type&&>; const auto success = [&]() { const auto game = GameVM::GetSingleton(); const auto vm = game ? game->GetVM() : nullptr; const auto typeInfo = GetTypeInfo>(); const auto size = a_val.size(); BSTSmartPointer out; if (!typeInfo || !vm || !vm->CreateArray(*typeInfo, static_cast(size), out) || !out) { return false; } std::uint32_t i = 0; for (auto&& elem : std::forward(a_val)) { detail::PackVariable(out->elements[i++], static_cast(elem)); } a_var = std::move(out); return true; }(); if (!success) { assert(false); REX::ERROR("failed to pack array"sv); a_var = nullptr; } } template void PackVariable(Variable& a_var, T&& a_val) // requires(detail::wrapper>) { a_var = detail::wrapper_accessor::get_proxy(std::forward(a_val)); } template void PackVariable(Variable& a_var, T&& a_val) // requires(detail::nullable>) { if (a_val) { detail::PackVariable(a_var, *std::forward(a_val)); } else { a_var = nullptr; } } namespace detail { template __forceinline void PackVariable(Variable& a_var, T&& a_val) { BSScript::PackVariable(a_var, std::forward(a_val)); } } template [[nodiscard]] std::monostate UnpackVariable([[maybe_unused]] const Variable& a_var) { assert(a_var.is()); return {}; } template [[nodiscard]] T* UnpackVariable(const Variable& a_var) { if (a_var.is() && get(a_var) == nullptr) { return nullptr; } const auto result = [&]() -> void* { if (!a_var.is()) { assert(false); return nullptr; } const auto game = GameVM::GetSingleton(); const auto vm = game ? game->GetVM() : nullptr; const auto object = get(a_var); if (!vm || !object) { return nullptr; } const auto& handles = vm->GetObjectHandlePolicy(); const auto handle = object->GetHandle(); if (!handles.IsHandleLoaded(handle)) { return nullptr; } return handles.GetObjectForHandle(GetVMTypeID(), handle); }(); if (!result) { assert(false); REX::ERROR("failed to get object from variable"sv); } return static_cast(result); } template [[nodiscard]] T* UnpackVariable(const Variable& a_var) { const auto result = [&]() -> void* { const auto game = GameVM::GetSingleton(); const auto vm = game ? game->GetVM() : nullptr; const auto object = get(a_var); if (!vm || !object) { return nullptr; } const auto& handles = vm->GetObjectHandlePolicy(); const auto handle = object->GetHandle(); if (!handles.HandleIsType(static_cast(ENUM_FORM_ID::kActiveEffect), handle)) return nullptr; return handles.GetObjectForHandle(GetVMTypeID(), handle); }(); if (!result) { assert(false); REX::ERROR("failed to get ActiveEffect from variable"sv); } return static_cast(result); } template [[nodiscard]] T UnpackVariable(const Variable& a_var) { const auto result = [&]() -> std::optional { if (!a_var.is()) { assert(false); return std::nullopt; } const auto obj = get(a_var); if (!obj) { return std::nullopt; } const auto game = GameVM::GetSingleton(); const auto vm = game ? game->GetVM() : nullptr; if (!vm) { return std::nullopt; } auto& policy = vm->GetObjectHandlePolicy(); const auto handle = obj->GetHandle(); if (!policy.HandleIsType(GetVMTypeID(), handle)) { return std::nullopt; } return T(handle); }(); if (!result) { assert(false); REX::ERROR("failed to get cobject from variable"sv); return T(); } else { return *result; } } template [[nodiscard]] T UnpackVariable(const Variable& a_var) { return get(a_var); } template [[nodiscard]] const T* UnpackVariable(const Variable& a_var) { return get(a_var); } template [[nodiscard]] T UnpackVariable(const Variable& a_var) { if (!a_var.is()) { assert(false); return T(); } if constexpr (std::same_as || std::same_as) { return get(a_var); } else { const auto str = get(a_var); return T(static_cast(str)); } } template [[nodiscard]] T UnpackVariable(const Variable& a_var) { if (!a_var.is()) { assert(false); return T(); } return static_cast(get(a_var)); } template [[nodiscard]] T UnpackVariable(const Variable& a_var) { if (!a_var.is()) { assert(false); return T(); } return static_cast(get(a_var)); } template [[nodiscard]] T UnpackVariable(const Variable& a_var) { if (!a_var.is()) { assert(false); return T(); } return static_cast(get(a_var)); } template [[nodiscard]] T UnpackVariable(const Variable& a_var) { if (!a_var.is()) { assert(false); return T(); } return static_cast(get(a_var)); } template [[nodiscard]] T UnpackVariable(const Variable& a_var) { if (!a_var.is()) { assert(false); return T(); } using value_type = typename T::value_type; T out; const auto in = get(a_var); for (const auto& var : in->elements) { out.push_back(detail::UnpackVariable(var)); } return out; } template [[nodiscard]] T UnpackVariable(const Variable& a_var) { return detail::wrapper_accessor::construct(a_var); } template [[nodiscard]] T UnpackVariable(const Variable& a_var) { if (a_var.is()) { return T(); } else { using value_type = typename T::value_type; return T(detail::UnpackVariable(a_var)); } } namespace detail { template [[nodiscard]] __forceinline T UnpackVariable(const Variable& a_var) { return BSScript::UnpackVariable>(a_var); } template consteval bool ValidateParameter() noexcept { // Must be one of (optionally cv-qualified): // * A pointer to one of: // * (optionally cv-qualified) `RE::TESForm` or any subclass thereof // * (optionally volatile) `const RE::BSScript::Variable` // * `RE::BSTSmartPointer` // * A string type which is one of: // * std::string // * std::string_view // * RE::BSFixedString // * RE::BSFixedStringCS // * A custom type which implements: // * A specialization of `RE::BSScript::script_traits` which provides a typedef `is_string` as `std::true_type` // * A conversion to `std::string_view` // * An integral type which is one of (optionally signed/unsigned): // * `char` // * `short` // * `int` // * `long` // * `long long` // * An enumeration type // * A floating point type which is one of: // * `float` // * `double` // * `long double` // * `bool` // * An array type which is one of: // * std::vector // * RE::BSTArray // * A custom type which implements: // * A specialization of `RE::BSScript::script_traits` which provides a typedef `is_array` as `std::true_type` // * A default constructor // * The following typedefs: // * `iterator` // * `size_type` // * `value_type` // * The following methods: // * `begin` // * Callable with no arguments // * Returns a type of `iterator` // * `end` // * Callable with no arguments // * Returns a type of `iterator` // * `size` // * Callable with no arguments // * Returns a type of `size_type` // * `push_back` // * Callable with an rvalue reference of type `value_type` // * Additionally, the array's `value_type` must be a valid parameter type // * A wrapper type which is one of: // * `RE::BSScript::structure_wrapper` // * A nullable type which is one of: // * std::optional // * A custom type which implements: // * A specialization of `RE::BSScript::script_traits` which provides a typedef `is_nullable` as `std::true_type` // * A default constructor // * The following typedefs: // * `value_type` // * The following methods: // * operator bool // * operator * // * Returns type of `value_type` // * Additionally, the nullable's `value_type` must be one of: // * An array type // * A structure type static_assert(detail::valid_parameter, "invalid parameter type"); if constexpr (detail::array || detail::nullable) { return detail::ValidateParameter::value_type>(); } else { return true; } } template consteval bool ValidateReturn() noexcept { // Must be one of: // * `void` // * A valid parameter type if constexpr (std::same_as) { return true; } else { return ValidateParameter(); } } template < bool LONG, class S, class... Args, class F, std::size_t... I> decltype(auto) DispatchHelper( Variable& a_self, Internal::VirtualMachine& a_vm, std::uint32_t a_stackID, const StackFrame& a_stackFrame, Stack& a_stack, const std::function& a_callback, std::index_sequence) { const auto self = [&]() -> S { // super::Call should guarantee the self parameter is not none if constexpr (detail::static_tag) { return std::monostate{}; } else if constexpr (detail::object>) { auto* const ptr = BSScript::UnpackVariable>(a_self); assert(ptr != nullptr); return *ptr; } else if constexpr (detail::eobject>) { auto* const ptr = BSScript::UnpackVariable>(a_self); assert(ptr != nullptr); return *ptr; } else if constexpr (detail::cobject>) { return BSScript::UnpackVariable>(a_self); } else if constexpr (detail::vmobject>) { const auto obj = get(a_self); assert(obj != nullptr); return *obj; } else { static_assert(false && sizeof(S), "unhandled case"); } }; const auto pframe = std::addressof(a_stackFrame); const auto page = a_stack.GetPageForFrame(pframe); const auto args = [&](std::in_place_type_t, std::size_t a_index) { if (a_stackFrame.size > a_index) { return UnpackVariable>( a_stack.GetStackFrameVariable( pframe, static_cast(a_index), page)); } else { assert(false); return T(); } }; if constexpr (LONG) { return a_callback( reinterpret_cast(a_vm), // TODO: static_cast a_stackID, self(), args(std::in_place_type_t{}, I)...); } else { return a_callback( self(), args(std::in_place_type_t{}, I)...); } } } template < class F, bool LONG, class R, class S, class... Args> class NativeFunction : public NF_util::NativeFunctionBase { private: using super = NF_util::NativeFunctionBase; public: // A function which takes a self parameter must be one of (optionally cv-qualified): // * An lvalue reference to one of: // * `RE::TESForm` or any subclass thereof // * `RE::BSScript::Object` // * `RE::GameScript::RefrOrInventoryObj` // A function which does not take a self parameter (a global function) must tag the self slot with `std::monostate` static_assert(detail::valid_self, "invalid self type"); static_assert(detail::ValidateReturn()); static_assert(((detail::ValidateParameter(), ...), true)); template NativeFunction(std::string_view a_object, std::string_view a_function, Fn a_func, bool a_isLatent) // requires(detail::invocable_r || detail::invocable_r) : super(a_object, a_function, sizeof...(Args), detail::static_tag, a_isLatent), _stub(std::move(a_func)) { assert(super::descTable.paramCount == sizeof...(Args)); std::size_t i = 0; ((super::descTable.entries[i++].second = GetTypeInfo>().value_or(nullptr)), ...); super::retType = GetTypeInfo>().value_or(nullptr); } // override (NF_util::NativeFunctionBase) bool HasStub() const override { return static_cast(_stub); } // 15 bool MarshallAndDispatch(Variable& a_self, Internal::VirtualMachine& a_vm, std::uint32_t a_stackID, Variable& a_retVal, const StackFrame& a_stackFrame) const override // 16 { a_retVal = nullptr; const auto stack = a_stackFrame.parent; if (!stack) { assert(false); REX::ERROR("native function called without relevant stack"sv); return false; } const auto invoke = [&]() { return detail::DispatchHelper( a_self, a_vm, a_stackID, a_stackFrame, *stack, _stub, std::index_sequence_for{}); }; if constexpr (!std::same_as) { PackVariable(a_retVal, invoke()); } else { invoke(); } return true; } private: std::function _stub; }; template < class R, class S, class... Args> NativeFunction(std::string_view, std::string_view, R (*)(S, Args...), bool) -> NativeFunction< R(S, Args...), false, R, S, Args...>; template < class R, class S, class... Args> NativeFunction(std::string_view, std::string_view, R (*)(IVirtualMachine&, std::uint32_t, S, Args...), bool) -> NativeFunction< R(IVirtualMachine&, std::uint32_t, S, Args...), true, R, S, Args...>; template void IVirtualMachine::BindNativeMethod( std::string_view a_object, std::string_view a_function, F a_func, std::optional a_taskletCallable, bool a_isLatent) { const auto success = BindNativeMethod(new NativeFunction( a_object, a_function, std::move(a_func), a_isLatent)); if (!success) { REX::ERROR("failed to register method \"{}\" on object \"{}\"", a_function, a_object); } if (success && a_taskletCallable) { SetCallableFromTasklets(a_object.data(), a_function.data(), *a_taskletCallable); } } namespace detail { template BSScrapArray PackVariables(Args&&... a_args) { constexpr auto size = sizeof...(a_args); auto args = std::make_tuple(std::forward(a_args)...); BSScrapArray result{ size }; [&](std::index_sequence) { ((BSScript::PackVariable(result.at(p), std::get

(args))), ...); }(std::make_index_sequence{}); return result; } } template bool IVirtualMachine::DispatchStaticCall( const BSFixedString& a_objName, const BSFixedString& a_funcName, const BSTSmartPointer& a_callback, Args... a_args) { return DispatchStaticCall( a_objName, a_funcName, [&](BSScrapArray& a_out) { a_out = detail::PackVariables(a_args...); return true; }, a_callback); } template bool IVirtualMachine::DispatchMethodCall( std::uint64_t a_objHandle, const BSFixedString& a_objName, const BSFixedString& a_funcName, const BSTSmartPointer& a_callback, Args... a_args) { return DispatchMethodCall( a_objHandle, a_objName, a_funcName, [&](BSScrapArray& a_out) { a_out = detail::PackVariables(a_args...); return true; }, a_callback); } }