2018-06-07 13:41:08 +00:00
#if ENABLE_UNET
2018-06-08 08:59:32 +00:00
using System.Text ;
2018-06-07 13:41:08 +00:00
using System.Collections.Generic ;
2018-06-08 08:59:32 +00:00
using System.Linq ;
2018-06-07 13:41:08 +00:00
using UnityEditor.Callbacks ;
using UnityEngine ;
using UnityEngine.Networking ;
namespace UnityEditor
{
public class NetworkScenePostProcess : MonoBehaviour
{
2018-06-08 08:59:32 +00:00
// persistent sceneId assignment to fix readstring bug that occurs when restarting the editor and
// connecting to a build again. sceneids were then different because FindObjectsOfType's order
// is not guranteed to be the same.
// -> we need something unique and persistent, aka always the same when pressing play/building the first time
// -> Unity has no built in unique id for GameObjects in the scene
// helper function to figure out a unique, persistent scene id for a GameObject in the hierarchy
// -> Unity's instanceId is unique but not persistent
// -> hashing the whole GameObject is not enough either since a duplicate would have the same hash
// -> we definitely need the transform sibling index in the hierarchy
// -> so we might as well use just that
// -> transforms have children too so we need a list of sibling indices like 0->3->5
public static List < int > SiblingPathFor ( Transform t )
{
List < int > result = new List < int > ( ) ;
while ( t ! = null )
{
result . Add ( t . GetSiblingIndex ( ) ) ;
t = t . parent ;
}
result . Reverse ( ) ; // parent to child instead of child to parent order
return result ;
}
// we need to compare by using the whole sibling list
// comparing the string won't work work because:
// "1->2"
// "20->2"
// would compare '1' vs '2', then '-' vs '0'
//
// tests:
// CompareSiblingPaths(new List<int>(){0}, new List<int>(){0}) => 0
// CompareSiblingPaths(new List<int>(){0}, new List<int>(){1}) => -1
// CompareSiblingPaths(new List<int>(){1}, new List<int>(){0}) => 1
// CompareSiblingPaths(new List<int>(){0,1}, new List<int>(){0,2}) => -1
// CompareSiblingPaths(new List<int>(){0,2}, new List<int>(){0,1}) => 1
// CompareSiblingPaths(new List<int>(){1}, new List<int>(){0,1}) => 1
// CompareSiblingPaths(new List<int>(){1}, new List<int>(){2,1}) => -1
public static int CompareSiblingPaths ( List < int > left , List < int > right )
{
// compare [0], remove it, compare next, etc.
while ( left . Count > 0 & & right . Count > 0 )
{
if ( left [ 0 ] < right [ 0 ] )
{
return - 1 ;
}
else if ( left [ 0 ] > right [ 0 ] )
{
return 1 ;
}
else
{
// equal, so they are both children of the same transform
// -> which also means that they both must have one more
// entry, so we can remove both without checking size
left . RemoveAt ( 0 ) ;
right . RemoveAt ( 0 ) ;
}
}
// equal if both were empty or both had the same entry without any
// more children (should never happen in practice)
return 0 ;
}
public static int CompareNetworkIdentitySiblingPaths ( NetworkIdentity left , NetworkIdentity right )
{
return CompareSiblingPaths ( SiblingPathFor ( left . transform ) , SiblingPathFor ( right . transform ) ) ;
}
2018-06-07 13:41:08 +00:00
[PostProcessScene]
public static void OnPostProcessScene ( )
{
var prefabWarnings = new HashSet < string > ( ) ;
2018-06-08 08:59:32 +00:00
// vis2k: MISMATCHING SCENEID BUG FIX
// problem:
// * FindObjectsOfType order is not guaranteed. restarting the
// editor results in a different order
// * connecting to a build again would cause UNET to deserialize
// the wrong objects, causing all kinds of weird errors like
// 'ReadString out of range'
//
// solution:
// sort by sibling-index path, e.g. [0,1,2] vs [1]
// this is the only deterministic way to sort a list of objects in
// the scene.
// -> it's the same result every single time, even after restarts
//
// note: there is a reason why we 'sort by' sibling path instead of
// using it as sceneId directly. networkmanager etc. use Dont-
// DestroyOnLoad, which changes the hierarchy:
//
// World:
// NetworkManager
// Player
//
// ..becomes..
//
// World:
// Player
// DontDestroyOnLoad:
// NetworkManager
//
// so the player's siblingindex would be decreased by one.
// -> this is a problem because when building, OnPostProcessScene
// is called before any dontdestroyonload happens, but when
// entering play mode, it's called after
// -> hence sceneids would differ by one
//
// => but if we only SORT it, then it doesn't matter if one
// inbetween disappeared. as long as no NetworkIdentity used
// DontDestroyOnLoad.
//
// note: assigning a GUID in NetworkIdentity.OnValidate would be way
// cooler, but OnValidate isn't called for other unopened scenes
// when building or pressing play, so the bug would still happen
// there.
//
// note: this can still fail if DontDestroyOnLoad is called for a
// NetworkIdentity - but no one should ever do that anyway.
var uvs = FindObjectsOfType < NetworkIdentity > ( ) . ToList ( ) ;
uvs . Sort ( CompareNetworkIdentitySiblingPaths ) ;
2018-06-07 13:41:08 +00:00
int nextSceneId = 1 ;
2018-06-08 08:59:32 +00:00
foreach ( NetworkIdentity uv in uvs )
2018-06-07 13:41:08 +00:00
{
// if we had a [ConflictComponent] attribute that would be better than this check.
// also there is no context about which scene this is in.
if ( uv . GetComponent < NetworkManager > ( ) ! = null )
{
Debug . LogError ( "NetworkManager has a NetworkIdentity component. This will cause the NetworkManager object to be disabled, so it is not recommended." ) ;
}
if ( uv . isClient | | uv . isServer )
continue ;
uv . gameObject . SetActive ( false ) ;
uv . ForceSceneId ( nextSceneId + + ) ;
2018-06-08 08:59:32 +00:00
if ( LogFilter . logDebug ) { Debug . Log ( "PostProcess sceneid assigned: name=" + uv . name + " scene=" + uv . gameObject . scene . name + " sceneid=" + uv . sceneId ) ; }
2018-06-07 13:41:08 +00:00
2018-06-08 08:59:32 +00:00
// saftey check for prefabs with more than one NetworkIdentity
2018-06-07 13:41:08 +00:00
var prefabGO = UnityEditor . PrefabUtility . GetPrefabParent ( uv . gameObject ) as GameObject ;
if ( prefabGO )
{
var prefabRootGO = UnityEditor . PrefabUtility . FindPrefabRoot ( prefabGO ) ;
if ( prefabRootGO )
{
var identities = prefabRootGO . GetComponentsInChildren < NetworkIdentity > ( ) ;
if ( identities . Length > 1 & & ! prefabWarnings . Contains ( prefabRootGO . name ) )
{
// make sure we only print one error per prefab
prefabWarnings . Add ( prefabRootGO . name ) ;
Debug . LogWarningFormat ( "Prefab '{0}' has several NetworkIdentity components attached to itself or its children, this is not supported." , prefabRootGO . name ) ;
}
}
}
}
}
}
}
#endif //ENABLE_UNET