holopy3/Assets/Normal/Realtime/View/RealtimeViewConfiguration.cs
2020-12-10 15:25:12 +01:00

313 lines
18 KiB
C#

#if UNITY_EDITOR
using System;
using System.Linq;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEditor;
namespace Normal.Realtime {
public class RealtimeViewConfiguration {
// RealtimeView Management
private static Dictionary<RealtimeView, byte[]> _rootSceneViewUUIDMap = new Dictionary<RealtimeView, byte[]>();
public static void _UpdateRealtimeViewSceneViewUUID(RealtimeView realtimeView, byte[] sceneViewUUID) {
_rootSceneViewUUIDMap[realtimeView] = sceneViewUUID;
}
public static byte[] GetSceneViewUUIDAsByteArray(SerializedProperty property) {
byte[] sceneViewUUID = new byte[property.arraySize];
for (int i = 0; i < property.arraySize; i++) {
sceneViewUUID[i] = (byte)property.GetArrayElementAtIndex(i).intValue;
}
return sceneViewUUID;
}
public static byte[] SetSceneViewUUIDUsingByteArray(SerializedProperty property, byte[] sceneViewUUID) {
property.ClearArray();
for (int i = 0; i < sceneViewUUID.Length; i++) {
property.InsertArrayElementAtIndex(i);
property.GetArrayElementAtIndex(i).intValue = sceneViewUUID[i];
}
return sceneViewUUID;
}
public static void _HierarchyDidChange() {
if (Application.isPlaying)
return;
GameObject[] sceneRootGameObjects = SceneManager.GetActiveScene().GetRootGameObjects();
Realtime[] realtimeInstances = sceneRootGameObjects.SelectMany(i => i.GetComponentsInChildren<Realtime>()).ToArray();
bool didWarnAboutRealtimeInstancesBeingTooFewOrTooMany = false;
// Automatically configure scene RealtimeViews
RealtimeView[] realtimeViews = Resources.FindObjectsOfTypeAll<RealtimeView>();
foreach (RealtimeView realtimeView in realtimeViews) {
if (realtimeView == null)
continue;
ConfigureRealtimeView(realtimeView, realtimeInstances, ref didWarnAboutRealtimeInstancesBeingTooFewOrTooMany);
}
}
public static void _ConfigureRealtimeView(RealtimeView realtimeView) {
GameObject[] sceneRootGameObjects = SceneManager.GetActiveScene().GetRootGameObjects();
Realtime[] realtimeInstances = sceneRootGameObjects.SelectMany(i => i.GetComponentsInChildren<Realtime>()).ToArray();
bool didWarnAboutRealtimeInstancesBeingTooFewOrTooMany = false;
ConfigureRealtimeView(realtimeView, realtimeInstances, ref didWarnAboutRealtimeInstancesBeingTooFewOrTooMany);
}
private static void ConfigureRealtimeView(RealtimeView realtimeView, Realtime[] realtimeInstances, ref bool didWarnAboutRealtimeInstancesBeingTooFewOrTooMany) {
SerializedObject realtimeViewSerializedObject = new SerializedObject(realtimeView);
realtimeViewSerializedObject.Update();
SerializedProperty realtimeProperty = realtimeViewSerializedObject.FindProperty("_realtime");
SerializedProperty sceneViewUUIDProperty = realtimeViewSerializedObject.FindProperty("_sceneViewUUID");
SerializedProperty isRootPrefabViewProperty = realtimeViewSerializedObject.FindProperty("_isRootPrefabView");
// Realtime Instance
Realtime realtime = realtimeProperty.objectReferenceValue as Realtime;
bool prefab = EditorUtility.IsPersistent(realtimeView.gameObject);
if (prefab) {
if (realtime != null) {
realtimeProperty.objectReferenceValue = null;
}
} else {
if (realtime == null) {
if (realtimeInstances.Length == 1) {
realtimeProperty.objectReferenceValue = realtimeInstances[0];
} else if (!didWarnAboutRealtimeInstancesBeingTooFewOrTooMany) {
if (realtimeInstances.Length == 0) {
Debug.LogWarning("RealtimeView: No instances of Realtime exist in the scene. Make sure to create an instance of Realtime otherwise this scene view will not work!");
} else if (realtimeInstances.Length > 1) {
Debug.LogWarning("RealtimeView: There are multiple instances of Realtime in the scene. If you plan to use this as a scene view, wire up a reference to Realtime manually under the Advanced Settings panel on the RealtimeView.");
}
didWarnAboutRealtimeInstancesBeingTooFewOrTooMany = true;
}
}
}
// Add realtime components
RealtimeComponent[] components = realtimeView.GetComponents<RealtimeComponent>();
foreach (RealtimeComponent component in components) {
AddComponentToViewListIfNeeded(realtimeView, component);
}
// Add to parent RealtimeView if this is a child view
bool isRoot = AddChildViewToParentViewIfNeeded(realtimeView, realtimeViewSerializedObject);
byte[] sceneViewUUID = GetSceneViewUUIDAsByteArray(sceneViewUUIDProperty);
if (isRoot && !prefab) {
// Root scene view
// Make sure this root scene view exists in our map. If it does, verify the UUID is set properly.
byte[] previouslyAssignedUUID;
if (_rootSceneViewUUIDMap.TryGetValue(realtimeView, out previouslyAssignedUUID)) {
// If previously assigned UUID doesn't match, reset it. This can happen when clicking Apply on a prefab.
// This is because the UUID gets stored on the prefab and cleared on the scene view because it inherits the value from the prefab.
// Then this script comes in and clears the UUID on the prefab, which then means the scene view will have no UUID because it's inheriting
// from the prefab. We'll detect that here and set it back on the scene view.
if (!previouslyAssignedUUID.SequenceEqual(sceneViewUUID)) {
// Reset scene view UUID
sceneViewUUID = SetSceneViewUUIDUsingByteArray(sceneViewUUIDProperty, previouslyAssignedUUID);
}
} else {
// Set scene UUID if needed
if (sceneViewUUID == null || sceneViewUUID.Length == 0) {
sceneViewUUID = SetSceneViewUUIDUsingByteArray(sceneViewUUIDProperty, Guid.NewGuid().ToByteArray());
} else {
// If this view doesn't exist in the UUID map, but it has a scene view UUID, it's possible it's been copy & pasted. Check the map to see if another scene view has the same UUID. If it does, reset it and log a warning.
foreach (KeyValuePair<RealtimeView, byte[]> viewUUIDPair in _rootSceneViewUUIDMap) {
RealtimeView view = viewUUIDPair.Key;
byte[] viewUUID = viewUUIDPair.Value;
if (sceneViewUUID.SequenceEqual(viewUUID) && realtimeView != view && realtimeView.gameObject.scene == view.gameObject.scene) {
// If we enter this block, it means there's already a realtime view with this UUID loaded, /and/ it exists in the same scene.
// As far as I know, the only way for that to happen is to copy & paste a root scene realtime view. In that case, I think it's ok to reset
// the UUID on the copy. And since the original is going to be the one that already exists in the map, that means this one is the copy.
// For root scene views that have the same UUID as a view in another scene, we'll log an error below. That can happen when a scene is saved
// as a copy, and the copy is additively loaded. In that case, we don't know which view the developer will want to keep so we log the error.
Debug.LogWarning("Realtime: Found a RealtimeView in scene with a duplicate UUID. Resetting the UUID on the copy.");
sceneViewUUID = SetSceneViewUUIDUsingByteArray(sceneViewUUIDProperty, Guid.NewGuid().ToByteArray());
break;
}
}
}
// Add to the map
_rootSceneViewUUIDMap[realtimeView] = sceneViewUUID;
}
} else {
// Not root scene view
// Clear scene UUID
if (sceneViewUUID == null || sceneViewUUID.Length != 0) {
// Clear the UUID
sceneViewUUID = SetSceneViewUUIDUsingByteArray(sceneViewUUIDProperty, new byte[0]);
// Remove from map
_rootSceneViewUUIDMap.Remove(realtimeView);
}
}
if (isRoot && prefab) {
// Root prefab view
// Set isRootPrefabView property
if (!isRootPrefabViewProperty.boolValue)
isRootPrefabViewProperty.boolValue = true;
} else {
// Not root prefab view
// Clear isRootPrefabView property
if (isRootPrefabViewProperty.boolValue)
isRootPrefabViewProperty.boolValue = false;
}
realtimeViewSerializedObject.ApplyModifiedPropertiesWithoutUndo();
}
private static void AddComponentToViewListIfNeeded(RealtimeView view, RealtimeComponent componentToAdd) {
// Add to view's components list.
SerializedObject viewSerializedObject = new SerializedObject(view);
viewSerializedObject.Update();
SerializedProperty componentsProperty = viewSerializedObject.FindProperty("_components");
int numberOfComponents = componentsProperty.arraySize;
// Check for component in the list
int largestComponentID = 0;
for (int i = 0; i < numberOfComponents; i++) {
SerializedProperty componentProperty = componentsProperty.GetArrayElementAtIndex(i);
SerializedProperty componentComponentIDProperty = componentProperty.FindPropertyRelative("componentID");
SerializedProperty componentComponentProperty = componentProperty.FindPropertyRelative("component");
MonoBehaviour component = componentComponentProperty.objectReferenceValue as MonoBehaviour;
// If the component exists, we're done.
if (component == componentToAdd)
return;
int componentID = componentComponentIDProperty.intValue;
if (componentID > largestComponentID)
largestComponentID = componentID;
}
// Not found. Add to list.
int componentIndex = numberOfComponents;
int nextAvailableComponentID = largestComponentID + 1;
componentsProperty.InsertArrayElementAtIndex(componentIndex);
SerializedProperty newComponentProperty = componentsProperty.GetArrayElementAtIndex(componentIndex);
newComponentProperty.FindPropertyRelative("componentID").intValue = nextAvailableComponentID;
newComponentProperty.FindPropertyRelative("component").objectReferenceValue = componentToAdd;
newComponentProperty.FindPropertyRelative("componentIDHasBeenUsed").boolValue = true;
viewSerializedObject.ApplyModifiedPropertiesWithoutUndo();
}
private static bool AddChildViewToParentViewIfNeeded(RealtimeView childView, SerializedObject childViewSerializedObject) {
if (childView == null) {
Debug.LogError("Attempting to add null child view to parent view. This is a bug! Bailing...");
return false;
}
// Recursively trace up to the parent
RealtimeView parentView = FindParentRealtimeViewForTransform(childView.transform);
// Remove from previous parentView and update _parentView property.
SerializedProperty parentViewSerializedProperty = childViewSerializedObject.FindProperty("_parentView");
RealtimeView oldParentView = parentViewSerializedProperty.objectReferenceValue as RealtimeView;
if (parentView != oldParentView) {
// We're going to change the parent below. Remove the child view from the old parent view's child view list.
if (oldParentView != null)
RemoveChildViewFromParentViewList(oldParentView, childView);
// Set parent property on child view
parentViewSerializedProperty.objectReferenceValue = parentView;
}
// No parent found, this is a root view.
if (parentView == null)
return true;
// Add to parent view's childViews list.
SerializedObject parentViewSerializedObject = new SerializedObject(parentView);
parentViewSerializedObject.Update();
SerializedProperty childViewsProperty = parentViewSerializedObject.FindProperty("_childViews");
int numberOfChildViews = childViewsProperty.arraySize;
// Check for child view in the list
int largestViewID = 0;
for (int i = 0; i < numberOfChildViews; i++) {
SerializedProperty childViewProperty = childViewsProperty.GetArrayElementAtIndex(i);
SerializedProperty childViewViewIDProperty = childViewProperty.FindPropertyRelative("viewID");
SerializedProperty childViewViewProperty = childViewProperty.FindPropertyRelative("view");
SerializedProperty childViewViewToUseIfMovedBackProperty = childViewProperty.FindPropertyRelative("viewToUseIfMovedBack");
RealtimeView view = childViewViewProperty.objectReferenceValue as RealtimeView;
RealtimeView viewToUseIfMovedBack = childViewViewToUseIfMovedBackProperty.objectReferenceValue as RealtimeView;
// If the view exists, we're done.
if (view == childView)
return false;
// If the view has been assigned to this property before, re-assign it and we're done.
if (viewToUseIfMovedBack == childView) {
childViewViewProperty.objectReferenceValue = childView;
parentViewSerializedProperty.objectReferenceValue = parentView;
parentViewSerializedObject.ApplyModifiedPropertiesWithoutUndo();
return false;
}
int viewID = childViewViewIDProperty.intValue;
if (viewID > largestViewID)
largestViewID = viewID;
}
// Not found. Add to list.
int viewIndex = numberOfChildViews;
int nextAvailableViewID = largestViewID + 1;
childViewsProperty.InsertArrayElementAtIndex(viewIndex);
SerializedProperty newChildViewProperty = childViewsProperty.GetArrayElementAtIndex(viewIndex);
newChildViewProperty.FindPropertyRelative("viewID").intValue = nextAvailableViewID;
newChildViewProperty.FindPropertyRelative("view").objectReferenceValue = childView;
newChildViewProperty.FindPropertyRelative("viewToUseIfMovedBack").objectReferenceValue = childView;
newChildViewProperty.FindPropertyRelative("viewIDHasBeenUsed").boolValue = true;
parentViewSerializedObject.ApplyModifiedPropertiesWithoutUndo();
return false;
}
private static RealtimeView FindParentRealtimeViewForTransform(Transform realtimeViewTransform) {
Transform parent = realtimeViewTransform.parent;
if (parent == null)
return null;
RealtimeView realtimeView = parent.GetComponent<RealtimeView>();
if (realtimeView != null)
return realtimeView;
return FindParentRealtimeViewForTransform(parent);
}
private static void RemoveChildViewFromParentViewList(RealtimeView parentView, RealtimeView childView) {
// Remove from parent view's childViews list.
SerializedObject parentViewSerializedObject = new SerializedObject(parentView);
parentViewSerializedObject.Update();
SerializedProperty childViewsProperty = parentViewSerializedObject.FindProperty("_childViews");
int numberOfChildViews = childViewsProperty.arraySize;
// Check for child view in the list
for (int i = 0; i < numberOfChildViews; i++) {
SerializedProperty childViewProperty = childViewsProperty.GetArrayElementAtIndex(i);
SerializedProperty childViewViewProperty = childViewProperty.FindPropertyRelative("view");
SerializedProperty childViewViewToUseIfMovedBackProperty = childViewProperty.FindPropertyRelative("viewToUseIfMovedBack");
RealtimeView view = childViewViewProperty.objectReferenceValue as RealtimeView;
// If the view exists, clear it out.
if (view == childView) {
// Clear view property
childViewViewProperty.objectReferenceValue = null;
// Store a reference so if the view is moved back, it gets assigned to the same View ID
childViewViewToUseIfMovedBackProperty.objectReferenceValue = childView;
}
}
parentViewSerializedObject.ApplyModifiedPropertiesWithoutUndo();
}
}
}
#endif