Skip to content

Commit dd2b87a

Browse files
Merge pull request #35 from CoderGamester/develop
Release 2.1.2
2 parents 8137141 + 528b939 commit dd2b87a

79 files changed

Lines changed: 1757 additions & 411 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.
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# Release preflight — required status check for develop -> master PRs.
2+
#
3+
# Installed into each package repo by:
4+
# release.py install-preflight <package>
5+
#
6+
# WHY THIS EXISTS
7+
# These are solo repos: GitHub refuses to let a PR author review their own PR
8+
# (HTTP 422 "Review cannot be requested from pull request author"), so a human
9+
# review gate is unavailable. This gives a real blocking gate instead — the same
10+
# package-local gates the release driver enforces, run before the merge rather
11+
# than after it.
12+
#
13+
# THIS FILE MUST STAY OUT OF THE PUBLISHED TARBALL
14+
# `.github/` IS packed by default — the published googlesheetimporter 0.7.2 asset
15+
# still contains `package/.github/workflows/openai.yml`. Each package therefore
16+
# lists `.github/` in its `.gitignore`, which Unity's packer uses as its
17+
# pack-ignore list; git keeps tracking the file regardless, since .gitignore does
18+
# not untrack existing paths. Measured on a real clone: 434 -> 433 entries,
19+
# `.github` 1 -> 0, `Runtime` unchanged.
20+
#
21+
# Do NOT verify this on a copy with `.git` removed: the packer behaves differently
22+
# without a repo and reports `.github` as excluded when it is not. `G24` also
23+
# surfaces it as an unexpected added file if the ignore line is ever dropped.
24+
#
25+
# WHAT IT DOES NOT CHECK
26+
# Only gates decidable from the package directory plus the base ref, so the check
27+
# needs no token, no submodules and no network: G7 (bare SemVer), G8/G9 (CHANGELOG
28+
# heading matches package.json and is newest+highest), G10 (date sane), G11
29+
# (version advances past master), G15 (the PR touches both files). Remote-state
30+
# gates (G0-G6, G12-G14) and the whole tarball chain (G20-G27) run locally in
31+
# `release.py preflight` / `pack` before the PR is opened.
32+
33+
name: release-preflight
34+
35+
on:
36+
pull_request:
37+
branches: [master]
38+
39+
permissions:
40+
contents: read
41+
42+
concurrency:
43+
group: release-preflight-${{ github.event.pull_request.number }}
44+
cancel-in-progress: true
45+
46+
jobs:
47+
preflight:
48+
runs-on: ubuntu-latest
49+
steps:
50+
# `lfs: true` is load-bearing, not a nicety. The default (false) leaves every
51+
# LFS-tracked file as a ~130-byte pointer stub, which would make G28 fail on
52+
# every PR. With it on, G28 becomes a genuine check that the LFS objects are
53+
# FETCHABLE from the remote — the exact failure that hit uiservice, where the
54+
# working tree held stubs while the published 1.2.1 had real content.
55+
- name: Check out the package
56+
uses: actions/checkout@v4
57+
with:
58+
fetch-depth: 0
59+
lfs: true
60+
persist-credentials: false
61+
62+
# The gate logic is shared rather than vendored into six repos, so there is
63+
# one source of truth. `ref` is explicit: actions/checkout defaults to the
64+
# target repo's DEFAULT branch (master), where the tooling does not exist
65+
# yet — omitting it fails with "No such file or directory". Retarget this to
66+
# master once the skill is merged there.
67+
- name: Check out the release tooling
68+
uses: actions/checkout@v4
69+
with:
70+
repository: CoderGamester/Frameworks
71+
ref: develop
72+
path: .release-tooling
73+
sparse-checkout: .claude/skills/unity-package-release/scripts
74+
persist-credentials: false
75+
76+
- name: Set up Python
77+
uses: actions/setup-python@v5
78+
with:
79+
python-version: "3.12"
80+
81+
- name: Preflight
82+
run: |
83+
python3 .release-tooling/.claude/skills/unity-package-release/scripts/release.py \
84+
preflight-pr --path . --base "origin/${{ github.base_ref }}"

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,6 @@ crashlytics-build.properties
8282

