Example #1
0
        /// <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);
        }
Example #2
0
        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);
        }
Example #3
0
        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);
        }
        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);
        }
Example #6
0
        /// <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);
                }
            }
        }
Example #7
0
        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;
        }