Skip to content

refactor: decouple PendingSoftSyncObjects from NetworkSpawnManager #913

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 16 commits into from
Jun 29, 2021
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ internal void GenerateGlobalObjectIdHash()
return;
}

// do NOT regenerate GlobalObjectIdHash if Editor is transitining into or out of PlayMode
// do NOT regenerate GlobalObjectIdHash if Editor is transitioning into or out of PlayMode
if (!UnityEditor.EditorApplication.isPlaying && UnityEditor.EditorApplication.isPlayingOrWillChangePlaymode)
{
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ public void HandleConnectionApproved(ulong clientId, Stream stream, float receiv

void DelayedSpawnAction(Stream continuationStream)
{

using (var continuationReader = PooledNetworkReader.Get(continuationStream))
{
if (!NetworkManager.NetworkConfig.EnableSceneManagement)
Expand All @@ -94,7 +95,7 @@ void DelayedSpawnAction(Stream continuationStream)
}
else
{
NetworkManager.SpawnManager.ClientCollectSoftSyncSceneObjectSweep(null);
NetworkManager.SceneManager.PopulateScenePlacedObjects();
}

var objectCount = continuationReader.ReadUInt32Packed();
Expand All @@ -103,7 +104,6 @@ void DelayedSpawnAction(Stream continuationStream)
NetworkObject.DeserializeSceneObject(continuationStream as NetworkBuffer, continuationReader, m_NetworkManager);
}

NetworkManager.SpawnManager.CleanDiffedSceneObjects();
NetworkManager.IsConnectedClient = true;
NetworkManager.InvokeOnClientConnectedCallback(NetworkManager.LocalClientId);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ public class NetworkSceneManager
internal readonly Dictionary<string, uint> SceneNameToIndex = new Dictionary<string, uint>();
internal readonly Dictionary<uint, string> SceneIndexToString = new Dictionary<uint, string>();
internal readonly Dictionary<Guid, SceneSwitchProgress> SceneSwitchProgresses = new Dictionary<Guid, SceneSwitchProgress>();
internal readonly Dictionary<uint, NetworkObject> ScenePlacedObjects = new Dictionary<uint, NetworkObject>();
Copy link
Contributor

Choose a reason for hiding this comment

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

Just to be sure, this functions the same as the previous PendingSoftSyncObjects? So once a scene placed object get spawned it will be removed from this list? Maybe we should call this PendingScenePlacedObjects or UnspawnedScenePlacedObjects if that is the case?


private static Scene s_LastScene;
private static string s_NextSceneName;
Expand Down Expand Up @@ -273,12 +274,49 @@ internal void OnFirstSceneSwitchSync(uint sceneIndex, Guid switchSceneGuid)
s_IsSwitching = false;
}




/// <summary>
/// Should be invoked on both the client and server side after:
/// -- A new scene has been loaded
/// -- Before any "DontDestroyOnLoad" NetworkObjects have been added back into the scene.
/// Added the ability to choose not to clear the scene placed objects for additive scene loading.
/// </summary>
internal void PopulateScenePlacedObjects(bool clearScenePlacedObjects = true)
Copy link
Contributor

Choose a reason for hiding this comment

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

minor (personal opinion):

Suggested change
internal void PopulateScenePlacedObjects(bool clearScenePlacedObjects = true)
internal void UpdateScenePlacedObjects(bool clearScenePlacedObjects = true)

{
if (clearScenePlacedObjects)
{
ScenePlacedObjects.Clear();
}

var networkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>();

// Just add every NetworkObject found that isn't already in the list
// If any "non-in-scene placed NetworkObjects" are added to this list it shouldn't matter
// The only thing that matters is making sure each NetworkObject is keyed off of their GlobalObjectIdHash
foreach (var networkObjectInstance in networkObjects)
{
if (!ScenePlacedObjects.ContainsKey(networkObjectInstance.GlobalObjectIdHash))
{
// We check to make sure the NetworkManager instance is the same one to be "MultiInstanceHelpers" compatible
if (networkObjectInstance.IsSceneObject == null && networkObjectInstance.NetworkManager == m_NetworkManager)
{
ScenePlacedObjects.Add(networkObjectInstance.GlobalObjectIdHash, networkObjectInstance);
}
}
}
}

private void OnSceneLoaded(Guid switchSceneGuid, Stream objectStream)
{
CurrentActiveSceneIndex = SceneNameToIndex[s_NextSceneName];
var nextScene = SceneManager.GetSceneByName(s_NextSceneName);
SceneManager.SetActiveScene(nextScene);

//Get all NetworkObjects loaded by the scene
PopulateScenePlacedObjects();

// Move all objects to the new scene
MoveObjectsToScene(nextScene);

Expand All @@ -288,31 +326,22 @@ private void OnSceneLoaded(Guid switchSceneGuid, Stream objectStream)

if (m_NetworkManager.IsServer)
{
OnSceneUnloadServer(switchSceneGuid);
OnServerLoadedScene(switchSceneGuid);
}
else
{
OnSceneUnloadClient(switchSceneGuid, objectStream);
OnClientLoadedScene(switchSceneGuid, objectStream);
}
}

private void OnSceneUnloadServer(Guid switchSceneGuid)
private void OnServerLoadedScene(Guid switchSceneGuid)
Copy link
Contributor

Choose a reason for hiding this comment

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

while you're doing some small refactors here, do you mind renaming all switchSceneGuid and other switchXXX with non-switch versions such as sceneGuid instead of switchSceneGuid? I believe it wouldn't harm the understanding but fix syntax-highlighter getting confused :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good idea...

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Will see if there is a better term for this... I ignored some of this stuff because the later phases of the proposal would effectively remove all of that.

{
// Justification: Rare alloc, could(should?) reuse
var newSceneObjects = new List<NetworkObject>();
// Register in-scene placed NetworkObjects with MLAPI
foreach (var keyValuePair in ScenePlacedObjects)
{
var networkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>();

for (int i = 0; i < networkObjects.Length; i++)
if (!keyValuePair.Value.IsPlayerObject)
{
if (networkObjects[i].NetworkManager == m_NetworkManager)
{
if (networkObjects[i].IsSceneObject == null)
{
m_NetworkManager.SpawnManager.SpawnNetworkObjectLocally(networkObjects[i], m_NetworkManager.SpawnManager.GetNetworkObjectId(), true, false, null, null, false, 0, false, true);
newSceneObjects.Add(networkObjects[i]);
}
}
m_NetworkManager.SpawnManager.SpawnNetworkObjectLocally(keyValuePair.Value, m_NetworkManager.SpawnManager.GetNetworkObjectId(), true, false, null, null, false, 0, false, true);
}
}

Expand All @@ -327,25 +356,24 @@ private void OnSceneUnloadServer(Guid switchSceneGuid)
writer.WriteByteArray(switchSceneGuid.ToByteArray());

uint sceneObjectsToSpawn = 0;
for (int i = 0; i < newSceneObjects.Count; i++)

foreach (var keyValuePair in ScenePlacedObjects)
{
if (newSceneObjects[i].Observers.Contains(m_NetworkManager.ConnectedClientsList[j].ClientId))
if (keyValuePair.Value.Observers.Contains(m_NetworkManager.ConnectedClientsList[j].ClientId))
{
sceneObjectsToSpawn++;
}
}

// Write number of scene objects to spawn
writer.WriteUInt32Packed(sceneObjectsToSpawn);

for (int i = 0; i < newSceneObjects.Count; i++)
foreach (var keyValuePair in ScenePlacedObjects)
{
if (newSceneObjects[i].Observers.Contains(m_NetworkManager.ConnectedClientsList[j].ClientId))
if (keyValuePair.Value.Observers.Contains(m_NetworkManager.ConnectedClientsList[j].ClientId))
{
newSceneObjects[i].SerializeSceneObject(writer, m_NetworkManager.ConnectedClientsList[j].ClientId);
keyValuePair.Value.SerializeSceneObject(writer, m_NetworkManager.ConnectedClientsList[j].ClientId);
}
}

m_NetworkManager.MessageSender.Send(m_NetworkManager.ConnectedClientsList[j].ClientId, NetworkConstants.SWITCH_SCENE, NetworkChannel.Internal, buffer);
}
}
Expand All @@ -362,12 +390,10 @@ private void OnSceneUnloadServer(Guid switchSceneGuid)
OnSceneSwitched?.Invoke();
}

