Skip to content

feat: snapshot spawn pre-requisite 2 #1192

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 32 commits into from
Sep 16, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
3185e2a
feat: snapshot. Enabling spawns via snapshot by default
jeffreyrainy Sep 9, 2021
5e2bff9
Merge remote-tracking branch 'origin/develop' into feature/enable-sna…
jeffreyrainy Sep 9, 2021
2526e78
Merge branch 'develop' into feature/enable-snapshot-spawn
jeffreyrainy Sep 9, 2021
e07b934
feat: snapshot. Fixing the snapshot spawns mechanism to not rely on S…
jeffreyrainy Sep 10, 2021
eff9a36
feat: snapshot. Removing the default on for snapshot spawns
jeffreyrainy Sep 10, 2021
6e1f2f6
Merge branch 'develop' into feature/enable-snapshot-spawn-pre
jeffreyrainy Sep 10, 2021
75ad1b7
Merge remote-tracking branch 'origin/develop' into feature/enable-sna…
jeffreyrainy Sep 13, 2021
8307af6
Merge remote-tracking branch 'origin/develop' into feature/enable-sna…
jeffreyrainy Sep 13, 2021
3ff0845
Merge branch 'feature/enable-snapshot-spawn-pre' into feature/enable-…
jeffreyrainy Sep 13, 2021
7063c7b
Merge remote-tracking branch 'origin/develop' into feature/enable-sna…
jeffreyrainy Sep 13, 2021
81f715d
test: adjusted HiddenVariableTests to separate spawning from value up…
jeffreyrainy Sep 13, 2021
3aa0ca3
fix
NoelStephensUnity Sep 13, 2021
5f04318
refactor
NoelStephensUnity Sep 13, 2021
9cf06ab
refactor
NoelStephensUnity Sep 13, 2021
fb040f8
Merge branch 'develop' into fix/unitytransport-connectionmode-buttons
NoelStephensUnity Sep 13, 2021
94ad362
style
NoelStephensUnity Sep 14, 2021
13e069b
Merge remote-tracking branch 'origin/develop' into feature/enable-sna…
jeffreyrainy Sep 14, 2021
774f398
Merge remote-tracking branch 'origin/fix/unitytransport-connectionmod…
jeffreyrainy Sep 14, 2021
543e4b1
Merge remote-tracking branch 'origin/develop' into feature/enable-sna…
jeffreyrainy Sep 14, 2021
933fad7
chore: removal of EnableNetworkVariable in NetworkConfig. It's always…
jeffreyrainy Sep 14, 2021
9e45a1a
Merge branch 'develop' into chore/enable-network-variable-removal
jeffreyrainy Sep 14, 2021
75a5280
Merge remote-tracking branch 'origin/develop' into feature/enable-sna…
jeffreyrainy Sep 14, 2021
82f2d0e
Merge remote-tracking branch 'origin/chore/enable-network-variable-re…
jeffreyrainy Sep 14, 2021
586290b
feat: snpashot enabled. Marking variables as dirty instead of seriali…
jeffreyrainy Sep 14, 2021
5263f5f
fix: this bool write cannot be remoed yet
jeffreyrainy Sep 14, 2021
128ac60
Merge remote-tracking branch 'origin/chore/enable-network-variable-re…
jeffreyrainy Sep 14, 2021
be4919a
Merge remote-tracking branch 'origin/develop' into feature/enable-sna…
jeffreyrainy Sep 14, 2021
fa8e321
feat: snapshot spawn. Improving test, and re-enabling
jeffreyrainy Sep 14, 2021
7c0515e
feat: snapshot. Pre-requisite to enabling snapshot spawns. Separating…
jeffreyrainy Sep 16, 2021
a96ae2f
Merge remote-tracking branch 'origin/develop' into feature/enable-sna…
jeffreyrainy Sep 16, 2021
40454f3
Merge remote-tracking branch 'origin/develop' into feature/enable-sna…
jeffreyrainy Sep 16, 2021
548b766
style: standards.py fix, removing useless using directive
jeffreyrainy Sep 16, 2021
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
Original file line number Diff line number Diff line change
Expand Up @@ -714,6 +714,14 @@ internal void HandleNetworkVariableDeltas(Stream stream, ulong clientId)
}
}

internal void MarkVariablesDirty()
{
for (int j = 0; j < NetworkVariableFields.Count; j++)
{
NetworkVariableFields[j].SetDirty(true);
}
}

