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