5 ms·
Game performance optimization – A practical example from Industry Idle
- reitzensteinm 5y agoIndustry Idle is cool - didn't realize you're on HN! One of the most frustrating things about using a web stack is issues like this where you've got an embarrassingly parallel problem and you can't use threads easily. Even in Unity non ECS, which is fairly constrained, high performance dot processing would be fairly straight forward. I'm working on an idle web game using ClojureScript, and it's very much the other end of the spectrum. The game design is event driven and state small out of necessity.
- deleted 5y ago[deleted]
- avereveard 5y agoMissing one important thing: if your object goes in a straight line at a fixed speed with a known start and end position, instead of going through the game engine tweens you can build an equivalent CSS animation between the start and stop position and it'll be blazingly fast.
- fenomas 5y agoBrowser games like this are normally drawing pixels into a canvas, not creating DOM objects for each game entity.
- avereveard 5y agoI know, that's not a limit tho. you get the canvas origin from the engine and offset the animation. you keep a pool of div on the side, and when you need one depending on your culling result you attach the sprite image as a background, tweak the css animation start,end and attacch it to the overlay. you keep a relative offseted parent on the div, so that as the canvas scroll, you can adjust it wherever you go. overflow hidden takes care of the visibility if they go out of the canvas area. hybrid canvas html game are a joy to work with, there's no reason to limit oneself to canvases as long as you can export and import events to the external world. I've built some, and having for example the ux in html sending control events to the canvas cut the development time a lot. all these 2d libs have painful ux api, with dreadful limitation, while in html you can just slap whatever stile and it'll always be crisp.
- Kiro 5y agoSounds massively complex compared to just a stateless rendering pipeline clearing the canvas and drawing the sprites with drawImage on every RAF.
- fenomas 5y agoI do hybrid canvas/DOM gamedev myself, and it's fine for overlaying a few pieces of UI and styled text and so on. But when you have 30,000 of something like in TFA, there is no world where it makes sense to draw them via the DOM.
- lmm 5y agoWhy should it be faster? It's doing the same work. In modern browsers with their heavily optimized and JITed JavaScript runtimes, an animation in JavaScript will often perform better than the equivalent CSS. (And that goes double if you're using WebGL and can just hand it off to the graphics hardware).
- 10x-dev 5y agoI'd like a citation for the claim that JavaScript animations are often faster than CSS animations. If it is true, it would be a very interesting read.
- krageon 5y agoIt was absolutely untrue five years ago (I'm sorry, no data - this is from experience). Perhaps JS interpreters have come a long way since then.
- avereveard 5y ago> if you're using WebGL iphone's chrome entered the chat
- sanojack 5y agoHero
- fenomas 5y agoLots of cool stuff here. But on the last point about managing renders, for my money any browser game is in a state of original sin if its logic and render loops are tightly coupled. The Right Thing To Do is to run logic and rendering at separate cadences, with rendering driven exclusively by requestAnimationFrame. Then when the browser is minimized, RAF stops firing and rendering is skipped entirely (on top of the other benefits of decoupling logic and rendering).
- bartread 5y agoI'd go further: for many games the right thing to do is pause execution entirely if the window is minimised or the user switches to another tab or application. Obviously not possible for online multiplayer games, but a good practice for single player. Rendering can be but isn't always the most expensive portion of a game's execution, but if you have a lot feeding into that in terms of object updates and game logic you can find yourself chewing a surprising amount of CPU time, and therefore power, which isn't great if the host device is running on batteries.
- tpxl 5y agoPlease have this setting be configurable if you do this. I hate when games pause when I alt-tab to another window on another screen while I'm waiting for something to finish in a game.
- Tyriar 5y agoFor many games, idle games are a special case though, I don't think anyone who plays an idle game would want the logic loop to pause. They typically take a long time to play so minimizing and checking back every now and then is a very common thing.
- insraq 5y agoHi, the game is already doing this. As mentioned in the article, logic tick is running at a much lower frequency than rendering loop (30/60FPS). When playing in the browser and is minimized, the game will simply pause - rendering and logic. Browsers will throttle timers which will cause inaccurate logic tick as well. Most idle gamers actually do not like this behavior [1] - people prefer the game to run in the background as well. The "background mode" mentioned in the article is about the Steam version, which runs in Electron and I have to set `backgroundThrottle: false` flag. In this case, Electron will not throttle timers and I can safely disable rendering while leave the logic running. [1] https://www.reddit.com/r/incremental_games/comments/seid8w/chrome_active_window_hardware_fix/ https://www.reddit.com/r/incremental_games/comments/seid8w/c...
- moritonal 5y agoWhilst I imagine the final line "I cannot get a discrete GPU at a reasonable price" is more a frustration, never underestimate the importance of working with restraints. If you develop against a terrible GPU you're forced to fix issues like this quickly. Although I'll of course agree a better computer helps the iterative dev cycle.
- lelandfe 5y agoIt also helps with user empathy! I work on the web rather than on games, but I used to have my small team walk over to Best Buy (American electronics store) and load our site on the worst laptops we could find to actually see what performance is like for worst-case users. You forget what “real people” experiences are like when you’re on your $2000 beefy rig.
- deleted 5y ago[deleted]
- archi42 5y agoHuh? I am surprised that you choose to use a "setTimeout"-like callback there. Everyone on the internet is an expert (/s), so here is my intuition: If a mine produces $stuff at a rate of x $stuff/s, the factory it is sending to will have a supply of x $stuff/s coming from that mine; the factory's total supply will be the sum of all these x from the various mines that are sending to that factory. So what I would do is a setTimeout("increase supply of stuff for target factory by x every 2.5s", 2.5) to link a mine to a factory (and "decrease..." for unlink). This simulates that the first resources take some time to arrive. Are the deliveries continuous and not bursty? Then the cheap way is then to increase the resources every second (or whatever your ticklength is) for the appropriate fraction by iterating over the factories - which are probably much fewer than the resources in flight. Players might notice this. Are the deliveries bursty? Keep an array of pairs (burst_amount, burst_time, burst_stride) and when iterating over the factories to add resources, add those for which "burst_time modulo tick_time == burst_stride". This requires some discretisation, but would the players even notice...? Just make sure that if you draw resources on the screen that they arrive roughly in sync with the actual resource update in the factory. Bonus: This could go in a thread of it's own that only takes care of these updates. But I am not sure if that can be done in your language like I would do it in C++. Curious why you've chosen to use massive amounts of timeouts with the respective overhead.
- wwilim 5y agoBecause that was closest to the idea of what's conceptually happening in the game world, and it was the quickest to implement - he mentions it in the article
- insraq 5y ago> Are the deliveries continuous and not bursty They are not "bursty", but they can fluctuate. Power fluctuation, fuel shortage or upstream supply chain issue could cause a building to skip a production cycle - and impact all its downstream. In fact that is a core challenge in the game. So the core game logic has to be simulated, not calculated. Doing multithread in JavaScript (via WebWorker) is kind of painful.
- 5y ago
- taylorius 5y agoI think a well written GPU renderer (in webGL for example, if running in the browser) ought to be able to render 30,000 dots at 60fps, even on an intel GPU.
- lentil_soup 5y agoThanks for the write-up. Always cool to see how different people solve these kind of problems. I don't know much about cocos2d but since the dots are purely visual, do they need to be game objects? Couldn't you keep their transform in a separate data structure which you can update independently and pass to a shader (or similar) for rendering? I'm thinking something similar to how particles in VFX are usually done.
- insraq 5y agoThanks for the comment. It is indeed something I've been considering. In fact, lots of the optimization done here is to move the logic away from the game objects , which should enable the flow you've described. I ended up not doing it in this iteration because: - cocos2d's custom shader support is kind of poor, especially documentation is pretty much zero - the dot is not "purely" visual. For example you can trace and highlight a resource's movement. This is not a blocker per se, but requires more work
- tobyhinloopen 5y agoI’ve done some hobby game-developing in the browser and I always end up frustrated by the poor and inconsistent performance. Using Unity to compile to the browser seems to yield much better performance out of the box
- Aliyekta 5y agois the profiler a home-grown one? or publicly available?
- lelandfe 5y agoLooks like Chrome Dev Tools: https://developer.chrome.com/docs/devtools/evaluate-performance/ https://developer.chrome.com/docs/devtools/evaluate-performa...
- mako-taco 5y agohave you considered moving the simulation to worker(s), and providing the game state to the render function through a SharedArrayBuffer?
- yxhuvud 5y ago> To optimize this, instead of scheduling a function call, we simply add the information to a Map - the arrival time, resource, amount, and target building. Then in the tickDots method, we loop through the map and if a resource has arrived, we remove it from the Map and add the resource to the target building. This reduces the frame time from barely under 16.7ms to a comfortable 11ms. What you have implemented here is a priority queue, with O(1) insertion and O(n) deletion. It is easy to get down priority queues to O(log n) deletion without sacrificing insertion speed noticeably much. So unless you have some good reason to iterate all resources every iteration, changing to a better priority queue implementation will most probably yield you even more of a benefit.
- insraq 5y agoYou are right. A heap/priority queue would be good. However the resources needs to be iterated and ticked every frame - to update the rendering data (transform) and perform culling (because the viewport might change - a player might move or zoom the camera).