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
5 changes: 3 additions & 2 deletions com.unity.netcode.gameobjects/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@ Additional documentation and release notes are available at [Multiplayer Documen

### Fixed

- Ensured a useful and human readable error is logged when a NetworkVariable is trying to serialize a private or protected type. (#4239)
- Ensured all callbacks are wrapped with exception handling to avoid silent errors. (#4161)

### Security

### Obsolete


## [3.2.0] - 2026-11-03

### Added
Expand All @@ -42,7 +44,6 @@ Additional documentation and release notes are available at [Multiplayer Documen

### Fixed

- Ensured all callbacks are wrapped with exception handling to avoid silent errors. (#4161)
- Issue where `NetworkTime` reported a `TickOffset` of almost a whole tick, instead of zero, for some times that land on a tick (for example 0.3 at a tick rate of 10), which put `TickWithPartial` one tick ahead or, in long sessions, `Tick` one tick behind. Times between minus one tick and zero now report tick -1 with a positive `TickOffset`, as other negative times do. (#4228)
- Issue where bits written with `BitWriter` were lost when a later `BitWriter.TryBeginWriteBits` call in the same bitwise context grew the `FastBufferWriter`. The bytes written earlier in that context were replaced with zeros or uninitialized memory. (#4226)
- Issue where a growable `FastBufferWriter` created with a size of 0 never returned from its first write or `TryBeginWrite` call that needed the buffer to grow. (#4224)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,14 @@ In version 2.x, the `Lerp` and `SmoothDampening` interpolation types smooth by d

Version 3.x introduces the foundations of the unified GameObject layer for Netcode for Entities, known as the hybrid prefab concept. Version 3.x doesn't enable this integration by default for standard GameObject workflows. Standard `NetworkBehaviour`, `NetworkVariable`, and RPC workflows work the same as in version 2.x.

## CoreCLR compilation changes

CoreCLR introduces a new compilation pipeline that changes some custom serialization generation.

## NetworkVariable serialization

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
## NetworkVariable serialization
### NetworkVariable serialization

Just to make it clear that this is related to the CoreCLR stuff above.


The Mono compilation pipeline was capable of generating serialization for private and protected types. With CoreCLR, this is no longer possible and will fail the compilation. Ensure any types used inside NetworkVariables in your project are declared as `public` or `internal`.

## Additional resources

- [Install Netcode for GameObjects](install.md)
Expand Down
30 changes: 30 additions & 0 deletions com.unity.netcode.gameobjects/Editor/CodeGen/CodeGenHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,36 @@ public static bool HasInterface(this TypeReference typeReference, string interfa
}
}

/// <summary>
/// Collects every type that assembly scoped code cannot access when it uses <paramref name="type"/>.
/// This is each private or protected nested type in its nesting chain, or in the chain of its generic arguments.
/// </summary>
/// <param name="type">The type the generated code uses.</param>
/// <param name="hidden">Receives each inaccessible type, without duplicates.</param>
public static void CollectTypesHiddenFromAssembly(this TypeReference type, List<TypeDefinition> hidden)
{
if (type is GenericInstanceType genericInstance)
{
foreach (var argument in genericInstance.GenericArguments)
{
argument.CollectTypesHiddenFromAssembly(hidden);
}
}
else if (type is TypeSpecification specification)
{
specification.ElementType.CollectTypesHiddenFromAssembly(hidden);
return;
}

for (var definition = type.Resolve(); definition != null && definition.IsNested; definition = definition.DeclaringType)
{
if ((definition.IsNestedPrivate || definition.IsNestedFamily || definition.IsNestedFamilyAndAssembly) && !hidden.Contains(definition))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1

InitializeSerialization still fails on CoreCLR when NetworkVariable<Payload> uses a referenced base's protected internal nested enum without friend access. A derived NetworkBehaviour can legally declare that field using protected access. GetAllFieldsAndResolveGenerics collects Payload, but this predicate does not check IsNestedFamilyOrAssembly. The generated NetworkVariableSerializationHelper cannot access Payload because the helper is outside the base's assembly and inheritance hierarchy. Pass the consuming assembly into CollectTypesHiddenFromAssembly. Reject IsNestedFamilyOrAssembly types when that assembly lacks internal or friend access.

🤖 Helpful? 👍/👎

{
hidden.Add(definition);
}
}
}

/// <summary>
/// Checks a generic method instantiation against the generic constraints of the method it instantiates.
/// Mono does not enforce these on the calls codegen emits, but CoreCLR does, and it rejects the entire
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,26 @@ private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly,
continue;
}

// CoreCLR enforces member access on the generic calls emitted below; Mono does not.
var hiddenTypes = new List<TypeDefinition>();
type.CollectTypesHiddenFromAssembly(hiddenTypes);
if (hiddenTypes.Count > 0)
{
#if ENABLE_CORECLR

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2

The warning/error decision can use the wrong backend when the editor and player runtimes differ. This #if uses the Editor-only ILPP assembly's compile-time defines, while assemblyDefines describes the assembly receiving the generated calls. The CI matrix includes Mono 6000.7 editors building CoreCLR players. If their ENABLE_CORECLR values differ, code generation only warns and retains calls that CoreCLR rejects. Select the branch using assemblyDefines.Contains("ENABLE_CORECLR").

🤖 Helpful? 👍/👎

foreach (var hiddenType in hiddenTypes)
{
m_Diagnostics.AddError($"{type} cannot be used in a network variable - Cannot access the {hiddenType.Name} type to generate serialization as it is {(hiddenType.IsNestedPrivate ? "private" : "protected")}. To fix this, change the access modifier to internal or public.");
}
continue;
#else
// Log a warning in Mono so we can warn users who haven't yet switched their projects over.
foreach (var hiddenType in hiddenTypes)
{
m_Diagnostics.AddWarning($"NetworkVariable serialization for {type.Name} will fail in CoreCLR! With CoreCLR generated code cannot access the {hiddenType.Name} type to generate serialization as it is {(hiddenType.IsNestedPrivate ? "private" : "protected")}. To fix this, change the access modifier to internal or public.");
}
#endif
}

if (serializeMethod != null)
{
instructions.Add(processor.Create(OpCodes.Call, m_MainModule.ImportReference(serializeMethod)));
Expand Down
Loading