Let we assert the cases when observer list try to be changed during notifying.
But, allow to remove observer during notifying, only for Object cases.
Until now, we just change the observer list directly, so if notifying, then
memory corruption occured.
To avoid this case, follow as ProcessorInterface did, copy the observer lists
and check whether it is valid or not, only if somebody touch original list.
Note : For BaseObject::Impl case, it is not opened
and it didn't send notify when scene on/off.
The only observer for BaseObject::Impl is WeakHandle. and OnDestroy() didnt'
send callback that timing.
Note2 : PropertyOwner case, it is also only for update-render thread for DALi.
We can control that non of callback change the observers for this thing.
So let we assume BaseObject::Impl::RemoveObserver and
PropertyOwner::RemoveObserver will not be called
during desturct object.
TODO : Should we also control Object::RemoveObserver case?
TODO : Should we remove observer copy?
Change-Id: Ia373243bdaae83613b1b91680a77b86409977028
Signed-off-by: Eunki, Hong <eunkiki.hong@samsung.com>
/*
- * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
namespace Dali
{
BaseObject::Impl::Impl(BaseObject& baseObject)
-: mBaseObject(baseObject)
+: mBaseObject(baseObject),
+ mObservers(),
+ mObserverNotifying(false)
{
}
BaseObject::Impl::~Impl()
{
+ // Guard Add/Remove observer during observer notifying.
+ mObserverNotifying = true;
+
// Notification for observers
for(auto&& item : mObservers)
{
item->ObjectDestroyed(mBaseObject);
}
+
+ // Note : We don't need to restore mObserverNotifying to false as we are in delete the object.
+ // If someone call AddObserver / RemoveObserver after this, assert.
+
+ // Remove all observers
+ mObservers.Clear();
}
BaseObject::Impl& BaseObject::Impl::Get(BaseObject& baseObject)
void BaseObject::Impl::AddObserver(Observer& observer)
{
+ DALI_ASSERT_ALWAYS(!mObserverNotifying && "Cannot add observer while notifying BaseObject::Impl::Observers");
+
// make sure an observer doesn't observe the same object twice
// otherwise it will get multiple calls to ObjectDestroyed()
DALI_ASSERT_DEBUG(mObservers.End() == std::find(mObservers.Begin(), mObservers.End(), &observer));
void BaseObject::Impl::RemoveObserver(Observer& observer)
{
+ DALI_ASSERT_ALWAYS(!mObserverNotifying && "Cannot remove observer while notifying BaseObject::Impl::Observers");
+
// Find the observer...
const auto endIter = mObservers.End();
for(auto iter = mObservers.Begin(); iter != endIter; ++iter)
#define DALI_BASE_OBJECT_IMPL_H
/*
- * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
private:
BaseObject& mBaseObject;
Dali::Vector<Observer*> mObservers;
+
+ bool mObserverNotifying : 1; ///< Whether we are currently notifying observers.
};
} // namespace Dali
constexpr Property::Index MAX_PER_CLASS_PROPERTY_INDEX = ANIMATABLE_PROPERTY_REGISTRATION_MAX_INDEX;
+/// Helper to notify observers
+using ObserverNotifyMethod = void (Object::Observer::*)(Object&);
+
+void NotifyObservers(Object& object, Object::ObserverContainer& observers, ObserverNotifyMethod memberFunction)
+{
+ // Guard Add/Remove observer during observer notifying.
+ object.mObserverNotifying = true;
+
+ // Copy observers to prevent changes to vector affecting loop iterator.
+ auto copiedObservers(observers);
+
+ object.mObserverRemoved = false;
+
+ // Notification for observers
+ for(auto&& item : copiedObservers)
+ {
+ if(!object.mObserverRemoved)
+ {
+ (item->*memberFunction)(object);
+ }
+ else
+ {
+ // Notify only if the observer is still in the list.
+ // It may be removed during the loop.
+ auto iter = std::find(observers.Begin(), observers.End(), item);
+ if(iter != observers.End())
+ {
+ (item->*memberFunction)(object);
+ }
+ }
+ }
+
+ object.mObserverNotifying = false;
+}
+
} // unnamed namespace
IntrusivePtr<Object> Object::New()
void Object::AddObserver(Observer& observer)
{
+ DALI_ASSERT_ALWAYS(!mObserverNotifying && "Cannot add observer while notifying Object::Observers");
+
// make sure an observer doesn't observe the same object twice
// otherwise it will get multiple calls to OnSceneObjectAdd(), OnSceneObjectRemove() and ObjectDestroyed()
DALI_ASSERT_DEBUG(mObservers.End() == std::find(mObservers.Begin(), mObservers.End(), &observer));
void Object::RemoveObserver(Observer& observer)
{
+ // Note : We allow to remove observer during notifying.
+ // TODO : Could we assert during notifying in future?
+
// Find the observer...
const auto endIter = mObservers.End();
for(auto iter = mObservers.Begin(); iter != endIter; ++iter)
{
if((*iter) == &observer)
{
+ mObserverRemoved = true;
mObservers.Erase(iter);
break;
}
mUpdateObject(sceneObject),
mTypeInfo(nullptr),
mConstraints(nullptr),
- mPropertyNotifications(nullptr)
+ mPropertyNotifications(nullptr),
+ mObserverNotifying(false),
+ mObserverRemoved(false)
{
}
Object::~Object()
{
- // Notification for observers
- for(auto&& item : mObservers)
- {
- item->ObjectDestroyed(*this);
- }
+ NotifyObservers(*this, mObservers, &Object::Observer::ObjectDestroyed);
+
+ // Note : We don't need to restore mObserverNotifying to false as we are in delete the object.
+ // If someone call AddObserver after this, assert.
+ mObserverNotifying = true;
+
+ // Remove all observers
+ mObserverRemoved = true;
+ mObservers.Clear();
// Disable property notifications in scene graph
DisablePropertyNotifications();
void Object::OnSceneObjectAdd()
{
- // Notification for observers
- for(auto&& item : mObservers)
- {
- item->SceneObjectAdded(*this);
- }
+ DALI_ASSERT_ALWAYS(!mObserverNotifying && "Should not be call OnSceneObjectAdd while notifying observers");
+
+ NotifyObservers(*this, mObservers, &Object::Observer::SceneObjectAdded);
// enable property notifications in scene graph
EnablePropertyNotifications();
void Object::OnSceneObjectRemove()
{
- // Notification for observers
- for(auto&& item : mObservers)
- {
- item->SceneObjectRemoved(*this);
- }
+ DALI_ASSERT_ALWAYS(!mObserverNotifying && "Should not be call OnSceneObjectRemove while notifying observers");
+
+ NotifyObservers(*this, mObservers, &Object::Observer::SceneObjectRemoved);
// disable property notifications in scene graph
DisablePropertyNotifications();
#define DALI_INTERNAL_OBJECT_H
/*
- * Copyright (c) 2022 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
public:
using Capability = Dali::Handle::Capability;
+ class Observer;
+ using ObserverContainer = Dali::Vector<Observer*>;
+
class Observer
{
public:
mutable const SceneGraph::PropertyOwner* mUpdateObject; ///< Reference to object to hold the scene graph properties
private:
- Dali::Vector<Observer*> mObservers;
+ ObserverContainer mObservers;
mutable OwnerContainer<PropertyMetadata*> mCustomProperties; ///< Used for accessing custom Node properties
mutable OwnerContainer<PropertyMetadata*> mAnimatableProperties; ///< Used for accessing animatable Node properties
mutable const TypeInfo* mTypeInfo; ///< The type-info for this object, mutable so it can be lazy initialized from const method if it is required
PropertyNotificationContainer* mPropertyNotifications; ///< Container of owned property notifications.
Handle::PropertySetSignalType mPropertySetSignal;
+
+public: /// To be used at observer container changes only.
+ bool mObserverNotifying : 1; ///< Whether we are currently notifying observers.
+ bool mObserverRemoved : 1; ///< Whether we have removed an observer during notify.
};
} // namespace Internal
if(hitActor && processor.mLastPrimaryHitActor.GetActor() != hitActor &&
localVars.primaryPointState == PointState::MOTION && GetImplementation(hitActor).GetLeaveRequired())
{
- // A leave event is sent to the previous actor first.
- localVars.lastPrimaryHitActor = processor.mLastPrimaryHitActor.GetActor();
- localVars.lastConsumedActor = processor.mLastConsumedActor.GetActor();
- Impl::DeliverLeaveEvent(processor, localVars);
+ // A leave event is sent to the previous actor first.
+ localVars.lastPrimaryHitActor = processor.mLastPrimaryHitActor.GetActor();
+ localVars.lastConsumedActor = processor.mLastConsumedActor.GetActor();
+ Impl::DeliverLeaveEvent(processor, localVars);
- localVars.hoverEvent->GetPoint(0).SetState(PointState::STARTED);
- localVars.primaryPointState = PointState::STARTED;
+ localVars.hoverEvent->GetPoint(0).SetState(PointState::STARTED);
+ localVars.primaryPointState = PointState::STARTED;
- // It sends a started event and updates information.
- localVars.consumedActor = EmitHoverSignals(hitActor, localVars.hoverEventHandle);
- UpdateMembersWithCurrentHitInformation(processor, localVars);
+ // It sends a started event and updates information.
+ localVars.consumedActor = EmitHoverSignals(hitActor, localVars.hoverEventHandle);
+ UpdateMembersWithCurrentHitInformation(processor, localVars);
}
else
{
#define DALI_INTERNAL_SCENE_GRAPH_ANIMATOR_H
/*
- * Copyright (c) 2023 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
/**
* @copydoc PropertyOwner::Observer::PropertyOwnerDisconnected( BufferIndex bufferIndex, PropertyOwner& owner )
*/
- void PropertyOwnerDisconnected(BufferIndex bufferIndex, PropertyOwner& owner) final
+ NotifyReturnType PropertyOwnerDisconnected(BufferIndex bufferIndex, PropertyOwner& owner) final
{
// If we are active, then bake the value if required
if(mAnimationPlaying && mDisconnectAction != Dali::Animation::DISCARD)
}
mEnabled = false;
+
+ return NotifyReturnType::KEEP_OBSERVING;
}
/**
#define DALI_INTERNAL_SCENE_GRAPH_CONSTRAINT_BASE_H
/*
- * Copyright (c) 2023 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
/**
* @copydoc PropertyOwner::Observer::PropertyOwnerDisconnected()
*/
- void PropertyOwnerDisconnected(BufferIndex bufferIndex, PropertyOwner& owner) override
+ NotifyReturnType PropertyOwnerDisconnected(BufferIndex bufferIndex, PropertyOwner& owner) override
{
if(!mDisconnected)
{
- // Stop observing property owners
- StopObservation();
-
- // Notification for derived class
- OnDisconnect();
+ // Call PropertyOwnerDestroyed(), for reduce duplicated code.
+ ConstraintBase::PropertyOwnerDestroyed(owner);
- mDisconnected = true;
+ // Let we make owner Stop observing this.
+ return NotifyReturnType::STOP_OBSERVING;
}
+
+ return NotifyReturnType::KEEP_OBSERVING;
}
/**
#define DALI_INTERNAL_SCENEGRAPH_NODE_RESETTER_H
/*
- * Copyright (c) 2023 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* @param[in] bufferIndex the current buffer index
* @param[in] owner The property owner
*/
- void PropertyOwnerDisconnected(BufferIndex bufferIndex, PropertyOwner& owner) override
+ NotifyReturnType PropertyOwnerDisconnected(BufferIndex bufferIndex, PropertyOwner& owner) override
{
mDisconnected = true;
+ return NotifyReturnType::KEEP_OBSERVING;
}
/**
/*
- * Copyright (c) 2023 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
void PropertyOwner::AddObserver(Observer& observer)
{
+ DALI_ASSERT_ALWAYS(!mObserverNotifying && "Cannot add observer while notifying PropertyOwner::Observers");
+
//Check for duplicates in debug builds
DALI_ASSERT_DEBUG(mObservers.End() == std::find(mObservers.Begin(), mObservers.End(), &observer));
void PropertyOwner::RemoveObserver(Observer& observer)
{
+ DALI_ASSERT_ALWAYS(!mObserverNotifying && "Cannot remove observer while notifying PropertyOwner::Observers");
+
// Find the observer...
const ConstObserverIter endIter = mObservers.End();
for(ObserverIter iter = mObservers.Begin(); iter != endIter; ++iter)
{
if((*iter) == &observer)
{
- // erase it
mObservers.Erase(iter);
break;
}
void PropertyOwner::Destroy()
{
+ // Guard Add/Remove observer during observer notifying.
+ mObserverNotifying = true;
+
// Notification for observers
- const ConstObserverIter endIter = mObservers.End();
- for(ConstObserverIter iter = mObservers.Begin(); iter != endIter; ++iter)
+ for(auto&& item : mObservers)
{
- (*iter)->PropertyOwnerDestroyed(*this);
+ item->PropertyOwnerDestroyed(*this);
}
+ // Note : We don't need to restore mObserverNotifying to false as we are in delete the object.
+ // If someone call AddObserver / RemoveObserver after this, assert.
+
+ // Remove all observers
mObservers.Clear();
// Remove all constraints when disconnected from scene-graph
void PropertyOwner::ConnectToSceneGraph()
{
+ DALI_ASSERT_ALWAYS(!mObserverNotifying && "Should not be call ConnectToSceneGraph while notifying PropertyOwner::Observers");
+
mIsConnectedToSceneGraph = true;
SetUpdated(true);
+ // Guard Add/Remove observer during observer notifying.
+ mObserverNotifying = true;
+
// Notification for observers
- const ConstObserverIter endIter = mObservers.End();
- for(ConstObserverIter iter = mObservers.Begin(); iter != endIter; ++iter)
+ for(auto&& item : mObservers)
{
- (*iter)->PropertyOwnerConnected(*this);
+ item->PropertyOwnerConnected(*this);
}
+
+ mObserverNotifying = false;
}
void PropertyOwner::DisconnectFromSceneGraph(BufferIndex updateBufferIndex)
{
+ DALI_ASSERT_ALWAYS(!mObserverNotifying && "Should not be call DisconnectFromSceneGraph while notifying PropertyOwner::Observers");
+
mIsConnectedToSceneGraph = false;
+ // Guard Add/Remove observer during observer notifying.
+ mObserverNotifying = true;
+
// Notification for observers
- const ConstObserverIter endIter = mObservers.End();
- for(ConstObserverIter iter = mObservers.Begin(); iter != endIter; ++iter)
+ for(auto iter = mObservers.Begin(), endIter = mObservers.End(); iter != endIter;)
{
- (*iter)->PropertyOwnerDisconnected(updateBufferIndex, *this);
+ auto returnValue = (*iter)->PropertyOwnerDisconnected(updateBufferIndex, *this);
+ if(returnValue == Observer::KEEP_OBSERVING)
+ {
+ ++iter;
+ }
+ else
+ {
+ iter = mObservers.Erase(iter);
+ }
}
+ mObserverNotifying = false;
+
// Remove all constraints when disconnected from scene-graph
mConstraints.Clear();
mPostConstraints.Clear();
PropertyOwner::PropertyOwner()
: mUpdated(false),
- mIsConnectedToSceneGraph(false)
+ mIsConnectedToSceneGraph(false),
+ mObserverNotifying(false)
{
}
#define DALI_INTERNAL_SCENE_GRAPH_PROPERTY_OWNER_H
/*
- * Copyright (c) 2023 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
public:
class Observer
{
+ public:
+ enum NotifyReturnType
+ {
+ STOP_OBSERVING,
+ KEEP_OBSERVING,
+ };
+
public:
/**
* Called when the observable object is connected to the scene graph.
* Called when the observable object is disconnected from the scene graph.
* @param[in] currentBufferIndex The buffer to reset.
* @param[in] owner A reference to the disconnected PropertyOwner
+ * @return NotifyReturnType::STOP_OBSERVING if we will not observe this object after this called
+ * NotifyReturnType::KEEP_OBSERVING if we will observe this object after this called.
*/
- virtual void PropertyOwnerDisconnected(BufferIndex updateBufferIndex, PropertyOwner& owner) = 0;
+ virtual NotifyReturnType PropertyOwnerDisconnected(BufferIndex updateBufferIndex, PropertyOwner& owner) = 0;
/**
* Called shortly before the observable object is destroyed.
ObserverContainer mObservers; ///< Container of observer raw-pointers (not owned)
- ConstraintOwnerContainer mConstraints; ///< Container of owned constraints
+ ConstraintOwnerContainer mConstraints; ///< Container of owned constraints
ConstraintOwnerContainer mPostConstraints; ///< Container of owned constraints
+
+ bool mObserverNotifying : 1; ///< Whether we are currently notifying observers.
};
} // namespace SceneGraph
* @param[in] bufferIndex the current buffer index
* @param[in] owner The property owner
*/
- void PropertyOwnerDisconnected(BufferIndex bufferIndex, PropertyOwner& owner) override
+ NotifyReturnType PropertyOwnerDisconnected(BufferIndex bufferIndex, PropertyOwner& owner) override
{
mDisconnected = true;
+ return NotifyReturnType::KEEP_OBSERVING;
}
/**
#define DALI_INTERNAL_SCENEGRAPH_RENDERER_RESETTER_H
/*
- * Copyright (c) 2023 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* @param[in] bufferIndex the current buffer index
* @param[in] owner The property owner
*/
- void PropertyOwnerDisconnected(BufferIndex bufferIndex, PropertyOwner& owner) override
+ NotifyReturnType PropertyOwnerDisconnected(BufferIndex bufferIndex, PropertyOwner& owner) override
{
+ return NotifyReturnType::KEEP_OBSERVING;
}
/**
/**
* @copydoc PropertyOwner::Observer::PropertyOwnerDisconnected()
*/
- void PropertyOwnerDisconnected(BufferIndex updateBufferIndex, PropertyOwner& owner) override
+ NotifyReturnType PropertyOwnerDisconnected(BufferIndex updateBufferIndex, PropertyOwner& owner) override
{ /* Nothing to do */
+ return NotifyReturnType::KEEP_OBSERVING;
}
/**
/**
* @copydoc SceneGraph::PropertyOwner::Observer::PropertyOwnerDisconnected()
*/
- void PropertyOwnerDisconnected(BufferIndex updateBufferIndex, SceneGraph::PropertyOwner& owner) override
+ NotifyReturnType PropertyOwnerDisconnected(BufferIndex updateBufferIndex, SceneGraph::PropertyOwner& owner) override
{ /* Nothing to do */
+ return NotifyReturnType::KEEP_OBSERVING;
}
/**
/*
- * Copyright (c) 2023 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
SetActiveStatus();
}
-void RenderTask::PropertyOwnerDisconnected(BufferIndex /*updateBufferIndex*/, PropertyOwner& owner)
+PropertyOwner::Observer::NotifyReturnType RenderTask::PropertyOwnerDisconnected(BufferIndex /*updateBufferIndex*/, PropertyOwner& owner)
{
mActive = false; // if either source or camera disconnected, we're no longer active
+ return PropertyOwner::Observer::NotifyReturnType::KEEP_OBSERVING;
}
void RenderTask::PropertyOwnerDestroyed(PropertyOwner& owner)
#define DALI_INTERNAL_SCENE_GRAPH_RENDER_TASK_H
/*
- * Copyright (c) 2023 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
/**
* @copydoc PropertyOwner::Observer::PropertyOwnerDisconnected( BufferIndex updateBufferIndex, PropertyOwner& owner )
*/
- void PropertyOwnerDisconnected(BufferIndex updateBufferIndex, PropertyOwner& owner) override;
+ PropertyOwner::Observer::NotifyReturnType PropertyOwnerDisconnected(BufferIndex updateBufferIndex, PropertyOwner& owner) override;
/**
* @copydoc PropertyOwner::Observer::PropertyOwnerDestroyed( PropertyOwner& owner )
RenderTask();
// Undefined
- RenderTask(const RenderTask&) = delete;
+ RenderTask(const RenderTask&) = delete;
RenderTask& operator=(const RenderTask&) = delete;
public: // Animatable Properties
RenderInstruction mRenderInstruction[2]; ///< Owned double buffered render instruction. (Double buffered because this owns render commands for the currently drawn frame)
- uint32_t mRefreshRate; ///< REFRESH_ONCE, REFRESH_ALWAYS or render every N frames
- uint32_t mFrameCounter; ///< counter for rendering every N frames
- uint32_t mRenderedOnceCounter; ///< Incremented whenever state changes to RENDERED_ONCE_AND_NOTIFIED
+ uint32_t mRefreshRate; ///< REFRESH_ONCE, REFRESH_ALWAYS or render every N frames
+ uint32_t mFrameCounter; ///< counter for rendering every N frames
+ uint32_t mRenderedOnceCounter; ///< Incremented whenever state changes to RENDERED_ONCE_AND_NOTIFIED
- State mState; ///< Render state.
+ State mState; ///< Render state.
uint32_t mRenderPassTag{0u};