// // GiftAnimationManager.m // YuMi // // Created by P on 2024/12/9. // #import "GiftAnimationManager.h" #import "GiftComboManager.h" #import "GiftAnimationHelper.h" #import "GiftReceiveInfoModel.h" @interface GiftAnimationManager () @property (nonatomic, strong) dispatch_source_t giftTimer; @property (nonatomic, strong) dispatch_queue_t queue; @property (nonatomic, strong) NSMutableArray *giftQueue; @property (nonatomic, strong) GiftAnimationHelper *animationHelper; @end @implementation GiftAnimationManager - (void)dealloc { if (_queue) { _queue = NULL; } if (_giftTimer) { dispatch_source_cancel(_giftTimer); _giftTimer = nil; } } - (instancetype)initWithContainerView:(UIView *)containerView { self = [super init]; if (self) { _containerView = containerView; _giftQueue = [NSMutableArray array]; _animationHelper = [[GiftAnimationHelper alloc] init]; _animationInterval = 0.2; _comboAnimationDelay = 0.2; _standardAnimationDelay = 0.3; _queue = dispatch_queue_create("com.GiftAnimationManager.queue", DISPATCH_QUEUE_SERIAL); } return self; } - (void)startGiftQueue { if (self.giftTimer) { return; } dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)); dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, self.animationInterval * NSEC_PER_SEC, 0.01 * NSEC_PER_SEC); @kWeakify(self); dispatch_source_set_event_handler(timer, ^{ @kStrongify(self); [self processNextGift]; }); // dispatch_async(dispatch_get_main_queue(), ^{ // @kStrongify(self); // [self processNextGift]; // }); dispatch_resume(timer); self.giftTimer = timer; } - (void)processNextGift { dispatch_async(self.queue, ^{ if (self.giftQueue.count == 0) { NSLog(@"[Combo effect] 📭 动画队列为空,停止处理"); [self stopGiftQueue]; return; } GiftReceiveInfoModel *giftInfo = self.giftQueue.firstObject; if (!giftInfo) { NSLog(@"[Combo effect] ⚠️ 队列第一个元素为空,跳过处理"); return; } // 检查并修复连击计数 if (giftInfo.comboCount < 1) { NSLog(@"[Combo effect] 🚨 动画处理中检测到连击计数异常 - comboCount: %ld", (long)giftInfo.comboCount); giftInfo.comboCount = 1; NSLog(@"[Combo effect] 🔧 修复动画连击计数为 1"); } NSLog(@"[Combo effect] 🎬 开始处理动画 - giftId: %ld, combo: %ld", (long)giftInfo.gift.giftId, (long)giftInfo.comboCount); // 在同一线程中移除元素 [self.giftQueue xpSafeRemoveObjectAtIndex:0]; NSLog(@"[Combo effect] 📊 移除后动画队列数量: %ld", (long)self.giftQueue.count); @kWeakify(self); dispatch_async(dispatch_get_main_queue(), ^{ @kStrongify(self); if (self) { [self distributeGiftAnimation:giftInfo]; } else { NSLog(@"[Combo effect] ⚠️ self已释放,跳过动画分发"); } }); }); } - (void)distributeGiftAnimation:(GiftReceiveInfoModel *)giftInfo { NSLog(@"[Combo effect] 🎯 开始分发动画 - uid: %@", giftInfo.uid); NSArray *targetUids = [self resolveTargetUids:giftInfo]; NSLog(@"[Combo effect] 🎯 目标用户数量: %ld", (long)targetUids.count); CGPoint startPoint = CGPointZero; BOOL isComboAnimation = [self shouldUseComboAnimationForSender:giftInfo.uid]; NSLog(@"[Combo effect] 🎯 是否使用连击动画: %@", isComboAnimation ? @"YES" : @"NO"); if (isComboAnimation) { startPoint = [self comboAnimationStartPoint]; NSLog(@"[Combo effect] 🎯 使用连击动画起点: %@", NSStringFromCGPoint(startPoint)); } else { startPoint = [self calculateAnimationPoint:giftInfo.uid isEndPoint:NO]; NSLog(@"[Combo effect] 🎯 使用普通动画起点: %@", NSStringFromCGPoint(startPoint)); } NSTimeInterval delay = isComboAnimation ? self.comboAnimationDelay : self.standardAnimationDelay; NSLog(@"[Combo effect] 🎯 动画延迟时间: %.2f", delay); for (NSString *targetUid in targetUids) { CGPoint endPoint = [self calculateAnimationPoint:targetUid isEndPoint:YES]; NSLog(@"[Combo effect] 🎯 为目标用户 %@ 创建动画 - 终点: %@", targetUid, NSStringFromCGPoint(endPoint)); [self scheduleAnimationWithDelay:delay giftInfo:giftInfo.gift startPoint:startPoint endPoint:endPoint isComboAnimation:isComboAnimation]; } } - (NSArray *)resolveTargetUids:(GiftReceiveInfoModel *)giftInfo { if (!giftInfo) { return @[]; } if (giftInfo.isLuckyBagGift) { return @[giftInfo.targetUid]; } if (giftInfo.targetUids.count > 0) { return [giftInfo.targetUids valueForKey:@"stringValue"]; } if (giftInfo.targetUsers) { NSArray *uidDatas = [self ensureArrayContainsOnlyStrings:[giftInfo.targetUsers valueForKeyPath:@"uid"]]; return uidDatas;//[giftInfo.targetUsers valueForKeyPath:@"uid"]; } return [NSString isEmpty:giftInfo.targetUid] ? @[] : @[giftInfo.targetUid]; } - (NSArray *)ensureArrayContainsOnlyStrings:(NSArray *)inputArray { // 用于存放最终结果 NSMutableArray *resultArray = [NSMutableArray array]; for (id item in inputArray) { if ([item isKindOfClass:[NSString class]]) { // 如果是 NSString,直接添加到结果数组 [resultArray addObject:item]; } else if ([item isKindOfClass:[NSNumber class]]) { // 如果是 NSNumber,转换为 NSString 后添加 [resultArray addObject:[item stringValue]]; } else { // 对于非 NSString 或 NSNumber 的类型,可以选择忽略或者抛出异常 // NSLog(@"Warning: Unsupported item type: %@", [item class]); } } return [resultArray copy]; // 返回不可变数组 } - (CGPoint)calculateAnimationPoint:(NSString *)uid isEndPoint:(BOOL)isEndPoint { if (uid.length == 0) { return [self fallbackPointForEndPoint:isEndPoint]; } CGPoint point = [self.delegate animationPointAtStageViewByUid:uid]; if (!CGPointEqualToPoint(point, CGPointZero)) { return point; } return [self fallbackPointForEndPoint:isEndPoint]; } - (void)scheduleAnimationWithDelay:(NSTimeInterval)delay giftInfo:(GiftInfoModel *)giftInfo startPoint:(CGPoint)startPoint endPoint:(CGPoint)endPoint isComboAnimation:(BOOL)isComboAnimation { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, delay * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ [self.animationHelper beginGiftAnimation:giftInfo.giftUrl startPoint:startPoint endPoint:endPoint isGiftCombing:isComboAnimation toTargetView:self.containerView]; }); } - (void)stopGiftQueue { if (self.giftTimer) { // 取消定时器 dispatch_source_cancel(self.giftTimer); // 设置取消回调,在资源完全释放后将 timer 置为 nil dispatch_source_set_cancel_handler(self.giftTimer, ^{ self.giftTimer = nil; }); } } - (void)enqueueGift:(GiftReceiveInfoModel *)giftInfo { if (!giftInfo) { NSLog(@"[Combo effect] ⚠️ 礼物信息为空,跳过动画队列"); return; } NSLog(@"[Combo effect] 🎬 添加礼物到动画队列 - giftId: %ld, combo: %ld", (long)giftInfo.gift.giftId, (long)giftInfo.comboCount); dispatch_async(self.queue, ^{ [self.giftQueue addObject:giftInfo]; NSLog(@"[Combo effect] 📊 动画队列当前数量: %ld", (long)self.giftQueue.count); [self startGiftQueue]; }); } // Helper methods - (BOOL)shouldUseComboAnimationForSender:(NSString *)uid { return [[GiftComboManager sharedManager] isActive] && [uid isEqualToString:[AccountInfoStorage instance].getUid]; } - (CGPoint)fallbackPointForEndPoint:(BOOL)isEndPoint { CGFloat x = [UIScreen mainScreen].bounds.size.width / 2; if (isEndPoint) { x += 30; } return CGPointMake(x, 44 + kSafeAreaTopHeight); } - (CGPoint)comboAnimationStartPoint { CGFloat x = 0; if (isMSRTL()) { x = kGetScaleWidth(90); } else { x = KScreenWidth - kGetScaleWidth(90); } return CGPointMake(x, KScreenHeight - kSafeAreaBottomHeight - kGetScaleWidth(140)); } @end