Files
Commonwealth-Online-Public/plugin/lib/commonlibf4/include/RE/B/BSScriptUtil.h
T
2026-05-30 18:33:24 +12:00

1282 lines
33 KiB
C++

#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 <class T>
using decay_t =
std::conditional_t<
std::is_pointer_v<T>,
std::remove_cv_t<std::remove_pointer_t<T>>,
std::remove_cvref_t<T>>;
template <class T>
[[nodiscard]] std::optional<TypeInfo> GetTypeInfo();
template <class T>
void PackVariable(Variable& a_var, T&& a_val);
template <class T>
[[nodiscard]] T UnpackVariable(const Variable& a_var);
template <class CharT, std::size_t N1, std::size_t N2>
[[nodiscard]] consteval auto make_structure_tag(
REX::TStaticString<CharT, N1> a_lhs,
REX::TStaticString<CharT, N2> a_rhs) noexcept
-> REX::TStaticString<CharT, N1 + 1 + N2>
{
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<const char(&)[N1 + 1 + N2 + 1]>(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 <class T>
std::optional<T> 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<T>(var);
}
}
if (!a_quiet) {
REX::ERROR("failed to find var \"{}\" on structure \"{}\"", a_name, name);
}
return std::nullopt;
}
template <class T>
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<T>(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<Struct> a_proxy) noexcept :
_proxy(std::move(a_proxy))
{
assert(_proxy != nullptr);
}
[[nodiscard]] BSTSmartPointer<Struct> get_proxy() const& { return _proxy; }
[[nodiscard]] BSTSmartPointer<Struct> get_proxy() && { return std::move(_proxy); }
private:
BSTSmartPointer<Struct> _proxy;
};
template <class>
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<T, Allocator>> final
{
using is_array = std::true_type;
};
template <class Traits>
struct script_traits<
std::basic_string_view<char, Traits>> final
{
using is_string = std::true_type;
};
template <
class Traits,
class Allocator>
struct script_traits<
std::basic_string<char, Traits, Allocator>> final
{
using is_string = std::true_type;
};
template <class T>
struct script_traits<
std::optional<T>> final
{
using is_nullable = std::true_type;
};
namespace detail
{
template <class>
struct _is_bstsmartptr :
std::false_type
{};
template <class T, template <class> class R>
struct _is_bstsmartptr<BSTSmartPointer<T, R>> :
std::true_type
{};
template <class T>
using is_bstsmartptr = _is_bstsmartptr<std::remove_cv_t<T>>;
template <class T>
inline constexpr bool is_bstsmartptr_v = is_bstsmartptr<T>::value;
template <class>
struct _is_structure_wrapper :
std::false_type
{};
template <REX::TStaticString O, REX::TStaticString S>
struct _is_structure_wrapper<structure_wrapper<O, S>> :
std::true_type
{};
template <class T>
using is_structure_wrapper = _is_structure_wrapper<std::remove_cv_t<T>>;
template <class T>
inline constexpr bool is_structure_wrapper_v = is_structure_wrapper<T>::value;
// clang-format off
template <class F, class R, class... Args>
concept invocable_r = requires(F&& a_func, Args&&... a_args)
{
{ std::invoke(std::forward<F>(a_func), std::forward<Args>(a_args)...) } -> std::same_as<R>;
};
// clang-format on
template <class From, class To>
concept decays_to = std::same_as<std::decay_t<From>, To>;
template <class T>
concept static_tag = std::same_as<T, std::monostate>;
// clang-format off
template <class T>
concept object =
std::derived_from<std::remove_cv_t<T>, TESForm> &&
requires { std::remove_cv_t<T>::FORM_ID; };
template <class T>
concept eobject =
std::derived_from<std::remove_cv_t<T>, ActiveEffect>&&
requires { std::remove_cv_t<T>::FORM_ID; };
// clang-format on
template <class T>
concept cobject = std::same_as<std::remove_cv_t<T>, GameScript::RefrOrInventoryObj>;
template <class T>
concept vmobject = std::same_as<std::remove_cv_t<T>, Object>;
template <class T>
concept vmobject_ptr =
is_bstsmartptr_v<T> &&
vmobject<typename std::remove_cv_t<T>::element_type>;
template <class T>
concept vmvariable = std::same_as<std::remove_cv_t<T>, Variable>;
template <class T>
concept string =
std::same_as<
typename script_traits<std::remove_cv_t<T>>::is_string,
std::true_type> &&
std::convertible_to<T, std::string_view> &&
std::constructible_from<T, std::string_view>;
template <class T>
concept integral =
(std::integral<T> && !std::same_as<std::remove_cv_t<T>, bool>) ||
std::is_enum_v<T>;
template <class T>
concept signed_integral =
(std::signed_integral<T> && !std::same_as<std::remove_cv_t<T>, bool>) ||
(std::is_enum_v<T> && std::signed_integral<std::underlying_type_t<T>>);
template <class T>
concept unsigned_integral =
(std::unsigned_integral<T> && !std::same_as<std::remove_cv_t<T>, bool>) ||
(std::is_enum_v<T> && std::unsigned_integral<std::underlying_type_t<T>>);
template <class T>
concept floating_point = std::floating_point<T>;
// clang-format off
template <class T>
concept boolean = std::same_as<std::remove_cv_t<T>, bool>;
// clang-format on
// clang-format off
template <class T>
concept array =
std::same_as<
typename script_traits<std::remove_cv_t<T>>::is_array,
std::true_type> &&
std::is_default_constructible_v<T> &&
requires(T a_array, typename T::value_type a_value)
{
{ a_array.begin() } -> std::same_as<typename T::iterator>;
{ a_array.end() } -> std::same_as<typename T::iterator>;
{ a_array.size() } -> std::same_as<typename T::size_type>;
a_array.push_back(static_cast<typename T::value_type&&>(a_value));
};
// clang-format on
template <class T>
concept wrapper = is_structure_wrapper_v<T>;
template <class T>
concept nullable =
std::same_as<
typename script_traits<std::remove_cv_t<T>>::is_nullable,
std::true_type> &&
std::is_default_constructible_v<T> &&
((array<typename T::value_type> || wrapper<typename T::value_type>)) && //
requires(T a_nullable) {
// clang-format off
static_cast<bool>(a_nullable);
{ *static_cast<T&&>(a_nullable) } -> decays_to<typename T::value_type>;
// clang-format on
};
template <class T>
concept valid_self =
static_tag<T> ||
((std::is_lvalue_reference_v<T> &&
(object<std::remove_reference_t<T>> || vmobject<std::remove_reference_t<T>>))) ||
cobject<T> || eobject<std::remove_reference_t<T>>;
template <class T>
concept valid_parameter =
(std::is_pointer_v<T> &&
(object<std::remove_pointer_t<T>> ||
(std::is_const_v<std::remove_pointer_t<T>> && vmvariable<std::remove_pointer_t<T>>))) ||
(!std::is_reference_v<T> &&
(cobject<T> ||
vmobject_ptr<T> ||
string<T> ||
integral<T> ||
floating_point<T> ||
boolean<T> ||
array<T> ||
wrapper<T> ||
nullable<T>));
template <class T>
concept valid_return =
valid_parameter<T> ||
std::same_as<T, void>;
struct wrapper_accessor
{
template <class T>
[[nodiscard]] static T construct(const Variable& a_var) //
requires(is_structure_wrapper_v<T>)
{
return T(get<Struct>(a_var));
}
template <class T>
[[nodiscard]] static auto get_proxy(T&& a_wrapper) //
requires(wrapper<std::remove_reference_t<T>>)
{
return std::forward<T>(a_wrapper).get_proxy();
}
};
}
template <detail::object T>
[[nodiscard]] constexpr std::uint32_t GetVMTypeID() noexcept
{
return static_cast<std::uint32_t>(T::FORM_ID);
}
template <detail::eobject T>
[[nodiscard]] constexpr std::uint32_t GetVMTypeID() noexcept
{
return static_cast<std::uint32_t>(T::FORM_ID);
}
template <detail::cobject T>
[[nodiscard]] constexpr std::uint32_t GetVMTypeID() noexcept
{
return GetVMTypeID<TESObjectREFR>();
}
template <class T>
[[nodiscard]] std::optional<TypeInfo> GetTypeInfo() //
requires(std::same_as<T, void>)
{
return TypeInfo::RawType::kNone;
}
template <detail::static_tag T>
[[nodiscard]] std::optional<TypeInfo> GetTypeInfo()
{
return TypeInfo::RawType::kNone;
}
template <detail::object T>
[[nodiscard]] std::optional<TypeInfo> GetTypeInfo()
{
const auto game = GameVM::GetSingleton();
const auto vm = game ? game->GetVM() : nullptr;
BSTSmartPointer<ObjectTypeInfo> typeInfo;
if (!vm ||
!vm->GetScriptObjectType(GetVMTypeID<T>(), typeInfo) ||
!typeInfo) {
assert(false);
REX::ERROR("failed to get type info for object"sv);
return std::nullopt;
} else {
return typeInfo.get();
}
}
template <detail::eobject T>
[[nodiscard]] std::optional<TypeInfo> GetTypeInfo()
{
const auto game = GameVM::GetSingleton();
const auto vm = game ? game->GetVM() : nullptr;
BSTSmartPointer<ObjectTypeInfo> typeInfo;
if (!vm ||
!vm->GetScriptObjectType(GetVMTypeID<T>(), typeInfo) ||
!typeInfo) {
assert(false);
REX::ERROR("failed to get type info for object"sv);
return std::nullopt;
} else {
return typeInfo.get();
}
}
template <detail::cobject T>
[[nodiscard]] std::optional<TypeInfo> GetTypeInfo()
{
return GetTypeInfo<TESObjectREFR>();
}
template <detail::vmobject T>
[[nodiscard]] std::optional<TypeInfo> GetTypeInfo()
{
const auto game = GameVM::GetSingleton();
const auto vm = game ? game->GetVM() : nullptr;
static REL::Relocation<RE::BSFixedString*> baseObjectName{ ID::BSScriptUtil::BaseObjectName };
BSTSmartPointer<ObjectTypeInfo> 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 <detail::vmvariable T>
[[nodiscard]] std::optional<TypeInfo> GetTypeInfo()
{
return TypeInfo::RawType::kVar;
}
template <detail::string T>
[[nodiscard]] std::optional<TypeInfo> GetTypeInfo()
{
return TypeInfo::RawType::kString;
}
template <detail::integral T>
[[nodiscard]] std::optional<TypeInfo> GetTypeInfo()
{
return TypeInfo::RawType::kInt;
}
template <detail::floating_point T>
[[nodiscard]] std::optional<TypeInfo> GetTypeInfo()
{
return TypeInfo::RawType::kFloat;
}
template <detail::boolean T>
[[nodiscard]] std::optional<TypeInfo> GetTypeInfo()
{
return TypeInfo::RawType::kBool;
}
template <detail::array T>
[[nodiscard]] std::optional<TypeInfo> GetTypeInfo()
{
using value_type = detail::decay_t<typename std::remove_cv_t<T>::value_type>;
auto typeInfo = detail::GetTypeInfo<value_type>();
if (typeInfo) {
typeInfo->SetArray(true);
}
return typeInfo;
}
template <detail::wrapper T>
[[nodiscard]] std::optional<TypeInfo> GetTypeInfo()
{
const auto game = GameVM::GetSingleton();
const auto vm = game ? game->GetVM() : nullptr;
if constexpr (detail::is_structure_wrapper_v<T>) {
BSTSmartPointer<StructTypeInfo> 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 <detail::nullable T>
[[nodiscard]] std::optional<TypeInfo> GetTypeInfo()
{
using value_type = typename std::remove_cv_t<T>::value_type;
return detail::GetTypeInfo<value_type>();
}
namespace detail
{
template <class T>
__forceinline std::optional<TypeInfo> GetTypeInfo()
{
return BSScript::GetTypeInfo<T>();
}
}
template <detail::object T>
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<ObjectTypeInfo> typeInfo;
if (!vm ||
!vm->GetScriptObjectType(GetVMTypeID<T>(), typeInfo) ||
!typeInfo) {
return false;
}
const auto& handles = vm->GetObjectHandlePolicy();
const auto handle = handles.GetHandleForObject(
GetVMTypeID<T>(),
const_cast<const void*>(
static_cast<const volatile void*>(a_val)));
if (handle == handles.EmptyHandle()) {
return false;
}
BSTSmartPointer<Object> 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 <detail::cobject T>
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<GameScript::RefrOrInventoryObj>();
if (!typeInfo || !typeInfo->IsObject()) {
return false;
}
const auto& objInfo = static_cast<const ObjectTypeInfo&>(*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> 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 <detail::vmobject_ptr T>
void PackVariable(Variable& a_var, T a_val)
{
a_var = a_val ? std::move(a_val) : nullptr;
}
template <detail::vmvariable T>
void PackVariable(Variable& a_var, const volatile T* a_val)
{
a_var = a_val ? const_cast<T*>(a_val) : nullptr;
}
template <class T>
void PackVariable(Variable& a_var, T&& a_val) //
requires(detail::string<std::remove_reference_t<T>>)
{
a_var = BSFixedString(std::forward<T>(a_val));
}
template <detail::signed_integral T>
void PackVariable(Variable& a_var, T a_val)
{
a_var = static_cast<std::int32_t>(a_val);
}
template <detail::unsigned_integral T>
void PackVariable(Variable& a_var, T a_val)
{
a_var = static_cast<std::uint32_t>(a_val);
}
template <detail::floating_point T>
void PackVariable(Variable& a_var, T a_val)
{
a_var = static_cast<float>(a_val);
}
template <detail::boolean T>
void PackVariable(Variable& a_var, T a_val)
{
a_var = static_cast<bool>(a_val);
}
template <class T>
void PackVariable(Variable& a_var, T&& a_val) //
requires(detail::array<std::remove_reference_t<T>>)
{
using value_type = detail::decay_t<typename std::remove_cvref_t<T>::value_type>;
using reference_type =
std::conditional_t<
std::is_lvalue_reference_v<T>,
typename std::remove_cvref_t<T>::value_type&,
typename std::remove_cvref_t<T>::value_type&&>;
const auto success = [&]() {
const auto game = GameVM::GetSingleton();
const auto vm = game ? game->GetVM() : nullptr;
const auto typeInfo = GetTypeInfo<std::remove_cvref_t<T>>();
const auto size = a_val.size();
BSTSmartPointer<Array> out;
if (!typeInfo ||
!vm ||
!vm->CreateArray(*typeInfo, static_cast<std::uint32_t>(size), out) ||
!out) {
return false;
}
std::uint32_t i = 0;
for (auto&& elem : std::forward<T>(a_val)) {
detail::PackVariable(out->elements[i++], static_cast<reference_type>(elem));
}
a_var = std::move(out);
return true;
}();
if (!success) {
assert(false);
REX::ERROR("failed to pack array"sv);
a_var = nullptr;
}
}
template <class T>
void PackVariable(Variable& a_var, T&& a_val) //
requires(detail::wrapper<std::remove_reference_t<T>>)
{
a_var = detail::wrapper_accessor::get_proxy(std::forward<T>(a_val));
}
template <class T>
void PackVariable(Variable& a_var, T&& a_val) //
requires(detail::nullable<std::remove_reference_t<T>>)
{
if (a_val) {
detail::PackVariable(a_var, *std::forward<T>(a_val));
} else {
a_var = nullptr;
}
}
namespace detail
{
template <class T>
__forceinline void PackVariable(Variable& a_var, T&& a_val)
{
BSScript::PackVariable(a_var, std::forward<T>(a_val));
}
}
template <detail::static_tag T>
[[nodiscard]] std::monostate UnpackVariable([[maybe_unused]] const Variable& a_var)
{
assert(a_var.is<std::nullptr_t>());
return {};
}
template <detail::object T>
[[nodiscard]] T* UnpackVariable(const Variable& a_var)
{
if (a_var.is<Object>() && get<Object>(a_var) == nullptr) {
return nullptr;
}
const auto result = [&]() -> void* {
if (!a_var.is<Object>()) {
assert(false);
return nullptr;
}
const auto game = GameVM::GetSingleton();
const auto vm = game ? game->GetVM() : nullptr;
const auto object = get<Object>(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<T>(), handle);
}();
if (!result) {
assert(false);
REX::ERROR("failed to get object from variable"sv);
}
return static_cast<T*>(result);
}
template <detail::eobject T>
[[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<Object>(a_var);
if (!vm || !object) {
return nullptr;
}
const auto& handles = vm->GetObjectHandlePolicy();
const auto handle = object->GetHandle();
if (!handles.HandleIsType(static_cast<uint32_t>(ENUM_FORM_ID::kActiveEffect), handle))
return nullptr;
return handles.GetObjectForHandle(GetVMTypeID<T>(), handle);
}();
if (!result) {
assert(false);
REX::ERROR("failed to get ActiveEffect from variable"sv);
}
return static_cast<T*>(result);
}
template <detail::cobject T>
[[nodiscard]] T UnpackVariable(const Variable& a_var)
{
const auto result = [&]() -> std::optional<T> {
if (!a_var.is<Object>()) {
assert(false);
return std::nullopt;
}
const auto obj = get<Object>(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<T>(), 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 <detail::vmobject_ptr T>
[[nodiscard]] T UnpackVariable(const Variable& a_var)
{
return get<Object>(a_var);
}
template <detail::vmvariable T>
[[nodiscard]] const T* UnpackVariable(const Variable& a_var)
{
return get<Variable>(a_var);
}
template <detail::string T>
[[nodiscard]] T UnpackVariable(const Variable& a_var)
{
if (!a_var.is<BSFixedString>()) {
assert(false);
return T();
}
if constexpr (std::same_as<T, BSFixedString> ||
std::same_as<T, BSFixedStringCS>) {
return get<BSFixedString>(a_var);
} else {
const auto str = get<BSFixedString>(a_var);
return T(static_cast<std::string_view>(str));
}
}
template <detail::signed_integral T>
[[nodiscard]] T UnpackVariable(const Variable& a_var)
{
if (!a_var.is<std::int32_t>()) {
assert(false);
return T();
}
return static_cast<T>(get<std::int32_t>(a_var));
}
template <detail::unsigned_integral T>
[[nodiscard]] T UnpackVariable(const Variable& a_var)
{
if (!a_var.is<std::uint32_t>()) {
assert(false);
return T();
}
return static_cast<T>(get<std::uint32_t>(a_var));
}
template <detail::floating_point T>
[[nodiscard]] T UnpackVariable(const Variable& a_var)
{
if (!a_var.is<float>()) {
assert(false);
return T();
}
return static_cast<T>(get<float>(a_var));
}
template <detail::boolean T>
[[nodiscard]] T UnpackVariable(const Variable& a_var)
{
if (!a_var.is<bool>()) {
assert(false);
return T();
}
return static_cast<T>(get<bool>(a_var));
}
template <detail::array T>
[[nodiscard]] T UnpackVariable(const Variable& a_var)
{
if (!a_var.is<Array>()) {
assert(false);
return T();
}
using value_type = typename T::value_type;
T out;
const auto in = get<Array>(a_var);
for (const auto& var : in->elements) {
out.push_back(detail::UnpackVariable<value_type>(var));
}
return out;
}
template <detail::wrapper T>
[[nodiscard]] T UnpackVariable(const Variable& a_var)
{
return detail::wrapper_accessor::construct<T>(a_var);
}
template <detail::nullable T>
[[nodiscard]] T UnpackVariable(const Variable& a_var)
{
if (a_var.is<std::nullptr_t>()) {
return T();
} else {
using value_type = typename T::value_type;
return T(detail::UnpackVariable<value_type>(a_var));
}
}
namespace detail
{
template <class T>
[[nodiscard]] __forceinline T UnpackVariable(const Variable& a_var)
{
return BSScript::UnpackVariable<decay_t<T>>(a_var);
}
template <class T>
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<RE::BSScript::Object>`
// * 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<T>, "invalid parameter type");
if constexpr (detail::array<T> || detail::nullable<T>) {
return detail::ValidateParameter<typename std::remove_cv_t<T>::value_type>();
} else {
return true;
}
}
template <class T>
consteval bool ValidateReturn() noexcept
{
// Must be one of:
// * `void`
// * A valid parameter type
if constexpr (std::same_as<T, void>) {
return true;
} else {
return ValidateParameter<T>();
}
}
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<F>& a_callback,
std::index_sequence<I...>)
{
const auto self = [&]() -> S {
// super::Call should guarantee the self parameter is not none
if constexpr (detail::static_tag<S>) {
return std::monostate{};
} else if constexpr (detail::object<std::remove_cvref_t<S>>) {
auto* const ptr = BSScript::UnpackVariable<std::remove_cvref_t<S>>(a_self);
assert(ptr != nullptr);
return *ptr;
} else if constexpr (detail::eobject<std::remove_cvref_t<S>>) {
auto* const ptr = BSScript::UnpackVariable<std::remove_cvref_t<S>>(a_self);
assert(ptr != nullptr);
return *ptr;
} else if constexpr (detail::cobject<std::remove_cv_t<S>>) {
return BSScript::UnpackVariable<std::remove_cv_t<S>>(a_self);
} else if constexpr (detail::vmobject<std::remove_cvref_t<S>>) {
const auto obj = get<Object>(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 = [&]<class T>(std::in_place_type_t<T>, std::size_t a_index) {
if (a_stackFrame.size > a_index) {
return UnpackVariable<detail::decay_t<T>>(
a_stack.GetStackFrameVariable(
pframe,
static_cast<std::uint32_t>(a_index),
page));
} else {
assert(false);
return T();
}
};
if constexpr (LONG) {
return a_callback(
reinterpret_cast<IVirtualMachine&>(a_vm), // TODO: static_cast
a_stackID,
self(),
args(std::in_place_type_t<Args>{}, I)...);
} else {
return a_callback(
self(),
args(std::in_place_type_t<Args>{}, 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<S>, "invalid self type");
static_assert(detail::ValidateReturn<R>());
static_assert(((detail::ValidateParameter<Args>(), ...), true));
template <class Fn>
NativeFunction(std::string_view a_object, std::string_view a_function, Fn a_func, bool a_isLatent) //
requires(detail::invocable_r<Fn, R, S, Args...> ||
detail::invocable_r<Fn, R, IVirtualMachine&, std::uint32_t, S, Args...>)
:
super(a_object, a_function, sizeof...(Args), detail::static_tag<S>, a_isLatent),
_stub(std::move(a_func))
{
assert(super::descTable.paramCount == sizeof...(Args));
std::size_t i = 0;
((super::descTable.entries[i++].second = GetTypeInfo<detail::decay_t<Args>>().value_or(nullptr)), ...);
super::retType = GetTypeInfo<detail::decay_t<R>>().value_or(nullptr);
}
// override (NF_util::NativeFunctionBase)
bool HasStub() const override { return static_cast<bool>(_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<LONG, S, Args...>(
a_self,
a_vm,
a_stackID,
a_stackFrame,
*stack,
_stub,
std::index_sequence_for<Args...>{});
};
if constexpr (!std::same_as<R, void>) {
PackVariable(a_retVal, invoke());
} else {
invoke();
}
return true;
}
private:
std::function<F> _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 <class F>
void IVirtualMachine::BindNativeMethod(
std::string_view a_object,
std::string_view a_function,
F a_func,
std::optional<bool> 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 <class... Args>
BSScrapArray<Variable> PackVariables(Args&&... a_args)
{
constexpr auto size = sizeof...(a_args);
auto args = std::make_tuple(std::forward<Args>(a_args)...);
BSScrapArray<Variable> result{ size };
[&]<std::size_t... p>(std::index_sequence<p...>) {
((BSScript::PackVariable(result.at(p), std::get<p>(args))), ...);
}(std::make_index_sequence<size>{});
return result;
}
}
template <class... Args>
bool IVirtualMachine::DispatchStaticCall(
const BSFixedString& a_objName,
const BSFixedString& a_funcName,
const BSTSmartPointer<IStackCallbackFunctor>& a_callback,
Args... a_args)
{
return DispatchStaticCall(
a_objName,
a_funcName,
[&](BSScrapArray<Variable>& a_out) {
a_out = detail::PackVariables(a_args...);
return true;
},
a_callback);
}
template <class... Args>
bool IVirtualMachine::DispatchMethodCall(
std::uint64_t a_objHandle,
const BSFixedString& a_objName,
const BSFixedString& a_funcName,
const BSTSmartPointer<IStackCallbackFunctor>& a_callback,
Args... a_args)
{
return DispatchMethodCall(
a_objHandle,
a_objName,
a_funcName,
[&](BSScrapArray<Variable>& a_out) {
a_out = detail::PackVariables(a_args...);
return true;
},
a_callback);
}
}