785 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			Swift
		
	
	
			
		
		
	
	
			785 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			Swift
		
	
	
| 
 | ||
| //
 | ||
| //  MabInterstitialEngine.swift
 | ||
| //  Pods
 | ||
| //
 | ||
| //  Created by 250102 on 2025/5/10.
 | ||
| //
 | ||
| 
 | ||
| import Foundation
 | ||
| 
 | ||
| // 类型别名定义
 | ||
| typealias InterstitialAdObtainer = (_ adPlatform: AdPlatform, _ adConfig: AdConfig) -> GuruInterstitialAd?
 | ||
| 
 | ||
| // InterstitialBidder 类实现
 | ||
| private class InterstitialBidder: CustomStringConvertible, MabBidder, FusionAdListener {
 | ||
|     
 | ||
|     // 静态属性和常量
 | ||
|     private static let idPool = AtomicInteger(0)
 | ||
|     static let TAG = "InterstitialBidder"
 | ||
|     
 | ||
|     // 实例属性
 | ||
|     private let engine: InterstitialAdEngine
 | ||
|     var config: AggregatorConfig
 | ||
|     private let ad: GuruInterstitialAd
 | ||
|     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: InterstitialAdEngine,
 | ||
|         config: AggregatorConfig,
 | ||
|         ad: GuruInterstitialAd,
 | ||
|         adUnitSpec: AdUnitSpec,
 | ||
|         faUnitId: String
 | ||
|     ) {
 | ||
|         self.engine = engine
 | ||
|         self.config = config
 | ||
|         self.ad = ad
 | ||
|         self.adUnitSpec = adUnitSpec
 | ||
|         self.faUnitId = faUnitId
 | ||
|         self.id = InterstitialBidder.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 = MabInterstitialEngine.BIDDING_LOAD_TIMEOUT_MILLIS
 | ||
|             }
 | ||
|             
 | ||
|             handler.postDelayed(runnable: { [weak self] in self?.performLoadTimeout() }, token: loadTimeOutToken, delayed: .milliseconds(Int(timeout)))
 | ||
|         }
 | ||
|         return result
 | ||
|     }
 | ||
|     
 | ||
|     func show(request: InterstitialShowRequest?) -> 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: MabInterstitialEngine.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: MabInterstitialEngine.EVENT_ON_DISPLAYED, obj: EventParams(ad: ad, id: id))
 | ||
|     }
 | ||
|     
 | ||
|     func onAdHidden(ad: FusionAd) {
 | ||
|         engine.sendMessage(what: MabInterstitialEngine.EVENT_ON_HIDDEN, obj: EventParams(ad: ad, id: id))
 | ||
|     }
 | ||
|     
 | ||
|     func onAdClicked(ad: FusionAd) {
 | ||
|         engine.sendMessage(what: MabInterstitialEngine.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:
 | ||
|             MabInterstitialEngine.EVENT_ON_LOAD_FAILED,
 | ||
|                            obj: EventParams(loadFailedInfo: loadFailedInfo, id: id)
 | ||
|         )
 | ||
|     }
 | ||
|     
 | ||
|     func onAdDisplayFailed(ad: FusionAd, error: FusionError?) {
 | ||
|         engine.sendMessage(
 | ||
|             what: MabInterstitialEngine.EVENT_ON_DISPLAY_FAILED,
 | ||
|             obj: EventParams(ad: ad, displayFailedInfo: error, id: id)
 | ||
|         )
 | ||
|     }
 | ||
|     
 | ||
|     func onAdRevenuePaid(ad: FusionAd) {
 | ||
|         engine.sendMessage(what: MabInterstitialEngine.EVENT_REVENUE_PAID, obj: EventParams(ad: ad, id: id))
 | ||
|     }
 | ||
|     
 | ||
|     // 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: MabInterstitialEngine.EVENT_CACHE_EXPIRED, obj: EventParams(id: id))
 | ||
|     }
 | ||
|     
 | ||
