Skip to content

Commit 18cfd93

Browse files
Merge pull request #30 from CoderGamester/develop
Release 2.0.0
2 parents ec6964f + 62b2655 commit 18cfd93

249 files changed

Lines changed: 29281 additions & 1318 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/openai.yml

Lines changed: 0 additions & 102 deletions
This file was deleted.

AGENTS.md

Lines changed: 279 additions & 86 deletions
Large diffs are not rendered by default.

CHANGELOG.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,54 @@ All notable changes to this package will be documented in this file.
44
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
55
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
66

7+
## [2.0.0] - 2026-04-26
8+
9+
**New**:
10+
- Added **Services Explorer** window (`Tools > GameLovers > Services Explorer`) with 13 live-refresh tabs: Overview, Installer, MessageBroker, Tick, Coroutine, Pool, Data, Time, RNG, AssetResolver, Versioning, Assets Importer, Addressable Ids — works in both Edit and Play mode
11+
- Menu stubs under `Tools > GameLovers`:
12+
- `Versioning / Refresh Version Data` and `Versioning / Open in Explorer`
13+
- `Assets Importer / Import Assets Data` and `Assets Importer / Open in Explorer`
14+
- `Addressable Ids / Generate Addressable Ids` and `Addressable Ids / Open in Explorer`
15+
- Added `Assets > Create > GameLovers Services > …` scaffolders: Message, Command, Service, Pool Entity (template-based, $NAME$ / $NAMESPACE$ substitution)
16+
- Absorbed `com.gamelovers.assetsimporter` v0.5.2 into this package
17+
- Added `IAssetLoader`, `ISceneLoader`, `AddressablesAssetLoader` to `Runtime/AssetsImporter/` (ns `GameLovers.Services.AssetsImporter`)
18+
- Added `AddressableConfig`, `AssetConfigsScriptableObject<TId,TAsset>`, `AssetLoaderUtils`, `AssetReferenceScene` to `Runtime/AssetsImporter/` (ns `GameLovers.Services.AssetsImporter`)
19+
- Added `AssetResolverService` (implements `IAssetResolverService` / `IAssetAdderService`) to `Runtime/` root (ns `GameLovers.Services`)
20+
- Added Editor/AssetsImporter/: `AssetsImporter`, `AssetsToolImporter`, `AssetConfigsImporter`, `AddressableIdsGenerator`, `AddressablesIdGeneratorSettings` (ns `GameLovers.Services.AssetsImporter.Editor`)
21+
- Added importable **Samples** under `Samples~/` (importable via Unity Package Manager > GameLovers Services > Samples).
22+
- **Services Playground** — single-scene, zero-setup walk-through that wires every foundation service via `MainInstaller` and exercises 10 of 13 Services Explorer tabs end-to-end.
23+
- **Asset Resolver** — focused demo of `AssetResolverService` end-to-end (`AddConfigs` / `RequestAsset` / `UnloadAssets`) with `SpriteConfigs : AssetConfigsScriptableObject<SpriteId, Sprite>`. Drives the three Services Explorer tabs the Playground does not cover (Asset Resolver, Assets Importer, Addressable Ids).
24+
25+
**Changed**:
26+
- Addressable Ids generator and Assets Importer settings moved from `Assets/*.asset` ScriptableObjects to `ProjectSettings/` ScriptableSingletons (mirrors `VersioningEditorSettings`).
27+
- Generation logic extracted from `AddressableIdsGenerator` into `AddressableIdsGeneratorUtils` (static `internal`); importer discovery/import logic extracted into `AssetsImporterEditorUtils`.
28+
- `Tools/Assets Importer/*` and `Tools/AddressableIds Generator/*` menu entries removed. Use `Tools/GameLovers/Assets Importer/...`, `Tools/GameLovers/Addressable Ids/...`, or the Services Explorer tabs instead.
29+
- `Toggle Auto Import On Refresh` menu entry removed. The toggle now lives exclusively in the Services Explorer **Assets Importer** tab.
30+
- `Assets/AssetsImporter.asset` and `Assets/AddressablesIdGeneratorSettings.asset` are no longer used (settings moved to `ProjectSettings/`); safe to delete from consumer projects.
31+
- Deleted editor source files: `AssetsImporter.cs`, `AssetsToolImporter.cs`, `AddressableIdsGenerator.cs`, `AddressablesIdGeneratorSettings.cs`.
32+
- Folder reorganization: `Runtime/` now has domain subfolders `DependencyInjection/`, `Commands/`, `Pooling/`, `AssetsImporter/`; `Editor/` now has `Versioning/` and `AssetsImporter/` subfolders
33+
- `Installer.cs` and `MainInstaller.cs` moved to `Runtime/DependencyInjection/` (namespace unchanged: `GameLovers.Services`)
34+
- `CommandService.cs` trimmed to concrete class only; command contract interfaces extracted to `Runtime/Commands/` under ns `GameLovers.Services.Commands`
35+
- `PoolService.cs` trimmed to concrete class only; pool interfaces + implementations moved to `Runtime/Pooling/` under ns `GameLovers.Services.Pooling`
36+
- `ObjectPool.cs` (578 lines, 10 types) split into 4 files under `Runtime/Pooling/`: `IPoolEntity.cs`, `IObjectPool.cs`, `ObjectPool.cs`, `GameObjectPool.cs`
37+
- `VersionEditorUtils.cs` and `GitEditorProcess.cs` moved to `Editor/Versioning/`; re-namespaced from `GameLovers.Services.Editor``GameLovers.Services.Versioning.Editor`
38+
- Added new hard dependencies: `com.unity.addressables` (1.21.20) and `com.cysharp.unitask` (2.5.10)
39+
40+
**Fixed**:
41+
- `AddressablesAssetLoader.UnloadAsset` no longer calls `GC.Collect()`, `GC.WaitForPendingFinalizers()`, or `Resources.UnloadUnusedAssets()`. The method now only decrements the Addressables reference count. The old implementation caused PlayMode Test Runner crashes on macOS and O(total-assets-in-memory) main-thread stalls per per-asset release. Callers that need memory reclamation should invoke `Resources.UnloadUnusedAssets()` themselves at appropriate moments (scene transitions, boot, memory-pressure events); Unity also runs an unused-assets sweep automatically on `LoadSceneMode.Single` scene loads.
42+
- Corrected `IAssetLoader.UnloadAsset` XML documentation: removed the incorrect "will also destroy GameObject instances" claim — `Addressables.Release(gameObject)` does not destroy the instance; callers must `Object.Destroy` it separately.
43+
- `IAsyncCoroutine.StopCoroutine(bool triggerOnComplete)` now honors its `triggerOnComplete` parameter and flips `IsCompleted` to `true` and `IsRunning` to `false` after stopping. The previous implementation always invoked `OnComplete` callbacks regardless of the flag and left state flags unchanged, so consumers could not distinguish a stopped coroutine from a running one and `triggerOnComplete: false` was silently ignored.
44+
- `GameObjectPool.Dispose()` and `GameObjectPool<T>.Dispose()` now skip pooled entries whose underlying `GameObject` has already been destroyed by an external owner (e.g. a parent GameObject was destroyed while pooled instances were still reparented under it via `DespawnToSampleParent`).
45+
46+
**Breaking Changes** — see `MIGRATION.md` for details:
47+
- Pool types moved from `GameLovers.Services` to `GameLovers.Services.Pooling` (`IPoolService`, `IObjectPool`, `IObjectPool<T>`, `ObjectPool<T>`, `ObjectPoolBase<T>`, `GameObjectPool`, `GameObjectPool<T>`, `IPoolEntitySpawn`, `IPoolEntitySpawn<T>`, `IPoolEntityDespawn`, `IPoolEntityObject<T>`). `PoolService` concrete class remains in `GameLovers.Services`.
48+
- Command contract types moved from `GameLovers.Services` to `GameLovers.Services.Commands` (`IGameCommandBase`, `IGameCommand<>`, `IGameServerCommand<>`, `ICommandService<>`). `CommandService<>` concrete class remains in `GameLovers.Services`.
49+
- `GameLovers.AssetsImporter.*` renamed to `GameLovers.Services.AssetsImporter.*`
50+
- `GameLovers.AssetsImporter.AssetResolverService` is now `GameLovers.Services.AssetResolverService`
51+
- `GameLovers.Services.Editor.*` (versioning editor) renamed to `GameLovers.Services.Versioning.Editor.*`
52+
- `IAssetLoader.UnloadAssetAsync<T>(T, Action)``IAssetLoader.UnloadAsset<T>(T, Action)`: method renamed (dropped `Async` suffix) and return type changed from `UniTask` to `void` to reflect its synchronous nature. Replace `await loader.UnloadAssetAsync(x);` with `loader.UnloadAsset(x);`.
53+
- Code generated by `AddressableIdsGenerator` must be re-generated (updated emitted `using` statement)
54+
755
## [1.0.1] - 2026-01-14
856

