/// <summary> /// Handler for the event ChangedAttribute event in the Entity. Update local sync info for the attribute and /// invokes changedAttributes method on the connected sync nodes to notify them about the change. /// </summary> /// <param name="sender">Event sender.</param> /// <param name="e">Event arguments.</param> private void HandleLocalChangedAttribute(object sender, ChangedAttributeEventArgs e) { var componentName = e.Component.Name; var attributeName = e.AttributeName; // Ignore this change if it was caused by the scalability plugin itself. lock (remoteAttributeChanges) { foreach (RemoteAttributeChange change in remoteAttributeChanges) { if (change.Entity == e.Entity && change.ComponentName == componentName && change.AttributeName == attributeName && change.Value == e.NewValue) { remoteAttributeChanges.Remove(change); return; } } } var newAttributeSyncInfo = new AttributeSyncInfo(LocalSyncID, e.NewValue); lock (localSyncInfo) { if (!localSyncInfo.ContainsKey(e.Entity.Guid)) { logger.Warn("Local attribute changed in an entity which has no sync info."); return; } EntitySyncInfo entitySyncInfo = localSyncInfo[e.Entity.Guid]; entitySyncInfo[componentName][attributeName] = newAttributeSyncInfo; } // TODO: Optimization: we can send batch updates to improve performance. Received code is written to // process batches already, but sending is trickier as we don't have network load feedback from KIARA yet. var changedAttributes = new EntitySyncInfo(); changedAttributes[componentName][attributeName] = newAttributeSyncInfo; lock (remoteSyncNodes) { foreach (Connection connection in remoteSyncNodes) { connection["changeAttributes"](e.Entity.Guid, changedAttributes); } } }
/// <summary> /// Synchronizes the values of attributes given a remote sync info. /// </summary> /// <param name="remoteAttrSyncInfo">Remote sync info.</param> /// <returns>True if the local attribute sync info has been updated.</returns> public bool Sync(AttributeSyncInfo remoteAttrSyncInfo) { if (remoteAttrSyncInfo.LastTimestamp < LastTimestamp) return false; // Equality sign in "<=" below is very important, because it ensures that we don't have circulating // updates. Should an update return back to the sender it will be discarded as it will have same timestamp // and same SyncID. if (remoteAttrSyncInfo.LastTimestamp == LastTimestamp && remoteAttrSyncInfo.LastSyncID.CompareTo(LastSyncID) <= 0) return false; LastValue = remoteAttrSyncInfo.LastValue; LastTimestamp = remoteAttrSyncInfo.LastTimestamp; LastSyncID = remoteAttrSyncInfo.LastSyncID; return true; }
/// <summary> /// Handles an update to a single attribute. /// </summary> /// <param name="localEntity">Entity containing attribute.</param> /// <param name="componentName">Name of the component containing attribute.</param> /// <param name="attributeName">Name of the attribute.</param> /// <param name="remoteAttributeSyncInfo">Remote sync info on this attribute.</param> /// <returns>True if the update should be propagated to other sync nodes.</returns> private bool HandleRemoteChangedAttribute(Entity localEntity, string componentName, string attributeName, AttributeSyncInfo remoteAttributeSyncInfo) { EntitySyncInfo localEntitySyncInfo = localSyncInfo[localEntity.Guid]; bool shouldUpdateLocalAttribute = false; if (!localEntitySyncInfo.Components.ContainsKey(componentName) || !localEntitySyncInfo[componentName].Attributes.ContainsKey(attributeName)) { shouldUpdateLocalAttribute = true; localEntitySyncInfo[componentName][attributeName] = remoteAttributeSyncInfo; } else { AttributeSyncInfo localAttributeSyncInfo = localEntitySyncInfo[componentName][attributeName]; shouldUpdateLocalAttribute = (localAttributeSyncInfo.LastValue == null && remoteAttributeSyncInfo.LastValue != null) || (localAttributeSyncInfo.LastValue != null && !localAttributeSyncInfo.LastValue.Equals(remoteAttributeSyncInfo.LastValue)); if (!localAttributeSyncInfo.Sync(remoteAttributeSyncInfo)) { return(false); // ignore this update because sync discarded it } } if (shouldUpdateLocalAttribute) { try { // This is necessary, because Json.NET serializes primitive types using basic JSON values, which do // not retain original type (e.g. integer values always become int even if they were stored as // float values before) and there is no way to change this. var attributeType = localEntity[componentName].Definition[attributeName].Type; var attributeValue = Convert.ChangeType(remoteAttributeSyncInfo.LastValue, attributeType); // Ignore event for this change. lock (remoteAttributeChanges) { var remoteChange = new RemoteAttributeChange { Entity = localEntity, ComponentName = componentName, AttributeName = attributeName, Value = attributeValue }; remoteAttributeChanges.Add(remoteChange); } localEntity[componentName][attributeName] = attributeValue; return(true); } catch (ComponentAccessException) { // This is fine, because we may have some plugins not loaded on this node. } } // If we are here, it means that this update was not discarded by sync and thus should be propagated to // other nodes. This is still the case even if the value has been the same and local attribute has not been // update, because we still need to update sync info on other nodes. return(true); }