Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package hyprutils for openSUSE:Factory 
checked in at 2026-07-20 09:59:58
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/hyprutils (Old)
 and      /work/SRC/openSUSE:Factory/.hyprutils.new.24530 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "hyprutils"

Mon Jul 20 09:59:58 2026 rev:13 rq:1366675 version:0.14.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/hyprutils/hyprutils.changes      2026-05-10 
16:48:17.816712062 +0200
+++ /work/SRC/openSUSE:Factory/.hyprutils.new.24530/hyprutils.changes   
2026-07-20 10:02:01.265543619 +0200
@@ -1,0 +2,13 @@
+Fri Jul 17 23:12:02 UTC 2026 - Avindra Goolcharan <[email protected]>
+
+- Update to version 0.14.0:
+  * animations: advance springs by elapsed time
+  * animation: expose spring
+  * animation/variable: set m_Begun for setValueAndWarp
+  * memory: add move assignment to CWeakPointer
+  * memory: fix implicit comparison of unrelated types
+  * memory: fix virtual inheritance casts
+  * memory: add operators, fix a nullptr deref, and follow #115 with uniqueptr
+  * memory: assert on locking unique pointer
+
+-------------------------------------------------------------------

Old:
----
  hyprutils-0.13.1.tar.xz

New:
----
  hyprutils-0.14.0.tar.xz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ hyprutils.spec ++++++
--- /var/tmp/diff_new_pack.xU7SS1/_old  2026-07-20 10:02:02.913599081 +0200
+++ /var/tmp/diff_new_pack.xU7SS1/_new  2026-07-20 10:02:02.929599621 +0200
@@ -17,10 +17,10 @@
 #
 
 
-%define sover 12
+%define sover 13
 
 Name:           hyprutils
-Version:        0.13.1
+Version:        0.14.0
 Release:        0
 Summary:        Utilities used across the Hypr* ecosystem
 License:        BSD-3-Clause

++++++ _service ++++++
--- /var/tmp/diff_new_pack.xU7SS1/_old  2026-07-20 10:02:03.297612005 +0200
+++ /var/tmp/diff_new_pack.xU7SS1/_new  2026-07-20 10:02:03.353613890 +0200
@@ -3,7 +3,7 @@
        <service name="obs_scm" mode="manual">
                <param 
name="url">https://github.com/hyprwm/hyprutils.git</param>
                <param name="scm">git</param>
-               <param name="revision">v0.13.1</param>
+               <param name="revision">v0.14.0</param>
                <param name="versionformat">@PARENT_TAG@</param>
                <param name="versionrewrite-pattern">v(.*)</param>
        </service>

++++++ hyprutils-0.13.1.tar.xz -> hyprutils-0.14.0.tar.xz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hyprutils-0.13.1/CMakeLists.txt 
new/hyprutils-0.14.0/CMakeLists.txt
--- old/hyprutils-0.13.1/CMakeLists.txt 2026-05-08 12:06:10.000000000 +0200
+++ new/hyprutils-0.14.0/CMakeLists.txt 2026-07-17 23:23:33.000000000 +0200
@@ -52,7 +52,7 @@
   PUBLIC "./include"
   PRIVATE "./src")
 set_target_properties(hyprutils PROPERTIES VERSION ${hyprutils_VERSION}
-                                           SOVERSION 12)
+                                           SOVERSION 13)
 target_link_libraries(hyprutils PkgConfig::deps)
 
 if(BUILD_TESTING)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hyprutils-0.13.1/VERSION new/hyprutils-0.14.0/VERSION
