Hey Guys,
I am currently reworking my game to be entity system based. After solving many problems the endboss appeared:
I have a huge Memory Consumption in combination with the GC Freezing everything for multiple seconds up to a minute (!) for little to no gain.
Now I know that ECS are demanding in terms of GC however that’s not what I expected. I know about an implementation where they used a component pool instead of permanent recreation.
On the other hand I guess this impact is just too heavy to be only that?
Doing the maths: 60 FPS x 1 Component à 40 Bytes (rough estimation of a transform) should only be 2.4kB/s of Memory, however I am rather seeing something like 50MiB/s, which is why I will be searching for memory leaks or something as you read this.
In addition to this the profiler shows a “low” gc time during that freeze but a high cpu time, shouldn’t it be the other way round?
Edit: Sorry that I tend to rather write a post than solving problems on my own, it’s just more easy
Profiling the right way showed:
java.util.concurrent.ConcurrentLinkedQueue$Node 1.287.972.144 B (90,8%) 53.665.506 (96,5%)
60 Mio Nodes taking 1.2GiB, now I need to find out where I am filling that queue.
Edit2: A detailed look at that class shows way less elements (only ~3Mio and only 64MiB), no clue why, however the callstack shows what I feared:
50/50 for two of my Transform-Related Systems:
The first one takes the spatial transform in postRender and applies that to the component. This is needed as I still use the BetterCharacterControl and this is the way to pick up physics events.
The second one invalidateChildren
actually invalidates transform children when their parent is changed.
The problem with this? I only have one entity which currently is influenced by physics and only has one child.
The Stack further shows setComponent
→ entityChange
→ ConcurrentLinkedQueue.add
→ ConcurrentLinkedQueue.offer
, so I don’t know if this might be me calling setComponent multiple times or if changing the entities each frame just sucks?
Edit:
Sorry for writing a monologue here. Reading the Docs would have helped more.
The issue was simply this:
public static void invalidateChildren(EntityId eId) {
EntitySet set = Main.self.getStateManager().getState(EntityDataState.class).getEntityData().getEntities(new FieldFilter(TransformComponent.class, "parent", eId), TransformComponent.class);
for (Entity e: set) {
// some stuff
invalidateChildren(e.getId());
}
set.release(); //this was missing!! read the docs more careful than I did
So by reading the implementation of entityChanges (Thanks to open source), I found out that it prepares that queue for each EntitySet registered. Then I thought: Why so many sets? And then… Especially since those sets where never read they were filled more and more…
I guess I have many other EntitySets however luckily enough they are stored in AppStates which currently are never removed