Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(ios): resolve animation thread race & add protection for running script #4205

Merged
merged 1 commit into from
Feb 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 22 additions & 4 deletions ios/sdk/base/executors/HippyJSCExecutor.mm
Original file line number Diff line number Diff line change
Expand Up @@ -744,7 +744,8 @@ static void handleJsExcepiton(std::shared_ptr<Scope> scope) {
HippyPerformanceLogger *performanceLogger,
JSGlobalContextRef ctx) {
@autoreleasepool {
HippyLogInfo(@"load script begin, length %zd for url %@", [script length], [sourceURL absoluteString]);
NSString *absoluteSourceUrl = [sourceURL absoluteString];
HippyLogInfo(@"load script begin, length %zd for url %@", [script length], absoluteSourceUrl);
if (isCommonBundle) {
[performanceLogger markStartForTag:HippyPLCommonScriptExecution];
} else {
Expand All @@ -753,9 +754,26 @@ static void handleJsExcepiton(std::shared_ptr<Scope> scope) {

JSValueRef jsError = NULL;
NSString *scriptText = [[NSString alloc] initWithData:script encoding:NSUTF8StringEncoding];
if (!absoluteSourceUrl) {
HippyLogError(@"Missing bundle URL for script execution");
return [NSError errorWithDomain:HippyErrorDomain
code:NSFileNoSuchFileError
userInfo:@{
NSLocalizedDescriptionKey: @"JavaScript bundle URL is required",
}];
}

if (!scriptText) {
HippyLogError(@"Script decoding failed, data length: %lu bytes", (unsigned long)script.length);
return [NSError errorWithDomain:HippyErrorDomain
code:NSFileReadInapplicableStringEncodingError
userInfo:@{
NSLocalizedDescriptionKey: @"Failed to decode script content",
}];
}
JSStringRef execJSString = JSStringCreateWithCFString((__bridge CFStringRef)scriptText);
//JSStringCreateWithUTF8CString((const char *)script.bytes);
JSStringRef bundleURL = JSStringCreateWithCFString((__bridge CFStringRef)sourceURL.absoluteString);
JSStringRef bundleURL = JSStringCreateWithCFString((__bridge CFStringRef)absoluteSourceUrl);


NSLock *lock = jslock();
BOOL lockSuccess = [lock lockBeforeDate:[NSDate dateWithTimeIntervalSinceNow:1]];
Expand All @@ -773,7 +791,7 @@ static void handleJsExcepiton(std::shared_ptr<Scope> scope) {

NSError *error = jsError ? HippyNSErrorFromJSErrorRef(jsError, ctx) : nil;
// HIPPY_PROFILE_END_EVENT(0, @"js_call");
HippyLogInfo(@"load script end,length %zd for url %@, error %@", [script length], [sourceURL absoluteString], [error description]);
HippyLogInfo(@"load script end,length %zd for url %@, error %@", [script length], absoluteSourceUrl, [error description]);

return error;
}
Expand Down
50 changes: 43 additions & 7 deletions ios/sdk/module/animation2/HippyNextAnimationModule.m
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,14 @@

@interface HippyNextAnimationModule () <HPOPAnimationDelegate, HPOPAnimatorDelegate, HippyNextAnimationControlDelegate>

/// Map of id-animation
@property (atomic, strong) NSMutableDictionary *animationById;
/// Map of hippyTag-Params
@property (atomic, strong) NSMutableDictionary<NSNumber *, HippyNextAnimationViewParams *> *paramsByHippyTag;
/// Map of AnimationId-Params
@property (atomic, strong) NSMutableDictionary<NSNumber *, NSMutableArray<HippyNextAnimationViewParams *> *> *paramsByAnimationId;
/// Lock for animationById & paramsByHippyTag
@property (nonatomic, strong) NSLock *dataAccessLock;
/// Map of id-animation, may access only in shadow and main thread
@property (nonatomic, strong) NSMutableDictionary *animationById;
/// Map of hippyTag-Params, may access only in shadow and main thread
@property (nonatomic, strong) NSMutableDictionary<NSNumber *, HippyNextAnimationViewParams *> *paramsByHippyTag;
/// Map of AnimationId-Params, access only in shadow thread
@property (nonatomic, strong) NSMutableDictionary<NSNumber *, NSMutableArray<HippyNextAnimationViewParams *> *> *paramsByAnimationId;

/// whether should relayout on next frame
@property (atomic, assign) BOOL shouldCallUIManagerToUpdateLayout;
Expand Down Expand Up @@ -67,15 +69,18 @@ - (dispatch_queue_t)methodQueue {
}

- (void)invalidate {
[self.dataAccessLock lock];
[self.animationById removeAllObjects];
[self.paramsByHippyTag removeAllObjects];
[self.dataAccessLock unlock];
[self.paramsByAnimationId removeAllObjects];
[HPOPAnimator.sharedAnimator removeAnimatorDelegate:self];
}

- (instancetype)init {
self = [super init];
if (self) {
_dataAccessLock = [[NSLock alloc] init];
_animationById = [NSMutableDictionary dictionary];
_paramsByHippyTag = [NSMutableDictionary dictionary];
_paramsByAnimationId = [NSMutableDictionary dictionary];
Expand All @@ -101,7 +106,9 @@ - (instancetype)init {
anim.controlDelegate = self;

// save animation
[self.dataAccessLock lock];
self.animationById[animationId] = anim;
[self.dataAccessLock unlock];
}

HIPPY_EXPORT_METHOD(createAnimationSet:(NSNumber *__nonnull)animationId
Expand All @@ -112,7 +119,9 @@ - (instancetype)init {
for (NSDictionary * info in children) {
NSNumber *subAnimationId = info[@"animationId"];
BOOL follow = [info[@"follow"] boolValue];
[self.dataAccessLock lock];
HippyNextAnimation *ani = self.animationById[subAnimationId];
[self.dataAccessLock unlock];
if (ani == nil) {
HippyAssert(ani != nil, @"create group animation but use illege sub animaiton");
return;
Expand All @@ -125,11 +134,15 @@ - (instancetype)init {
group.animations = anis;
group.animationId = animationId;
group.delegate = self;
[self.dataAccessLock lock];
self.animationById[animationId] = group;
[self.dataAccessLock unlock];
}

HIPPY_EXPORT_METHOD(startAnimation:(NSNumber *__nonnull)animationId) {
[self.dataAccessLock lock];
HippyNextAnimation *anim = self.animationById[animationId];
[self.dataAccessLock unlock];
if (HippyNextAnimationStartedState == anim.state) {
return;
}
Expand Down Expand Up @@ -172,19 +185,25 @@ - (instancetype)init {
}

HIPPY_EXPORT_METHOD(pauseAnimation:(NSNumber *__nonnull)animationId) {
[self.dataAccessLock lock];
HippyNextAnimation *anim = self.animationById[animationId];
[self.dataAccessLock unlock];
[anim setPausedWithoutReset:YES];
}

HIPPY_EXPORT_METHOD(resumeAnimation:(NSNumber *__nonnull)animationId) {
[self.dataAccessLock lock];
HippyNextAnimation *anim = self.animationById[animationId];
[self.dataAccessLock unlock];
[anim setPausedWithoutReset:NO];
}

HIPPY_EXPORT_METHOD(updateAnimation:(NSNumber *__nonnull)animationId
params:(NSDictionary *)params) {
if (!params) return;
[self.dataAccessLock lock];
HippyNextAnimation *anim = self.animationById[animationId];
[self.dataAccessLock unlock];
anim.state = HippyNextAnimationInitState;
[anim updateAnimation:params];

Expand All @@ -193,7 +212,9 @@ - (instancetype)init {
[p.animationIdWithPropDictionary enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key,
NSNumber * _Nonnull obj,
BOOL * _Nonnull stop) {
[self.dataAccessLock lock];
HippyNextAnimation *rcani = self.animationById[obj];
[self.dataAccessLock unlock];
if ([obj isEqual:animationId]) {
[p setValue:[rcani getPretreatedFromValueForAnimType:key] forProp:key];
HippyLogInfo(@"[Hippy_OC_Log][Animation], Update_Animation:[%@] key:[%@]", animationId, key);
Expand All @@ -206,8 +227,9 @@ - (instancetype)init {
}

HIPPY_EXPORT_METHOD(destroyAnimation:(NSNumber * __nonnull)animationId) {

[self.dataAccessLock lock];
[self.animationById removeObjectForKey:animationId];
[self.dataAccessLock unlock];
[self.paramsByAnimationId removeObjectForKey:animationId];
}

Expand All @@ -227,9 +249,13 @@ - (NSDictionary *)bindAnimaiton:(NSDictionary *)params
rootTag:rootTag];
[p parse];

[self.dataAccessLock lock];
BOOL contain = [self.paramsByHippyTag.allValues containsObject:p];
[self.dataAccessLock unlock];
[p.animationIdWithPropDictionary enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSNumber *animationId, __unused BOOL *stop) {
[self.dataAccessLock lock];
HippyNextAnimation *ani = self.animationById[animationId];
[self.dataAccessLock unlock];

if (ani.state == HippyNextAnimationFinishState) {
id tmpToValue = [ani getPretreatedToValueForAnimType:key];
Expand Down Expand Up @@ -260,7 +286,9 @@ - (NSDictionary *)bindAnimaiton:(NSDictionary *)params
}];

// record viewTag and view params
[self.dataAccessLock lock];
[self.paramsByHippyTag setObject:p forKey:viewTag];
[self.dataAccessLock unlock];

return p.updateParams;
}
Expand All @@ -269,11 +297,15 @@ - (NSDictionary *)bindAnimaiton:(NSDictionary *)params
/// - Parameter view: view with animation
- (void)connectAnimationToView:(UIView *)view {
NSNumber *hippyTag = view.hippyTag;
[self.dataAccessLock lock];
HippyNextAnimationViewParams *p = self.paramsByHippyTag[hippyTag];
[self.dataAccessLock unlock];

for (NSString *prop in p.animationIdWithPropDictionary.allKeys) {
NSNumber *animationId = p.animationIdWithPropDictionary[prop];
[self.dataAccessLock lock];
HippyNextAnimation *anim = self.animationById[animationId];
[self.dataAccessLock unlock];
if (HippyNextAnimationReadyState != anim.state) {
continue;
}
Expand Down Expand Up @@ -319,7 +351,9 @@ - (void)stopAnimationAndUpdateViewToEndState:(NSNumber * _Nonnull)animationId
__unused BOOL *stop) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
[p.animationIdWithPropDictionary enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSNumber *obj, __unused BOOL *stop1) {
[strongSelf.dataAccessLock lock];
HippyNextAnimation *ani = strongSelf.animationById[obj];
[strongSelf.dataAccessLock unlock];
if (![obj isEqual:animationId]) {
return;
}
Expand Down Expand Up @@ -525,7 +559,9 @@ - (void)hpop_animationDidStop:(HPOPAnimation *)anim finished:(BOOL)finished {
__strong __typeof(weakSelf)strongSelf = weakSelf;
for (HippyNextAnimationViewParams *p in strongSelf.paramsByAnimationId[animationId]) {
[p.animationIdWithPropDictionary enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSNumber *obj, __unused BOOL *stop1) {
[strongSelf.dataAccessLock lock];
HippyNextAnimation *ani = strongSelf.animationById[obj];
[strongSelf.dataAccessLock unlock];
if (![obj isEqual:animationId]) {
return;
}
Expand Down
Loading