animation: let callbacks have SP's to the self variable and dispatch them internally

To be absolutely sure that adding/removing callbacks can't cause
problems, dispatch them in tickDone outside of the loop.
I think looping over all the vars twice is still better than having
disconnectFromActive handle the removal, because that needs to search
for each variable in the active vector.
This commit is contained in:
Maximilian Seidler 2025-01-07 19:02:46 +01:00
parent 72dfbf5296
commit 71844cbde1
5 changed files with 49 additions and 20 deletions

View file

@ -14,7 +14,7 @@ namespace Hyprutils {
/* A base class for animated variables. */
class CBaseAnimatedVariable {
public:
using CallbackFun = std::function<void(Memory::CWeakPointer<CBaseAnimatedVariable> thisptr)>;
using CallbackFun = std::function<void(Memory::CSharedPointer<CBaseAnimatedVariable> thisptr)>;
CBaseAnimatedVariable() {
; // m_bDummy = true;

View file

@ -14,6 +14,7 @@ namespace Hyprutils {
class CAnimationManager {
public:
CAnimationManager();
virtual ~CAnimationManager() = default;
void tickDone();
bool shouldTickForNext();

View file

@ -103,8 +103,11 @@ bool CBaseAnimatedVariable::ok() const {
}
void CBaseAnimatedVariable::onUpdate() {
if (m_bIsBeingAnimated && m_fUpdateCallback)
m_fUpdateCallback(m_pSelf);
if (!m_bIsBeingAnimated || !m_fUpdateCallback)
return;
if (const auto SELF = m_pSelf.lock(); SELF)
m_fUpdateCallback(SELF);
}
void CBaseAnimatedVariable::setCallbackOnEnd(CallbackFun func, bool remove) {
@ -133,14 +136,15 @@ void CBaseAnimatedVariable::resetAllCallbacks() {
}
void CBaseAnimatedVariable::onAnimationEnd() {
m_bIsBeingAnimated = false;
/* We do not call disconnectFromActive here. The animation manager will remove it on a call to tickDone. */
m_bIsBeingAnimated = false;
if (m_fEndCallback) {
/* loading m_bRemoveEndAfterRan before calling the callback allows the callback to delete this animation safely if it is false. */
auto removeEndCallback = m_bRemoveEndAfterRan;
m_fEndCallback(m_pSelf);
if (removeEndCallback)
if (m_bIsConnectedToActive) // callback will we handled by tickDone
return;
if (const auto SELF = m_pSelf.lock(); SELF && m_fEndCallback) {
m_fEndCallback(SELF);
if (m_bRemoveEndAfterRan)
m_fEndCallback = nullptr; // reset
}
}
@ -150,8 +154,8 @@ void CBaseAnimatedVariable::onAnimationBegin() {
animationBegin = std::chrono::steady_clock::now();
connectToActive();
if (m_fBeginCallback) {
m_fBeginCallback(m_pSelf);
if (const auto SELF = m_pSelf.lock(); SELF && m_fBeginCallback) {
m_fBeginCallback(SELF);
if (m_bRemoveBeginAfterRan)
m_fBeginCallback = nullptr; // reset
}

View file

@ -37,6 +37,27 @@ bool CAnimationManager::shouldTickForNext() {
}
void CAnimationManager::tickDone() {
std::vector<std::function<void(void)>> callbacks;
callbacks.reserve(m_vActiveAnimatedVariables.size());
for (auto const& av : m_vActiveAnimatedVariables) {
const auto PAV = av.lock();
if (!PAV)
continue;
if (PAV->ok() && PAV->isBeingAnimated()) {
if (PAV->m_fUpdateCallback)
callbacks.emplace_back(std::bind(PAV->m_fUpdateCallback, PAV));
} else {
if (PAV->m_fEndCallback)
callbacks.emplace_back(std::bind(PAV->m_fEndCallback, PAV));
if (PAV->m_bRemoveEndAfterRan)
PAV->m_fEndCallback = nullptr;
}
}
for (const auto& cb : callbacks)
cb();
std::vector<CWeakPointer<CBaseAnimatedVariable>> active;
active.reserve(m_vActiveAnimatedVariables.size()); // avoid reallocations
for (auto const& av : m_vActiveAnimatedVariables) {

View file

@ -43,8 +43,8 @@ CAnimationConfigTree animationTree;
class CMyAnimationManager : public CAnimationManager {
public:
void tick() {
for (size_t i = 0; i < m_vActiveAnimatedVariables.size(); i++) {
const auto PAV = m_vActiveAnimatedVariables[i].lock();
for (const auto& av : m_vActiveAnimatedVariables) {
const auto PAV = av.lock();
if (!PAV || !PAV->ok())
continue;
@ -79,8 +79,6 @@ class CMyAnimationManager : public CAnimationManager {
std::cout << Colors::RED << "What are we even doing?" << Colors::RESET;
} break;
}
PAV->onUpdate();
}
tickDone();
@ -263,12 +261,16 @@ int main(int argc, char** argv, char** envp) {
int beginCallbackRan = 0;
int updateCallbackRan = 0;
int endCallbackRan = 0;
s.m_iA->setCallbackOnBegin([&beginCallbackRan](WP<CBaseAnimatedVariable> pav) { beginCallbackRan++; });
s.m_iA->setUpdateCallback([&updateCallbackRan](WP<CBaseAnimatedVariable> pav) { updateCallbackRan++; });
s.m_iA->setCallbackOnEnd([&endCallbackRan](WP<CBaseAnimatedVariable> pav) { endCallbackRan++; }, false);
s.m_iA->setCallbackOnBegin([&beginCallbackRan](SP<CBaseAnimatedVariable> pav) { beginCallbackRan++; }, false);
s.m_iA->setUpdateCallback([&updateCallbackRan](SP<CBaseAnimatedVariable> pav) { updateCallbackRan++; });
s.m_iA->setCallbackOnEnd([&endCallbackRan](SP<CBaseAnimatedVariable> pav) { endCallbackRan++; }, false);
EXPECT(gAnimationManager.shouldTickForNext(), false);
s.m_iA->setValueAndWarp(42);
EXPECT(gAnimationManager.shouldTickForNext(), false);
EXPECT(beginCallbackRan, 0);
EXPECT(updateCallbackRan, 1);
EXPECT(endCallbackRan, 2); // first called when setting the callback, then when warping.
@ -291,8 +293,8 @@ int main(int argc, char** argv, char** envp) {
// test adding / removing vars during a tick
s.m_iA->resetAllCallbacks();
s.m_iA->setUpdateCallback([&vars](WP<CBaseAnimatedVariable> v) {
if (v.lock() != vars.back())
s.m_iA->setUpdateCallback([&vars](SP<CBaseAnimatedVariable> v) {
if (v != vars.back())
vars.back()->warp();
});
s.m_iA->setCallbackOnEnd([&s, &vars](auto) {
@ -310,6 +312,7 @@ int main(int argc, char** argv, char** envp) {
EXPECT(s.m_iA->value(), 1000000);
// all vars should be set to 1337
EXPECT(std::find_if(vars.begin(), vars.end(), [](const auto& v) { return v->value() != 1337; }) == vars.end(), true);
EXPECT(endCallbackRan, 3);
// test one-time callbacks
s.m_iA->resetAllCallbacks();