Skip to content

fix: soft synchronization issues can be avoided if object creation and destruction messages are held until player has completely loaded scene#531

Closed
NoelStephensUnity wants to merge 71 commits intorelease/0.1.0from
fix/softsynch
Closed

fix: soft synchronization issues can be avoided if object creation and destruction messages are held until player has completely loaded scene#531
NoelStephensUnity wants to merge 71 commits intorelease/0.1.0from
fix/softsynch

Conversation

@NoelStephensUnity
Copy link
Copy Markdown
Member

This is a fix for soft synchronization issues when a client is transitioning to a new or existing in game session.
What this fix does:

  • All internal MLAPI create object or destroy object commands are held in the queue for a specified client while the client is loading a scene. Upon the client notifying the server (or host) that it has finished loading the scene, all create or destroy commands held in the queue are sent to the client.
  • Any queue items remaining in the internal mlapi queue (within the RPC queue framework) for longer than 60 seconds will be dropped (assuming a client disconnected or dropped during the scene loading process)
  • Network Manager automated spawning now sends a create object command via the queue as opposed to being added to the existing "scene state" binary stream sent to the player when a scene transition occurs (or when they are newly joining an existing game). This helps to prevent issues with objects being created on the client side while it is loading.
  • Removed the PostProcessing hack-fix to return if the application was running as this is no longer needed.

Other than some additional comments and clean up, that is the summary of this PR.

0xFA11 and others added 30 commits February 5, 2021 04:20
This fixes two issues:
It prevents the postprocessing method ProcessScene from being run during scene transitions while in PlayMode within the editor.

This fixes the issue where the transport is no longer being polled for events upon shutting down the server,host, or client.
This prevents this post processing code from being executed when in PlayMode (editor and stand alone build)
This ended up not being needed in the end.
Reverting back to the original file.
Removing the commented out OnDisable function (the containing code was moved into the Shutdown method) and removed the Awake method (the containing code was moved into the Init method).
This hadn't fully registered the merge.
Just getting the footprint ready and made some adjustments to the ground and wall materials.
WIP files for the scene transition samples.
Refactoring some comments
Copy link
Copy Markdown
Contributor

@0xFA11 0xFA11 left a comment

Choose a reason for hiding this comment

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

I think we should have 2 separate PRs for MLAPI changes and testproject changes.
There are so many things going under this PR and make it much harder to review.

