2026-06-05 21:44:35 +08:00
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Text;
|
|
|
|
|
using Runtime.ADAggregator;
|
|
|
|
|
using UnityEngine;
|
|
|
|
|
|
|
|
|
|
// 智能预加载实验:基于“场景出现次数/该场景实际播放请求次数”进行置信决策,
|
|
|
|
|
// 并允许通过策略配置进行服务端默认值覆盖。
|
|
|
|
|
public static class TapadnSmartLoadOrchestrator
|
|
|
|
|
{
|
|
|
|
|
private const string StatsPrefsKey = "TapadnSmartLoadStats.v1";
|
|
|
|
|
private const string DefaultScene = "__default__";
|
|
|
|
|
private const string PolicyDefaultScene = "__default__";
|
|
|
|
|
|
|
|
|
|
private static TapadnSmartLoadConfig _runtimeConfig;
|
|
|
|
|
private static Dictionary<string, TapadnSmartLoadSceneState> _states = new Dictionary<string, TapadnSmartLoadSceneState>(StringComparer.Ordinal);
|
|
|
|
|
private static Dictionary<string, TapadnSmartLoadPolicyItem> _policies = new Dictionary<string, TapadnSmartLoadPolicyItem>(StringComparer.Ordinal);
|
2026-06-17 15:40:25 +08:00
|
|
|
private static Dictionary<string, TapadnSmartLoadCacheState> _cacheStates = new Dictionary<string, TapadnSmartLoadCacheState>(StringComparer.Ordinal);
|
2026-06-05 21:44:35 +08:00
|
|
|
private static bool _initialized;
|
|
|
|
|
private static bool _enabled;
|
|
|
|
|
|
|
|
|
|
public static bool IsEnabled => _enabled;
|
|
|
|
|
|
|
|
|
|
public static string GetDebugStateDump()
|
|
|
|
|
{
|
|
|
|
|
if (!_initialized)
|
|
|
|
|
{
|
|
|
|
|
return "{}";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var lines = new List<string>();
|
|
|
|
|
foreach (var entry in _states.Values)
|
|
|
|
|
{
|
|
|
|
|
if (entry == null)
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var adType = SafeToAdType(entry.AdType);
|
|
|
|
|
if (!adType.HasValue)
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var policy = ResolvePolicy(adType.Value, entry.Scenario);
|
|
|
|
|
var prob = EstimateShowProbability(adType.Value, entry.Scenario);
|
|
|
|
|
lines.Add(
|
|
|
|
|
$"[{entry.AdType}]{entry.Scenario} e={entry.EnterCount} pReq={entry.PlayRequestCount} preReq={entry.PreloadRequestCount} preSuc={entry.PreloadSuccessCount} preFail={entry.PreloadFailureCount} showReq={entry.ShowRequestCount} showOk={entry.ShowStartCount} showFail={entry.ShowFailureCount} imm={entry.ImmediateHitCount} smartHit={entry.SmartCacheHitCount} smartUsed={entry.SmartPreloadConsumedCount} score={prob:F3} policy={policy?.PreloadThreshold:F2}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $"enabled={_enabled},states={lines.Count},policies={_policies.Count},cache={GetCacheDebugDump()},snapshot={string.Join(" ; ", lines)}";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static string ExportSnapshotCsv()
|
|
|
|
|
{
|
|
|
|
|
if (!_initialized)
|
|
|
|
|
{
|
|
|
|
|
return GetSnapshotCsvHeader();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var sb = new StringBuilder();
|
|
|
|
|
sb.AppendLine(GetSnapshotCsvHeader());
|
|
|
|
|
foreach (var entry in _states.Values)
|
|
|
|
|
{
|
|
|
|
|
if (entry == null)
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sb.AppendLine(string.Join(",",
|
|
|
|
|
entry.AdType,
|
|
|
|
|
EncodeCsv(NormalizeScenario(entry.Scenario)),
|
|
|
|
|
entry.EnterCount,
|
|
|
|
|
entry.PlayRequestCount,
|
|
|
|
|
entry.PreloadRequestCount,
|
|
|
|
|
entry.PreloadSuccessCount,
|
|
|
|
|
entry.PreloadFailureCount,
|
|
|
|
|
entry.ShowRequestCount,
|
|
|
|
|
entry.ShowStartCount,
|
|
|
|
|
entry.ShowFailureCount,
|
|
|
|
|
entry.ImmediateHitCount,
|
|
|
|
|
entry.SmartCacheHitCount,
|
|
|
|
|
entry.SmartCacheCrossSceneHitCount,
|
|
|
|
|
entry.UnattributedCacheHitCount,
|
|
|
|
|
entry.SmartPreloadConsumedCount,
|
|
|
|
|
entry.SmartPreloadConsumedSameSceneCount,
|
|
|
|
|
entry.SmartPreloadConsumedOtherSceneCount,
|
|
|
|
|
entry.SmartPreloadShowFailureCount,
|
|
|
|
|
entry.SmartPreloadExpiredCount,
|
|
|
|
|
EncodeCsv(NormalizeScenario(entry.LastSmartPreloadConsumedByScene)),
|
|
|
|
|
entry.LastSmartPreloadConsumedUnix,
|
|
|
|
|
entry.LastPreloadUnix,
|
|
|
|
|
entry.LastUpdatedUnix,
|
|
|
|
|
EstimateShowProbability((AD_Type)entry.AdType, entry.Scenario)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return sb.ToString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static void ResetLearningState()
|
|
|
|
|
{
|
|
|
|
|
if (!_initialized)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_states.Clear();
|
|
|
|
|
_cacheStates.Clear();
|
|
|
|
|
PlayerPrefs.DeleteKey(StatsPrefsKey);
|
|
|
|
|
PlayerPrefs.Save();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static void Initialize(TapadnControllerOptions options)
|
|
|
|
|
{
|
|
|
|
|
_runtimeConfig = BuildConfig(options);
|
|
|
|
|
_enabled = options?.SmartPreloadEnabled ?? false;
|
|
|
|
|
_initialized = true;
|
|
|
|
|
_states = new Dictionary<string, TapadnSmartLoadSceneState>(StringComparer.Ordinal);
|
2026-06-17 15:40:25 +08:00
|
|
|
_cacheStates = new Dictionary<string, TapadnSmartLoadCacheState>(StringComparer.Ordinal);
|
2026-06-05 21:44:35 +08:00
|
|
|
LoadStates();
|
|
|
|
|
EnsureDefaultPolicies(_runtimeConfig);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static void OnEnterAdScenario(AD_Type adType, string scenario, string _slotId)
|
|
|
|
|
{
|
|
|
|
|
if (!_initialized || !_enabled)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var normalizedScenario = NormalizeScenario(scenario);
|
|
|
|
|
var record = GetOrCreateState(adType, normalizedScenario);
|
|
|
|
|
record.EnterCount = Math.Max(0, record.EnterCount) + 1;
|
|
|
|
|
record.LastUpdatedUnix = GetNowUnixSeconds();
|
|
|
|
|
record.ShowRequestCount = Math.Max(0, record.ShowRequestCount);
|
|
|
|
|
SaveStates();
|
|
|
|
|
|
2026-06-17 15:40:25 +08:00
|
|
|
TryPreload(adType, normalizedScenario, _slotId);
|
2026-06-05 21:44:35 +08:00
|
|
|
}
|
|
|
|
|
|
2026-06-17 15:40:25 +08:00
|
|
|
public static void OnPlayRequestStarted(AD_Type adType, string scenario, bool cacheReadyAtRequest, string slotId = null)
|
2026-06-05 21:44:35 +08:00
|
|
|
{
|
|
|
|
|
if (!_initialized || !_enabled)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var normalizedScenario = NormalizeScenario(scenario);
|
|
|
|
|
var record = GetOrCreateState(adType, normalizedScenario);
|
|
|
|
|
record.PlayRequestCount = Math.Max(0, record.PlayRequestCount) + 1;
|
|
|
|
|
record.ShowRequestCount = Math.Max(0, record.ShowRequestCount) + 1;
|
|
|
|
|
record.LastUpdatedUnix = GetNowUnixSeconds();
|
|
|
|
|
if (cacheReadyAtRequest)
|
|
|
|
|
{
|
2026-06-17 15:40:25 +08:00
|
|
|
MarkImmediateHit(adType, normalizedScenario, slotId);
|
2026-06-05 21:44:35 +08:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2026-06-17 15:40:25 +08:00
|
|
|
MarkCacheExpiredIfStale(adType, slotId);
|
2026-06-05 21:44:35 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SaveStates();
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-17 15:40:25 +08:00
|
|
|
public static void OnLoadRequested(AD_Type adType, string scenario, string slotId = null)
|
2026-06-05 21:44:35 +08:00
|
|
|
{
|
|
|
|
|
if (!_initialized || !_enabled)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-17 15:40:25 +08:00
|
|
|
PrepareSmartLoadRequest(adType, scenario, slotId);
|
2026-06-05 21:44:35 +08:00
|
|
|
}
|
|
|
|
|
|
2026-06-17 15:40:25 +08:00
|
|
|
public static void OnLoadStarted(AD_Type adType, string scenario, string slotId = null)
|
2026-06-05 21:44:35 +08:00
|
|
|
{
|
|
|
|
|
if (!_initialized || !_enabled)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-17 15:40:25 +08:00
|
|
|
BeginLoadRequest(adType, scenario, slotId);
|
2026-06-05 21:44:35 +08:00
|
|
|
SaveStates();
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-17 15:40:25 +08:00
|
|
|
public static void OnLoadResult(AD_Type adType, string scenario, bool success, string slotId = null)
|
2026-06-05 21:44:35 +08:00
|
|
|
{
|
|
|
|
|
if (!_initialized || !_enabled)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-17 15:40:25 +08:00
|
|
|
CompleteLoadRequest(adType, scenario, success, slotId);
|
2026-06-05 21:44:35 +08:00
|
|
|
SaveStates();
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-17 15:40:25 +08:00
|
|
|
public static void OnShowStart(AD_Type adType, string scenario, string slotId = null)
|
2026-06-05 21:44:35 +08:00
|
|
|
{
|
|
|
|
|
if (!_initialized || !_enabled)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var record = GetOrCreateState(adType, NormalizeScenario(scenario));
|
|
|
|
|
record.ShowStartCount = Math.Max(0, record.ShowStartCount) + 1;
|
|
|
|
|
record.LastUpdatedUnix = GetNowUnixSeconds();
|
2026-06-17 15:40:25 +08:00
|
|
|
MarkCacheConsumed(adType, scenario, slotId);
|
2026-06-05 21:44:35 +08:00
|
|
|
SaveStates();
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-17 15:40:25 +08:00
|
|
|
public static void OnShowError(AD_Type adType, string scenario, string slotId = null)
|
2026-06-05 21:44:35 +08:00
|
|
|
{
|
|
|
|
|
if (!_initialized || !_enabled)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var record = GetOrCreateState(adType, NormalizeScenario(scenario));
|
|
|
|
|
record.ShowFailureCount = Math.Max(0, record.ShowFailureCount) + 1;
|
|
|
|
|
record.LastUpdatedUnix = GetNowUnixSeconds();
|
2026-06-17 15:40:25 +08:00
|
|
|
MarkCacheShowFailed(adType, scenario, slotId);
|
2026-06-05 21:44:35 +08:00
|
|
|
SaveStates();
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-17 15:40:25 +08:00
|
|
|
private static void TryPreload(AD_Type adType, string scenario, string slotId)
|
2026-06-05 21:44:35 +08:00
|
|
|
{
|
|
|
|
|
if (!_initialized || !_enabled)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!ADManager.Instance.CheckNetwork())
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var policy = ResolvePolicy(adType, scenario);
|
|
|
|
|
if (policy == null)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!NeedPreload(adType, scenario, policy))
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var now = GetNowUnixSeconds();
|
|
|
|
|
var state = GetOrCreateState(adType, scenario);
|
|
|
|
|
if (now - state.LastPreloadUnix < policy.CooldownSeconds)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ADManager.Instance.IsRealy(adType))
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-17 15:40:25 +08:00
|
|
|
MarkCacheExpiredIfStale(adType, slotId);
|
|
|
|
|
OnLoadRequested(adType, scenario, slotId);
|
2026-06-05 21:44:35 +08:00
|
|
|
ADManager.Instance.LoadAD(adType);
|
2026-06-17 15:40:25 +08:00
|
|
|
var smartLoadStarted = HasPendingSmartLoadForScene(adType, scenario, slotId);
|
|
|
|
|
ClearPreparedLoadIfNotStarted(adType, slotId);
|
2026-06-05 21:44:35 +08:00
|
|
|
if (!smartLoadStarted)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
state.LastPreloadUnix = now;
|
|
|
|
|
state.LastUpdatedUnix = now;
|
|
|
|
|
SaveStates();
|
|
|
|
|
Debug.Log($"[TapADN SmartLoad] Preload triggered: type={adType}, scenario={scenario}, score={EstimateShowProbability(adType, scenario):F3}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static bool NeedPreload(AD_Type adType, string scenario, TapadnSmartLoadPolicyItem policy)
|
|
|
|
|
{
|
|
|
|
|
var score = EstimateShowProbability(adType, scenario);
|
|
|
|
|
return score >= policy.PreloadThreshold;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static float EstimateShowProbability(AD_Type adType, string scenario)
|
|
|
|
|
{
|
|
|
|
|
var policy = ResolvePolicy(adType, scenario);
|
|
|
|
|
if (policy == null)
|
|
|
|
|
{
|
|
|
|
|
return 0f;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var state = GetOrCreateState(adType, scenario);
|
|
|
|
|
if (state.EnterCount <= 0)
|
|
|
|
|
{
|
|
|
|
|
return policy.BaseProbability;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var observedRate = Mathf.Clamp01(state.PlayRequestCount / (float)state.EnterCount);
|
|
|
|
|
var confidence = Mathf.Clamp01(state.EnterCount / (float)Mathf.Max(1, policy.MinSamplesForConfidence));
|
|
|
|
|
var ageHours = Mathf.Max(0f, (GetNowUnixSeconds() - state.LastUpdatedUnix) / 3600f);
|
|
|
|
|
var decay = Mathf.Pow(0.5f, ageHours / Mathf.Max(1f, policy.DecayHalfLifeHours));
|
|
|
|
|
var trust = Mathf.Clamp01(confidence * decay);
|
|
|
|
|
|
|
|
|
|
return Mathf.Lerp(policy.BaseProbability, observedRate, trust);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-17 15:40:25 +08:00
|
|
|
private static void PrepareSmartLoadRequest(AD_Type adType, string scenario, string slotId)
|
2026-06-05 21:44:35 +08:00
|
|
|
{
|
2026-06-17 15:40:25 +08:00
|
|
|
var state = GetOrCreateCacheState(adType, slotId);
|
2026-06-05 21:44:35 +08:00
|
|
|
if (state.PendingSmartLoad)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
state.PreparedSmartLoad = true;
|
|
|
|
|
state.PreparedOriginScene = NormalizeScenario(scenario);
|
|
|
|
|
state.PreparedOriginScore = EstimateShowProbability(adType, scenario);
|
|
|
|
|
state.PreparedRequestUnix = GetNowUnixSeconds();
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-17 15:40:25 +08:00
|
|
|
private static void BeginLoadRequest(AD_Type adType, string scenario, string slotId)
|
2026-06-05 21:44:35 +08:00
|
|
|
{
|
2026-06-17 15:40:25 +08:00
|
|
|
var state = GetOrCreateCacheState(adType, slotId);
|
2026-06-05 21:44:35 +08:00
|
|
|
var now = GetNowUnixSeconds();
|
|
|
|
|
if (state.PreparedSmartLoad)
|
|
|
|
|
{
|
|
|
|
|
var originScene = NormalizeScenario(state.PreparedOriginScene);
|
|
|
|
|
var record = GetOrCreateState(adType, originScene);
|
|
|
|
|
record.PreloadRequestCount = Math.Max(0, record.PreloadRequestCount) + 1;
|
|
|
|
|
record.LastUpdatedUnix = now;
|
|
|
|
|
state.PendingSmartLoad = true;
|
|
|
|
|
state.PendingOriginScene = originScene;
|
|
|
|
|
state.PendingOriginScore = state.PreparedOriginScore;
|
|
|
|
|
state.PendingRequestUnix = now;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
state.PendingSmartLoad = false;
|
|
|
|
|
state.PendingOriginScene = NormalizeScenario(scenario);
|
|
|
|
|
state.PendingOriginScore = EstimateShowProbability(adType, scenario);
|
|
|
|
|
state.PendingRequestUnix = now;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
state.HasReadyCache = false;
|
|
|
|
|
state.PreparedSmartLoad = false;
|
|
|
|
|
state.PreparedOriginScene = null;
|
|
|
|
|
state.PreparedOriginScore = 0f;
|
|
|
|
|
state.PreparedRequestUnix = 0;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-17 15:40:25 +08:00
|
|
|
private static void ClearPreparedLoadIfNotStarted(AD_Type adType, string slotId)
|
2026-06-05 21:44:35 +08:00
|
|
|
{
|
2026-06-17 15:40:25 +08:00
|
|
|
var state = GetOrCreateCacheState(adType, slotId);
|
2026-06-05 21:44:35 +08:00
|
|
|
if (state.PendingSmartLoad)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
state.PreparedSmartLoad = false;
|
|
|
|
|
state.PreparedOriginScene = null;
|
|
|
|
|
state.PreparedOriginScore = 0f;
|
|
|
|
|
state.PreparedRequestUnix = 0;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-17 15:40:25 +08:00
|
|
|
private static bool HasPendingSmartLoadForScene(AD_Type adType, string scenario, string slotId)
|
2026-06-05 21:44:35 +08:00
|
|
|
{
|
2026-06-17 15:40:25 +08:00
|
|
|
var state = GetOrCreateCacheState(adType, slotId);
|
2026-06-05 21:44:35 +08:00
|
|
|
return state.PendingSmartLoad &&
|
|
|
|
|
string.Equals(NormalizeScenario(state.PendingOriginScene), NormalizeScenario(scenario), StringComparison.Ordinal);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-17 15:40:25 +08:00
|
|
|
private static void CompleteLoadRequest(AD_Type adType, string scenario, bool success, string slotId)
|
2026-06-05 21:44:35 +08:00
|
|
|
{
|
2026-06-17 15:40:25 +08:00
|
|
|
var cacheState = GetOrCreateCacheState(adType, slotId);
|
2026-06-05 21:44:35 +08:00
|
|
|
var normalizedScenario = NormalizeScenario(scenario);
|
|
|
|
|
var originScene = cacheState.PendingSmartLoad ? NormalizeScenario(cacheState.PendingOriginScene) : normalizedScenario;
|
|
|
|
|
var originRecord = GetOrCreateState(adType, originScene);
|
|
|
|
|
|
|
|
|
|
if (cacheState.PendingSmartLoad)
|
|
|
|
|
{
|
|
|
|
|
if (success)
|
|
|
|
|
{
|
|
|
|
|
originRecord.PreloadSuccessCount = Math.Max(0, originRecord.PreloadSuccessCount) + 1;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
originRecord.PreloadFailureCount = Math.Max(0, originRecord.PreloadFailureCount) + 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
originRecord.LastUpdatedUnix = GetNowUnixSeconds();
|
|
|
|
|
if (success)
|
|
|
|
|
{
|
|
|
|
|
cacheState.HasReadyCache = true;
|
|
|
|
|
cacheState.ReadyFromSmart = cacheState.PendingSmartLoad;
|
|
|
|
|
cacheState.ReadyOriginScene = originScene;
|
|
|
|
|
cacheState.ReadyOriginScore = cacheState.PendingSmartLoad ? cacheState.PendingOriginScore : EstimateShowProbability(adType, originScene);
|
|
|
|
|
cacheState.ReadyLoadedUnix = GetNowUnixSeconds();
|
|
|
|
|
cacheState.ReadyRequestUnix = cacheState.PendingRequestUnix;
|
|
|
|
|
cacheState.Consumed = false;
|
|
|
|
|
}
|
|
|
|
|
else if (cacheState.PendingSmartLoad)
|
|
|
|
|
{
|
|
|
|
|
ClearReadyCache(cacheState);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cacheState.PendingSmartLoad = false;
|
|
|
|
|
cacheState.PendingOriginScene = null;
|
|
|
|
|
cacheState.PendingOriginScore = 0f;
|
|
|
|
|
cacheState.PendingRequestUnix = 0;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-17 15:40:25 +08:00
|
|
|
private static void MarkImmediateHit(AD_Type adType, string scenario, string slotId)
|
2026-06-05 21:44:35 +08:00
|
|
|
{
|
|
|
|
|
var consumedScene = NormalizeScenario(scenario);
|
|
|
|
|
var currentSceneRecord = GetOrCreateState(adType, consumedScene);
|
|
|
|
|
currentSceneRecord.ImmediateHitCount = Math.Max(0, currentSceneRecord.ImmediateHitCount) + 1;
|
|
|
|
|
currentSceneRecord.LastUpdatedUnix = GetNowUnixSeconds();
|
|
|
|
|
|
2026-06-17 15:40:25 +08:00
|
|
|
var cacheState = GetOrCreateCacheState(adType, slotId);
|
2026-06-05 21:44:35 +08:00
|
|
|
if (!cacheState.HasReadyCache)
|
|
|
|
|
{
|
|
|
|
|
currentSceneRecord.UnattributedCacheHitCount = Math.Max(0, currentSceneRecord.UnattributedCacheHitCount) + 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-17 15:40:25 +08:00
|
|
|
private static void MarkCacheConsumed(AD_Type adType, string scenario, string slotId)
|
2026-06-05 21:44:35 +08:00
|
|
|
{
|
2026-06-17 15:40:25 +08:00
|
|
|
var cacheState = GetOrCreateCacheState(adType, slotId);
|
2026-06-05 21:44:35 +08:00
|
|
|
if (!cacheState.HasReadyCache || cacheState.Consumed)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var consumedScene = NormalizeScenario(scenario);
|
|
|
|
|
var originScene = NormalizeScenario(cacheState.ReadyOriginScene);
|
|
|
|
|
var currentSceneRecord = GetOrCreateState(adType, consumedScene);
|
|
|
|
|
var originSceneRecord = GetOrCreateState(adType, originScene);
|
|
|
|
|
|
|
|
|
|
if (cacheState.ReadyFromSmart)
|
|
|
|
|
{
|
|
|
|
|
currentSceneRecord.SmartCacheHitCount = Math.Max(0, currentSceneRecord.SmartCacheHitCount) + 1;
|
|
|
|
|
originSceneRecord.SmartPreloadConsumedCount = Math.Max(0, originSceneRecord.SmartPreloadConsumedCount) + 1;
|
|
|
|
|
originSceneRecord.LastSmartPreloadConsumedByScene = consumedScene;
|
|
|
|
|
originSceneRecord.LastSmartPreloadConsumedUnix = GetNowUnixSeconds();
|
|
|
|
|
|
|
|
|
|
if (string.Equals(originScene, consumedScene, StringComparison.Ordinal))
|
|
|
|
|
{
|
|
|
|
|
originSceneRecord.SmartPreloadConsumedSameSceneCount = Math.Max(0, originSceneRecord.SmartPreloadConsumedSameSceneCount) + 1;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
currentSceneRecord.SmartCacheCrossSceneHitCount = Math.Max(0, currentSceneRecord.SmartCacheCrossSceneHitCount) + 1;
|
|
|
|
|
originSceneRecord.SmartPreloadConsumedOtherSceneCount = Math.Max(0, originSceneRecord.SmartPreloadConsumedOtherSceneCount) + 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
currentSceneRecord.LastUpdatedUnix = GetNowUnixSeconds();
|
|
|
|
|
originSceneRecord.LastUpdatedUnix = GetNowUnixSeconds();
|
|
|
|
|
cacheState.Consumed = true;
|
|
|
|
|
ClearReadyCache(cacheState);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-17 15:40:25 +08:00
|
|
|
private static void MarkCacheShowFailed(AD_Type adType, string scenario, string slotId)
|
2026-06-05 21:44:35 +08:00
|
|
|
{
|
2026-06-17 15:40:25 +08:00
|
|
|
var cacheState = GetOrCreateCacheState(adType, slotId);
|
2026-06-05 21:44:35 +08:00
|
|
|
if (!cacheState.HasReadyCache)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var originScene = NormalizeScenario(cacheState.ReadyOriginScene);
|
|
|
|
|
if (cacheState.ReadyFromSmart)
|
|
|
|
|
{
|
|
|
|
|
var originSceneRecord = GetOrCreateState(adType, originScene);
|
|
|
|
|
originSceneRecord.SmartPreloadShowFailureCount = Math.Max(0, originSceneRecord.SmartPreloadShowFailureCount) + 1;
|
|
|
|
|
originSceneRecord.LastSmartPreloadConsumedByScene = NormalizeScenario(scenario);
|
|
|
|
|
originSceneRecord.LastSmartPreloadConsumedUnix = GetNowUnixSeconds();
|
|
|
|
|
originSceneRecord.LastUpdatedUnix = GetNowUnixSeconds();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ClearReadyCache(cacheState);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-17 15:40:25 +08:00
|
|
|
private static void MarkCacheExpiredIfStale(AD_Type adType, string slotId)
|
2026-06-05 21:44:35 +08:00
|
|
|
{
|
2026-06-17 15:40:25 +08:00
|
|
|
var cacheState = GetOrCreateCacheState(adType, slotId);
|
2026-06-05 21:44:35 +08:00
|
|
|
if (!cacheState.HasReadyCache)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ADManager.Instance.IsRealy(adType))
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (cacheState.ReadyFromSmart)
|
|
|
|
|
{
|
|
|
|
|
var originSceneRecord = GetOrCreateState(adType, NormalizeScenario(cacheState.ReadyOriginScene));
|
|
|
|
|
originSceneRecord.SmartPreloadExpiredCount = Math.Max(0, originSceneRecord.SmartPreloadExpiredCount) + 1;
|
|
|
|
|
originSceneRecord.LastUpdatedUnix = GetNowUnixSeconds();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ClearReadyCache(cacheState);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-17 15:40:25 +08:00
|
|
|
private static TapadnSmartLoadCacheState GetOrCreateCacheState(AD_Type adType, string slotId)
|
2026-06-05 21:44:35 +08:00
|
|
|
{
|
2026-06-17 15:40:25 +08:00
|
|
|
var key = ComposeCacheKey(adType, slotId);
|
2026-06-05 21:44:35 +08:00
|
|
|
if (_cacheStates.TryGetValue(key, out var state) && state != null)
|
|
|
|
|
{
|
|
|
|
|
return state;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
state = new TapadnSmartLoadCacheState();
|
|
|
|
|
_cacheStates[key] = state;
|
|
|
|
|
return state;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void ClearReadyCache(TapadnSmartLoadCacheState state)
|
|
|
|
|
{
|
|
|
|
|
if (state == null)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
state.HasReadyCache = false;
|
|
|
|
|
state.ReadyFromSmart = false;
|
|
|
|
|
state.ReadyOriginScene = null;
|
|
|
|
|
state.ReadyOriginScore = 0f;
|
|
|
|
|
state.ReadyLoadedUnix = 0;
|
|
|
|
|
state.ReadyRequestUnix = 0;
|
|
|
|
|
state.Consumed = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static string GetCacheDebugDump()
|
|
|
|
|
{
|
|
|
|
|
if (_cacheStates == null || _cacheStates.Count == 0)
|
|
|
|
|
{
|
|
|
|
|
return "empty";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var lines = new List<string>();
|
|
|
|
|
foreach (var entry in _cacheStates)
|
|
|
|
|
{
|
|
|
|
|
var state = entry.Value;
|
|
|
|
|
if (state == null)
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
lines.Add($"[{entry.Key}]prepared={state.PreparedSmartLoad}:{NormalizeScenario(state.PreparedOriginScene)} pending={state.PendingSmartLoad}:{NormalizeScenario(state.PendingOriginScene)} ready={state.HasReadyCache}:{state.ReadyFromSmart}:{NormalizeScenario(state.ReadyOriginScene)} score={state.ReadyOriginScore:F3}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return string.Join(" | ", lines);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static string GetSnapshotCsvHeader()
|
|
|
|
|
{
|
|
|
|
|
return "ad_type,scenario,enter_count,play_request_count,preload_request,preload_success,preload_fail,show_request,show_start,show_fail,immediate_hit,smart_cache_hit,smart_cache_cross_scene_hit,unattributed_cache_hit,smart_preload_consumed,smart_preload_consumed_same_scene,smart_preload_consumed_other_scene,smart_preload_show_fail,smart_preload_expired,last_smart_preload_consumed_by_scene,last_smart_preload_consumed_unix,last_preload_unix,last_updated_unix,last_score";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static TapadnSmartLoadPolicyItem ResolvePolicy(AD_Type adType, string scenario)
|
|
|
|
|
{
|
|
|
|
|
if (_runtimeConfig == null)
|
|
|
|
|
{
|
|
|
|
|
_runtimeConfig = BuildDefaultConfig();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var normalizedScenario = NormalizeScenario(scenario);
|
|
|
|
|
var exactKey = ComposeKey(adType, normalizedScenario);
|
|
|
|
|
if (_policies.TryGetValue(exactKey, out var exactPolicy))
|
|
|
|
|
{
|
|
|
|
|
return exactPolicy;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var defaultScenarioKey = ComposeKey(adType, PolicyDefaultScene);
|
|
|
|
|
if (_policies.TryGetValue(defaultScenarioKey, out var defaultPolicy))
|
|
|
|
|
{
|
|
|
|
|
return defaultPolicy;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return _runtimeConfig?.GlobalDefault != null ? _runtimeConfig.GlobalDefault : BuildDefaultConfig().GlobalDefault;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void EnsureDefaultPolicies(TapadnSmartLoadConfig config)
|
|
|
|
|
{
|
|
|
|
|
_policies.Clear();
|
|
|
|
|
if (config == null || config.ScenePolicies == null)
|
|
|
|
|
{
|
|
|
|
|
config = BuildDefaultConfig();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (config.GlobalDefault == null)
|
|
|
|
|
{
|
|
|
|
|
config.GlobalDefault = BuildDefaultPolicy();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach (var policy in config.ScenePolicies)
|
|
|
|
|
{
|
|
|
|
|
if (policy == null || string.IsNullOrWhiteSpace(policy.Scene))
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (policy.AdType < 0 || policy.AdType > 2)
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var key = ComposeKey((AD_Type)policy.AdType, NormalizeScenario(policy.Scene));
|
|
|
|
|
_policies[key] = new TapadnSmartLoadPolicyItem
|
|
|
|
|
{
|
|
|
|
|
AdType = policy.AdType,
|
|
|
|
|
Scene = NormalizeScenario(policy.Scene),
|
|
|
|
|
BaseProbability = Mathf.Clamp01(policy.BaseProbability),
|
|
|
|
|
PreloadThreshold = Mathf.Clamp(policy.PreloadThreshold, 0f, 1f),
|
|
|
|
|
CooldownSeconds = Math.Max(0, policy.CooldownSeconds),
|
|
|
|
|
MinSamplesForConfidence = Math.Max(1, policy.MinSamplesForConfidence),
|
|
|
|
|
DecayHalfLifeHours = Math.Max(1f, policy.DecayHalfLifeHours),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach (AD_Type adType in Enum.GetValues(typeof(AD_Type)))
|
|
|
|
|
{
|
|
|
|
|
var key = ComposeKey(adType, PolicyDefaultScene);
|
|
|
|
|
if (!_policies.ContainsKey(key))
|
|
|
|
|
{
|
|
|
|
|
_policies[key] = ClonePolicy(config.GlobalDefault, adType);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static TapadnSmartLoadPolicyItem ClonePolicy(TapadnSmartLoadPolicyItem source, AD_Type adType)
|
|
|
|
|
{
|
|
|
|
|
if (source == null)
|
|
|
|
|
{
|
|
|
|
|
source = BuildDefaultPolicy();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return new TapadnSmartLoadPolicyItem
|
|
|
|
|
{
|
|
|
|
|
AdType = (int)adType,
|
|
|
|
|
Scene = PolicyDefaultScene,
|
|
|
|
|
BaseProbability = source.BaseProbability,
|
|
|
|
|
PreloadThreshold = source.PreloadThreshold,
|
|
|
|
|
CooldownSeconds = source.CooldownSeconds,
|
|
|
|
|
MinSamplesForConfidence = source.MinSamplesForConfidence,
|
|
|
|
|
DecayHalfLifeHours = source.DecayHalfLifeHours
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static TapadnSmartLoadConfig BuildConfig(TapadnControllerOptions options)
|
|
|
|
|
{
|
|
|
|
|
var config = BuildDefaultConfig();
|
|
|
|
|
MergePolicyJson(config, ReadPolicyAssetJson(options));
|
|
|
|
|
MergePolicyJson(config, options?.SmartPreloadConfigJson);
|
|
|
|
|
MergePolicyJson(config, options?.SmartPreloadRemoteConfigJson);
|
|
|
|
|
return config;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static string ReadPolicyAssetJson(TapadnControllerOptions options)
|
|
|
|
|
{
|
|
|
|
|
if (options == null || string.IsNullOrWhiteSpace(options.SmartPreloadConfigAssetPath))
|
|
|
|
|
{
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var asset = Resources.Load<TextAsset>(options.SmartPreloadConfigAssetPath);
|
|
|
|
|
return asset != null ? asset.text : null;
|
|
|
|
|
}
|
|
|
|
|
catch (Exception exception)
|
|
|
|
|
{
|
|
|
|
|
Debug.LogWarning($"[TapADN SmartLoad] Load policy asset failed: {exception.Message}");
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static TapadnSmartLoadConfig BuildDefaultConfig()
|
|
|
|
|
{
|
|
|
|
|
return new TapadnSmartLoadConfig
|
|
|
|
|
{
|
|
|
|
|
GlobalDefault = BuildDefaultPolicy(),
|
|
|
|
|
ScenePolicies = new List<TapadnSmartLoadPolicyItemDto>(),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static TapadnSmartLoadPolicyItem BuildDefaultPolicy()
|
|
|
|
|
{
|
|
|
|
|
return new TapadnSmartLoadPolicyItem
|
|
|
|
|
{
|
|
|
|
|
AdType = -1,
|
|
|
|
|
Scene = PolicyDefaultScene,
|
|
|
|
|
BaseProbability = 0.08f,
|
|
|
|
|
PreloadThreshold = 0.75f,
|
|
|
|
|
CooldownSeconds = 120,
|
|
|
|
|
MinSamplesForConfidence = 8,
|
|
|
|
|
DecayHalfLifeHours = 72f
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void MergePolicyJson(TapadnSmartLoadConfig target, string json)
|
|
|
|
|
{
|
|
|
|
|
if (string.IsNullOrWhiteSpace(json))
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var parsed = JsonUtility.FromJson<TapadnSmartLoadConfig>(json);
|
|
|
|
|
if (parsed == null)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (parsed.GlobalDefault != null)
|
|
|
|
|
{
|
|
|
|
|
target.GlobalDefault = NormalizePolicy(parsed.GlobalDefault);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (parsed.ScenePolicies == null || parsed.ScenePolicies.Count == 0)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (target.ScenePolicies == null)
|
|
|
|
|
{
|
|
|
|
|
target.ScenePolicies = new List<TapadnSmartLoadPolicyItemDto>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach (var policy in parsed.ScenePolicies)
|
|
|
|
|
{
|
|
|
|
|
if (policy == null)
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (policy.AdType < 0 || policy.AdType > 2)
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(policy.Scene))
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
target.ScenePolicies.Add(policy);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (Exception exception)
|
|
|
|
|
{
|
|
|
|
|
Debug.LogWarning($"[TapADN SmartLoad] Merge policy JSON failed: {exception.Message}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static string EncodeCsv(string value)
|
|
|
|
|
{
|
|
|
|
|
if (string.IsNullOrEmpty(value))
|
|
|
|
|
{
|
|
|
|
|
return "0";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (value.Contains(",") || value.Contains("\"") || value.Contains("\n"))
|
|
|
|
|
{
|
|
|
|
|
return $"\"{value.Replace("\"", "\"\"")}\"";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static TapadnSmartLoadPolicyItem NormalizePolicy(TapadnSmartLoadPolicyItem policy)
|
|
|
|
|
{
|
|
|
|
|
return new TapadnSmartLoadPolicyItem
|
|
|
|
|
{
|
|
|
|
|
AdType = policy.AdType,
|
|
|
|
|
Scene = NormalizeScenario(policy.Scene),
|
|
|
|
|
BaseProbability = Mathf.Clamp01(policy.BaseProbability),
|
|
|
|
|
PreloadThreshold = Mathf.Clamp(policy.PreloadThreshold, 0f, 1f),
|
|
|
|
|
CooldownSeconds = Math.Max(0, policy.CooldownSeconds),
|
|
|
|
|
MinSamplesForConfidence = Math.Max(1, policy.MinSamplesForConfidence),
|
|
|
|
|
DecayHalfLifeHours = Math.Max(1f, policy.DecayHalfLifeHours),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static TapadnSmartLoadSceneState GetOrCreateState(AD_Type adType, string scenario)
|
|
|
|
|
{
|
|
|
|
|
var key = ComposeKey(adType, NormalizeScenario(scenario));
|
|
|
|
|
if (_states.TryGetValue(key, out var state))
|
|
|
|
|
{
|
|
|
|
|
return state;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
state = new TapadnSmartLoadSceneState
|
|
|
|
|
{
|
|
|
|
|
AdType = (int)adType,
|
|
|
|
|
Scenario = NormalizeScenario(scenario),
|
|
|
|
|
EnterCount = 0,
|
|
|
|
|
PlayRequestCount = 0,
|
|
|
|
|
LastUpdatedUnix = GetNowUnixSeconds()
|
|
|
|
|
};
|
|
|
|
|
_states[key] = state;
|
|
|
|
|
return state;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void LoadStates()
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var raw = PlayerPrefs.GetString(StatsPrefsKey, string.Empty);
|
|
|
|
|
if (string.IsNullOrWhiteSpace(raw))
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var data = JsonUtility.FromJson<TapadnSmartLoadStateData>(raw);
|
|
|
|
|
if (data == null || data.Entries == null)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach (var entry in data.Entries)
|
|
|
|
|
{
|
|
|
|
|
if (entry == null || entry.AdType < 0)
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var adType = SafeToAdType(entry.AdType);
|
|
|
|
|
var scenario = NormalizeScenario(entry.Scenario);
|
|
|
|
|
if (adType == null)
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var key = ComposeKey(adType.Value, scenario);
|
|
|
|
|
_states[key] = entry;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (Exception exception)
|
|
|
|
|
{
|
|
|
|
|
Debug.LogWarning($"[TapADN SmartLoad] Load state failed: {exception.Message}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static AD_Type? SafeToAdType(int value)
|
|
|
|
|
{
|
|
|
|
|
if (value < 0 || value > 2)
|
|
|
|
|
{
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (AD_Type)value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void SaveStates()
|
|
|
|
|
{
|
|
|
|
|
var data = new TapadnSmartLoadStateData();
|
|
|
|
|
data.Entries = new List<TapadnSmartLoadSceneState>();
|
|
|
|
|
|
|
|
|
|
foreach (var entry in _states.Values)
|
|
|
|
|
{
|
|
|
|
|
data.Entries.Add(entry);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var raw = JsonUtility.ToJson(data);
|
|
|
|
|
if (string.IsNullOrWhiteSpace(raw))
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PlayerPrefs.SetString(StatsPrefsKey, raw);
|
|
|
|
|
PlayerPrefs.Save();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static string ComposeKey(AD_Type adType, string scenario)
|
|
|
|
|
{
|
|
|
|
|
return ((int)adType) + "|" + NormalizeScenario(scenario);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-17 15:40:25 +08:00
|
|
|
private static string ComposeCacheKey(AD_Type adType, string slotId)
|
|
|
|
|
{
|
|
|
|
|
return ((int)adType) + "|" + NormalizeSlotId(slotId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static string NormalizeSlotId(string slotId)
|
|
|
|
|
{
|
|
|
|
|
return string.IsNullOrWhiteSpace(slotId) ? "__default_slot__" : slotId.Trim();
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-05 21:44:35 +08:00
|
|
|
private static string NormalizeScenario(string scenario)
|
|
|
|
|
{
|
|
|
|
|
if (string.IsNullOrWhiteSpace(scenario))
|
|
|
|
|
{
|
|
|
|
|
return DefaultScene;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return scenario.Trim();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static long GetNowUnixSeconds()
|
|
|
|
|
{
|
|
|
|
|
return (long)(DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Serializable]
|
|
|
|
|
private sealed class TapadnSmartLoadConfig
|
|
|
|
|
{
|
|
|
|
|
public TapadnSmartLoadPolicyItem GlobalDefault;
|
|
|
|
|
public List<TapadnSmartLoadPolicyItemDto> ScenePolicies;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Serializable]
|
|
|
|
|
private sealed class TapadnSmartLoadPolicyItem
|
|
|
|
|
{
|
|
|
|
|
public int AdType;
|
|
|
|
|
public string Scene;
|
|
|
|
|
public float BaseProbability;
|
|
|
|
|
public float PreloadThreshold;
|
|
|
|
|
public int CooldownSeconds;
|
|
|
|
|
public int MinSamplesForConfidence;
|
|
|
|
|
public float DecayHalfLifeHours;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Serializable]
|
|
|
|
|
private sealed class TapadnSmartLoadPolicyItemDto
|
|
|
|
|
{
|
|
|
|
|
public int AdType;
|
|
|
|
|
public string Scene;
|
|
|
|
|
public float BaseProbability = 0.08f;
|
|
|
|
|
public float PreloadThreshold = 0.75f;
|
|
|
|
|
public int CooldownSeconds = 120;
|
|
|
|
|
public int MinSamplesForConfidence = 8;
|
|
|
|
|
public float DecayHalfLifeHours = 72f;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Serializable]
|
|
|
|
|
private sealed class TapadnSmartLoadStateData
|
|
|
|
|
{
|
|
|
|
|
public int Version = 1;
|
|
|
|
|
public List<TapadnSmartLoadSceneState> Entries;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private sealed class TapadnSmartLoadCacheState
|
|
|
|
|
{
|
|
|
|
|
public bool PreparedSmartLoad;
|
|
|
|
|
public string PreparedOriginScene;
|
|
|
|
|
public float PreparedOriginScore;
|
|
|
|
|
public long PreparedRequestUnix;
|
|
|
|
|
public bool PendingSmartLoad;
|
|
|
|
|
public string PendingOriginScene;
|
|
|
|
|
public float PendingOriginScore;
|
|
|
|
|
public long PendingRequestUnix;
|
|
|
|
|
public bool HasReadyCache;
|
|
|
|
|
public bool ReadyFromSmart;
|
|
|
|
|
public string ReadyOriginScene;
|
|
|
|
|
public float ReadyOriginScore;
|
|
|
|
|
public long ReadyLoadedUnix;
|
|
|
|
|
public long ReadyRequestUnix;
|
|
|
|
|
public bool Consumed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Serializable]
|
|
|
|
|
private sealed class TapadnSmartLoadSceneState
|
|
|
|
|
{
|
|
|
|
|
public int AdType;
|
|
|
|
|
public string Scenario;
|
|
|
|
|
public int EnterCount;
|
|
|
|
|
public int PlayRequestCount;
|
|
|
|
|
public int PreloadRequestCount;
|
|
|
|
|
public int PreloadSuccessCount;
|
|
|
|
|
public int PreloadFailureCount;
|
|
|
|
|
public int ShowRequestCount;
|
|
|
|
|
public int ShowStartCount;
|
|
|
|
|
public int ShowFailureCount;
|
|
|
|
|
public int ImmediateHitCount;
|
|
|
|
|
public int SmartCacheHitCount;
|
|
|
|
|
public int SmartCacheCrossSceneHitCount;
|
|
|
|
|
public int UnattributedCacheHitCount;
|
|
|
|
|
public int SmartPreloadConsumedCount;
|
|
|
|
|
public int SmartPreloadConsumedSameSceneCount;
|
|
|
|
|
public int SmartPreloadConsumedOtherSceneCount;
|
|
|
|
|
public int SmartPreloadShowFailureCount;
|
|
|
|
|
public int SmartPreloadExpiredCount;
|
|
|
|
|
public string LastSmartPreloadConsumedByScene;
|
|
|
|
|
public long LastSmartPreloadConsumedUnix;
|
|
|
|
|
public long LastUpdatedUnix;
|
|
|
|
|
public long LastPreloadUnix;
|
|
|
|
|
}
|
|
|
|
|
}
|