Timers are for chumps - GS optimization tips

domeniusdomenius Member Posts: 108
edited June 2012 in Working with GS (Mac)
Hey GS community! Over the past few months, I've been developing a (relatively) large GS project. Over the course of development, I hit a lot of performance walls that almost had me looking elsewhere for an SDK; but through perseverance and experimentation I was able to develop a few crafty workarounds that had a profound effect on FPS. I hope to compile a more exhaustive list of tips and tricks in the near future, but the sheer magnitude of improvement I was able to achieve by nearly eliminating all the timers in my project was by far the biggest. Not only did this change in approach improve performance, it's also more accurate then the standard GS timers. Today, I will show you how to make this improvement in your own projects, so put your thinking caps on and get ready to learn!


Most GS developers know that timers are performance heavy and avoid them whenever possible. When you create a timer in GameSalad, you force the engine to disjoint a rule from its true location on a particular actor and encapsulate it within an event that runs outside of its usual scope for a set duration. Sometimes this is a desired side-effect, usually when a Timer lives longer than the actor that creates it (run to completion), but other times this leaves us wondering exactly when in a particular loop a timed action will begin or end, which can lead to all sorts of difficult to pinpoint bugs. Timers also force GS to calculate a delta time to try and equate a certain processing speed (in FPS) to real world time with relative accuracy, such calculations are not performance friendly and are not always necessary, sometimes it can even be counter-productive and introduce irregularities.

Regardless of how slow timers are, timed events are crucial to a lot of game logic, especially within AI programming, and the reality of GS is that we don't always have as much control over the order of execution as we would like. An action that needs to take place across multiple actors will often employ a short delay to ensure variables are maintained long enough for each actor to notice they have changed, especially since no single prototype can definitively confirm that every other prototype is done processing, nor its order in actor execution. So… what do we do when a timer is unavoidable? Well, we hack around it! Most types of timers can be emulated using simple math and GameSalad's own time tracking variables, which has a number of inherit benefits beyond processing speed.


Before we start getting into structuring our timers themselves, I'll quickly explain how these variables work and why you should use them. GS keeps track of time on every major variable scope; game, scene, and actor. These attributes can be found within any category of the attribute browser, second from the top, and are created automatically by the engine. Once initialized they count, starting at 0, up. These attributes are always "real" type and represent time in seconds. They calculate time with floating-point accuracy (to 5 decimal places), which is important to remember later on when we set up the conditions for our "timers". Another important distinction between these variables is that they only count as long as their container is actively being processed. This means that an individual actors self.time variable, for example, does NOT count during a pause screen, but game.time WILL continue to count during that pause time. Be sure to keep this in mind when deciding on what variable to use as the basis of your timer.

At this point you may be wondering why this attribute exists, or how it works, especially since keeping track of time in GS is something we think of as performance heavy and unadvisable unless absolutely needed. In reality, using these attributes has nearly zero performance cost since they use the devices internal clock instead of GameSalad's own timer system. In fact, these attributes are almost always more accurate than a GS timer since the device clock is used to keep the time every moment of the day on your device, it already calculates a delta time right at the OS level, and does it a lot more accurately than GS does. GS timers tend to compensate for lost time by altering their length of execution unlike any other timing system I've seen, this is more evident on timers that perform an action every X seconds, where I have often seen individual iterations take anywhere from double to half their specified times if the device lags. This can cause a de-synchonization of game events, which can be dangerous and unpredictable in any project. I'm sure everyone has seen their game glitch out in the viewer if you're running intensive programs alongside the creator. A wonky timer is almost always the culprit.

Still with me? Good... Its time to get serious. Now that we know GS keeps track of time for us, how the time is formatted, and that it's more accurate than GS timers since it uses native iOS information, lets see how we can exploit this to improve our games.


---AFTER AND FOR TIMER REPLACEMENT---

