Skip to content

活动资源优化 - 后置加载方案

概述

为了优化进入大厅的加载速度,将部分活动资源从预加载改为后置加载。玩家进入大厅后,这些活动显示加载占位符,资源在后台下载完成后自动替换为实际入口。


当前架构(v2.0 - 事件驱动)

重构日期:2025-10-24 提交哈希:e8b62e66889 完整工作记录WTC 资源加载优化任务跟踪

架构原则

依赖倒置 - 资源层不依赖 UI 层 ✅ 职责分离 - 资源层负责下载,UI 层负责显示 ✅ 事件驱动 - 通过事件实现完全解耦 ✅ 生命周期安全 - 无跨层引用,无野指针风险

模块结构

ActivityDownloader (src/common/resource_v2/downloaders/)
  ├─ 职责:纯资源下载逻辑
  ├─ 输入:活动信息数组 [{activityId, themeName, ...}]
  ├─ 输出:事件通知(DOWNLOAD_START / DOWNLOAD_COMPLETE)
  └─ 特点:完全不涉及 UI,不持有任何 Node 引用

ResourceManV2 (src/common/resource_v2/)
  ├─ 统一资源下载入口
  └─ downloadActivities(activities) - 调度 ActivityDownloader

ActivityMan (src/task/model/)
  ├─ getLagLoadActivities() - 返回活动信息数组
  ├─ downloadLagLoadActivities() - 触发下载
  └─ createLagLoadActivityPlaceholders() - [已废弃] 保留兼容

ActivityEntranceGroupController (src/task/controller/activity_center/)
  ├─ 职责:完全接管 Placeholder 生命周期管理
  ├─ 监听:ACTIVITY_RESOURCE_DOWNLOAD_START
  ├─ 监听:ACTIVITY_RESOURCE_DOWNLOAD_COMPLETE
  ├─ _placeholderMap - 管理 placeholder 引用
  ├─ _onActivityDownloadStart() - 创建 placeholder
  └─ _onActivityDownloadComplete() - 移除 placeholder,创建真实入口

事件驱动流程

ActivityEntranceGroupController (UI层)

  ├─ onEnter()
   ├─ 监听 ACTIVITY_RESOURCE_DOWNLOAD_START
   └─ 监听 ACTIVITY_RESOURCE_DOWNLOAD_COMPLETE

  ├─ _startLagLoadActivityDownload()
   └─ ActivityMan.downloadLagLoadActivities()
        └─ ResourceManV2.downloadActivities(activities)
             └─ ActivityDownloader.download(activities)
                  ├─ 🔔 发送 DOWNLOAD_START 事件
                  ├─ 执行下载
                  └─ 🔔 发送 DOWNLOAD_COMPLETE 事件

  ├─ _onActivityDownloadStart(event)  监听到事件
   ├─ 创建 placeholder (UI操作)
   └─ 保存到 _placeholderMap

  └─ _onActivityDownloadComplete(event)  监听到事件
      ├─ _placeholderMap 获取 placeholder
      ├─ 移除 placeholder (UI操作)
      ├─ 创建真实入口 (UI操作)
      └─ 触发布局更新

事件定义

位置src/common/events/CommonEvent.js

javascript
// 单个活动资源下载开始(UI层监听)
ACTIVITY_RESOURCE_DOWNLOAD_START: "activity_resource_download_start"

// 单个活动资源下载完成(UI层监听)
ACTIVITY_RESOURCE_DOWNLOAD_COMPLETE: "activity_resource_download_complete"

事件数据格式

javascript
// DOWNLOAD_START 事件数据
{
    activityInfo: {
        activityId: string,
        activityName: string,
        themeName: string,
        activityTag: number
    }
}

// DOWNLOAD_COMPLETE 事件数据
{
    activityInfo: {
        activityId: string,
        activityName: string,
        themeName: string,
        isSuccess: boolean  // 下载是否成功
    }
}

TypeScript 类型支持

typescript
// ActivityDownloader.d.ts
interface ActivityInfo {
    activityId: string;
    activityName: string;
    themeName: string;
    activityTag?: number;
    isSilentLoad?: boolean;
    lagLoadPriority?: number;
}

class ActivityDownloader extends BaseDownloader {
    constructor();
    download(activities: ActivityInfo[]): void;
}

