So I have been wracking my brain trying to find out why Spoxel tends to start lagging about after an hour of gameplay. After a lucky break, a user noticed the spike in cpu usage that increased significantly over time in a section of code that adds and removes nifty elements. As it turns out, every element that nifty creates it calls:
When nifty removes elements… well it doesn’t remove them from this event service. Also, every time you add an element it calls this method which iterates through every element in the list that matches the key. As far as I can tell that subscription isn’t used under normal operation (I removed it in my local copy of nifty and have had 0 issues so far). I’m not sure why they set this up since it is far cheaper to just walk the nifty scene graph and check for styles to update. Instead you get to pay a performance penalty any time an object is added or removed as well as never freeing any of the memory since it’s never removed from those internal lists.
If I wasn’t so close to release I probably would switch away from nifty at this point.
I meant to do that earlier but it looks like I forgot to hit submit. It has been added now. I’m not holding my breath since Nifty is basically just in maintenance mode now.
Although… that being said… I will not support or maintain Nifty anymore than is required for my own goals. You can either fork my fork, or use it through Jitpack.
Just for documentation: I had the same problem in my own game a few years ago - There, you could close+restart a jME app over and over again and I noticed the memory was not cleaned up after closing. I just removed the subscribers in cleanup (when the app was closed) in that case:
@Override
public void cleanup() {
super.cleanup();
// Unsubscribe leftover element styleListeners to prevent memory leak (https://github.com/nifty-gui/nifty-gui/issues/461)
for (Nifty nifty : runningNifties) {
nifty.getEventService().clearAllSubscribers();
}
}
… which solved the memory issue. I don’t know the Nifty internals too well, but I assume if you care about memory leaking while the application is running, you have to take a different approach, which you probably did in your fork. But if it’s only a matter of purging all subscribers once when the app closes (to clear all references and enable the GC to work), you can take the official Nifty dependency and use the above workaround.
Thanks, that is also what I initially did. But that doesn’t fix the memory leak that is happening in-game, given that in game means that you are using one screen and always modifying it. Also it still has the performance problem with the subscriber system. This is a problem if one does a lot of modifications at run time. But totally viable otherwise, more hassle free than using the fork.
And of course if you wipe out the subscribers on wrong occasion you will also wipe out the ones you need (the regular event stuff, such as reacting to component changes/requests).