internal void WriteNetworkVariableData(Stream stream, ulong clientId)
{
if (NetworkVariableFields.Count == 0)
Expand Down
9 changes: 9 additions & 0 deletions com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -852,6 +852,15 @@ internal void WriteNetworkVariableData(Stream stream, ulong clientId)
}
}

internal void MarkVariablesDirty()
{
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
{
var behavior = ChildNetworkBehaviours[i];
behavior.MarkVariablesDirty();
}
}

internal void SetNetworkVariableData(Stream stream)
{
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,20 @@ public override bool IsDirty()
public override void WriteDelta(Stream stream)
{
using var writer = PooledNetworkWriter.Get(stream);

if (base.IsDirty())
{
Copy link
Contributor

Choose a reason for hiding this comment

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

Problably stupid question because I'm misunderstanding this. But this causes the full list to be serialized to all connected clients whenever IsDirty is set to true? Wouldn't that happen too often? E.g. when:

  • An item is added / removed from the list
  • The object becomes visible on any client.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

On the first point, my understanding is that NetworkList.Add() will do:

            m_List.Add(item); // add to the underlying container

            var listEvent = new NetworkListEvent<T>()  {
                Type = NetworkListEvent<T>.EventType.Add,
...            }; // create an event

            HandleAddListEvent(listEvent); // handle the event
        }

This then does:

        private void HandleAddListEvent(NetworkListEvent<T> listEvent)
        {
            m_DirtyEvents.Add(listEvent);
            OnListChanged?.Invoke(listEvent);
        }

and as such does not mark the list dirty. So, dirty means: needs complete refresh, and m_DirtyEvents means need incremental changes.

On the second point, yeah. There's a price to pay here. But until AoI is integrated and we start automatically hiding/showing object, I think these will be very infrequent.

Copy link
Contributor

Choose a reason for hiding this comment

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

Okay yeah that makes sense

Copy link
Contributor

Choose a reason for hiding this comment

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

I follow, but would add:

  • please add a comment to the effect of "IsDirty means totally dirty, else m_DirtyEvents means incremental
  • but I have a hunch this will bite us and should be re-visited

writer.WriteUInt16Packed(1);
writer.WriteByte((byte)NetworkListEvent<T>.EventType.Full);
WriteField(stream);

return;
}

writer.WriteUInt16Packed((ushort)m_DirtyEvents.Length);
for (int i = 0; i < m_DirtyEvents.Length; i++)
{
writer.WriteBits((byte)m_DirtyEvents[i].Type, 3);
writer.WriteByte((byte)m_DirtyEvents[i].Type);
switch (m_DirtyEvents[i].Type)
{
case NetworkListEvent<T>.EventType.Add:
Expand Down Expand Up @@ -145,7 +155,7 @@ public override void ReadDelta(Stream stream, bool keepDirtyDelta)
ushort deltaCount = reader.ReadUInt16Packed();
for (int i = 0; i < deltaCount; i++)
{
var eventType = (NetworkListEvent<T>.EventType)reader.ReadBits(3);
var eventType = (NetworkListEvent<T>.EventType)reader.ReadByte();
switch (eventType)
{
case NetworkListEvent<T>.EventType.Add:
Expand Down Expand Up @@ -311,6 +321,12 @@ public override void ReadDelta(Stream stream, bool keepDirtyDelta)
}
}
break;
case NetworkListEvent<T>.EventType.Full:
{
ReadField(stream);
ResetDirty();
}
break;
}
}
}
Expand Down Expand Up @@ -495,7 +511,12 @@ public enum EventType
/// <summary>
/// Clear
/// </summary>
Clear
Clear,

/// <summary>
/// Full list refresh
/// </summary>
Full
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,8 @@ internal void SendSpawnCallForObject(ulong clientId, NetworkObject networkObject
var size = bufferSizeCapture.StopMeasureSegment();
NetworkManager.NetworkMetrics.TrackObjectSpawnSent(clientId, networkObject.NetworkObjectId, networkObject.name, size);
}

networkObject.MarkVariablesDirty();
}
}

Expand Down Expand Up @@ -451,7 +453,6 @@ internal void WriteSpawnCallForObject(PooledNetworkWriter writer, ulong clientId
var (isReparented, latestParent) = networkObject.GetNetworkParenting();
NetworkObject.WriteNetworkParenting(writer, isReparented, latestParent);
}
networkObject.WriteNetworkVariableData(writer.GetStream(), clientId);
}

internal void DespawnObject(NetworkObject networkObject, bool destroyObject = false)
Expand Down
144 changes: 114 additions & 30 deletions com.unity.netcode.gameobjects/Tests/Runtime/HiddenVariableTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.TestTools;

