MassCommandAddFragmentInstances adding fragments to incorrect entities

I’ve been seeing some unexpected behavior recently when attempting to use FMassCommandAddFragmentInstances to add the same type of fragment to multiple entities at once, where each instance of the fragment contains different data. When I do this, the entities get assigned the correct fragment types, but the fragment data gets scrambled between the different entities.

I would very much appreciate either:
A) Is anybody able to confirm that this is a bug (and ideally help me find a workaround until it’s fixed); or
B) Help me understand what I’m doing incorrectly with FMassCommandAddFragmentInstances and what I should be doing instead.

Thank you!


The below snippet is a much simplified example of what I’m attempting to do:

struct FFooFragment : public FMassFragment
{
  // For the sake of this example, this should always be the entity
  // that has this fragment.
  UPROPERTY()
  FMassEntityHandle SelfEntity;
}

// First processor
for (FMassEntityHandle Entity : some set of entities)
{
  FFooFragment Foo.
  Foo.SelfEntity = Entity;
  Context.Defer().PushCommand<FMassCommandAddFragmentInstances>(Entity, Foo);
}

// deferred command buffer gets flushed...

// Second processor
FooQuery.ForEachEntityChunk(...)
{
  auto FooView = Context.GetFragmentView<FFoo>();
  for (int EntityIndex = 0; EntityIndex < Context.GetNumEntities(); ++EntityIndex)
  {
    // This check fails!
    check(FooView[EntityIndex].SelfEntity == Context.GetEntity(EntityIndex));
  }
}

The behavior I see is that the check near the bottom fails! Even though I populated each Foo fragment with a reference to the entity to which the Foo fragment was to be added, when I fetch that data later the Foo associated with each entity is not the correct one.


This behavior manifests when the first processor operates on multiple non-contiguous entities in the same archetype. For example, say we have an archetype with four entities [ A, B, C, D ] and the first processor operates on A, C, and D. Because A, C, and D are not contiguous, the entity collection passed into these functions will be split into two ranges:

{
	{ SubchunkStart = 0, Length = 1 } // [A]
	{ SubchunkStart = 2, Length = 2 } // [C, D]
}

Since we’re adding a Foo component, BatchAddFragmentInstancesForEntities needs to move those entities to another archetype before it can call BatchSetFragmentValues. BatchMoveEntitiesToAnotherArchetype wants to operate on these back-to-front, as described in the following snippet at MassArchetypeData.cpp line 946-952:

// Sorting the subchunks info so that subchunks of a given chunk are processed "from the back". Otherwise removing 
// a subchunk from the front of the chunk would inevitably invalidate following subchunks' information.
TArray<FMassArchetypeEntityCollection::FArchetypeEntityRange> Subchunks(EntityCollection.GetRanges());
Subchunks.Sort([](const FMassArchetypeEntityCollection::FArchetypeEntityRange& A, const FMassArchetypeEntityCollection::FArchetypeEntityRange& B)
	{
		return A.ChunkIndex < B.ChunkIndex || (A.ChunkIndex == B.ChunkIndex && A.SubchunkStart > B.SubchunkStart);
	});

So this sort will cause the range containing [C, D] to be handled before the one containing [A]. When handling each range, it calls PrepareNextEntitiesSpanInternal on the new archetype. This adds those entities to the new archetype’s entity map in a front-to-back manner. This means that the order of those entities in the new archetype will be [C, D, A]. This isn’t a problem in and of itself, since order of entities within a chunk is arbitrary.

BUT… after returning from this function, it calls BatchSetFragmentValues passing in EntityRangesWithPayload. EntityRangesWithPayload contains the fragment data in the order that the entities were present in the original archetype, and this data was not reordered when the entities were moved to a new archetype. That snippet at MassEntityManager.cpp line 1063-1066 looks like this:

// at this point all the entities are in the target archetype, we can set the values
// note that even though the "subchunk" information could have changed the order of entities is the same and 
// corresponds to the order in FMassArchetypeEntityCollectionWithPayload's payload
TargetArchetypeHandle.DataPtr->BatchSetFragmentValues(TargetArchetypeEntityRanges, EntityRangesWithPayload.GetPayload());

That assumption seems incorrect to me. The order of entities is not necessarily the same; as shown above, the order of entities in the new archetype is [C, D, A] but the fragment data in EntityRangesWithPayload is still [A, C, D]. This means that when we go into that function, we copy the data such that we wind up with the following:

Entity     FooFragment
C          A
D          C
A          D

As far as I have been able to tell, this is what is causing my entities to have incorrect data associated with them in the example above. As far as I can tell, the incorrect logic is entirely inside BatchAddFragmentInstancesForEntities, which makes me feel like it’s probably an engine bug. However if this were the case, I would have expected it to affect many more people than just me, so I don’t want to write off the possibility that the error is on my end. I’d appreciate any help understanding what the problem is and what I can do to address it.

Thanks in advance!

I was able to make a small example that reproduces the problem, at least for me. This example creates four entities and associates a “Foo” value with each of them. This is the cyan text in the screenshot.

Entity    Foo
0         0
1         1
2         2
3         3

It then uses FMassCommandAddFragmentInstances to add a “Bar” value to three of those entities, but not the fourth. Since the one that is skipped in the middle of the chunk, the entities that are moved to a new archetype will be broken into two ranges and the order of those ranges is reversed. This is the blue text in the screenshot.

This should result in the following:

Entity    Foo    Bar
3         3      3
0         0      0
1         1      1

But instead it does result in:

Entity    Foo    Bar
3         3      0
0         0      1
1         1      3

The red text in the screenshot shows the result. Note that the bar values added by FMassCommandAddFragmentInstances in the first processor (blue text) do not match the bar values read by the next processor (red text). But those values would have matched if BatchMoveEntitiesToAnotherArchetype had not rearranged the order of those entities in the new archetype!

(In the attached screenshot, I’m running this example in a project that also has some unrelated logic, so the entity IDs are 17-20 instead of 0-3, but everything else is as described above. Keep in mind that AddOnScreenDebugMessage adds newer messages at the top of the screen, so all of the messages in the screenshot were printed in bottom-to-top order).
AddFragmentInstancesBug.h (5.2 KB)
AddFragmentInstancesBug