Skip to content

Commit b63a410

Browse files
author
Unity Technologies
committed
## [1.2.0-pre.4] - 2023-11-28 ### Added * You can now disable the automatic Entities `ICustomBootstrap` bootstrapping (which calls NetCode's own `ClientServerBootstrap`) by either; a) disabling it in the ProjectSettings (default value is enabled), or b) adding the new `OverrideAutomaticNetcodeBootstrap` MonoBehaviour to your first build scene (i.e. your Active scene). Thus, there is no longer any need to write a custom bootstrap just to support a Frontend scene vs a Gameplay scene. * A `NetCodeConfig` ScriptableObject, containing most NetCode configuration variables, allowing customization without needing to modify code. Most variables are live-tweakable. * A 'Snapshot Sequence Id' (SSId), which is used to accurately measure packet loss. It adds 1 byte of header to each snapshot, but enables us to measure Netcode-caused causes of PL (i.e. out of order snapshots being discarded, and discarding a snapshot if another arrives on the same frame). Access statistics via a new struct on the client's `NetworkSnapshotAck`. * `RpcCollection.GetRpcHeaderLength` and `NetworkStreamDriver.GetMaximumHeaderSize` to allow users to determine max safe payload sizes. ### Fixed * Esoteric exception in `MultiplayerPlaymodeWindow` in server-only cases. * Interpolated ghosts now support `IInputComponentData` and `AutoCommandTarget`. * Improved `UpdateGhostOwnerIsLocal` to make it reactive to `GhostOwner` changes, thus it no longer needs to poll. * NetDbg `ArgumentException` when a predicted ghost contains a replicated enableable flag component. * Display-only issue where the variants for additional entities (created via baking) were calculated as if they were 'root' entities. They are - in fact - child entities, thus the variants automatically selected for them should default to child defaults. * QoL issue; we now allow users to opt-out of auto-baking `GhostAuthoringInspectionComponent`s when selecting their GameObject, reducing stalls when clicking around the Hierarchy or Project. * QoL issue where `GhostAuthoringInspectionComponent` was not always modifiable in areas of the Editor where it is valid to modify them. * Issue where `GhostAuthoringComponent` was disallowed in nested prefab setups (where the root prefab is NOT a ghost). * Log verbiage when creating a driver in DefaultDriverConstructor read like a 'call to action'. It's not.
1 parent 35c9c7d commit b63a410

Some content is hidden

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

48 files changed

+1412
-406
lines changed

CHANGELOG.md

+22
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,28 @@ uid: changelog
44

55
# Changelog
66