// ResourceManV2.d.ts
class ResourceManV2 {
    downloadActivities(activities: ActivityInfo[]): void;
}

关键代码位置

| 模块 | 路径 | 关键方法 | |------|------|---------| | ActivityDownloader | src/common/resource_v2/downloaders/ActivityDownloader.js | download(), _onDownloadComplete() | | ResourceManV2 | src/common/resource_v2/ResourceManV2.js | downloadActivities() | | ActivityMan | src/task/model/ActivityMan.js | getLagLoadActivities(), downloadLagLoadActivities() | | ActivityEntranceGroupController | src/task/controller/activity_center/ActivityEntranceGroupController.js | _onActivityDownloadStart(), _onActivityDownloadComplete() | | 事件定义 | src/common/events/CommonEvent.js | ACTIVITY_RESOURCE_DOWNLOAD_START, ACTIVITY_RESOURCE_DOWNLOAD_COMPLETE | | 类型定义 | src/common/resource_v2/downloaders/ActivityDownloader.d.ts | TypeScript 接口 |


配置说明

活动标记

字段类型说明
isSilentLoadBoolean是否静默加载(不显示占位符)
activityTagNumber入口节点的 tag 值
lagLoadPriorityNumber后置加载优先级(数值越小,优先级越高)

优先级规范

配置位置res_oldvegas/flavor/js_src/task/model/ActivityConfig.json

优先级范围分类典型活动
90核心系统活动SystemCasinoChallenge, SystemDailyMission, SystemMedalRush
101-102高优先级活动BingoIsland, Badges, GrowUpBounty, PigPower
110-120重要活动CollectCity, HotSpin, SurprisePackage, PosterCenter
130-150普通活动LuckyDraw, Season, AlbumCatcher 等
160-190低优先级活动VideoReward, Guide 等辅助功能

配置建议

  • 核心系统活动优先级最高(90),确保最先加载
  • 用户高频交互的活动设置高优先级(101-120)
  • 辅助功能和引导类活动设置低优先级(160-190)
  • 预留优先级区间,便于后续插入新活动

使用示例

javascript
// 需要占位符的活动
var activity = {
    activityId: "badges_2025",
    activityName: "Badges Event",
    themeName: "badges_2025",
    isSilentLoad: false,  // 显示占位符
    activityTag: 1001,
    lagLoadPriority: 150
};

// 静默加载的活动
var silentActivity = {
    activityId: "guide_activity",
    activityName: "Guide Activity",
    themeName: "guide",
    isSilentLoad: true,   // 不显示占位符
    activityTag: 1002,
    lagLoadPriority: 200
};

补单延迟加载机制

背景

活动采用延迟加载策略后(initActivity 和 startActivity 分离),补单(verify purchase)可能在活动资源未加载时触发,导致补单界面无法正常显示。

解决方案

通过新增专用事件和延迟回调机制,确保补单时活动资源已加载。

核心组件

1. 新增事件

位置src/task/enum/ActivityNoticeType.js

javascript
// 延迟加载活动的补单专用事件
NOTICE_VERIFY_PURCHASE_FOR_LOG_LOAD_ACTIVITY: 112

2. 延迟加载补单回调

位置src/task/entity/BaseActivity.js

javascript
/**
 * 延迟加载补单回调(资源加载完成后调用)
 * @param {*} args - 补单参数
 */
onVerifyPurchaseWithLagLoaing: function(args) {
    'use strict';
    // 1. 确保资源已加载
    this.startActivity();

    // 2. 调用原有补单回调
    this.onVerifyPurchase(args);
}

3. 事件监听器保护

位置src/task/model/ActivityMan.js

javascript
// 数据刷新时保护补单事件监听器
_refreshActivityData: function() {
    'use strict';
    // 移除旧事件监听器(排除补单事件)
    this._eventTarget.removeAllListeners(function(eventType) {
        return eventType !== ActivityNoticeType.NOTICE_VERIFY_PURCHASE_FOR_LOG_LOAD_ACTIVITY;
    });

    // 重新注册活动事件监听器
    // ...
}

工作流程

StoreMan 补单触发

  ├─ 发送 NOTICE_VERIFY_PURCHASE_FOR_LOG_LOAD_ACTIVITY 事件
   └─ noticeParams: {activityId, ...}


