Files
test/Docs/Addressables-Bootstrap.md
T
2026-05-25 10:33:40 +07:00

17 KiB
Raw Blame History

Addressables — Bootstrap & tóm tắt nghiệp vụ

Tài liệu này tóm tắt ghi chép Addressables (catalog / cache / remote) và các script runtime trong project perfect-world-unity: AddressablesCatalogUpdater, AddressablesRuntimeUrlRewriter, GameContentBootstrap, cùng chỉnh sửa AddressableManager.

Tham chiếu thêm: bản ghi chép tổng quan ban đầu (Addressables-Tong-hop.md nếu có trong repo hoặc bản copy ngoài project).


0. Hai scene — Content trước, Bootstrap sau (khuyến nghị)

Tách tải Addressables / catalog / bulk khỏi scene game để Bootstrap.unity (UI, CECUIManager, AddressableManager, …) không bị ảnh hưởng.

flowchart LR
  A[GameContentBootstrap.unity index 0] -->|sync OK LoadScene Single| B[Bootstrap.unity]
  B -->|BootstrapSceneController ~1s| C[LoginScene]
Scene Build index Chứa gì
GameContentBootstrap 0 Chỉ GameContentBootstrap (+ UI loading tuỳ chọn)
Bootstrap 1 BootstrapSceneController, AddressableManager, CECUIManager, … không gắn GameContentBootstrap
LoginScene Load từ BootstrapSceneController._nextSceneName như cũ

Setup một lần trong Editor: menu Perfect World → Addressables → Setup Two-Scene Bootstrap

  • Tạo Assets/PerfectWorld/Scene/GameContentBootstrap.unity (nếu chưa có)
  • Gỡ GameContentBootstrap khỏi Bootstrap.unity
  • Đưa scene Content lên index 0 Build Settings

Trên component GameContentBootstrap (scene Content):

Field Gợi ý
_loadNextSceneAfterSuccess Bật
_nextSceneName Bootstrap (tên scene trong Build Settings)
_stayOnSceneWhenSyncFails Bật — lỗi thì không vào game

Sau khi sync thành công: GameContentBootstrapSession.IsContentReady = true, Addressables đã init → scene Bootstrap chỉ cần AddressableManager / AUIManager gọi AddressablesInitService (không chờ gate).


Cấu hình component (GameContentBootstrap)

Hai chế độ

Chế độ _useServerForVersionInfo Bạn làm gì
Test / không API Tắt Chỉ cần hardcode (bảng dưới). Không cần _versionEndpointUrl.
Production Bật Điền URL API + (tuỳ chọn) POST body. Server sau khi kiểm tra dữ liệu trả JSON có contentVersion và (nếu cần) assetsBaseUrl.

Editor: bật _skipRemoteCallInEditor nếu muốn không gọi mạng trong Editor; lúc đó luôn dùng hardcode / _editorFakeContentVersion như checklist cuối tài liệu.

Các biến cần biết (theo thứ tự ưu tiên đọc)

