/// <summary> /// Update components in GameObject, creating them if necessary. /// </summary> /// <param name="request"></param> private void UpdateComponents(Request request) { // Find a node of the correct gameobject for the request to use, creating it if it doesn't exist yet. ObjectNode node = this.injector.objectStore.GetOrCreate(request.name); // Update the components foreach (AbstractComponent serializedComponent in request.components) { // Get component type Type type = this.injector.serializer.GetCommonType(serializedComponent); // Check if type is not null if (type != null) { // If component is not allowed to receive update, then drop request if (!RuleUtility.FindComponentRule(this.injector, node.gameObject, type, UpdateType.RECEIVE)) { continue; } // Get or create the component Component component = node.GetOrCreateComponent(type); // Try to apply updates; if successful, request is fulfilled if (serializedComponent.Apply(this.injector, component)) { node.UpdateComponent(type); LogUtility.Log(this.injector, LogType.INFORMATION, "Component " + type + " update sucessful"); continue; } // Component creation could not add values in this update // Create Request to try in a later update Request newRequest = Request.UpdateComponents(this.injector, request.name, new List <AbstractComponent>() { serializedComponent }); newRequest.channel = request.channel; // Save channel information // If this request was handled less times than allowed, then try in an later update loop if (request.iteration < Model.ATTEMPTS_TO_UPDATE) { // Integrate the iterations of the old request newRequest.iteration = request.iteration + 1; nextRequestQueue.Enqueue(newRequest); LogUtility.Log(this.injector, LogType.INFORMATION, "Component " + type + " update unsucessful because missing references"); } // Else, report failed resource update else { LogUtility.Log(this.injector, LogType.WARNING, "Request to update component " + type + " for " + request.name + " dropped"); } } } }
/// <summary> /// Update content of Resource, creating it if necessary. /// </summary> /// <param name="request"></param> private void UpdateResource(Request request) { // Get Unity Type of Resource Type commonType = this.injector.serializer.ToCommonType(request.resourceType); // If resource is not allowed to receive update, then drop request if (!RuleUtility.FindResourceRule(this.injector, commonType, UpdateType.RECEIVE)) { return; } ResourceNode node; if (this.injector.resourceStore.TryGet(request.name, commonType, out node)) { // If Resource exists, try to apply updates. If successful, request is fulfilled. if (request.resource.Apply(this.injector, node.resource)) { node.UpdateHash(); LogUtility.Log(this.injector, LogType.INFORMATION, "Resource update sucessful"); return; } } else { // If Resource does not exist, create it UnityEngine.Object resource = request.resource.Construct(); resource.name = request.name; // Try to apply updates; if successful, request is fulfilled if (request.resource.Apply(this.injector, resource)) { node = this.injector.resourceStore.Add(request.name, resource); node.UpdateHash(); LogUtility.Log(this.injector, LogType.INFORMATION, "Resource " + commonType + " update sucessful"); return; } } // Resource creation was not successful in this update // If this request was handled less times than allowed, then try in an later update loop if (request.iteration < Model.ATTEMPTS_TO_UPDATE) { request.iteration++; nextRequestQueue.Enqueue(request); LogUtility.Log(this.injector, LogType.INFORMATION, "Resource " + commonType + " update unsucessful because of missing references"); } // Else, report failed resource update else { LogUtility.Log(this.injector, LogType.WARNING, "Request to update resource " + commonType + " for " + request.name + " dropped"); } }
/// <summary> /// Method to initialize subscriptions (afterwards channels cannot be changed) /// </summary> internal void Initialize() { this.sendChannels = this.injector.configuration.SENDCHANNELS; this.receiveChannels = this.injector.configuration.RECEIVECHANNELS; this.EnableSend(); this.EnableReceive(); this.isInitialized = true; LogUtility.Log(this.injector, LogType.INFORMATION, "Successful subscribed to channels"); }
/// <summary> /// Handler for new Requests from the server. Simply enqueues them to be applied during the next ApplyChanges(). /// </summary> /// <param name="json"></param> internal void ReceiveRequest(string json) { if (this.injector.configuration.DEBUGRECEIVE) { LogUtility.Log("Received message " + json); } // Remove empty parameter json = json.Replace(",\"string1\":\"\"", ""); json = json.Replace(",\"string2\":\"\"", ""); this.injector.model.AddRequest(JsonUtility.FromJson <Request>(json)); }
/// <summary> /// OnDestory method from MonoBehaviour /// </summary> void OnDestroy() { // Close websocket connection if (this.injector != null) { Connection connection = this.injector.connection; if (connection != null) { connection.CloseAsync(); } } LogUtility.Log(this.injector, LogType.INFORMATION, "Successfully disconnected from server"); }
/// <summary> /// Asynchronously send Request to the server /// </summary> /// <param name="request"></param> internal void SendRequest(Request request) { // Send request to every channel foreach (string channel in this.injector.subscriptions.sendChannelList) { // Set channel of request request.channel = channel; // Refresh timestamp request.RefreshTimestamp(); // Send request string json = JsonUtility.ToJson(request); if (this.injector.configuration.DEBUGSEND) { LogUtility.Log("Sent message " + json); } this.SendAsync(json, null); } }
/// <summary> /// Checks whether connection is alive and tries to connect if it isn't. Then returns whether the connection is alive /// </summary> /// <returns></returns> internal bool EnsureIsAlive() { // If the websocket is not alive try to connect to the server if (!this.IsAlive) { this.connectingAttempt = true; // Check if last attempt to connect is at least one Reconnect Period ago if (Time.realtimeSinceStartup > this.lastAttempt + this.injector.configuration.RECONNECT) { LogUtility.Log(this.injector, LogType.INFORMATION, "Attempt to connect to server"); try { // Connect to the server this.ConnectAsync(); this.lastAttempt = Time.realtimeSinceStartup; } catch (Exception) { LogUtility.Log(this.injector, LogType.ERROR, "Unable to connect to server"); } } } else { // If there was a recent connecting attempt, it was successful if (this.connectingAttempt) { this.connectingAttempt = false; LogUtility.Log(this.injector, LogType.INFORMATION, "Successfully connected to server"); } } // Return if websocket is alive return(this.IsAlive); }
/// <summary> /// Check synchronized part of scene for changes in GameObjects, Components or Resources. /// </summary> internal void TrackChanges() { // Create lists of resources and components that have pending requests List <NameTypePair> blockedResources = new List <NameTypePair>(); List <NameTypePair> blockedComponents = new List <NameTypePair>(); // Check pending requests for components and resources foreach (Request request in requestQueue.ToArray()) { switch (request.messageType) { // Check if request is pending for resource case Request.RESOURCE: if (request.resource != null) { blockedResources.Add(new NameTypePair(request.name, request.resource.commonType)); } else if (request.resourceType != null) { blockedResources.Add(new NameTypePair(request.name, request.resourceType)); } break; // Check if request is pending for component case Request.COMPONENT: if (request.components != null) { // Component requests can contain multiple components foreach (AbstractComponent component in request.components) { blockedComponents.Add(new NameTypePair(request.name, component.commonType)); } } else if (request.componentTypes != null) { // Component requests can contain multiple components foreach (Type type in request.componentTypes) { blockedComponents.Add(new NameTypePair(request.name, type)); } } break; } } // Add untracked GameObjects to ObjectStore as Objects without Components foreach (Transform transform in this.gameObject.GetComponentsInChildren <Transform>()) { // Make sure that GameObject with NetworkModel attached does not get synced to Server if (transform != this.gameObject.transform) { // Get existing reference or create a unique reference for GameObject transform.gameObject.name = this.injector.objectStore.GetReferenceName(transform.gameObject); // Check if GameObject is already in ObjectStore if (!this.injector.objectStore.ContainsKey(transform.gameObject.name)) { this.injector.objectStore.Add(transform.gameObject); } } } // List of GameObjects that have been deleted on this client List <string> objectsToDelete = new List <string>(); // Iterate over GameObject Store foreach (KeyValuePair <string, ObjectNode> objectEntry in this.injector.objectStore) { // Never synchronize the root gameobject containing the NetworkModel string referenceName = objectEntry.Key; if (referenceName == Model.ROOT_NAME) { continue; } // If Element does not exist anymore (or the name was changed), then add it to the list to schedule the deletion // (When changing the name of a GameObject, the ObjectNode of that GameOBject gets destroyed and a new ObjectNode is created) ObjectNode node = objectEntry.Value; if (node.gameObject == null || node.gameObject.name != referenceName) { objectsToDelete.Add(referenceName); } // Else, check for changes in hierarchy and components else { // Check if parent of gameObject has changed compared to last known state if (node.gameObject.transform.parent.GetInstanceID() != node.parentID) { // Send updated parent information to server this.injector.connection.SendRequest(Request.UpdateObject(this.injector, node.gameObject)); node.gameObject.transform.hasChanged = false; } // Update Node with current values from represented GameObject node.Update(); // List of Components that have been deleted on this client List <Type> deletedComponents = new List <Type>(); // List of Components that have been deleted and inform the server about it List <Type> sendDeletedComponents = new List <Type>(); // List of Components that have been changed on this client List <AbstractComponent> updatedComponents = new List <AbstractComponent>(); // List of Components that have been changed and inform the server about it List <AbstractComponent> sendUpdatedComponents = new List <AbstractComponent>(); // Iterate over tracked components of the node foreach (KeyValuePair <Type, long> hashEntry in node.hashes) { // If component is blocked from tracking changes, then continue with next component if (blockedComponents.Contains(new NameTypePair(node.gameObject.name, hashEntry.Key))) { LogUtility.Log(this.injector, LogType.INFORMATION, "Component " + hashEntry.Key + " update blocked"); continue; } // Get the component which is pointed at in this loop Component component = node.gameObject.GetComponent(hashEntry.Key); // If component is allowed to send, then track changes bool send = RuleUtility.FindComponentRule(this.injector, node.gameObject, component.GetType(), UpdateType.SEND); // If Component does not exist anymore, then schedule it for deletion if (component == null) { // Delete references to component on client Type type = this.injector.serializer.ToSerializableType(hashEntry.Key); deletedComponents.Add(type); // Inform the server about the change if (send) { sendDeletedComponents.Add(type); } continue; } // Transform UnityEngine Component to NetworkModel AbstractComponent AbstractComponent serializedComponent = this.injector.serializer.ToSerializableComponent(component); // If Component does not exist as a serializable Component if (serializedComponent == null) { continue; } // Generate hash of component and compare it to the previously stored value; if not equal, then add it to update list if (serializedComponent.GetHash() != hashEntry.Value) { // Update hash stored for component on client updatedComponents.Add(serializedComponent); // Inform the server about the change if (send) { sendUpdatedComponents.Add(serializedComponent); } } } // Components scheduled for Delete foreach (Type type in deletedComponents) { node.RemoveComponent(this.injector.serializer.ToCommonType(type)); } // Components scheduled for Update foreach (AbstractComponent component in updatedComponents) { node.UpdateComponent(component.commonType); } // Check if there are deleted components the server needs to be informed about if (sendDeletedComponents.Count > 0) { this.injector.connection.SendRequest(Request.DeleteComponents(this.injector, referenceName, sendDeletedComponents)); } // Check if there are updated components the server needs to be informed about if (sendUpdatedComponents.Count > 0) { this.injector.connection.SendRequest(Request.UpdateComponents(this.injector, referenceName, sendUpdatedComponents)); node.gameObject.transform.hasChanged = false; } } } // Delete scheduled GameObjects and send Delete Request foreach (string referenceName in objectsToDelete) { this.injector.objectStore.Remove(referenceName); this.injector.connection.SendRequest(Request.DeleteObject(this.injector, referenceName)); } // List of Resources that have been deleted on this client List <string> resourcesToDelete = new List <string>(); // Iterate over Resource Store for (int i = 0; i < this.injector.resourceStore.Count; i++) { // Get the resource which is pointed at in this loop ResourceNode node = (ResourceNode)this.injector.resourceStore[i]; string resourceName = node.name; // If resource name is null, then check next resource if (resourceName == "null") { continue; } // If resource is blocked from tracking changes, then continue with next resource if (blockedResources.Contains(new NameTypePair(resourceName, node.type))) { LogUtility.Log(this.injector, LogType.INFORMATION, "Resource " + node.type + " update blocked"); continue; } // If resource is allowed to send, then track changes bool send = RuleUtility.FindResourceRule(this.injector, node.type, UpdateType.SEND); // If Element does not exist anymore, then schedule it for deletion if (node.resource == null || node.resource.name != resourceName || node.resource.GetType() != node.type) { // Delete references to resource on client resourcesToDelete.Add(resourceName); // Inform the server about the change if (send) { this.injector.connection.SendRequest(Request.DeleteResource(this.injector, resourceName)); } } // Else, check for changes else { // Transform UnityEngine Resource to NetworkModel AbstractResource AbstractResource serializedResource = this.injector.serializer.ToSerializableResource(node.resource); if (serializedResource.GetHash() != node.hash) { // Inform the server about the change if (send) { this.injector.connection.SendRequest(Request.UpdateResource(this.injector, resourceName, this.injector.serializer.ToSerializableType(node.type), serializedResource)); } // Update hash stored for resource on client node.UpdateHash(); } } } // Delete resources scheduled for deleting from ResourceStore foreach (string resourceName in resourcesToDelete) { this.injector.resourceStore.Remove(resourceName); } }
/// <summary> /// Function to find valid rule for Component from GameObject /// </summary> /// <param name="injector"></param> /// <param name="gameObject"></param> /// <param name="type"></param> /// <param name="updateType"></param> /// <returns></returns> internal static bool FindComponentRule(Injector injector, GameObject gameObject, Type type, UpdateType updateType) { // Find relevant rule Transform pointer = gameObject.transform; do { // See if current GameObject has a NetworkModel Rule component NetworkModelRule networkModelRule = pointer.GetComponent <NetworkModelRule>(); // If true, then a rule was found if (networkModelRule != null) { // Check if the Rule was found in the passed GameObject if (gameObject.transform == pointer) { // Check if the Rule is active for the GameObject it is attached to if (networkModelRule.applyToObject) { // Get the correct Rule RuleType ruleType = CheckComponentRule(networkModelRule, type, updateType); // Check, if Rule is enabled if (ruleType != RuleType.DISABLED) { return(ruleType.ToBool()); } } else { // Rule does not apply, continue with parent GameObject pointer = pointer.parent; continue; } } // ELse, the Rule was found in a parent GameObject else { // Check if the Rule is active for the GameObject it is attached to if (networkModelRule.applyToChildren) { // Get the correct Rule RuleType ruleType = CheckComponentRule(networkModelRule, type, updateType); // Check, if Rule is enabled if (ruleType != RuleType.DISABLED) { return(ruleType.ToBool()); } } else { // Rule does not apply, continue with parent GameObject pointer = pointer.parent; continue; } } } // Go one GameObject up in Hierarchy pointer = pointer.parent; } while (pointer != null); LogUtility.Log(injector, LogType.INFORMATION, "No active rule found for Type " + type); // Default value return(RuleUtility.DEFAULT_RULE); }