|     private func logLoaded(_ fusionAd: FusionAd) {
 | ||
|         guard let duration = consumeLoadDuration() else {
 | ||
|             Logger.w(tag: InterstitialBidder.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: InterstitialBidder.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 "InterstitialBidder[\(aggregatorId)(\(adUnitSpec.adUnitId))]"
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| // MabInterstitialEngine 类实现
 | ||
| internal class MabInterstitialEngine: InterstitialAdEngine {
 | ||
|     
 | ||
|     // 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_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: MabInterstitialConfig
 | ||
|     private let adObtainer: InterstitialAdObtainer
 | ||
|     
 | ||
|     private let auctioneer: MabAuctioneer<InterstitialBidder>
 | ||
|     
 | ||
|     // MARK: - 初始化方法
 | ||
|     
 | ||
|     init(
 | ||
|         viewController: UIViewController,
 | ||
|         id: Int,
 | ||
|         faUnitId: String,
 | ||
|         mabConfig: MabInterstitialConfig,
 | ||
|         adObtainer: @escaping InterstitialAdObtainer
 | ||
|     ) {
 | ||
|         self.mabConfig = mabConfig
 | ||
|         self.adObtainer = adObtainer
 | ||
|         self.faUnitId = faUnitId
 | ||
|         
 | ||
|         // 创建拍卖器
 | ||
|         weak var futureSelf: MabInterstitialEngine? = nil
 | ||
|         auctioneer = MabAuctioneer<InterstitialBidder>(
 | ||
|             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.Interstitial, 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
 | ||
|     ) -> InterstitialBidder? {
 | ||
|         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 InterstitialBidder(
 | ||
|             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 MabInterstitialEngine.ACTION_LOAD:
 | ||
|             logWarn("ACTION_LOAD on \(String(describing: currentState?.name))")
 | ||
|             adLoadFailed(
 | ||
|                 loadFailedInfo: LoadFailedInfo(
 | ||
|                     engineId: id,
 | ||
|                     adPlatform: AdPlatform.fusion,
 | ||
|                     adType: adType,
 | ||
|                     adUnitId: "mab_interstitial",
 | ||
|                     error: LoadAdRequestError(message: "unhandled ACTION_LOAD, current state is \(String(describing: currentState?.name))")
 | ||
|                 )
 | ||
|             )
 | ||
|             return true
 | ||
|             
 | ||
|         case MabInterstitialEngine.ACTION_SHOW:
 | ||
|             adDisplayFailed(ad: InvalidFusionAd.interstitial(engineId: id), error: ShowAdRequestError())
 | ||
|             return true
 | ||
|             
 | ||
|         case MabInterstitialEngine.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 MabInterstitialEngine.EVENT_REVENUE_PAID:
 | ||
|             logWarn("revenue paid event unhandled!")
 | ||
|             
 | ||
|         case MabInterstitialEngine.EVENT_ON_LOADED:
 | ||
|             logWarn("event EVENT_ON_LOADED unhandled!")
 | ||
|             
 | ||
|         case MabInterstitialEngine.EVENT_ON_LOAD_FAILED:
 | ||
|             logWarn("event EVENT_ON_LOAD_FAILED unhandled!")
 | ||
|         
 | ||
|         default:
 | ||
|             break
 | ||
|         }
 | ||
|         
 | ||
|         return false
 | ||
|     }
 | ||
|     
 | ||
|     private func reset() {
 | ||
|         auctioneer.reset()
 | ||
|         removeMessages(what: MabInterstitialEngine.EVENT_SHOW_TIMEOUT)
 | ||
|     }
 | ||
|     
 | ||
|     // MARK: - InterstitialAdEngine 方法实现
 | ||
|     
 | ||
|     override func requestLoad() {
 | ||
|         sendMessage(what: MabInterstitialEngine.ACTION_LOAD)
 | ||
|     }
 | ||
|     
 | ||
|     override func requestShow(request: InterstitialShowRequest?) {
 | ||
|         sendMessage(what: MabInterstitialEngine.ACTION_SHOW, obj: request)
 | ||
|     }
 | ||
|     
 | ||
|     override func requestDestroy() {
 | ||
|         sendMessage(what: MabInterstitialEngine.ACTION_DESTROY)
 | ||
|     }
 | ||
|     
 | ||
|     override var supportedAdPlatforms: Set<AdPlatform> {
 | ||
|         return [AdPlatform.fusion]
 | ||
|     }
 | ||
|     
 | ||
|     // MARK: - 内部状态类
 | ||
|     
 | ||
|     class IdleImpl: Idle {
 | ||
|         
 | ||
|         public let engine: MabInterstitialEngine
 | ||
|         
 | ||
|         init(_ engine: MabInterstitialEngine) {
 | ||
|             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 == MabInterstitialEngine.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_interstitial",
 | ||
|                             error: LoadAdNotAvailableError()
 | ||
|                         )
 | ||
|                     )
 | ||
|                     return true
 | ||
|                 }
 | ||
|             }
 | ||
|             
 | ||
|             return engine.handleUnhandledMessage(msg) || super.processMessage(msg)
 | ||
|         }
 | ||
|         
 | ||
|         override var name: String {
 | ||
|             return super.name
 | ||
|         }
 | ||
|     }
 | ||
|     
 | ||
|     class LoadingImpl: Loading {
 | ||
|         public let engine: MabInterstitialEngine
 | ||
|         
 | ||
|         init(_ engine: MabInterstitialEngine) {
 | ||
|             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 MabInterstitialEngine.ACTION_RETRY:
 | ||
|                 retryCount += 1
 | ||
|                 engine.transitionTo(engine.idleState)
 | ||
|                 engine.requestLoad()
 | ||
|                 return true
 | ||
|                 
 | ||
|             case MabInterstitialEngine.ACTION_LOAD:
 | ||
|                 engine.logWarn("Already loading! Ignore load request")
 | ||
|                 return true
 | ||
|                 
 | ||
|             case MabInterstitialEngine.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 MabInterstitialEngine.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_interstitial",
 | ||
|                         error: LoadAdNotAvailableError()
 | ||
|                     )
 | ||
|                     
 | ||
|                     engine.adLoadFailed(loadFailedInfo: failedInfo)
 | ||
|                     
 | ||
|                     let delay = min(max(pow(2.0, Double(retryCount)) as Double, 10), 300)
 | ||
|                     engine.sendMessageDelayed(what: MabInterstitialEngine.ACTION_RETRY, delayMillis: Int(delay * 1000))
 | ||
|                 }
 | ||
|                 return true
 | ||
|                 
 | ||
|             default:
 | ||
|                 break
 | ||
|             }
 | ||