BaseActivity 接收事件

  ├─ onVerifyPurchaseWithLagLoaing(args)
   ├─ 1. this.startActivity()  确保资源加载
    └─ 如果未加载,触发资源加载流程

   └─ 2. this.onVerifyPurchase(args)  调用原有补单逻辑
        └─ 显示补单界面

  └─ 补单完成

关键代码位置

模块路径关键方法
StoreMansrc/store/model/StoreMan.jsverifyPurchase() - 发送补单事件
BaseActivitysrc/task/entity/BaseActivity.jsonVerifyPurchaseWithLagLoaing() - 延迟加载补单回调
ActivityNoticeTypesrc/task/enum/ActivityNoticeType.jsNOTICE_VERIFY_PURCHASE_FOR_LOG_LOAD_ACTIVITY - 事件定义
ActivityMansrc/task/model/ActivityMan.js_refreshActivityData() - 事件监听器保护

兼容性说明

  • ✅ 向后兼容:旧的 NOTICE_VERIFY_PURCHASE 事件仍然存在
  • ✅ 数据兼容:优化了 isCustomDataValid() 的 funcType 判断,兼容历史活动数据(funcType 为空)
  • ✅ 双事件共存:延迟加载活动使用新事件,普通活动仍使用旧事件

调试技巧

关键日志

javascript
// StoreMan
"[StoreMan] verifyPurchase: Triggering lag-load activity purchase verify"

// BaseActivity
"[BaseActivity] onVerifyPurchaseWithLagLoaing: Start loading resources for activity:"
"[BaseActivity] onVerifyPurchaseWithLagLoaing: Resources loaded, calling original callback"

常见问题排查

  1. 补单界面无法显示 → 检查活动是否正确配置了 lagLoadPriority
  2. 补单事件被清理 → 检查 ActivityMan._refreshActivityData() 是否正确保护了补单事件
  3. funcType 验证失败 → 检查活动数据中的 funcType 字段是否正确

维护建议

日常维护

  1. ✅ 新活动需明确配置 isSilentLoad 标志
  2. ✅ UI 层(ActivityEntranceGroupController)负责监听下载事件
  3. ✅ 资源层(ActivityDownloader)只负责下载,不涉及 UI
  4. ✅ 测试时注意观察事件触发和 placeholder 生命周期

调试技巧

关键日志

javascript
// ActivityEntranceGroupController
"[ActivityEntranceGroupController] Starting lag-load activity download, count:"
"[ActivityEntranceGroupController] Creating placeholder for:"
"[ActivityEntranceGroupController] Replacing placeholder with real entrance:"

// ActivityDownloader
"[ActivityDownloader] download: Start batch download, total activities:"
"[ActivityDownloader] _onDownloadComplete: Success, themeName:"

常见问题排查

  1. 占位符未创建 → 检查 isSilentLoad 配置和事件监听
  2. 下载完成后未替换 → 检查 ACTIVITY_RESOURCE_DOWNLOAD_COMPLETE 事件是否正确触发
  3. 活动未激活 → 检查 ActivityMan._activateLagLoadActivity() 是否被调用

大厅 Flagstone 入口刷新机制

背景

部分活动(如 CloverClash、WinnerSlots、TowerTrials 等)拥有大厅 Flagstone 入口。当这些活动配置为延迟加载时,需要在资源加载完成后动态刷新大厅入口列表。

支持的活动

活动名称活动类优先级
WinnerSlotsWinnerSlotsActivity160
TowerTrialsTowerTrialsActivity170
CloverClashCloverClashActivity150
SpinBattleRoyaleSpinBattleRoyaleActivity-
BadgesBadgesActivity102

核心机制

1. 事件定义

位置src/common/events/CommonEvent.js

javascript
// 延迟加载活动下载完成后触发大厅刷新
LAGLOAD_ACTIVITY_DOWNLOAD_ENDED: "lagload_activity_download_ended"

2. FlagStoneTableView 接口

位置src/common/custom_node/FlagStoneTableView.js

javascript
// 获取所有拥有 flagstone 入口的活动列表
getHasFlagStoneActivityList: function() {
    var list = [];
    // 按顺序检查各活动:WinnerSlots, TowerTrials, CloverClash, SpinBattle, Badges
    // 返回当前开放的活动数组
    return list;
}