First, we will start with the most basic timer types, after and for timers. Lets say we have an actor that needs to wait 2 seconds after spawning to perform its given action, instead of making a timer stating after 2 seconds, do actions, we can save all that processing by just tapping into one of our time attributes, in this case self.time, and let the internal clock count for us. Try replacing that timer with this simple rule: if self.time>2, perform actions. The result is, a timerless timer! "For" type timers are almost identical, just flip the operator from > to < and your actions will be performed only for the first 2 seconds of an actors life. Cool beans, right? You may also notice that using this new rule based timer gives you access to the "otherwise" section just like any other rule. Using otherwise can allow you to save on timers or logic that may have been required before!

At this point you may be thinking, "Thats all fine and dandy, but most of my timers need to start when a particular action is performed, not when an actor is created." Well, who said we can't work around that too? Lets say we need to start a 1 second timer every time the player clicks on a button. To do this, we need to create one "real" attribute, for the sake of this example I will call it startTime. When the player presses the button, we change startTime to self.time. This way we have a refrence time (usually called a timestamp) to construct our rule around. Now we can accurately determine how long to run the rule for with a little bit of math. If self.time>startTime+1, perform actions. The only thing we need to ensure now is that the rule is not fired a second after initialization when startTime is 0. Do this by adding if startTime>0 to the previous rule, giving it two conditions. Every time we update startTime, this rule will effectively be fired one second later and all the actions within will be run. Now you can use For and After style timers anywhere, anytime and all the game needs to process is one variable and one rule, way faster than a timer!

The example above would be adequate if the required actions are confined to a single actor, but sometimes we need to update multiple actors. You could make a global boolean that is flicked on each time we run the timer, but this approach is more finicky (The boolean has to be turned off at some point after we are sure everybody is updated) and adds an attribute. Instead, we can simply make our comparisons versus game.time instead of self.time and create the startTime attribute as a game variable rather than an actor variable. Self.time fluctuates between actors depending on when they were created, but there is only one game.time. Just remember that game.time is always counting, during ads, on load, when the game is paused, even when the game is running in the background. Use self.time when possible to avoid problems that could crop up otherwise.

For timers are particularly dangerous when using game.time as these can get stuck in an "always on" status and may miss tasks, but are perfectly safe using self.time. My game employs self.time for all of its timers except one, which is utilized by the AI to hunt targets. If the AI per chance misses all or part of this event due to a pause or multitasking, it will most likely miss on its next shot and knows to re-aim accordingly based on an unsuccessful shot attempt. This would be an example of a safe use of game.time since missing the event once does not break the game indefinitely, and there is another non-reliant condition that resets the attribute when needed.

If you absolutely can not avoid a For type timer using game.time and the actions contained within are vital to operation, you can create a safety statement that should not fire unless the game is broken and track each iteration of the rule. To do this, create one extra boolean, lets call it "timerRunning". When your timer rule begins, change this boolean to true. Once all the actions in the rule are run, change the boolean to false. Then, in our previous example, if game.time>startTime+1.5 and timerRunning = true, change timerRunning to false and either re-run the timer by updating startTime to the current time or reset any attributes that may cause issues back to their defaults/safe value.


---EVERY X SECONDS TIMER REPLACEMENT---

Now that you know how to create basic timers "sans timers", lets work on the "Every" type timer. These timers are designed to loop every X seconds and were the source of undying frustration within my project. As mentioned previously, GS isn't always the best at determining an accurate delta time, nowhere is this more evident than in these timers. For reasons I can not fathom, "Every" type timers are incapable of accuracy with iterations faster than 0.1 seconds, especially on older devices. Even at 50FPS, where the theoretical maximum would be iterations every 0.02 seconds, i've seen these timers exhibit times anywhere from half to double their intended duration. The good news is, we can trick GS into making this type of timer with a rule, which in my experience has exhibited near perfect timings.

