public void GenerateFieldUpdateDiff(FieldValueUpdate fieldsCollection, [NotNull] IChangeTrackableEntityDataCollection changeTrackable) { if (changeTrackable == null) { throw new ArgumentNullException(nameof(changeTrackable)); } lock (changeTrackable.SyncObj) { int updateDiffIndex = 0; foreach (int setIndex in fieldsCollection.FieldValueUpdateMask.EnumerateSetBitsByIndex()) { changeTrackable.SetFieldValue(setIndex, fieldsCollection.FieldValueUpdates.ElementAt(updateDiffIndex)); //Hey, so there was a bug for 8byte values that caused this to break. //I know it's bad design but we all have deadlines here. We NEED this to FORCEIBLY //make it appear as if it's changed even if it hasn't. Otherwise the change may not get dispatched. //Trust me, these hacks will be hidden deep in networking engine code like this that runs on another thread //that nobody understands. If you're here, then you understand. Tell them only that the Lich King is dead //and that Bolvar Fordragon died with him. changeTrackable.ChangeTrackingArray.Set(setIndex, true); updateDiffIndex++; } } }
public void Test_FieldValueFactory_With_Single_Value_Produces_Correct_FieldValueUpdate([EntityDataCollectionTestRange] int index, [Values(1, 2, 3, 4, 5, 6, 7, 8)] int value) { //arrange ChangeTrackingEntityFieldDataCollectionDecorator collection = new ChangeTrackingEntityFieldDataCollectionDecorator(new EntityFieldDataCollection(8)); FieldValueUpdateFactory updateFactory = new FieldValueUpdateFactory(); //act collection.SetFieldValue <int>(index, value); FieldValueUpdate fieldValueUpdate = updateFactory.Create(new EntityFieldUpdateCreationContext(collection, collection.ChangeTrackingArray)); //assert Assert.AreEqual(1, fieldValueUpdate.FieldValueUpdateMask.EnumerateSetBitsByIndex().Count(), $"Found more than 1 set bit."); Assert.AreEqual(value, fieldValueUpdate.FieldValueUpdates.First(), $"Serialized value was not expected value."); Assert.AreEqual(index, fieldValueUpdate.FieldValueUpdateMask.EnumerateSetBitsByIndex().First(), $"Index: {index} was expected to be in the update but was not."); }
public void GenerateFieldUpdateDiff(FieldValueUpdate fieldsCollection, [NotNull] IChangeTrackableEntityDataCollection changeTrackable) { if (changeTrackable == null) { throw new ArgumentNullException(nameof(changeTrackable)); } lock (changeTrackable.SyncObj) { int updateDiffIndex = 0; foreach (int setIndex in fieldsCollection.FieldValueUpdateMask.EnumerateSetBitsByIndex()) { changeTrackable.SetFieldValue(setIndex, fieldsCollection.FieldValueUpdates.ElementAt(updateDiffIndex)); updateDiffIndex++; } } }
public void Test_ChangeTracker_With_Multiple_Value_Indicates_No_Changes_After_Clearing_FieldValueUpdate() { //arrange ChangeTrackingEntityFieldDataCollectionDecorator collection = new ChangeTrackingEntityFieldDataCollectionDecorator(new EntityFieldDataCollection(8)); FieldValueUpdateFactory updateFactory = new FieldValueUpdateFactory(); //act collection.SetFieldValue <int>(1, 5); collection.SetFieldValue <int>(2, 4); collection.SetFieldValue <int>(3, 7); collection.ClearTrackedChanges(); FieldValueUpdate fieldValueUpdate = updateFactory.Create(new EntityFieldUpdateCreationContext(collection, collection.ChangeTrackingArray)); //assert Assert.AreEqual(0, fieldValueUpdate.FieldValueUpdateMask.EnumerateSetBitsByIndex().Count(), $"Found more than 1 set bit."); Assert.AreEqual(0, fieldValueUpdate.FieldValueUpdates.Count, $"Field updates should be empty due to no changes.."); }
public void Test_FieldValueFactory_With_Multiple_Value_Produces_Correct_FieldValueUpdate() { //arrange ChangeTrackingEntityFieldDataCollectionDecorator collection = new ChangeTrackingEntityFieldDataCollectionDecorator(new EntityFieldDataCollection(8)); FieldValueUpdateFactory updateFactory = new FieldValueUpdateFactory(); //act collection.SetFieldValue <int>(1, 5); collection.SetFieldValue <int>(2, 4); collection.SetFieldValue <int>(3, 7); FieldValueUpdate fieldValueUpdate = updateFactory.Create(new EntityFieldUpdateCreationContext(collection, collection.ChangeTrackingArray)); //assert Assert.AreEqual(3, fieldValueUpdate.FieldValueUpdateMask.EnumerateSetBitsByIndex().Count(), $"Found more than 1 set bit."); Assert.AreEqual(5, fieldValueUpdate.FieldValueUpdates.First(), $"Serialized value was not expected value."); Assert.AreEqual(1, fieldValueUpdate.FieldValueUpdateMask.EnumerateSetBitsByIndex().First(), $"Index: {1} was expected to be first index."); }
/// <inheritdoc /> public void Tick() { foreach (var entry in GuidToInterestCollectionMappable.EnumerateWithGuid(KnownEntities, EntityType.Player)) { InterestCollection interest = entry.ComponentValue; //Even if we only know ourselves we should do this anyway //so that the client can receieve entity data changes about itself //TODO: We probably won't send an update about ALL entites, so this is some wasted allocations and time List <EntityAssociatedData <FieldValueUpdate> > updates = new List <EntityAssociatedData <FieldValueUpdate> >(interest.ContainedEntities.Count); foreach (var interestingEntityGuid in interest.ContainedEntities) { //Don't build an update for entities that don't have any changes if (!ChangeTrackerHasChangesForEntity(interestingEntityGuid)) { continue; } //TODO: We should cache this update value so we don't need to recompute it for ALL players who are interested //This is the update collection for the particular Entity with guid interestingEntityGuid //We want to use the CHANGE TRACKING bitarray for updates. If this was initial discovery we'd use the SIT bitarray to send all set values. FieldValueUpdate update = UpdateFactory.Create(new EntityFieldUpdateCreationContext(ChangeTrackingCollections.RetrieveEntity(interestingEntityGuid), ChangeTrackingCollections.RetrieveEntity(interestingEntityGuid).ChangeTrackingArray)); updates.Add(new EntityAssociatedData <FieldValueUpdate>(interestingEntityGuid, update)); } //It's possible no entity had updates, so we should not send a packet update if (updates.Count != 0) { SendUpdate(entry.EntityGuid, updates); } } foreach (var dataEntityCollection in ChangeTrackingCollections.Enumerate(KnownEntities)) { dataEntityCollection.ClearTrackedChanges(); } }
public void Test_ChangeTracker_Doesnt_Set_Change_Bits_On_Same_Value_After_Clear_FieldValueUpdate() { //arrange WireReadyBitArray bitArray = new WireReadyBitArray(1328); bitArray.Set(1, true); bitArray.Set(2, true); bitArray.Set(4, true); //Reference the actual client's visibile field update computation. IEntityDataFieldContainer dataCollection = NetworkVisibilityCreationBlockToVisibilityEventFactory.CreateInitialEntityFieldContainer(new FieldValueUpdate(bitArray, new int[] { 5, 4, 7 })); ChangeTrackingEntityFieldDataCollectionDecorator collection = new ChangeTrackingEntityFieldDataCollectionDecorator(dataCollection, bitArray); FieldValueUpdateFactory updateFactory = new FieldValueUpdateFactory(); //act FieldValueUpdate fieldValueUpdate = updateFactory.Create(new EntityFieldUpdateCreationContext(collection, collection.ChangeTrackingArray)); Assert.AreEqual(3, fieldValueUpdate.FieldValueUpdateMask.EnumerateSetBitsByIndex().Count(), $"Found more than 1 set bit."); Assert.AreEqual(5, fieldValueUpdate.FieldValueUpdates.First(), $"Serialized value was not expected value."); Assert.AreEqual(1, fieldValueUpdate.FieldValueUpdateMask.EnumerateSetBitsByIndex().First(), $"Index: {1} was expected to be first index."); collection.ClearTrackedChanges(); //Check they're event before setting them again Assert.AreEqual(collection.GetFieldValue <int>(1), 5, $"Values not the same."); Assert.AreEqual(collection.GetFieldValue <int>(2), 4, $"Values not the same."); collection.SetFieldValue(1, 5); collection.SetFieldValue(2, 4); fieldValueUpdate = updateFactory.Create(new EntityFieldUpdateCreationContext(collection, collection.ChangeTrackingArray)); //assert Assert.AreEqual(0, fieldValueUpdate.FieldValueUpdateMask.EnumerateSetBitsByIndex().Count(), $"Found more than 1 set bit."); Assert.AreEqual(0, fieldValueUpdate.FieldValueUpdates.Count, $"Field updates should be empty due to no changes.."); }
//Public and static because it's referenced in a unit test. That design oddity is //worth the absolute critical design fault that was uncovered and now protected by a test. public static unsafe IEntityDataFieldContainer CreateInitialEntityFieldContainer(FieldValueUpdate fieldValueData) { //TODO: We could pool this. //we actually CAN'T use the field enum length or count. Since TrinityCore may send additional bytes at the end so that //it's evently divisible by 32. byte[] internalEntityDataBytes = new byte[fieldValueData.FieldValueUpdateMask.Length * sizeof(int)]; //It's absolutely CRITICAL that we don't use the sent fieldvalue internal bits for the set indication data //BECAUSE it also will be used as the initial changed values/change array. So we do the one-time copy here to avoid this critical //fault which is now covered by tests. byte[] copiedFieldUpdateMask = new byte[fieldValueData.FieldValueUpdateMask.InternalIntegerArray.Count]; Buffer.BlockCopy(fieldValueData.FieldValueUpdateMask.InternalIntegerArray.ToArrayTryAvoidCopy(), 0, copiedFieldUpdateMask, 0, fieldValueData.FieldValueUpdateMask.InternalIntegerArray.Count); IEntityDataFieldContainer t = new EntityFieldDataCollection(new WireReadyBitArray(copiedFieldUpdateMask), internalEntityDataBytes); int updateDiffIndex = 0; foreach (int setIndex in t.DataSetIndicationArray.EnumerateSetBitsByIndex()) { int value = fieldValueData.FieldValueUpdates.ElementAt(updateDiffIndex); byte *bytes = (byte *)&value; //TODO: Would it be faster to buffer copy? //The way wow works is these are 4 byte chunks for (int i = 0; i < 4; i++) { internalEntityDataBytes[setIndex * sizeof(int) + i] = *(bytes + i); } updateDiffIndex++; } return(t); }
/// <inheritdoc /> public EntityCreationData([NotNull] NetworkEntityGuid entityGuid, [NotNull] IMovementData initialMovementData, [NotNull] FieldValueUpdate initialFieldValues) { EntityGuid = entityGuid ?? throw new ArgumentNullException(nameof(entityGuid)); InitialMovementData = initialMovementData ?? throw new ArgumentNullException(nameof(initialMovementData)); InitialFieldValues = initialFieldValues ?? throw new ArgumentNullException(nameof(initialFieldValues)); }