797 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			Swift
		
	
	
		
		
			
		
	
	
			797 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			Swift
		
	
	
|  | 
 | |||
|  | // | |||
|  | //  MabRewardedEngine.swift | |||
|  | //  Pods | |||
|  | // | |||
|  | //  Created by 250102 on 2025/5/10. | |||
|  | // | |||
|  | 
 | |||
|  | import Foundation | |||
|  | 
 | |||
|  | // 类型别名定义 | |||
|  | typealias RewardedAdObtainer = (_ adPlatform: AdPlatform, _ adConfig: AdConfig) -> GuruRewardedAd? | |||
|  | 
 | |||
|  | // RewardedBidder 类实现 | |||
|  | private class RewardedBidder: CustomStringConvertible, MabBidder, FusionAdListener { | |||
|  |      | |||
|  |     // 静态属性和常量 | |||
|  |     private static let idPool = AtomicInteger(0) | |||
|  |     static let TAG = "RewardedBidder" | |||
|  |      | |||
|  |     // 实例属性 | |||
|  |     private let engine: RewardedAdEngine | |||
|  |     var config: AggregatorConfig | |||
|  |     private let ad: GuruRewardedAd | |||
|  |     private let adUnitSpec: AdUnitSpec | |||
|  |     private let faUnitId: String | |||
|  |      | |||
|  |     var id: Int | |||
|  |     var revenue: Double = 0.0 | |||
|  |      | |||
|  |     private var fusionAd: FusionAd? | |||
|  |     private var loadedTimeInMillis: Int64? | |||
|  |      | |||
|  |     private var loaded: Bool { | |||
|  |         return loadedTimeInMillis != nil | |||
|  |     } | |||
|  |      | |||
|  |     var isExpired: Bool? { | |||
|  |         get { | |||
|  |             // 若配置为0 则永不过期 | |||
|  |             if config.cacheExpireInSecond == 0.0 { | |||
|  |                 return false | |||
|  |             } | |||
|  |             guard let loadedTime = loadedTimeInMillis else { | |||
|  |                 return nil | |||
|  |             } | |||
|  |             return (SystemClock.elapsedRealtime() - loadedTime) > Int64(config.cacheExpireInSecond * 1000) | |||
|  |         } | |||
|  |     } | |||
|  |      | |||
|  |     var isFilled: Bool { | |||
|  |         return loaded && !(isExpired ?? true) | |||
|  |     } | |||
|  |      | |||
|  |     func getFusionAd() -> FusionAd? { | |||
|  |         return fusionAd | |||
|  |     } | |||
|  |      | |||
|  |     // 私有属性 | |||
|  |     private let loadTimeOutToken = UUID() | |||
|  |     private let cacheExpireToken = UUID() | |||
|  |     private var loadStartTime: Int64? | |||
|  |      | |||
|  |     // 初始化方法 | |||
|  |     init( | |||
|  |         engine: RewardedAdEngine, | |||
|  |         config: AggregatorConfig, | |||
|  |         ad: GuruRewardedAd, | |||
|  |         adUnitSpec: AdUnitSpec, | |||
|  |         faUnitId: String | |||
|  |     ) { | |||
|  |         self.engine = engine | |||
|  |         self.config = config | |||
|  |         self.ad = ad | |||
|  |         self.adUnitSpec = adUnitSpec | |||
|  |         self.faUnitId = faUnitId | |||
|  |         self.id = RewardedBidder.idPool.incrementAndGet() | |||
|  |         ad.listener = self | |||
|  |     } | |||
|  |      | |||
|  |     private func consumeLoadDuration() -> Int64? { | |||
|  |         guard let start = loadStartTime else { | |||
|  |             return nil | |||
|  |         } | |||
|  |         loadStartTime = nil | |||
|  |         return SystemClock.elapsedRealtime() - start | |||
|  |     } | |||
|  |      | |||
|  |     private var handler: StateMachine { | |||
|  |         return engine | |||
|  |     } | |||
|  |      | |||
|  |     func load(reason: RequestReason?) -> Bool { | |||
|  |         MabAnalyticEvents.faLoad(faUnitId: faUnitId, adType: engine.adType, aggregatorId: config.aggregatorId, adUnitId: adUnitSpec.adUnitId, requestReason: reason) | |||
|  |         let result = ad.load() | |||
|  |         if result { | |||
|  |             handler.removeCallbacksAndMessages(cacheExpireToken) | |||
|  |             loadedTimeInMillis = nil | |||
|  |             loadStartTime = SystemClock.elapsedRealtime() | |||
|  |              | |||
|  |             let timeout: Int64 | |||
|  |             if let configTimeout = config.loadTimeoutInSecond { | |||
|  |                 timeout = Int64(configTimeout * 1000) | |||
|  |             } else { | |||
|  |                 timeout = MabRewardedEngine.BIDDING_LOAD_TIMEOUT_MILLIS | |||
|  |             } | |||
|  |              | |||
|  |             handler.postDelayed(runnable: { [weak self] in self?.performLoadTimeout() }, token: loadTimeOutToken, delayed: .milliseconds(Int(timeout))) | |||
|  |         } | |||
|  |         return result | |||
|  |     } | |||
|  |      | |||
|  |     func show(request: RewardedShowRequest?) -> Bool { | |||
|  |         let result = ad.show(request) | |||
|  |         if result { | |||
|  |             handler.removeCallbacksAndMessages(cacheExpireToken) | |||
|  |             loadedTimeInMillis = nil | |||
|  |         } | |||
|  |         return result | |||
|  |     } | |||
|  |      | |||
|  |     func destroy() { | |||
|  |         handler.removeCallbacksAndMessages(loadTimeOutToken) | |||
|  |         handler.removeCallbacksAndMessages(cacheExpireToken) | |||
|  |         revenue = 0.0 | |||
|  |         ad.destroy() | |||
|  |         fusionAd = nil | |||
|  |         loadedTimeInMillis = nil | |||
|  |         loadStartTime = nil | |||
|  |     } | |||
|  |      | |||
|  |     // MARK: - FusionAdListener 方法 | |||
|  |      | |||
|  |     func onAdLoaded(ad: FusionAd) { | |||
|  |         logLoaded(ad) | |||
|  |         handler.removeCallbacksAndMessages(loadTimeOutToken) | |||
|  |         fusionAd = ad | |||
|  |         loadedTimeInMillis = SystemClock.elapsedRealtime() | |||
|  |         revenue = MabBidderUtils.adjustRevenue(ad: ad, floor: adUnitSpec.floor) | |||
|  |         engine.sendMessage(what: MabRewardedEngine.EVENT_ON_LOADED, obj: EventParams(ad: ad, id: id)) | |||
|  |          | |||
|  |         handler.removeCallbacksAndMessages(cacheExpireToken) | |||
|  |         let cacheTimeInMillis = Int64(config.cacheExpireInSecond * 1000) | |||
|  |         if cacheTimeInMillis > 0 { | |||
|  |             handler.postDelayed(runnable: { [weak self] in self?.performCacheExpired() }, token: cacheExpireToken, delayed: .milliseconds(Int(cacheTimeInMillis + 50))) | |||
|  |         } | |||
|  |     } | |||
|  |      | |||
|  |     func onAdDisplayed(ad: FusionAd) { | |||
|  |         engine.sendMessage(what: MabRewardedEngine.EVENT_ON_DISPLAYED, obj: EventParams(ad: ad, id: id)) | |||
|  |     } | |||
|  |      | |||
|  |     func onAdHidden(ad: FusionAd) { | |||
|  |         engine.sendMessage(what: MabRewardedEngine.EVENT_ON_HIDDEN, obj: EventParams(ad: ad, id: id)) | |||
|  |     } | |||
|  |      | |||
|  |     func onAdClicked(ad: FusionAd) { | |||
|  |         engine.sendMessage(what: MabRewardedEngine.EVENT_ON_CLICK, obj: EventParams(ad: ad, id: id)) | |||
|  |     } | |||
|  |      | |||
|  |     func onAdLoadFailed(loadFailedInfo: LoadFailedInfo) { | |||
|  |         let errorCode = loadFailedInfo.error?.errorCode ?? FusionErrorCodes.UNKNOWN.code | |||
|  |         logLoadFailed(errorCode) | |||
|  |         loadedTimeInMillis = nil | |||
|  |         handler.removeCallbacksAndMessages(loadTimeOutToken) | |||
|  |         engine.sendMessage(what: | |||
|  |             MabRewardedEngine.EVENT_ON_LOAD_FAILED, | |||
|  |                            obj: EventParams(loadFailedInfo: loadFailedInfo, id: id) | |||
|  |         ) | |||
|  |     } | |||
|  |      | |||
|  |     func onAdDisplayFailed(ad: FusionAd, error: FusionError?) { | |||
|  |         engine.sendMessage( | |||
|  |             what: MabRewardedEngine.EVENT_ON_DISPLAY_FAILED, | |||
|  |             obj: EventParams(ad: ad, displayFailedInfo: error, id: id) | |||
|  |         ) | |||
|  |     } | |||
|  |      | |||
|  |     func onAdRevenuePaid(ad: FusionAd) { | |||
|  |         engine.sendMessage(what: MabRewardedEngine.EVENT_REVENUE_PAID, obj: EventParams(ad: ad, id: id)) | |||
|  |     } | |||
|  |      | |||
|  |     func onUserRewarded(ad: FusionAd, reward: (any FusionReward)?) { | |||
|  |         engine.sendMessage(what: MabRewardedEngine.EVENT_USER_REWARDED, obj: EventParams(ad: ad, reward: reward)) | |||
|  |     } | |||
|  |      | |||
|  |     // MARK: - 私有方法 | |||
|  |      | |||
|  |     private func performLoadTimeout() { | |||
|  |         ad.destroy() | |||
|  |         onAdLoadFailed( | |||
|  |             loadFailedInfo: LoadFailedInfo( | |||
|  |                 engineId: ad.engineId, | |||
|  |                 adPlatform: ad.adPlatform, | |||
|  |                 adType: engine.adType, | |||
|  |                 adUnitId: ad.adUnitId, | |||
|  |                 error: LoadAdTimeoutError() | |||
|  |             ) | |||
|  |         ) | |||
|  |     } | |||
|  |      | |||
|  |     private func performCacheExpired() { | |||
|  |         engine.sendMessage(what: MabRewardedEngine.EVENT_CACHE_EXPIRED, obj: EventParams(id: id)) | |||
|  |     } | |||
|  |      | |||
|  |     private func logLoaded(_ fusionAd: FusionAd) { | |||
|  |         guard let duration = consumeLoadDuration() else { | |||
|  |             Logger.w(tag: RewardedBidder.TAG, message: "logLoaded but duration is nil, ignored") | |||
|  |             return | |||
|  |         } | |||
|  |          | |||
|  |         MabAnalyticEvents.faLoaded( | |||
|  |             faUnitId: faUnitId, | |||
|  |             adType: engine.adType, | |||
|  |             adSource: fusionAd.networkName, | |||
|  |             aggregatorId: config.aggregatorId, | |||
|  |             adUnitId: ad.adUnitId, | |||
|  |             durationInMillis: duration | |||
|  |         ) | |||
|  |     } | |||
|  |      | |||
|  |     private func logLoadFailed(_ errorCode: Int) { | |||
|  |         guard let duration = consumeLoadDuration() else { | |||
|  |             Logger.w(tag: RewardedBidder.TAG, message: "logLoadFailed but duration is nil, ignored") | |||
|  |             return | |||
|  |         } | |||
|  |          | |||
|  |         MabAnalyticEvents.faFailed( | |||
|  |             faUnitId: faUnitId, | |||
|  |             adType: engine.adType, | |||
|  |             aggregatorId: config.aggregatorId, | |||
|  |             adUnitId: ad.adUnitId, | |||
|  |             durationInMillis: duration, | |||
|  |             errorCode: errorCode | |||
|  |         ) | |||
|  |     } | |||
|  |      | |||
|  |     var description: String { | |||
|  |         return "RewardedBidder[\(aggregatorId)(\(adUnitSpec.adUnitId))]" | |||
|  |     } | |||
|  | } | |||
|  | 
 | |||
