Skip to content

Commit

Permalink
feat: add NiSmartPointer, support TESForm smart pointers (#117)
Browse files Browse the repository at this point in the history
- the function `DecRefCount` has been moved to `TESForm` because it
calls `TESForm` specific vfuncs (0xB, 0xD, 0xE)
  • Loading branch information
powerof3 authored Sep 28, 2023
1 parent 8261471 commit fe6445a
Show file tree
Hide file tree
Showing 4 changed files with 205 additions and 0 deletions.
179 changes: 179 additions & 0 deletions CommonLibSF/include/RE/N/NiSmartPointer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
#pragma once

namespace RE
{
template <class T>
class NiPointer
{
public:
using element_type = T;

constexpr NiPointer() noexcept = default;

constexpr NiPointer(std::nullptr_t) noexcept {}

template <class Y>
explicit NiPointer(Y* a_rhs) //
requires(std::convertible_to<Y*, element_type*>)
:
_ptr(static_cast<element_type*>(a_rhs))
{
TryAttach();
}

NiPointer(const NiPointer& a_rhs) :
_ptr(a_rhs._ptr)
{
TryAttach();
}

template <class Y>
NiPointer(const NiPointer<Y>& a_rhs) //
requires(std::convertible_to<Y*, element_type*>)
:
_ptr(static_cast<element_type*>(a_rhs._ptr))
{
TryAttach();
}

NiPointer(NiPointer&& a_rhs) noexcept :
_ptr(std::exchange(a_rhs._ptr, nullptr))
{}

template <class Y>
NiPointer(NiPointer<Y>&& a_rhs) noexcept //
requires(std::convertible_to<Y*, element_type*>)
:
_ptr(static_cast<element_type*>(std::exchange(a_rhs._ptr, nullptr)))
{}

~NiPointer() noexcept { reset(); }

NiPointer& operator=(const NiPointer& a_rhs)
{
if (this != std::addressof(a_rhs)) {
TryDetach();
_ptr = a_rhs._ptr;
TryAttach();
}
return *this;
}

template <class Y>
NiPointer& operator=(const NiPointer<Y>& a_rhs) //
requires(std::convertible_to<Y*, element_type*>)
{
TryDetach();
_ptr = static_cast<element_type*>(a_rhs._ptr);
TryAttach();
return *this;
}

NiPointer& operator=(NiPointer&& a_rhs) noexcept
{
if (this != std::addressof(a_rhs)) {
TryDetach();
_ptr = std::exchange(a_rhs._ptr, nullptr);
}
return *this;
}

template <class Y>
NiPointer& operator=(NiPointer<Y>&& a_rhs) noexcept //
requires(std::convertible_to<Y*, element_type*>)
{
TryDetach();
_ptr = std::exchange(a_rhs._ptr, nullptr);
return *this;
}

void reset() { TryDetach(); }

template <class Y>
void reset(Y* a_ptr) //
requires(std::convertible_to<Y*, element_type*>)
{
if (_ptr != a_ptr) {
TryDetach();
_ptr = static_cast<element_type*>(a_ptr);
TryAttach();
}
}

[[nodiscard]] constexpr element_type* get() const noexcept { return _ptr; }

[[nodiscard]] explicit constexpr operator bool() const noexcept { return _ptr != nullptr; }

[[nodiscard]] constexpr element_type& operator*() const noexcept
{
assert(static_cast<bool>(*this));
return *_ptr;
}

[[nodiscard]] constexpr element_type* operator->() const noexcept
{
assert(static_cast<bool>(*this));
return _ptr;
}

protected:
template <class>
friend class NiPointer;

void TryAttach()
{
if (_ptr) {
_ptr->IncRefCount();
}
}

void TryDetach()
{
if (_ptr) {
_ptr->DecRefCount();
_ptr = nullptr;
}
}

// members
element_type* _ptr{ nullptr }; // 0
};
static_assert(sizeof(NiPointer<void*>) == 0x8);

template <class T, class... Args>
[[nodiscard]] NiPointer<T> make_nismart(Args&&... a_args)
{
return NiPointer<T>{ new T(std::forward<Args>(a_args)...) };
}

template <class T, class U>
[[nodiscard]] constexpr bool operator==(const NiPointer<T>& a_lhs, const NiPointer<U>& a_rhs) noexcept
{
return a_lhs.get() == a_rhs.get();
}

template <class T, class U>
[[nodiscard]] constexpr std::strong_ordering operator<=>(const NiPointer<T>& a_lhs, const NiPointer<U>& a_rhs) noexcept
{
return std::compare_three_way{}(a_lhs.get(), a_rhs.get());
}

template <class T>
[[nodiscard]] constexpr bool operator==(const NiPointer<T>& a_lhs, std::nullptr_t) noexcept
{
return !a_lhs;
}

template <class T>
[[nodiscard]] constexpr std::strong_ordering operator<=>(const NiPointer<T>& a_lhs, std::nullptr_t) noexcept
{
return std::compare_three_way{}(a_lhs.get(), static_cast<typename NiPointer<T>::element_type*>(nullptr));
}

template <class T>
NiPointer(T*) -> NiPointer<T>;
}

#define NiSmartPointer(className) \
class className; \
using className##Ptr = RE::NiPointer<className>
7 changes: 7 additions & 0 deletions CommonLibSF/include/RE/T/TESForm.h
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,13 @@ namespace RE
virtual void Unk_60(); // 60
virtual void Unk_61(); // 61

std::uint64_t DecRefCount() const
{
using func_t = decltype(&TESForm::DecRefCount);
REL::Relocation<func_t> func{ REL::ID(35164) };
return func(this);
}

[[nodiscard]] static TESForm* LookupByID(std::uint32_t a_formID)
{
using func_t = decltype(&TESForm::LookupByID);
Expand Down
8 changes: 8 additions & 0 deletions CommonLibSF/include/RE/T/TESFormRefCount.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ namespace RE
public:
SF_RTTI(TESFormRefCount);

enum
{
kRefCountMask = 0x3FFFFF,
};

std::uint64_t IncRefCount() const;
[[nodiscard]] std::uint64_t QRefCount() const;

// members
volatile mutable std::uint64_t refCount; // 08
};
Expand Down
11 changes: 11 additions & 0 deletions CommonLibSF/src/RE/T/TESFormRefCount.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,15 @@
#include "RE/T/TESFormRefCount.h"

namespace RE
{
std::uint64_t TESFormRefCount::IncRefCount() const
{
stl::atomic_ref myRefCount{ refCount };
return ++myRefCount;
}

std::uint64_t TESFormRefCount::QRefCount() const
{
return refCount & kRefCountMask;
}
}

0 comments on commit fe6445a

Please sign in to comment.