Author Topic: Version 2.3 Feature Suggestions  (Read 9131 times)

Plush

  • Regular
  • **
  • Posts: 22
    • View Profile
    • Email
Re: Version 2.3 Feature Suggestions
« Reply #15 on: 2017-12-29, 07:17:18 PM »
http://buildnewgames.com/garbage-collector-friendly-code/

Have a look at this:

I've smoothened out the performance of the drawing by removing all local variable declarations in MapLayer.draw() and many local variable declarations in the drawing functions in general.

This creates less garbage and when I inspect my Android game's webview in chrome://inspect, the memory peaks are smaller than before, and also seem less frequent.

The game seems a tad slower overall (With the added scope lookup time) but each frame seems to be less variable in timing. I'd much rather have a slower speed overall than a fast but insanely variable speed, especially when I have more being drawn on screen.

https://jsperf.com/global/4

That JSperf may explain this a little better than I can

You may have said that these optimizations are penny wise but pound foolish ;) but when added up, these optimizations seem to really help the game's performance on low end mobile devices.

EDIT:

Is this code also drawing sprites which aren't on screen?
Maybe some browsers are smart enough to know that something is being drawn offscreen but is this something I can always rely on?
« Last Edit: 2017-12-30, 04:17:22 AM by Plush »

Plush

  • Regular
  • **
  • Posts: 22
    • View Profile
    • Email
Re: Version 2.3 Feature Suggestions
« Reply #16 on: 2017-12-31, 07:19:04 PM »
One thing

https://www.ibm.com/developerworks/library/wa-canvashtml5layering/

This seems like a good way to handle MapLayer.js  :)

But then again I'm unsure how every browser handles multiple canvases.
« Last Edit: 2017-12-31, 07:32:20 PM by Plush »

bluemonkmn

  • SGDK Author
  • Administrator
  • Fanatic
  • *****
  • Posts: 2761
    • ICQ Messenger - 2678251
    • MSN Messenger - BlueMonkMN@gmail.com
    • View Profile
    • http://sgdk2.sf.net/
    • Email
Re: Version 2.3 Feature Suggestions
« Reply #17 on: 2018-01-02, 07:58:57 AM »
The game seems a tad slower overall (With the added scope lookup time) but each frame seems to be less variable in timing. I'd much rather have a slower speed overall than a fast but insanely variable speed, especially when I have more being drawn on screen.

Consistent speed was my reason for using a method like setInterval to draw frames instead of requestAnimationFrame. Did you consider comparing the result against the stock SGDK2 behavior with a constant (low) frame rate limit set. Of course if you set the limit too high, it won't be hit, but if you set it appropriately low, it should be relatively consistent.

Are you also aware that you can pre-create most sprite instances and use "ActivateSprite" instead of add sprite so that you're not creating and deleting objects?

Is this code also drawing sprites which aren't on screen?
Maybe some browsers are smart enough to know that something is being drawn offscreen but is this something I can always rely on?

I believe the SGDK2 code is smart enough not to draw sprites that don't intersect the visible view on the screen. You shouldn't have to rely on the browser's smarts for that.
« Last Edit: 2018-01-02, 08:06:10 AM by bluemonkmn »

Plush

  • Regular
  • **
  • Posts: 22
    • View Profile
    • Email
Re: Version 2.3 Feature Suggestions
« Reply #18 on: 2018-01-02, 11:44:04 AM »

I believe the SGDK2 code is smart enough not to draw sprites that don't intersect the visible view on the screen. You shouldn't have to rely on the browser's smarts for that.

Code: [Select]
   for(si = 0; si < this.sprites.length; si++) {
      var curSprite = this.sprites[si];
      if (!curSprite.isActive) continue;
      var frames = curSprite.getCurFrames();
      if (frames == null) continue;
      var frameSet = frameSets[curSprite.states[curSprite.state].frameSetName];
      if (typeof frames == 'number')
         frameSet.frames[frames % frameSet.frames.length].draw(ctx, curSprite.x + this.currentX, curSprite.y + this.currentY);
      else
         for(var fi = 0; fi < frames.length; fi++)
            frameSet.frames[frames[fi] % frameSet.frames.length].draw(ctx, curSprite.x + this.currentX, curSprite.y + this.currentY);
   }

Okay, I must either be fatally misunderstanding something in MapLayer.js or I'm using an earlier version of SGDK2. Where in your Javascript code is it checking for whether it's within the visible rectangle? If I am, I'm sorry, please correct me! :-X (Either way, I manually wrote a check that would only draw sprites within the visible region of the screen using curSprite.layer.currentX/Y.)

Quote
Consistent speed was my reason for using a method like setInterval to draw frames instead of requestAnimationFrame. Did you consider comparing the result against the stock SGDK2 behavior with a constant (low) frame rate limit set. Of course if you set the limit too high, it won't be hit, but if you set it appropriately low, it should be relatively consistent.

I'd say I'm having smoother performance with the edits:

