Mass:在 CreationContext 激活时 CompositionChange 通知被跳过或合并的问题(UE 5.6)

问题:​

  1. 疑似Bug:在CreationContext存在期间​,如果对CreationContext影响的Entity之外的Entity修改Archetype,与后者相关的Notification会丢失,见结果#1
    1. ​逻辑上不被CreationContext覆盖的Entity的修改应该是被正常通知的,想确认一下当前效果是设计如此吗?是否为Bug?
  2. 为解决1中的问题,我在FMassObserverManager::OnCompositionChange()的函数实现中加了判断(见后续的代码),这样修改后的结果(见结果#2):​所有在该CreationContext存在期间被创建的Entity的CompositionChange通知,都会被合并到第一次创建Entity时触发的一次通知中
    1. ​如果#1是Bug,这样修改是否合理?
    2. 这样修改后的Entity创建操作通知的合并,是否能按实际的操作顺序通知?(见​期望的结果)

MassObserverManager.cpp OnCompositionChange的实现中加了判断:
if (TSharedPtr<FCreationContext> CreationContext = GetCreationContext())
{
    if (EntityCollection.EntityHandle.IsSet()
       ? CreationContext->IsContainsEntity(EntityManager, EntityCollection.EntityHandle) // 这个函数实现见后续的代码
       : CreationContext->IsContainsEntityCollection(EntityManager, EntityCollection.EntityCollection)) // 这个函数的实现见后续的代码
    {
       // a composition mutating operation is taking place, while creation lock is active - this operation invalidates the stored collections
       CreationContext->MarkDirty();
       return false;
    }
}


bool FCreationContext::IsContainsEntityCollection(const FMassEntityManager& InEntityManager, const FMassArchetypeEntityCollection& InEntityCollectionToTest) const
{
    if (CreationHandle.IsSet())
    {
       const FBufferedNotification& Notification = Lock->GetCreationNotification(CreationHandle);

       if (const FEntityCollection* CreatedEntities = Notification.AffectedEntities.TryGet<FEntityCollection>())
       {
          const TConstArrayView<FMassArchetypeEntityCollection> PerArchetypeCollection = CreatedEntities->GetUpToDatePerArchetypeCollections(InEntityManager);
          return INDEX_NONE != PerArchetypeCollection.IndexOfByPredicate([&InEntityCollectionToTest](const FMassArchetypeEntityCollection& EntityCollectionInArray) -> bool
          {
             return InEntityCollectionToTest.IsOverlapping(EntityCollectionInArray);
          });
       }
       else
       {
          const FMassEntityHandle EntityHandle = Notification.AffectedEntities.Get<FMassEntityHandle>();
          if (InEntityCollectionToTest.GetArchetype() == InEntityManager.GetArchetypeForEntity(EntityHandle))
          {
             return InEntityCollectionToTest.IsOverlapping(FMassArchetypeEntityCollection(InEntityCollectionToTest.GetArchetype(), MakeArrayView(&EntityHandle, 1), FMassArchetypeEntityCollection::NoDuplicates));
          }
       }
    }
    return false;
}

bool FCreationContext::IsContainsEntity(const FMassEntityManager& InEntityManager, const FMassEntityHandle& InEntityHandleToTest) const
{
    if (CreationHandle.IsSet())
    {
       FMassArchetypeEntityCollection InEntityHandleCollection(InEntityManager.GetArchetypeForEntity(InEntityHandleToTest), MakeArrayView(&InEntityHandleToTest, 1), FMassArchetypeEntityCollection::NoDuplicates);
       return IsContainsEntityCollection(InEntityManager, InEntityHandleCollection);
    }
    return false;
}

bool FMassArchetypeEntityCollection::IsOverlapping(const FMassArchetypeEntityCollection& Other) const
{
    if (GetArchetype() == Other.GetArchetype())
    {
       return INDEX_NONE != GetRanges().IndexOfByPredicate([&Other](const FArchetypeEntityRange& LhsRange) -> bool
       {
          return INDEX_NONE != Other.GetRanges().IndexOfByPredicate([&LhsRange](const FArchetypeEntityRange& RhsRange) -> bool
          {
             return LhsRange.IsOverlapping(RhsRange);
          });
       });
    }
    return false;
}

重现步骤

下载附件中的Test文件,放到MassEntityTestSuite模块中,按问题中的说明执行Test,应该会得到结果#1

运行附件中的Test
 
结果#1:
[00] * Create Entity1 (IntsArchetype)
[01] Notify - Add Int on Entity:1 - Means that the creation of entity has been detected.
[02] * Create ObserverLock
[03] 	* Create CreationContext
[04] 		* Create Entity2 (IntsArchetype)
[05] 		* Change Archetype for Entity1 (Add FTestFragment_Bool)
[06] 		<--- 'Add' notification of FTestFragment_Bool on Entity1 should be received before Entity3 created, but it's missing.
[07] 		* Create Entity3 (IntsArchetype)
[08] 		* Change Archetype for Entity2 (Add FTestFragment_Float)
[09] 		* Change Archetype for Entity3 (Add FTestFragment_Float)
[10] 	* Destroy CreationContext
[11] 	* Change Archetype for Entity1 (Add FTestFragment_Float)
[12] 	* Create Entity4 (IntsArchetype)
[13] * Destroy ObserverLock
[14] Notify - Add Float on Entity:2
[15] Notify - Add Float on Entity:3
[16] Notify - Add Int on Entity:2 - Means that the creation of entity has been detected.
[17] Notify - Add Int on Entity:3 - Means that the creation of entity has been detected. <-- 位于这一条之后的通知被跳过了
[18] Notify - Add Float on Entity:1
[19] Notify - Add Int on Entity:4 - Means that the creation of entity has been detected.
 
结果#2:
[00] * Create Entity1 (IntsArchetype)
[01] Notify - Add Int on Entity:1 - Means that the creation of entity has been detected.
[02] * Create ObserverLock
[03] 	* Create CreationContext
[04] 		* Create Entity2 (IntsArchetype)
[05] 		* Change Archetype for Entity1 (Add FTestFragment_Bool)
[06] 		<--- 'Add' notification of FTestFragment_Bool on Entity1 should be received before Entity3 created, but it's missing.
[07] 		* Create Entity3 (IntsArchetype)
[08] 		* Change Archetype for Entity2 (Add FTestFragment_Float)
[09] 		* Change Archetype for Entity3 (Add FTestFragment_Float)
[10] 	* Destroy CreationContext
[11] 	* Change Archetype for Entity1 (Add FTestFragment_Float)
[12] 	* Create Entity4 (IntsArchetype)
[13] * Destroy ObserverLock
[14] Notify - Add Float on Entity:2
[15] Notify - Add Float on Entity:3
[16] Notify - Add Int on Entity:2 - Means that the creation of entity has been detected.
[17] Notify - Add Int on Entity:3 - Means that the creation of entity has been detected. <-- 位于该条之前的操作,被延后了,涉及Entity创建的操作是被合并在一起通知的
[18] Notify - Add Bool on Entity:1
[19] Notify - Add Float on Entity:1
[20] Notify - Add Int on Entity:4 - Means that the creation of entity has been detected.
 
期望的结果:
 
[00] * Create Entity1 (IntsArchetype)
[01] Notify - Add Int on Entity:1 - Means that the creation of entity has been detected.
[02] * Create ObserverLock
[03] 	* Create CreationContext
[04] 		* Create Entity2 (IntsArchetype)
[05] 		* Change Archetype for Entity1 (Add FTestFragment_Bool)
[06] 		<--- 'Add' notification of FTestFragment_Bool on Entity1 should be received before Entity3 created, but it's missing.
[07] 		* Create Entity3 (IntsArchetype)
[08] 		* Change Archetype for Entity2 (Add FTestFragment_Float)
[09] 		* Change Archetype for Entity3 (Add FTestFragment_Float)
[10] 	* Destroy CreationContext
[11] 	* Change Archetype for Entity1 (Add FTestFragment_Float)
[12] 	* Create Entity4 (IntsArchetype)
[13] * Destroy ObserverLock
[14] Notify - Add Float on Entity:2
[15] Notify - Add Float on Entity:3
[16] Notify - Add Int on Entity:2 - Means that the creation of entity has been detected.
[17] Notify - Add Bool on Entity:1
[18] Notify - Add Int on Entity:3 - Means that the creation of entity has been detected.
[19] Notify - Add Float on Entity:1
[20] Notify - Add Int on Entity:4 - Means that the creation of entity has been detected.

貌似附件没有成功加上,我再提一下

您好,这个问题的确是个错误已经在5.7上修复,只不过包含内容比较多不太容易合并到5.6上

感谢解答!

不过我看 5.7preview 中也是相似的实现,增加更多的 ObservedOperation,但是并没有解决​上面提到的两个问题,请问是在正式版修复的吗?

对于CreationContext影响的Entity之外的Entity修改Archetype,与后者相关的Notification会丢失的问题,我觉得是个bug,在内部有类似的Jira,我可能会反馈一下这个是否在正式版已经合并,我觉得您可以先本地绕过这个问题

但是对于第二个问题要保证严格顺序,我觉得引擎暂时没考虑需要保证时序

明白,非常感谢。我现在已经在本地调整了实现,Entity的CreationContext并不会阻止CreationContext无关的Notification了,暂时不会影响功能了。

好的~