8383
# Tests audit history (unity-tests-audit skill -- local developer state, never committed)
8484
.audit-history.md
85+
86+
# CI config: tracked in git, excluded from the published UPM tarball
87+
.github/

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,17 @@ 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.1.2] - 2026-08-04
8+
9+
**Changed**:
10+
- Added the `com.unity.test-framework.performance` (3.5.0) dependency so the package's test assemblies compile when tests are enabled.
11+
12+
**Fixed**:
13+
- Fixed `GameObjectPool.Dispose(false)` and its generic equivalent so they preserve the sample entity when requested.
14+
- Fixed pooling of Unity objects so destroyed pooled `GameObject` and `Behaviour` instances are skipped instead of being returned to callers.
15+
- Fixed Addressable ID enum generation so addresses that sanitize to the same C# identifier receive distinct members.
16+
- Fixed the Services Playground Input System setup by assigning default actions when its UI module is created, so sample controls respond as expected.
17+
718
## [2.1.1] - 2026-07-04
819

920
**Changed**:

Editor/AddressableIds/AddressableIdsEditorSettings.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,13 @@ public string AddressableLabel
8686
? default
8787
: new DateTime(_lastGenerationUtcTicks, DateTimeKind.Utc);
8888

89+
/// <summary>How many ids the last generation emitted.</summary>
8990
public int LastGenerationIdCount => _lastGenerationIdCount;
91+
/// <summary>How many labels the last generation emitted.</summary>
9092
public int LastGenerationLabelCount => _lastGenerationLabelCount;
93+
/// <summary>Script filename the last generation used; a change from the current setting makes the snapshot stale.</summary>
9194
public string LastGenerationFilenameUsed => _lastGenerationFilenameUsed ?? string.Empty;
95+
/// <summary>Label filter the last generation used; a change from the current setting makes the snapshot stale.</summary>
9296
public string LastGenerationLabelFilterUsed => _lastGenerationLabelFilterUsed ?? string.Empty;
9397

9498
/// <summary>Sorted list of addressable addresses that were emitted in the last generation. Empty array when no snapshot.</summary>

Editor/AddressableIds/AddressableIdsGeneratorUtils.cs

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,24 @@ public static string ResolveScriptPath(AddressableIdsEditorSettings settings)
241241
return scriptPath;
242242
}
243243