// 按名称获取单个活动
getHasFlagStoneActivity: function(activityName) {
    // 返回活动对象或 null
}

3. 智能刷新逻辑

位置ClassicLobbyViewController.js / LobbyViewController.js

javascript
refreshActivityLobbyFlagstoneList: function() {
    // 1. 检查是否在 Main Lobby(非 Main Lobby 直接返回)
    if (window.ClassicLobbyTabType !== SubjectClassify.SUBJECT_ALL) return;

    // 2. 检查是否在动画/滚动中(如果是则延迟刷新)
    if (tableView.isDragging() || !tableView._hasScrollEnd) {
        this.schedule(this.updateLoop, 0.0);  // 轮询等待
        return;
    }

    // 3. 获取需要刷新的活动列表
    var activityList = this.flagstoneTable.getHasFlagStoneActivityList();

    // 4. 保存当前滚动位置,刷新列表
    var originOffset = tableView.getContentOffset();
    this.initFlagStone();

    // 5. 计算新增入口宽度,恢复滚动位置
    var offsetX = 0;
    for (var i = 0; i < activityList.length; i++) {
        offsetX += activityList[i].getEntranceNodeWidth();
    }
    tableView.setContentOffset(cc.p(originOffset.x - offsetX, originOffset.y));
}

工作流程

ActivityLoader 资源加载完成

  ├─ 发送 LAGLOAD_ACTIVITY_DOWNLOAD_ENDED 事件


LobbyViewController 接收事件

  ├─ refreshActivityLobbyFlagstoneList()

   ├─ 检查是否在 Main Lobby
   ├─ 检查是否在动画/滚动中
   └─ 如果是,延迟刷新(schedule 轮询)

   ├─ 获取活动列表 getHasFlagStoneActivityList()
   ├─ 保存滚动位置
   ├─ 刷新 flagstone 列表 initFlagStone()
   └─ 恢复滚动位置(加上新增入口宽度)

  └─ 刷新完成,用户无感知

配置说明

ActivityConfig.json 中配置延迟加载:

json
{
  "WinnerSlots": {
    "templateName": "WinnerSlotsActivity",
    "themeName": "winner_slots",
    "lagLoadPriority": 160,
    "isSilentLoad": true
  },
  "TowerTrials": {
    "templateName": "TowerTrialsActivity",
    "themeName": "tower_trials",
    "lagLoadPriority": 170,
    "isSilentLoad": true
  }
}

关键代码位置

模块路径关键方法
ClassicLobbyViewControllersrc/common/controller/classic/ClassicLobbyViewController.jsrefreshActivityLobbyFlagstoneList()
LobbyViewControllersrc/common/controller/LobbyViewController.jsrefreshActivityLobbyFlagstoneList()
ClassicFlagStoneTableViewsrc/common/custom_node/ClassicFlagStoneTableView.jsgetHasFlagStoneActivityList()
FlagStoneTableViewsrc/common/custom_node/FlagStoneTableView.jsgetHasFlagStoneActivityList()
CommonEventsrc/common/events/CommonEvent.jsLAGLOAD_ACTIVITY_DOWNLOAD_ENDED

性能指标

  • 首次进入大厅加载时间:减少约 30-50%(取决于后置加载活动数量)
  • 后台下载时间:约 2-5 秒(取决于网络和资源大小)
  • 并发下载数:由 ResourceManV2 控制(默认 3-5 个)

历史架构说明

v1.0 架构(已废弃)

v1.0 架构存在以下问题,已在 v2.0 中完全重构:

  • ❌ 资源层(ActivityResourceDownloadManager)直接持有 UI 层的 Node 引用
  • ❌ 资源层负责创建和管理 placeholder(违反单一职责)
  • ❌ 存在跨层引用和生命周期管理风险

当前状态

  • ⚠️ 不建议继续使用 createLagLoadActivityPlaceholders() 接口
  • ⚠️ 该接口已废弃,保留仅为向后兼容
  • ✅ 新代码应使用 downloadLagLoadActivities() 接口

详细的架构演进过程和技术决策,请参考:WTC 资源加载优化任务跟踪


最后更新:2025-11-26 架构版本:v2.1(事件驱动 + Flagstone 刷新) 维护者:WTC Team

Released under the MIT License.