New Look!

LostVectors.com now served with Blogability. LostVectors.com is constantly evolving. Recent site enhancements allow for better organization of news updates and devlog entries. It’s now even easier to maintain the site so you should expect to see more frequent news updates. New to this site is the ability to directly comment on news articles. Feel free to us know what you think.

AS3 Memory Fail

I’ve been too busy to update the devlog recently but I have in fact posted new content to Bowmaster Winter Storm every Friday for the past few weeks. Go check it out if you haven’t taken a look lately.

www.lostvectors.com/winterstormbeta

I’ve had some recent major triumphs, some mini defeats and mini triumphs lately. In the not so distant past I had some issues with a memory leak in Winter Storm. After much investigation and some thrashing I decided it was time to do some much needed refactoring. At this time I still had no real idea where the cause of the leak was.

My first step was to create a more formal process for handling event listeners. In AS3 I take issue with the lack of management functions that exist for working with events. The event model is much improved over AS2, but a few things caused some issues for me.

Function “removeEventListener” does not provide feedback on whether the operation was successful.

Ideally you would always call this function with the correct parameters to remove an event listener. However if you mistakenly give just a single incorrect parameter then the removeEventListener function call fails silently. The event listener that you though you removed remains in memory along with all of its attached references thus causing a memory leak.

for example:

1
myHugeObject.addEventListener("destroy", handleDestroy);

… later in the process, somewhere else in the code…

1
myHugeObject.removeListener("destroyed", handleDestroy);

Note: The event string parameter is the incorrect string. Syntactically there is no error. The program will compile. Not only that, but there is also no runtime error as well. Even though no such listener exists with the signature of event string “destroyed” and “handleDestroy” the function will not throw an error.

Even if you now do this:

1
myHugeObject = null;

The object remains in memory, now with no way to reference it, hence memory leak.

I was certain that I used removeEventListener responsibly and everywhere I added a listener I made sure to include a removeEventListener in a destroy function that was called when the object is no longer used. The problem was that I had made the mistake I described above. I found this out with the help of a custom event management class I created called EventManager.

Essentially I created a class to handle all event listener adds and removes so that all such operations could be audited for validity.

My syntax changed from:

1
2
myDispatcher.addEventListener("event", listenerFunc);
myDispatcher.removeEventListener("event", listenerFunc);

to:

1
2
EventManager.addListener(myDispatcher, "event", listenerFunc);
EventManager.removeListener(myDispatcher, "event", listenerFunc);

By funneling all event listener add and remove calls through this static class I was able to provide extra debugging feedback. For example, whereas before with removeEventListener if you attempted to remove an event listener that does not exist Flash would not care to inform you. With EventManager, if you call removeListener and it cannot find the listener you’re looking for a runtime error will be thrown.

EventManager essentially maintains a global registry of all event listeners that is now visible to the developer. I had to convert hundreds of calls to add/removeEventListner to use EventManager but it was worth it. With the added visibility and error checking of my EventManager class I was able to see when and if the registry was continually growing instead of staying within a certain size. And upon initially testing the fully converted code I immediately discovered a few listeners that were lingering and occasionally had runtime errors indicating that I was attempting to remove events listeners that did not exists.

So this was a major triumph in my battle against those evil pesky memory leaks. After fixing all the issues with event listeners I thought I was sure to see no more memory problems. While I did notice fewer memory issues, I still noticed some instances where memory usage would continue to creep up. It was then that I decided to revisit the method of disposing of application objects.

For all of my objects I have some form of “destroy” function that is called when the object is no longer needed and this function primarily just removed event listeners. I didn’t attempt to nullify references to other objects stored as member data within this object because I figured as long as my application didn’t refer to this object (i.e. store a reference to it) then garbage collection would eventually take care of everything.

For example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class ObjectA
{
   public var m_objB:ObjectB;
   public var m_objC:ObjectC;

   public function destroy():void
   {
      // remove listeners
   }
}
class MainApp
{
   public var m_objA:ObjectA;
   ...

   public function startNewLevel():void
   {
      m_objA = new ObjectA();
   }

   public function showUpgradesMenu():void
   {
      // show upgrades
   }

   public function endLevel():void
   {
      m_objA.destroy();
   }
}

With this design, the ObjectA’s destroy function would only remove listeners. Also, in MainApp, the reference to m_objA is only overridden when startNewLevel() is called. Therefore even though endLevel() was called, if the next step is to call showUpgradesMenu() the user may be looking at new upgrades to purchase for a while before startNewLevel() is called. In the meantime the reference to m_objA still exists even though it’s destroy function was called, and within it exist references to instances of ObjectB and ObjectC.

I decided to take a more aggressive and proactive approach to freeing memory. I made the design decision to nullify all member data object references whenever  an object is destroyed and then also nullify the current reference to the destroyed object.

