]> git.sesse.net Git - vlc/blob - projects/macosx/framework/Sources/VLCEventManager.m
b810b24686d9c6b00060094f4c52efb38ae7a18e
[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 @end
60
61 /**
62  * Provides a function for the main entry point for the dispatch thread.  It dispatches any messages that is queued.
63  * \param user_data Pointer to the VLCEventManager instance that instiated this thread.
64  */
65 static void * EventDispatcherMainLoop(void * user_data)
66 {
67     VLCEventManager * self = user_data;
68     
69     for(;;)
70     {
71         NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
72         message_t * message, * message_newer = NULL;
73         NSData * dataMessage;
74         NSInteger i;
75
76         /* Sleep a bit not to flood the interface */
77         usleep(300);
78
79         /* Wait for some data */
80
81         pthread_mutex_lock( [self queueLock] );
82         /* Wait until we have something on the queue */
83         while( [[self messageQueue] count] <= 0 )
84         {
85             pthread_cond_wait( [self signalData], [self queueLock] );
86         }
87
88         //if( [[self messageQueue] count] % 100 == 0 || [[self messageQueue] count] < 100 )
89           //  NSLog(@"[EVENT_MANAGER] On the stack we have %d elements", [[self messageQueue] count]);
90
91         /* Get the first object off the queue. */
92         dataMessage = [[[self messageQueue] lastObject] retain];    // Released in 'call'
93         [[self messageQueue] removeLastObject];
94         message = (message_t *)[dataMessage bytes];
95         
96         /* Remove duplicate notifications. */
97         if( message->type == VLCNotification )
98         {
99             for( i = [[self messageQueue] count]-1; i >= 0; i-- )
100             {
101                 message_newer = (message_t *)[(NSData *)[[self messageQueue] objectAtIndex:i] bytes];
102                 if( message_newer->type == VLCNotification &&
103                     message_newer->target == message->target &&
104                    [message_newer->u.name isEqualToString:message->u.name] )
105                 {
106                     [message_newer->target release];
107                     [message_newer->u.name release];
108                     [[self messageQueue] removeObjectAtIndex:i];
109                 }
110             }
111         }
112         else if( message->type == VLCObjectMethodWithArrayArg )
113         {
114             NSMutableArray * newArg = nil;
115
116             /* Collapse messages that takes array arg by sending one bigger array */
117             for( i = [[self messageQueue] count]-1; i >= 0; i-- )
118             {
119                 message_newer = (message_t *)[(NSData *)[[self messageQueue] objectAtIndex: i] bytes];
120                 if( message_newer->type == VLCObjectMethodWithArrayArg &&
121                     message_newer->target == message->target && 
122                     message_newer->sel == message->sel )
123                 {
124                     if(!newArg)
125                     {
126                         newArg = [NSMutableArray arrayWithArray:message->u.object];
127                         [message->u.object release];
128                     }
129                     
130                     [newArg addObjectsFromArray:message_newer->u.object];
131                     [message_newer->target release];
132                     [message_newer->u.object release];
133                     [[self messageQueue] removeObjectAtIndex:i];
134                 }
135                 /* It shouldn be a good idea not to collapse event with other kind of event in-between.
136                  * This could be particulary problematic when the same object receive two related events
137                  * (for instance Added and Removed).
138                  * Ignore for now only if target is the same */
139                 else if( message_newer->target == message->target )
140                     break;
141             }
142             
143             if( newArg )
144                 message->u.object = [newArg retain];
145         }
146         
147         pthread_mutex_unlock( [self queueLock] );
148
149         if( message->type == VLCNotification )
150             [self performSelectorOnMainThread:@selector(callDelegateOfObjectAndSendNotificationWithArgs:) 
151                                    withObject:dataMessage
152                                 waitUntilDone: NO];
153         else
154             [self performSelectorOnMainThread:@selector(callObjectMethodWithArgs:) 
155                                    withObject:dataMessage
156                                 waitUntilDone: YES];
157
158         [pool drain];
159     }
160     return nil;
161 }
162
163 @implementation VLCEventManager
164 + (id)sharedManager
165 {
166     static VLCEventManager * defaultManager = NULL;
167     
168     /* We do want a lock here to avoid leaks */
169     if ( !defaultManager )
170     {
171         defaultManager = [[VLCEventManager alloc] init];
172     }
173
174     return defaultManager;
175 }
176
177 - (void)dummy
178 {
179     /* Put Cocoa in multithreaded mode by calling a dummy function */
180 }
181
182 - (id)init
183 {
184     if( self = [super init] )
185     {
186         if(![NSThread isMultiThreaded])
187         {
188             [NSThread detachNewThreadSelector:@selector(dummy) toTarget:self withObject:nil];
189             NSAssert([NSThread isMultiThreaded], @"Can't put Cocoa in multithreaded mode");
190         }
191
192         pthread_mutex_init( &queueLock, NULL );
193         pthread_cond_init( &signalData, NULL );
194         pthread_create( &dispatcherThread, NULL, EventDispatcherMainLoop, self );
195         messageQueue = [[NSMutableArray alloc] initWithCapacity:10];
196     }
197     return self;
198 }
199
200 - (void)dealloc
201 {
202     pthread_kill( dispatcherThread, SIGKILL );
203     pthread_join( dispatcherThread, NULL );
204
205     [messageQueue release];
206     [super dealloc];
207 }
208
209 - (void)callOnMainThreadDelegateOfObject:(id)aTarget withDelegateMethod:(SEL)aSelector withNotificationName: (NSString *)aNotificationName
210 {
211     /* Don't send on main thread before this gets sorted out */
212     NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
213     
214     message_t message = 
215     { 
216         [aTarget retain], 
217         aSelector, 
218         [aNotificationName retain], 
219         VLCNotification 
220     };
221
222     if( [NSThread isMainThread] )
223     {
224         [self callDelegateOfObjectAndSendNotificationWithArgs:[[NSData dataWithBytes:&message length:sizeof(message_t)] retain] /* released in the call */];
225     } 
226     else 
227     {
228         pthread_mutex_lock( [self queueLock] );
229         [[self messageQueue] insertObject:[NSData dataWithBytes:&message length:sizeof(message_t)] atIndex:0];
230         pthread_cond_signal( [self signalData] );
231         pthread_mutex_unlock( [self queueLock] );
232     }
233     
234     [pool drain];
235 }
236
237 - (void)callOnMainThreadObject:(id)aTarget withMethod:(SEL)aSelector withArgumentAsObject: (id)arg
238 {
239     NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
240     message_t message = 
241     { 
242         [aTarget retain], 
243         aSelector, 
244         [arg retain], 
245         [arg isKindOfClass:[NSArray class]] ? VLCObjectMethodWithArrayArg : VLCObjectMethodWithObjectArg 
246     };
247
248     pthread_mutex_lock( [self queueLock] );
249     [[self messageQueue] insertObject:[NSData dataWithBytes:&message length:sizeof(message_t)] atIndex:0];
250     pthread_cond_signal( [self signalData] );
251     pthread_mutex_unlock( [self queueLock] );
252     
253     [pool drain];
254 }
255 @end
256
257 @implementation VLCEventManager (Private)
258 - (void)callDelegateOfObjectAndSendNotificationWithArgs:(NSData*)data
259 {
260     message_t * message = (message_t *)[data bytes];
261
262     [self callDelegateOfObject:message->target withDelegateMethod:message->sel withNotificationName:message->u.name];
263     [message->u.name release];
264     [message->target release];
265     [data release];
266 }
267
268 - (void)callObjectMethodWithArgs:(NSData*)data
269 {
270     message_t * message = (message_t *)[data bytes];
271     void (*method)(id, SEL, id) = (void (*)(id, SEL, id))[message->target methodForSelector: message->sel];
272
273     method( message->target, message->sel, message->u.object);
274     [message->u.object release];
275     [message->target release];
276     [data release];
277 }
278
279 - (void)callDelegateOfObject:(id) aTarget withDelegateMethod:(SEL)aSelector withNotificationName: (NSString *)aNotificationName
280 {
281     [[NSNotificationCenter defaultCenter] postNotification: [NSNotification notificationWithName:aNotificationName object:aTarget]];
282     
283     if (![aTarget delegate] || ![[aTarget delegate] respondsToSelector:aSelector])
284         return;
285     
286     void (*method)(id, SEL, id) = (void (*)(id, SEL, id))[[aTarget delegate] methodForSelector: aSelector];
287     method( [aTarget delegate], aSelector, [NSNotification notificationWithName:aNotificationName object:aTarget]);
288 }
289
290 - (NSMutableArray *)messageQueue
291 {
292     return messageQueue;
293 }
294
295 - (pthread_cond_t *)signalData
296 {
297     return &signalData;
298 }
299
300 - (pthread_mutex_t *)queueLock
301 {
302     return &queueLock;
303 }
304 @end