Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 52 additions & 41 deletions .yamato/api-updater-test.yml
Original file line number Diff line number Diff line change
@@ -1,41 +1,52 @@
{% metadata_file .yamato/project.metafile %}
---

# DESCRIPTION--------------------------------------------------------------------------
# This job validates the NGO 2.x -> 3.x upgrade path for editor scripts.
# NGO 3.0 renamed the editor assembly and its namespaces (Unity.Netcode.Editor ->
# Unity.Netcode.GameObjects.Editor), and every relocated public type carries a [MovedFrom] so that
# Unity's API updater rewrites a 2.x project's editor scripts automatically on upgrade.
# apiupdaterproject holds editor code written against the 2.x API; the job imports it with
# -accept-apiupdate and asserts that every 2.x type reference was rewritten and none survived.
# See apiupdaterproject/README.md.


# TECHNICAL CONSIDERATIONS---------------------------------------------------------------
# apiupdaterproject/Packages/manifest.json references the package by relative path
# (file:../../com.unity.netcode.gameobjects), so the job tests the package as it sits in the repo
# and needs no package-pack dependency.
# The script restores the 2.x sources when it finishes, so the checkout is left unmodified and the
# job is safe to re-run on the same agent.
# --clean purges Library first: the assertion is meaningless against a warm Library that already
# holds rewritten sources from a previous run.

{% for platform in test_platforms.default -%}
{% for editor in validation_editors.default -%}
api_updater_test_{{ platform.name }}_{{ editor }}:
name : API Updater Test - NGO 2.x editor scripts upgrade [{{ platform.name }}, {{ editor }}]
agent:
type: {{ platform.type }}
image: {{ platform.image }}
flavor: {{ platform.flavor }}
commands:
- unity-downloader-cli --fast --wait -u {{ editor }} -c Editor # Installing basic editor for the import
- python apiupdaterproject/run_upgrade_test.py --unity .Editor --clean
artifacts:
logs:
paths:
- "apiupdaterproject/upgrade-test.log"
dependencies:
- .yamato/_run-all.yml#run_quick_checks # initial checks to perform fast validation of common errors
{% endfor -%}
{% endfor -%}
{% metadata_file .yamato/project.metafile %}
---

# DESCRIPTION--------------------------------------------------------------------------
# This job validates the NGO 2.x -> 3.x upgrade path for user scripts.
# NGO 3.0 renamed the editor assembly and its namespaces (Unity.Netcode.Editor ->
# Unity.Netcode.GameObjects.Editor) and moved the runtime timing types (Unity.Netcode.NetworkTime
# and friends -> Unity.Netcode.GameObjects.Timing), and every relocated public type carries a
# [MovedFrom] so that Unity's API updater rewrites a 2.x project's scripts automatically on upgrade.
# apiupdaterproject holds code written against the 2.x API; the job imports it with
# -accept-apiupdate and asserts that every 2.x type reference was rewritten and none survived.
# See apiupdaterproject/README.md.
#
# The second run adds an assembly that occupies Unity.Netcode.NetworkTimeSystem, which is what
# Netcode for Entities does as of 6.7.0 - it sub-namespaced NetworkTime out of the shared root but
# left NetworkTimeSystem in it - and inverts the expectation for that one name: the updater is
# driven by resolution failure, so a name that still resolves cannot be migrated. NetworkTime and
# NetworkTickSystem are absent from the stub and must still migrate, so a pass proves both halves.
# This is the regression test for why the timing types were moved at all.


# TECHNICAL CONSIDERATIONS---------------------------------------------------------------
# apiupdaterproject/Packages/manifest.json references the package by relative path
# (file:../../com.unity.netcode.gameobjects), so the job tests the package as it sits in the repo
# and needs no package-pack dependency.
# The script restores the 2.x sources when it finishes, so the checkout is left unmodified and the
# job is safe to re-run on the same agent.
# --clean purges Library first: the assertion is meaningless against a warm Library that already
# holds rewritten sources from a previous run. That is also why the two runs are sequential
# commands rather than one - each needs its own cold import, and the script removes the stub and
# restores the sources on every exit path, so the second run starts from the same state as the first.