private void OnSceneUnloadClient(Guid switchSceneGuid, Stream objectStream)
private void OnClientLoadedScene(Guid switchSceneGuid, Stream objectStream)
{
var networkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>();

m_NetworkManager.SpawnManager.ClientCollectSoftSyncSceneObjectSweep(networkObjects);

using (var reader = PooledNetworkReader.Get(objectStream))
{
var newObjectsCount = reader.ReadUInt32Packed();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,6 @@ public class NetworkSpawnManager
/// </summary>
public readonly Dictionary<ulong, NetworkObject> SpawnedObjects = new Dictionary<ulong, NetworkObject>();
Copy link
Contributor

Choose a reason for hiding this comment

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

I guess we will eventually get rid of NetworkSpawnManager entirely at some point. For instance, SpawnedObjects should probably be managed by NetworkSceneManager since they are properties of scenes. Even if we were to move objects between scenes, they are still "in" scenes. Managing object spawning and instances in 2 (NetworkSpawnManager & NetworkSceneManager) feels a bit weird to me :P

But obv, that's something for later — just wanted to braindump my 2-cents here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah, it is weird indeed. This update removes the dependency from NetworkSpawnManager and places it into NetworkSceneManager in preparation for the proposed direction. If we do go in the proposed general direction, then the object synchronization process will be handled differently anyway.... so keeping a list of in-scene NetworkObjects instantiated via scene loading really isn't "handling spawning"... it is just "tracking what was automatically instantiated".


// Pending SoftSync objects
internal readonly Dictionary<ulong, NetworkObject> PendingSoftSyncObjects = new Dictionary<ulong, NetworkObject>();

/// <summary>
/// A list of the spawned objects
/// </summary>
Expand Down Expand Up @@ -246,17 +243,19 @@ internal NetworkObject CreateLocalNetworkObject(bool isSceneObject, uint globalO
}
else
{
// SoftSync them by mapping
if (!PendingSoftSyncObjects.TryGetValue(globalObjectIdHash, out NetworkObject networkObject))
if (!NetworkManager.SceneManager.ScenePlacedObjects.TryGetValue(globalObjectIdHash, out NetworkObject networkObject))
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
{
NetworkLog.LogError($"{nameof(NetworkPrefab)} hash was not found! In-Scene placed {nameof(NetworkObject)} soft synchronization failure for Hash: {globalObjectIdHash}!");
}

return null;
}

PendingSoftSyncObjects.Remove(globalObjectIdHash);
else
{
NetworkManager.SceneManager.ScenePlacedObjects.Remove(globalObjectIdHash);
}

if (parentNetworkObject != null)
{
Expand Down Expand Up @@ -557,20 +556,6 @@ internal void DestroySceneObjects()
}
}

internal void CleanDiffedSceneObjects()
{
// Clean up any in-scene objects that had been destroyed
if (PendingSoftSyncObjects.Count > 0)
{
foreach (var pair in PendingSoftSyncObjects)
{
UnityEngine.Object.Destroy(pair.Value.gameObject);
}

// Make sure to clear this once done destroying all remaining NetworkObjects
PendingSoftSyncObjects.Clear();
}
}

internal void ServerSpawnSceneObjectsOnStartSweep()
{
Expand All @@ -588,25 +573,6 @@ internal void ServerSpawnSceneObjectsOnStartSweep()
}
}