Expand All @@ -13,30 +14,29 @@ public class HiddenVariableObject : NetworkBehaviour
public NetworkVariable<int> MyNetworkVariable = new NetworkVariable<int>();
public NetworkList<int> MyNetworkList = new NetworkList<int>();

public static int ChangeCount = 0;
public static int ListChangeCount = 0;
public static Dictionary<ulong, int> ValueOnClient = new Dictionary<ulong, int>();
public static int ExpectedSize = 0;
public static int SpawnCount = 0;

public override void OnNetworkSpawn()
{
Debug.Log($"{nameof(HiddenVariableObject)}.{nameof(OnNetworkSpawn)}()");
Debug.Log($"{nameof(HiddenVariableObject)}.{nameof(OnNetworkSpawn)}() with value {MyNetworkVariable.Value}");

MyNetworkVariable.OnValueChanged += Changed;
MyNetworkList.OnListChanged += ListChanged;
SpawnCount++;

base.OnNetworkSpawn();
}

public void Changed(int before, int after)
{
Debug.Log($"Value changed from {before} to {after} on {NetworkManager.LocalClientId}");
ChangeCount++;
ValueOnClient[NetworkManager.LocalClientId] = after;
}
public void ListChanged(NetworkListEvent<int> listEvent)
{
Debug.Log($"ListEvent received: type {listEvent.Type}, index {listEvent.Index}, value {listEvent.Value}");
ListChangeCount++;

Debug.Assert(ExpectedSize == MyNetworkList.Count);
}
}
Expand All @@ -46,6 +46,7 @@ public class HiddenVariableTests : BaseMultiInstanceTest
protected override int NbClients => 4;

private NetworkObject m_NetSpawnedObject;
private List<NetworkObject> m_NetSpawnedObjectOnClient = new List<NetworkObject>();
private GameObject m_TestNetworkPrefab;

[UnitySetUp]
Expand Down Expand Up @@ -83,75 +84,158 @@ public IEnumerator WaitForConnectedCount(int targetCount)
}
}

