You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: AGENTS.md
+4-4Lines changed: 4 additions & 4 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -128,7 +128,7 @@ flowchart TD
128
128
129
129
### Build/Version Info
130
130
`Runtime/VersionServices.cs`
131
-
- Static class for `version-data` Resources metadata. `VersionExternal` is always safe;`VersionInternal`, `Branch`, `Commit`, and `BuildNumber`require either `LoadVersionData()` (sync) or `LoadVersionDataAsync()`(async) to have completed successfully first.
131
+
- Static class for `version-data` Resources metadata. `VersionExternal` is always safe.`VersionInternal`, `Branch`, `Commit`, and `BuildNumber`auto-load via a `[RuntimeInitializeOnLoadMethod(SubsystemRegistration)]``Bootstrap` hook and additionally lazy-load on first property access (via the private `EnsureLoaded()`) when the bootstrap hook has not yet fired. Consumers do NOT need to call `LoadVersionData()`/ `LoadVersionDataAsync()` explicitly for the default flow. Both load methods remain public for explicit pre-warming.
132
132
133
133
## 3. Key Directories / Files
134
134
@@ -243,8 +243,8 @@ The concrete `PoolService` stays in `Runtime/` root under `GameLovers.Services`
243
243
-`VersionEditorUtils` writes `version-data.txt` on every domain reload (`[InitializeOnLoadMethod]`) and can be invoked by build pipelines. It uses git CLI; failures are handled gracefully.
244
244
- The **write folder** is configurable per-project via `VersioningEditorSettings.instance.ResourcesFolderPath` (default `Assets/Configs/Resources`). Change it from the Versioning tab in the Services Explorer (browse + reset). The chosen folder must contain a `Resources` path segment so `Resources.Load<TextAsset>("version-data")` can locate the file at runtime.
245
245
-`VersioningEditorSettings` persists to `ProjectSettings/VersioningEditorSettings.asset` (editor-only, not committed to version control by default).
246
-
-`VersionExternal` is always safe (reads `Application.version` directly). `VersionInternal`, `Branch`, `Commit`, and `BuildNumber`throw `Exception("Version Data not loaded.")` if data has not been loaded — call `LoadVersionData()`(sync) or `LoadVersionDataAsync()` (async) early in boot.
247
-
-**Sync vs async load**: `LoadVersionData()` uses `Resources.Load<TextAsset>` (synchronous, main-thread); `LoadVersionDataAsync()` uses `Resources.LoadAsync<TextAsset>` wrapped in a `TaskCompletionSource`. Both funnel into the private `ApplyTextAsset(TextAsset, bool asyncContext)` helper that parses JSON, flips `_loaded`, and calls `Resources.UnloadAsset`. Behaviour is identical apart from the wording in the failure log line (`"Could not async load …"` vs `"Could not load …"`). Sync is the recommended default for the shipping `version-data.txt` (a few hundred bytes); async is only worth the ceremony if `VersionData` is extended with large embedded blobs (e.g. a baked manifest) that would noticeably stall the main thread.
246
+
-`VersionExternal` is always safe (reads `Application.version` directly). `VersionInternal`, `Branch`, `Commit`, and `BuildNumber`auto-bootstrap at `[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]` (private `Bootstrap` method calling `LoadVersionData()`), and additionally lazy-load on first property access via the private `EnsureLoaded()`— covering the case where a sibling-assembly `SubsystemRegistration` callback fires before this package's. When the `version-data` Resource is missing or fails to parse the accessors return their documented fallbacks (`VersionInternal` → `Application.version`, others → `string.Empty`) and a `Debug.LogError` is emitted; no exception is raised.
247
+
-**Sync vs async load**: `LoadVersionData()` uses `Resources.Load<TextAsset>` (synchronous, main-thread); `LoadVersionDataAsync()` uses `Resources.LoadAsync<TextAsset>` wrapped in a `TaskCompletionSource`. Both funnel into the private `ApplyTextAsset(TextAsset, bool asyncContext)` helper that parses JSON, flips `_loaded`, and calls `Resources.UnloadAsset`. Behaviour is identical apart from the wording in the failure log line (`"Could not async load …"` vs `"Could not load …"`). The sync variant is what `Bootstrap` calls and what `EnsureLoaded()` falls back to; both methods remain public for callers that want explicit pre-warming. The async variant is only worth the ceremony if `VersionData` is extended with large embedded blobs (e.g. a baked manifest) that would noticeably stall the main thread.
248
248
-`VersionServices.IsOutdatedVersion(string)` requires a 3-part `Major.Minor.Patch` semver and throws `IndexOutOfRangeException` on any 1- or 2-part input — the parser unconditionally accesses `Split('.')[0..2]`. Consumers that compare against `Application.version` must ensure `ProjectSettings.bundleVersion` is 3-part (defaults to `0.1` / `1.0` for fresh projects, which will throw). Either bump `bundleVersion` to a 3-part value, guard with `parts.Length < 3` at the call site, or harden the method itself with a length guard. Tests calling this against the host editor's `Application.version` should `Assert.Inconclusive` on shorter strings rather than `Assert.Throws` — the throw is a real production bug surface, not a documented contract.
249
249
250
250
### Editor Introspection (InternalsVisibleTo)
@@ -277,7 +277,7 @@ Services expose minimal `internal` read-only accessors so the Services Explorer
277
277
-`MessageBrokerService.Subscribe` rejects static methods; direct `Publish<T>` can throw if the subscription list is mutated during dispatch.
278
278
-`DataService.GetData<T>` throws when missing; `LoadData<T>` requires a parameterless constructor.
279
279
- Duplicate `PoolService.AddPool<T>` calls throw; `AssetResolverService` requests throw `MissingMemberException` until assets/scenes are registered.
280
-
-`VersionInternal`, `Branch`, `Commit`, and `BuildNumber` throw `Exception("Version Data not loaded.")` until version data loads successfully.
280
+
-`VersionInternal`, `Branch`, `Commit`, and `BuildNumber`no longer throw — auto-bootstrap + lazy-load make access safe at any phase; missing-Resource cases fall back to `Application.version` / `string.Empty` with a `Debug.LogError`.
281
281
282
282
## 5. Coding Standards (Unity 6 / C# 9.0)
283
283
-**C#**: C# 9.0 syntax; explicit namespaces; no global usings.
Copy file name to clipboardExpand all lines: CHANGELOG.md
+12Lines changed: 12 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -4,6 +4,18 @@ All notable changes to this package will be documented in this file.
4
4
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
5
5
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
6
6
7
+
## [2.1.0] - 2026-05-20
8
+
9
+
**New**:
10
+
-`VersionServices` now auto-bootstraps via `[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]`, populating version metadata before any scene `Awake` callback and before vendor-SDK `SubsystemRegistration` callbacks that read it. Consumers no longer need to call `LoadVersionData()` / `LoadVersionDataAsync()` explicitly for the default flow.
11
+
12
+
**Changed**:
13
+
- Property getters (`VersionInternal`, `Branch`, `Commit`, `BuildNumber`) now lazy-load via a new private `EnsureLoaded()` on first access if the auto-bootstrap hook has not yet fired — protects against undefined ordering between sibling assemblies' `[RuntimeInitializeOnLoadMethod]` callbacks at the same phase.
14
+
- Removed the private `IsLoaded()` helper (replaced by `EnsureLoaded()` invoked from each property getter).
15
+
16
+
**Docs**:
17
+
-`docs/version-services.md` rewritten around the new auto-bootstrap contract: the recommended usage no longer includes any explicit load call, the lazy-load fallback is documented, and the Error Reference table now describes the fallback behaviour (no exception is raised).
Copy file name to clipboardExpand all lines: Samples~/README.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -112,7 +112,7 @@ These are the mistakes most likely to bite first-time users — every sample bel
112
112
-**`DataService` keys `PlayerPrefs` by `typeof(T).Name`.** Two types named `PlayerData` in different namespaces will collide.
113
113
-**`PoolService.AddPool<T>` throws on duplicate registrations.** One pool per type. Call `RemovePool<T>()` first if you need to re-register.
114
114
-**`AssetResolverService.RequestAsset` / `LoadSceneAsync` throw `MissingMemberException` until you call `AddConfigs` / `AddAssets`.** Sample 2 demonstrates the registration step.
115
-
-**`VersionServices.VersionInternal` / `Branch` / `Commit` / `BuildNumber`throw until `LoadVersionDataAsync()` completes.**`VersionExternal` is always safe (reads `Application.version`).
115
+
-**`VersionServices` auto-bootstraps at `SubsystemRegistration` + lazy-loads on first property access.**`VersionInternal` / `Branch` / `Commit` / `BuildNumber`are safe to read at any time; missing `version-data.txt` returns `Application.version` / `string.Empty` and logs an error. `LoadVersionData()` / `LoadVersionDataAsync()` remain available for explicit pre-warming.
116
116
-**`TickService` and `CoroutineService` each create a `DontDestroyOnLoad` GameObject.** Always `Dispose()` them on teardown, otherwise the host objects accumulate across domain reloads. The playground bootstrap uses `MainInstaller.CleanDispose<T>()` for this.
117
117
-**`IAsyncCoroutine.StopCoroutine(triggerOnComplete)`** — the parameter is currently not respected. The completion callback fires regardless. Do not rely on cancellation-without-callback semantics.
0 commit comments