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> componentMap = CreateComponentMap(); RealtimeViewComponentsModel componentsModel = CreateComponentsModel(componentMap); // Create child views model Dictionary 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> componentMap = CreateComponentMap(); RealtimeViewComponentsModel componentsModel = CreateComponentsModel(componentMap); // Create child views model Dictionary 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> componentMap = CreateComponentMap(); RealtimeViewComponentsModel componentsModel = CreateComponentsModel(componentMap); // Create child views model Dictionary childViewsMap = CreateChildViewMap(); RealtimeViewComponentsModel childViewsModel = CreateChildViewsModel(childViewsMap); // Create RealtimeViewModel RealtimeViewModel viewModel = new RealtimeViewModel(componentsModel, childViewsModel); return viewModel; } public RealtimeViewComponentsModel _CreateComponentsModel() { // Get component map Dictionary> componentMap = CreateComponentMap(); return CreateComponentsModel(componentMap); } public RealtimeViewComponentsModel _CreateChildViewsModel() { // Get child views map Dictionary childViewMap = CreateChildViewMap(); return CreateChildViewsModel(childViewMap); } private RealtimeViewComponentsModel CreateComponentsModel(Dictionary> componentMap) { // Create models for all components Dictionary componentModelMap = new Dictionary(); foreach (KeyValuePair> 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> CreateComponentMap() { Dictionary> componentMap = new Dictionary>(); // 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, createModelMethod, modelProperty, realtimeViewProperty)); } return componentMap; } private RealtimeViewComponentsModel CreateChildViewsModel(Dictionary childViewMap) { // Create models for all views Dictionary childViewModelMap = new Dictionary(); foreach (KeyValuePair 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 CreateChildViewMap() { Dictionary childViewMap = new Dictionary(); // 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> 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> 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 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 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 { 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 { 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; } } } }