From cf079dbfa3d9a5cd1fe3bd9d7615c15fac2ceb28 Mon Sep 17 00:00:00 2001 From: Christopher Stawarz Date: Wed, 9 Jun 2010 15:17:18 -0400 Subject: [PATCH] Reworked EyeWindow so that it uses a reasonable amount of CPU time --- EyeWindow/MWEyeSamplePlotElement.h | 6 +- EyeWindow/MWEyeSamplePlotElement.m | 2 +- EyeWindow/MWEyeWindowController.m | 2 +- EyeWindow/MWPlotView.h | 13 +- EyeWindow/MWPlotView.m | 204 ++++++++---------- .../MWorksEyeWindow.xcodeproj/project.pbxproj | 4 +- 6 files changed, 108 insertions(+), 123 deletions(-) diff --git a/EyeWindow/MWEyeSamplePlotElement.h b/EyeWindow/MWEyeSamplePlotElement.h index 6d4e506..cdf49f8 100644 --- a/EyeWindow/MWEyeSamplePlotElement.h +++ b/EyeWindow/MWEyeSamplePlotElement.h @@ -14,16 +14,16 @@ NSPoint position; int is_saccading; // during a saccade or not? - mw::MWorksTime time; + NSTimeInterval time; } -- (id)initWithTime:(mw::MWorksTime)_time +- (id)initWithTime:(NSTimeInterval)_time position:(NSPoint)position isSaccading:(int)_is_saccading; @property(readonly) NSPoint position; -@property(readonly) mw::MWorksTime time; +@property(readonly) NSTimeInterval time; @property(readonly) int saccading; @end diff --git a/EyeWindow/MWEyeSamplePlotElement.m b/EyeWindow/MWEyeSamplePlotElement.m index bceca36..2dea408 100644 --- a/EyeWindow/MWEyeSamplePlotElement.m +++ b/EyeWindow/MWEyeSamplePlotElement.m @@ -10,7 +10,7 @@ @implementation MWEyeSamplePlotElement -- (id)initWithTime:(mw::MWorksTime)_time +- (id)initWithTime:(NSTimeInterval)_time position:(NSPoint)_position isSaccading:(int)_is_saccading { diff --git a/EyeWindow/MWEyeWindowController.m b/EyeWindow/MWEyeWindowController.m index 1c93474..6e758d1 100644 --- a/EyeWindow/MWEyeWindowController.m +++ b/EyeWindow/MWEyeWindowController.m @@ -115,7 +115,7 @@ - (void)updateEyeVariableNames { *******************************************************************/ - (void)codecReceived:(MWCocoaEvent *)event { if(!eyeWindowStarted) { - [NSThread detachNewThreadSelector:@selector(aggregateEvents:) + [NSThread detachNewThreadSelector:@selector(checkForUpdates:) toTarget:plotView withObject:nil]; eyeWindowStarted = YES; diff --git a/EyeWindow/MWPlotView.h b/EyeWindow/MWPlotView.h index 3b2847f..3c31605 100644 --- a/EyeWindow/MWPlotView.h +++ b/EyeWindow/MWPlotView.h @@ -31,14 +31,15 @@ Copy right 2006 MIT. All rights reserved. NSMutableArray *eye_samples; NSMutableArray *stm_samples; - NSMutableArray *eyeHEvents; - NSMutableArray *eyeVEvents; + MWCocoaEvent *currentEyeH; + MWCocoaEvent *currentEyeV; - mw::MWorksTime last_state_change_time; + MWorksTime last_state_change_time; int current_state; - mw::MWorksTime timeOfTail; - mw::MWorksTime time_between_updates; + NSTimeInterval timeOfTail; + NSTimeInterval time_between_updates; + BOOL needUpdate; } - (void)setWidth:(int)width; @@ -46,7 +47,7 @@ Copy right 2006 MIT. All rights reserved. - (void)addEyeVEvent:(MWCocoaEvent *)event; - (void)addEyeStateEvent:(MWCocoaEvent *)event; - (void)acceptStmAnnounce:(mw::Datum *)stm_announce - Time:(mw::MWorksTime)event_time; + Time:(MWorksTime)event_time; - (void)setTimeOfTail:(NSTimeInterval)_newTimeOfTail; - (void)setUpdateRate:(float)updates_per_second; - (void)acceptCalAnnounce:(mw::Datum *)cal_announce; diff --git a/EyeWindow/MWPlotView.m b/EyeWindow/MWPlotView.m index 5d74cd5..e13e3d0 100644 --- a/EyeWindow/MWPlotView.m +++ b/EyeWindow/MWPlotView.m @@ -11,13 +11,21 @@ #define SACCADING 1 -@interface MWPlotView(PrivateMethods) -- (void)aggregateEvents:(id)object; +@interface MWPlotView () + +@property(nonatomic, retain) MWCocoaEvent *currentEyeH; +@property(nonatomic, retain) MWCocoaEvent *currentEyeV; + +- (void)syncHEvent:(MWCocoaEvent *)eyeH withVEvent:(MWCocoaEvent *)eyeV; +- (void)checkForUpdates:(id)object; - (void)setNeedsDisplayOnMainThread:(id)arg; + @end @implementation MWPlotView +@synthesize currentEyeH, currentEyeV; + - (id)initWithFrame:(NSRect)frameRect { width = 180; gridStepX = 10; @@ -26,16 +34,13 @@ - (id)initWithFrame:(NSRect)frameRect { eye_samples = [[NSMutableArray alloc] init]; stm_samples = [[NSMutableArray alloc] init]; - - eyeVEvents = [[NSMutableArray alloc] init]; - eyeHEvents = [[NSMutableArray alloc] init]; last_state_change_time = 0; current_state = FIXATION; // these correspond to the defaults in the options window. - timeOfTail = 1000000; // 1s - time_between_updates = 100000; // 100ms + timeOfTail = 1.0; // 1s + time_between_updates = 0.1; // 100ms GLuint attribs[] = { @@ -71,8 +76,8 @@ - (id)initWithFrame:(NSRect)frameRect { - (void)dealloc { [eye_samples release]; [stm_samples release]; - [eyeVEvents release]; - [eyeHEvents release]; + [currentEyeH release]; + [currentEyeV release]; [super dealloc]; } @@ -194,20 +199,70 @@ - (void)setWidth:(int)width_in { - (void)addEyeHEvent:(MWCocoaEvent *)event { @synchronized(self) { - [eyeHEvents addObject:event]; + if (!self.currentEyeH || ([event time] > [self.currentEyeH time])) { + [self syncHEvent:event withVEvent:self.currentEyeV]; + } } } - (void)addEyeVEvent:(MWCocoaEvent *)event { @synchronized(self) { - [eyeVEvents addObject:event]; + if (!self.currentEyeV || ([event time] > [self.currentEyeV time])) { + [self syncHEvent:self.currentEyeH withVEvent:event]; + } } } +#define EVENT_SYNC_TIME_US 250 + +- (void)syncHEvent:(MWCocoaEvent *)eyeH withVEvent:(MWCocoaEvent *)eyeV { + MWorksTime eyeHTime; + MWorksTime eyeVTime; + BOOL synced = NO; + + if (eyeH && eyeV) { + eyeHTime = [eyeH time]; + eyeVTime = [eyeV time]; + MWorksTime t_diff = eyeHTime - eyeVTime; + if (abs(t_diff) <= EVENT_SYNC_TIME_US) { + synced = YES; + } else { + if (t_diff > 0) { + eyeV = nil; + } else { + eyeH = nil; + } + } + } + + self.currentEyeH = eyeH; + self.currentEyeV = eyeV; + + if (synced) { + // + // We have a synced eyeH/eyeV pair, so add a plot element for it + // + + int eye_state = (max(eyeHTime, eyeVTime) >= last_state_change_time) ? current_state : !current_state; + + MWEyeSamplePlotElement *sample = nil; + sample = [[MWEyeSamplePlotElement alloc] initWithTime:[NSDate timeIntervalSinceReferenceDate] + position:NSMakePoint([self.currentEyeH data]->getFloat(), + [self.currentEyeV data]->getFloat()) + isSaccading:eye_state]; + [eye_samples addObject:sample]; + [sample release]; + + self.currentEyeH = nil; + self.currentEyeV = nil; + needUpdate = YES; + } +} + - (void)addEyeStateEvent:(MWCocoaEvent *)event { @synchronized(self) { if([event data]->getInteger() != current_state) { - mw::MWorksTime time_of_state_change = [event time]; + MWorksTime time_of_state_change = [event time]; if(time_of_state_change > last_state_change_time) { last_state_change_time = time_of_state_change; current_state = !current_state; @@ -218,12 +273,12 @@ - (void)addEyeStateEvent:(MWCocoaEvent *)event { //==================== stimulus announce is handled here =============================== -- (void)acceptStmAnnounce:(mw::Datum *)stm_announce Time:(mw::MWorksTime)event_time +- (void)acceptStmAnnounce:(mw::Datum *)stm_announce Time:(MWorksTime)event_time { @synchronized(self) { #define MAX_STIM_DRAW_LAG 1000 - static mw::MWorksTime last_event_time = 0LL; + static MWorksTime last_event_time = 0LL; //First check for refresh @@ -465,19 +520,20 @@ - (void)clear @synchronized(self) { [eye_samples removeAllObjects]; [stm_samples removeAllObjects]; + needUpdate = YES; } } - (void)setTimeOfTail:(NSTimeInterval)_newTimeOfTail { @synchronized(self) { - timeOfTail = _newTimeOfTail*1000000; + timeOfTail = _newTimeOfTail; } } - (void)setUpdateRate:(float)updates_per_second { @synchronized(self) { - time_between_updates = 1000000/updates_per_second; + time_between_updates = 1.0/updates_per_second; } } @@ -485,108 +541,36 @@ - (void)setUpdateRate:(float)updates_per_second { // Private methods ///////////////////////////////////////////////////////////////////////// -#define EVENT_SYNC_TIME_US 250 +#define MAX_SLEEP_INTERVAL 1.0 // 1 second -- (void)aggregateEvents:(id)object { - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - float currentEyeH = 0; - float currentEyeV = 0; - mw::MWorksTime currentEventTime = 0; - mw::MWorksTime previous_event_time = 0; - - - +- (void)checkForUpdates:(id)object { while(1) { NSAutoreleasePool *loop_pool = [[NSAutoreleasePool alloc] init]; - MWEyeSamplePlotElement *sample = nil; - [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.000005]]; + + NSTimeInterval sleepInterval; + @synchronized(self) { + sleepInterval = min(time_between_updates, MAX_SLEEP_INTERVAL); + } + + [NSThread sleepForTimeInterval:sleepInterval]; @synchronized(self) { - if([eyeHEvents count] > 0 && - [eyeVEvents count] > 0) { - MWCocoaEvent *headEyeH = [eyeHEvents objectAtIndex:0]; - MWCocoaEvent *headEyeV = [eyeVEvents objectAtIndex:0]; - - mw::MWorksTime eyeHTime = [headEyeH time]; - mw::MWorksTime eyeVTime = [headEyeV time]; - - currentEyeH = [headEyeH data]->getFloat(); - currentEyeV = [headEyeV data]->getFloat(); - - if(eyeHTime >= eyeVTime) { - // eyeH is the newest on the head - - // advance eyeV - while(eyeHTime - eyeVTime > EVENT_SYNC_TIME_US && - [eyeVEvents count] > 0) { - [eyeVEvents removeObjectAtIndex:0]; - if([eyeVEvents count] > 0) { - headEyeV = [eyeVEvents objectAtIndex:0]; - currentEyeV = [headEyeV data]->getFloat(); - eyeVTime = [headEyeV time]; - } - } - - currentEventTime = eyeHTime; - - } else { - // eyeV is the newest on the head - - // advance eyeH - while(eyeVTime - eyeHTime > EVENT_SYNC_TIME_US && - [eyeHEvents count] > 0) { - [eyeHEvents removeObjectAtIndex:0]; - if([eyeHEvents count] > 0) { - headEyeH = [eyeHEvents objectAtIndex:0]; - currentEyeH = [headEyeH data]->getFloat(); - eyeHTime = [headEyeH time]; - } - } - - currentEventTime = eyeVTime; - } - - // currenly, it can only be 1 or 0 - int eye_state = currentEventTime >= last_state_change_time ? current_state : !current_state; - - - - // now all events roughly equal in time - sample = [[MWEyeSamplePlotElement alloc] initWithTime:currentEventTime - position:NSMakePoint(currentEyeH, currentEyeV) - isSaccading:eye_state]; - - if([eyeHEvents count] > 0) { - [eyeHEvents removeObjectAtIndex:0]; - } - - if([eyeVEvents count] > 0) { - [eyeVEvents removeObjectAtIndex:0]; - } - } - } - - if(sample != nil) { - @synchronized(self) { - [eye_samples addObject:sample]; - [sample release]; - - while(currentEventTime - timeOfTail > [[eye_samples objectAtIndex:0] time]) { - [eye_samples removeObjectAtIndex:0]; - } - } + NSTimeInterval cutoffTime = [NSDate timeIntervalSinceReferenceDate] - timeOfTail; + while(([eye_samples count] > 0) && ([[eye_samples objectAtIndex:0] time] < cutoffTime)) { + [eye_samples removeObjectAtIndex:0]; + needUpdate = YES; + } + + if (needUpdate) { + [self performSelectorOnMainThread:@selector(setNeedsDisplayOnMainThread:) + withObject:nil + waitUntilDone:NO]; + needUpdate = NO; + } } - if(currentEventTime - previous_event_time > time_between_updates) { - [self performSelectorOnMainThread:@selector(setNeedsDisplayOnMainThread:) - withObject:nil - waitUntilDone:NO]; - previous_event_time = currentEventTime; - } - - [loop_pool release]; + [loop_pool drain]; } - [pool release]; } - (void)setNeedsDisplayOnMainThread:(id)arg { diff --git a/EyeWindow/MWorksEyeWindow.xcodeproj/project.pbxproj b/EyeWindow/MWorksEyeWindow.xcodeproj/project.pbxproj index 4790ef9..6551376 100644 --- a/EyeWindow/MWorksEyeWindow.xcodeproj/project.pbxproj +++ b/EyeWindow/MWorksEyeWindow.xcodeproj/project.pbxproj @@ -128,15 +128,15 @@ children = ( 68D2929F0AF8E8AE00E9A885 /* MWEyeWindowOptionController.h */, 68D292A00AF8E8AE00E9A885 /* MWEyeWindowOptionController.m */, + 5C10618C09904FE9007A63F8 /* MWPlotViewElement.h */, B28377190A8A951400358169 /* MWStimulusPlotElement.h */, B283771A0A8A951400358169 /* MWStimulusPlotElement.m */, 5C8E4A6B098FA6FF00EB44CA /* MWPlotView.h */, 5C8E4A6C098FA6FF00EB44CA /* MWPlotView.m */, 5C8E4A79098FB02400EB44CA /* MWEyeWindowController.h */, 5C8E4A7A098FB02400EB44CA /* MWEyeWindowController.m */, - 5CF28589099402F4000C55A3 /* MWEyeSamplePlotElement.m */, - 5C10618C09904FE9007A63F8 /* MWPlotViewElement.h */, 5CF28588099402F4000C55A3 /* MWEyeSamplePlotElement.h */, + 5CF28589099402F4000C55A3 /* MWEyeSamplePlotElement.m */, ); name = Classes; sourceTree = "";