You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
806 lines
20 KiB
806 lines
20 KiB
/** |
|
Copyright (c) 2014-present, Facebook, Inc. |
|
All rights reserved. |
|
|
|
This source code is licensed under the BSD-style license found in the |
|
LICENSE file in the root directory of this source tree. An additional grant |
|
of patent rights can be found in the PATENTS file in the same directory. |
|
*/ |
|
|
|
#import "POPAnimator.h" |
|
#import "POPAnimatorPrivate.h" |
|
|
|
#import <list> |
|
#import <vector> |
|
|
|
#if !TARGET_OS_IPHONE |
|
#import <libkern/OSAtomic.h> |
|
#endif |
|
|
|
#import <objc/objc-auto.h> |
|
|
|
#import <QuartzCore/QuartzCore.h> |
|
|
|
#import "POPAnimation.h" |
|
#import "POPAnimationExtras.h" |
|
#import "POPBasicAnimationInternal.h" |
|
#import "POPDecayAnimation.h" |
|
|
|
using namespace std; |
|
using namespace POP; |
|
|
|
#define ENABLE_LOGGING_DEBUG 0 |
|
#define ENABLE_LOGGING_INFO 0 |
|
|
|
#if ENABLE_LOGGING_DEBUG |
|
#define FBLogAnimDebug NSLog |
|
#else |
|
#define FBLogAnimDebug(...) |
|
#endif |
|
|
|
#if ENABLE_LOGGING_INFO |
|
#define FBLogAnimInfo NSLog |
|
#else |
|
#define FBLogAnimInfo(...) |
|
#endif |
|
|
|
class POPAnimatorItem |
|
{ |
|
public: |
|
id __weak object; |
|
NSString *key; |
|
POPAnimation *animation; |
|
NSInteger refCount; |
|
id __unsafe_unretained unretainedObject; |
|
|
|
POPAnimatorItem(id o, NSString *k, POPAnimation *a) POP_NOTHROW |
|
{ |
|
object = o; |
|
key = [k copy]; |
|
animation = a; |
|
refCount = 1; |
|
unretainedObject = o; |
|
} |
|
|
|
~POPAnimatorItem() |
|
{ |
|
} |
|
|
|
bool operator==(const POPAnimatorItem& o) const { |
|
return unretainedObject == o.unretainedObject && animation == o.animation && [key isEqualToString:o.key]; |
|
} |
|
|
|
}; |
|
|
|
typedef std::shared_ptr<POPAnimatorItem> POPAnimatorItemRef; |
|
typedef std::shared_ptr<const POPAnimatorItem> POPAnimatorItemConstRef; |
|
|
|
typedef std::list<POPAnimatorItemRef> POPAnimatorItemList; |
|
typedef POPAnimatorItemList::iterator POPAnimatorItemListIterator; |
|
typedef POPAnimatorItemList::const_iterator POPAnimatorItemListConstIterator; |
|
|
|
static BOOL _disableBackgroundThread = YES; |
|
|
|
@interface POPAnimator () |
|
{ |
|
#if TARGET_OS_IPHONE |
|
CADisplayLink *_displayLink; |
|
#else |
|
CVDisplayLinkRef _displayLink; |
|
int32_t _enqueuedRender; |
|
#endif |
|
POPAnimatorItemList _list; |
|
CFMutableDictionaryRef _dict; |
|
NSMutableArray *_observers; |
|
POPAnimatorItemList _pendingList; |
|
CFRunLoopObserverRef _pendingListObserver; |
|
CFTimeInterval _slowMotionStartTime; |
|
CFTimeInterval _slowMotionLastTime; |
|
CFTimeInterval _slowMotionAccumulator; |
|
CFTimeInterval _beginTime; |
|
OSSpinLock _lock; |
|
BOOL _disableDisplayLink; |
|
} |
|
@end |
|
|
|
@implementation POPAnimator |
|
@synthesize delegate = _delegate; |
|
@synthesize disableDisplayLink = _disableDisplayLink; |
|
@synthesize beginTime = _beginTime; |
|
|
|
#if !TARGET_OS_IPHONE |
|
static CVReturn displayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp *now, const CVTimeStamp *outputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *context) |
|
{ |
|
if (_disableBackgroundThread) { |
|
__unsafe_unretained POPAnimator *pa = (__bridge POPAnimator *)context; |
|
int32_t* enqueuedRender = &pa->_enqueuedRender; |
|
if (*enqueuedRender == 0) { |
|
OSAtomicIncrement32(enqueuedRender); |
|
dispatch_async(dispatch_get_main_queue(), ^{ |
|
[(__bridge POPAnimator*)context render]; |
|
OSAtomicDecrement32(enqueuedRender); |
|
}); |
|
} |
|
} else { |
|
[(__bridge POPAnimator*)context render]; |
|
} |
|
return kCVReturnSuccess; |
|
} |
|
#endif |
|
|
|
// call while holding lock |
|
static void updateDisplayLink(POPAnimator *self) |
|
{ |
|
BOOL paused = (0 == self->_observers.count && self->_list.empty()) || self->_disableDisplayLink; |
|
|
|
#if TARGET_OS_IPHONE |
|
if (paused != self->_displayLink.paused) { |
|
FBLogAnimInfo(paused ? @"pausing display link" : @"unpausing display link"); |
|
self->_displayLink.paused = paused; |
|
} |
|
#else |
|
if (paused == CVDisplayLinkIsRunning(self->_displayLink)) { |
|
FBLogAnimInfo(paused ? @"pausing display link" : @"unpausing display link"); |
|
if (paused) { |
|
CVDisplayLinkStop(self->_displayLink); |
|
} else { |
|
CVDisplayLinkStart(self->_displayLink); |
|
} |
|
} |
|
#endif |
|
} |
|
|
|
static void updateAnimatable(id obj, POPPropertyAnimationState *anim, bool shouldAvoidExtraneousWrite = false) |
|
{ |
|
// handle user-initiated stop or pause; halt animation |
|
if (!anim->active || anim->paused) |
|
return; |
|
|
|
if (anim->hasValue()) { |
|
pop_animatable_write_block write = anim->property.writeBlock; |
|
if (NULL == write) |
|
return; |
|
|
|
// current animation value |
|
VectorRef currentVec = anim->currentValue(); |
|
|
|
if (!anim->additive) { |
|
|
|
// if avoiding extraneous writes and we have a read block defined |
|
if (shouldAvoidExtraneousWrite) { |
|
|
|
pop_animatable_read_block read = anim->property.readBlock; |
|
if (read) { |
|
// compare current animation value with object value |
|
Vector4r currentValue = currentVec->vector4r(); |
|
Vector4r objectValue = read_values(read, obj, anim->valueCount); |
|
if (objectValue == currentValue) { |
|
return; |
|
} |
|
} |
|
} |
|
|
|
// update previous values; support animation convergence |
|
anim->previous2Vec = anim->previousVec; |
|
anim->previousVec = currentVec; |
|
|
|
// write value |
|
write(obj, currentVec->data()); |
|
if (anim->tracing) { |
|
[anim->tracer writePropertyValue:POPBox(currentVec, anim->valueType, true)]; |
|
} |
|
} else { |
|
pop_animatable_read_block read = anim->property.readBlock; |
|
NSCAssert(read, @"additive requires an animatable property readBlock"); |
|
if (NULL == read) { |
|
return; |
|
} |
|
|
|
// object value |
|
Vector4r objectValue = read_values(read, obj, anim->valueCount); |
|
|
|
// current value |
|
Vector4r currentValue = currentVec->vector4r(); |
|
|
|
// determine animation change |
|
if (anim->previousVec) { |
|
Vector4r previousValue = anim->previousVec->vector4r(); |
|
currentValue -= previousValue; |
|
} |
|
|
|
// avoid writing no change |
|
if (shouldAvoidExtraneousWrite && currentValue == Vector4r::Zero()) { |
|
return; |
|
} |
|
|
|
// add to object value |
|
currentValue += objectValue; |
|
|
|
// update previous values; support animation convergence |
|
anim->previous2Vec = anim->previousVec; |
|
anim->previousVec = currentVec; |
|
|
|
// write value |
|
write(obj, currentValue.data()); |
|
if (anim->tracing) { |
|
[anim->tracer writePropertyValue:POPBox(currentVec, anim->valueType, true)]; |
|
} |
|
} |
|
} |
|
} |
|
|
|
static void applyAnimationTime(id obj, POPAnimationState *state, CFTimeInterval time) |
|
{ |
|
if (!state->advanceTime(time, obj)) { |
|
return; |
|
} |
|
|
|
POPPropertyAnimationState *ps = dynamic_cast<POPPropertyAnimationState*>(state); |
|
if (NULL != ps) { |
|
updateAnimatable(obj, ps); |
|
} |
|
|
|
state->delegateApply(); |
|
} |
|
|
|
static void applyAnimationToValue(id obj, POPAnimationState *state) |
|
{ |
|
POPPropertyAnimationState *ps = dynamic_cast<POPPropertyAnimationState*>(state); |
|
|
|
if (NULL != ps) { |
|
|
|
// finalize progress |
|
ps->finalizeProgress(); |
|
|
|
// write to value, updating only if needed |
|
updateAnimatable(obj, ps, true); |
|
} |
|
|
|
state->delegateApply(); |
|
} |
|
|
|
static POPAnimation *deleteDictEntry(POPAnimator *self, id __unsafe_unretained obj, NSString *key, BOOL cleanup = YES) |
|
{ |
|
POPAnimation *anim = nil; |
|
|
|
// lock |
|
OSSpinLockLock(&self->_lock); |
|
|
|
NSMutableDictionary *keyAnimationsDict = (__bridge id)CFDictionaryGetValue(self->_dict, (__bridge void *)obj); |
|
if (keyAnimationsDict) { |
|
|
|
anim = keyAnimationsDict[key]; |
|
if (anim) { |
|
|
|
// remove key |
|
[keyAnimationsDict removeObjectForKey:key]; |
|
|
|
// cleanup empty dictionaries |
|
if (cleanup && 0 == keyAnimationsDict.count) { |
|
CFDictionaryRemoveValue(self->_dict, (__bridge void *)obj); |
|
} |
|
} |
|
} |
|
|
|
// unlock |
|
OSSpinLockUnlock(&self->_lock); |
|
return anim; |
|
} |
|
|
|
static void stopAndCleanup(POPAnimator *self, POPAnimatorItemRef item, bool shouldRemove, bool finished) |
|
{ |
|
// remove |
|
if (shouldRemove) { |
|
deleteDictEntry(self, item->unretainedObject, item->key); |
|
} |
|
|
|
// stop |
|
POPAnimationState *state = POPAnimationGetState(item->animation); |
|
state->stop(shouldRemove, finished); |
|
|
|
if (shouldRemove) { |
|
// lock |
|
OSSpinLockLock(&self->_lock); |
|
|
|
// find item in list |
|
// may have already been removed on animationDidStop: |
|
POPAnimatorItemListIterator find_iter = find(self->_list.begin(), self->_list.end(), item); |
|
BOOL found = find_iter != self->_list.end(); |
|
|
|
if (found) { |
|
self->_list.erase(find_iter); |
|
} |
|
|
|
// unlock |
|
OSSpinLockUnlock(&self->_lock); |
|
} |
|
} |
|
|
|
+ (id)sharedAnimator |
|
{ |
|
static POPAnimator* _animator = nil; |
|
static dispatch_once_t onceToken; |
|
dispatch_once(&onceToken, ^{ |
|
_animator = [[POPAnimator alloc] init]; |
|
}); |
|
return _animator; |
|
} |
|
|
|
+ (BOOL)disableBackgroundThread |
|
{ |
|
return _disableBackgroundThread; |
|
} |
|
|
|
+ (void)setDisableBackgroundThread:(BOOL)flag |
|
{ |
|
_disableBackgroundThread = flag; |
|
} |
|
|
|
#pragma mark - Lifecycle |
|
|
|
- (id)init |
|
{ |
|
self = [super init]; |
|
if (nil == self) return nil; |
|
|
|
#if TARGET_OS_IPHONE |
|
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(render)]; |
|
_displayLink.paused = YES; |
|
[_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; |
|
#else |
|
CVDisplayLinkCreateWithActiveCGDisplays(&_displayLink); |
|
CVDisplayLinkSetOutputCallback(_displayLink, displayLinkCallback, (__bridge void *)self); |
|
#endif |
|
|
|
_dict = POPDictionaryCreateMutableWeakPointerToStrongObject(5); |
|
_lock = OS_SPINLOCK_INIT; |
|
|
|
return self; |
|
} |
|
|
|
- (void)dealloc |
|
{ |
|
#if TARGET_OS_IPHONE |
|
[_displayLink invalidate]; |
|
#else |
|
CVDisplayLinkStop(_displayLink); |
|
CVDisplayLinkRelease(_displayLink); |
|
#endif |
|
[self _clearPendingListObserver]; |
|
} |
|
|
|
#pragma mark - Utility |
|
|
|
- (void)_processPendingList |
|
{ |
|
// rendering pending animations |
|
CFTimeInterval time = [self _currentRenderTime]; |
|
[self _renderTime:(0 != _beginTime) ? _beginTime : time items:_pendingList]; |
|
|
|
// lock |
|
OSSpinLockLock(&_lock); |
|
|
|
// clear list and observer |
|
_pendingList.clear(); |
|
[self _clearPendingListObserver]; |
|
|
|
// unlock |
|
OSSpinLockUnlock(&_lock); |
|
} |
|
|
|
- (void)_clearPendingListObserver |
|
{ |
|
if (_pendingListObserver) { |
|
CFRunLoopRemoveObserver(CFRunLoopGetMain(), _pendingListObserver, kCFRunLoopCommonModes); |
|
CFRelease(_pendingListObserver); |
|
_pendingListObserver = NULL; |
|
} |
|
} |
|
|
|
- (void)_scheduleProcessPendingList |
|
{ |
|
// see WebKit for magic numbers, eg http://trac.webkit.org/changeset/166540 |
|
static const CFIndex CATransactionCommitRunLoopOrder = 2000000; |
|
static const CFIndex POPAnimationApplyRunLoopOrder = CATransactionCommitRunLoopOrder - 1; |
|
|
|
// lock |
|
OSSpinLockLock(&_lock); |
|
|
|
if (!_pendingListObserver) { |
|
__weak POPAnimator *weakSelf = self; |
|
|
|
_pendingListObserver = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopBeforeWaiting | kCFRunLoopExit, false, POPAnimationApplyRunLoopOrder, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { |
|
[weakSelf _processPendingList]; |
|
}); |
|
|
|
if (_pendingListObserver) { |
|
CFRunLoopAddObserver(CFRunLoopGetMain(), _pendingListObserver, kCFRunLoopCommonModes); |
|
} |
|
} |
|
|
|
// unlock |
|
OSSpinLockUnlock(&_lock); |
|
} |
|
|
|
- (void)_renderTime:(CFTimeInterval)time items:(std::list<POPAnimatorItemRef>)items |
|
{ |
|
// begin transaction with actions disabled |
|
[CATransaction begin]; |
|
[CATransaction setDisableActions:YES]; |
|
|
|
// notify delegate |
|
__strong __typeof__(_delegate) delegate = _delegate; |
|
[delegate animatorWillAnimate:self]; |
|
|
|
// lock |
|
OSSpinLockLock(&_lock); |
|
|
|
// count active animations |
|
const NSUInteger count = items.size(); |
|
if (0 == count) { |
|
// unlock |
|
OSSpinLockUnlock(&_lock); |
|
} else { |
|
// copy list into vector |
|
std::vector<POPAnimatorItemRef> vector{ items.begin(), items.end() }; |
|
|
|
// unlock |
|
OSSpinLockUnlock(&_lock); |
|
|
|
for (auto item : vector) { |
|
[self _renderTime:time item:item]; |
|
} |
|
} |
|
|
|
// notify observers |
|
for (id observer in self.observers) { |
|
[observer animatorDidAnimate:(id)self]; |
|
} |
|
|
|
// lock |
|
OSSpinLockLock(&_lock); |
|
|
|
// update display link |
|
updateDisplayLink(self); |
|
|
|
// unlock |
|
OSSpinLockUnlock(&_lock); |
|
|
|
// notify delegate and commit |
|
[delegate animatorDidAnimate:self]; |
|
[CATransaction commit]; |
|
} |
|
|
|
- (void)_renderTime:(CFTimeInterval)time item:(POPAnimatorItemRef)item |
|
{ |
|
id obj = item->object; |
|
POPAnimation *anim = item->animation; |
|
POPAnimationState *state = POPAnimationGetState(anim); |
|
|
|
if (nil == obj) { |
|
// object exists not; stop animating |
|
NSAssert(item->unretainedObject, @"object should exist"); |
|
stopAndCleanup(self, item, true, false); |
|
} else { |
|
|
|
// start if needed |
|
state->startIfNeeded(obj, time, _slowMotionAccumulator); |
|
|
|
// only run active, not paused animations |
|
if (state->active && !state->paused) { |
|
// object exists; animate |
|
applyAnimationTime(obj, state, time); |
|
|
|
FBLogAnimDebug(@"time:%f running:%@", time, item->animation); |
|
if (state->isDone()) { |
|
// set end value |
|
applyAnimationToValue(obj, state); |
|
|
|
state->repeatCount--; |
|
if (state->repeatForever || state->repeatCount > 0) { |
|
if ([anim isKindOfClass:[POPPropertyAnimation class]]) { |
|
POPPropertyAnimation *propAnim = (POPPropertyAnimation *)anim; |
|
id oldFromValue = propAnim.fromValue; |
|
propAnim.fromValue = propAnim.toValue; |
|
|
|
if (state->autoreverses) { |
|
if (state->tracing) { |
|
[state->tracer autoreversed]; |
|
} |
|
|
|
if (state->type == kPOPAnimationDecay) { |
|
POPDecayAnimation *decayAnimation = (POPDecayAnimation *)propAnim; |
|
decayAnimation.velocity = [decayAnimation reversedVelocity]; |
|
} else { |
|
propAnim.toValue = oldFromValue; |
|
} |
|
} else { |
|
if (state->type == kPOPAnimationDecay) { |
|
POPDecayAnimation *decayAnimation = (POPDecayAnimation *)propAnim; |
|
id originalVelocity = decayAnimation.originalVelocity; |
|
decayAnimation.velocity = originalVelocity; |
|
} else { |
|
propAnim.fromValue = oldFromValue; |
|
} |
|
} |
|
} |
|
|
|
state->stop(NO, NO); |
|
state->reset(true); |
|
|
|
state->startIfNeeded(obj, time, _slowMotionAccumulator); |
|
} else { |
|
stopAndCleanup(self, item, state->removedOnCompletion, YES); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
#pragma mark - API |
|
|
|
- (NSArray *)observers |
|
{ |
|
// lock |
|
OSSpinLockLock(&_lock); |
|
|
|
// get observers |
|
NSArray *observers = 0 != _observers.count ? [_observers copy] : nil; |
|
|
|
// unlock |
|
OSSpinLockUnlock(&_lock); |
|
return observers; |
|
} |
|
|
|
- (void)addAnimation:(POPAnimation *)anim forObject:(id)obj key:(NSString *)key |
|
{ |
|
if (!anim || !obj) { |
|
return; |
|
} |
|
|
|
// support arbitrarily many nil keys |
|
if (!key) { |
|
key = [[NSUUID UUID] UUIDString]; |
|
} |
|
|
|
// lock |
|
OSSpinLockLock(&_lock); |
|
|
|
// get key, animation dict associated with object |
|
NSMutableDictionary *keyAnimationDict = (__bridge id)CFDictionaryGetValue(_dict, (__bridge void *)obj); |
|
|
|
// update associated animation state |
|
if (nil == keyAnimationDict) { |
|
keyAnimationDict = [NSMutableDictionary dictionary]; |
|
CFDictionarySetValue(_dict, (__bridge void *)obj, (__bridge void *)keyAnimationDict); |
|
} else { |
|
// if the animation instance already exists, avoid cancelling only to restart |
|
POPAnimation *existingAnim = keyAnimationDict[key]; |
|
if (existingAnim) { |
|
// unlock |
|
OSSpinLockUnlock(&_lock); |
|
|
|
if (existingAnim == anim) { |
|
return; |
|
} |
|
[self removeAnimationForObject:obj key:key cleanupDict:NO]; |
|
|
|
// lock |
|
OSSpinLockLock(&_lock); |
|
} |
|
} |
|
keyAnimationDict[key] = anim; |
|
|
|
// create entry after potential removal |
|
POPAnimatorItemRef item(new POPAnimatorItem(obj, key, anim)); |
|
|
|
// add to list and pending list |
|
_list.push_back(item); |
|
_pendingList.push_back(item); |
|
|
|
// support animation re-use, reset all animation state |
|
POPAnimationGetState(anim)->reset(true); |
|
|
|
// update display link |
|
updateDisplayLink(self); |
|
|
|
// unlock |
|
OSSpinLockUnlock(&_lock); |
|
|
|
// schedule runloop processing of pending animations |
|
[self _scheduleProcessPendingList]; |
|
} |
|
|
|
- (void)removeAllAnimationsForObject:(id)obj |
|
{ |
|
// lock |
|
OSSpinLockLock(&_lock); |
|
|
|
NSArray *animations = [(__bridge id)CFDictionaryGetValue(_dict, (__bridge void *)obj) allValues]; |
|
CFDictionaryRemoveValue(_dict, (__bridge void *)obj); |
|
|
|
// unlock |
|
OSSpinLockUnlock(&_lock); |
|
|
|
if (0 == animations.count) { |
|
return; |
|
} |
|
|
|
NSHashTable *animationSet = [[NSHashTable alloc] initWithOptions:NSHashTableObjectPointerPersonality capacity:animations.count]; |
|
for (id animation in animations) { |
|
[animationSet addObject:animation]; |
|
} |
|
|
|
// lock |
|
OSSpinLockLock(&_lock); |
|
|
|
POPAnimatorItemRef item; |
|
for (auto iter = _list.begin(); iter != _list.end();) { |
|
item = *iter; |
|
if(![animationSet containsObject:item->animation]) { |
|
iter++; |
|
} else { |
|
iter = _list.erase(iter); |
|
} |
|
} |
|
|
|
// unlock |
|
OSSpinLockUnlock(&_lock); |
|
|
|
for (POPAnimation *anim in animations) { |
|
POPAnimationState *state = POPAnimationGetState(anim); |
|
state->stop(true, !state->active); |
|
} |
|
} |
|
|
|
- (void)removeAnimationForObject:(id)obj key:(NSString *)key cleanupDict:(BOOL)cleanupDict |
|
{ |
|
POPAnimation *anim = deleteDictEntry(self, obj, key, cleanupDict); |
|
if (nil == anim) { |
|
return; |
|
} |
|
|
|
// lock |
|
OSSpinLockLock(&_lock); |
|
|
|
// remove from list |
|
POPAnimatorItemRef item; |
|
for (auto iter = _list.begin(); iter != _list.end();) { |
|
item = *iter; |
|
if(anim == item->animation) { |
|
_list.erase(iter); |
|
break; |
|
} else { |
|
iter++; |
|
} |
|
} |
|
|
|
// remove from pending list |
|
for (auto iter = _pendingList.begin(); iter != _pendingList.end();) { |
|
item = *iter; |
|
if(anim == item->animation) { |
|
_pendingList.erase(iter); |
|
break; |
|
} else { |
|
iter++; |
|
} |
|
} |
|
|
|
// unlock |
|
OSSpinLockUnlock(&_lock); |
|
|
|
// stop animation and callout |
|
POPAnimationState *state = POPAnimationGetState(anim); |
|
state->stop(true, (!state->active && !state->paused)); |
|
} |
|
|
|
- (void)removeAnimationForObject:(id)obj key:(NSString *)key |
|
{ |
|
[self removeAnimationForObject:obj key:key cleanupDict:YES]; |
|
} |
|
|
|
- (NSArray *)animationKeysForObject:(id)obj |
|
{ |
|
// lock |
|
OSSpinLockLock(&_lock); |
|
|
|
// get keys |
|
NSArray *keys = [(__bridge id)CFDictionaryGetValue(_dict, (__bridge void *)obj) allKeys]; |
|
|
|
// unlock |
|
OSSpinLockUnlock(&_lock); |
|
return keys; |
|
} |
|
|
|
- (id)animationForObject:(id)obj key:(NSString *)key |
|
{ |
|
// lock |
|
OSSpinLockLock(&_lock); |
|
|
|
// lookup animation |
|
NSDictionary *keyAnimationsDict = (__bridge id)CFDictionaryGetValue(_dict, (__bridge void *)obj); |
|
POPAnimation *animation = keyAnimationsDict[key]; |
|
|
|
// unlock |
|
OSSpinLockUnlock(&_lock); |
|
return animation; |
|
} |
|
|
|
- (CFTimeInterval)_currentRenderTime |
|
{ |
|
CFTimeInterval time = CACurrentMediaTime(); |
|
|
|
#if TARGET_IPHONE_SIMULATOR |
|
// support slow-motion animations |
|
time += _slowMotionAccumulator; |
|
float f = POPAnimationDragCoefficient(); |
|
|
|
if (f > 1.0) { |
|
if (!_slowMotionStartTime) { |
|
_slowMotionStartTime = time; |
|
} else { |
|
time = (time - _slowMotionStartTime) / f + _slowMotionStartTime; |
|
_slowMotionLastTime = time; |
|
} |
|
} else if (_slowMotionStartTime) { |
|
CFTimeInterval dt = (_slowMotionLastTime - time); |
|
time += dt; |
|
_slowMotionAccumulator += dt; |
|
_slowMotionStartTime = 0; |
|
} |
|
#endif |
|
|
|
return time; |
|
} |
|
|
|
- (void)render |
|
{ |
|
CFTimeInterval time = [self _currentRenderTime]; |
|
[self renderTime:time]; |
|
} |
|
|
|
- (void)renderTime:(CFTimeInterval)time |
|
{ |
|
[self _renderTime:time items:_list]; |
|
} |
|
|
|
- (void)addObserver:(id<POPAnimatorObserving>)observer |
|
{ |
|
NSAssert(nil != observer, @"attempting to add nil %@ observer", self); |
|
if (nil == observer) { |
|
return; |
|
} |
|
|
|
// lock |
|
OSSpinLockLock(&_lock); |
|
|
|
if (!_observers) { |
|
// use ordered collection for deterministic callout |
|
_observers = [[NSMutableArray alloc] initWithCapacity:1]; |
|
} |
|
|
|
[_observers addObject:observer]; |
|
updateDisplayLink(self); |
|
|
|
// unlock |
|
OSSpinLockUnlock(&_lock); |
|
} |
|
|
|
- (void)removeObserver:(id<POPAnimatorObserving>)observer |
|
{ |
|
NSAssert(nil != observer, @"attempting to remove nil %@ observer", self); |
|
if (nil == observer) { |
|
return; |
|
} |
|
|
|
// lock |
|
OSSpinLockLock(&_lock); |
|
|
|
[_observers removeObject:observer]; |
|
updateDisplayLink(self); |
|
|
|
// unlock |
|
OSSpinLockUnlock(&_lock); |
|
} |
|
|
|
@end
|
|
|