Skip to content

Commit

Permalink
update
Browse files Browse the repository at this point in the history
Split apart the NetworkObjectRefreshTool from NetworkObject.
Made some updates that don't require any form of editor application update.
Added script in NetworkObject.RefreshAllPrefabInstances context menu method that handles refreshing the currently active scene and all enabled scenes in the build list.
  • Loading branch information
NoelStephensUnity committed Sep 20, 2023
1 parent 570f3ba commit 906fad3
Show file tree
Hide file tree
Showing 5 changed files with 213 additions and 126 deletions.
153 changes: 27 additions & 126 deletions com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
#if UNITY_2021_2_OR_NEWER
using UnityEditor.SceneManagement;
#else
using UnityEditor.SceneManagement;
using UnityEditor.Experimental.SceneManagement;
#endif
#endif
Expand All @@ -16,107 +15,6 @@

namespace Unity.Netcode
{

#if UNITY_EDITOR
/// <summary>
/// This would only need to be here if we have automatic updating of in-scene placed network prefab
/// instances upon loading a scene in the editor.
/// </summary>
/// <remarks>
/// This is not in editor assembly since NetworkObject needs access to this class.
/// TODO: Migrate class into its own file
/// </remarks>
internal class NetworkObjectManagement
{

[InitializeOnLoadMethod]
internal static void OnLoad()
{
// Assure no double subscriptions
EditorApplication.update -= OnEditorUpdate;
// Subscribe to editor updates
EditorApplication.update += OnEditorUpdate;
}

internal enum SceneDirtyStates
{
None,
Mark,
Marked,
Save,
Saving,
Saved
}

internal static SceneDirtyStates SceneDirtyState;

private static float s_MarkedDelay;

internal static Scene TargetScene;

// TODO: If auto-save scenes with GlobalObjectIdHash updates is disabled then
// EditorApplication.update would not be subscribed to
internal static void OnEditorUpdate()
{
if (SceneDirtyState == SceneDirtyStates.None)
{
return;
}
switch (SceneDirtyState)
{
case SceneDirtyStates.Mark:
{
if (EditorSceneManager.MarkSceneDirty(TargetScene))
{
// Just provide a small delay to allow the scene to become recognized as dirty
s_MarkedDelay = Time.realtimeSinceStartup + 0.1f;
SceneDirtyState = SceneDirtyStates.Marked;

// TODO: Remove logging before making full PR
Debug.Log($"[{TargetScene.name}] marked as dirty!");
}
break;
}
case SceneDirtyStates.Marked:
{
if (s_MarkedDelay < Time.realtimeSinceStartup)
{
SceneDirtyState = SceneDirtyStates.Save;
// TODO: Remove logging before making full PR
Debug.Log($"[{TargetScene.name}] Saving scene...");
}
break;
}
case SceneDirtyStates.Save:
{
s_MarkedDelay = 0.0f;
EditorSceneManager.sceneSaved += SceneSaved;
SceneDirtyState = SceneDirtyStates.Saving;
if (!EditorSceneManager.SaveScene(TargetScene))
{
// TODO: Show dialog to user regarding the failure to save the scene
Debug.LogError($"[{TargetScene.name}] Failed to save scene!");
}
break;
}
case SceneDirtyStates.Saved:
{
// TODO: Remove logging before making full PR
Debug.Log($"[{TargetScene.name}] Scene saved!");
SceneDirtyState = SceneDirtyStates.None;
break;
}
}
}

private static void SceneSaved(Scene scene)
{
EditorSceneManager.sceneSaved -= SceneSaved;
SceneDirtyState = SceneDirtyStates.Saved;
}
}
#endif

/// <summary>
/// A component used to identify that a GameObject in the network
/// </summary>
Expand Down Expand Up @@ -149,14 +47,35 @@ public uint PrefabIdHash
}

#if UNITY_EDITOR
private const string k_GlobalIdTemplate = "GlobalObjectId_V1-{0}-{1}-{2}-{3}";

[ContextMenu("Update All In-Scene Placed Instances")]
private void UpdateAllPrefabInstances()
[ContextMenu("Refresh In-Scene Prefab Instances")]
private void RefreshAllPrefabInstances()
{
Debug.Log("TODO: Store the currently active scene, open all scenes within the scenes in build list, update all GlobalObjectIdHash values.");
}
// Handle updating the currently active scene
var networkObjects = FindObjectsByType<NetworkObject>(FindObjectsInactive.Include, FindObjectsSortMode.None);
foreach (var networkObject in networkObjects)
{
networkObject.OnValidate();
}
NetworkObjectRefreshTool.ProcessActiveScene();

private const string k_GlobalIdTemplate = "GlobalObjectId_V1-{0}-{1}-{2}-{3}";
// Refresh all build settings scenes
var activeScene = SceneManager.GetActiveScene();
foreach (var editorScene in EditorBuildSettings.scenes)
{
// skip disabled scenes and the currently active scene
if (!editorScene.enabled || activeScene.path == editorScene.path)
{
continue;
}
// Add the scene to be processed
NetworkObjectRefreshTool.ProcessScene(editorScene.path, false);
}

// Process all added scenes
NetworkObjectRefreshTool.ProcessScenes();
}