Biến Khi nào cần Điền gì (ngắn gọn)
_useServerForVersionInfo Luôn quyết định trước Test → tắt. Live → bật.
_hardcodedContentVersion Server tắt hoặc Editor skip Chuỗi version (vd 1, 20250514). Đổi số này = game coi như có content mới → catalog + bulk download.
_hardcodedAssetsBaseUrl Test CDN / không dùng server Prefix URL thư mục remote Addressables trên CDN (vd https://my-cdn.com/PW/Android/). Phải khớp cấu trúc upload ServerData/<Platform>/. Để trống nếu bundle đã trỏ đúng URL lúc build, không cần đổi host.
_versionEndpointUrl Chỉ khi server bật URL đầy đủ một endpoint do backend cung cấp, vd https://api.game.com/v1/addressables/resolve.
_usePostForVersionRequest Server bật Bật nếu luồng của bạn là: client gửi JSON lên → server validate → trả link/prefix. Tắt = GET (không body).
_versionPostBody POST bật JSON một dòng hoặc nhiều dòng (vd {"platform":"Android","token":"..."}). Để trống → client gửi {}.
_bakedRemoteUrlPrefixForRewrite Khi dùng assetsBaseUrl (từ server hoặc hardcode) Đúng phần đầu URL đã bake trong catalog (trùng Remote.LoadPath lúc build), vd https://old-build-cdn.example.com/Android/. Runtime thay prefix này bằng assetsBaseUrl. Để trống nếu không rewrite. Phải khớp đầu mọi InternalId remote cần đổi host.
_remoteBulkDownloadLabel Gần như luôn Trùng label trên group Addressables remote cần tải full (mặc định RemoteContent).
_holdAddressablesInitUntilVersionChecked Khuyến nghị Giữ bật để Addressables init sau khi có version + rewrite.
_skipRemoteCallInEditor / _editorFakeContentVersion Editor Bật skip + fake version khi không muốn HTTP trong Editor.

_versionEndpointUrl vs _bakedRemoteUrlPrefixForRewrite — ví dụ một dòng

  • _versionEndpointUrl: không phải link tải từng bundle; là URL gọi API (GET hoặc POST) để lấy metadata: contentVersion + tuỳ chọn assetsBaseUrl.
  • _bakedRemoteUrlPrefixForRewrite: không gọi trực tiếp; là chuỗi copy từ build (prefix URL trong catalog). Ví dụ build profile Remote.LoadPath = https://cdn-a.com/Android → điền y hệt vào field này; server trả assetsBaseUrl = https://cdn-b.com/Android → mọi bundle path đổi từ cdn-a sang cdn-b.

Luồng bạn muốn (POST → server kiểm tra → trả “link”)

  1. Bật _useServerForVersionInfo, điền _versionEndpointUrl, bật _usePostForVersionRequest, điền _versionPostBody (vd token, platform, build channel).
  2. Client POST Content-Type: application/json tới URL đó.
  3. Server validate; nếu OK trả cùng format như trước (để JsonUtility parse được):
{
  "contentVersion": "12",
  "assetsBaseUrl": "https://cdn.example.com/PW/Android/"
}

assetsBaseUrl ở đây là base / prefix cho remote Addressables (nơi có catalog_*.json và bundle), không nhất thiết là một URL file đơn lẻ — Addressables vẫn dùng catalog + download theo label như cũ.

  1. Tắt _useServerForVersionInfo.
  2. Điền _hardcodedContentVersion (vd test1).
  3. Nếu cần trỏ CDN test khác với URL đã bake trong catalog: điền _hardcodedAssetsBaseUrl _bakedRemoteUrlPrefixForRewrite như bảng trên.
  4. Editor: có thể bật _skipRemoteCallInEditor để khỏi gọi server khi test trong Editor.

1. Khái niệm nhanh (catalog / bundle / cache)

Thành phần Vai trò
Catalog (catalog_*.json + .hash) Danh mục: address → bundle, URL, hash/CRC… Catalog mới = tín hiệu “có nội dung mới”.
AssetBundle Chứa dữ liệu asset thực tế.
Nhóm Local / Remote Local: kèm build / StreamingAssets. Remote: tải qua HTTP(S).
Player / Editor Bundle theo platform — không dùng bundle Windows cho Android.

Cập nhật trên máy người chơi

  • Catalog trỏ tới bundle/hash/CRC khác lần trước luồng runtime thực hiện tải catalog mới / cập nhật catalog / load bundle thành công.
  • Không tự cập nhật chỉ vì file trên server đổi nếu game không tải catalog mới hoặc không load đúng key.
  • Release (RAM): asset có thể hết trong bộ nhớ; lần load sau có thể đọc lại từ bundle đã cache trên đĩakhông đồng nghĩa “tải lại toàn bộ từ CDN”.

Cache bundle

  • Nếu bundle đã cache hợp lệ theo catalog (CRC/hash đúng cấu hình), hệ thống ưu tiên dùng bản local, không tải lại cả file từ server.

Runtime đổi CDN / base URL

  • Remote.LoadPath trong profile là lúc build (URL ghi vào catalog).
  • Đổi host/path lúc chạy: dùng Addressables.InternalIdTransformFunc (rewrite InternalId) — trước InitializeAsync / load remote nhiều nhất có thể.

HTTP / HTTPS

  • Player Settings: Allow downloads over HTTP nếu dùng http:// (thường chỉ dev).
  • Production: HTTPS; Android/iOS có thể chặn cleartext.

2. Script trong project

2.1. AddressablesCatalogUpdater.cs

Bọc API Addressables (package ~2.7.x):

  • EnsureInitializedAsync
  • CheckForCatalogUpdatesAsync / UpdateCatalogsAsync / CheckAndUpdateCatalogsIfNeededAsync
  • GetDownloadSizeBytesAsync
  • DownloadDependenciesAsync
  • CleanBundleCacheAsync

2.2. AddressablesRuntimeUrlRewriter.cs

  • InstallPrefixRewrite(fromPrefix, toPrefix) — thay prefix URL trong InternalId (CDN mới).
  • ClearInstalledRewrite
  • ChainPreviousTransform — gọi transform cũ trước (nếu có).

2.3. GameContentVersionServerClient.cs

  • Struct GameContentVersionFetchResult (Ok, ContentVersion, AssetsBaseUrl, Error).
  • GameContentVersionServerClient.FetchAsync(url, timeoutSeconds, usePost, postJsonBody) — GET hoặc POST JSON (Content-Type: application/json), parse JSON phẳng (JsonUtility).

2.4. GameContentBootstrap.cs (scene bootstrap)

Nguồn phiên bản / URL

  • _useServerForVersionInfo: tắt = không gọi server, dùng _hardcodedContentVersion / _hardcodedAssetsBaseUrl (test, API chưa chốt).
  • Bật = gọi GameContentVersionServerClient.FetchAsync tới _versionEndpointUrl (GET hoặc POST theo _usePostForVersionRequest + _versionPostBody) (trừ khi Editor bật skip bên dưới).
  • _skipRemoteCallInEditor: Editor không HTTP; ưu tiên hardcode nếu có version, không thì _editorFakeContentVersion.
  • Bật server nhưng URL trống → fallback hardcode (cảnh báo log).

Luồng

  1. Resolve version (server hoặc hardcode theo trên).
  2. Nếu có assetsBaseUrl _bakedRemoteUrlPrefixForRewriteInstallPrefixRewrite.
  3. Mở gate → cho phép AddressableManager gọi Addressables.InitializeAsync.
  4. EnsureInitializedAsync, so sánh version với PlayerPrefs:
    • Lần đầu hoặc contentVersion khác bản đã lưu → CheckAndUpdateCatalogsIfNeededAsync + DownloadDependenciesAsync theo _remoteBulkDownloadLabel.
    • Trùng version (và không cần làm content work) → không catalog/bulk download.

PlayerPrefs

  • PW_GameContent_FirstRemoteSyncDone
  • PW_GameContent_LastContentVersion

Event / API

  • Finished (BootstrapResult: Success, DidContentWork, ErrorMessage, ServerContentVersion).
  • RunInternalAsync() — gọi từ code khác nếu cần.

JSON mẫu server (phẳng, đúng JsonUtility)

{
  "contentVersion": "12",
  "assetsBaseUrl": "https://cdn.example.com/Android"
}

2.5. AddressableManager.cs

  • Trước Addressables.InitializeAsync() await GameContentBootstrap.WaitForPreAddressablesSetupIfAnyAsync() để không init Addressables trước khi bootstrap xong HTTP + rewrite (khi gate được tạo).

3. Bảng liên hệ “MD nghiệp vụ ↔ code”

Nghiệp vụ Code
Kiểm tra / cập nhật catalog AddressablesCatalogUpdater + gọi trong GameContentBootstrap
Đổi CDN runtime AddressablesRuntimeUrlRewriter + assetsBaseUrl từ API
First run / mỗi lần mở + so version (server hoặc hardcode) GameContentBootstrap + GameContentVersionServerClient + PlayerPrefs
Init Addressables sau rewrite Gate + AddressablesInitService + AddressableManager chờ gate
Init tập trung (tránh init sớm) AddressablesInitServicekhông gọi Addressables.InitializeAsync() trực tiếp; AUIManager đã chuyển sang service

4. Checklist cấu hình (team)

  • Scene bootstrap có GameContentBootstrap: tắt _useServerForVersionInfo + gán hardcode khi test; production bật server + _versionEndpointUrl.
  • _bakedRemoteUrlPrefixForRewrite trùng prefix URL đã bake trong bản build đang chạy (không chỉ Remote.LoadPath hiện tại trong Editor). Ví dụ build cũ trỏ https://prefect-world-asset....wcsapi.com/ → phải điền đúng prefix đó; _hardcodedAssetsBaseUrl / server assetsBaseUrl = CDN mới (vd https://pw-assets.brewmonster.vn/Android/).
  • Mọi entry remote cần “tải full” lần đầu / khi đổi version đều có cùng label với _remoteBulkDownloadLabel (mặc định RemoteContent).
  • Build Addressables đúng platform → upload đúng ServerData/<Platform> lên host (nếu dùng remote).
  • Có luồng catalog khi hot-update (bootstrap đã gọi khi version đổi / first run).
  • Production dùng HTTPS; mọi chỗ load Addressables dùng AddressablesInitService (hoặc AddressableManager.WaitUntilInitializedAsync), không InitializeAsync().WaitForCompletion() trực tiếp.
  • Hai scene: index 0 = GameContentBootstrap, không gắn GameContentBootstrap trên Bootstrap.unity (chạy menu Setup Two-Scene Bootstrap).
  • _nextSceneName = Bootstrap; BootstrapSceneController._nextSceneName vẫn trỏ LoginScene (hoặc scene in-game) như cũ.

5. Mobile: SSL CA certificate error / catalog URL CDN cũ

Triệu chứng: CheckForCatalogUpdates / tải catalog_*.hash lỗi ConnectionError : SSL CA certificate error, URL vẫn là host cũ (vd prefect-world-asset....wcsapi.com) dù profile Editor đã đổi sang CDN khác.

Nguyên nhân thường gặp:

  1. Init sớm: UI (AUIManager / CECUIManager.Awake) gọi Addressables.InitializeAsync() trước GameContentBootstrap gắn InternalIdTransformFunc → request vẫn tới CDN bake trong catalog build.
  2. Thiếu rewrite: assetsBaseUrl có nhưng _bakedRemoteUrlPrefixForRewrite trống → không đổi host.
  3. Chứng chỉ CDN: Chuỗi SSL/intermediate trên host đích không tin cậy được trên Android (sửa phía CDN / Let's Encrypt full chain).

Đã xử lý trong code: AddressablesInitService chờ gate bootstrap; AUIManager dùng service thay vì init trực tiếp.

Bạn cần kiểm tra trên scene mobile:

Field Gợi ý
_bakedRemoteUrlPrefixForRewrite Prefix y hệt URL trong catalog bản APK (vd https://prefect-world-asset.wcscdn51.v1.wcsapi.com/)
_hardcodedAssetsBaseUrl CDN đích HTTPS hợp lệ (vd https://pw-assets.brewmonster.vn/Android/)

Log mong đợi trước init: [Cuong] GameContentBootstrap: URL rewrite | from=... → to=...

Các file khác vẫn có thể gọi InitializeAsync().WaitForCompletion() (EC_Game, EC_HPWorkNavigate, …) — nên chuyển dần sang AddressablesInitService.


6. Ghi chú Editor

  • Bật _skipRemoteCallInEditor để không gọi mạng; dùng _hardcodedContentVersion / _hardcodedAssetsBaseUrl hoặc _editorFakeContentVersion (khi hardcode version trống).

7. Debug log runtime ([Cuong])

Trong Console Unity, lọc [Cuong] để theo dõi bootstrap / tải remote.

Tiến độ tải (% + MB)

AddressablesCatalogUpdater.DownloadDependenciesAsync log mỗi 5% (mặc định) và lúc bắt đầu / kết thúc 100%, kèm dung lượng khi Addressables báo được TotalBytes:

[Cuong] AddressablesCatalogUpdater: Bắt đầu tải dependencies (key=RemoteContent)...
[Cuong] AddressablesCatalogUpdater: 0% (0.0/512.3 MB) — key=RemoteContent
[Cuong] AddressablesCatalogUpdater: 5% (25.6/512.3 MB) — key=RemoteContent
[Cuong] AddressablesCatalogUpdater: 10% (51.2/512.3 MB) — key=RemoteContent
...
[Cuong] AddressablesCatalogUpdater: 100% (512.3/512.3 MB) — key=RemoteContent
[Cuong] AddressablesCatalogUpdater: Tải xong dependencies (key=RemoteContent).

Trước khi bulk download, GameContentBootstrap gọi GetDownloadSizeBytesAsync và log dung lượng cần tải (ước lượng theo catalog + cache):

[Cuong] GameContentBootstrap: Đang tải remote content (label=RemoteContent), dung lượng cần tải ~512.3 MB...

Nếu đã cache đủ: dung lượng cần tải ~0 MB hoặc log “đã có trong cache”.

Đổi bước log %: gọi DownloadDependenciesAsync(key, progressLogStepPercent: 10) (1100).

Bảng log theo giai đoạn

Giai đoạn Ví dụ log
Bắt đầu bootstrap GameContentBootstrap: Bắt đầu bootstrap Addressables...
Đang lấy version Đang lấy contentVersion / assetsBaseUrl...
Đang init / catalog Đang khởi tạo Addressables..., Đang kiểm tra / cập nhật catalog...
Trước bulk dung lượng cần tải ~N MB (label=...)
Đang tải bulk AddressablesCatalogUpdater: N% (x/y MB) — key=... (mỗi ~5%)
Xong (không cần tải) Hoàn tất — không cần tải catalog/bulk (version không đổi).
Xong (đã tải hết) Bootstrap hoàn tất — đã tải xong hết nội dung
AddressableManager Bootstrap gate xong, InitializeAsync xong — sẵn sàng load asset

Lỗi dùng Debug.LogError cùng tiền tố [Cuong].


Tài liệu mô tả hành vi và cấu hình trong repo; chi tiết API theo đúng phiên bản com.unity.addressables nên đối chiếu Unity Manual: Addressables.