/// <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 = syncInfo[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 = remoteAttributeSyncInfo.LastValue; // Ignore event for this change. lock (ignoredAttributeChanges) { ignoredAttributeChanges.Add(new AttributeUpdate(localEntity.Guid, componentName, attributeName, attributeValue)); } localEntity[componentName][attributeName].Suggest(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); }
public void ShouldChangeAttributesOnUpdate() { var entity = new Entity(); World.Instance.Add(entity); var changedAttributes = new EntitySyncInfo(); changedAttributes["test"]["a"] = new AttributeSyncInfo(Guid.NewGuid().ToString(), 99); var worldSync = new WorldSync(); worldSync.HandleRemoteChangedAttributes(remoteConnectionMock.Object, entity.Guid.ToString(), changedAttributes); Assert.AreEqual(entity["test"]["a"].Value, 99); }
public void ShouldNotSendUpdatesWhenTheyResultFromRemoteUpdate() { var changedAttributes = new EntitySyncInfo(); changedAttributes["test"]["a"] = new AttributeSyncInfo(Guid.NewGuid().ToString(), 99); var entity = new Entity(); var worldSync = new WorldSync(); worldSync.HandleRemoteAddedEntity(remoteConnectionMock.Object, entity.Guid.ToString(), entity.Owner.ToString(), new EntitySyncInfo()); worldSync.HandleRemoteChangedAttributes(remoteConnectionMock.Object, entity.Guid.ToString(), new EntitySyncInfo()); worldSync.HandleRemoteRemovedEntity(remoteConnectionMock.Object, entity.Guid.ToString()); handlers.Verify(h => h.AddEntity(entity.Guid.ToString(), It.IsAny <EntitySyncInfo>()), Times.Never()); handlers.Verify(h => h.ChangeAttributes(entity.Guid.ToString(), It.IsAny <EntitySyncInfo>()), Times.Never()); handlers.Verify(h => h.RemoveEntity(entity.Guid.ToString()), Times.Never()); }
public void ShouldCorrectlyResolveSyncConflicts() { var guid1 = Guid.Parse("38d6f8c5-ded5-405a-9f7d-80ebd36d7a26"); var guid2 = Guid.Parse("e0021595-ead4-4a70-825b-749175c0d9b9"); var info1 = new AttributeSyncInfo(guid2, 1); var info2 = new AttributeSyncInfo(guid2, 2); var info3 = new AttributeSyncInfo(guid1, 3); info3.LastTimestamp = info1.LastTimestamp; Assert.IsFalse(info2.Sync(info1)); Assert.AreEqual(2, info2.LastValue); Assert.IsTrue(info3.Sync(info1)); Assert.AreEqual(1, info3.LastValue); }
/// <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> internal 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 (ignoredAttributeChanges) { var attributeUpdate = new AttributeUpdate(e.Entity.Guid, componentName, attributeName, e.NewValue); if (ignoredAttributeChanges.Remove(attributeUpdate)) { return; } } var newAttributeSyncInfo = new AttributeSyncInfo(ServerSync.LocalServer.SyncID.ToString(), e.NewValue); lock (syncInfo) { if (!syncInfo.ContainsKey(e.Entity.Guid)) { logger.Warn("Local attribute changed in an entity which has no sync info."); return; } EntitySyncInfo entitySyncInfo = syncInfo[e.Entity.Guid]; entitySyncInfo[componentName][attributeName] = newAttributeSyncInfo; } var changedAttributes = new EntitySyncInfo(); changedAttributes[componentName][attributeName] = newAttributeSyncInfo; foreach (IRemoteServer server in ServerSync.RemoteServers) { if (server.DoI.IsInterestedInAttributeChange(e.Entity, e.Component.Name, e.AttributeName)) { server.Connection["serverSync.changeAttributes"](e.Entity.Guid.ToString(), changedAttributes); } } }
public void ShouldForwardUpdatesToServersOtherThanTheSource() { var otherConnectionMock = new Mock <Connection>(); var handlers2 = new Mock <IHandlers>(); otherConnectionMock.Setup(rc => rc.GenerateClientFunction("serverSync", "addEntity")) .Returns((ClientFunction)handlers2.Object.AddEntity); otherConnectionMock.Setup(rc => rc.GenerateClientFunction("serverSync", "removeEntity")) .Returns((ClientFunction)handlers2.Object.RemoveEntity); otherConnectionMock.Setup(rc => rc.GenerateClientFunction("serverSync", "changeAttributes")) .Returns((ClientFunction)handlers2.Object.ChangeAttributes); var guid = Guid.NewGuid(); RemoteServerImpl remoteServer2 = new RemoteServerImpl(otherConnectionMock.Object, doiMock.Object, dorMock.Object, guid); serverSyncMock.Setup(ss => ss.RemoteServers).Returns( new List <IRemoteServer> { remoteServer, remoteServer2 }); var changedAttributes = new EntitySyncInfo(); changedAttributes["test"]["a"] = new AttributeSyncInfo(Guid.NewGuid().ToString(), 99); var entity = new Entity(); var worldSync = new WorldSync(); worldSync.HandleRemoteAddedEntity(remoteConnectionMock.Object, entity.Guid.ToString(), entity.Owner.ToString(), new EntitySyncInfo()); worldSync.HandleRemoteChangedAttributes(remoteConnectionMock.Object, entity.Guid.ToString(), changedAttributes); worldSync.HandleRemoteRemovedEntity(remoteConnectionMock.Object, entity.Guid.ToString()); handlers.Verify(h => h.AddEntity(entity.Guid.ToString(), It.IsAny <EntitySyncInfo>()), Times.Never()); handlers.Verify(h => h.ChangeAttributes(entity.Guid.ToString(), It.IsAny <EntitySyncInfo>()), Times.Never()); handlers.Verify(h => h.RemoveEntity(entity.Guid.ToString()), Times.Never()); handlers2.Verify(h => h.AddEntity(entity.Guid.ToString(), entity.Owner.ToString(), It.IsAny <EntitySyncInfo>()), Times.Once()); handlers2.Verify(h => h.ChangeAttributes(entity.Guid.ToString(), It.IsAny <EntitySyncInfo>()), Times.Once()); handlers2.Verify(h => h.RemoveEntity(entity.Guid.ToString()), Times.Once()); }
/// <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 = syncInfo[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 (ignoredAttributeChanges) { ignoredAttributeChanges.Add(new AttributeUpdate(localEntity.Guid, componentName, attributeName, attributeValue)); } localEntity[componentName][attributeName].Suggest(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; }
/// <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> internal 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 (ignoredAttributeChanges) { var attributeUpdate = new AttributeUpdate(e.Entity.Guid, componentName, attributeName, e.NewValue); if (ignoredAttributeChanges.Remove(attributeUpdate)) return; } var newAttributeSyncInfo = new AttributeSyncInfo(ServerSync.LocalServer.SyncID, e.NewValue); lock (syncInfo) { if (!syncInfo.ContainsKey(e.Entity.Guid)) { logger.Warn("Local attribute changed in an entity which has no sync info."); return; } EntitySyncInfo entitySyncInfo = syncInfo[e.Entity.Guid]; entitySyncInfo[componentName][attributeName] = newAttributeSyncInfo; } var changedAttributes = new EntitySyncInfo(); changedAttributes[componentName][attributeName] = newAttributeSyncInfo; foreach (IRemoteServer server in ServerSync.RemoteServers) if (server.DoI.IsInterestedInAttributeChange(e.Entity, e.Component.Name, e.AttributeName)) server.Connection["serverSync.changeAttributes"](e.Entity.Guid, changedAttributes); }
public void ShouldNotSendUpdatesWhenTheyResultFromRemoteUpdate() { var changedAttributes = new EntitySyncInfo(); changedAttributes["test"]["a"] = new AttributeSyncInfo(Guid.NewGuid(), 99); var entity = new Entity(); var worldSync = new WorldSync(); worldSync.HandleRemoteAddedEntity(remoteConnectionMock.Object, entity.Guid, entity.Owner, new EntitySyncInfo()); worldSync.HandleRemoteChangedAttributes(remoteConnectionMock.Object, entity.Guid, new EntitySyncInfo()); worldSync.HandleRemoteRemovedEntity(remoteConnectionMock.Object, entity.Guid); handlers.Verify(h => h.AddEntity(entity.Guid, It.IsAny<EntitySyncInfo>()), Times.Never()); handlers.Verify(h => h.ChangeAttributes(entity.Guid, It.IsAny<EntitySyncInfo>()), Times.Never()); handlers.Verify(h => h.RemoveEntity(entity.Guid), Times.Never()); }
public void ShouldForwardUpdatesToServersOtherThanTheSource() { var otherConnectionMock = new Mock<Connection>(); var handlers2 = new Mock<IHandlers>(); otherConnectionMock.Setup(rc => rc.GenerateClientFunction("serverSync","addEntity")) .Returns((ClientFunction)handlers2.Object.AddEntity); otherConnectionMock.Setup(rc => rc.GenerateClientFunction("serverSync","removeEntity")) .Returns((ClientFunction)handlers2.Object.RemoveEntity); otherConnectionMock.Setup(rc => rc.GenerateClientFunction("serverSync", "changeAttributes")) .Returns((ClientFunction)handlers2.Object.ChangeAttributes); var guid = Guid.NewGuid(); RemoteServerImpl remoteServer2 = new RemoteServerImpl(otherConnectionMock.Object, doiMock.Object, dorMock.Object, guid); serverSyncMock.Setup(ss => ss.RemoteServers).Returns( new List<IRemoteServer> { remoteServer, remoteServer2 }); var changedAttributes = new EntitySyncInfo(); changedAttributes["test"]["a"] = new AttributeSyncInfo(Guid.NewGuid(), 99); var entity = new Entity(); var worldSync = new WorldSync(); worldSync.HandleRemoteAddedEntity(remoteConnectionMock.Object, entity.Guid, entity.Owner, new EntitySyncInfo()); worldSync.HandleRemoteChangedAttributes(remoteConnectionMock.Object, entity.Guid, changedAttributes); worldSync.HandleRemoteRemovedEntity(remoteConnectionMock.Object, entity.Guid); handlers.Verify(h => h.AddEntity(entity.Guid, It.IsAny<EntitySyncInfo>()), Times.Never()); handlers.Verify(h => h.ChangeAttributes(entity.Guid, It.IsAny<EntitySyncInfo>()), Times.Never()); handlers.Verify(h => h.RemoveEntity(entity.Guid), Times.Never()); handlers2.Verify(h => h.AddEntity(entity.Guid, It.IsAny<EntitySyncInfo>()), Times.Once()); handlers2.Verify(h => h.ChangeAttributes(entity.Guid, It.IsAny<EntitySyncInfo>()), Times.Once()); handlers2.Verify(h => h.RemoveEntity(entity.Guid), Times.Once()); }
public void ShouldChangeAttributesOnUpdate() { var entity = new Entity(); World.Instance.Add(entity); var changedAttributes = new EntitySyncInfo(); changedAttributes["test"]["a"] = new AttributeSyncInfo(Guid.NewGuid(), 99); var worldSync = new WorldSync(); worldSync.HandleRemoteChangedAttributes(remoteConnectionMock.Object, entity.Guid, changedAttributes); Assert.AreEqual(entity["test"]["a"].Value, 99); }
/// <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; }