The first step is to create a new "real" attribute, I called it mod1. This attribute does not store any dynamic data, it is required since we can only create expressions on the right hand side of the operator, not the left. For now, set mod1 to 0.05. Next, we create a rule that states if mod1>self.time%0.1. For those who do not know, % is the modulus operator. The internet is full of information on modulus and other math operators so I won't go too deep into why we use this, but the basic idea is that % forces a number to wrap around a specified point (in this case 0.1). It makes 0.1 and 0 synonymous and causes our time attribute to count up to 0.1, then return to 0 and count up again. This sort of operation is typically used for circle math (where 360 degrees and 0 degrees are the same point) as well as 12 hour time, where 3=15 mod 12 (3AM and 3PM). The example above would iterate every 0.1 seconds and allow 0.05 seconds for processing to complete within the rule itself. Simply adjust the number after the modulus operator to change the timers duration. ex: if mod1>self.time%1 would fire every second.

If more processing time is needed, mod1 can be set up on a per-use basis, but any calculation taking longer than 0.05 seconds shouldn't be in your game! You can fire two alternating events within a single rule by using the otherwise section of these timer types. The value of your mod1 attribute dictates how much time is given to each part of the rule. As a general guideline, do not make mod1 any smaller than 0.02, especially if the effects of this timer are needed in multiple actors. Also avoid using 0, or the iteration time (in this case, 0.1), as your mod1 value, Modulus is inherently sketchy at its start and end points. I've been able to create iterations as quick as every 0.04 seconds with this sort of calculation with no issue, fast enough for very smooth programatic animation or constrain-like updates on background attributes. In fact, I recommend using these sorts of timers for animation instead of the standard animate function, it seems to play smoother and being able to adjust the animation parameters at runtime can be particularily useful for complex actors.


Now that you know how to emulate every type of timer, let's talk about a few differences from standard timers and how to design with them in mind. First of all, this sort of timer will never exhibit the same behaviour as a "run to completion" type timer, it follows the same principles as any other rule. In my opinion, being able to define the exact entry and exit point of a timer within your game code is of far greater value than having the run to completion behaviour. If you need one of these timers to work similar to "run to completion", you can place the actual timer within a dummy actor, I have one in my games I call "event manager", and use a global boolean to transmit its on/off state to the rest of the game. It's still faster than a regular timer! Cross compatibility between timers and even actors can be explored if you use global time and save timestamps as game attributes. This can allow you to make comparisons between timed events that may not have been possible before or may have required more timers. Thats not to say you can't compare self time attributes as well within a single actor, nested timers can be easily separated and optimized in this fashion. Code for toggles in particular can be much cleaner using modulus timers rather than conventional means if you use the otherwise section. It's pretty rare to find a situation where making your own timer is vastly inconvenient or doesn't fit the situation.


Wow! You made it! I hope this post has helped you to identify some ways to cut down on the timers in your project and ultimately reach your performance goals. Even though the methods above decimate regular timer performance, the best timer is still no timer. Don't use this newfound power as an excuse to create more timers, always look for an alternative. If you have any issues implementing these rules, feel free to post and I'll do what I can to help. Until next time, happy coding and good luck!
«13456

