Today, while coding Galactic Wars v2.0, I stumbled upon a problem I have been having with content since I started working with XNA. The following diagram shows how easy it is to create the problem and how devastating it can be to any application. In my case, I have two (or more) menus that are referencing a piece of content.
Each menu references the same managed object which is really a wrapper for a second managed object (Texture2D in this case). The second object is a managed wrapper for the underlying unmanaged data, again in this case it is texture data. The problem is when one menu is being phased out (unloaded) and another is being phased in (loaded). The menu being loaded grabs a reference to the content before the other menu can unload it. The ordering is done this way to ensure that a menu actually loads its content before the current menu is unloaded.
After the menu is done loading content, the second menu is unloaded which drops the content and releases the unmanaged memory. Aha! The problem stems from the last point: I am trying to manage unmanaged memory when I should just let the GC take care of it. The reason I do this is for performance reasons, primarily on the Zune. Instead of letting textures just sit around, I release them when they are no longer needed to avoid piling up the garbage.
So what is the solution?
I would love to do this but unfortunately it introduces a second problem. If every menu (object using the content) dereferences the asset, it still will not be collected by the GC. The AssetManager still holds a reference which is now being unused and thus memory is wasted. The upside to this is that I do not need to implement the second option…
By implementing a counter on the asset itself and asking developers to always call Unload or Unload(ILoadable) on the AssetManager, I can guarantee that no Asset will be disposed while another object is using it. The downside is that it is an ugly implementation of something that is done in the GC anyways. The major upside is that even if developers use the previous method, the GC will still eventually catch the wasted memory.
So now Thrust’s AssetManager and IAsset require (and implement) reference counting to avoid the problem described above. And it works wonderfully.
I was pushing Galactic Defense to the Zune today to do some minor version testing (I develop on the PC and then push to Zune for platform specific tests) and ran into some major problems. The GC was collecting about once an update which was slowing the game down tremendously. So I dug in and started searching my code.
One of the best ways to figure out where you are creating garbage (when no tool can do it for you) is just to start commenting things out. For starters, I commented out the draw code for my map. This worked, but not enough. I ended up adding a line that removed the InputManager from the component collection (effectively commenting out its update code) and I had it: 0 allocations per update.
So I looked at the draw code again and realized (with the help from guys in #xna) that I was using an enumeration in a Dictionary. A big no no since the framework allocates every time you do a lookup. A quick switch to ints (cast all lookups to integers) fixed that problem. Now onto the InputManager.
To make a long story short, I followed the trail and cornered this line of code:
1 | if(!repeatKeys.Contains(button)) |
It is this line of code that actively makes sure that I have all the buttons in the repeatKey list so I can check and update the repeated keys later on. I removed this line of code and added the following:
1 2 3 4 5 6 7 8 9 10 11 12 | bool found = false; for (int i = 0; i < repeatKeys.Count; i++) { if (repeatKeys[i] == button) { found = true; break; } } if (!found) repeatKeys.Add(button); |
And now I am back to a normal amount of allocations (some for strings) per update. I wonder why Contains is causing an allocation. I am too lazy to figure that one out right now…