244+
/// <summary>
245+
/// Pure string-builder: turns <paramref name="addresses"/> into the generated C# enum member-block
246+
/// source text (opening brace, one member per address, closing brace) that <see cref="GenerateScript"/>
247+
/// inserts after the <c>public enum &lt;Name&gt;</c> header line. Takes no <c>AssetDatabase</c>
248+
/// dependency — operates on plain address strings only, so it is directly unit-testable.
249+
/// </summary>
250+
internal static string BuildEnumSource(IReadOnlyCollection<string> addresses)
251+
{
252+
var addressList = addresses as IReadOnlyList<string> ?? new List<string>(addresses);
253+
var stringBuilder = new StringBuilder();
254+
255+
stringBuilder.AppendLine("\t{");
256+
AppendAddressEnumMembers(stringBuilder, addressList);
257+
stringBuilder.AppendLine("\t}");
258+
259+
return stringBuilder.ToString();
260+
}
261+
244262
private static List<AddressableAssetEntry> GetAssetList()
245263
{
246264
var assetList = new List<AddressableAssetEntry>();
@@ -278,9 +296,7 @@ private static void GenerateScript(List<AddressableAssetEntry> assetList, Addres
278296
stringBuilder.AppendLine("{");
279297

280298
stringBuilder.AppendLine($"\tpublic enum {settings.ScriptFilename}");
281-
stringBuilder.AppendLine("\t{");
282-
GenerateAddressEnums(stringBuilder, assetList);
283-
stringBuilder.AppendLine("\t}");
299+
stringBuilder.Append(BuildEnumSource(ExtractAddresses(assetList)));
284300

285301
stringBuilder.AppendLine("");
286302
stringBuilder.AppendLine("\tpublic enum AddressableLabel");
@@ -485,27 +501,22 @@ private static void ProcessData(IList<AddressableAssetEntry> assetList, Addressa
485501
}
486502
}
487503

488-
private static void GenerateAddressEnums(StringBuilder stringBuilder, IReadOnlyList<AddressableAssetEntry> assetList)
504+
private static void AppendAddressEnumMembers(StringBuilder stringBuilder, IReadOnlyList<string> addresses)
489505
{
490506
var addedNames = new List<string>();
491507

492-
for (var i = 0; i < assetList.Count; i++)
508+
for (var i = 0; i < addresses.Count; i++)
493509
{
494-
var name = ResolveSanitizedEnumName(assetList[i].address, addedNames, out _);
510+
var name = ResolveSanitizedEnumName(addresses[i], addedNames, out _);
495511
addedNames.Add(name);
496512

497513
stringBuilder.Append("\t\t");
498-
stringBuilder.Append(GetCleanName(assetList[i].address, true));
499-
stringBuilder.Append(i + 1 == assetList.Count ? "\n" : ",\n");
514+
stringBuilder.Append(name);
515+
stringBuilder.Append(i + 1 == addresses.Count ? "\n" : ",\n");
500516
}
501517
}
502518

503-
/// <summary>
504-
/// Resolves the enum-member name for a given Addressable <paramref name="address"/>, applying the
505-
/// same <c>name_filetype</c> fallback that <see cref="GenerateAddressEnums"/> uses when the cleaned
506-
/// name collides with one already in <paramref name="seenNames"/>. Sets <paramref name="collided"/>
507-
/// to <c>true</c> when the fallback path was taken.
508-
/// </summary>
519+
// Disambiguates a collision by re-cleaning the address with its file extension appended (name_filetype).
509520
private static string ResolveSanitizedEnumName(string address, List<string> seenNames, out bool collided)
510521
{
511522
var name = GetCleanName(address, true);
@@ -566,10 +577,7 @@ private static List<string> DetectNullAssetTypes(IReadOnlyList<AddressableAssetE
566577
return nulls;
567578
}
568579

569-
/// <summary>
570-
/// Returns the elements of <paramref name="left"/> that are not in <paramref name="right"/>.
571-
/// Both inputs MUST be pre-sorted ordinally; output is also sorted ordinally.
572-
/// </summary>
580+
// Both inputs must already be sorted ordinally; nothing here enforces it.
573581
private static List<string> SortedSetDiff(IReadOnlyList<string> left, IReadOnlyList<string> right)
574582
{
575583
var result = new List<string>();

Editor/AssetsImporter/AssetConfigsImporter.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,11 +115,16 @@ public void Import(string assetsFolderPath = null)
115115
$"To: '{typeof(TScriptableObject).Name}' - From '{scriptableObject.AssetsFolderPath}' ");
116116
}
117117

118+
/// <summary>The filename fragment an id is matched against; override when assets are not named after the id.</summary>
118119
protected virtual string IdPattern(TId id)
119120
{
120121
return id.ToString();
121122
}
122123

124+
/// <summary>
125+
/// Pairs each id with the asset whose path contains it, skipping ids with no match.
126+
/// Override to change how ids are mapped onto the discovered assets.
127+
/// </summary>
123128
protected virtual List<Pair<TId, AssetReference>> OnImportIds(TScriptableObject scriptableObject,
124129
List<string> assetGuids,
125130
List<string> assetsPaths)
@@ -142,6 +147,7 @@ protected virtual List<Pair<TId, AssetReference>> OnImportIds(TScriptableObject
142147
return list;
143148
}
144149

150+
/// <summary>Index of the first path containing <c>/{id}.</c>, or -1; the dot is what stops a prefix matching.</summary>
145151
protected int IndexOfId(string id, IList<string> assetsPath)
146152
{
147153
for (var i = 0; i < assetsPath.Count; i++)
@@ -155,16 +161,20 @@ protected int IndexOfId(string id, IList<string> assetsPath)
155161
return -1;
156162
}
157163

164+
/// <summary>Runs after the import has written the asset; the base implementation does nothing.</summary>
158165
protected virtual void OnImportComplete(TScriptableObject scriptableObject) { }
159166
}
160167

