/// <summary> /// Relays the changes to attributes to other connected sync nodes and process them locally. /// </summary> /// <param name="guid">Guid of the entity containing affected attributes.</param> /// <param name="changedAttributes">A set of modified attributes with their remote sync info.</param> internal void HandleRemoteChangedAttributes(Connection connection, string id, EntitySyncInfo changedAttributes) { Guid guid = new Guid(id); Entity entity = World.Instance.FindEntity(guid); foreach (IRemoteServer server in ServerSync.RemoteServers) { if (server.Connection == connection) { continue; } EntitySyncInfo filteredAttributes = new EntitySyncInfo(); bool containsAttributesToSync = false; foreach (var component in changedAttributes.Components) { foreach (var attribute in component.Value.Attributes) { if (server.DoI.IsInterestedInAttributeChange(entity, component.Key, attribute.Key)) { filteredAttributes[component.Key][attribute.Key] = attribute.Value; containsAttributesToSync = true; } } } if (containsAttributesToSync) { server.Connection["serverSync.changeAttributes"](id, filteredAttributes); } } ProcessChangedAttributes(guid, changedAttributes); }
/// <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); }
/// <summary> /// Creates a new sync for an entity. /// </summary> /// <param name="entity">Entity for which sync info is to be created.</param> /// <returns>Created sync info.</returns> private EntitySyncInfo CreateSyncInfoForNewEntity(Entity entity) { var entitySyncInfo = new EntitySyncInfo(); foreach (Component component in entity.Components) { foreach (ReadOnlyAttributeDefinition attrDefinition in component.Definition.AttributeDefinitions) { entitySyncInfo[component.Name][attrDefinition.Name] = new AttributeSyncInfo(ServerSync.LocalServer.SyncID, component[attrDefinition.Name].Value); } } return(entitySyncInfo); }
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()); }
/// <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); } } }
/// <summary> /// Processes changes to a set of attributes on the remote sync node and updates local values accordingly. /// </summary> /// <param name="guid">Guid of the entity containing affected attributes.</param> /// <param name="changedAttributes">A set of modified attributes with their remote sync info.</param> private void ProcessChangedAttributes(Guid guid, EntitySyncInfo changedAttributes) { lock (syncInfo) { if (!syncInfo.ContainsKey(guid)) { logger.Warn("Ignoring changes to attributes in an entity that does not exist. Guid: " + guid); return; } Entity localEntity = World.Instance.FindEntity(guid); foreach (KeyValuePair <string, ComponentSyncInfo> componentPair in changedAttributes.Components) { foreach (KeyValuePair <string, AttributeSyncInfo> attributePair in componentPair.Value.Attributes) { HandleRemoteChangedAttribute(localEntity, componentPair.Key, attributePair.Key, attributePair.Value); } } } }
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()); }
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> /// Creates a new sync for an entity. /// </summary> /// <param name="entity">Entity for which sync info is to be created.</param> /// <returns>Created sync info.</returns> private EntitySyncInfo CreateSyncInfoForNewEntity(Entity entity) { var entitySyncInfo = new EntitySyncInfo(); foreach (Component component in entity.Components) { foreach (ReadOnlyAttributeDefinition attrDefinition in component.Definition.AttributeDefinitions) { entitySyncInfo[component.Name][attrDefinition.Name] = new AttributeSyncInfo(ServerSync.LocalServer.SyncID, component[attrDefinition.Name].Value); } } return entitySyncInfo; }
/// <summary> /// Relays the changes to attributes to other connected sync nodes and process them locally. /// </summary> /// <param name="guid">Guid of the entity containing affected attributes.</param> /// <param name="changedAttributes">A set of modified attributes with their remote sync info.</param> internal void HandleRemoteChangedAttributes(Connection connection, Guid guid, EntitySyncInfo changedAttributes) { Entity entity = World.Instance.FindEntity(guid); foreach (IRemoteServer server in ServerSync.RemoteServers) { if (server.Connection == connection) continue; EntitySyncInfo filteredAttributes = new EntitySyncInfo(); bool containsAttributesToSync = false; foreach (var component in changedAttributes.Components) { foreach (var attribute in component.Value.Attributes) { if (server.DoI.IsInterestedInAttributeChange(entity, component.Key, attribute.Key)) { filteredAttributes[component.Key][attribute.Key] = attribute.Value; containsAttributesToSync = true; } } } if (containsAttributesToSync) server.Connection["serverSync.changeAttributes"](guid, filteredAttributes); } ProcessChangedAttributes(guid, changedAttributes); }
internal void HandleRemoteAddedEntity(Connection connection, Guid guid, Guid owner, EntitySyncInfo initialSyncInfo) { lock (syncInfo) { if (!syncInfo.ContainsKey(guid)) { syncInfo.Add(guid, new EntitySyncInfo()); lock (ignoredEntityAdditions) ignoredEntityAdditions.Add(guid); World.Instance.Add(new Entity(guid, owner)); } else { logger.Warn("Processing addition of already existing entity. Guid: " + guid); return; } ProcessChangedAttributes(guid, initialSyncInfo); Entity entity = World.Instance.FindEntity(guid); foreach (IRemoteServer server in ServerSync.RemoteServers) if (server.Connection != connection && server.DoI.IsInterestedInEntity(entity)) server.Connection["serverSync.addEntity"](guid, initialSyncInfo); } }
/// <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()); }
internal void HandleRemoteAddedEntity(Connection connection, string id, string owner, EntitySyncInfo initialSyncInfo) { var guid = new Guid(id); lock (syncInfo) { if (!syncInfo.ContainsKey(guid)) { syncInfo.Add(guid, new EntitySyncInfo()); lock (ignoredEntityAdditions) ignoredEntityAdditions.Add(guid); World.Instance.Add(new Entity(guid, new Guid(owner))); } else { logger.Warn("Processing addition of already existing entity. Guid: " + guid); return; } ProcessChangedAttributes(guid, initialSyncInfo); Entity entity = World.Instance.FindEntity(guid); foreach (IRemoteServer server in ServerSync.RemoteServers) { if (server.Connection != connection && server.DoI.IsInterestedInEntity(entity)) { server.Connection["serverSync.addEntity"](id, owner, initialSyncInfo); } } } }
/// <summary> /// Processes changes to a set of attributes on the remote sync node and updates local values accordingly. /// </summary> /// <param name="guid">Guid of the entity containing affected attributes.</param> /// <param name="changedAttributes">A set of modified attributes with their remote sync info.</param> private void ProcessChangedAttributes(Guid guid, EntitySyncInfo changedAttributes) { lock (syncInfo) { if (!syncInfo.ContainsKey(guid)) { logger.Warn("Ignoring changes to attributes in an entity that does not exist. Guid: " + guid); return; } Entity localEntity = World.Instance.FindEntity(guid); foreach (KeyValuePair<string, ComponentSyncInfo> componentPair in changedAttributes.Components) { foreach (KeyValuePair<string, AttributeSyncInfo> attributePair in componentPair.Value.Attributes) { HandleRemoteChangedAttribute(localEntity, componentPair.Key, attributePair.Key, attributePair.Value); } } } }