2 * Copyright (c) 2024 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
19 #include <dali/public-api/signals/base-signal.h>
25 #include <dali/integration-api/debug.h>
29 struct CallbackBasePtrCompare
31 bool operator()(const Dali::CallbackBase* lhs, const Dali::CallbackBase* rhs) const noexcept
33 const void* lhsFunctionPtr = reinterpret_cast<void*>(lhs->mFunction);
34 const void* rhsFunctionPtr = reinterpret_cast<void*>(rhs->mFunction);
35 if(lhsFunctionPtr < rhsFunctionPtr)
39 else if(lhsFunctionPtr > rhsFunctionPtr)
44 if(lhs->mImpl.mObjectPointer < rhs->mImpl.mObjectPointer)
54 } // unnamed namespace
59 * @brief Extra struct for callback base cache.
61 struct BaseSignal::Impl
67 * @brief Get the iterator of connections list by the callback base pointer.
68 * Note that we should compare the 'value' of callback, not pointer.
69 * So, we need to define custom compare functor of callback base pointer.
71 std::map<const CallbackBase*, std::list<SignalConnection>::iterator, CallbackBasePtrCompare> mCallbackCache;
74 BaseSignal::BaseSignal()
75 : mCacheImpl(new BaseSignal::Impl()),
80 BaseSignal::~BaseSignal()
82 // We can't assert in a destructor
85 DALI_LOG_ERROR("Invalid destruction of Signal during Emit()\n");
87 // Set the signal deletion flag as well if set
90 *mSignalDeleted = true;
94 // The signal is being destroyed. We have to inform any slots
95 // that are connected, that the signal is dead.
96 for(auto iter = mSignalConnections.begin(), iterEnd = mSignalConnections.end(); iter != iterEnd; ++iter)
98 auto& connection = *iter;
100 // Note that values are set to NULL in DeleteConnection
103 connection.Disconnect(this);
110 void BaseSignal::OnConnect(CallbackBase* callback)
112 DALI_ASSERT_ALWAYS(nullptr != callback && "Invalid member function pointer passed to Connect()");
114 auto iter = FindCallback(callback);
116 // Don't double-connect the same callback
117 if(iter == mSignalConnections.end())
119 auto newIter = mSignalConnections.insert(mSignalConnections.end(), SignalConnection(callback));
121 // Store inserted iterator for this callback
122 mCacheImpl->mCallbackCache[callback] = newIter;
131 void BaseSignal::OnDisconnect(CallbackBase* callback)
133 DALI_ASSERT_ALWAYS(nullptr != callback && "Invalid member function pointer passed to Disconnect()");
135 auto iter = FindCallback(callback);
137 if(iter != mSignalConnections.end())
139 DeleteConnection(iter);
142 // call back is a temporary created to find which slot should be disconnected.
146 void BaseSignal::OnConnect(ConnectionTrackerInterface* tracker, CallbackBase* callback)
148 DALI_ASSERT_ALWAYS(nullptr != tracker && "Invalid ConnectionTrackerInterface pointer passed to Connect()");
149 DALI_ASSERT_ALWAYS(nullptr != callback && "Invalid member function pointer passed to Connect()");
151 auto iter = FindCallback(callback);
153 // Don't double-connect the same callback
154 if(iter == mSignalConnections.end())
156 auto newIter = mSignalConnections.insert(mSignalConnections.end(), {tracker, callback});
158 // Store inserted iterator for this callback
159 mCacheImpl->mCallbackCache[callback] = newIter;
161 // Let the connection tracker know that a connection between a signal and a slot has been made.
162 tracker->SignalConnected(this, callback);
171 void BaseSignal::OnDisconnect(ConnectionTrackerInterface* tracker, CallbackBase* callback)
173 DALI_ASSERT_ALWAYS(nullptr != tracker && "Invalid ConnectionTrackerInterface pointer passed to Disconnect()");
174 DALI_ASSERT_ALWAYS(nullptr != callback && "Invalid member function pointer passed to Disconnect()");
176 auto iter = FindCallback(callback);
178 if(iter != mSignalConnections.end())
180 // temporary pointer to disconnected callback
181 // Note that (*iter).GetCallback() != callback is possible.
182 CallbackBase* disconnectedCallback = (*iter).GetCallback();
184 // close the signal side connection first.
185 DeleteConnection(iter);
187 // close the slot side connection
188 tracker->SignalDisconnected(this, disconnectedCallback);
191 // call back is a temporary created to find which slot should be disconnected.
195 // for SlotObserver::SlotDisconnected
196 void BaseSignal::SlotDisconnected(CallbackBase* callback)
198 DALI_ASSERT_ALWAYS(nullptr != callback && "Invalid callback function passed to SlotObserver::SlotDisconnected()");
200 auto iter = FindCallback(callback);
201 if(DALI_LIKELY(iter != mSignalConnections.end()))
203 DeleteConnection(iter);
207 DALI_ABORT("Callback lost in SlotDisconnected()");
210 std::list<SignalConnection>::iterator BaseSignal::FindCallback(CallbackBase* callback) noexcept
212 const auto& convertorIter = mCacheImpl->mCallbackCache.find(callback);
214 if(convertorIter != mCacheImpl->mCallbackCache.end())
216 const auto& iter = convertorIter->second; // std::list<SignalConnection>::iterator
218 if(*iter && iter->GetCallback()) // the value of iterator can be null.
220 if(*(iter->GetCallback()) == *callback)
226 return mSignalConnections.end();
229 void BaseSignal::DeleteConnection(std::list<SignalConnection>::iterator iter)
231 // Erase cache first.
232 mCacheImpl->mCallbackCache.erase(iter->GetCallback());
236 // IMPORTANT - do not remove from items from mSignalConnections, reset instead.
237 // Signal Emit() methods require that connection count is not reduced while iterating
238 // i.e. DeleteConnection can be called from within callbacks, while iterating through mSignalConnections.
244 // If application connects and disconnects without the signal never emitting,
245 // the mSignalConnections vector keeps growing and growing as CleanupConnections() is done from Emit.
246 mSignalConnections.erase(iter);
250 void BaseSignal::CleanupConnections()
252 if(!mSignalConnections.empty())
254 //Remove Signals that are already markeed nullptr.
255 mSignalConnections.remove_if([](auto& elem) { return (elem) ? false : true; });
257 mNullConnections = 0;
260 // BaseSignal::EmitGuard
262 BaseSignal::EmitGuard::EmitGuard(bool& flag)
272 // mFlag is NULL when Emit() is called during Emit()
273 DALI_LOG_ERROR("Cannot call Emit() from inside Emit()\n");
277 BaseSignal::EmitGuard::~EmitGuard()
285 bool BaseSignal::EmitGuard::ErrorOccurred()
287 // mFlag is NULL when Emit() is called during Emit()
288 return (nullptr == mFlag);