2 * Copyright (c) 2022 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>
22 #include <unordered_map>
25 #include <dali/integration-api/debug.h>
29 struct CallbackBasePtrHash
31 std::size_t operator()(const Dali::CallbackBase* callback) const noexcept
33 std::size_t functionHash = reinterpret_cast<std::size_t>(reinterpret_cast<void*>(callback->mFunction));
34 std::size_t objectHash = reinterpret_cast<std::size_t>(reinterpret_cast<void*>(callback->mImpl.mObjectPointer));
35 return functionHash ^ objectHash;
38 struct CallbackBasePtrEqual
40 bool operator()(const Dali::CallbackBase* lhs, const Dali::CallbackBase* rhs) const noexcept
42 return (*lhs) == (*rhs);
45 } // unnamed namespace
50 * @brief Extra struct for callback base cache.
52 struct BaseSignal::Impl
58 * @brief Get the iterator of connections list by the callback base pointer.
59 * Note that we should compare the 'value' of callback, not pointer.
60 * So, we need to define custom hash & compare functor of callback base pointer.
62 std::unordered_map<const CallbackBase*, std::list<SignalConnection>::iterator, CallbackBasePtrHash, CallbackBasePtrEqual> mCallbackCache;
65 BaseSignal::BaseSignal()
66 : mCacheImpl(new BaseSignal::Impl()),
71 BaseSignal::~BaseSignal()
73 // We can't assert in a destructor
76 DALI_LOG_ERROR("Invalid destruction of Signal during Emit()\n");
78 // Set the signal deletion flag as well if set
81 *mSignalDeleted = true;
85 // The signal is being destroyed. We have to inform any slots
86 // that are connected, that the signal is dead.
87 for(auto iter = mSignalConnections.begin(), iterEnd = mSignalConnections.end(); iter != iterEnd; ++iter)
89 auto& connection = *iter;
91 // Note that values are set to NULL in DeleteConnection
94 connection.Disconnect(this);
101 void BaseSignal::OnConnect(CallbackBase* callback)
103 DALI_ASSERT_ALWAYS(nullptr != callback && "Invalid member function pointer passed to Connect()");
105 auto iter = FindCallback(callback);
107 // Don't double-connect the same callback
108 if(iter == mSignalConnections.end())
110 auto newIter = mSignalConnections.insert(mSignalConnections.end(), SignalConnection(callback));
112 // Store inserted iterator for this callback
113 mCacheImpl->mCallbackCache[callback] = newIter;
122 void BaseSignal::OnDisconnect(CallbackBase* callback)
124 DALI_ASSERT_ALWAYS(nullptr != callback && "Invalid member function pointer passed to Disconnect()");
126 auto iter = FindCallback(callback);
128 if(iter != mSignalConnections.end())
130 DeleteConnection(iter);
133 // call back is a temporary created to find which slot should be disconnected.
137 void BaseSignal::OnConnect(ConnectionTrackerInterface* tracker, CallbackBase* callback)
139 DALI_ASSERT_ALWAYS(nullptr != tracker && "Invalid ConnectionTrackerInterface pointer passed to Connect()");
140 DALI_ASSERT_ALWAYS(nullptr != callback && "Invalid member function pointer passed to Connect()");
142 auto iter = FindCallback(callback);
144 // Don't double-connect the same callback
145 if(iter == mSignalConnections.end())
147 auto newIter = mSignalConnections.insert(mSignalConnections.end(), {tracker, callback});
149 // Store inserted iterator for this callback
150 mCacheImpl->mCallbackCache[callback] = newIter;
152 // Let the connection tracker know that a connection between a signal and a slot has been made.
153 tracker->SignalConnected(this, callback);
162 void BaseSignal::OnDisconnect(ConnectionTrackerInterface* tracker, CallbackBase* callback)
164 DALI_ASSERT_ALWAYS(nullptr != tracker && "Invalid ConnectionTrackerInterface pointer passed to Disconnect()");
165 DALI_ASSERT_ALWAYS(nullptr != callback && "Invalid member function pointer passed to Disconnect()");
167 auto iter = FindCallback(callback);
169 if(iter != mSignalConnections.end())
171 // temporary pointer to disconnected callback
172 // Note that (*iter).GetCallback() != callback is possible.
173 CallbackBase* disconnectedCallback = (*iter).GetCallback();
175 // close the signal side connection first.
176 DeleteConnection(iter);
178 // close the slot side connection
179 tracker->SignalDisconnected(this, disconnectedCallback);
182 // call back is a temporary created to find which slot should be disconnected.
186 // for SlotObserver::SlotDisconnected
187 void BaseSignal::SlotDisconnected(CallbackBase* callback)
189 DALI_ASSERT_ALWAYS(nullptr != callback && "Invalid callback function passed to SlotObserver::SlotDisconnected()");
191 auto iter = FindCallback(callback);
192 if(DALI_LIKELY(iter != mSignalConnections.end()))
194 DeleteConnection(iter);
198 DALI_ABORT("Callback lost in SlotDisconnected()");
201 std::list<SignalConnection>::iterator BaseSignal::FindCallback(CallbackBase* callback) noexcept
203 const auto& convertorIter = mCacheImpl->mCallbackCache.find(callback);
205 if(convertorIter != mCacheImpl->mCallbackCache.end())
207 const auto& iter = convertorIter->second; // std::list<SignalConnection>::iterator
209 if(*iter) // the value of iterator can be null.
211 if(*(iter->GetCallback()) == *callback)
217 return mSignalConnections.end();
220 void BaseSignal::DeleteConnection(std::list<SignalConnection>::iterator iter)
222 // Erase cache first.
223 mCacheImpl->mCallbackCache.erase(iter->GetCallback());
227 // IMPORTANT - do not remove from items from mSignalConnections, reset instead.
228 // Signal Emit() methods require that connection count is not reduced while iterating
229 // i.e. DeleteConnection can be called from within callbacks, while iterating through mSignalConnections.
235 // If application connects and disconnects without the signal never emitting,
236 // the mSignalConnections vector keeps growing and growing as CleanupConnections() is done from Emit.
237 mSignalConnections.erase(iter);
241 void BaseSignal::CleanupConnections()
243 if(!mSignalConnections.empty())
245 //Remove Signals that are already markeed nullptr.
246 mSignalConnections.remove_if([](auto& elem) { return (elem) ? false : true; });
248 mNullConnections = 0;
251 // BaseSignal::EmitGuard
253 BaseSignal::EmitGuard::EmitGuard(bool& flag)
263 // mFlag is NULL when Emit() is called during Emit()
264 DALI_LOG_ERROR("Cannot call Emit() from inside Emit()\n");
268 BaseSignal::EmitGuard::~EmitGuard()
276 bool BaseSignal::EmitGuard::ErrorOccurred()
278 // mFlag is NULL when Emit() is called during Emit()
279 return (nullptr == mFlag);