|  | // MabRewardedEngine 类实现 | |||
|  | internal class MabRewardedEngine: RewardedAdEngine { | |||
|  |      | |||
|  |     // MARK: - 常量定义 | |||
|  |      | |||
|  |     static let ACTION_LOAD = 1 | |||
|  |     static let ACTION_SHOW = 2 | |||
|  |     static let ACTION_DESTROY = 4 | |||
|  |     static let ACTION_RETRY = 5 | |||
|  |      | |||
|  |     static let EVENT_ON_LOADED = 100 | |||
|  |     static let EVENT_ON_LOAD_FAILED = 101 | |||
|  |     static let EVENT_ON_DISPLAYED = 102 | |||
|  |     static let EVENT_ON_DISPLAY_FAILED = 103 | |||
|  |     static let EVENT_ON_CLICK = 104 | |||
|  |     static let EVENT_ON_HIDDEN = 105 | |||
|  |     static let EVENT_USER_REWARDED = 106 | |||
|  |     static let EVENT_REVENUE_PAID = 107 | |||
|  |     static let EVENT_SHOW_TIMEOUT = 109 | |||
|  |     static let EVENT_CACHE_EXPIRED = 110 | |||
|  |      | |||
|  |     static let BIDDING_LOAD_TIMEOUT_MILLIS: Int64 = 60_000 | |||
|  |     static let SHOW_TIMEOUT_MILLIS: Int64 = 120_000 | |||
|  |      | |||
|  |     // MARK: - 属性 | |||
|  |      | |||
|  |     private let faUnitId: String | |||
|  |     private let mabConfig: MabRewardedConfig | |||
|  |     private let adObtainer: RewardedAdObtainer | |||
|  |      | |||
|  |     private let auctioneer: MabAuctioneer<RewardedBidder> | |||
|  |      | |||
|  |     // MARK: - 初始化方法 | |||
|  |      | |||
|  |     init( | |||
|  |         viewController: UIViewController, | |||
|  |         id: Int, | |||
|  |         faUnitId: String, | |||
|  |         mabConfig: MabRewardedConfig, | |||
|  |         adObtainer: @escaping RewardedAdObtainer | |||
|  |     ) { | |||
|  |         self.mabConfig = mabConfig | |||
|  |         self.adObtainer = adObtainer | |||
|  |         self.faUnitId = faUnitId | |||
|  |          | |||
|  |         // 创建拍卖器 | |||
|  |         weak var futureSelf: MabRewardedEngine? = nil | |||
|  |         auctioneer = MabAuctioneer<RewardedBidder>( | |||
|  |             engineId: id, | |||
|  |             faUnitId: faUnitId, | |||
|  |             aggregatorConfigs: mabConfig.aggregatorConfigs, | |||
|  |             bidderFactory: { config, adUnitSpec in | |||
|  |                 guard let self = futureSelf else { return nil } | |||
|  |                 return self.createBidder(config: config, adUnitSpec: adUnitSpec) | |||
|  |             } | |||
|  |         ) | |||
|  |         super.init(viewController: viewController, id: id, adType: AdType.Rewarded, strategyName: "Mab") | |||
|  |         futureSelf = self | |||
|  |     } | |||
|  |      | |||
|  |     public override func createIdleState() -> Idle { | |||
|  |         return IdleImpl(self) | |||
|  |     } | |||
|  |      | |||
|  |     public override func createLoadingState() -> Loading { | |||
|  |         return LoadingImpl(self) | |||
|  |     } | |||
|  |      | |||
|  |     public override func createLoadedState() -> Loaded { | |||
|  |         return LoadedImpl(self) | |||
|  |     } | |||
|  |      | |||
|  |     public override func createShowingState() -> Showing { | |||
|  |         return ShowingImpl(self) | |||
|  |     } | |||
|  |      | |||
|  |     // MARK: - 私有方法 | |||
|  |      | |||
|  |     private func createBidder( | |||
|  |         config: AggregatorConfig, | |||
|  |         adUnitSpec: AdUnitSpec | |||
|  |     ) -> RewardedBidder? { | |||
|  |         let adAmzId = adUnitSpec.adAmazonSlotId | |||
|  |         let adPlatform = AdPlatform.fromName(config.adPlatform) | |||
|  |          | |||
|  |         guard let ad = adObtainer( | |||
|  |             adPlatform, | |||
|  |             AdConfig(engineId: id, adUnitId: adUnitSpec.adUnitId, adAmazonSlotId: adAmzId, requireDisableAutoRetries: true) | |||
|  |         ) else { | |||
|  |             return nil | |||
|  |         } | |||
|  |          | |||
|  |         return RewardedBidder( | |||
|  |             engine: self, | |||
|  |             config: config, | |||
|  |             ad: ad, | |||
|  |             adUnitSpec: adUnitSpec, | |||
|  |             faUnitId: faUnitId | |||
|  |         ) | |||
|  |     } | |||
|  |      | |||
|  |     private func handleUnhandledMessage(_ msg: Message?) -> Bool { | |||
|  |         guard let what = msg?.what else { return false } | |||
|  |          | |||
|  |         switch what { | |||
|  |         case MabRewardedEngine.ACTION_LOAD: | |||
|  |             logWarn("ACTION_LOAD on \(String(describing: currentState?.name))") | |||
|  |             adLoadFailed( | |||
|  |                 loadFailedInfo: LoadFailedInfo( | |||
|  |                     engineId: id, | |||
|  |                     adPlatform: AdPlatform.fusion, | |||
|  |                     adType: adType, | |||
|  |                     adUnitId: "mab_rewarded", | |||
|  |                     error: LoadAdRequestError(message: "unhandled ACTION_LOAD, current state is \(String(describing: currentState?.name))") | |||
|  |                 ) | |||
|  |             ) | |||
|  |             return true | |||
|  |              | |||
|  |         case MabRewardedEngine.ACTION_SHOW: | |||
|  |             adDisplayFailed(ad: InvalidFusionAd.rewarded(engineId: id), error: ShowAdRequestError()) | |||
|  |             return true | |||
|  |              | |||
|  |         case MabRewardedEngine.ACTION_DESTROY: | |||
|  |             logWarn("Destroy may not supported in a Mab Engine, may lead memory leakages if you are trying to destroy one.") | |||
|  |             transitionTo(idleState) | |||
|  |             // destroy all ads | |||
|  |             return true | |||
|  |              | |||
|  |         case MabRewardedEngine.EVENT_REVENUE_PAID: | |||
|  |             logWarn("revenue paid event unhandled!") | |||
|  |              | |||
|  |         case MabRewardedEngine.EVENT_ON_LOADED: | |||
|  |             logWarn("event EVENT_ON_LOADED unhandled!") | |||
|  |              | |||
|  |         case MabRewardedEngine.EVENT_ON_LOAD_FAILED: | |||
|  |             logWarn("event EVENT_ON_LOAD_FAILED unhandled!") | |||
|  |          | |||
|  |         default: | |||
|  |             break | |||
|  |         } | |||
|  |          | |||
|  |         return false | |||
|  |     } | |||
|  |      | |||
|  |     private func reset() { | |||
|  |         auctioneer.reset() | |||
|  |         removeMessages(what: MabRewardedEngine.EVENT_SHOW_TIMEOUT) | |||
|  |     } | |||
|  |      | |||
|  |     // MARK: - RewardedAdEngine 方法实现 | |||
|  |      | |||
|  |     override func requestLoad() { | |||
|  |         sendMessage(what: MabRewardedEngine.ACTION_LOAD) | |||
|  |     } | |||
|  |      | |||
|  |     override func requestShow(_ request: RewardedShowRequest?) { | |||
|  |         sendMessage(what: MabRewardedEngine.ACTION_SHOW, obj: request) | |||
|  |     } | |||
|  |      | |||
|  |     override func requestDestroy() { | |||
|  |         sendMessage(what: MabRewardedEngine.ACTION_DESTROY) | |||
|  |     } | |||
|  |      | |||
|  |     override var supportedAdPlatforms: Set<AdPlatform> { | |||
|  |         return [AdPlatform.fusion] | |||
|  |     } | |||
|  |      | |||
|  |     // MARK: - 内部状态类 | |||
|  |      | |||
|  |     class IdleImpl: Idle { | |||
|  |          | |||
|  |         public let engine: MabRewardedEngine | |||
|  |          | |||
|  |         init(_ engine: MabRewardedEngine) { | |||
|  |             self.engine = engine | |||
|  |         } | |||
|  |          | |||
|  |         override func enter(from: IState?, params: Any?) { | |||
|  |             super.enter(from: from, params: params) | |||
|  |             self.engine.reset() | |||
|  |         } | |||
|  |          | |||
|  |         override func exit(to: IState?) { | |||
|  |             super.exit(to: to) | |||
|  |         } | |||
|  |          | |||
|  |         override func processMessage(_ msg: Message) -> Bool { | |||
|  |             let what = msg.what | |||
|  |             if what == MabRewardedEngine.ACTION_LOAD { | |||
|  |                 let result = engine.auctioneer.requestNext() | |||
|  |                 if result != MabAuctioneer.RequestResult.noAvailableSource { | |||
|  |                     engine.transitionTo(engine.loadingState) | |||
|  |                     return true | |||
|  |                 } else { | |||
|  |                     engine.logError("No available source") | |||
|  |                     engine.adLoadFailed( | |||
|  |                         loadFailedInfo: LoadFailedInfo( | |||
|  |                             engineId: engine.id, | |||
|  |                             adPlatform: AdPlatform.fusion, | |||
|  |                             adType: engine.adType, | |||
|  |                             adUnitId: "mab_rewarded", | |||
|  |                             error: LoadAdNotAvailableError() | |||
|  |                         ) | |||
|  |                     ) | |||
|  |                     return true | |||
|  |                 } | |||
|  |             } | |||
|  |              | |||
|  |             return engine.handleUnhandledMessage(msg) || super.processMessage(msg) | |||
|  |         } | |||
|  |          | |||
|  |         override var name: String { | |||
|  |             return super.name | |||
|  |         } | |||
|  |     } | |||
|  |      | |||
|  |     class LoadingImpl: Loading { | |||
|  |         public let engine: MabRewardedEngine | |||
|  |          | |||
|  |         init(_ engine: MabRewardedEngine) { | |||
|  |             self.engine = engine | |||
|  |         } | |||
|  |          | |||
|  |         private var retryCount = 0 | |||
|  |          | |||
|  |         override func enter(from: IState?, params: Any?) { | |||
|  |             super.enter(from: from, params: params) | |||
|  |         } | |||
|  |          | |||
|  |         override func exit(to: IState?) { | |||
|  |             super.exit(to: to) | |||
|  |         } | |||
|  |          | |||
|  |         override func processMessage(_ msg: Message) -> Bool { | |||
|  |             let what = msg.what | |||
|  |              | |||
|  |             switch what { | |||
|  |             case MabRewardedEngine.ACTION_RETRY: | |||
|  |                 retryCount += 1 | |||
|  |                 engine.transitionTo(engine.idleState) | |||
|  |                 engine.requestLoad() | |||
|  |                 return true | |||
|  |                  | |||
|  |             case MabRewardedEngine.ACTION_LOAD: | |||
|  |                 engine.logWarn("Already loading! Ignore load request") | |||
|  |                 return true | |||
|  |                  | |||
|  |             case MabRewardedEngine.EVENT_ON_LOADED: | |||
|  |                 if let eventParams = msg.obj as? EventParams { | |||
|  |                     if eventParams.ad != nil { | |||
|  |                         engine.auctioneer.onLoaded(bidderId: eventParams.id) | |||
|  |                         engine.auctioneer.requestNext() | |||
|  |                         if engine.auctioneer.hasWinner { | |||
|  |                             engine.transitionTo(engine.loadedState, params: eventParams) | |||
|  |                         } | |||
|  |                     } | |||
|  |                 } | |||
|  |                 return true | |||
|  |                  | |||
|  |             case MabRewardedEngine.EVENT_ON_LOAD_FAILED: | |||
|  |                 guard let eventParams = msg.obj as? EventParams else { | |||
|  |                     return true | |||
|  |                 } | |||
|  |                  | |||
|  |                 engine.auctioneer.onLoadFailed(bidderId: eventParams.id) | |||
|  |                 let result = engine.auctioneer.requestNext() | |||
|  |                  | |||
|  |                 if result == MabAuctioneer.RequestResult.noAvailableSource { | |||
|  |                     let failedInfo = eventParams.loadFailedInfo ?? LoadFailedInfo( | |||
|  |                         engineId: engine.id, | |||
|  |                         adPlatform: AdPlatform.fusion, | |||
|  |                         adType: engine.adType, | |||
|  |                         adUnitId: "mab_rewarded", | |||
|  |                         error: LoadAdNotAvailableError() | |||
|  |                     ) | |||
|  |                      | |||
|  |                     engine.adLoadFailed(loadFailedInfo: failedInfo) | |||
|  |                      | |||
|  |                     let delay = min(max(pow(2.0, Double(retryCount)) as Double, 10), 300) | |||
|  |                     engine.sendMessageDelayed(what: MabRewardedEngine.ACTION_RETRY, delayMillis: Int(delay * 1000)) | |||
|  |                 } | |||
|  |                 return true | |||
|  |                  | |||
|  |             default: | |||
|  |                 break | |||
|  |             } | |||
|  |              | |||
|  |             return engine.handleUnhandledMessage(msg) || super.processMessage(msg) | |||
|  |         } | |||
|  |     } | |||
|  |      | |||
|  |     class LoadedImpl: Loaded { | |||
|  |         public let engine: MabRewardedEngine | |||
|  |          | |||
|  |         init(_ engine: MabRewardedEngine) { | |||
|  |             self.engine = engine | |||
|  |         } | |||
|  |         override func enter(from: IState?, params: Any?) { | |||
|  |             if let params = params as? EventParams, let ad = params.ad { | |||
|  |                 self.engine.adLoaded(ad: ad) | |||
|  |             } else { | |||
|  |                 self.engine.logWarn("Loaded state enter with invalid params") | |||
|  |                 self.engine.transitionTo(self.engine.idleState) | |||
|  |             } | |||
|  |             super.enter(from: from, params: params) | |||
|  |         } | |||
|  |          | |||
|  |         override func exit(to: IState?) { | |||
|  |             super.exit(to: to) | |||
|  |         } | |||
|  |          | |||
|  |         override func processMessage(_ msg: Message) -> Bool { | |||
|  |              | |||
|  |             let what = msg.what | |||
|  |              | |||
|  |             switch what { | |||
|  |             case MabRewardedEngine.ACTION_SHOW: | |||
|  |                 let showRequest = msg.obj as? RewardedShowRequest | |||
|  |                 var showResult: Bool? = nil | |||
|  |                 var finalWinner: RewardedBidder? = nil | |||
|  |                  | |||
|  |                 while true { | |||
|  |                     guard let winner = engine.auctioneer.pollMaxRevenue() else { | |||
|  |                         break | |||
|  |                     } | |||
|  |                      | |||
|  |                     if !winner.isFilled { | |||
|  |                         continue | |||
|  |                     } | |||
|  |                      | |||
|  |                     showResult = winner.show(request: showRequest) | |||
|  |                      | |||
|  |                     if showResult != true { | |||
|  |                         continue | |||
|  |                     } | |||
|  |                      | |||
|  |                     finalWinner = winner | |||
|  |                     break | |||
|  |                 } | |||
|  |                  | |||
|  |                 if let winner = finalWinner { | |||
|  |                     engine.auctioneer.onShown(winner: winner) | |||
|  |                     engine.transitionTo(engine.showingState) | |||
|  |                 } else { | |||
|  |                     let errorMessage = showResult == nil | |||
|  |                         ? "requestShow while NO winner in candidates" | |||
|  |                         : "Show Ad returns false" | |||
|  |                      | |||
|  |                     engine.adDisplayFailed( | |||
|  |                         ad: InvalidFusionAd.rewarded(engineId: engine.id), | |||
|  |                         error: ShowAdRequestError(message: errorMessage) | |||
|  |                     ) | |||
|  |                      | |||
|  |                     let result = engine.auctioneer.requestNext() | |||
|  |                     if result != MabAuctioneer.RequestResult.noAvailableSource { | |||
|  |                         engine.transitionTo(engine.loadingState) | |||
|  |                     } else { | |||
|  |                         engine.transitionTo(engine.idleState) | |||
|  |                         engine.requestLoad() | |||
|  |                     } | |||
|  |                 } | |||
|  |                 return true | |||
|  |                  | |||
|  |             case MabRewardedEngine.EVENT_ON_LOADED: | |||
|  |                 if let eventParams = msg.obj as? EventParams, eventParams.ad != nil { | |||
|  |                     engine.auctioneer.onLoaded(bidderId: eventParams.id) | |||
|  |                     engine.auctioneer.requestNext() | |||
|  |                 } | |||
|  |                 return true | |||
|  |                  | |||
|  |             case MabRewardedEngine.EVENT_ON_LOAD_FAILED: | |||
|  |                 processLoadFailed(msg) | |||
|  |                 return true | |||
|  |                  | |||
|  |             case MabRewardedEngine.EVENT_CACHE_EXPIRED: | |||
|  |                 engine.auctioneer.evictCache() | |||
|  |                 if !engine.auctioneer.hasWinner { | |||
|  |                     engine.transitionTo(engine.idleState) | |||
|  |                     engine.requestLoad() | |||
|  |                 } | |||
|  |                 return true | |||
|  |                  | |||
|  |             default: | |||
|  |                 break | |||
|  |             } | |||
|  |              | |||
|  |             return engine.handleUnhandledMessage(msg) || super.processMessage(msg) | |||
|  |         } | |||
|  |          | |||
|  |         private func processLoadFailed(_ msg: Message) { | |||
|  |             guard let eventParams = msg.obj as? EventParams else { | |||
|  |                 return | |||
|  |             } | |||
|  |              | |||
|  |             engine.auctioneer.onLoadFailed(bidderId: eventParams.id) | |||
|  |             engine.auctioneer.requestNext() | |||
|  |         } | |||
|  |     } | |||
|  |      | |||
|  |     class ShowingImpl: Showing { | |||
|  |         public let engine: MabRewardedEngine | |||
|  |          | |||
|  |         init(_ engine: MabRewardedEngine) { | |||
|  |             self.engine = engine | |||
|  |         } | |||
|  |          | |||
|  |         override func enter(from: IState?, params: Any?) { | |||
|  |              | |||
|  |             let timeout: Int64 | |||
|  |             if let configTimeout = engine.mabConfig.showTimeoutInSecond { | |||
|  |                 timeout = Int64(configTimeout * 1000) | |||
|  |             } else { | |||
|  |                 timeout = MabRewardedEngine.SHOW_TIMEOUT_MILLIS | |||
|  |             } | |||
|  |              | |||
|  |             engine.sendMessageDelayed(what: MabRewardedEngine.EVENT_SHOW_TIMEOUT, delayed: .milliseconds(Int(timeout))) | |||
|  |             super.enter(from: from, params: params) | |||
|  |         } | |||
|  |          | |||
|  |         override func exit(to: IState?) { | |||
|  |             self.engine.removeMessages(what: MabRewardedEngine.EVENT_SHOW_TIMEOUT) | |||
|  |             super.exit(to: to) | |||
|  |         } | |||
|  |          | |||
|  |         override func processMessage(_ msg: Message) -> Bool { | |||
|  |             let what = msg.what | |||
|  |              | |||
|  |             switch what { | |||
|  |             case MabRewardedEngine.EVENT_ON_DISPLAYED: | |||
|  |                 if let eventParams = msg.obj as? EventParams, let ad = eventParams.ad { | |||
|  |                     engine.adDisplayed(ad: ad) | |||
|  |                 } else { | |||
|  |                     engine.logWarn("Showing state enter with invalid params") | |||
|  |                 } | |||
|  |                 engine.removeMessages(what: MabRewardedEngine.EVENT_SHOW_TIMEOUT) | |||
|  |                 return true | |||
|  |                  | |||
|  |             case MabRewardedEngine.EVENT_ON_CLICK: | |||
|  |                 if let eventParams = msg.obj as? EventParams, let ad = eventParams.ad { | |||
|  |                     engine.adClicked(ad: ad) | |||
|  |                 } else { | |||
|  |                     engine.logWarn("Showing state enter with invalid params") | |||
|  |                 } | |||
|  |                 engine.removeMessages(what: MabRewardedEngine.EVENT_SHOW_TIMEOUT) | |||
|  |                 return true | |||
|  |                  | |||
|  |             case MabRewardedEngine.EVENT_ON_HIDDEN: | |||
|  |                 if let eventParams = msg.obj as? EventParams, let ad = eventParams.ad { | |||
|  |                     engine.adHidden(ad: ad) | |||
|  |                 } else { | |||
|  |                     engine.logWarn("Showing state enter with invalid params") | |||
|  |                 } | |||
|  |                 processRequestAfterConsumed() | |||
|  |                 return true | |||
|  |                  | |||
|  |             case MabRewardedEngine.EVENT_ON_DISPLAY_FAILED: | |||
|  |                 if let eventParams = msg.obj as? EventParams, let ad = eventParams.ad { | |||
|  |                     engine.adDisplayFailed(ad: ad, error: eventParams.displayFailedInfo) | |||
|  |                 } else { | |||
|  |                     engine.logWarn("Showing state enter with invalid params") | |||
|  |                 } | |||
|  |                 processRequestAfterConsumed() | |||
|  |                 return true | |||
|  |                  | |||
|  |             case MabRewardedEngine.EVENT_SHOW_TIMEOUT: | |||
|  |                 engine.adDisplayFailed(ad: InvalidFusionAd.rewarded(engineId: engine.id), error: ShowAdTimeoutError()) | |||
|  |                 processRequestAfterConsumed() | |||
|  |                 return true | |||
|  |                  | |||
|  |             case MabRewardedEngine.EVENT_USER_REWARDED: | |||
|  |                 guard let rewarded = (msg.obj as? EventParams)?.reward, let ad = (msg.obj as? EventParams)?.ad else { | |||
|  |                     return true | |||
|  |                 } | |||
|  |                 engine.adUserRewarded(ad: ad, reward: rewarded) | |||
|  |                 return true | |||
|  |                  | |||
|  |             case MabRewardedEngine.EVENT_REVENUE_PAID: | |||
|  |                 if let eventParams = msg.obj as? EventParams, let ad = eventParams.ad { | |||
|  |                     engine.adRevenuePaid(ad: ad) | |||
|  |                 } else { | |||
|  |                     engine.logWarn("revenue paid event with invalid params") | |||
|  |                 } | |||
|  |                 return true | |||
|  |                  | |||
|  |             case MabRewardedEngine.EVENT_ON_LOADED: | |||
|  |                 if let eventParams = msg.obj as? EventParams { | |||
|  |                     engine.auctioneer.onLoaded(bidderId: eventParams.id) | |||
|  |                 } | |||
|  |                 return true | |||
|  |                  | |||
|  |             case MabRewardedEngine.EVENT_ON_LOAD_FAILED: | |||
|  |                 if let eventParams = msg.obj as? EventParams { | |||
|  |                     engine.auctioneer.onLoadFailed(bidderId: eventParams.id) | |||
|  |                 } | |||
|  |                 return true | |||
|  |                  | |||
|  |             default: | |||
|  |                 break | |||
|  |             } | |||
|  |              | |||
|  |             return engine.handleUnhandledMessage(msg) || super.processMessage(msg) | |||
|  |         } | |||
|  |          | |||
|  |         /**
 | |||
|  |          * 消耗后重新填充请求队列 | |||
|  |          * 正常情况下执行完毕后,队列头部为自抬价聚合请求,后续为按priority降序排列的聚合 | |||
|  |          * 并开始一轮requestNext | |||
|  |          */ | |||
|  |         private func processRequestAfterConsumed() { | |||
|  |              | |||
|  |             engine.auctioneer.refillCandidatesQueue() | |||
|  |              | |||
|  |             let result = engine.auctioneer.requestNext() | |||
|  |             if let winner = engine.auctioneer.peekMaxRevenue() { | |||
|  |                 engine.transitionTo( | |||
|  |                     engine.loadedState, | |||
|  |                     params: EventParams(ad: winner.getFusionAd()) | |||
|  |                 ) | |||
|  |             } else if result != MabAuctioneer.RequestResult.noAvailableSource { | |||
|  |                 engine.transitionTo(engine.loadingState) | |||
|  |             } else { | |||
|  |                 engine.transitionTo(engine.idleState) | |||
|  |                 engine.requestLoad() | |||
|  |             } | |||
|  |         } | |||
|  |     } | |||
|  | } | |||
|  | 
 | |||
|  | private class AtomicInteger { | |||
|  |     private var value: Int | |||
|  |      | |||
|  |     init(_ initialValue: Int) { | |||
|  |         self.value = initialValue | |||
|  |     } | |||
|  |      | |||
|  |     func get() -> Int { | |||
|  |         return value | |||
|  |     } | |||
|  |      | |||
|  |     func incrementAndGet() -> Int { | |||
|  |         value += 1 | |||
|  |         return value | |||
|  |     } | |||
|  | } | |||
|  | 
 | |||
|  | private class SystemClock { | |||
|  |     static func elapsedRealtime() -> Int64 { | |||
|  |         return Int64(ProcessInfo.processInfo.systemUptime * 1000) | |||
|  |     } | |||
|  |      | |||
|  |     static func elapsedRealtimeNanos() -> Int64 { | |||
|  |         return Int64(ProcessInfo.processInfo.systemUptime * 1_000_000_000) | |||
|  |     } | |||
|  | } |