private void OnValidate()
{
Expand Down Expand Up @@ -200,12 +119,6 @@ internal void GenerateGlobalObjectIdHash()
// Check if this is an in-scnee placed NetworkObject (Special Case for In-Scene Placed)
if (!IsEditingPrefab() && gameObject.scene.name != null && gameObject.scene.name != gameObject.name)
{
// TODO: Remove before making full PR
if (gameObject.name.Contains("TestGlobalObjectIdHash"))
{
Debug.Log($"[{gameObject.name}] Did not save its GlobalObjectIdHash value!");
}

// Sanity check to make sure this is a scene placed object
if (globalId.identifierType != 2)
{
Expand All @@ -220,27 +133,15 @@ internal void GenerateGlobalObjectIdHash()
PrefabUtility.RecordPrefabInstancePropertyModifications(this);
}

// TODO: This will be dependent upon an NGO project setting and/or a context menu initiated action
// This is just a temporary way to validate the POC of the approach
NetworkObjectManagement.SceneDirtyState = NetworkObjectManagement.SceneDirtyStates.Mark;
NetworkObjectManagement.TargetScene = gameObject.scene;
NetworkObjectRefreshTool.ProcessScene(gameObject.scene.path);
}
else // Otherwise, this is a standard network prefab asset so we just mark it dirty for the AssetDatabase to update it
{
EditorUtility.SetDirty(this);
}
}
else
{
// TODO: Remove before making full PR
if (gameObject.name.Contains("TestGlobalObjectIdHash"))
{
Debug.Log($"[{gameObject.name}] GlobalObjectIdHash {GlobalObjectIdHash}!");
}
}
}


private bool IsEditingPrefab()
{
// Check if we are directly editing the prefab
Expand Down
114 changes: 114 additions & 0 deletions com.unity.netcode.gameobjects/Runtime/Core/NetworkObjectRefreshTool.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
#if UNITY_EDITOR
using System.Collections.Generic;
using System.Linq;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;

namespace Unity.Netcode
{
/// <summary>
/// This is a helper tool to update all in-scene placed instances of a prefab that
/// originally did not have a NetworkObject component but one was added to the prefab
/// later.
/// </summary>
internal class NetworkObjectRefreshTool
{
private static List<string> s_ScenesToUpdate = new List<string>();
private static bool s_ProcessScenes;
private static bool s_CloseScenes;

internal static void ProcessScene(string scenePath, bool processScenes = true)
{
if (!s_ScenesToUpdate.Contains(scenePath))
{
if (s_ScenesToUpdate.Count == 0)
{
EditorSceneManager.sceneOpened += EditorSceneManager_sceneOpened;
EditorSceneManager.sceneSaved += EditorSceneManager_sceneSaved;
}
s_ScenesToUpdate.Add(scenePath);
}
s_ProcessScenes = processScenes;
}

internal static void ProcessActiveScene()
{
var activeScene = SceneManager.GetActiveScene();
if (s_ScenesToUpdate.Contains(activeScene.path) && s_ProcessScenes)
{
SceneOpened(activeScene);
}
}

internal static void ProcessScenes()
{
if (s_ScenesToUpdate.Count != 0)
{
s_CloseScenes = true;
var scenePath = s_ScenesToUpdate.First();
EditorSceneManager.OpenScene(scenePath, OpenSceneMode.Additive);
}
else
{
s_CloseScenes = false;
EditorSceneManager.sceneSaved -= EditorSceneManager_sceneSaved;
EditorSceneManager.sceneOpened -= EditorSceneManager_sceneOpened;
}
}

private static void FinishedProcessingScene(Scene scene, bool refreshed = false)
{
if (s_ScenesToUpdate.Contains(scene.path))
{
// Provide a log of all scenes that were modified to the user
if (refreshed)
{
Debug.Log($"Refreshed and saved updates to scene: {scene.name}");
}
s_ProcessScenes = false;
s_ScenesToUpdate.Remove(scene.path);

if (scene != SceneManager.GetActiveScene())
{
EditorSceneManager.CloseScene(scene, s_CloseScenes);
}
ProcessScenes();
}
}

private static void EditorSceneManager_sceneSaved(Scene scene)
{
FinishedProcessingScene(scene, true);
}

private static void SceneOpened(Scene scene)
{
if (s_ScenesToUpdate.Contains(scene.path))
{
if (s_ProcessScenes)
{
if (!EditorSceneManager.MarkSceneDirty(scene))
{
Debug.Log($"Scene {scene.name} did not get marked as dirty!");
FinishedProcessingScene(scene);
}
else
{
EditorSceneManager.SaveScene(scene);
}
}
else
{
FinishedProcessingScene(scene);
}
}
}

private static void EditorSceneManager_sceneOpened(Scene scene, OpenSceneMode mode)
{
SceneOpened(scene);
}
}
}
#endif // UNITY_EDITOR

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &1445163809825623502
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 4895178628090191772}
- component: {fileID: 7314688500250972154}
m_Layer: 0
m_Name: MyNetworkObject
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &4895178628090191772
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1445163809825623502}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &7314688500250972154
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1445163809825623502}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3}
m_Name:
m_EditorClassIdentifier:
GlobalObjectIdHash: 0
AlwaysReplicateAsRoot: 0
SynchronizeTransform: 1
ActiveSceneSynchronization: 0
SceneMigrationSynchronization: 1
SpawnWithObservers: 1
DontDestroyWithOwner: 0
AutoObjectParentSync: 1

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 906fad3

Please sign in to comment.