public static void ProcessScene()
{
//If we are in playmode (editor or stand alone) we do not want this to execute
if (Application.isPlaying)
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.

I wonder why the code before skipped out in this case. Any recollections @TwoTenPvP ?

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.

This has been added as a fix by Noel but with the new better fix isn't necessary anymore I think.

/// </summary>
public class NetworkClient
{
public bool IsClientDoneLoadingScene;
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.

Please comment public's

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.

+1 to Matt's point.
I'd also suggest naming it as IsLoadingScene too (and reverse the flag being false by default).

Fixing a merge artifact...
if(createPlayerObject)
{
//Provide meaningful feedback to developers that this happened.
NetworkLog.LogWarning("NetworkManager configured to spawn player prefab but no prefab is defined or there is a problem with the prefab hash.");
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.

This is interesting. Is this different behavior than now, where if you don't have your default player spawn it crashes? Now you get a warning?


//Reset our observed objects list
//[NSS]: This assures we have an up to date list of all observed objects
m_ObservedObjects.Clear();
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.

I wonder why m_ObservedObjects is not a local to HandleApproval? It's only used in HA, and only in the approved state, and in that state, it is unconditionally cleared, then built up and read from.

Due to name changes, the default player prefab was removed from the NetworkManager.
@0xFA11
Copy link
Copy Markdown
Contributor

0xFA11 commented Mar 6, 2021

I also wonder how this relates to #493
Looks like we're unfixing a fix as a fix that went in very quickly in the first place :)

Comment on lines -14 to -18
//If we are in playmode (editor or stand alone) we do not want this to execute
if (Application.isPlaying)
{
return;
}
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.

can this change go as an "unfix" for #493 on itself in isolation?

Comment on lines +1304 to +1310
if (PendingClients.ContainsKey(clientId))
PendingClients.Remove(clientId);
NetworkClient client = new NetworkClient()
{
IsClientDoneLoadingScene = false,
ClientId = clientId,
};
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
if (PendingClients.ContainsKey(clientId))
PendingClients.Remove(clientId);
NetworkClient client = new NetworkClient()
{
IsClientDoneLoadingScene = false,
ClientId = clientId,
};
if (PendingClients.ContainsKey(clientId))
{
PendingClients.Remove(clientId);
}
var client = new NetworkClient
{
IsLoadingScene = true,
ClientId = clientId,
};

}

//Create the new player from the assigned prefab
NetworkObject netObject = NetworkSpawnManager.CreateLocalNetworkObject(false, 0, (playerPrefabHash == null ? NetworkConfig.PlayerPrefabHash.Value : playerPrefabHash.Value), null, position, rotation);
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
NetworkObject netObject = NetworkSpawnManager.CreateLocalNetworkObject(false, 0, (playerPrefabHash == null ? NetworkConfig.PlayerPrefabHash.Value : playerPrefabHash.Value), null, position, rotation);
var networkObject = NetworkSpawnManager.CreateLocalNetworkObject(false, 0, (playerPrefabHash == null ? NetworkConfig.PlayerPrefabHash.Value : playerPrefabHash.Value), null, position, rotation);

Comment on lines -71 to +74
/// GetStreamBufferFrameCount
/// Returns how many frames have been processed (Inbound/Outbound)
/// </summary>
/// <param name="queueType"></param>
/// <returns>number of frames procssed</returns>
public uint GetStreamBufferFrameCount(RpcQueueHistoryFrame.QueueFrameType queueType)
{
return queueType == RpcQueueHistoryFrame.QueueFrameType.Inbound ? m_InboundFramesProcessed : m_OutboundFramesProcessed;
}

/// <summary>
/// AddToInternalMLAPISendQueue
/// NSS-TODO: This will need to be removed once we determine how we want to handle specific
/// internal MLAPI commands relative to RPCS.
/// Example: An network object is destroyed via server side (internal mlapi) command, but prior to this several RPCs are invoked for the to be destroyed object (Client RPC)
/// If both the DestroyObject internal mlapi command and the ClientRPCs are received in the same frame but the internal mlapi DestroyObject command is processed prior to the
/// RPCs being invoked then the object won't exist and additional warnings will be logged that the object no longer exists.
/// The vices versa scenario (create and then RPCs sent) is an unlikely/improbable scenario, but just in case added the CreateObject to this special case scenario.
///
/// To avoid the DestroyObject scenario, the internal MLAPI commands (DestroyObject and CreateObject) are always invoked after RPCs.
/// </summary>
/// <param name="queueItem">item to add to the internal MLAPI queue</param>
public void AddToInternalMLAPISendQueue(RpcFrameQueueItem queueItem)
internal void AddToInternalMLAPISendQueue(RpcFrameQueueItem queueItem)
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.

changes in this file are looking more like refactorings, should probably go into another isolated PR (and maybe not even targeting release branch IMHO)

Comment on lines +40 to +44
//In case there is no Singleton, then just exit
if(!NetworkManager.Singleton)
{
return;
}
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.

why? if there is no NetworkManager.Singleton, we are probably in trouble already, right?

return;
}

bool AdvanceFrameHistory = false;
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.

I believe local variables should be "camelCase" as it used to be...

Suggested change
bool AdvanceFrameHistory = false;
bool advanceFrameHistory = false;

Comment on lines -88 to +97
public void ProcessSendQueue()
internal void ProcessSendQueue()
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.

why all these public to internal changes? the class itself is already internal, there is no extra protection coming from all these public to internal changes anyway?

{
var PoolStream = queueItem.NetworkBuffer;
if (NetworkManager.Singleton.IsListening)
List<RpcFrameQueueItem> CompletedRpcQueueItems = new List<RpcFrameQueueItem>();
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
List<RpcFrameQueueItem> CompletedRpcQueueItems = new List<RpcFrameQueueItem>();
var completedRpcQueueItems = new List<RpcFrameQueueItem>();

{
switch (queueItem.QueueItemType)
var clientIds = new List<ulong>(queueItem.ClientNetworkIds);
var PoolStream = queueItem.NetworkBuffer;
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
var PoolStream = queueItem.NetworkBuffer;
var poolStream = queueItem.NetworkBuffer;

internal void InternalMessagesSendAndFlush()
{
foreach (RpcFrameQueueItem queueItem in m_InternalMLAPISendQueue)
if(NetworkManager.Singleton && NetworkManager.Singleton.IsListening)
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.

again, similar to above, I'm not sure if this check is really needed. maybe IsListening but not sure if Singleton check is required here. also, why we're checking if we're IsListening here? shouldn't the call-site of InternalMessagesSendAndFlush method do that check first before calling this method in the first place? I have questions... :P

Comment on lines -163 to +197
var advanceFrameHistory = false;
//In case there is no Singleton, then just exit
if(!NetworkManager.Singleton)
{
return;
}

var AdvanceFrameHistory = false;
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.

the same pattern followed here, see my comments above ^^^

Comment on lines +320 to +321
//Right before the server sends the switch scene message, it marks the client it is sending the message to as having not loaded the scene being switched to
NetworkManager.Singleton.ConnectedClientsList[j].IsClientDoneLoadingScene = false;
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.

huh? why? what's going on here? :D

Comment on lines -1 to +2
m_EditorVersion: 2019.4.17f1
m_EditorVersionWithRevision: 2019.4.17f1 (667c8606c536)
m_EditorVersion: 2019.4.20f1
m_EditorVersionWithRevision: 2019.4.20f1 (6dd1c08eedfa)
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.

this change is unnecessary!

Comment on lines -1 to +47
"dependencies": {
"com.unity.collab-proxy": "1.2.16",
"com.unity.ide.rider": "1.1.4",
"com.unity.ide.vscode": "1.2.3",
"com.unity.multiplayer.mlapi": "file:../../com.unity.multiplayer.mlapi",
"com.unity.package-validation-suite": "0.19.2-preview",
"com.unity.test-framework": "1.1.19",
"com.unity.textmeshpro": "2.1.1",
"com.unity.timeline": "1.2.17",
"com.unity.ugui": "1.0.0",
"com.unity.modules.ai": "1.0.0",
"com.unity.modules.androidjni": "1.0.0",
"com.unity.modules.animation": "1.0.0",
"com.unity.modules.assetbundle": "1.0.0",
"com.unity.modules.audio": "1.0.0",
"com.unity.modules.cloth": "1.0.0",
"com.unity.modules.director": "1.0.0",
"com.unity.modules.imageconversion": "1.0.0",
"com.unity.modules.imgui": "1.0.0",
"com.unity.modules.jsonserialize": "1.0.0",
"com.unity.modules.particlesystem": "1.0.0",
"com.unity.modules.physics": "1.0.0",
"com.unity.modules.physics2d": "1.0.0",
"com.unity.modules.screencapture": "1.0.0",
"com.unity.modules.terrain": "1.0.0",
"com.unity.modules.terrainphysics": "1.0.0",
"com.unity.modules.tilemap": "1.0.0",
"com.unity.modules.ui": "1.0.0",
"com.unity.modules.uielements": "1.0.0",
"com.unity.modules.umbra": "1.0.0",
"com.unity.modules.unityanalytics": "1.0.0",
"com.unity.modules.unitywebrequest": "1.0.0",
"com.unity.modules.unitywebrequestassetbundle": "1.0.0",
"com.unity.modules.unitywebrequestaudio": "1.0.0",
"com.unity.modules.unitywebrequesttexture": "1.0.0",
"com.unity.modules.unitywebrequestwww": "1.0.0",
"com.unity.modules.vehicles": "1.0.0",
"com.unity.modules.video": "1.0.0",
"com.unity.modules.vr": "1.0.0",
"com.unity.modules.wind": "1.0.0",
"com.unity.modules.xr": "1.0.0"
},
"testables": [
"com.unity.multiplayer.mlapi"
]
}

No newline at end of file
"dependencies": {
"com.unity.collab-proxy": "1.2.16",
"com.unity.ide.rider": "1.1.4",
"com.unity.ide.vscode": "1.2.3",
"com.unity.multiplayer.mlapi": "file:../../com.unity.multiplayer.mlapi",
"com.unity.package-validation-suite": "0.19.2-preview",
"com.unity.test-framework": "1.1.22",
"com.unity.textmeshpro": "2.1.1",
"com.unity.timeline": "1.2.17",
"com.unity.ugui": "1.0.0",
"com.unity.modules.ai": "1.0.0",
"com.unity.modules.androidjni": "1.0.0",
"com.unity.modules.animation": "1.0.0",
"com.unity.modules.assetbundle": "1.0.0",
"com.unity.modules.audio": "1.0.0",
"com.unity.modules.cloth": "1.0.0",
"com.unity.modules.director": "1.0.0",
"com.unity.modules.imageconversion": "1.0.0",
"com.unity.modules.imgui": "1.0.0",
"com.unity.modules.jsonserialize": "1.0.0",
"com.unity.modules.particlesystem": "1.0.0",
"com.unity.modules.physics": "1.0.0",
"com.unity.modules.physics2d": "1.0.0",
"com.unity.modules.screencapture": "1.0.0",
"com.unity.modules.terrain": "1.0.0",
"com.unity.modules.terrainphysics": "1.0.0",
"com.unity.modules.tilemap": "1.0.0",
"com.unity.modules.ui": "1.0.0",
"com.unity.modules.uielements": "1.0.0",
"com.unity.modules.umbra": "1.0.0",
"com.unity.modules.unityanalytics": "1.0.0",
"com.unity.modules.unitywebrequest": "1.0.0",
"com.unity.modules.unitywebrequestassetbundle": "1.0.0",
"com.unity.modules.unitywebrequestaudio": "1.0.0",
"com.unity.modules.unitywebrequesttexture": "1.0.0",
"com.unity.modules.unitywebrequestwww": "1.0.0",
"com.unity.modules.vehicles": "1.0.0",
"com.unity.modules.video": "1.0.0",
"com.unity.modules.vr": "1.0.0",
"com.unity.modules.wind": "1.0.0",
"com.unity.modules.xr": "1.0.0"
},
"testables": [
"com.unity.multiplayer.mlapi"
]
}
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.

this is probably due to the engine version upgrade, please leave this change out...

Comment on lines +96 to +104
public String GetSceneNameLinkedToState(GlobalGameState.GameStates gameState, int currentindex = 0)
{
var results = m_StateToSceneList.Where(entry => entry.stateToLoadScene == gameState);
if (results != null && ( (results.Count() - 1) >= currentindex) )
{
return results.ElementAt(currentindex).SceneToLoad;
}
return String.Empty;
}
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
public String GetSceneNameLinkedToState(GlobalGameState.GameStates gameState, int currentindex = 0)
{
var results = m_StateToSceneList.Where(entry => entry.stateToLoadScene == gameState);
if (results != null && ( (results.Count() - 1) >= currentindex) )
{
return results.ElementAt(currentindex).SceneToLoad;
}
return String.Empty;
}
public string GetSceneNameLinkedToState(GlobalGameState.GameStates gameState, int currentindex = 0)
{
var results = m_StateToSceneList.Where(entry => entry.stateToLoadScene == gameState);
if (results != null && ((results.Count() - 1) >= currentindex))
{
return results.ElementAt(currentindex).SceneToLoad;
}
return string.Empty;
}

@NoelStephensUnity
Copy link
Copy Markdown
Member Author

Closing this fix because it needs to be refactored to only fix the soft synch issue when:

  • a host-server is already in a game session
  • the host-server is sending the MLAPI layer ADD_OBJECT command frequently enough to send when a newly joining client is still loading the scene.
  • the fix should only include the code base that will keep those commands in the queue for the client still loading a scene
  • upon the client signaling to the server that it is done loading the scene, the server will flush the pending commands to the client
  • The scene transitioning and global game state sample should not be part of this PR
  • The scene transitioning and global game state sample needs to be refactored to not use scene management or automated player spawning due to the more recent issues discovered.

@NoelStephensUnity NoelStephensUnity deleted the fix/softsynch branch April 16, 2021 15:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants