MacOSX/Framework/VLCEventManager: Code cleanup. (Patch by Enrique Osuna).
[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 static VLCEventManager * defaultManager = NULL;
30
31 enum message_type_t
32 {
33     VLCNotification,
34     VLCObjectMethodWithObjectArg,
35     VLCObjectMethodWithArrayArg
36 };
37
38 struct message {
39     id target;
40     SEL sel;
41     union u
42     {
43         NSString * name;
44         id object;
45     } u;
46     enum message_type_t type;
47 };
48
49 @interface VLCEventManager (Private)
50 - (void)callDelegateOfObjectAndSendNotificationWithArgs:(NSData*)data;
51 - (void)callObjectMethodWithArgs:(NSData*)data;
52 - (void)callDelegateOfObject:(id) aTarget withDelegateMethod:(SEL)aSelector withNotificationName: (NSString *)aNotificationName;
53 - (pthread_cond_t *)signalData;
54 - (pthread_mutex_t *)queueLock;
55 - (NSMutableArray *)messageQueue;
56 @end
57
58 static void * EventDispatcherMainLoop(void * user_data)
59 {
60     VLCEventManager * self = user_data;
61     
62     for(;;)
63     {
64         NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
65         struct message * message, * message_newer = NULL;
66         NSData * dataMessage;
67         int i;
68
69         /* Sleep a bit not to flood the interface */
70         msleep(300);
71         /* Wait for some data */
72
73         pthread_mutex_lock( [self queueLock] );
74         /* Wait until we have something on the queue */
75         while( [[self messageQueue] count] <= 0 )
76         {
77             pthread_cond_wait( [self signalData], [self queueLock] );
78         }
79
80         //if( [[self messageQueue] count] % 100 == 0 || [[self messageQueue] count] < 100 )
81           //  NSLog(@"[EVENT_MANAGER] On the stack we have %d elements", [[self messageQueue] count]);
82
83         message = (struct message *)[(NSData *)[[self messageQueue] lastObject] bytes];
84         
85         /* Don't send the same notification twice */
86         if( message->type == VLCNotification )
87         {
88             for( i = 0; i < [[self messageQueue] count]-1; i++ )
89             {
90                 message_newer = (struct message *)[(NSData *)[[self messageQueue] objectAtIndex:i] bytes];
91                 if( message_newer->type == VLCNotification &&
92                     message_newer->target == message->target &&
93                    [message_newer->u.name isEqualToString:message->u.name] )
94                 {
95                     [message_newer->target release];
96                     [message_newer->u.name release];
97                     [[self messageQueue] removeObjectAtIndex:i];
98                     i--;
99                     continue;
100                 }
101             }
102         }
103         else if( message->type == VLCObjectMethodWithArrayArg )
104         {
105             NSMutableArray * newArg = nil;
106             /* Collapse messages that takes array arg by sending one bigger array */
107             for( i = [[self messageQueue] count]-2; i >= 0; i-- )
108             {
109                 message_newer = (struct message *)[(NSData *)[[self messageQueue] objectAtIndex: i] bytes];
110                 if( message_newer->type == VLCObjectMethodWithArrayArg &&
111                     message_newer->target == message->target && 
112                     message_newer->sel == message->sel )
113                 {
114                     if(!newArg)
115                         newArg = [NSMutableArray arrayWithArray:message->u.object];
116                     [newArg addObjectsFromArray:message_newer->u.object];
117                     [message_newer->target release];
118                     [message_newer->u.object release];
119                     [[self messageQueue] removeObjectAtIndex: i];
120                     i++;
121                     continue;
122                 }
123                 /* It should be a good idea not to collapse event, with other kind of event in-between
124                  * Ignore for now only if target is the same */
125                 else if( message_newer->target == message->target )
126                     break;
127             }
128             if( newArg )
129             {
130                 [message->u.object release];
131                 message->u.object = [newArg retain];
132                 [newArg retain];
133             }
134         }
135
136         dataMessage = [[self messageQueue] lastObject];
137         
138         pthread_mutex_unlock( [self queueLock] );
139
140         if( message->type == VLCNotification )
141             [self performSelectorOnMainThread:@selector(callDelegateOfObjectAndSendNotificationWithArgs:) withObject:[dataMessage retain]  /* released in the call */ waitUntilDone: NO];
142         else
143             [self performSelectorOnMainThread:@selector(callObjectMethodWithArgs:) withObject:[dataMessage retain]  /* released in the call */ waitUntilDone: YES];
144
145         pthread_mutex_lock( [self queueLock] );
146         [[self messageQueue] removeLastObject];
147         pthread_mutex_unlock( [self queueLock] );
148     
149         [pool release];
150     }
151     return nil;
152 }
153
154 @implementation VLCEventManager
155 + (id)sharedManager
156 {
157     /* We do want a lock here to avoid leaks */
158     if ( !defaultManager )
159     {
160         defaultManager = [[VLCEventManager alloc] init];
161     }
162
163     return defaultManager;
164 }
165
166 - (void)dummy
167 {
168     /* Put Cocoa in multithreaded mode by calling a dummy function */
169 }
170
171 - (id)init
172 {
173     if( self = [super init] )
174     {
175         if(![NSThread isMultiThreaded])
176         {
177             [NSThread detachNewThreadSelector:@selector(dummy) toTarget:self withObject:nil];
178             NSAssert([NSThread isMultiThreaded], @"Can't put Cocoa in multithreaded mode");
179         }
180
181         pthread_mutex_init( &queueLock, NULL );
182         pthread_cond_init( &signalData, NULL );
183         pthread_create( &dispatcherThread, NULL, EventDispatcherMainLoop, self );
184         messageQueue = [[NSMutableArray alloc] initWithCapacity:10];
185     }
186     return self;
187 }
188
189 - (void)dealloc
190 {
191     pthread_kill( dispatcherThread, SIGKILL );
192     pthread_join( dispatcherThread, NULL );
193
194     [messageQueue release];
195     [super dealloc];
196 }
197
198 - (void)callOnMainThreadDelegateOfObject:(id)aTarget withDelegateMethod:(SEL)aSelector withNotificationName: (NSString *)aNotificationName
199 {
200     NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
201     
202     struct message message = 
203     { 
204         [aTarget retain], 
205         aSelector, 
206         [aNotificationName retain], 
207         VLCNotification 
208     };
209
210     if([NSThread isMainThread])
211     {
212         [self callDelegateOfObjectAndSendNotificationWithArgs:[[NSData dataWithBytes:&message length:sizeof(struct message)] retain] /* released in the call */];
213         return;
214     }
215
216    // pthread_mutex_lock( [self queueLock] );
217    // [[self messageQueue] insertObject:[NSData dataWithBytes:&message length:sizeof(struct message)] atIndex:0];
218    // pthread_cond_signal( [self signalData] );
219    // pthread_mutex_unlock( [self queueLock] );
220     
221     [pool release];
222 }
223
224 - (void)callOnMainThreadObject:(id)aTarget withMethod:(SEL)aSelector withArgumentAsObject: (id)arg
225 {
226     NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
227     struct message message = 
228     { 
229         [aTarget retain], 
230         aSelector, 
231         [arg retain], 
232         [arg isKindOfClass:[NSArray class]] ? VLCObjectMethodWithArrayArg : VLCObjectMethodWithObjectArg 
233     };
234
235     pthread_mutex_lock( [self queueLock] );
236     [[self messageQueue] insertObject:[NSData dataWithBytes:&message length:sizeof(struct message)] atIndex:0];
237     pthread_cond_signal( [self signalData] );
238     pthread_mutex_unlock( [self queueLock] );
239     
240     [pool release];
241 }
242 @end
243
244 @implementation VLCEventManager (Private)
245 - (void)callDelegateOfObjectAndSendNotificationWithArgs:(NSData*)data
246 {
247     struct message * message = (struct message *)[data bytes];
248
249     [self callDelegateOfObject:message->target withDelegateMethod:message->sel withNotificationName:message->u.name];
250     [message->u.name release];
251     [message->target release];
252     [data release];
253 }
254
255 - (void)callObjectMethodWithArgs:(NSData*)data
256 {
257     struct message * message = (struct message *)[data bytes];
258     void (*method)(id, SEL, id) = (void (*)(id, SEL, id))[message->target methodForSelector: message->sel];
259
260     method( message->target, message->sel, message->u.object);
261     [message->u.object release];
262     [message->target release];
263     [data release];
264 }
265
266 - (NSMutableArray *)messageQueue
267 {
268     return messageQueue;
269 }
270
271 - (pthread_cond_t *)signalData
272 {
273     return &signalData;
274 }
275
276 - (pthread_mutex_t *)queueLock
277 {
278     return &queueLock;
279 }
280
281 - (void)callDelegateOfObject:(id) aTarget withDelegateMethod:(SEL)aSelector withNotificationName: (NSString *)aNotificationName
282 {
283 //    [[NSNotificationCenter defaultCenter] postNotification: [NSNotification notificationWithName:aNotificationName object:aTarget]];
284
285     if (![aTarget delegate] || ![[aTarget delegate] respondsToSelector: aSelector])
286         return;
287
288     void (*method)(id, SEL, id) = (void (*)(id, SEL, id))[[aTarget delegate] methodForSelector: aSelector];
289     method( [aTarget delegate], aSelector, [NSNotification notificationWithName:aNotificationName object:aTarget]);
290 }
291 @end