PrimaryFeather/Starling-Framework — pull request #488 by zeh (2014-02-10 20:59:44+00:00)
Title: Added a more performant normalizeAngle()

This is a more performant version of normalizeAngle(). It correct clamps the value of the angle to be in the -PI..PI range, applying a proper modulo-like conversion. As it uses no while() loops, it will have consistent performance, regardless of the angle.

---

I'm working on some code that is supposed to be running for days of hours without stop. I started noticing that after a few hundred hours, certain functions started taking more and more time to execute, and that Starling's `normalizeAngle()` was by far the biggest culprit in increasing execution time.

There were no memory leaks. However, in my code, I have several particles and sprites that rotate endlessly. I do so by taking the current timer and applying it as the rotation angle of the image. This means that the rotation property of the image would, at times, contain huge numbers (think of the typical "rotation = getTimer() / speed").

Looking at the code of normalizeAngle() quickly made me realize what the problem was. After a few thousand rotations of my particles, updating the rotation would trigger long loops because of the while() blocks used for clamping the range.

I fixed my code with my own range-modulo code, but it would make sense to have this on the public version of Starling since more people may run into this problem.

The solution contains no loops so it will always perform the same. It *does* have a difference of resulting values compared to the old version when the rotation value contains a few million rotations, probably because of floating point rounding. That said, I believe the new equation is more precise.

Some quick benchmarks of the function itself, for 1000000 conversions:

Rotation of "-1":
Old: 294ms, new: 335ms

Rotation of "1":
Old: 302ms, new: 333ms

Rotation of "4":
Old: 299ms, new: 327ms

Rotation of "20":
Old: 315ms, new: 329ms

Rotation of "200":
Old: 673ms, new: 320ms

Rotation of "2000":
Old: 4111ms, new: 336ms

As you can see, the new method has a ~10% impact in performance when converting most valid ranges. But the impact on ranges that are far out of the range is brutal.

(This impact is mostly solved by using a TWO_PI constant, by the way, but I just didn't want to alter the class too much)