internal void ClientCollectSoftSyncSceneObjectSweep(NetworkObject[] networkObjects)
{
if (networkObjects == null)
{
networkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>();
}

for (int i = 0; i < networkObjects.Length; i++)
{
if (networkObjects[i].NetworkManager == NetworkManager)
{
if (networkObjects[i].IsSceneObject == null)
{
PendingSoftSyncObjects.Add(networkObjects[i].GlobalObjectIdHash, networkObjects[i]);
}
}
}
}

internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObject)
{
if (NetworkManager == null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,9 @@ public IEnumerator InstantiateDestroySpawnNotCalled()

yield return null;

// instantiate
var instance = Object.Instantiate(gameObject);
yield return null;

// destroy
Object.Destroy(instance);
Object.Destroy(gameObject);

yield return null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@ public void NetworkObjectSceneSerializationFailure()
// Serialize the valid NetworkObject
networkObject.SerializeSceneObject(writer, 0);

// Add this valid NetworkObject into the PendinigSoftSyncObjects list
NetworkManagerHelper.NetworkManagerObject.SpawnManager.PendingSoftSyncObjects.Add(networkObject.GlobalObjectIdHash, networkObject);
// Add this valid NetworkObject into the ScenePlacedObjects list
NetworkManagerHelper.NetworkManagerObject.SceneManager.ScenePlacedObjects.Add(networkObject.GlobalObjectIdHash, networkObject);
}
}