public IEnumerator WaitForChangeCount(int targetCount)
public IEnumerator WaitForSpawnCount(int targetCount)
{
var endTime = Time.realtimeSinceStartup + 1.0;
while ((HiddenVariableObject.ChangeCount != targetCount ||
HiddenVariableObject.ListChangeCount != targetCount) &&
while (HiddenVariableObject.SpawnCount != targetCount &&
Time.realtimeSinceStartup < endTime)
{
yield return new WaitForSeconds(0.01f);
}
}

public void VerifyLists()
{
NetworkList<int> prev = null;
int numComparison = 0;

// for all the instances of NetworkList
foreach (var gameObject in m_NetSpawnedObjectOnClient)
{
// this skips despawned/hidden objects
if (gameObject != null)
{
// if we've seen another one before
if (prev != null)
{
var curr = gameObject.GetComponent<HiddenVariableObject>().MyNetworkList;

// check that the two lists are identical
Debug.Assert(curr.Count == prev.Count);
for (int index = 0; index < curr.Count; index++)
{
Debug.Assert(curr[index] == prev[index]);
}
numComparison++;
}
// store the list
prev = gameObject.GetComponent<HiddenVariableObject>().MyNetworkList;
}
}
Debug.Log($"{numComparison} comparisons done.");
}

public IEnumerator RefreshGameObects()
{
m_NetSpawnedObjectOnClient.Clear();

foreach (var netMan in m_ClientNetworkManagers)
{
var serverClientPlayerResult = new MultiInstanceHelpers.CoroutineResultWrapper<NetworkObject>();
yield return MultiInstanceHelpers.Run(
MultiInstanceHelpers.GetNetworkObjectByRepresentation(
x => x.NetworkObjectId == m_NetSpawnedObject.NetworkObjectId,
netMan,
serverClientPlayerResult));
m_NetSpawnedObjectOnClient.Add(serverClientPlayerResult.Result);
}
}

[UnityTest]
public IEnumerator HiddenVariableTest()
{
HiddenVariableObject.SpawnCount = 0;
HiddenVariableObject.ValueOnClient.Clear();
HiddenVariableObject.ExpectedSize = 0;
HiddenVariableObject.SpawnCount = 0;

Debug.Log("Running test");

var spawnedObject = Object.Instantiate(m_TestNetworkPrefab);
m_NetSpawnedObject = spawnedObject.GetComponent<NetworkObject>();
m_NetSpawnedObject.NetworkManagerOwner = m_ServerNetworkManager;
yield return WaitForConnectedCount(NbClients);
Debug.Log("Clients connected");

// Spawn object with ownership on one client
// ==== Spawn object with ownership on one client
var client = m_ServerNetworkManager.ConnectedClientsList[1];
var otherClient = m_ServerNetworkManager.ConnectedClientsList[2];
m_NetSpawnedObject.SpawnWithOwnership(client.ClientId);

// Set the NetworkVariable value to 2
yield return RefreshGameObects();

// === Check spawn occured
yield return WaitForSpawnCount(NbClients + 1);
Debug.Assert(HiddenVariableObject.SpawnCount == NbClients + 1);
Debug.Log("Objects spawned");

// ==== Set the NetworkVariable value to 2
HiddenVariableObject.ExpectedSize = 1;
HiddenVariableObject.ChangeCount = 0;
HiddenVariableObject.ListChangeCount = 0;
HiddenVariableObject.SpawnCount = 0;

m_NetSpawnedObject.GetComponent<HiddenVariableObject>().MyNetworkVariable.Value = 2;
m_NetSpawnedObject.GetComponent<HiddenVariableObject>().MyNetworkList.Add(2);

yield return WaitForChangeCount(NbClients + 1);
Debug.Assert(HiddenVariableObject.ChangeCount == NbClients + 1);
Debug.Assert(HiddenVariableObject.ListChangeCount == NbClients + 1);
yield return new WaitForSeconds(1.0f);

foreach (var id in m_ServerNetworkManager.ConnectedClientsIds)
{
Debug.Assert(HiddenVariableObject.ValueOnClient[id] == 2);
}

VerifyLists();

// Hide our object to a different client
Debug.Log("Value changed");

// ==== Hide our object to a different client
HiddenVariableObject.ExpectedSize = 2;
HiddenVariableObject.ChangeCount = 0;
HiddenVariableObject.ListChangeCount = 0;
m_NetSpawnedObject.NetworkHide(otherClient.ClientId);

// Change the NetworkVariable value
// ==== Change the NetworkVariable value
// we should get one less notification of value changing and no errors or exception
m_NetSpawnedObject.GetComponent<HiddenVariableObject>().MyNetworkVariable.Value = 3;
m_NetSpawnedObject.GetComponent<HiddenVariableObject>().MyNetworkList.Add(3);

yield return new WaitForSeconds(1.0f);
Debug.Assert(HiddenVariableObject.ChangeCount == NbClients);
Debug.Assert(HiddenVariableObject.ListChangeCount == NbClients);
foreach (var id in m_ServerNetworkManager.ConnectedClientsIds)
{
if (id != otherClient.ClientId)
{
Debug.Assert(HiddenVariableObject.ValueOnClient[id] == 3);
}
}

// Show our object again to this client
VerifyLists();
Debug.Log("Values changed");

// ==== Show our object again to this client
HiddenVariableObject.ExpectedSize = 3;
HiddenVariableObject.ChangeCount = 0;
HiddenVariableObject.ListChangeCount = 0;
m_NetSpawnedObject.NetworkShow(otherClient.ClientId);

// Change the NetworkVariable value
// ==== Wait for object to be spawned
yield return WaitForSpawnCount(1);
Debug.Assert(HiddenVariableObject.SpawnCount == 1);
Debug.Log("Object spawned");

// ==== We need a refresh for the newly re-spawned object
yield return RefreshGameObects();

// ==== Change the NetworkVariable value
// we should get all notifications of value changing and no errors or exception
m_NetSpawnedObject.GetComponent<HiddenVariableObject>().MyNetworkVariable.Value = 4;
m_NetSpawnedObject.GetComponent<HiddenVariableObject>().MyNetworkList.Add(4);

yield return WaitForChangeCount(NbClients + 1);
Debug.Assert(HiddenVariableObject.ChangeCount == NbClients + 1);
Debug.Assert(HiddenVariableObject.ListChangeCount == NbClients + 1);
yield return new WaitForSeconds(1.0f);

foreach (var id in m_ServerNetworkManager.ConnectedClientsIds)
{
Debug.Assert(HiddenVariableObject.ValueOnClient[id] == 4);
}

VerifyLists();
Debug.Log("Values changed");

// Hide our object to that different client again, and then destroy it
// ==== Hide our object to that different client again, and then destroy it
m_NetSpawnedObject.NetworkHide(otherClient.ClientId);
yield return new WaitForSeconds(0.2f);
m_NetSpawnedObject.Despawn();
Expand Down