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

452 lines
22 KiB
C#

using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
using UnityEngine.Serialization;
using Normal.Realtime;
using Normal.Realtime.Serialization;
using Normal.Utility;
namespace Normal.Realtime {
[DisallowMultipleComponent]
public class RealtimeView : MonoBehaviour {
[SerializeField]
private Realtime _realtime;
public Realtime realtime { get { return _realtime; } }
public int ownerID { get { return _model.ownerID; } set { _model.ownerID = value; } }
public uint lifetimeFlags { get { return _model.lifetimeFlags; } set { _model.lifetimeFlags = value; } }
public bool isOwnedLocally { get { return _model.ownerID == realtime.clientID; } }
public bool isOwnedByWorld { get { return _model.ownerID == -1; } }
#pragma warning disable 0649 // Disable variable is never assigned to warning.
[SerializeField]
private byte[] _sceneViewUUID = { };
public byte[] sceneViewUUID { get { return _sceneViewUUID; } }
public bool isRootSceneView { get { return _sceneViewUUID.Length != 0; } }
[SerializeField]
private bool _sceneViewOwnedByCreatingClient = true;
[SerializeField]
private bool _sceneViewPreventOwnershipTakeover = false;
[SerializeField]
private bool _sceneViewDestroyWhenOwnerOrLastClientLeaves = true;
[SerializeField]
private bool _isRootPrefabView;
public bool isRootPrefabView { get { return _isRootPrefabView; } }
public bool isChildView { get { return !isRootSceneView && !isRootPrefabView; } }
private RealtimeViewModel _model;
public RealtimeViewModel model { get { return _model; } set { SetModel(value); } }
[Serializable]
public class RealtimeViewIDComponentPair {
[FormerlySerializedAs("propertyID")]
public int componentID;
public MonoBehaviour component;
public bool componentIDHasBeenUsed;
}
[FormerlySerializedAs("_properties")]
[SerializeField]
private RealtimeViewIDComponentPair[] _components;
#if UNITY_EDITOR
[SerializeField]
private RealtimeView _parentView;
#endif
[Serializable]
public class RealtimeViewChildIDViewPair {
public int viewID;
public RealtimeView view;
public bool viewIDHasBeenUsed;
public RealtimeView viewToUseIfMovedBack; // If a RealtimeView is moved to a child view, then moved back, we use this property to make sure it's assigned to its original viewID.
}
[SerializeField]
private RealtimeViewChildIDViewPair[] _childViews;
#pragma warning restore 0649
private void Start() {
// If this is a root scene view, register with Realtime
if (isRootSceneView) {
if (_realtime == null) {
// This can happen if this RealtimeView is a scene view in a scene that's meant to be additively loaded onto a scene that has a Realtime instance in it. Attempt to auto-detect.
if (Realtime.instances.Count == 1) {
foreach (Realtime instance in Realtime.instances) {
if (instance == null) {
Debug.LogError("RealtimeView: Realtime.instances contains a null value. This is a bug!");
}
_realtime = instance;
break;
}
} else if (Realtime.instances.Count == 0) {
Debug.LogError("RealtimeView: Attempting to auto-detect Realtime instance, but none exist in the scene yet. Please make sure there's an instance of Realtime in the scene.");
} else if (Realtime.instances.Count > 1) {
Debug.LogError("RealtimeView: Attempting to auto-detect Realtime instance, but multiple instances of Realtime exist in the scene. Please wire up a reference to Realtime manually in Advanced Settings on each scene RealtimeView.");
}
}
if (_realtime != null)
_realtime._RegisterSceneRealtimeView(this);
}
}
private void OnDestroy() {
// If this is a root scene view, unregister with Realtime
if (isRootSceneView) {
if (_realtime != null)
_realtime._UnregisterSceneRealtimeView(this);
}
}
private void Reset() {
#if UNITY_EDITOR
RealtimeViewConfiguration._ConfigureRealtimeView(this);
#endif
}
// Used to populate _realtime for prefab RealtimeViews
public void _SetRealtime(Realtime realtime) {
_realtime = realtime;
}
public RealtimeViewModel _CreateRootSceneViewModel() {
if (!isRootSceneView) {
Debug.LogError("RealtimeView: Asked to create root scene view model for view that's not a root scene view. This is a bug!");
}
// Create components model
Dictionary<int, Tuple<Component, MethodInfo, PropertyInfo, PropertyInfo>> componentMap = CreateComponentMap();
RealtimeViewComponentsModel componentsModel = CreateComponentsModel(componentMap);
// Create child views model
Dictionary<int, RealtimeView> childViewsMap = CreateChildViewMap();
RealtimeViewComponentsModel childViewsModel = CreateChildViewsModel(childViewsMap);
// Ownership / lifetime flags
int ownerID = _sceneViewOwnedByCreatingClient ? _realtime.clientID : -1;
uint lifetimeFlags = 0;
if (_sceneViewPreventOwnershipTakeover)
lifetimeFlags |= (uint)MetaModel.LifetimeFlags.PreventOwnershipTakeover;
if (_sceneViewDestroyWhenOwnerOrLastClientLeaves)
lifetimeFlags |= (uint)MetaModel.LifetimeFlags.DestroyWhenOwnerOrLastClientLeaves;
// Create RealtimeViewModel
RealtimeViewModel viewModel = new RealtimeViewModel(_sceneViewUUID, ownerID, lifetimeFlags, componentsModel, childViewsModel);
return viewModel;
}
public RealtimeViewModel _CreateRootPrefabViewModel(string prefabName, int ownerID, uint lifetimeFlags) {
if (!isRootPrefabView) {
Debug.LogError("RealtimeView: Asked to create root prefab view model for view that's not on the root of a prefab. This is a bug!");
}
// Create components model
Dictionary<int, Tuple<Component, MethodInfo, PropertyInfo, PropertyInfo>> componentMap = CreateComponentMap();
RealtimeViewComponentsModel componentsModel = CreateComponentsModel(componentMap);
// Create child views model
Dictionary<int, RealtimeView> childViewsMap = CreateChildViewMap();
RealtimeViewComponentsModel childViewsModel = CreateChildViewsModel(childViewsMap);
// Create RealtimeViewModel
RealtimeViewModel viewModel = new RealtimeViewModel(prefabName, ownerID, lifetimeFlags, componentsModel, childViewsModel);
return viewModel;
}
public RealtimeViewModel _CreateChildViewModel() {
if (!isChildView) {
Debug.LogError("RealtimeView: Asked to create child view model for view that's not a child view. This is a bug!");
}
// Create components model
Dictionary<int, Tuple<Component, MethodInfo, PropertyInfo, PropertyInfo>> componentMap = CreateComponentMap();
RealtimeViewComponentsModel componentsModel = CreateComponentsModel(componentMap);
// Create child views model
Dictionary<int, RealtimeView> childViewsMap = CreateChildViewMap();
RealtimeViewComponentsModel childViewsModel = CreateChildViewsModel(childViewsMap);
// Create RealtimeViewModel
RealtimeViewModel viewModel = new RealtimeViewModel(componentsModel, childViewsModel);
return viewModel;
}
public RealtimeViewComponentsModel _CreateComponentsModel() {
// Get component map
Dictionary<int, Tuple<Component, MethodInfo, PropertyInfo, PropertyInfo>> componentMap = CreateComponentMap();
return CreateComponentsModel(componentMap);
}
public RealtimeViewComponentsModel _CreateChildViewsModel() {
// Get child views map
Dictionary<int, RealtimeView> childViewMap = CreateChildViewMap();
return CreateChildViewsModel(childViewMap);
}
private RealtimeViewComponentsModel CreateComponentsModel(Dictionary<int, Tuple<Component, MethodInfo, PropertyInfo, PropertyInfo>> componentMap) {
// Create models for all components
Dictionary<int, IModel> componentModelMap = new Dictionary<int, IModel>();
foreach (KeyValuePair<int, Tuple<Component, MethodInfo, PropertyInfo, PropertyInfo>> pair in componentMap) {
int componentID = pair.Key;
Component component = pair.Value.First;
MethodInfo createModelMethod = pair.Value.Second;
PropertyInfo modelProperty = pair.Value.Third;
// Create model
object componentModelObject = null;
if (createModelMethod != null) {
componentModelObject = createModelMethod.Invoke(null, null);
if (componentModelObject == null) {
Debug.LogError("Model supplied by MonoBehaviour's CreateModel method is null. Skipping component: (" + componentID + ":" + component + ")", component);
continue;
}
} else {
// Create it using the type from the model property.
Type modelType = modelProperty.PropertyType;
try {
componentModelObject = Activator.CreateInstance(modelType);
} catch (MissingMethodException) {
Debug.LogError("MonoBehaviour doesn't have CreateModel method, and model type (" + modelType + ") doesn't have a public default constructor. Skipping component: (" + componentID + ":" + component + ")", component);
continue;
} catch (Exception exception) {
Debug.LogError("MonoBehaviour doesn't have CreateModel method, and Realtime was unable to create a model instance. Skipping component: (" + componentID + ":" + component + ") (" + exception + ")", component);
continue;
}
}
// Verify model implements IModel
IModel componentModel = componentModelObject as IModel;
if (componentModel == null) {
Debug.LogError("Model created by MonoBehaviour (" + componentModelObject.GetType() + ") doesn't implement IModel interface. Skipping component: (" + componentID + ":" + component + ")", component);
continue;
}
// Set model
componentModelMap[componentID] = componentModel;
}
// Create RealtimeViewComponentsModel
RealtimeViewComponentsModel componentsModel = new RealtimeViewComponentsModel(componentModelMap);
return componentsModel;
}
private Dictionary<int, Tuple<Component, MethodInfo, PropertyInfo, PropertyInfo>> CreateComponentMap() {
Dictionary<int, Tuple<Component, MethodInfo, PropertyInfo, PropertyInfo>> componentMap = new Dictionary<int, Tuple<Component, MethodInfo, PropertyInfo, PropertyInfo>>();
// Loop through all components, ignore invalid ones.
foreach (RealtimeViewIDComponentPair pair in _components) {
int componentID = pair.componentID;
MonoBehaviour component = pair.component;
// Check for valid component ID (greater than zero)
// TODO: We need to verify the upper bound limit on componentIDs too
if (componentID <= 0) {
Debug.LogError("RealtimeView components must have a componentID of 1 or greater. Skipping component: (" + componentID + ":" + component + ")", component);
continue;
}
// Make sure component is valid
if (component == null) {
// Note: Deprecated component IDs will have a null view property. This is normal and they can be safely ignored.
continue;
}
// Get CreateModel method if it exists
MethodInfo createModelMethod = component.GetType().GetMethod("CreateModel", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
// Get model property
PropertyInfo modelProperty = component.GetType().GetProperty("model", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (modelProperty == null) {
Debug.LogError("MonoBehaviour doesn't have a \"model\" property. Skipping component: (" + componentID + ":" + component + ")", component);
continue;
}
// Get realtimeView property
PropertyInfo realtimeViewProperty = component.GetType().GetProperty("realtimeView", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (realtimeViewProperty != null)
realtimeViewProperty = realtimeViewProperty.DeclaringType.GetProperty("realtimeView", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
// Check for duplicate
if (componentMap.Remove(componentID)) {
Debug.LogError("Found duplicate componentID (" + componentID + "). Skipping both components to avoid data corruption.", component);
continue;
}
componentMap.Add(componentID, new Tuple<Component, MethodInfo, PropertyInfo, PropertyInfo>(component, createModelMethod, modelProperty, realtimeViewProperty));
}
return componentMap;
}
private RealtimeViewComponentsModel CreateChildViewsModel(Dictionary<int, RealtimeView> childViewMap) {
// Create models for all views
Dictionary<int, IModel> childViewModelMap = new Dictionary<int, IModel>();
foreach (KeyValuePair<int, RealtimeView> pair in childViewMap) {
int viewID = pair.Key;
RealtimeView view = pair.Value;
if (view == null) {
// Note: Deprecated view IDs will have a null view property. This is normal and they can be safely ignored.
continue;
}
// Create model
RealtimeViewModel viewModel = view._CreateChildViewModel();
if (viewModel == null) {
Debug.LogError("Model supplied by child RealtimeView is null. Skipping view: (" + viewID + ":" + view + ")", view);
continue;
}
// Set model
childViewModelMap[viewID] = viewModel;
}
// Create RealtimeViewComponentsModel
RealtimeViewComponentsModel childViewsModel = new RealtimeViewComponentsModel(childViewModelMap);
return childViewsModel;
}
private Dictionary<int, RealtimeView> CreateChildViewMap() {
Dictionary<int, RealtimeView> childViewMap = new Dictionary<int, RealtimeView>();
// Loop through all child views, ignore invalid ones.
foreach (RealtimeViewChildIDViewPair pair in _childViews) {
int viewID = pair.viewID;
RealtimeView view = pair.view;
// Check for valid view ID (greater than zero)
// TODO: We need to verify the upper bound limit on viewIDs too
if (viewID <= 0) {
Debug.LogError("RealtimeView child views must have an ID of 1 or greater. Skipping view: (" + viewID + ":" + view + ")", view);
continue;
}
// Check for duplicate
if (childViewMap.Remove(viewID)) {
Debug.LogError("Found duplicate child view ID (" + viewID + "). Skipping both views to avoid data corruption.", view);
continue;
}
childViewMap.Add(viewID, view);
}
return childViewMap;
}
private void SetModel(RealtimeViewModel model) {
if (model == _model)
return;
if (_model != null) {
_model._SetRealtimeView(null);
}
_model = model;
if (_model != null) {
_model._SetRealtimeView(this);
Dictionary<int, Tuple<Component, MethodInfo, PropertyInfo, PropertyInfo>> componentMap = CreateComponentMap();
// Create components model if needed (can happen if RealtimeViewModel existed in the datastore but hasn't been linked to a RealtimeView yet)
RealtimeViewComponentsModel componentsModel = _model.componentsModel;
if (_model.componentsModel == null) {
componentsModel = CreateComponentsModel(componentMap);
_model.SetComponentsModelAndDeserializeCachedModelsIfNeeded(componentsModel);
}
// Loop through components and assign models
foreach (KeyValuePair<int, Tuple<Component, MethodInfo, PropertyInfo, PropertyInfo>> pair in componentMap) {
int componentID = pair.Key;
Component component = pair.Value.First;
MethodInfo createModelMethod = pair.Value.Second;
PropertyInfo modelProperty = pair.Value.Third;
PropertyInfo realtimeViewProperty = pair.Value.Fourth;
IModel componentModel = componentsModel[componentID];
if (componentModel == null) {
Debug.LogError("RealtimeView is attempting to connect a component to its model, but cannot find model for component: (" + componentID + ":" + component + "). This is a bug!", component);
continue;
}
// Set realtime view reference on object if it supports it
if (realtimeViewProperty != null)
realtimeViewProperty.SetValue(component, this, null);
// Set model on component
modelProperty.SetValue(component, componentModel, null);
}
Dictionary<int, RealtimeView> childViewMap = CreateChildViewMap();
// Create child views model if needed (can happen if RealtimeViewModel existed in the datastore but hasn't been linked to a RealtimeView yet)
RealtimeViewComponentsModel childViewsModel = _model.childViewsModel;
if (childViewsModel == null) {
childViewsModel = CreateChildViewsModel(childViewMap);
_model.SetChildViewsModelAndDeserializeCachedModelsIfNeeded(childViewsModel);
}
// Loop through child views and assign models
foreach (KeyValuePair<int, RealtimeView> pair in childViewMap) {
int viewID = pair.Key;
RealtimeView view = pair.Value;
RealtimeViewModel viewModel = childViewsModel[viewID] as RealtimeViewModel;
if (viewModel == null) {
Debug.LogError("RealtimeView attempting to connect child view to its models, but cannot find model for view: (" + viewID + ":" + view + "). This is a bug!", view);
continue;
}
// Set realtime instance and model
view._SetRealtime(_realtime);
view.model = viewModel;
}
}
}
public void RequestOwnership() {
_model.RequestOwnership(realtime.clientID);
}
public void ClearOwnership() {
_model.ClearOwnership();
}
private class Tuple<T1, T2, T3> {
public T1 First { get; private set; }
public T2 Second { get; private set; }
public T3 Third { get; private set; }
public Tuple(T1 first, T2 second, T3 third) {
First = first;
Second = second;
Third = third;
}
}
private class Tuple<T1, T2, T3, T4> {
public T1 First { get; private set; }
public T2 Second { get; private set; }
public T3 Third { get; private set; }
public T4 Fourth { get; private set; }
public Tuple(T1 first, T2 second, T3 third, T4 fourth) {
First = first;
Second = second;
Third = third;
Fourth = fourth;
}
}
}
}