Support for the macOS platform
[platform/core/uifw/dali-adaptor.git] / dali / internal / system / macos / callback-manager-mac.mm
1 /*
2  * Copyright (c) 2020 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 #import <Foundation/Foundation.h>
19 #include "extern-definitions.h"
20
21 #include "callback-manager-mac.h"
22
23 #include <unordered_map>
24 #include <optional>
25
26 namespace
27 {
28 NSString *EventName = @"Dali::Internal::Adaptor::CallbackManager";
29 }
30
31 using Dali::Internal::Adaptor::CocoaCallbackManager;
32
33 // This is the observer that processes callback events
34 @interface CallbackObserver : NSObject
35 - (CallbackObserver *) init;
36 - (void) ReceiveCallback:(NSNotification *) aNotification;
37 @end
38
39 // DaliCallback is the Objective-C object holding the information to execute the callback
40 @interface DaliCallback : NSObject
41 - (DaliCallback *) initWithImpl:(CocoaCallbackManager::Impl*) impl
42                    withCallback:(Dali::CallbackBase *) callback;
43 - (void) ExecuteCallback;
44 @end
45
46 namespace Dali::Internal::Adaptor
47 {
48
49 namespace Detail
50 {
51 // Helper class to implement the callbacks containers
52 // The boolean value corresponds to the hasReturnValue parameter
53 struct CallbackContainer final : public std::unordered_map<CallbackBase*, bool>
54 {
55   using Parent = std::unordered_map<CallbackBase*, bool>;
56   using iterator = Parent::iterator;
57   using const_iterator = Parent::const_iterator;
58   using value_type = Parent::value_type;
59   using key_type = Parent::key_type;
60   using size_type = Parent::size_type;
61
62   ~CallbackContainer() { Clear(); }
63
64   void RemoveCallback(const_iterator item)
65   {
66     delete item->first;
67     erase(item);
68   }
69
70   bool RemoveCallback(CallbackBase *callback)
71   {
72     if (auto it(find(callback)); it != end())
73     {
74       RemoveCallback(it);
75       return true;
76     }
77
78     return false;
79   }
80
81   void Clear()
82   {
83     for (auto [cb, dummy]: *this)
84     {
85       delete cb;
86     }
87
88     clear();
89   }
90
91   // Execute the callback if it is present. The first item in the
92   // return value tells either the callback was executed or not.
93   // The second item gives the return value of the callback itself,
94   // if any.
95   std::pair<const_iterator, std::optional<bool>>
96   Execute(CallbackBase *callback) const
97   {
98     std::optional<bool> retValue;
99
100     auto it(find(callback));
101     if (it != end())
102     {
103       retValue = Execute(it);
104     }
105
106     return std::make_pair(it, retValue);
107   }
108
109   std::optional<bool> Execute(const_iterator it) const
110   {
111     auto [callback, hasReturnValue] = *it;
112     if (hasReturnValue)
113     {
114       return CallbackBase::ExecuteReturn<bool>(*callback);
115     }
116     else
117     {
118       CallbackBase::Execute(*callback);
119     }
120
121     return std::optional<bool>();
122   }
123 };
124 }
125
126 // Internal implementation of the CallbackManager
127 struct CocoaCallbackManager::Impl final
128 {
129   CFRunLoopObserverContext mObserverContext;
130
131   Detail::CallbackContainer mCallbacks, mIdleEntererCallbacks;
132   CFRef<CFRunLoopObserverRef> mIdleObserver;
133   CallbackObserver *mObserver;
134
135   Impl();
136   ~Impl();
137
138   Impl(const Impl &) = delete;
139   Impl &operator=(const Impl&) = delete;
140   Impl(const Impl &&) = delete;
141   Impl &operator=(const Impl&&) = delete;
142
143   bool ProcessIdle();
144   void EnqueueNotification(DaliCallback *callback) const;
145   inline bool AddIdleEntererCallback(CallbackBase *callback);
146   bool AddIdleCallback(CallbackBase *callback, bool hasReturnValue);
147
148 private:
149   static void IdleEnterObserverCallback(
150     CFRunLoopObserverRef observer,
151     CFRunLoopActivity activity,
152     void *info
153   );
154 };
155
156 CocoaCallbackManager::Impl::Impl()
157   : mObserverContext{0, this, nullptr, nullptr, 0}
158   // mIdleObserver is configured to receive a notification
159   // when to run loop is about to sleep
160   , mIdleObserver(MakeRef(CFRunLoopObserverCreate(
161       kCFAllocatorDefault,
162       kCFRunLoopBeforeWaiting,
163       true,
164       0,
165       IdleEnterObserverCallback,
166       &mObserverContext)))
167 {
168   CFRunLoopAddObserver(CFRunLoopGetMain(), mIdleObserver.get(), kCFRunLoopCommonModes);
169   mObserver = [[CallbackObserver alloc] init];
170 }
171
172 CocoaCallbackManager::Impl::~Impl()
173 {
174   CFRunLoopRemoveObserver(CFRunLoopGetMain(), mIdleObserver.get(), kCFRunLoopCommonModes);
175   auto *center = [NSNotificationCenter defaultCenter];
176   [center removeObserver:mObserver name:EventName object:nil];
177 }
178
179 bool CocoaCallbackManager::Impl::ProcessIdle()
180 {
181   auto ret = !mCallbacks.empty();
182   for (auto it(cbegin(mCallbacks)), e(cend(mCallbacks)); it != e; ++it)
183   {
184     if (!mCallbacks.Execute(it).value_or(false))
185     {
186       mCallbacks.RemoveCallback(it);
187     }
188   }
189
190   return ret;
191 }
192
193 void CocoaCallbackManager::Impl::EnqueueNotification(DaliCallback *callback) const
194 {
195   auto *notification = [NSNotification notificationWithName:EventName object:callback];
196   auto *queue = [NSNotificationQueue defaultQueue];
197   [queue enqueueNotification:notification postingStyle:NSPostWhenIdle coalesceMask:0 forModes:nil];
198 }
199
200 bool CocoaCallbackManager::Impl::AddIdleEntererCallback(CallbackBase *callback)
201 {
202   return mIdleEntererCallbacks.emplace(callback, true).second;
203 }
204
205 void CocoaCallbackManager::Impl::IdleEnterObserverCallback(
206   CFRunLoopObserverRef observer,
207   CFRunLoopActivity activity,
208   void *info
209 )
210 {
211   auto *pImpl = reinterpret_cast<Impl*>(info);
212
213   for (auto it(cbegin(pImpl->mIdleEntererCallbacks)),
214       e(cend(pImpl->mIdleEntererCallbacks)); it != e; ++it)
215   {
216     if (!pImpl->mIdleEntererCallbacks.Execute(it).value_or(false))
217     {
218       pImpl->mIdleEntererCallbacks.RemoveCallback(it);
219     }
220   }
221 }
222
223 bool CocoaCallbackManager::Impl::AddIdleCallback(
224     CallbackBase *callback, bool hasReturnValue)
225 {
226   if (mCallbacks.emplace(callback, hasReturnValue).second)
227   {
228     auto *daliCallback = [[DaliCallback alloc] initWithImpl:this
229                                                withCallback:callback];
230     EnqueueNotification(daliCallback);
231     return true;
232   }
233
234   return false;
235 }
236
237 // Creates a concrete interface for CallbackManager
238 CallbackManager* CallbackManager::New()
239 {
240   return new CocoaCallbackManager;
241 }
242
243 CocoaCallbackManager::CocoaCallbackManager()
244   : mImpl(std::make_unique<CocoaCallbackManager::Impl>())
245   , mRunning(false)
246 {
247 }
248
249 bool CocoaCallbackManager::AddIdleCallback(CallbackBase *callback, bool hasReturnValue)
250 {
251   return mRunning && mImpl->AddIdleCallback(callback, hasReturnValue);
252 }
253
254 void CocoaCallbackManager::RemoveIdleCallback(CallbackBase *callback)
255 {
256   mImpl->mCallbacks.RemoveCallback(callback);
257 }
258
259 bool CocoaCallbackManager::ProcessIdle()
260 {
261   return mImpl->ProcessIdle();
262 }
263
264 void CocoaCallbackManager::ClearIdleCallbacks()
265 {
266   mImpl->mCallbacks.Clear();
267 }
268
269 bool CocoaCallbackManager::AddIdleEntererCallback(CallbackBase* callback)
270 {
271   return mRunning && mImpl->AddIdleEntererCallback(callback);;
272 }
273
274 void CocoaCallbackManager::RemoveIdleEntererCallback(CallbackBase* callback)
275 {
276   mImpl->mIdleEntererCallbacks.RemoveCallback(callback);
277 }
278
279 void CocoaCallbackManager::Start()
280 {
281   DALI_ASSERT_DEBUG( mRunning == false );
282   mRunning = true;
283 }
284
285 void CocoaCallbackManager::Stop()
286 {
287   DALI_ASSERT_DEBUG( mRunning == true );
288   mRunning = false;
289 }
290
291 }
292
293 @implementation DaliCallback
294 {
295   CocoaCallbackManager::Impl *mImpl;
296   Dali::CallbackBase *mCallback;
297 }
298
299 - (DaliCallback *) initWithImpl:(CocoaCallbackManager::Impl *) impl
300                    withCallback:(Dali::CallbackBase *) callback
301 {
302   self = [super init];
303   if (self)
304   {
305     mImpl = impl;
306     mCallback = callback;
307   }
308   return self;
309 }
310
311 - (void) ExecuteCallback
312 {
313   // Look for the callback inside the list.
314   // If it is not there, then it was either called by ProcessIdle
315   // or was removed by RemoveCallback.
316   if (auto [iter, shouldKeep] = mImpl->mCallbacks.Execute(mCallback);
317       iter != mImpl->mCallbacks.end())
318   {
319     if (!shouldKeep.value_or(false))
320     {
321       mImpl->mCallbacks.RemoveCallback(iter);
322     }
323     else
324     {
325       mImpl->EnqueueNotification(self);
326     }
327   }
328 }
329 @end
330
331 @implementation CallbackObserver
332 - (CallbackObserver *) init
333 {
334   self = [super init];
335   if (self)
336   {
337     auto *center = [NSNotificationCenter defaultCenter];
338     [center addObserver:self
339                selector:@selector(ReceiveCallback:)
340                    name:EventName
341                  object:nil];
342   }
343   return self;
344 }
345
346 - (void) ReceiveCallback:(NSNotification *)aNotification
347 {
348   DaliCallback *callback = [aNotification object];
349   [callback ExecuteCallback];
350 }
351 @end