7+
## [1.2.0-pre.4] - 2023-11-28
8+
9+
### Added
10+
11+
* You can now disable the automatic Entities `ICustomBootstrap` bootstrapping (which calls NetCode's own `ClientServerBootstrap`) by either; a) disabling it in the ProjectSettings (default value is enabled), or b) adding the new `OverrideAutomaticNetcodeBootstrap` MonoBehaviour to your first build scene (i.e. your Active scene). Thus, there is no longer any need to write a custom bootstrap just to support a Frontend scene vs a Gameplay scene.
12+
* A `NetCodeConfig` ScriptableObject, containing most NetCode configuration variables, allowing customization without needing to modify code. Most variables are live-tweakable.
13+
* A 'Snapshot Sequence Id' (SSId), which is used to accurately measure packet loss. It adds 1 byte of header to each snapshot, but enables us to measure Netcode-caused causes of PL (i.e. out of order snapshots being discarded, and discarding a snapshot if another arrives on the same frame). Access statistics via a new struct on the client's `NetworkSnapshotAck`.
14+
* `RpcCollection.GetRpcHeaderLength` and `NetworkStreamDriver.GetMaximumHeaderSize` to allow users to determine max safe payload sizes.
15+
16+
### Fixed
17+
18+
* Esoteric exception in `MultiplayerPlaymodeWindow` in server-only cases.
19+
* Interpolated ghosts now support `IInputComponentData` and `AutoCommandTarget`.
20+
* Improved `UpdateGhostOwnerIsLocal` to make it reactive to `GhostOwner` changes, thus it no longer needs to poll.
21+
* NetDbg `ArgumentException` when a predicted ghost contains a replicated enableable flag component.
22+
* Display-only issue where the variants for additional entities (created via baking) were calculated as if they were 'root' entities. They are - in fact - child entities, thus the variants automatically selected for them should default to child defaults.
23+
* QoL issue; we now allow users to opt-out of auto-baking `GhostAuthoringInspectionComponent`s when selecting their GameObject, reducing stalls when clicking around the Hierarchy or Project.
24+
* QoL issue where `GhostAuthoringInspectionComponent` was not always modifiable in areas of the Editor where it is valid to modify them.
25+
* Issue where `GhostAuthoringComponent` was disallowed in nested prefab setups (where the root prefab is NOT a ghost).
26+
* Log verbiage when creating a driver in DefaultDriverConstructor read like a 'call to action'. It's not.
27+
28+
729
## [1.2.0-exp.3] - 2023-11-09
830

931
### Added

Documentation~/networked-cube.md

-1
Original file line numberDiff line numberDiff line change
@@ -459,7 +459,6 @@ using Unity.Entities;
459459
using Unity.NetCode;
460460
using UnityEngine;
461461

462-
[GhostComponent(PrefabType=GhostPrefabType.AllPredicted)]
463462
public struct CubeInput : IInputComponentData
464463
{
465464
public int Horizontal;

Editor/Authoring/BakedNetCodeComponents.cs

+25-7
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,36 @@
88

99
namespace Unity.NetCode.Editor
1010
{
11-
/// <summary>Internal class used by the GhostComponentInspector to store post-conversion (i.e. Baked) data.</summary>
12-
struct BakedGameObjectResult
11+
/// <summary>Internal structs used by the GhostComponentInspector to store post-conversion (i.e. Baked) data.</summary>
12+
class BakedResult
1313
{
14+
public Dictionary<GameObject, BakedGameObjectResult> GameObjectResults;
15+
public GhostAuthoringComponent GhostAuthoring;
16+
17+
public BakedGameObjectResult GetInspectionResult(GhostAuthoringInspectionComponent inspection)
18+
{
19+
foreach (var kvp in GameObjectResults)
20+
{
21+
if (kvp.Value.SourceInspection == inspection)
22+
return kvp.Value;
23+
}
24+
return null;
25+
}
26+
}
27+
28+
class BakedGameObjectResult
29+
{
30+
public BakedResult AuthoringRoot;
1431
public GameObject SourceGameObject;
15-
[CanBeNull] public GhostAuthoringInspectionComponent SourceInspection => SourceGameObject.GetComponent<GhostAuthoringInspectionComponent>();
16-
public GhostAuthoringComponent RootAuthoring;
32+
[CanBeNull] public GhostAuthoringInspectionComponent SourceInspection;
33+
public GhostAuthoringComponent RootAuthoring => AuthoringRoot.GhostAuthoring;
1734
public string SourcePrefabPath;
1835
public List<BakedEntityResult> BakedEntities;
36+
public int NumComponents;
1937
}
2038

2139
/// <inheritdoc cref="BakedGameObjectResult"/>
22-
struct BakedEntityResult
40+
class BakedEntityResult
2341
{
2442
public BakedGameObjectResult GoParent;
2543
public Entity Entity;
@@ -29,7 +47,7 @@ struct BakedEntityResult
2947
public bool IsPrimaryEntity => EntityIndex == 0;
3048
public List<BakedComponentItem> BakedComponents;
3149
public bool IsLinkedEntity;
32-
public bool IsRoot;
50+
public bool IsRoot => !IsLinkedEntity && GoParent.SourceGameObject == GoParent.RootAuthoring.gameObject && IsPrimaryEntity;
3351
}
3452

3553
/// <inheritdoc cref="BakedGameObjectResult"/>
@@ -100,7 +118,7 @@ public ref GhostAuthoringInspectionComponent.ComponentOverride GetPrefabOverride
100118
{
101119
if (EntityParent.GoParent.SourceInspection.TryFindExistingOverrideIndex(managedType, entityGuid, out var index))
102120
return ref EntityParent.GoParent.SourceInspection.ComponentOverrides[index];
103-
throw new InvalidOperationException("No override declared.");
121+
throw new InvalidOperationException($"No override created for '{fullname}'! '{serializationStrategy.ToFixedString()}', EntityGuid: {entityGuid.ToString()}!");
104122
}
105123

106124
/// <summary>Returns true if this Inspection Component has a prefab override for this Baked Component Type.</summary>

Editor/Authoring/EntityPrefabComponentsPreview.cs

+98-47
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Linq;
4-
using System.Reflection;
54
using Unity.Collections;
65
using Unity.Collections.NotBurstCompatible;
76
using Unity.Entities;
8-
using Unity.Entities.Conversion;
97
using UnityEditor;
108
using UnityEngine;
119

@@ -20,113 +18,166 @@ class EntityPrefabComponentsPreview
2018
struct ComponentNameComparer : IComparer<ComponentType>
2119
{
2220
public int Compare(ComponentType x, ComponentType y) =>
23-
x.GetManagedType().FullName.CompareTo(y.GetManagedType().FullName);
21+
string.Compare(x.GetManagedType().FullName, y.GetManagedType().FullName, StringComparison.Ordinal);
2422
}
2523

2624
/// <summary>Triggers the baking conversion process on the 'authoringComponent' and appends all resulting baked entities and components to the 'bakedDataMap'.</summary>
27-
public void BakeEntireNetcodePrefab(GhostAuthoringComponent authoringComponent, Dictionary<GameObject, BakedGameObjectResult> bakedDataMap)
25+
public void BakeEntireNetcodePrefab(GhostAuthoringComponent ghostAuthoring, GhostAuthoringInspectionComponent inspectionComponent, Dictionary<GhostAuthoringInspectionComponent, BakedResult> cachedBakedResults)
2826
{
27+
GhostAuthoringInspectionComponent.forceBake = false;
28+
if (ghostAuthoring == null)
29+
{
30+
Debug.LogError($"Attempting to bake `GhostAuthoringInspectionComponent` '{inspectionComponent.name}', but no root `GhostAuthoringComponent` found!");
31+
return;
32+
}
33+
2934
try
3035
{
31-
EditorUtility.DisplayProgressBar($"Baking '{authoringComponent}'...", "Baking triggered by the GhostAuthoringInspectionComponent.", .9f);
32-
GhostAuthoringInspectionComponent.forceBake = false;
33-
GhostAuthoringInspectionComponent.forceSave = true;
36+
EditorUtility.DisplayProgressBar($"Baking '{ghostAuthoring}'...", "Baking triggered by the GhostAuthoringInspectionComponent.", .9f);
3437

3538
// TODO - Handle exceptions due to invalid prefab setup. E.g.
3639
// "InvalidOperationException: OwnerPrediction mode can only be used on prefabs which have a GhostOwner"
37-
using(var world = new World(nameof(EntityPrefabComponentsPreview)))
40+
using var world = new World(nameof(EntityPrefabComponentsPreview));
41+
using var blobAssetStore = new BlobAssetStore(128);
42+
ghostAuthoring.ForcePrefabConversion = true;
43+
44+
var bakeResult = new BakedResult
3845
{
39-
using var blobAssetStore = new BlobAssetStore(128);
40-
authoringComponent.ForcePrefabConversion = true;
46+
GhostAuthoring = ghostAuthoring,
47+
GameObjectResults = new (32),
48+
};
4149

42-
var bakingSettings = new BakingSettings(BakingUtility.BakingFlags.AddEntityGUID, blobAssetStore);
43-
BakingUtility.BakeGameObjects(world, new[] {authoringComponent.gameObject}, bakingSettings);
44-
var bakingSystem = world.GetExistingSystemManaged<BakingSystem>();
45-
var primaryEntitiesMap = new HashSet<Entity>(16);
50+
var bakingSettings = new BakingSettings(BakingUtility.BakingFlags.AddEntityGUID, blobAssetStore);
51+
BakingUtility.BakeGameObjects(world, new[] {ghostAuthoring.gameObject}, bakingSettings);
52+
var bakingSystem = world.GetExistingSystemManaged<BakingSystem>();
53+
var primaryEntitiesMap = new HashSet<Entity>(16);
4654

47-
var primaryEntity = bakingSystem.GetEntity(authoringComponent.gameObject);
48-
var ghostBlobAsset = world.EntityManager.GetComponentData<GhostPrefabMetaData>(primaryEntity).Value;
55+
var primaryEntity = bakingSystem.GetEntity(ghostAuthoring.gameObject);
56+
var ghostBlobAsset = world.EntityManager.GetComponentData<GhostPrefabMetaData>(primaryEntity).Value;
4957

50-
CreatedBakedResultForPrimaryEntities(world, bakedDataMap, authoringComponent, bakingSystem, primaryEntitiesMap, ghostBlobAsset);
51-
CreatedBakedResultForLinkedEntities(world, bakedDataMap, primaryEntitiesMap, ghostBlobAsset);
52-
}
58+
CreatedBakedResultForPrimaryEntities(bakeResult, world, bakingSystem, primaryEntitiesMap, ghostBlobAsset, cachedBakedResults);
59+
CreatedBakedResultForAdditionalEntities(bakeResult, world, primaryEntitiesMap, ghostBlobAsset, bakingSystem);
5360
}
5461
finally
5562
{
5663
EditorUtility.ClearProgressBar();
57-
authoringComponent.ForcePrefabConversion = false;
64+
GhostAuthoringInspectionComponent.forceRebuildInspector = true;
65+
ghostAuthoring.ForcePrefabConversion = false;
5866
}
5967
}
6068

61-
void CreatedBakedResultForPrimaryEntities(World world, Dictionary<GameObject, BakedGameObjectResult> bakedDataMap, GhostAuthoringComponent authoringComponent, BakingSystem bakingSystem, HashSet<Entity> primaryEntitiesMap, BlobAssetReference<GhostPrefabBlobMetaData> blobAssetReference)
69+
70+
internal static int CountComponents(GameObject go)
71+
{
72+
return go.GetComponents<Component>().Length;
73+
}
74+
75+
static void CreatedBakedResultForPrimaryEntities(BakedResult bakedResult, World world, BakingSystem bakingSystem, HashSet<Entity> primaryEntitiesMap, BlobAssetReference<GhostPrefabBlobMetaData> blobAssetReference, Dictionary<GhostAuthoringInspectionComponent, BakedResult> cachedBakedResults)
6276
{
63-
foreach (var t in authoringComponent.GetComponentsInChildren<Transform>())
77+
foreach (var t in bakedResult.GhostAuthoring.GetComponentsInChildren<Transform>())
6478
{
6579
var go = t.gameObject;
6680

67-
// I'd like to skip children that DONT have an Inspection component, but not possible as they may add one.
68-
6981
var sourcePrefabPath = AssetDatabase.GetAssetPath(go);
70-
var result = new BakedGameObjectResult
82+
var goResult = new BakedGameObjectResult
7183
{
84+
AuthoringRoot = bakedResult,
7285
SourceGameObject = go,
86+
SourceInspection = go.GetComponent<GhostAuthoringInspectionComponent>(),
7387
SourcePrefabPath = sourcePrefabPath,
74-
RootAuthoring = authoringComponent,
75-
BakedEntities = new List<BakedEntityResult>(1)
88+
BakedEntities = new List<BakedEntityResult>(2),
89+
NumComponents = CountComponents(go),
7690
};
91+
var discoveredInspectionComponent = goResult.SourceInspection;
92+
if (discoveredInspectionComponent != null)
93+
cachedBakedResults[discoveredInspectionComponent] = bakedResult;
7794

7895
var primaryEntity = bakingSystem.GetEntity(go);
7996
if (bakingSystem.EntityManager.Exists(primaryEntity))
8097
{
81-
result.BakedEntities.Add(CreateBakedEntityResult(result, 0, world, primaryEntity, false, blobAssetReference));
98+
goResult.BakedEntities.Add(CreateBakedEntityResult(goResult, 0, world, bakingSystem, primaryEntity, false, blobAssetReference));
8299
primaryEntitiesMap.Add(primaryEntity);
83100
}
84-
bakedDataMap[go] = result;
101+
bakedResult.GameObjectResults[go] = goResult;
85102
}
86103
}
87104

88-
void CreatedBakedResultForLinkedEntities(World world, Dictionary<GameObject, BakedGameObjectResult> bakedDataMap, HashSet<Entity> primaryEntitiesMap, BlobAssetReference<GhostPrefabBlobMetaData> blobAssetReference)
105+
static void CreatedBakedResultForAdditionalEntities(BakedResult bakedResult, World world, HashSet<Entity> primaryEntitiesMap, BlobAssetReference<GhostPrefabBlobMetaData> blobAssetReference, BakingSystem bakingSystem)
89106
{
90-
foreach (var kvp in bakedDataMap)
107+
// Note: We only expect the ROOT entity to have a LinkedEntityGroup,
108+
// but checking EVERY baked GameObject as this is not an assumption we control.
109+
foreach (var kvp in bakedResult.GameObjectResults)
91110
{
92111
// TODO - Test-case to ensure the root entity does not contain ALL linked entities (even for children + additional).
93112
for (int index = 0, max = kvp.Value.BakedEntities.Count; index < max; index++)
94113
{
95114
var bakedEntityResult = kvp.Value.BakedEntities[index];
96115
var primaryEntity = bakedEntityResult.Entity;
97-
if (world.EntityManager.HasComponent<LinkedEntityGroup>(primaryEntity))
116+
if (!world.EntityManager.HasComponent<LinkedEntityGroup>(primaryEntity))
117+
continue;
118+
119+
var linkedEntityGroup = world.EntityManager.GetBuffer<LinkedEntityGroup>(primaryEntity);
120+
for (int i = 1; i < linkedEntityGroup.Length; ++i)
98121
{
99-
var linkedEntityGroup = world.EntityManager.GetBuffer<LinkedEntityGroup>(primaryEntity);
100-
for (int i = 1; i < linkedEntityGroup.Length; ++i)
122+
var linkedEntity = linkedEntityGroup[i].Value;
123+
124+
// Child entities are considered 'primary' entities. Thus, ignore them.
125+
// I.e. During Baking, if users call `CreateAdditionalEntity`, it won't be 'primary'.
126+
if (primaryEntitiesMap.Contains(linkedEntity))
127+
continue;
128+
129+
// Find the actual authoring GameObject for this linked entity. It might be one of our children.
130+
var foundActualAuthoring = TryGetAuthoringForAdditionalEntity(linkedEntity, bakingSystem, bakedResult.GameObjectResults.Values, out var actualAuthoring);
131+
if (!foundActualAuthoring)
101132
{
102-
var linkedEntity = linkedEntityGroup[i].Value;
103-
104-
// Only show linked entities if they're not primary entities of child GameObjects.
105-
// I.e. Only possible if, during Baking, users call `CreateAdditionalEntity`.
106-
if (!primaryEntitiesMap.Contains(linkedEntity))
107-
{
108-
kvp.Value.BakedEntities.Add(CreateBakedEntityResult(kvp.Value, i, world, linkedEntity, true, blobAssetReference));
109-
}
133+
Debug.LogWarning($"Expected to find the source BakedGameObjectResult for Additional Entity '{linkedEntity.ToFixedString()}' ('{bakingSystem.EntityManager.GetName(linkedEntity)}') (via EntityGuid search), but did not! Assuming the authoring GameObject is '{kvp.Value.SourceGameObject.name}'! Please file a bug report if this assumption is false.", kvp.Value.SourceGameObject);
134+
135+
actualAuthoring = kvp.Value;
110136
}
137+
var entityResult = CreateBakedEntityResult(actualAuthoring, i, world, bakingSystem, linkedEntity, true, blobAssetReference);
138+
actualAuthoring.BakedEntities.Add(entityResult);
139+
}
140+
}
141+
}
142+
}
143+
144+
static bool TryGetAuthoringForAdditionalEntity(Entity additionalEntity, BakingSystem bakingSystem, Dictionary<GameObject, BakedGameObjectResult>.ValueCollection results, out BakedGameObjectResult found)
145+
{
146+
found = default;
147+
if (!bakingSystem.EntityManager.HasComponent<EntityGuid>(additionalEntity))
148+
{
149+
Debug.LogError($"Additional entity '{additionalEntity.ToFixedString()}' did not have an EntityGuid! Thus, cannot find Authoring for it!");
150+
return false;
151+
}
152+
var additionalEntitiesEntityGuid = bakingSystem.EntityManager.GetComponentData<EntityGuid>(additionalEntity);
153+
154+
foreach (var result in results)
155+
{
156+
foreach (var x in result.BakedEntities)
157+
{
158+
if (x.Guid.OriginatingId == additionalEntitiesEntityGuid.OriginatingId)
159+
{
160+
found = result;
161+
return true;
111162
}
112163
}
113164
}
165+
166+
return false;
114167
}
115168

116-
BakedEntityResult CreateBakedEntityResult(BakedGameObjectResult parent, int entityIndex, World world, Entity convertedEntity, bool isLinkedEntity, BlobAssetReference<GhostPrefabBlobMetaData> blobAssetReference)
169+
static BakedEntityResult CreateBakedEntityResult(BakedGameObjectResult authoring, int entityIndex, World world, BakingSystem bakingSystem, Entity convertedEntity, bool isLinkedEntity, BlobAssetReference<GhostPrefabBlobMetaData> blobAssetReference)
117170
{
118-
var isRoot = parent.SourceGameObject == parent.RootAuthoring.gameObject;
119171
var guid = world.EntityManager.GetComponentData<EntityGuid>(convertedEntity);
120172
var result = new BakedEntityResult
121173
{
122-
GoParent = parent,
174+
GoParent = authoring,
123175
Entity = convertedEntity,
124176
Guid = guid,
125177
EntityName = world.EntityManager.GetName(convertedEntity),
126178
EntityIndex = entityIndex,
127179
BakedComponents = new List<BakedComponentItem>(16),
128180
IsLinkedEntity = isLinkedEntity,
129-
IsRoot = isRoot,
130181
};
131182

132183
using var query = world.EntityManager.CreateEntityQuery(ComponentType.ReadOnly<GhostComponentSerializerCollectionData>());
@@ -144,7 +195,7 @@ BakedEntityResult CreateBakedEntityResult(BakedGameObjectResult parent, int enti
144195
{
145196
variantTypesList.Add(compItem.availableSerializationStrategies[i]);
146197
}
147-
compItem.serializationStrategy = collectionData.SelectSerializationStrategyForComponentWithHash(ComponentType.ReadWrite(compItem.managedType), searchHash, variantTypesList, isRoot);
198+
compItem.serializationStrategy = collectionData.SelectSerializationStrategyForComponentWithHash(ComponentType.ReadWrite(compItem.managedType), searchHash, variantTypesList, result.IsRoot);
148199
compItem.sendToOwnerType = compItem.serializationStrategy.IsSerialized != 0 ? collectionData.Serializers[compItem.serializationStrategy.SerializerIndex].SendToOwner : SendToOwnerType.None;
149200

150201
if (compItem.anyVariantIsSerialized)

0 commit comments

Comments
 (0)