Comments

  • jn2002dkjn2002dk Member Posts: 102
    Awesome post!

    One question though - in your mod1 mod2 example i'm not really clear on the rule condition for mod2. Any chance you could explain that to me?

    Thanks!
  • domeniusdomenius Member Posts: 108
    Awesome post!

    One question though - in your mod1 mod2 example i'm not really clear on the rule condition for mod2. Any chance you could explain that to me?

    Thanks!
    It seems you learn something new every day! I almost wrote a completely different response, but I just recreated this type of timer in a much simpler project and mod2 is actually not needed. It did have some benefits for the triggers in my particular application but for everyone else its an option. Im gonna quickly update that section now to use only one mod ;).
  • jn2002dkjn2002dk Member Posts: 102
    Awesome post!

    One question though - in your mod1 mod2 example i'm not really clear on the rule condition for mod2. Any chance you could explain that to me?

    Thanks!
    It seems you learn something new every day! I almost wrote a completely different response, but I just recreated this type of timer in a much simpler project and mod2 is actually not needed. It did have some benefits for the triggers in my particular application but for everyone else its an option. Im gonna quickly update that section now to use only one mod ;).
    Ahhh makes sense now. I tried it with just mod1 and it worked perfectly so i was a bit confused lol

    Anyway, awesome post. Very useful as i've been having lots of headaches with the Timers

    Thanks again:)

  • MarkOnTheIronMarkOnTheIron Member Posts: 1,447
    Thanks for the post. I already learned the hard way how to use self. scene. and game. time attributes but for a lot of users this will be a real discovery.

    Thanks for taking the time to write everything down. :-bd

  • simo103simo103 Member, PRO Posts: 1,331
    @domenius ... great post ... thank you. I have a glitchy project and I suspect timers might be the culprit based on your post so I will be trying your solution this weekend.
  • domeniusdomenius Member Posts: 108
    Finally fixed the post, it thought my psuedocode was HTML! Shouldn't have missing chunks anymore.
  • xforcexforce Member Posts: 187
    ive never used self.time before. tried out the mod1 trick for the everytimer. difference in performance is amazing. while my iphone 4s runs my game pretty smoothly before the change ,the creator actually lagged pretty badly during tests. though just replacing only 1 timer has made a big difference. kinda looking forward to going through all of them through the game now.
  • RThurmanRThurman Member, Sous Chef, PRO Posts: 2,879
    edited June 2012
    Thanks so much! This is great info. Just about every paragraph had one of those 'Aha!' moments!
  • domeniusdomenius Member Posts: 108
    Does self.time stops when I use pause behavior?
    Yes, it does. This is why it is a safer alternative than game.time in some instances.

  • LiquidGameworksLiquidGameworks Anchorage, AKMember, Sous Chef Posts: 956
    Give this guy a "book of knowledge" badge :) When @RThurman enjoys it, you know its good.
  • imGuaimGua Member Posts: 1,089
    Awesome. Really want to see yellow post in this thread. Maybe GS team can use this to make standard timer better.
  • tatiangtatiang Member, Sous Chef, PRO, Senior Sous-Chef Posts: 11,949
    Great explanation. I hadn't thought about how to replace an "every" timer, but that makes perfect sense.

    New to GameSalad? (FAQs)   |   Tutorials   |   Templates   |   Greenleaf Games   |   Educator & Certified GameSalad User

  • LumpAppsLumpApps Member Posts: 2,880
    On a project I made last year I had loads of performance problems. The game depended a lot on timers. When I replaced al the timers by self.time attributes and such the increase of performance was amazing. Even the loading times went down.
    Thanks for the post. It explains a lot.
  • DanDaMan123DanDaMan123 Member Posts: 216
    Normally I use timers but I guess i'll try it, does it really make a difference?
  • domeniusdomenius Member Posts: 108
    Normally I use timers but I guess i'll try it, does it really make a difference?
    In complex or timer-heavy projects, it can make a huge difference. I've seen gains upwards of 15-20 fps on average in a complex project, and that's adhoc. If you build a project using these concepts rather than timers, it definitely saves optimization work later.

  • DanDaMan123DanDaMan123 Member Posts: 216
    I like using the attributes, thanks, I think that you did a really thorough job telling us all of this.
  • Fabri DamazioFabri Damazio Member Posts: 97
    We are testing this changes. Later i will post about performances changes
  • RThurmanRThurman Member, Sous Chef, PRO Posts: 2,879
    Give this guy a "book of knowledge" badge :) When @RThurman enjoys it, you know its good.
    Yup! Got schooled today -- and loved it!
  • kinzuakinzua Member Posts: 554
    @domenius great stuff there.. i wish i'd known all this before compiling my project.. will utilize the methodology in my new projects.. u'v catered to a v.core issue.. best part is that it sounds like it'll work..
  • YoRoosterYoRooster Member Posts: 159
    You are a LEGEND!
  • domeniusdomenius Member Posts: 108
    Hey everyone! I'm glad you guys are finding this post useful. I just put together a quick demo project that shows all three timer types (for, after,every) in operation as well as one global "every" timer that uses a game boolean to transmit its state. It can be downloaded via the link below.

    http://www.gamefront.com/files/21822535/timer_example.gameproj.zip
  • tenrdrmertenrdrmer Member, Sous Chef, Senior Sous-Chef Posts: 9,934
    This is some really cool stuff. Great work!!
  • ellfireellfire Member Posts: 187
    Wow, this rocks. Haven't even got into the timers based on events yet. I have an intro that is timer heavy in my game. (Basically, timers tell each panel of the sequence when to turn on and off.) These are all After timers. Swapped them out with no timer timers, and it works flawlessly. Way to go @domenius !
  • domeniusdomenius Member Posts: 108
    edited June 2012
    I've been doing a few tests to show the inherit flaws of GS timers vs time variables and to show the differences in operation visually. I created a stress test project that I believe shows the differences quite well. It also demonstrates how to use mod timers for efficient animation. You will notice that the animations tagged as "math" do not jitter as much when stress is placed on the application and they maintain an even iteration time. The timer based animation does NOT maintain an even iteration time, you will see these animations slowly move in and out of sync with the others. Even timer based animation is better than the default animate behaviour, which is also demonstrated in this app. It looks something like this:

    image

    The most troubling part of this application is the two timers shown at the bottom of the screen. The top number (in light blue) represents the number of seconds that has gone by since application start using game.time, the second number (in purple/pink) shows time gone by since application start using a timer that updates every 0.05 seconds. You can check the code if you like, I'm not doing anything to simulate a loss of time. In fact, I update the time on game initialization to game.time to make the test more fair. As the application runs, these numbers move further and further out of sync. When you place load on the project (using the stress test button) the issue is further intensified.

    On my mac the times do not move as far out of sync, its a new macbook pro with an i7 and a bunch of ram, but on device (iPhone 4) the issue is very obvious even with a full adhoc build. I'm sure is even worse on a 3gs. Truth be told, the inaccuracy of timers is even worse than I had originally thought. I would really like to hear from the GS development team as far as why this happens and if it's something that can be fixed on native GS timers. It's bad enough that these timers are slow, but they should at least be accurate. If anyone wants to try the project themselves I would be really interested to hear the results on other devices as well, download it here: http://www.gamefront.com/files/21823560/stresstest.gameproj.zip



  • Fabri DamazioFabri Damazio Member Posts: 97
    Ok, tested. Our results:


    We have a heavy based game on timers. Everything on the gameplay uses timers. All logic, actors, time counters and event triggers are based on timers.

    We changed 95% of all timers of our project to this new way.

    We have almost 30% in gain of speed and the game almost got playable in a iPhone 3G!

    We recommend everyone to change all timers possible to this new way.

    We done it in 6 hours and the results was awesome.

    Thanks for the post. We really need study more ways to optimize the use of this formidable engine.

  • PhoticsPhotics Member Posts: 4,172
    I remember a post on this topic from way back...
    http://forums.gamesalad.com/discussion/4575/syn-tip-a-great-substitute-for-an-after-timer-will-aid-big-in-optimization

    On the list of performance killers, I worry more about keeping my images as powers of 2 and reducing physics usage.
    We have almost 30% in gain of speed and the game almost got playable in a iPhone 3G!
    I think it depends on the game though. I'm surprised that a 30% gain in speed occurred from simply using a different timer approach.

  • AppTekAppTek Member Posts: 152
    Amazing!!! Great Work!!!
  • Fabri DamazioFabri Damazio Member Posts: 97
    We have almost 30% in gain of speed and the game almost got playable in a iPhone 3G!
    I think it depends on the game though. I'm surprised that a 30% gain in speed occurred from simply using a different timer approach.

    Our game is heavy based on timers. Almost all actions is based on timers. I think that why we gain a lot on performance.

    We had a lot of actions based on "EVERY X SECONDS" or "AFTER X SECONDS" running at the same time.


  • ShadowMoonShadowMoon Member Posts: 146
    after all these years, someone start taking it seriously. @domenius you rock!
Sign In or Register to comment.