Hey,
I just spent three days on this and got to the root of the problem and I’m able to get it working by just adding an extra processor:
#pragma once
#include "MassProcessor.h"
#include "MassStationaryProcessor.generated.h"
/**
* Processor that ensures stationary ISM entities are maintained in the ISM system
* by marking them as processed each frame to prevent auto-removal
*/
UCLASS()
class UMassStationaryProcessor : public UMassProcessor
{
GENERATED_BODY()
public:
UMassStationaryProcessor();
protected:
virtual void ConfigureQueries() override;
virtual void Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context) override;
FMassEntityQuery EntityQuery;
};
//////////////////////////////////////////
#include "MassStationaryProcessor.h"
#include "MassEntityView.h"
#include "MassRepresentationFragments.h"
#include "MassCommonFragments.h"
#include "MassCommonTypes.h"
#include "MassStationaryISMSwitcherProcessor.h"
#include "MassExecutionContext.h"
#include "MassRepresentationSubsystem.h"
UMassStationaryProcessor::UMassStationaryProcessor()
: EntityQuery(*this)
{
ExecutionOrder.ExecuteInGroup = UE::Mass::ProcessorGroupNames::Representation;
// Make sure this processor runs after the ISM switcher processor
ExecutionOrder.ExecuteAfter.Add(UMassStationaryISMSwitcherProcessor::StaticClass()->GetFName());
bAutoRegisterWithProcessingPhases = true;
ExecutionFlags = static_cast<int32>(EProcessorExecutionFlags::AllNetModes);
}
void UMassStationaryProcessor::ConfigureQueries()
{
// Target entities with:
// 1. Static representation tag (stationary entities)
// 2. StaticMeshInstance representation
// 3. Required components for visualization
EntityQuery.AddTagRequirement<FMassStaticRepresentationTag>(EMassFragmentPresence::All);
EntityQuery.AddTagRequirement<FMassStationaryISMSwitcherProcessorTag>(EMassFragmentPresence::All);
EntityQuery.AddRequirement<FTransformFragment>(EMassFragmentAccess::ReadOnly);
EntityQuery.AddRequirement<FMassRepresentationLODFragment>(EMassFragmentAccess::ReadOnly);
EntityQuery.AddRequirement<FMassRepresentationFragment>(EMassFragmentAccess::ReadWrite);
EntityQuery.AddSharedRequirement<FMassRepresentationSubsystemSharedFragment>(EMassFragmentAccess::ReadWrite);
}
void UMassStationaryProcessor::Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context)
{
EntityQuery.ForEachEntityChunk(EntityManager, Context, [](FMassExecutionContext& Context)
{
UMassRepresentationSubsystem* RepresentationSubsystem = Context.GetMutableSharedFragment<FMassRepresentationSubsystemSharedFragment>().RepresentationSubsystem;
check(RepresentationSubsystem);
FMassInstancedStaticMeshInfoArrayView ISMInfos = RepresentationSubsystem->GetMutableInstancedStaticMeshInfos();
const TConstArrayView<FTransformFragment> TransformList = Context.GetFragmentView<FTransformFragment>();
const TConstArrayView<FMassRepresentationLODFragment> RepresentationLODList = Context.GetFragmentView<FMassRepresentationLODFragment>();
const TArrayView<FMassRepresentationFragment> RepresentationList = Context.GetMutableFragmentView<FMassRepresentationFragment>();
const int32 NumEntities = Context.GetNumEntities();
for (int32 EntityIdx = 0; EntityIdx < NumEntities; EntityIdx++)
{
const FMassEntityHandle EntityHandle = Context.GetEntity(EntityIdx);
const FTransformFragment& TransformFragment = TransformList[EntityIdx];
const FMassRepresentationLODFragment& RepresentationLOD = RepresentationLODList[EntityIdx];
FMassRepresentationFragment& Representation = RepresentationList[EntityIdx];
// Only process entities with StaticMeshInstance representation type
if (Representation.CurrentRepresentation == EMassRepresentationType::StaticMeshInstance &&
Representation.StaticMeshDescHandle.IsValid())
{
// The key part - add this entity to the update list to prevent it from being auto-removed
// We pass the same transform for both current and previous transform, since it's stationary
ISMInfos[Representation.StaticMeshDescHandle.ToIndex()].AddBatchedTransform(
EntityHandle,
TransformFragment.GetTransform(),
TransformFragment.GetTransform(),
RepresentationLOD.LODSignificance,
Representation.PrevLODSignificance
);
// Update the PrevLODSignificance to match current
Representation.PrevLODSignificance = RepresentationLOD.LODSignificance;
}
}
});
}
I believe it’s due to this part of the code in UMassVisualizationComponent::EndVisualChanges
- without some updates the instances are considered unprocessed and removed - I did notice when I simulated that I could see some meshes for a frame sometimes and there’s a comment just below “note that we’re not clearing the dirty flag on purpose - these components require constant updates” that made me suspicious.
TArray<FPrimitiveInstanceId> RemovedISMInstanceIds;
RemovedISMInstanceIds.Reserve(Unprocessed.Num());
{
for(TConstSetBitIterator<> BitIt(Unprocessed); BitIt; ++BitIt)
{
Experimental::FHashElementId ElementId(BitIt.GetIndex());
if (SharedIdMap.ContainsElementId(ElementId))
{
FPrimitiveInstanceId InstanceId = SharedIdMap.GetByElementId(ElementId).Value;
check(InstanceId.IsValid());
SharedIdMap.RemoveByElementId(ElementId);
RemovedISMInstanceIds.Add(InstanceId);
}
}
ISMComponent->RemoveInstancesById(RemovedISMInstanceIds);
}
I will roll with this for the moment but I suspect I may overhaul this, it’s kind of a pointless system and should just be fixed within UMassVisualizationComponent.