Introduce DebuggerModel, which will be updated by the BackEnd.
[macgdbp.git] / Source / BSProtocolThreadInvoker.m
1 /*
2 * MacGDBp
3 * Copyright (c) 2013, Blue Static <http://www.bluestatic.org>
4 *
5 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
6 * General Public License as published by the Free Software Foundation; either version 2 of the
7 * License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
10 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along with this program; if not,
14 * write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
15 */
16
17 #import "BSProtocolThreadInvoker.h"
18
19 @interface BSProtocolThreadInvoker (Private)
20 // Installs a run loop observer on the target thread that is used to start
21 // dispatching messages again after falling out of a nested run loop.
22 // *MUST* be called on the target thread.
23 - (void)addRunLoopObserver;
24
25 // Removes the observer added with |-addRunLoopObserver|. Can be called on any
26 // thread since CFRunLoop is threadsafe.
27 - (void)removeRunLoopObserver;
28
29 // Enqueues the |invocation| for execution, and dequeues the first if not called
30 // reentrantly.
31 // *MUST* be called on the target thread.
32 - (void)dispatchInvocation:(NSInvocation*)invocation;
33
34 // Callback for the run loop whenever it begins a new pass. This will schedule
35 // work if any was previously deferred due to reentrancy protection.
36 - (void)observedRunLoopEnter;
37 @end
38
39 @implementation BSProtocolThreadInvoker {
40 // The fully qualified target of the invocation.
41 NSObject* _object;
42 Protocol* _protocol;
43 NSThread* _thread;
44 CFRunLoopRef _runLoop;
45 NSArray* _modes;
46
47 // If executing an invocation from |-dispatchInvocation:|. Protects against
48 // reentering the target.
49 BOOL _isDispatching;
50
51 // The queue of work to be executed. Enqueues in |-dispatchInvocation:|.
52 NSMutableArray* _invocations;
53
54 CFRunLoopObserverRef _observer;
55 }
56
57 @synthesize object = _object;
58
59 - (id)initWithObject:(NSObject*)object
60 protocol:(Protocol*)protocol
61 thread:(NSThread*)thread
62 {
63 return [self initWithObject:object
64 protocol:protocol
65 thread:thread
66 modes:@[ NSRunLoopCommonModes ]];
67 }
68
69 - (id)initWithObject:(NSObject*)object
70 protocol:(Protocol*)protocol
71 thread:(NSThread*)thread
72 modes:(NSArray*)runLoopModes
73 {
74 if ((self = [super init])) {
75 _object = object;
76 _protocol = protocol;
77 _thread = thread;
78 _modes = [runLoopModes retain];
79 _invocations = [[NSMutableArray alloc] init];
80
81 [self performSelector:@selector(addRunLoopObserver)
82 onThread:_thread
83 withObject:nil
84 waitUntilDone:NO
85 modes:_modes];
86 }
87 return self;
88 }
89
90 - (void)dealloc
91 {
92 [self removeRunLoopObserver];
93 [_modes release];
94 [_invocations release];
95 [super dealloc];
96 }
97
98 - (BOOL)conformsToProtocol:(Protocol*)protocol
99 {
100 return [_protocol isEqual:protocol];
101 }
102
103 - (NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector
104 {
105 if (!_object)
106 return [_protocol methodSignatureForSelector:aSelector];
107 return [_object methodSignatureForSelector:aSelector];
108 }
109
110 - (BOOL)respondsToSelector:(SEL)aSelector
111 {
112 if (!_object)
113 return [_protocol respondsToSelector:aSelector];
114 return [_object respondsToSelector:aSelector];
115 }
116
117 - (void)forwardInvocation:(NSInvocation*)invocation
118 {
119 if ([_object respondsToSelector:[invocation selector]]) {
120 [invocation retainArguments];
121 [self performSelector:@selector(dispatchInvocation:)
122 onThread:_thread
123 withObject:invocation
124 waitUntilDone:NO
125 modes:_modes];
126 }
127 }
128
129 // Private /////////////////////////////////////////////////////////////////////
130
131 - (void)addRunLoopObserver
132 {
133 assert([NSThread currentThread] == _thread);
134 _runLoop = CFRunLoopGetCurrent();
135
136 BSProtocolThreadInvoker* __block weakSelf = self;
137 _observer = CFRunLoopObserverCreateWithHandler(
138 kCFAllocatorDefault,
139 kCFRunLoopEntry,
140 TRUE, // Repeats.
141 0, // Order.
142 ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
143 [weakSelf observedRunLoopEnter];
144 });
145 for (NSString* mode in _modes)
146 CFRunLoopAddObserver(_runLoop, _observer, (CFStringRef)mode);
147 }
148
149 - (void)removeRunLoopObserver
150 {
151 for (NSString* mode in _modes)
152 CFRunLoopRemoveObserver(_runLoop, _observer, (CFStringRef)mode);
153 CFRelease(_observer);
154 }
155
156 - (void)dispatchInvocation:(NSInvocation*)invocation
157 {
158 // |invocation| will be nil if dispatch was requested after entering a new
159 // pass of the run loop, to process deferred work.
160 if (invocation)
161 [_invocations addObject:invocation];
162
163 // Protect the target object from reentering itself. This work will be
164 // rescheduled when another run loop starts (including falling out of a
165 // nested loop and starting a new pass through a lower loop).
166 if (_isDispatching)
167 return;
168
169 _isDispatching = YES;
170
171 // Dequeue only one item. If multiple items are present, the next pass through
172 // the run loop will schedule another dispatch via |-observedRunLoopEnter|.
173 invocation = [_invocations objectAtIndex:0];
174 [invocation invokeWithTarget:_object];
175 [_invocations removeObjectAtIndex:0];
176
177 _isDispatching = NO;
178 }
179
180 - (void)observedRunLoopEnter
181 {
182 // Don't do anything if there's nothing to do.
183 if ([_invocations count] == 0)
184 return;
185
186 // If this nested run loop is still executing from within
187 // |-dispatchInvocation:|, continue to wait for a the nested loop to exit.
188 if (_isDispatching)
189 return;
190
191 // A run loop has started running outside of |-dispatchInvocation:|, so
192 // schedule work to be done again.
193 [self performSelector:@selector(dispatchInvocation:)
194 onThread:_thread
195 withObject:nil
196 waitUntilDone:NO
197 modes:_modes];
198 }
199
200 @end