AnotherJake
2006.06.06, 05:47 PM
I have a project where I use two threads: 1) for OpenGL rendering and 2) for updating the simulation, physics, etc. Both have one timer each to call them back regularly. One of the things that came to my attention yesterday is that my dealloc method of my NSOpenGLView was not being called. After investigating further I had forgotten that not only do the timers retain the view but so does the extra thread for the sim (most example code I've seen forgets about the timer retain too, so I don't feel too bad). So, I have four retains on the GLView after everything is up and running:
- window (presumably)
- render timer
- update simulation timer
- update simulation thread
To get rid of the timer retains I simply call [timer invalidate] in the NSWindowWillCloseNotification -- simple enough. The window gets rid of its own retain. But the crapper is that the thread doesn't want to give up very easily. Calling [NSThread exit] is not only `not preferred' according to Apple docs, but it hangs in my situation for whatever reason. The way I have somewhat stumbled upon getting the thread to release itself is to set up a dummy port in the thread. I have scoured the Apple docs as much as my patience will allow, but they do not say specifically whether it's a bad idea or not. Indeed, in the RunLoop docs they mention that you should set up an empty port if you want to ensure that your runloop won't exit. I was under the impression that I needed to specifically add the port to the runloop, but if I do that then it crashes on app termination (works okay on window close though).
What I suspect is that when I call [NSPort port] it automatically hooks it up the the NSDefaultRunLoopMode, but it doesn't say that anywhere in the docs (that I've found). OR, it could be that when I invalidate the port, some message or notification automatically gets sent to the runloop and that triggers it to do its obligatory handling of one message and then returns from runMode:beforeDate:, which in turn exits the thread. Invalidating the timer is not enough to get runMode:beforeDate: to return -- apparently timers don't count the same as other inputs to the runloop.
This is kind of complicated to describe, maybe some code snippets would help better illustrate. This works as advertised and calls dealloc when I close the window or quit, as it should:
@interface GLView : NSOpenGLView
{
NSTimer *renderTimer;
NSTimer *updateTimer;
NSPort *dummyPortForExitingRunLoop;
}
@end
#define SIM_TICK 110.0
@implementation GLView
- (id)initWithFrame:(NSRect)frameRect
{
...
// standard OpenGL context init stuff here
...
[NSThread detachNewThreadSelector:@selector(initSimThread:) toTarget:self withObject:nil];
renderTimer = [NSTimer scheduledTimerWithTimeInterval:0.001 target:self selector:@selector(renderFrame) userInfo:nil repeats:YES];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(shutdownRenderTimer:) name:NSWindowWillCloseNotification object:[self window]];
return self;
}
- (void)initSimThread:(id)object
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
updateTimer = [NSTimer scheduledTimerWithTimeInterval: 1.0 / SIM_TICK target:self selector:@selector(updateSimulation) userInfo:nil repeats:YES];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(shutdownSimThread:) name:NSWindowWillCloseNotification object:[self window]];
dummyPortForExitingRunLoop = [NSPort port];
//[[NSRunLoop currentRunLoop] addPort:dummyPortForRunLoop forMode:NSDefaultRunLoopMode]; // <- can't do this or it crashes on quit
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
[pool release];
}
- (void)shutdownSimThread:(NSNotification *)notification
{
[dummyPortForExitingRunLoop invalidate];
[updateTimer invalidate];
}
- (void)shutdownRenderTimer:(NSNotification *)notification
{
[renderTimer invalidate];
}
- (void)dealloc
{
NSLog(@"dealloc is definitely being called");
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super dealloc];
}
@end
So my big fat question is this: Is this okay to do, or is there a better way to do it? Specifically, I mean, is using dummyPortForExitingRunLoop, as I am doing, a proper way to exit my sim thread?
- window (presumably)
- render timer
- update simulation timer
- update simulation thread
To get rid of the timer retains I simply call [timer invalidate] in the NSWindowWillCloseNotification -- simple enough. The window gets rid of its own retain. But the crapper is that the thread doesn't want to give up very easily. Calling [NSThread exit] is not only `not preferred' according to Apple docs, but it hangs in my situation for whatever reason. The way I have somewhat stumbled upon getting the thread to release itself is to set up a dummy port in the thread. I have scoured the Apple docs as much as my patience will allow, but they do not say specifically whether it's a bad idea or not. Indeed, in the RunLoop docs they mention that you should set up an empty port if you want to ensure that your runloop won't exit. I was under the impression that I needed to specifically add the port to the runloop, but if I do that then it crashes on app termination (works okay on window close though).
What I suspect is that when I call [NSPort port] it automatically hooks it up the the NSDefaultRunLoopMode, but it doesn't say that anywhere in the docs (that I've found). OR, it could be that when I invalidate the port, some message or notification automatically gets sent to the runloop and that triggers it to do its obligatory handling of one message and then returns from runMode:beforeDate:, which in turn exits the thread. Invalidating the timer is not enough to get runMode:beforeDate: to return -- apparently timers don't count the same as other inputs to the runloop.
This is kind of complicated to describe, maybe some code snippets would help better illustrate. This works as advertised and calls dealloc when I close the window or quit, as it should:
@interface GLView : NSOpenGLView
{
NSTimer *renderTimer;
NSTimer *updateTimer;
NSPort *dummyPortForExitingRunLoop;
}
@end
#define SIM_TICK 110.0
@implementation GLView
- (id)initWithFrame:(NSRect)frameRect
{
...
// standard OpenGL context init stuff here
...
[NSThread detachNewThreadSelector:@selector(initSimThread:) toTarget:self withObject:nil];
renderTimer = [NSTimer scheduledTimerWithTimeInterval:0.001 target:self selector:@selector(renderFrame) userInfo:nil repeats:YES];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(shutdownRenderTimer:) name:NSWindowWillCloseNotification object:[self window]];
return self;
}
- (void)initSimThread:(id)object
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
updateTimer = [NSTimer scheduledTimerWithTimeInterval: 1.0 / SIM_TICK target:self selector:@selector(updateSimulation) userInfo:nil repeats:YES];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(shutdownSimThread:) name:NSWindowWillCloseNotification object:[self window]];
dummyPortForExitingRunLoop = [NSPort port];
//[[NSRunLoop currentRunLoop] addPort:dummyPortForRunLoop forMode:NSDefaultRunLoopMode]; // <- can't do this or it crashes on quit
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
[pool release];
}
- (void)shutdownSimThread:(NSNotification *)notification
{
[dummyPortForExitingRunLoop invalidate];
[updateTimer invalidate];
}
- (void)shutdownRenderTimer:(NSNotification *)notification
{
[renderTimer invalidate];
}
- (void)dealloc
{
NSLog(@"dealloc is definitely being called");
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super dealloc];
}
@end
So my big fat question is this: Is this okay to do, or is there a better way to do it? Specifically, I mean, is using dummyPortForExitingRunLoop, as I am doing, a proper way to exit my sim thread?