]> git.sesse.net Git - vlc/blob - projects/macosx/framework/Sources/VLCEventManager.m
VLCKit: Import MobileVLCKit.
[vlc] / projects / macosx / framework / Sources / VLCEventManager.m
1 /*****************************************************************************
2  * VLCEventManager.m: VLCKit.framework VLCEventManager implementation
3  *****************************************************************************
4  * Copyright (C) 2007 Pierre d'Herbemont
5  * Copyright (C) 2007 the VideoLAN team
6  * $Id$
7  *
8  * Authors: Pierre d'Herbemont <pdherbemont # videolan.org>
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23  *****************************************************************************/
24
25 #import "VLCEventManager.h"
26 #import <pthread.h>
27
28 /**
29  * Defines the type of interthread message on the queue.
30  */
31 typedef enum
32 {
33     VLCNotification,                //< Standard NSNotification.
34     VLCObjectMethodWithObjectArg,   //< Method with an object argument.
35     VLCObjectMethodWithArrayArg     //< Method with an array argument.
36 } message_type_t;
37
38 /**
39  * Data structured used to enqueue messages onto the queue.
40  */
41 typedef struct {
42     id target;                      //< Target object that should receive the message (retained until method is called).
43     SEL sel;                        //< A selector that identifies the message to be sent to the target.
44     union u                         //< Object could either be a VLCNotification or other.
45     {
46         NSString * name;            //< Name to be used for NSNotification
47         id object;                  //< Object argument to pass to the target via the selector.
48     } u;
49     message_type_t type;            //< Type of queued message.
50 } message_t;
51
52 @interface VLCEventManager (Private)
53 - (void)callDelegateOfObjectAndSendNotificationWithArgs:(NSData*)data;
54 - (void)callObjectMethodWithArgs:(NSData*)data;
55 - (void)callDelegateOfObject:(id) aTarget withDelegateMethod:(SEL)aSelector withNotificationName:(NSString *)aNotificationName;
56 - (pthread_cond_t *)signalData;
57 - (pthread_mutex_t *)queueLock;
58 - (NSMutableArray *)messageQueue;
59 - (NSMutableArray *)pendingMessagesOnMainThread;
60 - (NSLock *)pendingMessagesLock;
61
62 - (void)addMessageToHandleOnMainThread:(NSData *)messageAsData;
63 @end
64
65 /**
66  * Provides a function for the main entry point for the dispatch thread.  It dispatches any messages that is queued.
67  * \param user_data Pointer to the VLCEventManager instance that instiated this thread.
68  */
69 static void * EventDispatcherMainLoop(void * user_data)
70 {
71     VLCEventManager * self = user_data;
72
73     for(;;)
74     {
75         NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
76         message_t * message, * message_newer = NULL;
77         NSData * dataMessage;
78         NSInteger i;
79
80         /* Sleep a bit not to flood the interface */
81         usleep(300);
82
83         /* Wait for some data */
84
85         pthread_mutex_lock([self queueLock]);
86         /* Wait until we have something on the queue */
87         while( [[self messageQueue] count] <= 0)
88         {
89             pthread_cond_wait([self signalData], [self queueLock]);
90         }
91
92         //if( [[self messageQueue] count] % 100 == 0 || [[self messageQueue] count] < 100 )
93           //  NSLog(@"[EVENT_MANAGER] On the stack we have %d elements", [[self messageQueue] count]);
94
95         /* Get the first object off the queue. */
96         dataMessage = [[[self messageQueue] lastObject] retain];    // Released in 'call'
97         [[self messageQueue] removeLastObject];
98         message = (message_t *)[dataMessage bytes];
99
100         /* Remove duplicate notifications. */
101         if( message->type == VLCNotification )
102         {
103             for( i = [[self messageQueue] count]-1; i >= 0; i-- )
104             {
105                 message_newer = (message_t *)[(NSData *)[[self messageQueue] objectAtIndex:i] bytes];
106                 if( message_newer->type == VLCNotification &&
107                     message_newer->target == message->target &&
108                    [message_newer->u.name isEqualToString:message->u.name] )
109                 {
110                     [message_newer->u.name release];
111                     [[self messageQueue] removeObjectAtIndex:i];
112                 }
113             }
114         }
115         else if( message->type == VLCObjectMethodWithArrayArg )
116         {
117             NSMutableArray * newArg = nil;
118
119             /* Collapse messages that takes array arg by sending one bigger array */
120             for(i = [[self messageQueue] count] - 1; i >= 0; i--)
121             {
122                 message_newer = (message_t *)[(NSData *)[[self messageQueue] objectAtIndex: i] bytes];
123                 if (message_newer->type == VLCObjectMethodWithArrayArg &&
124                     message_newer->target == message->target &&
125                     message_newer->sel == message->sel)
126                 {
127                     if (!newArg)
128                     {
129                         newArg = [NSMutableArray arrayWithArray:message->u.object];
130                         [message->u.object release];
131                     }
132
133                     [newArg addObjectsFromArray:message_newer->u.object];
134                     [message_newer->u.object release];
135                     [[self messageQueue] removeObjectAtIndex:i];
136                 }
137                 /* It shouldn be a good idea not to collapse event with other kind of event in-between.
138                  * This could be particulary problematic when the same object receive two related events
139                  * (for instance Added and Removed).
140                  * Ignore for now only if target is the same */
141                 else if( message_newer->target == message->target )
142                     break;
143             }
144
145             if (newArg)
146                 message->u.object = [newArg retain];
147         }
148
149         [self addMessageToHandleOnMainThread:dataMessage];
150
151         pthread_mutex_unlock([self queueLock]);
152
153
154         if( message->type == VLCNotification )
155             [self performSelectorOnMainThread:@selector(callDelegateOfObjectAndSendNotificationWithArgs:)
156                                    withObject:dataMessage
157                                 waitUntilDone: NO];
158         else
159             [self performSelectorOnMainThread:@selector(callObjectMethodWithArgs:)
160                                    withObject:dataMessage
161                                 waitUntilDone: YES];
162
163         [pool drain];
164     }
165     return nil;
166 }
167
168 @implementation VLCEventManager
169 + (id)sharedManager
170 {
171     static VLCEventManager *defaultManager = NULL;
172
173     /* We do want a lock here to avoid leaks */
174     if (!defaultManager)
175         defaultManager = [[VLCEventManager alloc] init];
176
177     return defaultManager;
178 }
179
180 - (void)dummy
181 {
182     /* Put Cocoa in multithreaded mode by calling a dummy function */
183 }
184
185 - (id)init
186 {
187     if(self = [super init])
188     {
189         if(![NSThread isMultiThreaded])
190         {
191             [NSThread detachNewThreadSelector:@selector(dummy) toTarget:self withObject:nil];
192             NSAssert([NSThread isMultiThreaded], @"Can't put Cocoa in multithreaded mode");
193         }
194
195         pthread_mutex_init(&queueLock, NULL);
196         pthread_cond_init(&signalData, NULL);
197         pthread_create(&dispatcherThread, NULL, EventDispatcherMainLoop, self);
198         messageQueue = [[NSMutableArray alloc] initWithCapacity:10];
199         pendingMessagesOnMainThread = [[NSMutableArray alloc] initWithCapacity:10];
200         pendingMessagesLock = [[NSLock alloc] init];
201     }
202     return self;
203 }
204
205 - (void)dealloc
206 {
207     pthread_kill(dispatcherThread, SIGKILL);
208     pthread_join(dispatcherThread, NULL);
209
210     [messageQueue release];
211     [pendingMessagesOnMainThread release];
212     [super dealloc];
213 }
214
215 - (void)callOnMainThreadDelegateOfObject:(id)aTarget withDelegateMethod:(SEL)aSelector withNotificationName:(NSString *)aNotificationName
216 {
217     /* Don't send on main thread before this gets sorted out */
218     NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
219
220     message_t message =
221     {
222         aTarget,
223         aSelector,
224         [aNotificationName retain],
225         VLCNotification
226     };
227
228     if( [NSThread isMainThread] )
229     {
230         NSData *message = [NSData dataWithBytes:&message length:sizeof(message_t)];
231         [self addMessageToHandleOnMainThread:message];
232         [self callDelegateOfObjectAndSendNotificationWithArgs:[message retain] /* released in the call */];
233     }
234     else
235     {
236         pthread_mutex_lock([self queueLock]);
237         [[self messageQueue] insertObject:[NSData dataWithBytes:&message length:sizeof(message_t)] atIndex:0];
238         pthread_cond_signal([self signalData]);
239         pthread_mutex_unlock([self queueLock]);
240     }
241
242     [pool drain];
243 }
244
245 - (void)callOnMainThreadObject:(id)aTarget withMethod:(SEL)aSelector withArgumentAsObject:(id)arg
246 {
247     NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
248     message_t message =
249     {
250         aTarget,
251         aSelector,
252         [arg retain],
253         [arg isKindOfClass:[NSArray class]] ? VLCObjectMethodWithArrayArg : VLCObjectMethodWithObjectArg
254     };
255
256     pthread_mutex_lock([self queueLock]);
257     [[self messageQueue] insertObject:[NSData dataWithBytes:&message length:sizeof(message_t)] atIndex:0];
258     pthread_cond_signal([self signalData]);
259     pthread_mutex_unlock([self queueLock]);
260
261     [pool drain];
262 }
263
264 - (void)cancelCallToObject:(id)target
265 {
266
267     // Remove all queued message
268     pthread_mutex_lock([self queueLock]);
269     [pendingMessagesLock lock];
270
271     NSMutableArray *queue = [self messageQueue];
272     for (int i = [queue count] - 1; i >= 0; i--) {
273         NSData *data = [queue objectAtIndex:i];
274         message_t *message = (message_t *)[data bytes];
275         if (message->target == target) {
276             [queue removeObjectAtIndex:i];
277         }
278     }
279
280     // Remove all pending messages
281     NSMutableArray *messages = pendingMessagesOnMainThread;
282     for (int i = [messages count] - 1; i >= 0; i--) {
283         NSData *data = [messages objectAtIndex:i];
284         message_t *message = (message_t *)[data bytes];
285
286         if (message->target == target) {
287             [messages removeObjectAtIndex:i];
288
289         }
290     }
291
292     [pendingMessagesLock unlock];
293     pthread_mutex_unlock([self queueLock]);
294
295 }
296 @end
297
298 @implementation VLCEventManager (Private)
299
300 - (void)addMessageToHandleOnMainThread:(NSData *)messageAsData
301 {
302     [pendingMessagesLock lock];
303     [pendingMessagesOnMainThread addObject:messageAsData];
304     [pendingMessagesLock unlock];
305
306 }
307
308 - (BOOL)markMessageHandledOnMainThreadIfExists:(NSData *)messageAsData
309 {
310     [pendingMessagesLock lock];
311     BOOL cancelled = ![pendingMessagesOnMainThread containsObject:messageAsData];
312     if (!cancelled)
313         [pendingMessagesOnMainThread removeObject:messageAsData];
314     [pendingMessagesLock unlock];
315
316     return !cancelled;
317 }
318
319 - (void)callDelegateOfObjectAndSendNotificationWithArgs:(NSData*)data
320 {
321     message_t * message = (message_t *)[data bytes];
322
323     // Check that we were not cancelled, ie, target was released
324     if ([self markMessageHandledOnMainThreadIfExists:data]) {
325         [self callDelegateOfObject:message->target withDelegateMethod:message->sel withNotificationName:message->u.name];
326     }
327
328     [message->u.name release];
329     [data release];
330 }
331
332 - (void)callObjectMethodWithArgs:(NSData*)data
333 {
334     message_t * message = (message_t *)[data bytes];
335
336     // Check that we were not cancelled
337     if ([self markMessageHandledOnMainThreadIfExists:data]) {
338         void (*method)(id, SEL, id) = (void (*)(id, SEL, id))[message->target methodForSelector: message->sel];
339         method(message->target, message->sel, message->u.object);
340     }
341
342     [message->u.object release];
343     [data release];
344 }
345
346 - (void)callDelegateOfObject:(id)aTarget withDelegateMethod:(SEL)aSelector withNotificationName:(NSString *)aNotificationName
347 {
348     [[NSNotificationCenter defaultCenter] postNotification: [NSNotification notificationWithName:aNotificationName object:aTarget]];
349
350     id delegate = [aTarget delegate];
351     if (!delegate || ![delegate respondsToSelector:aSelector])
352         return;
353
354     void (*method)(id, SEL, id) = (void (*)(id, SEL, id))[[aTarget delegate] methodForSelector: aSelector];
355     method([aTarget delegate], aSelector, [NSNotification notificationWithName:aNotificationName object:aTarget]);
356 }
357
358 - (NSMutableArray *)messageQueue
359 {
360     return messageQueue;
361 }
362
363 - (NSMutableArray *)pendingMessagesOnMainThread
364 {
365     return pendingMessagesOnMainThread;
366 }
367
368 - (NSLock *)pendingMessagesLock
369 {
370     return pendingMessagesLock;
371 }
372
373
374 - (pthread_cond_t *)signalData
375 {
376     return &signalData;
377 }
378
379 - (pthread_mutex_t *)queueLock
380 {
381     return &queueLock;
382 }
383 @end