{% for platform in test_platforms.default -%}
{% for editor in validation_editors.default -%}
api_updater_test_{{ platform.name }}_{{ editor }}:
name : API Updater Test - NGO 2.x editor scripts upgrade [{{ platform.name }}, {{ editor }}]
agent:
type: {{ platform.type }}
image: {{ platform.image }}
flavor: {{ platform.flavor }}
commands:
- unity-downloader-cli --fast --wait -u {{ editor }} -c Editor # Installing basic editor for the import
- python apiupdaterproject/run_upgrade_test.py --unity .Editor --clean
- python apiupdaterproject/run_upgrade_test.py --unity .Editor --clean --collision-stub
artifacts:
logs:
paths:
- "apiupdaterproject/upgrade-test.log"
dependencies:
- .yamato/_run-all.yml#run_quick_checks # initial checks to perform fast validation of common errors
{% endfor -%}
{% endfor -%}
Expand Down
49 changes: 41 additions & 8 deletions apiupdaterproject/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,17 @@ what it is and how to run it; this file covers why it is built this way and what

* This is a standalone Unity project at the repo root. It is not part of `testproject` or
`minimalproject`, and the package does not reference it.
* It validates one thing end to end: that a project written against the **NGO 2.x** editor API is
migrated automatically by Unity's API updater when the package is upgraded to **3.x**.
* It validates one thing end to end: that a project written against the **NGO 2.x** API is migrated
automatically by Unity's API updater when the package is upgraded to **3.x**. Two relocations are
covered — the editor namespaces, and the runtime timing types.
* **The mechanism it tests does not live here.** The `[MovedFrom]` attributes are on the real types in
`com.unity.netcode.gameobjects/Editor/**`. This project only consumes them.
* **Do not "fix" the sources under `Assets/Editor`.** They are deliberately written against the 2.x
API and are the input to the test. A helpful cleanup there silently guts it.
* The expected-type list in `run_upgrade_test.py` is frozen: it enumerates the public editor API of
`develop-2.0.0`, which is released and cannot change. It only needs extending if a public editor
type is relocated again within 3.x.
`com.unity.netcode.gameobjects/Editor/**` and `com.unity.netcode.gameobjects/Runtime/Timing/**`.
This project only consumes them.
* **Do not "fix" the sources under `Assets/Editor` or `Assets/Runtime`.** They are deliberately
written against the 2.x API and are the input to the test. A helpful cleanup there silently guts it.
* The two editor blocks of `EXPECTED_MOVES` in `run_upgrade_test.py` are frozen: they enumerate the
public editor API of `develop-2.0.0`, which is released and cannot change. A block only needs
extending if a public type is relocated again within 3.x — as the timing types were.
* CI runs it on demand only — comment `/ci apiupdater` on a PR. See `.yamato/api-updater-test.yml`.
* **Opening this project locally mutates it.** Unity rewrites `ProjectVersion.txt` to whatever editor
opened it, and the package manager can add builtin modules to `Packages/manifest.json` that only
Expand Down Expand Up @@ -63,6 +65,37 @@ alias, type alias, base type, `typeof`, and generic type argument. The dead
`using Unity.Netcode.Editor;` directives are removed and namespace aliases are rewritten in place
rather than expanded at each use.

## The timing move is a namespace-only relocation, and that is a different case

Every row measured above was a namespace **and** assembly move. The timing types keep their assembly
(`Unity.Netcode.Runtime`), so they carry `[MovedFrom(true, "Unity.Netcode", null, null)]` — a null
`sourceAssembly`, which `MovedFromAttributeData.Set` records as `assemblyHasChanged = false`.

Two things about that are worth knowing before trusting it:

* **The null form is the documented one.** The attribute's own comment states that any null string is
read as "has not changed" and its value is taken from the decorated type, and there is a
single-argument `MovedFromAttribute(string sourceNamespace)` constructor that does exactly
`Set(true, ns, null, null)`. Passing the real assembly name instead would set `assemblyHasChanged`
for a change that did not happen.
* **It has not been measured here.** The table above has no namespace-only row. `Assets/Runtime` plus
the timing block in `EXPECTED_MOVES` is what settles it; a `/ci apiupdater` run is the proof.