161168
/// <inheritdoc />
162169
public abstract class AssetsConfigsGeneratorImporter<TAsset> : IAssetConfigsGeneratorImporter
163170
{
171+
/// <summary>Assembly-qualified name of the id enum the generated script should use.</summary>
164172
public abstract string TIdName { get; }
165173

174+
/// <summary>Assembly-qualified name of the configs ScriptableObject the generated script should target.</summary>
166175
public abstract string TScriptableObjectName { get; }
167176

177+
/// <summary>When true the previous generated script is kept as a backup before being overwritten.</summary>
168178
public virtual bool CacheScriptAsOld => true;
169179

170180
/// <inheritdoc />

Editor/Explorer/Tabs/AddressableIdsTab.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ public class AddressableIdsTab : ServiceTab
2323
private static readonly Color OkColor = new Color(0.6f, 0.9f, 0.6f);
2424
private static readonly Color MutedColor = new Color(0.7f, 0.7f, 0.7f);
2525

26+
/// <inheritdoc />
2627
public override string DisplayName => "Addressable Ids";
28+
/// <inheritdoc />
2729
protected override int RefreshIntervalMs => 2000;
2830

2931
private TextField _filenameField;
@@ -43,6 +45,7 @@ public class AddressableIdsTab : ServiceTab
4345
private VisualElement _removedList;
4446
private VisualElement _warningsList;
4547

48+
/// <inheritdoc />
4649
protected override void BuildUi()
4750
{
4851
var scroll = new ScrollView(ScrollViewMode.Vertical);
@@ -174,6 +177,7 @@ protected override void BuildUi()
174177
RefreshOutput();
175178
}
176179

180+
/// <inheritdoc />
177181
protected override void Refresh()
178182
{
179183
var settings = AddressableIdsEditorSettings.instance;

Editor/Explorer/Tabs/AssetResolverTab.cs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,15 @@ namespace GameLovers.Services.Editor.Explorer.Tabs
1515
/// </summary>
1616
public class AssetResolverTab : ServiceTab
1717
{
18+
/// <inheritdoc />
1819
public override string DisplayName => "Asset Resolver";
1920

2021
private ScrollView _scroll;
2122
private VisualElement _tree;
2223
private Toggle _destructiveToggle;
2324
private Label _countLabel;
2425

26+
/// <inheritdoc />
2527
protected override void BuildUi()
2628
{
2729
var header = new VisualElement();
@@ -51,6 +53,7 @@ protected override void BuildUi()
5153
Add(bar);
5254
}
5355

56+
/// <inheritdoc />
5457
protected override void Refresh()
5558
{
5659
var resolver = TryResolve<IAssetResolverService>() as AssetResolverService;
@@ -136,13 +139,7 @@ protected override void Refresh()
136139
}
137140
}
138141

139-
/// <summary>
140-
/// Builds a deterministic digest of every piece of state the rebuild path renders:
141-
/// the not-bound branch, the destructive-toggle flag (gates per-row Unload buttons),
142-
/// and per-row <c>(assetType, idType, id, loaded)</c> tuples. When two consecutive
143-
/// refreshes produce the same digest the rebuild can be skipped — keeping rapid
144-
/// foldout clicks from getting destroyed mid-click by the 250 ms timer.
145-
/// </summary>
142+
// Must cover every input the rebuild conditions on, the destructive toggle included; see AGENTS.md §4.
146143
private string ComputeDigest(AssetResolverService resolver)
147144
{
148145
if (resolver == null)

Editor/Explorer/Tabs/AssetsImporterTab.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,16 @@ namespace GameLovers.Services.Editor.Explorer.Tabs
1515
/// </summary>
1616
public class AssetsImporterTab : ServiceTab
1717
{
18+
/// <inheritdoc />
1819
public override string DisplayName => "Assets Importer";
20+
/// <inheritdoc />
1921
protected override int RefreshIntervalMs => 2000;
2022

2123
private Toggle _autoImportToggle;
2224
private VisualElement _importerList;
2325
private List<ImportData> _cachedImporters;
2426

27+
/// <inheritdoc />
2528
protected override void BuildUi()
2629
{
2730
var scroll = new ScrollView(ScrollViewMode.Vertical);
@@ -54,6 +57,7 @@ protected override void BuildUi()
5457
Add(bar);
5558
}
5659

60+
/// <inheritdoc />
5761
protected override void Refresh()
5862
{
5963
_autoImportToggle.SetValueWithoutNotify(AssetsImporterEditorSettings.instance.AutoUpdateOnRefresh);

Editor/Explorer/Tabs/CoroutineTab.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@ namespace GameLovers.Services.Editor.Explorer.Tabs
88
/// </summary>
99
public class CoroutineTab : ServiceTab
1010
{
11+
/// <inheritdoc />
1112
public override string DisplayName => "Coroutine";
1213

1314
private ScrollView _scroll;
1415
private VisualElement _list;
1516
private Label _totalLabel;
1617

18+
/// <inheritdoc />
1719
protected override void BuildUi()
1820
{
1921
var header = new VisualElement();
@@ -34,6 +36,7 @@ protected override void BuildUi()
3436
Add(bar);
3537
}
3638

39+
/// <inheritdoc />
3740
protected override void Refresh()
3841
{
3942
_list.Clear();
@@ -87,6 +90,7 @@ protected override void Refresh()
8790
// Forcibly clear the active-coroutine list synchronously when the user stops
8891
// play mode. Belt-and-braces against bootstraps that fail to dispose the
8992
// coroutine service / call MainInstaller.Clean() in OnDestroy.
93+
/// <inheritdoc />
9094
protected override void OnExitingPlayMode()
9195
{
9296
_totalLabel.text = "Active: 0";

0 commit comments

Comments
 (0)