The only frame-rate that the original code was consistent at was 15FPS (At least on my slower Android phones) (PC seemed to be a mixed bag, but it was slightly higher around 25FPS). That original code was even more capricious in performance than this code I'm using now ;) The setInterval handler was reported by Chrome to take around 100ms, sometimes even reaching 200ms for scenes with tons of tiles.

With this newer code, performance is stable at 25FPS on even the worst phone I can find. My requestAnimationFrame handler is reported to take 50-60ms at worst on an Android webview, which is close enough to 25FPS to not make things noticeable, despite flagging a Violation in console.

Code: [Select]

Are you also aware that you can pre-create most sprite instances and use "ActivateSprite" instead of add sprite so that you're not creating and deleting objects?

Nice trick. It was mainly the tile drawing that I made changes to, not the sprite drawing. Tons of local variables are created it seems whenever tiled layers are drawn and I'm trying to refactor this to only use global variables rather than polluting with local variables.

So I never call the AddSpriteHere method. As a Javascript developer I've had issues in the past with the garbage collector for high sprite games, and for things such as bullets, I try to create once them and activate them, then move them to where I need them.

The ActivateSprite is a rule I hadn't seen, I've been manually activating these by setting their properties. I'll look at it.

Most of the performance variance I have isn't coming from me creating or destroying objects, it seemed that it laid somewhere within the rendering code, and ctx.drawImage() was probably one of many other things I had to look into.
 8)
« Last Edit: 2018-01-02, 12:02:40 PM by Plush »

bluemonkmn

  • SGDK Author
  • Administrator
  • Fanatic
  • *****
  • Posts: 2761
    • ICQ Messenger - 2678251
    • MSN Messenger - BlueMonkMN@gmail.com
    • View Profile
    • http://sgdk2.sf.net/
    • Email
Re: Version 2.3 Feature Suggestions
« Reply #19 on: 2018-01-02, 12:41:22 PM »
Sorry for possibly misleading you on the visible sprite filtering. I know it was done in the C# code, but it appears that may not have carried over to the JS code in my hastiness to get things "done". This is the C# code that filters visible sprites in LayerBase.cs:

Code: [Select]
   public bool IsSpriteVisible(SpriteBase sprite)
   {
      return sprite.isActive && sprite.GetBounds().IntersectsWith(VisibleArea);
   }
   public void InjectSprites()
   {
      for (int i = 0; (i < m_nInjectStartIndex) && (i < m_Sprites.Count); i++)
      {
         SpriteBase sprite = m_Sprites[i];
         if (IsSpriteVisible(sprite))
            AppendFrames(sprite.PixelX, sprite.PixelY, sprite.GetCurrentFramesetFrames(), sprite.color, -1);
      }
      for (int i = m_nInjectStartIndex; (i < m_nAppendStartIndex) && (i < m_Sprites.Count); i++)
      {
         SpriteBase sprite = m_Sprites[i];
         if (IsSpriteVisible(sprite))
            InjectFrames(sprite.PixelX, sprite.PixelY, sprite.GetCurrentFramesetFrames(), sprite.color);
      }
      for (int i = m_nAppendStartIndex; (i < m_Sprites.Count); i++)
      {
         SpriteBase sprite = m_Sprites[i];
         if (IsSpriteVisible(sprite))
            AppendFrames(sprite.PixelX, sprite.PixelY, sprite.GetCurrentFramesetFrames(), sprite.color, 1);
      }
   }

Plush

  • Regular
  • **
  • Posts: 22
    • View Profile
    • Email
Re: Version 2.3 Feature Suggestions
« Reply #20 on: 2018-01-03, 02:19:42 PM »
That's fine, I'm sorry for bothering you about it. I was going to ask but I imagined that maybe you had a smart reason (Like relying on the browser to not draw these sprites, since some browsers are faster for offscreen calls to drawImage() and take less time to execute it)


My project only compiles for HTML5. It gives errors for C#. Well, I'm abusing the rule system to use Javascript code, which is why. I have very little interest in the C# runtime so I guess everything I'll discuss will be HTML5 related.  ;)

As for C# discussion, I'm not really as interested.. ;D the HTML5 stuff is the coolest part of SGDK2. HTML5+node = useful for 99% of all tasks, plus multiplatform.

I see little reason to continue using the C# stuff but maybe I just don't understand the subtleties between C# and HTML5 generated games! SGDK2 should focus on HTML5.



The requestAnimationFrame code with my other changes seem to give me a consistent framerate for the main game loop.. with less random game slowdowns even when I'm standing still If the screen is ready to draw and the timing is right, it draws, then updates. If the timing isn't right, it just draws.

This gives an amazing refresh rate asynchronously independent of the limited framerate, even if I reduce the FPS of the game to something small like 10. I'm using a css transform to reposition the canvas, so you can see when the screen refreshes (You get a fast flicker-like CRT effect, it's hard to describe but it's beautiful).

The longest violation I get in console for my call to mainLoop is 70ms, and it's rare.. Most often it's 50ms and that's only when I draw+update() in the same frame.

These optimizations seem useful.

I mentioned a ton of other points and ideas above, you can look into them if you want :)

Namaste

« Last Edit: 2018-01-03, 02:30:50 PM by Plush »