1 /*****************************************************************************
2 * VLCEventManager.m: VLCKit.framework VLCEventManager implementation
3 *****************************************************************************
4 * Copyright (C) 2007 Pierre d'Herbemont
5 * Copyright (C) 2007 the VideoLAN team
8 * Authors: Pierre d'Herbemont <pdherbemont # videolan.org>
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.
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.
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 *****************************************************************************/
25 #import "VLCEventManager.h"
29 * Defines the type of interthread message on the queue.
33 VLCNotification, //< Standard NSNotification.
34 VLCObjectMethodWithObjectArg, //< Method with an object argument.
35 VLCObjectMethodWithArrayArg //< Method with an array argument.
39 * Data structured used to enqueue messages onto the queue.
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.
46 NSString * name; //< Name to be used for NSNotification
47 id object; //< Object argument to pass to the target via the selector.
49 message_type_t type; //< Type of queued message.
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;
62 - (void)addMessageToHandleOnMainThread:(NSData *)messageAsData;
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.
69 static void * EventDispatcherMainLoop(void * user_data)
71 VLCEventManager * self = user_data;
75 NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
76 message_t * message, * message_newer = NULL;
80 /* Sleep a bit not to flood the interface */
83 /* Wait for some data */
85 pthread_mutex_lock([self queueLock]);
86 /* Wait until we have something on the queue */
87 while( [[self messageQueue] count] <= 0)
89 pthread_cond_wait([self signalData], [self queueLock]);
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]);
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];
100 /* Remove duplicate notifications. */
101 if( message->type == VLCNotification )
103 for( i = [[self messageQueue] count]-1; i >= 0; i-- )
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] )
110 [message_newer->u.name release];
111 [[self messageQueue] removeObjectAtIndex:i];
115 else if( message->type == VLCObjectMethodWithArrayArg )
117 NSMutableArray * newArg = nil;
119 /* Collapse messages that takes array arg by sending one bigger array */
120 for(i = [[self messageQueue] count] - 1; i >= 0; i--)
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)
129 newArg = [NSMutableArray arrayWithArray:message->u.object];
130 [message->u.object release];
133 [newArg addObjectsFromArray:message_newer->u.object];
134 [message_newer->u.object release];
135 [[self messageQueue] removeObjectAtIndex:i];
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 )
146 message->u.object = [newArg retain];
149 [self addMessageToHandleOnMainThread:dataMessage];
151 pthread_mutex_unlock([self queueLock]);
154 if( message->type == VLCNotification )
155 [self performSelectorOnMainThread:@selector(callDelegateOfObjectAndSendNotificationWithArgs:)
156 withObject:dataMessage
159 [self performSelectorOnMainThread:@selector(callObjectMethodWithArgs:)
160 withObject:dataMessage
168 @implementation VLCEventManager
171 static VLCEventManager *defaultManager = NULL;
173 /* We do want a lock here to avoid leaks */
175 defaultManager = [[VLCEventManager alloc] init];
177 return defaultManager;
182 /* Put Cocoa in multithreaded mode by calling a dummy function */
187 if(self = [super init])
189 if(![NSThread isMultiThreaded])
191 [NSThread detachNewThreadSelector:@selector(dummy) toTarget:self withObject:nil];
192 NSAssert([NSThread isMultiThreaded], @"Can't put Cocoa in multithreaded mode");
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];
207 pthread_kill(dispatcherThread, SIGKILL);
208 pthread_join(dispatcherThread, NULL);
210 [messageQueue release];
211 [pendingMessagesOnMainThread release];
215 - (void)callOnMainThreadDelegateOfObject:(id)aTarget withDelegateMethod:(SEL)aSelector withNotificationName:(NSString *)aNotificationName
217 /* Don't send on main thread before this gets sorted out */
218 NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
224 [aNotificationName retain],
228 if( [NSThread isMainThread] )
230 NSData *message = [NSData dataWithBytes:&message length:sizeof(message_t)];
231 [self addMessageToHandleOnMainThread:message];
232 [self callDelegateOfObjectAndSendNotificationWithArgs:[message retain] /* released in the call */];
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]);
245 - (void)callOnMainThreadObject:(id)aTarget withMethod:(SEL)aSelector withArgumentAsObject:(id)arg
247 NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
253 [arg isKindOfClass:[NSArray class]] ? VLCObjectMethodWithArrayArg : VLCObjectMethodWithObjectArg
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]);
264 - (void)cancelCallToObject:(id)target
267 // Remove all queued message
268 pthread_mutex_lock([self queueLock]);
269 [pendingMessagesLock lock];
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];
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];
286 if (message->target == target) {
287 [messages removeObjectAtIndex:i];
292 [pendingMessagesLock unlock];
293 pthread_mutex_unlock([self queueLock]);
298 @implementation VLCEventManager (Private)
300 - (void)addMessageToHandleOnMainThread:(NSData *)messageAsData
302 [pendingMessagesLock lock];
303 [pendingMessagesOnMainThread addObject:messageAsData];
304 [pendingMessagesLock unlock];
308 - (BOOL)markMessageHandledOnMainThreadIfExists:(NSData *)messageAsData
310 [pendingMessagesLock lock];
311 BOOL cancelled = ![pendingMessagesOnMainThread containsObject:messageAsData];
313 [pendingMessagesOnMainThread removeObject:messageAsData];
314 [pendingMessagesLock unlock];
319 - (void)callDelegateOfObjectAndSendNotificationWithArgs:(NSData*)data
321 message_t * message = (message_t *)[data bytes];
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];
328 [message->u.name release];
332 - (void)callObjectMethodWithArgs:(NSData*)data
334 message_t * message = (message_t *)[data bytes];
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);
342 [message->u.object release];
346 - (void)callDelegateOfObject:(id)aTarget withDelegateMethod:(SEL)aSelector withNotificationName:(NSString *)aNotificationName
348 [[NSNotificationCenter defaultCenter] postNotification: [NSNotification notificationWithName:aNotificationName object:aTarget]];
350 id delegate = [aTarget delegate];
351 if (!delegate || ![delegate respondsToSelector:aSelector])
354 void (*method)(id, SEL, id) = (void (*)(id, SEL, id))[[aTarget delegate] methodForSelector: aSelector];
355 method([aTarget delegate], aSelector, [NSNotification notificationWithName:aNotificationName object:aTarget]);
358 - (NSMutableArray *)messageQueue
363 - (NSMutableArray *)pendingMessagesOnMainThread
365 return pendingMessagesOnMainThread;
368 - (NSLock *)pendingMessagesLock
370 return pendingMessagesLock;
374 - (pthread_cond_t *)signalData
379 - (pthread_mutex_t *)queueLock