The new way:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class ObjectA
{
   public var m_objB:ObjectB;
   public var m_objC:ObjectC;

   public function destroy():void
   {
      // remove listeners
      nullifyReferences();
   }

   public function nullifyReferences():void
   {
      m_objB = null;
      m_objC = null;
   }
}
class MainApp
{
   public var m_objA:ObjectA;
   ...

   public function startNewLevel():void
   {
      m_objA = new ObjectA();
   }

   public function showUpgradesMenu():void
   {
      // show upgrades
   }

   public function endLevel():void
   {
      m_objA.destroy();
      m_objA = null;
   }
}

You may think that just setting m_objA to null after the destroy() call should be enough, and for what you can see in this example it definitely would be, but in the context of a larger application there may exist other references to m_objA so simply nullifying it in MainApp will not guarantee that it is a candidate for garbage collection.

So I applied this design strategy to my game application. It was a bit tedious but worth it because a useful side effect of nullifying member references after destroying an object is that any other objects that may have been incorrectly using an object after destruction can no longer access public member data in the destroyed object. What happened in my game after I implemented this design was that the game application started to show runtime null reference errors everywhere in application was using a destroyed object when it wasn’t supposed to.

As part of a “design by contract” approach the game application is not supposed to use destroyed objects. This means that instead of checking “isDestroyed?” every time I want to use an object’s data, I make sure that the code doesn’t try to use destroyed objects in the first place. For instance, game unit AI is not supposed to target units that are destroyed, so when the application returns a list of all the closest enemies it makes sure to not return any “destroyed” enemies. The AI can assume the list of enemies it received consists of only “alive” enemies (a precondition for using the list). For the most part, I did a good job of ensuring that destroyed objects were not used. However, just like the event listener issues mentioned above, there were a few instances where I made mistakes.

So with a little bit of debugging and redesign I was able to not only improve memory usage efficiency but I was able to locate erroneous areas of my code. Since this design overhaul the game is running much more smoothly and previous failed memory tests are now passing and I now have a good infrastructure in place to prevent further memory issues. Now I can focus my efforts on more of the fun stuff like creating new content and game balancing.

Bowmaster Winter Storm Beta Update 0.1.0.2

www.lostvectors.com/winterstormbeta

There’s some cool new upgrades I added to help defend against those pesky elemental dragons. Introducing the fire and ice flak bomb upgrades. These work just like the regular flak bomb but now harness elemental damage which should prove useful against enemies weak to specific elemental attacks. Tip: Use fire flak bombs against ice dragons.

Another cool upgrade is the Fairy Bomb. This is a cure related skill that unleashes ferocious war mongering fairies that are hell bent on seeking out their targets to inflict imminent and utter … healing. Yeah, so they’re not that ferocious I guess but don’t tell them that. These guys home in on injured units (you included) and restore some health when they get close to you. If no units need healing they will live out their natural short lives happily bouncing about the terrain until their life timer expires at which point they explode (as they should) dealing no significant impact on the surrounding environment.

The user interface received a few much needed tweaks. You can now scroll through your action bars using the mouse wheel. The action bar ID is now listed in between the up and down action bar scroll buttons. Currently there are three available action bars to put all your cool projectile related upgrades. This may increase in the future.

Also UI related, you may now move your character with the arrow keys in addition to the WASD keys. I’m not sure why I forgot to add that, it was literally just four lines of code 😉 Don’t get too excited though; I’m not yet working on a fully customizable keyboard bindings interface. That’s a nice-to-have feature that you may see in the future though.

Updates:

  • New Upgrade! Fire Flak Bomb Arrow (fire skill)
  • New Upgrade! Ice Flak Bomb Arrow (ice skill)
  • New Upgrade! Fairy Bomb Arrow (cure skill)
  • Added "Like" button to the HTML landing page of this game. If you’re on facebook then be sure to "like" this page.

Balancing:

  • Decreased mana cost for elemental arrows significantly
  • Decreased mana cost of Flak Bomb Arrow.
  • Increased the cost per rank upgrade for Flak Bomb Arrow

Fixes:

  • Fixed issue that caused Hunter’s Spike Trap to not work properly.
    Unintentional cruelty to units fixed: After last update, dead units would get inverted and placed underground when frozen, on fire, or sliced in half. Death visuals should work properly now. Poor game units. As if being frozen, chopped, or burnt wasn’t enough I had to also turn them inside out and bury them underground.
  • Fixed issue where a grunt unit’s head when encased in ice caused extra ice to form above the top of the head as if to accommodate a cone head shaped unit. The ice now forms evenly around the unit’s head thus making him less self conscious but more importantly allows him to die with dignity.
  • Fixed issue where the Wacky Bomb would break if it moved to the left or right edge of the screen too high above the ground. Oh that wacky Wacky Bomb. Always getting itself into trouble. Perhaps he just needs some extra love and attention so he won’t act out so much. This can be said for much of the features in this game so he will just have to wait his turn!
    Stay tuned for updates. Keep me informed of any issues you may find.