0729fab27b2eb232a03817b354febe3cefb13f09
[platform/core/uifw/dali-core.git] / dali / public-api / signals / base-signal.cpp
1 /*
2  * Copyright (c) 2022 Samsung Electronics Co., Ltd.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  *
16  */
17
18 // CLASS HEADER
19 #include <dali/public-api/signals/base-signal.h>
20
21 // EXTERNAL INCLUDES
22 #include <map>
23
24 // INTERNAL INCLUDES
25 #include <dali/integration-api/debug.h>
26
27 namespace
28 {
29 struct CallbackBasePtrCompare
30 {
31   bool operator()(const Dali::CallbackBase* lhs, const Dali::CallbackBase* rhs) const noexcept
32   {
33     const void* lhsFunctionPtr = reinterpret_cast<void*>(lhs->mFunction);
34     const void* rhsFunctionPtr = reinterpret_cast<void*>(rhs->mFunction);
35     if(lhsFunctionPtr < rhsFunctionPtr)
36     {
37       return true;
38     }
39     else if(lhsFunctionPtr > rhsFunctionPtr)
40     {
41       return false;
42     }
43
44     if(lhs->mImpl.mObjectPointer < rhs->mImpl.mObjectPointer)
45     {
46       return true;
47     }
48     else
49     {
50       return false;
51     }
52   }
53 };
54 } // unnamed namespace
55
56 namespace Dali
57 {
58 /**
59  * @brief Extra struct for callback base cache.
60  */
61 struct BaseSignal::Impl
62 {
63   Impl()  = default;
64   ~Impl() = default;
65
66   /**
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.
70    */
71   std::map<const CallbackBase*, std::list<SignalConnection>::iterator, CallbackBasePtrCompare> mCallbackCache;
72 };
73
74 BaseSignal::BaseSignal()
75 : mCacheImpl(new BaseSignal::Impl()),
76   mEmittingFlag(false)
77 {
78 }
79
80 BaseSignal::~BaseSignal()
81 {
82   // We can't assert in a destructor
83   if(mEmittingFlag)
84   {
85     DALI_LOG_ERROR("Invalid destruction of Signal during Emit()\n");
86
87     // Set the signal deletion flag as well if set
88     if(mSignalDeleted)
89     {
90       *mSignalDeleted = true;
91     }
92   }
93
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)
97   {
98     auto& connection = *iter;
99
100     // Note that values are set to NULL in DeleteConnection
101     if(connection)
102     {
103       connection.Disconnect(this);
104     }
105   }
106
107   delete mCacheImpl;
108 }
109
110 void BaseSignal::OnConnect(CallbackBase* callback)
111 {
112   DALI_ASSERT_ALWAYS(nullptr != callback && "Invalid member function pointer passed to Connect()");
113
114   auto iter = FindCallback(callback);
115
116   // Don't double-connect the same callback
117   if(iter == mSignalConnections.end())
118   {
119     auto newIter = mSignalConnections.insert(mSignalConnections.end(), SignalConnection(callback));
120
121     // Store inserted iterator for this callback
122     mCacheImpl->mCallbackCache[callback] = newIter;
123   }
124   else
125   {
126     // clean-up required
127     delete callback;
128   }
129 }
130
131 void BaseSignal::OnDisconnect(CallbackBase* callback)
132 {
133   DALI_ASSERT_ALWAYS(nullptr != callback && "Invalid member function pointer passed to Disconnect()");
134
135   auto iter = FindCallback(callback);
136
137   if(iter != mSignalConnections.end())
138   {
139     DeleteConnection(iter);
140   }
141
142   // call back is a temporary created to find which slot should be disconnected.
143   delete callback;
144 }
145
146 void BaseSignal::OnConnect(ConnectionTrackerInterface* tracker, CallbackBase* callback)
147 {
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()");
150
151   auto iter = FindCallback(callback);
152
153   // Don't double-connect the same callback
154   if(iter == mSignalConnections.end())
155   {
156     auto newIter = mSignalConnections.insert(mSignalConnections.end(), {tracker, callback});
157
158     // Store inserted iterator for this callback
159     mCacheImpl->mCallbackCache[callback] = newIter;
160
161     // Let the connection tracker know that a connection between a signal and a slot has been made.
162     tracker->SignalConnected(this, callback);
163   }
164   else
165   {
166     // clean-up required
167     delete callback;
168   }
169 }
170
171 void BaseSignal::OnDisconnect(ConnectionTrackerInterface* tracker, CallbackBase* callback)
172 {
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()");
175
176   auto iter = FindCallback(callback);
177
178   if(iter != mSignalConnections.end())
179   {
180     // temporary pointer to disconnected callback
181     // Note that (*iter).GetCallback() != callback is possible.
182     CallbackBase* disconnectedCallback = (*iter).GetCallback();
183
184     // close the signal side connection first.
185     DeleteConnection(iter);
186
187     // close the slot side connection
188     tracker->SignalDisconnected(this, disconnectedCallback);
189   }
190
191   // call back is a temporary created to find which slot should be disconnected.
192   delete callback;
193 }
194
195 // for SlotObserver::SlotDisconnected
196 void BaseSignal::SlotDisconnected(CallbackBase* callback)
197 {
198   DALI_ASSERT_ALWAYS(nullptr != callback && "Invalid callback function passed to SlotObserver::SlotDisconnected()");
199
200   auto iter = FindCallback(callback);
201   if(DALI_LIKELY(iter != mSignalConnections.end()))
202   {
203     DeleteConnection(iter);
204     return;
205   }
206
207   DALI_ABORT("Callback lost in SlotDisconnected()");
208 }
209
210 std::list<SignalConnection>::iterator BaseSignal::FindCallback(CallbackBase* callback) noexcept
211 {
212   const auto& convertorIter = mCacheImpl->mCallbackCache.find(callback);
213
214   if(convertorIter != mCacheImpl->mCallbackCache.end())
215   {
216     const auto& iter = convertorIter->second; // std::list<SignalConnection>::iterator
217
218     if(*iter) // the value of iterator can be null.
219     {
220       if(*(iter->GetCallback()) == *callback)
221       {
222         return iter;
223       }
224     }
225   }
226   return mSignalConnections.end();
227 }
228
229 void BaseSignal::DeleteConnection(std::list<SignalConnection>::iterator iter)
230 {
231   // Erase cache first.
232   mCacheImpl->mCallbackCache.erase(iter->GetCallback());
233
234   if(mEmittingFlag)
235   {
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.
239     (*iter) = {nullptr};
240     ++mNullConnections;
241   }
242   else
243   {
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);
247   }
248 }
249
250 void BaseSignal::CleanupConnections()
251 {
252   if(!mSignalConnections.empty())
253   {
254     //Remove Signals that are already markeed nullptr.
255     mSignalConnections.remove_if([](auto& elem) { return (elem) ? false : true; });
256   }
257   mNullConnections = 0;
258 }
259
260 // BaseSignal::EmitGuard
261
262 BaseSignal::EmitGuard::EmitGuard(bool& flag)
263 : mFlag(nullptr)
264 {
265   if(!flag)
266   {
267     mFlag = &flag;
268     flag  = true;
269   }
270   else
271   {
272     // mFlag is NULL when Emit() is called during Emit()
273     DALI_LOG_ERROR("Cannot call Emit() from inside Emit()\n");
274   }
275 }
276
277 BaseSignal::EmitGuard::~EmitGuard()
278 {
279   if(mFlag)
280   {
281     *mFlag = false;
282   }
283 }
284
285 bool BaseSignal::EmitGuard::ErrorOccurred()
286 {
287   // mFlag is NULL when Emit() is called during Emit()
288   return (nullptr == mFlag);
289 }
290
291 } // namespace Dali