|             
 | ||
|             return engine.handleUnhandledMessage(msg) || super.processMessage(msg)
 | ||
|         }
 | ||
|     }
 | ||
|     
 | ||
|     class LoadedImpl: Loaded {
 | ||
|         public let engine: MabInterstitialEngine
 | ||
|         
 | ||
|         init(_ engine: MabInterstitialEngine) {
 | ||
|             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 MabInterstitialEngine.ACTION_SHOW:
 | ||
|                 let showRequest = msg.obj as? InterstitialShowRequest
 | ||
|                 var showResult: Bool? = nil
 | ||
|                 var finalWinner: InterstitialBidder? = 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.interstitial(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 MabInterstitialEngine.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 MabInterstitialEngine.EVENT_ON_LOAD_FAILED:
 | ||
|                 processLoadFailed(msg)
 | ||
|                 return true
 | ||
|                 
 | ||
|             case MabInterstitialEngine.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: MabInterstitialEngine
 | ||
|         
 | ||
|         init(_ engine: MabInterstitialEngine) {
 | ||
|             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 = MabInterstitialEngine.SHOW_TIMEOUT_MILLIS
 | ||
|             }
 | ||
|             
 | ||
|             engine.sendMessageDelayed(what: MabInterstitialEngine.EVENT_SHOW_TIMEOUT, delayed: .milliseconds(Int(timeout)))
 | ||
|             super.enter(from: from, params: params)
 | ||
|         }
 | ||
|         
 | ||
|         override func exit(to: IState?) {
 | ||
|             self.engine.removeMessages(what: MabInterstitialEngine.EVENT_SHOW_TIMEOUT)
 | ||
|             super.exit(to: to)
 | ||
|         }
 | ||
|         
 | ||
|         override func processMessage(_ msg: Message) -> Bool {
 | ||
|             let what = msg.what
 | ||
|             
 | ||
|             switch what {
 | ||
|             case MabInterstitialEngine.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: MabInterstitialEngine.EVENT_SHOW_TIMEOUT)
 | ||
|                 return true
 | ||
|                 
 | ||
|             case MabInterstitialEngine.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: MabInterstitialEngine.EVENT_SHOW_TIMEOUT)
 | ||
|                 return true
 | ||
|                 
 | ||
|             case MabInterstitialEngine.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 MabInterstitialEngine.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 MabInterstitialEngine.EVENT_SHOW_TIMEOUT:
 | ||
|                 engine.adDisplayFailed(ad: InvalidFusionAd.interstitial(engineId: engine.id), error: ShowAdTimeoutError())
 | ||
|                 processRequestAfterConsumed()
 | ||
|                 return true
 | ||
|                 
 | ||
|             case MabInterstitialEngine.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 MabInterstitialEngine.EVENT_ON_LOADED:
 | ||
|                 if let eventParams = msg.obj as? EventParams {
 | ||
|                     engine.auctioneer.onLoaded(bidderId: eventParams.id)
 | ||
|                 }
 | ||
|                 return true
 | ||
|                 
 | ||
|             case MabInterstitialEngine.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)
 | ||
|     }
 | ||
| }
 |