Summary
On 41.00, subscribing to mesh_component.EntityEnteredEvent / EntityExitedEvent on a detector volume imposes a continuous game-thread (TPS) cost that scales with the number of scene graph entities currently idling inside the volume, not only on enter/exit transitions. With many entities sitting inside, TPS degrades steadily, and on top of that there is a recurring, severe TPS collapse roughly every N × 60s (consistent with the engine’s default GC cycle) whose magnitude also scales with the entity count inside. Removing the .Subscribe(…) calls fully eliminates both the steady cost and the periodic spikes, confirming the subscription itself is the source.
Please select what you are reporting on:
Verse
What Type of Bug are you experiencing?
Scene Graph
Steps to Reproduce
- Add a scene graph entity with a mesh_component configured as a detector volume (Collidable + Queryable) (OverlapAll) and attach the detector_volume_component below.
- Add a button + the entity_spawner_component below to spawn hundreds of mesh entities inside that volume.
- Test A (baseline): leave the two Subscribe lines in detector_volume_component commented out. Spawn 100–500 entities inside the volume. Note TPS via stat unit it stays healthy.
- Test B (bug): uncomment the two Subscribe lines, relaunch, spawn the same entities inside. The entities just idle (no enter/exit firing).
- Observe the TPS.
Expected Result
Subscribing to the overlap events should only incur cost when entities actually cross the volume boundary. Entities idling inside, with no transitions occurring, should impose no recurring per-tick cost beyond normal idle-entity cost, and should not amplify garbage collection.
Observed Result
- With the subscriptions active, TPS drops continuously while entities idle inside, scaling with the number of entities inside.
- A severe TPS collapse recurs roughly every N × 60s (consistent with the default ~30–60s GC interval); magnitude scales with entity count inside, suggesting per-tick garbage is generated/retained per contained entity and released in bulk at GC time.
- Commenting out .Subscribe(…) calls removes both the steady cost and the spikes entirely.
Platform(s)
PC - UEFN edit session and UEFN live published session
Additional Notes
Reproduction code
Detector volume — toggle the two Subscribe lines to switch between baseline (Test A) and bug (Test B):
using { /Verse.org/SceneGraph }
using { /Verse.org/Simulation }
detector_volume_component<public> := class<final_super>(component):
OnBeginSimulation<override>():void=
if (MeshComp := Entity.GetComponent[mesh_component]):
# COMMENTED OUT = baseline (Test A): TPS stays healthy no matter how many
# entities idle inside the volume.
# UNCOMMENTED = bug (Test B): per-tick TPS cost scales with the number of
# entities currently inside, even though no enter/exit is happening, plus
# a recurring TPS collapse roughly every N*60s.
MeshComp.EntityEnteredEvent.Subscribe(OnEntityEntered)
MeshComp.EntityExitedEvent.Subscribe(OnEntityExited)
OnEntityEntered(EnteredEntity : entity): void =
Print("Entity Entered Volume")
OnEntityExited(ExitedEntity : entity): void =
Print("Entity Exited Volume")
Entity spawner — press a button to spawn SpawnAmount mesh entities inside the volume (replace YOUR_MESH_HERE with any mesh asset set Collidable + Queryable so it registers with the detector):
using { /Verse.org/Simulation }
using { /Verse.org/SceneGraph }
using { /Verse.org/SpatialMath }
using { /Fortnite.com/Devices }
entity_spawner_component<public> := class<final_super>(component):
@editable
ButtonToListenTo : button_device = button_device{}
@editable
SpawnAmount : int = 100
var HeightOffset : float = 0.0
OnSimulate<override>()<suspends>:void=
ButtonToListenTo.InteractedWithEvent.Subscribe(OnInteracted)
OnInteracted(Agent:agent):void=
if (ParentSimEntity := Entity.GetSimulationEntity[]):
set HeightOffset = HeightOffset + 100.0
for (I := 1..SpawnAmount):
NewChildEntity := entity{}
ForwardOffset := 100.0 * I
NewTransform := transform_component:
Entity := NewChildEntity
LocalTransform := transform:
Translation := vector3{Forward := ForwardOffset, Left := 0.0, Up := HeightOffset}
# Replace with your own mesh component / mesh asset (Collidable + Queryable).
MeshComp := YOUR_MESH_HERE{Entity := NewChildEntity}
NewChildEntity.AddComponents(array{ NewTransform, MeshComp })
ParentSimEntity.AddEntities(array{NewChildEntity})