Expand Down
9 changes: 6 additions & 3 deletions testproject/Assets/Prefabs/Player.prefab
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,16 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
TransformAuthority: 1
FixedSendsPerSecond: 5
FixedSendsPerSecond: 15
InterpolatePosition: 1
SnapDistance: 10
InterpolateServer: 1
MinMeters: 0.16
MinMeters: 0.15
MinDegrees: 1.5
MinSize: 0.15
Channel: 0
Channel: 2
m_UseLocal:
m_InternalValue: 0
--- !u!114 &-3775814466963834669
MonoBehaviour:
m_ObjectHideFlags: 0
Expand All @@ -73,6 +75,7 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
GlobalObjectIdHash: 951099334
m_MarkedAsSceneObject: 0
AlwaysReplicateAsRoot: 0
DontDestroyWithOwner: 0
--- !u!33 &4079352819444256610
Expand Down
20 changes: 17 additions & 3 deletions testproject/Assets/Tests/Manual/Scripts/NetworkPrefabPool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public class NetworkPrefabPool : NetworkBehaviour
[Tooltip("When enabled, this will utilize the NetworkPrefabHandler to register a custom INetworkPrefabInstanceHandler")]
public bool EnableHandler;

[Tooltip("When enabled, this will register register a custom INetworkPrefabInstanceHandler using a NetworkObject reference")]
[Tooltip("When enabled, this will register a custom INetworkPrefabInstanceHandler using a NetworkObject reference")]
public bool RegisterUsingNetworkObject;

[Tooltip("What is going to be spawned on the server side from the pool.")]
Expand Down Expand Up @@ -167,6 +167,9 @@ public override void OnNetworkSpawn()
{
m_DelaySpawning = Time.realtimeSinceStartup + InitialSpawnDelay;
StartSpawningBoxes();

//Make sure our slider reflects the current spawn rate
UpdateSpawnsPerSecond();
}
}
}
Expand Down Expand Up @@ -233,7 +236,6 @@ private GameObject AddNewInstance()
/// </summary>
private void StartSpawningBoxes()
{

if (NetworkManager.IsHost && SpawnSlider != null)
{
SpawnSlider.gameObject.SetActive(true);
Expand All @@ -246,14 +248,26 @@ private void StartSpawningBoxes()
}

/// <summary>
/// Checks to detemrine if we need to update our spawns per second calculations
/// Checks to determine if we need to update our spawns per second calculations
/// </summary>
public void UpdateSpawnsPerSecond()
{
if (SpawnSlider != null)
{
SpawnsPerSecond = (int)SpawnSlider.value;
SpawnSliderValueText.text = SpawnsPerSecond.ToString();

// Handle case where the initial value is set to zero and so coroutine needs to be started
if(SpawnsPerSecond > 0 && !m_IsSpawningObjects)
{
StartSpawningBoxes();
}
else //Handle case where spawning coroutine is running but we set our spawn rate to zero
if (SpawnsPerSecond == 0 && m_IsSpawningObjects)
{
m_IsSpawningObjects = false;
StopCoroutine(SpawnObjects());
}
}
}

Expand Down
Loading