--- old/hyprutils-0.13.1/VERSION        2026-05-08 12:06:10.000000000 +0200
+++ new/hyprutils-0.14.0/VERSION        2026-07-17 23:23:33.000000000 +0200
@@ -1 +1 @@
-0.13.1
+0.14.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/hyprutils-0.13.1/include/hyprutils/animation/AnimatedVariable.hpp 
new/hyprutils-0.14.0/include/hyprutils/animation/AnimatedVariable.hpp
--- old/hyprutils-0.13.1/include/hyprutils/animation/AnimatedVariable.hpp       
2026-05-08 12:06:10.000000000 +0200
+++ new/hyprutils-0.14.0/include/hyprutils/animation/AnimatedVariable.hpp       
2026-07-17 23:23:33.000000000 +0200
@@ -264,6 +264,7 @@
             /* Sets the actual value and goal*/
             void setValueAndWarp(const VarType& v) {
                 m_Goal             = v;
+                m_Begun            = v;
                 m_bIsBeingAnimated = true;
 
                 warp();
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/hyprutils-0.13.1/include/hyprutils/animation/Spring.hpp 
new/hyprutils-0.14.0/include/hyprutils/animation/Spring.hpp
--- old/hyprutils-0.13.1/include/hyprutils/animation/Spring.hpp 1970-01-01 
01:00:00.000000000 +0100
+++ new/hyprutils-0.14.0/include/hyprutils/animation/Spring.hpp 2026-07-17 
23:23:33.000000000 +0200
@@ -0,0 +1,9 @@
+#pragma once
+
+#include <hyprutils/animation/AnimationManager.hpp>
+
+#include <chrono>
+
+namespace Hyprutils::Animation {
+    void advanceSpring(float& value, float& velocity, const SSpringCurve& 
spring, std::chrono::duration<float> elapsed);
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hyprutils-0.13.1/include/hyprutils/memory/Atomic.hpp 
new/hyprutils-0.14.0/include/hyprutils/memory/Atomic.hpp
--- old/hyprutils-0.13.1/include/hyprutils/memory/Atomic.hpp    2026-05-08 
12:06:10.000000000 +0200
+++ new/hyprutils-0.14.0/include/hyprutils/memory/Atomic.hpp    2026-07-17 
23:23:33.000000000 +0200
@@ -54,7 +54,7 @@
         using validHierarchy = 
std::enable_if_t<std::is_assignable_v<CAtomicSharedPointer<T>&, X>, 
CAtomicSharedPointer&>;
 
       public:
-        explicit CAtomicSharedPointer(T* object) noexcept : m_ptr(new 
Atomic_::impl(sc<void*>(object), _delete), sc<void*>(object)) {
+        explicit CAtomicSharedPointer(T* object) noexcept : m_ptr(new 
Atomic_::impl(Impl_::dataPointer(object), _delete), Impl_::dataPointer(object)) 
{
             ;
         }
 
@@ -203,8 +203,8 @@
             return m_ptr.get();
         }
 
-        operator bool() const {
-            return m_ptr;
+        explicit operator bool() const {
+            return static_cast<bool>(m_ptr);
         }
 
         bool operator==(const CAtomicSharedPointer& rhs) const {
@@ -301,6 +301,9 @@
         validHierarchy<const CAtomicWeakPointer<U>&> operator=(const 
CAtomicWeakPointer<U>& rhs) {
             reset();
 
+            if (!rhs.m_ptr.impl_)
+                return *this;
+
             auto lg = rhs.implLockGuard();
             m_ptr   = rhs.m_ptr;
             return *this;
@@ -312,6 +315,9 @@
 
             reset();
 
+            if (!rhs.m_ptr.impl_)
+                return *this;
+
             auto lg = rhs.implLockGuard();
             m_ptr   = rhs.m_ptr;
             return *this;
@@ -366,8 +372,8 @@
             return m_ptr.get();
         }
 
-        operator bool() const {
-            return m_ptr;
+        explicit operator bool() const {
+            return static_cast<bool>(m_ptr);
         }
 
         bool operator==(const CAtomicWeakPointer& rhs) const {
@@ -426,26 +432,26 @@
 
     template <typename T, typename U>
     CAtomicSharedPointer<T> reinterpretPointerCast(const 
CAtomicSharedPointer<U>& ref) {
-        return CAtomicSharedPointer<T>(ref.impl(), ref.m_data);
+        return CAtomicSharedPointer<T>(ref.impl(), 
Impl_::dataPointer(ref.get()));
     }
 
     template <typename T, typename U>
     CAtomicSharedPointer<T> dynamicPointerCast(const CAtomicSharedPointer<U>& 
ref) {
         if (!ref)
             return nullptr;
-        T* newPtr = dynamic_cast<T*>(sc<U*>(ref.impl()->getData()));
+        T* newPtr = dynamic_cast<T*>(ref.get());
         if (!newPtr)
             return nullptr;
-        return CAtomicSharedPointer<T>(ref.impl(), newPtr);
+        return CAtomicSharedPointer<T>(ref.impl(), Impl_::dataPointer(newPtr));
     }
 
     template <typename T, typename U>
     CAtomicWeakPointer<T> dynamicPointerCast(const CAtomicWeakPointer<U>& ref) 
{
         if (!ref)
             return nullptr;
-        T* newPtr = dynamic_cast<T*>(sc<U*>(ref.impl()->getData()));
+        T* newPtr = dynamic_cast<T*>(ref.get());
         if (!newPtr)
             return nullptr;
-        return CAtomicWeakPointer<T>(ref.impl(), newPtr);
+        return CAtomicWeakPointer<T>(ref.impl(), Impl_::dataPointer(newPtr));
     }
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/hyprutils-0.13.1/include/hyprutils/memory/ImplBase.hpp 
new/hyprutils-0.14.0/include/hyprutils/memory/ImplBase.hpp
--- old/hyprutils-0.13.1/include/hyprutils/memory/ImplBase.hpp  2026-05-08 
12:06:10.000000000 +0200
+++ new/hyprutils-0.14.0/include/hyprutils/memory/ImplBase.hpp  2026-07-17 
23:23:33.000000000 +0200
@@ -6,6 +6,11 @@
 namespace Hyprutils {
     namespace Memory {
         namespace Impl_ {
+            template <typename T>
+            constexpr void* dataPointer(T* ptr) noexcept {
+                return const_cast<void*>(static_cast<const void*>(ptr));
+            }
+
             class impl_base {
               public:
                 using DeleteFn = void (*)(void*);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/hyprutils-0.13.1/include/hyprutils/memory/SharedPtr.hpp 
new/hyprutils-0.14.0/include/hyprutils/memory/SharedPtr.hpp
--- old/hyprutils-0.13.1/include/hyprutils/memory/SharedPtr.hpp 2026-05-08 
12:06:10.000000000 +0200
+++ new/hyprutils-0.14.0/include/hyprutils/memory/SharedPtr.hpp 2026-07-17 
23:23:33.000000000 +0200
@@ -28,13 +28,13 @@
 
             /* creates a new shared pointer managing a resource
                avoid calling. Could duplicate ownership. Prefer makeShared */
-            explicit CSharedPointer(T* object) noexcept : impl_(new 
Impl_::impl_base(sc<void*>(object), _delete)), m_data(sc<void*>(object)) {
+            explicit CSharedPointer(T* object) noexcept : impl_(new 
Impl_::impl_base(Impl_::dataPointer(object), _delete)), 
m_data(Impl_::dataPointer(object)) {
                 increment();
             }
 
             /* creates a shared pointer from a reference */
             template <typename U, typename = isConstructible<U>>
-            CSharedPointer(const CSharedPointer<U>& ref) noexcept : 
impl_(ref.impl_), m_data(ref.m_data) {
+            CSharedPointer(const CSharedPointer<U>& ref) noexcept : 
impl_(ref.impl_), m_data(Impl_::dataPointer(sc<T*>(ref.get()))) {
                 increment();
             }
 
@@ -44,8 +44,10 @@
 
             template <typename U, typename = isConstructible<U>>
             CSharedPointer(CSharedPointer<U>&& ref) noexcept {
-                std::swap(impl_, ref.impl_);
-                std::swap(m_data, ref.m_data);
+                impl_      = ref.impl_;
+                m_data     = Impl_::dataPointer(sc<T*>(ref.get()));
+                ref.impl_  = nullptr;
+                ref.m_data = nullptr;
             }
 
             CSharedPointer(CSharedPointer&& ref) noexcept {
@@ -72,12 +74,14 @@
 
             template <typename U>
             validHierarchy<const CSharedPointer<U>&> operator=(const 
CSharedPointer<U>& rhs) {
-                if (impl_ == rhs.impl_)
+                if (impl_ == rhs.impl_) {
+                    m_data = Impl_::dataPointer(sc<T*>(rhs.get()));
                     return *this;
+                }
 
                 decrement(impl_);
                 impl_  = rhs.impl_;
-                m_data = rhs.m_data;
+                m_data = Impl_::dataPointer(sc<T*>(rhs.get()));
                 increment();
                 return *this;
             }
@@ -95,8 +99,11 @@
 
             template <typename U>
             validHierarchy<const CSharedPointer<U>&> 
operator=(CSharedPointer<U>&& rhs) {
+                auto* rhsData = rhs.get();
+
                 std::swap(impl_, rhs.impl_);
                 std::swap(m_data, rhs.m_data);
+                m_data = Impl_::dataPointer(sc<T*>(rhsData));
                 return *this;
             }
 
@@ -106,7 +113,7 @@
                 return *this;
             }
 
-            operator bool() const {
+            explicit operator bool() const {
                 return impl_ && impl_->dataNonNull();
             }
 
@@ -116,6 +123,14 @@
                 return impl_ == rhs.impl_;
             }
 
+            bool operator==(std::nullptr_t) const {
+                return !(impl_ && impl_->dataNonNull());
+            }
+
+            bool operator!=(std::nullptr_t) const {
+                return impl_ && impl_->dataNonNull();
+            }
+
             bool operator()(const CSharedPointer& lhs, const CSharedPointer& 
rhs) const {
                 return rc<uintptr_t>(lhs.impl_) < rc<uintptr_t>(rhs.impl_);
             }
@@ -206,10 +221,10 @@
         CSharedPointer<T> dynamicPointerCast(const CSharedPointer<U>& ref) {
             if (!ref)
                 return nullptr;
-            T* newPtr = dynamic_cast<T*>(sc<U*>(ref.impl_->getData()));
+            T* newPtr = dynamic_cast<T*>(ref.get());
             if (!newPtr)
                 return nullptr;
-            return CSharedPointer<T>(ref.impl_, newPtr);
+            return CSharedPointer<T>(ref.impl_, Impl_::dataPointer(newPtr));
         }
     }
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/hyprutils-0.13.1/include/hyprutils/memory/UniquePtr.hpp 
new/hyprutils-0.14.0/include/hyprutils/memory/UniquePtr.hpp
--- old/hyprutils-0.13.1/include/hyprutils/memory/UniquePtr.hpp 2026-05-08 
12:06:10.000000000 +0200
+++ new/hyprutils-0.14.0/include/hyprutils/memory/UniquePtr.hpp 2026-07-17 
23:23:33.000000000 +0200
@@ -22,7 +22,8 @@
 
             /* creates a new unique pointer managing a resource
                avoid calling. Could duplicate ownership. Prefer makeUnique */
-            explicit CUniquePointer(T* object) noexcept : impl_(new 
Impl_::impl_base(sc<void*>(object), [](void* p) { 
std::default_delete<T>{}(sc<T*>(p)); }, false)) {
+            explicit CUniquePointer(T* object) noexcept :
+                impl_(new Impl_::impl_base(Impl_::dataPointer(object), 
[](void* p) { std::default_delete<T>{}(sc<T*>(p)); }, false)), 
m_data(Impl_::dataPointer(object)) {
                 increment();
             }
 
@@ -33,11 +34,15 @@
 
             template <typename U, typename = isConstructible<U>>
             CUniquePointer(CUniquePointer<U>&& ref) noexcept {
-                std::swap(impl_, ref.impl_);
+                impl_      = ref.impl_;
+                m_data     = Impl_::dataPointer(sc<T*>(ref.get()));
+                ref.impl_  = nullptr;
+                ref.m_data = nullptr;
             }
 
             CUniquePointer(CUniquePointer&& ref) noexcept {
                 std::swap(impl_, ref.impl_);
+                std::swap(m_data, ref.m_data);
             }
 
             /* creates an empty unique pointer with no implementation */
@@ -58,23 +63,44 @@
 
             template <typename U>
             validHierarchy<const CUniquePointer<U>&> 
operator=(CUniquePointer<U>&& rhs) {
+                auto* rhsData = rhs.get();
+
                 std::swap(impl_, rhs.impl_);
+                std::swap(m_data, rhs.m_data);
+                m_data = Impl_::dataPointer(sc<T*>(rhsData));
                 return *this;
             }
 
             CUniquePointer& operator=(CUniquePointer&& rhs) noexcept {
                 std::swap(impl_, rhs.impl_);
+                std::swap(m_data, rhs.m_data);
                 return *this;
             }
 
-            operator bool() const {
+            explicit operator bool() const {
                 return impl_;
             }
 
+            bool operator==(const CUniquePointer& rhs) const {
+                return impl_ == rhs.impl_;
+            }
+
+            bool operator==(std::nullptr_t) const {
+                return !impl_;
+            }
+
+            bool operator!=(std::nullptr_t) const {
+                return impl_ != nullptr;
+            }
+
             bool operator()(const CUniquePointer& lhs, const CUniquePointer& 
rhs) const {
                 return rc<uintptr_t>(lhs.impl_) < rc<uintptr_t>(rhs.impl_);
             }
 
+            bool operator<(const CUniquePointer& rhs) const {
+                return rc<uintptr_t>(impl_) < rc<uintptr_t>(rhs.impl_);
+            }
+
             T* operator->() const {
                 return get();
             }
@@ -85,15 +111,19 @@
 
             void reset() {
                 decrement();
-                impl_ = nullptr;
+                impl_  = nullptr;
+                m_data = nullptr;
             }
 
             T* get() const {
-                return impl_ ? sc<T*>(impl_->getData()) : nullptr;
+                return impl_ && impl_->dataNonNull() ? sc<T*>(m_data) : 
nullptr;
             }
 
             Impl_::impl_base* impl_ = nullptr;
 
+            // Never use directly: raw data ptr, could be UAF
+            void* m_data = nullptr;
+
           private:
             /* 
                 no-op if there is no impl_
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/hyprutils-0.13.1/include/hyprutils/memory/WeakPtr.hpp 
new/hyprutils-0.14.0/include/hyprutils/memory/WeakPtr.hpp
--- old/hyprutils-0.13.1/include/hyprutils/memory/WeakPtr.hpp   2026-05-08 
12:06:10.000000000 +0200
+++ new/hyprutils-0.14.0/include/hyprutils/memory/WeakPtr.hpp   2026-07-17 
23:23:33.000000000 +0200
@@ -3,6 +3,7 @@
 #include "./SharedPtr.hpp"
 #include "./UniquePtr.hpp"
 #include "./Casts.hpp"
+#include "../misc/HyprAssert.hpp"
 
 /*
     This is a Hyprland implementation of std::weak_ptr.
@@ -27,7 +28,7 @@
                     return;
 
                 impl_  = ref.impl_;
-                m_data = ref.m_data;
+                m_data = Impl_::dataPointer(sc<T*>(ref.get()));
                 incrementWeak();
             }
 
@@ -38,7 +39,7 @@
                     return;
 
                 impl_  = ref.impl_;
-                m_data = ref.impl_->getData();
+                m_data = Impl_::dataPointer(sc<T*>(ref.get()));
                 incrementWeak();
             }
 
@@ -49,7 +50,7 @@
                     return;
 
                 impl_  = ref.impl_;
-                m_data = ref.m_data;
+                m_data = Impl_::dataPointer(sc<T*>(ref.get()));
                 incrementWeak();
             }
 
@@ -72,8 +73,10 @@
 
             template <typename U, typename = isConstructible<U>>
             CWeakPointer(CWeakPointer<U>&& ref) noexcept {
-                std::swap(impl_, ref.impl_);
-                std::swap(m_data, ref.m_data);
+                impl_      = ref.impl_;
+                m_data     = Impl_::dataPointer(sc<T*>(ref.get()));
+                ref.impl_  = nullptr;
+                ref.m_data = nullptr;
             }
 
             CWeakPointer(CWeakPointer&& ref) noexcept {
@@ -84,12 +87,14 @@
             /* create a weak ptr from another weak ptr with assignment */
             template <typename U>
             validHierarchy<const CWeakPointer<U>&> operator=(const 
CWeakPointer<U>& rhs) {
-                if (impl_ == rhs.impl_)
+                if (impl_ == rhs.impl_) {
+                    m_data = Impl_::dataPointer(sc<T*>(rhs.get()));
                     return *this;
+                }
 
                 decrementWeak();
                 impl_  = rhs.impl_;
-                m_data = rhs.m_data;
+                m_data = Impl_::dataPointer(sc<T*>(rhs.get()));
                 incrementWeak();
                 return *this;
             }
@@ -105,15 +110,37 @@
                 return *this;
             }
 
+            template <typename U>
+            validHierarchy<const CWeakPointer<U>&> operator=(CWeakPointer<U>&& 
rhs) noexcept {
+                auto* rhsData = rhs.get();
+
+                std::swap(impl_, rhs.impl_);
+                std::swap(m_data, rhs.m_data);
+                m_data = Impl_::dataPointer(sc<T*>(rhsData));
+                return *this;
+            }
+
+            CWeakPointer& operator=(CWeakPointer&& rhs) noexcept {
+                if (this == &rhs)
+                    return *this;
+
+                std::swap(impl_, rhs.impl_);
+                std::swap(m_data, rhs.m_data);
+
+                return *this;
+            }
+
             /* create a weak ptr from a shared ptr with assignment */
             template <typename U>
             validHierarchy<const CWeakPointer<U>&> operator=(const 
CSharedPointer<U>& rhs) {
-                if (rc<uintptr_t>(impl_) == rc<uintptr_t>(rhs.impl_))
+                if (rc<uintptr_t>(impl_) == rc<uintptr_t>(rhs.impl_)) {
+                    m_data = Impl_::dataPointer(sc<T*>(rhs.get()));
                     return *this;
+                }
 
                 decrementWeak();
                 impl_  = rhs.impl_;
-                m_data = rhs.m_data;
+                m_data = Impl_::dataPointer(sc<T*>(rhs.get()));
                 incrementWeak();
                 return *this;
             }
@@ -147,14 +174,20 @@
             }
 
             CSharedPointer<T> lock() const {
-                if (!impl_ || !impl_->dataNonNull() || impl_->destroying() || 
!impl_->lockable())
+                if (!impl_ || !impl_->dataNonNull() || impl_->destroying())
+                    return {};
+
+                // a weak ptr over a CUniquePointer can never be locked: a 
shared ptr would
+                HYPRUTILS_ASSERT_MSG(impl_->lockable(), "tried to lock a 
CWeakPointer over a CUniquePointer");
+
+                if (!impl_->lockable())
                     return {};
 
                 return CSharedPointer<T>(impl_, m_data);
             }
 
             /* this returns valid() */
-            operator bool() const {
+            explicit operator bool() const {
                 return valid();
             }
 
@@ -233,10 +266,10 @@
         CWeakPointer<T> dynamicPointerCast(const CWeakPointer<U>& ref) {
             if (!ref)
                 return nullptr;
-            T* newPtr = dynamic_cast<T*>(sc<U*>(ref.impl_->getData()));
+            T* newPtr = dynamic_cast<T*>(ref.get());
             if (!newPtr)
                 return nullptr;
-            return CWeakPointer<T>(ref.impl_, newPtr);
+            return CWeakPointer<T>(ref.impl_, Impl_::dataPointer(newPtr));
         }
     }
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/hyprutils-0.13.1/include/hyprutils/misc/HyprAssert.hpp 
new/hyprutils-0.14.0/include/hyprutils/misc/HyprAssert.hpp
--- old/hyprutils-0.13.1/include/hyprutils/misc/HyprAssert.hpp  1970-01-01 
01:00:00.000000000 +0100
+++ new/hyprutils-0.14.0/include/hyprutils/misc/HyprAssert.hpp  2026-07-17 
23:23:33.000000000 +0200
@@ -0,0 +1,20 @@
+#pragma once
+
+#include <exception>
+#include <iostream>
+
+#define HYPRUTILS_ASSERT(cond)                                                 
                                                                                
                    \
+    do {                                                                       
                                                                                
                    \
+        if (!(cond)) {                                                         
                                                                                
                    \
+            std::cerr << "Assertion failed: " << #cond << " @ " << __FILE__ << 
":" << __LINE__ << "\n";                                                        
                    \
+            std::terminate();                                                  
                                                                                
                    \
+        }                                                                      
                                                                                
                    \
+    } while (0)
+
+#define HYPRUTILS_ASSERT_MSG(cond, msg)                                        
                                                                                
                    \
+    do {                                                                       
                                                                                
                    \
+        if (!(cond)) {                                                         
                                                                                
                    \
+            std::cerr << "Assertion failed: " << #cond << " (" << msg << ") @ 
" << __FILE__ << ":" << __LINE__ << "\n";                                       
                     \
+            std::terminate();                                                  
                                                                                
                    \
+        }                                                                      
                                                                                
                    \
+    } while (0)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hyprutils-0.13.1/src/animation/AnimatedVariable.cpp 
new/hyprutils-0.14.0/src/animation/AnimatedVariable.cpp
--- old/hyprutils-0.13.1/src/animation/AnimatedVariable.cpp     2026-05-08 
12:06:10.000000000 +0200
+++ new/hyprutils-0.14.0/src/animation/AnimatedVariable.cpp     2026-07-17 
23:23:33.000000000 +0200
@@ -1,6 +1,7 @@
 #include <hyprutils/animation/AnimatedVariable.hpp>
 #include <hyprutils/animation/AnimationManager.hpp>
 #include <hyprutils/memory/WeakPtr.hpp>
+#include <hyprutils/animation/Spring.hpp>
 
 #include <algorithm>
 #include <cmath>
@@ -122,29 +123,10 @@
         return {.value = 1.F, .finished = true};
 
     const auto NOW = std::chrono::steady_clock::now();
-    float      dt  = std::chrono::duration<float>(NOW - 
springLastStep).count();
+    const auto DT  = NOW - springLastStep;
     springLastStep = NOW;
 
-    constexpr float MINDELTA = 1.F / 240.F;
-    if (dt <= 0.F)
-        dt = MINDELTA;
-    else
-        dt = std::clamp(dt, MINDELTA, 0.05F);
-
-    if (dt > 0.F) {
-        constexpr const float FIXEDSTEP = 1.F / 240.F;
-        const int             SUBSTEPS  = 
std::clamp(static_cast<int>(std::ceil(dt / FIXEDSTEP)), 1, 16);
-        const float           STEPTIME  = dt / SUBSTEPS;
-        const float           MASS      = std::max(SPRING->mass, 0.0001f);
-
-        for (int i = 0; i < SUBSTEPS; ++i) {
-            const float displacement = m_fSpringValue - 1.f;
-            const float acceleration = ((-SPRING->stiffness * displacement) - 
(SPRING->damping * m_fSpringVelocity)) / MASS;
-
-            m_fSpringVelocity += acceleration * STEPTIME;
-            m_fSpringValue += m_fSpringVelocity * STEPTIME;
-        }
-    }
+    advanceSpring(m_fSpringValue, m_fSpringVelocity, *SPRING, DT);
 
     const bool FINISHED = std::abs(1.F - m_fSpringValue) <= 
SPRING->valueEpsilon && std::abs(m_fSpringVelocity) <= SPRING->velocityEpsilon;
     if (FINISHED) {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hyprutils-0.13.1/src/animation/Spring.cpp 
new/hyprutils-0.14.0/src/animation/Spring.cpp
--- old/hyprutils-0.13.1/src/animation/Spring.cpp       1970-01-01 
01:00:00.000000000 +0100
+++ new/hyprutils-0.14.0/src/animation/Spring.cpp       2026-07-17 
23:23:33.000000000 +0200
@@ -0,0 +1,56 @@
+#include <hyprutils/animation/Spring.hpp>
+
+#include <algorithm>
+#include <cmath>
+
+using namespace Hyprutils;
+using namespace Hyprutils::Animation;
+
+void Animation::advanceSpring(float& value, float& velocity, const 
SSpringCurve& spring, std::chrono::duration<float> elapsed) {
+    const float DT = std::max(elapsed.count(), 0.F);
+    if (DT <= 0.F)
+        return;
+
+    const float MASS      = std::max(spring.mass, 0.0001F);
+    const float STIFFNESS = std::max(spring.stiffness, 0.0001F);
+    const float DAMPING   = std::max(spring.damping, 0.F);
+
+    const float DISPLACEMENT = value - 1.F;
+    const float OMEGA0       = std::sqrt(STIFFNESS / MASS);
+    const float GAMMA        = DAMPING / (2.F * MASS);
+
+    if (GAMMA < OMEGA0) {
+        const float OMEGAD = std::sqrt((OMEGA0 * OMEGA0) - (GAMMA * GAMMA));
+        const float EXP    = std::exp(-GAMMA * DT);
+        const float SIN    = std::sin(OMEGAD * DT);
+        const float COS    = std::cos(OMEGAD * DT);
+
+        const float NEW_DISPLACEMENT = EXP * ((DISPLACEMENT * COS) + 
(((velocity + (GAMMA * DISPLACEMENT)) / OMEGAD) * SIN));
+        const float NEW_VELOCITY     = EXP * ((velocity * COS) - (((GAMMA * 
velocity) + (OMEGA0 * OMEGA0 * DISPLACEMENT)) / OMEGAD) * SIN);
+
+        value    = 1.F + NEW_DISPLACEMENT;
+        velocity = NEW_VELOCITY;
+        return;
+    }
+
+    const float CRITICAL_EPSILON = std::max(OMEGA0, 1.F) * 0.0001F;
+    if (std::abs(GAMMA - OMEGA0) <= CRITICAL_EPSILON) {
+        const float EXP = std::exp(-GAMMA * DT);
+        const float B   = velocity + (GAMMA * DISPLACEMENT);
+
+        value    = 1.F + (EXP * (DISPLACEMENT + (B * DT)));
+        velocity = EXP * (velocity - (GAMMA * B * DT));
+        return;
+    }
+
+    const float ROOT = std::sqrt((GAMMA * GAMMA) - (OMEGA0 * OMEGA0));
+    const float R1   = -GAMMA + ROOT;
+    const float R2   = -GAMMA - ROOT;
+    const float A    = (velocity - (R2 * DISPLACEMENT)) / (R1 - R2);
+    const float B    = DISPLACEMENT - A;
+    const float E1   = std::exp(R1 * DT);
+    const float E2   = std::exp(R2 * DT);
+
+    value    = 1.F + (A * E1) + (B * E2);
+    velocity = (A * R1 * E1) + (B * R2 * E2);
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hyprutils-0.13.1/tests/animation/Animation.cpp 
new/hyprutils-0.14.0/tests/animation/Animation.cpp
--- old/hyprutils-0.13.1/tests/animation/Animation.cpp  2026-05-08 
12:06:10.000000000 +0200
+++ new/hyprutils-0.14.0/tests/animation/Animation.cpp  2026-07-17 
23:23:33.000000000 +0200
@@ -6,6 +6,9 @@
 #include <hyprutils/animation/AnimatedVariable.hpp>
 #include <hyprutils/memory/WeakPtr.hpp>
 #include <hyprutils/memory/UniquePtr.hpp>
+#include <hyprutils/animation/Spring.hpp>
+
+#include <chrono>
 
 #define SP CSharedPointer
 #define WP CWeakPointer
@@ -391,49 +394,72 @@
     EXPECT_EQ(pAnimationManager.get(), nullptr);
 }
 
-TEST(Animation, springRetargetPreservesVelocity) {
-    config();
-
-    pAnimationManager->addSpringWithName("momentum",
-                                         {
-                                             .stiffness       = 120.f,
-                                             .damping         = 8.f,
-                                             .mass            = 1.f,
-                                             .valueEpsilon    = 0.001f,
-                                             .velocityEpsilon = 0.001f,
-                                         });
-
-    animationTree.createNode("spring_global");
-    animationTree.createNode("spring_velocity", "spring_global");
-    animationTree.setConfigForNode("spring_global", 1, 1.f, "spring:momentum");
+TEST(Animation, springAdvanceUsesElapsedTime) {
+    const SSpringCurve SPRING = {
+        .stiffness       = 100.f,
+        .damping         = 20.f,
+        .mass            = 1.f,
+        .valueEpsilon    = 0.001f,
+        .velocityEpsilon = 0.001f,
+    };
+
+    float oneMsValue        = 0.F;
+    float oneMsVelocity     = 0.F;
+    float hundredMsValue    = 0.F;
+    float hundredMsVelocity = 0.F;
+    float negativeValue     = 0.F;
+    float negativeVelocity  = 0.F;
+
+    advanceSpring(oneMsValue, oneMsVelocity, SPRING, 
std::chrono::milliseconds(1));
+    advanceSpring(hundredMsValue, hundredMsVelocity, SPRING, 
std::chrono::milliseconds(120));
+    advanceSpring(negativeValue, negativeVelocity, SPRING, 
std::chrono::milliseconds(-1));
+
+    EXPECT_GT(oneMsValue, 0.F);
+    EXPECT_GT(hundredMsValue, oneMsValue);
+    EXPECT_EQ(negativeValue, 0.F);
+    EXPECT_EQ(negativeVelocity, 0.F);
+}
 
-    PANIMVAR<int> av;
-    pAnimationManager->createAnimation(0, av, "spring_velocity");
+TEST(Animation, springAdvancesAcrossLateTicks) {
+    const SSpringCurve SPRING = {
+        .stiffness       = 100.f,
+        .damping         = 20.f,
+        .mass            = 1.f,
+        .valueEpsilon    = 0.001f,
+        .velocityEpsilon = 0.001f,
+    };
+
+    float lateValue      = 0.F;
+    float lateVelocity   = 0.F;
+    float cappedValue    = 0.F;
+    float cappedVelocity = 0.F;
 
-    *av = 100;
+    advanceSpring(lateValue, lateVelocity, SPRING, 
std::chrono::milliseconds(120));
+    advanceSpring(cappedValue, cappedVelocity, SPRING, 
std::chrono::milliseconds(50));
 
-    int ticks = 0;
-    while (av->value() < 40 && ticks++ < 300) {
-        pAnimationManager->tick();
-    }
-
-    const auto RETARGETPOINT = av->value();
-    EXPECT_GT(RETARGETPOINT, 20);
-
-    PANIMVAR<int> fromRest;
-    pAnimationManager->createAnimation(RETARGETPOINT, fromRest, 
"spring_velocity");
+    EXPECT_GT(lateValue, cappedValue);
+    EXPECT_GT(lateValue, 0.25F);
+}
 
-    *av       = 0;
-    *fromRest = 0;
-    EXPECT_EQ(av->value(), RETARGETPOINT);
+TEST(Animation, springDoesNotAdvanceFasterThanElapsedTime) {
+    const SSpringCurve SPRING = {
+        .stiffness       = 38.f,
+        .damping         = 8.f,
+        .mass            = 2.4f,
+        .valueEpsilon    = 0.001f,
+        .velocityEpsilon = 0.001f,
+    };
+
+    float repeatedValue    = 0.F;
+    float repeatedVelocity = 0.F;
+    float singleValue      = 0.F;
+    float singleVelocity   = 0.F;
 
-    pAnimationManager->tick();
-    EXPECT_GT(av->value(), fromRest->value());
+    for (size_t i = 0; i < 132; ++i)
+        advanceSpring(repeatedValue, repeatedVelocity, SPRING, 
std::chrono::milliseconds(1));
 
-    while (pAnimationManager->shouldTickForNext() && ticks++ < 2000) {
-        pAnimationManager->tick();
-    }
+    advanceSpring(singleValue, singleVelocity, SPRING, 
std::chrono::milliseconds(132));
 
-    EXPECT_EQ(av->value(), 0);
-    EXPECT_EQ(fromRest->value(), 0);
+    EXPECT_NEAR(repeatedValue, singleValue, 0.0001F);
+    EXPECT_LT(repeatedValue, 0.5F);
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hyprutils-0.13.1/tests/memory/Memory.cpp 
new/hyprutils-0.14.0/tests/memory/Memory.cpp
--- old/hyprutils-0.13.1/tests/memory/Memory.cpp        2026-05-08 
12:06:10.000000000 +0200
+++ new/hyprutils-0.14.0/tests/memory/Memory.cpp        2026-07-17 
23:23:33.000000000 +0200
@@ -19,6 +19,20 @@
 #define NTHREADS   8
 #define ITERATIONS 10000
 
+namespace {
+    class Peepee {};
+    class Poopoo {};
+}
+
+template <typename A, typename B>
+concept EqualityComparable = requires(A a, B b) { a == b; };
+
+static_assert(!EqualityComparable<SP<Peepee>, SP<Poopoo>>);
+static_assert(!EqualityComparable<WP<Peepee>, WP<Poopoo>>);
+static_assert(!EqualityComparable<UP<Peepee>, UP<Poopoo>>);
+static_assert(!EqualityComparable<ASP<Peepee>, ASP<Poopoo>>);
+static_assert(!EqualityComparable<AWP<Peepee>, AWP<Poopoo>>);
+
 static void testAtomicImpl() {
     {
         // Using makeShared here could lead to invalid refcounts.
@@ -43,7 +57,7 @@
         // Actual count is not incremented in a thread-safe manner here, so we 
can't check it.
         // We just want to check that the concurent refcounting doesn't cause 
any memory corruption.
         shared.reset();
-        EXPECT_EQ(shared, false);
+        EXPECT_FALSE(shared);
     }
 
     {
@@ -73,7 +87,7 @@
         EXPECT_EQ(weak.valid(), false);
 
         auto shared2 = weak.lock();
-        EXPECT_EQ(shared, false);
+        EXPECT_FALSE(shared);
         EXPECT_EQ(shared2.get(), nullptr);
         EXPECT_EQ(shared.strongRef(), 0);
         EXPECT_EQ(weak.valid(), false);
@@ -148,6 +162,35 @@
     int m_childAInt = 4;
 };
 
+class IVirtualRoot {
+  public:
+    virtual ~IVirtualRoot() = default;
+    int m_rootInt           = 5;
+};
+
+class IVirtualLeft : public virtual IVirtualRoot {
+  public:
+    virtual ~IVirtualLeft() = default;
+    int m_leftInt           = 6;
+};
+
+class IVirtualRight : public virtual IVirtualRoot {
+  public:
+    virtual ~IVirtualRight() = default;
+    int m_rightInt           = 7;
+};
+
+class CVirtualChild : public IVirtualLeft, public IVirtualRight {
+  public:
+    virtual ~CVirtualChild() = default;
+    int m_virtualChildInt    = 8;
+};
+
+class CConstObject {
+  public:
+    int m_value = 9;
+};
+
 static void testHierarchy() {
     // Same test for atomic and non-atomic
     {
@@ -243,6 +286,167 @@
     }
 }
 
+static void testVirtualHierarchy() {
+    {
+        SP<CVirtualChild> child = makeShared<CVirtualChild>();
+
+        SP<IVirtualRoot>  root        = child;
+        SP<IVirtualRight> right       = 
dynamicPointerCast<IVirtualRight>(root);
+        SP<CVirtualChild> recastChild = 
dynamicPointerCast<CVirtualChild>(root);
+
+        EXPECT_TRUE(root);
+        EXPECT_TRUE(right);
+        EXPECT_TRUE(recastChild);
+        EXPECT_EQ(root->m_rootInt, 5);
+        EXPECT_EQ(right->m_rightInt, 7);
+        EXPECT_EQ(recastChild->m_virtualChildInt, 8);
+
+        SP<IVirtualRoot> rootFromCast = 
dynamicPointerCast<IVirtualRoot>(child);
+        EXPECT_TRUE(rootFromCast);
+        EXPECT_EQ(rootFromCast->m_rootInt, 5);
+
+        WP<IVirtualRoot>  rootWeak  = child;
+        WP<IVirtualRight> rightWeak = 
dynamicPointerCast<IVirtualRight>(rootWeak);
+
+        EXPECT_TRUE(rootWeak);
+        EXPECT_TRUE(rightWeak);
+        EXPECT_EQ(rootWeak->m_rootInt, 5);
+        EXPECT_EQ(rightWeak->m_rightInt, 7);
+        EXPECT_EQ(rightWeak.lock()->m_rightInt, 7);
+    }
+
+    {
+        ASP<CVirtualChild> child = makeAtomicShared<CVirtualChild>();
+
+        ASP<IVirtualRoot>  root        = child;
+        ASP<IVirtualRight> right       = 
dynamicPointerCast<IVirtualRight>(root);
+        ASP<CVirtualChild> recastChild = 
dynamicPointerCast<CVirtualChild>(root);
+
+        EXPECT_TRUE(root);
+        EXPECT_TRUE(right);
+        EXPECT_TRUE(recastChild);
+        EXPECT_EQ(root->m_rootInt, 5);
+        EXPECT_EQ(right->m_rightInt, 7);
+        EXPECT_EQ(recastChild->m_virtualChildInt, 8);
+
+        ASP<IVirtualRoot> rootFromCast = 
dynamicPointerCast<IVirtualRoot>(child);
+        EXPECT_TRUE(rootFromCast);
+        EXPECT_EQ(rootFromCast->m_rootInt, 5);
+
+        AWP<IVirtualRoot>  rootWeak  = root;
+        AWP<IVirtualRight> rightWeak = 
dynamicPointerCast<IVirtualRight>(rootWeak);
+
+        EXPECT_TRUE(rootWeak);
+        EXPECT_TRUE(rightWeak);
+        EXPECT_EQ(rootWeak->m_rootInt, 5);
+        EXPECT_EQ(rightWeak->m_rightInt, 7);
+        EXPECT_EQ(rightWeak.lock()->m_rightInt, 7);
+    }
+
+    {
+        UP<CVirtualChild> child = makeUnique<CVirtualChild>();
+        EXPECT_TRUE(child);
+        EXPECT_EQ(child->m_virtualChildInt, 8);
+
+        UP<IVirtualRight> right = makeUnique<CVirtualChild>();
+        UP<IVirtualRoot>  root  = makeUnique<CVirtualChild>();
+
+        EXPECT_TRUE(right);
+        EXPECT_TRUE(root);
+        EXPECT_EQ(right->m_rightInt, 7);
+        EXPECT_EQ(root->m_rootInt, 5);
+
+        UP<IVirtualRoot> rootMoved = std::move(root);
+        EXPECT_TRUE(rootMoved);
+        EXPECT_FALSE(root);
+        EXPECT_EQ(rootMoved->m_rootInt, 5);
+
+        UP<IVirtualRight> rightAssigned;
+        rightAssigned = makeUnique<CVirtualChild>();
+        EXPECT_TRUE(rightAssigned);
+        EXPECT_EQ(rightAssigned->m_rightInt, 7);
+
+        WP<IVirtualRoot>  rootWeak  = right;
+        WP<IVirtualRight> rightWeak = right;
+
+        EXPECT_TRUE(rootWeak);
+        EXPECT_TRUE(rightWeak);
+        EXPECT_EQ(rootWeak->m_rootInt, 5);
+        EXPECT_EQ(rightWeak->m_rightInt, 7);
+
+        // locking a weak ptr over a unique ptr is not allowed
+        EXPECT_DEBUG_DEATH(EXPECT_EQ(rightWeak.lock(), nullptr), "");
+    }
+}
+
+static void testConstPointers() {
+    {
+        SP<const CConstObject> shared = makeShared<const CConstObject>();
+        WP<const CConstObject> weak   = shared;
+
+        EXPECT_TRUE(shared);
+        EXPECT_TRUE(weak);
+        EXPECT_EQ(shared->m_value, 9);
+        EXPECT_EQ(weak->m_value, 9);
+    }
+
+    {
+        UP<const CConstObject> unique = makeUnique<const CConstObject>();
+        WP<const CConstObject> weak   = unique;
+
+        EXPECT_TRUE(unique);
+        EXPECT_TRUE(weak);
+        EXPECT_EQ(unique->m_value, 9);
+        EXPECT_EQ(weak->m_value, 9);
+    }
+
+    {
+        SP<const CVirtualChild> child = makeShared<const CVirtualChild>();
+
+        SP<const IVirtualRoot>  root        = child;
+        SP<const IVirtualRight> right       = dynamicPointerCast<const 
IVirtualRight>(root);
+        SP<const CVirtualChild> recastChild = dynamicPointerCast<const 
CVirtualChild>(root);
+
+        EXPECT_TRUE(root);
+        EXPECT_TRUE(right);
+        EXPECT_TRUE(recastChild);
+        EXPECT_EQ(root->m_rootInt, 5);
+        EXPECT_EQ(right->m_rightInt, 7);
+        EXPECT_EQ(recastChild->m_virtualChildInt, 8);
+
+        WP<const IVirtualRoot>  rootWeak  = child;
+        WP<const IVirtualRight> rightWeak = dynamicPointerCast<const 
IVirtualRight>(rootWeak);
+
+        EXPECT_TRUE(rootWeak);
+        EXPECT_TRUE(rightWeak);
+        EXPECT_EQ(rootWeak->m_rootInt, 5);
+        EXPECT_EQ(rightWeak->m_rightInt, 7);
+    }
+
+    {
+        ASP<const CVirtualChild> child = makeAtomicShared<const 
CVirtualChild>();
+
+        ASP<const IVirtualRoot>  root        = child;
+        ASP<const IVirtualRight> right       = dynamicPointerCast<const 
IVirtualRight>(root);
+        ASP<const CVirtualChild> recastChild = dynamicPointerCast<const 
CVirtualChild>(root);
+
+        EXPECT_TRUE(root);
+        EXPECT_TRUE(right);
+        EXPECT_TRUE(recastChild);
+        EXPECT_EQ(root->m_rootInt, 5);
+        EXPECT_EQ(right->m_rightInt, 7);
+        EXPECT_EQ(recastChild->m_virtualChildInt, 8);
+
+        AWP<const IVirtualRoot>  rootWeak  = root;
+        AWP<const IVirtualRight> rightWeak = dynamicPointerCast<const 
IVirtualRight>(rootWeak);
+
+        EXPECT_TRUE(rootWeak);
+        EXPECT_TRUE(rightWeak);
+        EXPECT_EQ(rootWeak->m_rootInt, 5);
+        EXPECT_EQ(rightWeak->m_rightInt, 7);
+    }
+}
+
 class CSelfDestruct {
   public:
     SP<CSelfDestruct> self;
@@ -285,8 +489,8 @@
     EXPECT_EQ(weakUnique.expired(), false);
     EXPECT_EQ(intUnique.impl_->wref(), 1);
 
-    SP<int> sharedFromUnique = weakUnique.lock();
-    EXPECT_EQ(sharedFromUnique, nullptr);
+    // locking a weak ptr over a unique ptr is not allowed
+    EXPECT_DEBUG_DEATH(EXPECT_EQ(weakUnique.lock(), nullptr), "");
 
     std::vector<SP<int>> sps;
     sps.push_back(intPtr);
@@ -320,5 +524,9 @@
 
     testHierarchy();
 
+    testVirtualHierarchy();
+
+    testConstPointers();
+
     testSelfDestruct();
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hyprutils-0.13.1/tests/memory/WeakPtrMove.cpp 
new/hyprutils-0.14.0/tests/memory/WeakPtrMove.cpp
--- old/hyprutils-0.13.1/tests/memory/WeakPtrMove.cpp   1970-01-01 
01:00:00.000000000 +0100
+++ new/hyprutils-0.14.0/tests/memory/WeakPtrMove.cpp   2026-07-17 
23:23:33.000000000 +0200
@@ -0,0 +1,65 @@
+#include <hyprutils/memory/SharedPtr.hpp>
+#include <hyprutils/memory/WeakPtr.hpp>
+
+#include <gtest/gtest.h>
+#include <utility>
+
+using namespace Hyprutils::Memory;
+
+namespace {
+    class IBase {
+      public:
+        virtual ~IBase() = default;
+        int value        = 0;
+    };
+
+    class CDerived : public IBase {};
+}
+
+TEST(Memory, weakPointerMoveAssignment) {
+    auto lhsOwner = makeShared<int>(1);
+    auto rhsOwner = makeShared<int>(2);
+
+    CWeakPointer<int> lhs = lhsOwner;
+    CWeakPointer<int> rhs = rhsOwner;
+
+    auto* lhsImpl = lhs.impl_;
+    auto* rhsImpl = rhs.impl_;
+
+    ASSERT_EQ(lhsImpl->wref(), 1U);
+    ASSERT_EQ(rhsImpl->wref(), 1U);
+
+    lhs = std::move(rhs);
+
+    EXPECT_EQ(lhs.impl_, rhsImpl);
+    EXPECT_EQ(rhs.impl_, lhsImpl);
+    EXPECT_EQ(lhsImpl->wref(), 1U);
+    EXPECT_EQ(rhsImpl->wref(), 1U);
+    EXPECT_EQ(*lhs, 2);
+    EXPECT_EQ(*rhs, 1);
+}
+
+TEST(Memory, weakPointerConvertingMoveAssignment) {
+    auto lhsOwner   = makeShared<CDerived>();
+    auto rhsOwner   = makeShared<CDerived>();
+    lhsOwner->value = 1;
+    rhsOwner->value = 2;
+
+    CWeakPointer<IBase>    lhs = lhsOwner;
+    CWeakPointer<CDerived> rhs = rhsOwner;
+
+    auto* lhsImpl = lhs.impl_;
+    auto* rhsImpl = rhs.impl_;
+
+    ASSERT_EQ(lhsImpl->wref(), 1U);
+    ASSERT_EQ(rhsImpl->wref(), 1U);
+
+    lhs = std::move(rhs);
+
+    EXPECT_EQ(lhs.impl_, rhsImpl);
+    EXPECT_EQ(rhs.impl_, lhsImpl);
+    EXPECT_EQ(lhsImpl->wref(), 1U);
+    EXPECT_EQ(rhsImpl->wref(), 1U);
+    EXPECT_EQ(lhs->value, 2);
+    EXPECT_EQ(rhs->value, 1);
+}

++++++ hyprutils.obsinfo ++++++
--- /var/tmp/diff_new_pack.xU7SS1/_old  2026-07-20 10:02:04.261644448 +0200
+++ /var/tmp/diff_new_pack.xU7SS1/_new  2026-07-20 10:02:04.285645256 +0200
@@ -1,5 +1,5 @@
 name: hyprutils
-version: 0.13.1
-mtime: 1778234770
-commit: a2dbd8a4cc51f7cbe4224732668392bb1aa79df2
+version: 0.14.0
+mtime: 1784323413
+commit: 5f03477ab3a005ff27c527486f551883535aea2f
 

Reply via email to