957
**Changed**:

CLAUDE.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Claude Code Guide — GameLovers Services
2+
3+
This package's contributor/agent guide lives in `AGENTS.md`.
4+
Claude Code will automatically import it below.
5+
6+
@AGENTS.md
7+
8+
## Claude-Specific Notes
9+
10+
- Treat `AGENTS.md` as the source of truth.
11+
- If anything in this file appears to conflict with `AGENTS.md`, prefer `AGENTS.md`.
12+
- For user-facing usage, see `README.md`.

CLAUDE.md.meta

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Editor/AddressableIds.meta

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using UnityEditor;
4+
using UnityEngine;
5+
6+
namespace GameLovers.Services.AddressableIds.Editor
7+
{
8+
/// <summary>
9+
/// Editor-only project-level settings for the Addressable Ids Generator.
10+
/// Persisted to <c>ProjectSettings/AddressableIdsEditorSettings.asset</c> via <see cref="ScriptableSingleton{T}"/>.
11+
/// </summary>
12+
[FilePath("ProjectSettings/AddressableIdsEditorSettings.asset", FilePathAttribute.Location.ProjectFolder)]
13+
internal sealed class AddressableIdsEditorSettings : ScriptableSingleton<AddressableIdsEditorSettings>
14+
{
15+
[SerializeField] private string _scriptFilename = "AddressableId";
16+
[SerializeField] private string _namespace = "Game.Ids";
17+
[SerializeField] private string _addressableLabel = "GenerateIds";
18+
19+
// ---- Last-generation snapshot (persisted) ----
20+
[SerializeField] private long _lastGenerationUtcTicks;
21+
[SerializeField] private int _lastGenerationIdCount;
22+
[SerializeField] private int _lastGenerationLabelCount;
23+
[SerializeField] private string _lastGenerationFilenameUsed;
24+
[SerializeField] private string _lastGenerationLabelFilterUsed;
25+
[SerializeField] private string[] _lastGenerationAddresses = Array.Empty<string>();
26+
[SerializeField] private string[] _lastGenerationLabels = Array.Empty<string>();
27+
28+
/// <summary>Name of the generated C# file (without extension) and the enum/class it contains.</summary>
29+
public string ScriptFilename
30+
{
31+
get => string.IsNullOrWhiteSpace(_scriptFilename) ? "AddressableId" : _scriptFilename;
32+
set
33+
{
34+
var trimmed = (value ?? "AddressableId").Trim();
35+
if (_scriptFilename == trimmed)
36+
{
37+
return;
38+
}
39+
40+
_scriptFilename = trimmed;
41+
Save(true);
42+
}
43+
}
44+
45+
/// <summary>C# namespace for the generated file.</summary>
46+
public string Namespace
47+
{
48+
get => string.IsNullOrWhiteSpace(_namespace) ? "Game.Ids" : _namespace;
49+
set
50+
{
51+
var trimmed = (value ?? "Game.Ids").Trim();
52+
if (_namespace == trimmed)
53+
{
54+
return;
55+
}
56+
57+
_namespace = trimmed;
58+
Save(true);
59+
}
60+
}
61+
62+
/// <summary>Addressables label used to filter which assets get Ids generated. Empty = generate all.</summary>
63+
public string AddressableLabel
64+
{
65+
get => _addressableLabel ?? "";
66+
set
67+
{
68+
var trimmed = (value ?? "").Trim();
69+
if (_addressableLabel == trimmed)
70+
{
71+
return;
72+
}
73+
74+
_addressableLabel = trimmed;
75+
Save(true);
76+
}
77+
}
78+
79+
// ---- Last-generation snapshot accessors ----
80+
81+
/// <summary>True when a generation snapshot has been recorded by <see cref="RecordGeneration"/>.</summary>
82+
public bool HasSnapshot => _lastGenerationUtcTicks != 0L;
83+
84+
/// <summary>UTC timestamp of the last successful generation, or <c>default(DateTime)</c> when none.</summary>
85+
public DateTime LastGenerationUtc => _lastGenerationUtcTicks == 0L
86+
? default
87+
: new DateTime(_lastGenerationUtcTicks, DateTimeKind.Utc);
88+
89+
public int LastGenerationIdCount => _lastGenerationIdCount;
90+
public int LastGenerationLabelCount => _lastGenerationLabelCount;
91+
public string LastGenerationFilenameUsed => _lastGenerationFilenameUsed ?? string.Empty;
92+
public string LastGenerationLabelFilterUsed => _lastGenerationLabelFilterUsed ?? string.Empty;
93+
94+
/// <summary>Sorted list of addressable addresses that were emitted in the last generation. Empty array when no snapshot.</summary>
95+
public IReadOnlyList<string> LastGenerationAddresses => _lastGenerationAddresses ?? Array.Empty<string>();
96+
97+
/// <summary>Sorted list of addressable labels that were emitted in the last generation. Empty array when no snapshot.</summary>
98+
public IReadOnlyList<string> LastGenerationLabels => _lastGenerationLabels ?? Array.Empty<string>();
99+
100+
/// <summary>
101+
/// Records the snapshot of the last successful generation: addresses, labels, and the
102+
/// generator settings (filename, label filter) that were used at that moment. Both lists are
103+
/// stored sorted so subsequent set-diffs can be done in O(n+m) without re-sorting at read time.
104+
/// Persists immediately via <c>Save(true)</c>.
105+
/// </summary>
106+
internal void RecordGeneration(IReadOnlyList<string> addresses, IReadOnlyList<string> labels)
107+
{
108+
_lastGenerationUtcTicks = DateTime.UtcNow.Ticks;
109+
_lastGenerationIdCount = addresses?.Count ?? 0;
110+
_lastGenerationLabelCount = labels?.Count ?? 0;
111+
_lastGenerationFilenameUsed = ScriptFilename;
112+
_lastGenerationLabelFilterUsed = AddressableLabel;
113+
114+
_lastGenerationAddresses = SortedCopy(addresses);
115+
_lastGenerationLabels = SortedCopy(labels);
116+
117+
Save(true);
118+
}
119+
120+
private static string[] SortedCopy(IReadOnlyList<string> source)
121+
{
122+
if (source == null || source.Count == 0)
123+
{
124+
return Array.Empty<string>();
125+
}
126+
127+
var copy = new string[source.Count];
128+
129+
for (var i = 0; i < source.Count; i++)
130+
{
131+
copy[i] = source[i];
132+
}
133+
134+
Array.Sort(copy, StringComparer.Ordinal);
135+
return copy;
136+
}
137+
138+
/// <summary>
139+
/// Validates <paramref name="identifier"/> for use as a C# script filename / enum name.
140+
/// Returns <c>true</c> when valid; populates <paramref name="error"/> on failure.
141+
/// </summary>
142+
public static bool IsValidIdentifier(string identifier, out string error)
143+
{
144+
error = null;
145+
146+
if (string.IsNullOrWhiteSpace(identifier))
147+
{
148+
error = "Identifier cannot be empty.";
149+
return false;
150+
}
151+
152+
var trimmed = identifier.Trim();
153+
154+
if (char.IsDigit(trimmed[0]))
155+
{
156+
error = "Identifier must not start with a digit.";
157+
return false;
158+
}
159+
160+
foreach (var c in trimmed)
161+
{
162+
if (!char.IsLetterOrDigit(c) && c != '_')
163+
{
164+
error = $"Identifier contains invalid character '{c}'. Only letters, digits, and underscores are allowed.";
165+
return false;
166+
}
167+
}
168+
169+
return true;
170+
}
171+
172+
/// <summary>
173+
/// Validates <paramref name="ns"/> as a C# namespace string (dot-separated identifiers).
174+
/// Returns <c>true</c> when valid; populates <paramref name="error"/> on failure.
175+
/// </summary>
176+
public static bool IsValidNamespace(string ns, out string error)
177+
{
178+
error = null;
179+
180+
if (string.IsNullOrWhiteSpace(ns))
181+
{
182+
error = "Namespace cannot be empty.";
183+
return false;
184+
}
185+
186+
var segments = ns.Trim().Split('.');
187+
188+
foreach (var segment in segments)
189+
{
190+
if (string.IsNullOrEmpty(segment))
191+
{
192+
error = "Namespace must not contain consecutive dots or trailing dots.";
193+
return false;
194+
}
195+
196+
if (!IsValidIdentifier(segment, out var segmentError))
197+
{
198+
error = $"Namespace segment \"{segment}\" is invalid: {segmentError}";
199+
return false;
200+
}
201+
}
202+
203+
return true;
204+
}
205+
}
206+
}

Editor/AddressableIds/AddressableIdsEditorSettings.cs.meta

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)