Do not reach for `AffectsAPIUpdater` to reason about this. It reads
`!classHasChanged && !assemblyHasChanged`, which would make it false for the editor move — and the
editor move demonstrably works, so whatever that property gates, it is not script rewriting.

### Open form: a namespace alias whose target survives

`Assets/Runtime/DeprecatedTimingUsage.cs` contains `using TimeNs = Unity.Netcode;` used as
`TimeNs.NetworkTickSystem`. This is **not** the same case as the editor project's
`using Cfg = Unity.Netcode.Editor.Configuration;`: there the alias target itself stopped resolving and
was rewritten in place, whereas `Unity.Netcode` still exists and still holds `NetworkBehaviour` and
the rest. So the alias target cannot be rewritten and the use site has to be. The assertions do not
depend on this form — `NetworkTickSystem` is also referenced by simple name and fully qualified — so
if the updater leaves that one line alone the run still passes. Read the rewritten source rather than
assuming it was handled.

## Known gap: assembly definition references

The updater rewrites C# source only; it does not touch `.asmdef` files.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "N4E.CollisionStub",
"rootNamespace": "Unity.Netcode",
"references": [],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": true
}
17 changes: 17 additions & 0 deletions apiupdaterproject/Assets/CollisionStub~/N4ECollisionStub.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Stands in for a second package occupying Unity.Netcode.NetworkTimeSystem, which is what Netcode
// for Entities does as of 6.7.0: its casing correction moved 204 files into Unity.Netcode, and it
// sub-namespaced NetworkTime into Unity.Netcode.NetcodeTime but left NetworkTimeSystem behind.
//
// Only that one name collides. NetworkTime and NetworkTickSystem deliberately are not declared here,
// so a --collision-stub run asserts both halves of the finding in one pass: those two migrate, and
// the one whose old name still resolves cannot.
//
// Inert until run_upgrade_test.py --collision-stub copies this folder into place. Unity does not
// import a directory whose name ends in '~'.
namespace Unity.Netcode
{
public class NetworkTimeSystem
{
public uint EffectiveInputLatencyTicks;
}
}
8 changes: 8 additions & 0 deletions apiupdaterproject/Assets/Runtime.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

45 changes: 45 additions & 0 deletions apiupdaterproject/Assets/Runtime/DeprecatedTimingUsage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Update only if the relocated runtime timing API changes. Deliberately written against the
// pre-move namespace: this file is the test's input, not code to clean up. See ../../README.md.
//
// Each of the three types is named at least once in fully qualified form, so a --collision-stub run
// can tell "was not rewritten" apart from "was never referenced".
#pragma warning disable 169 // Ignore field is never used warnings

using System;
using System.Collections.Generic;
using Unity.Netcode;
using TimeNs = Unity.Netcode;
using TimeValue = Unity.Netcode.NetworkTime;

namespace ApiUpdaterProject
{
// Unity.Netcode -> Unity.Netcode.GameObjects.Timing
internal class DeprecatedTimingUsage
{
// using directive plus simple name
private NetworkTime m_SimpleName;
private NetworkTimeSystem m_TimeSystem;
private NetworkTickSystem m_TickSystem;

// Fully qualified
private Unity.Netcode.NetworkTime m_TimeFullyQualified;
private Unity.Netcode.NetworkTimeSystem m_TimeSystemFullyQualified;
private Unity.Netcode.NetworkTickSystem m_TickSystemFullyQualified;

// Through a namespace alias, and through a type alias
private TimeNs.NetworkTickSystem m_ThroughNamespaceAlias;
Comment thread
NoelStephensUnity marked this conversation as resolved.
private TimeValue m_ThroughTypeAlias;

// As a generic type argument
private List<NetworkTime> m_AsGenericArgument;

// typeof
private Type TimeSystemType => typeof(NetworkTimeSystem);

// Constructor call, and as a return type
private NetworkTime Construct(uint tickRate) => new NetworkTime(tickRate, 0d);

// As a parameter type
private static double TickOf(NetworkTime time) => time.TickWithPartial;
}
}
11 changes: 11 additions & 0 deletions apiupdaterproject/Assets/Runtime/DeprecatedTimingUsage.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading