Skip to content

Commit

Permalink
as pointed out by hobsynth at Gamua#53, when many touches were queued…
Browse files Browse the repository at this point in the history
… in the touch processor, they essentially would "overwrite" themselves causing touches to be lost in the game loop; this is now corrected by checking for duplicates and repeating processing until the queue is empty
  • Loading branch information
racarone committed Feb 1, 2016
1 parent 0f5b38e commit f3d44f0
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 67 deletions.
4 changes: 2 additions & 2 deletions sparrow/src/Classes/SPTouchProcessor.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ NS_ASSUME_NONNULL_BEGIN
/// Called internally by "advanceTime:". To calculate updated targets, the method will call
/// "hitTestPoint:" on the "root" object.
///
/// @param touches A list of all touches that have changed just now.
- (void)processTouches:(NSMutableOrderedSet *)touches;
/// @param touches A set of all touches that have changed just now.
- (void)processTouches:(NSSet *)touches;

/// Enqueues a new touch.
- (void)enqueueTouch:(SPTouch *)touch;
Expand Down
132 changes: 67 additions & 65 deletions sparrow/src/Classes/SPTouchProcessor.m
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,9 @@ - (instancetype)initWithStage:(SPStage *)stage
_queuedTouches = [[NSMutableArray alloc] init];
_lastTaps = [[NSMutableArray alloc] init];

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(cancelCurrentTouches)
name:UIApplicationWillResignActiveNotification object:nil];
[[NSNotificationCenter defaultCenter]
addObserver:self selector:@selector(cancelCurrentTouches)
name:UIApplicationWillResignActiveNotification object:nil];
}

return self;
Expand Down Expand Up @@ -83,37 +84,48 @@ - (void)dealloc
- (void)advanceTime:(double)seconds
{
_elapsedTime += seconds;

if (_lastTaps.count)
{
NSMutableArray *remainingTaps = [NSMutableArray array];

for (SPTouch *touch in _lastTaps)
if (_elapsedTime - touch.timestamp <= _multitapTime)
[remainingTaps addObject:touch];

SP_RELEASE_AND_RETAIN(_lastTaps, remainingTaps);
}

if (_queuedTouches.count)
{
for (SPTouch *touch in _currentTouches)
if (touch.phase == SPTouchPhaseBegan || touch.phase == SPTouchPhaseMoved)
touch.phase = SPTouchPhaseStationary;

for (SPTouch *touch in _queuedTouches)
[_updatedTouches addObject:[self createOrUpdateTouch:touch]];

[self processTouches:_updatedTouches];
[_updatedTouches removeAllObjects];

NSMutableOrderedSet *remainingTouches = [NSMutableOrderedSet orderedSet];
for (SPTouch *touch in _currentTouches)
if (touch.phase != SPTouchPhaseEnded && touch.phase != SPTouchPhaseCancelled)
[remainingTouches addObject:touch];

SP_RELEASE_AND_RETAIN(_currentTouches, remainingTouches);
[_queuedTouches removeAllObjects];

while (_queuedTouches.count)
{
NSMutableArray *excessTouches = [NSMutableArray array];

for (SPTouch *touch in _queuedTouches)
{
if (![_updatedTouches containsObject:touch])
{
[self addCurrentTouch:touch];
[_updatedTouches addObject:touch];
}
else
{
[excessTouches addObject:touch];
}
}

[self processTouches:_updatedTouches.set];
[_updatedTouches removeAllObjects];

SP_RELEASE_AND_RETAIN(_queuedTouches, excessTouches);
}

[self removeEndedTouches];
}
}

Expand All @@ -131,11 +143,8 @@ - (NSInteger)numCurrentTouches

#pragma mark Process Touches

- (void)processTouches:(NSMutableOrderedSet *)touches
- (void)processTouches:(NSSet *)touches
{
// the same touch event will be dispatched to all targets
SPTouchEvent *touchEvent = [[SPTouchEvent alloc] initWithType:SPEventTypeTouch touches:_currentTouches.set];

// hit test our updated touches
for (SPTouch *touch in touches)
{
Expand All @@ -145,6 +154,10 @@ - (void)processTouches:(NSMutableOrderedSet *)touches
touch.target = [_root hitTestPoint:touchPosition forTouch:YES];
}
}

// the same touch event will be dispatched to all targets
SPTouchEvent *touchEvent =
[[SPTouchEvent alloc] initWithType:SPEventTypeTouch touches:_currentTouches.set];

// dispatch events for the rest of our updated touches
for (SPTouch *touch in touches)
Expand All @@ -155,55 +168,45 @@ - (void)processTouches:(NSMutableOrderedSet *)touches

- (void)cancelCurrentTouches
{
double now = CACurrentMediaTime();

// remove touches that have already ended / were already canceled
[_currentTouches filterUsingPredicate:
[NSPredicate predicateWithBlock:^ BOOL (SPTouch *touch, NSDictionary *bindings)
{
return touch.phase != SPTouchPhaseEnded && touch.phase != SPTouchPhaseCancelled;
}]];

[self removeEndedTouches];

double now = CACurrentMediaTime();
for (SPTouch *touch in _currentTouches)
{
touch.phase = SPTouchPhaseCancelled;
touch.timestamp = now;
}

SPTouchEvent *touchEvent = [[SPTouchEvent alloc]
initWithType:SPEventTypeTouch touches:_currentTouches.set];

for (SPTouch *touch in _currentTouches)
{
SPTouchEvent *touchEvent = [[SPTouchEvent alloc] initWithType:SPEventTypeTouch
touches:_currentTouches.set];
[touch.target dispatchEvent:touchEvent];
[touchEvent release];
}


[touchEvent release];
[_currentTouches removeAllObjects];
}

#pragma mark Update Touches

- (SPTouch *)createOrUpdateTouch:(SPTouch *)touch
- (void)addCurrentTouch:(SPTouch *)touch
{
SPTouch *currentTouch = [self currentTouchWithID:touch.touchID];
if (!currentTouch)
NSUInteger index = [_currentTouches indexOfObject:touch];

if (index != NSNotFound)
{
currentTouch = [SPTouch touchWithID:touch.touchID];
[_currentTouches addObject:currentTouch];
SPTouch *currentTouch = _currentTouches[index];
touch.target = currentTouch.target; // save the target
[_currentTouches replaceObjectAtIndex:index withObject:touch];
}
else
{
[_currentTouches addObject:touch];
}

currentTouch.globalX = touch.globalX;
currentTouch.globalY = touch.globalY;
currentTouch.previousGlobalX = touch.previousGlobalX;
currentTouch.previousGlobalY = touch.previousGlobalY;
currentTouch.phase = touch.phase;
currentTouch.forceFactor = touch.forceFactor;
currentTouch.timestamp = _elapsedTime;

if (currentTouch.phase == SPTouchPhaseBegan)
[self updateTapCount:currentTouch];

return currentTouch;
if (touch.phase == SPTouchPhaseBegan)
[self updateTapCount:touch];
}

- (void)updateTapCount:(SPTouch *)touch
Expand All @@ -213,8 +216,8 @@ - (void)updateTapCount:(SPTouch *)touch

for (SPTouch *tap in _lastTaps)
{
float sqDist = powf(tap.globalX - touch.globalX, 2) +
powf(tap.globalY - touch.globalY, 2);
float sqDist = powf(tap.globalX - touch.globalX, 2) +
powf(tap.globalY - touch.globalY, 2);

if (sqDist <= minSqDist)
nearbyTap = tap;
Expand All @@ -233,15 +236,14 @@ - (void)updateTapCount:(SPTouch *)touch
[_lastTaps addObject:[[touch copy] autorelease]];
}

#pragma mark Current Touches

- (SPTouch *)currentTouchWithID:(size_t)touchID
- (void)removeEndedTouches
{
for (SPTouch *touch in _currentTouches)
if (touch.touchID == touchID)
return touch;

return nil;
[_currentTouches filterUsingPredicate:
[NSPredicate predicateWithBlock:^ BOOL (SPTouch *touch, NSDictionary *bindings)
{
return touch.phase != SPTouchPhaseEnded &&
touch.phase != SPTouchPhaseCancelled;
}]];
}

@end

0 comments on commit f3d44f0

Please sign in to comment.