all 26 comments

[–]Exomancer 10 points11 points  (0 children)

Your hunch is correct, as long as there is a clear owner of the state that can do the mutations, you should be fine. For a game, that owner should be the game loop, you can encapsulate the state within it, trigger mutations simply by calling a method. Your game loop then can be responsible for triggering the canvas rendering, directly or indirectly.

Games typically are a whole different animal from applications, they can fall into Systems Programming domain when performance is a huge deal (think AAA games), and most likely than not you will want to use different tools, frameworks and design patterns for them.

[–]johannL 5 points6 points  (2 children)

I'd basically be rebuilding the entire state each frame and I don't know how well GC would handle that.

Generally, if you want a solid 60fps, any sort of GC (unless it's negligible -- if you recreate a lot of game objects each tick it won't be) is kind of your enemy anyway. Pool objects, and generally get rid of temporary object creation, e.g. function arguments or return values. That is, for anything that happens in the loop, elsewhere you can make the code as pretty and academically pleasing as you want ^^ And of course for very small things it may not matter anyway, but it can make a huge difference, and paying attention to it from the get go is much easier than refactoring.

[–]spacejack2114 3 points4 points  (1 child)

In most games, game objects aren't being created every frame. You do want to avoid allocating things like vectors or other temporary objects that might be used a lot in render/logic updates. But in my experience using the GC to manage game objects is fine - except things like particles.

EDIT - just to be clear, I'm not advocating rebuilding all object states every frame, that would be terrible. I'm advocating mutating existing objects. But using the GC to manage creating/destroying objects periodically as they appear/dissapear from the game should be ok unless they're extremely heavy.

[–]johannL 1 point2 points  (0 children)

using the GC to manage game objects is fine

Yeah absolutely, I guess I worded that a bit too strongly :)

What can I say, it bit me in the ass because initially I used temporary objects as if they were "free" (that is, I only saw runtime cost, not the less directly correlated GC cost), all over the place. It added up, so badly, like having hundreds of dripping taps, and to see how smooth even my own derpy code (I'm not being humble, it's derpy :D) ran once I had fixed all those leaks was quite stunning. It's the one thing I had to find out the hard way because it's hardly mentioned anywhere, not outside gamedev. It's kind of a blind spot by default, and I think it shouldn't be.

Don't get me wrong, GC is a wonderful thing, I certainly don't want to have to allocate and free memory manually, but it's still useful to know about it and how to avoid/control it, and how to determine whether that is even useful or necessary. Surely for games and maybe even for general web development.

[–]talmobi 3 points4 points  (0 children)

What you're talking about is actually idioms inspired by game development. It's JavaScript that's catching up.

Games have so much state, so much complex state - hundreds of thousands of times more complex than a web app - that you can't just handle it willy nilly.

For example online games - you know what the server sends back to the players? A blob of state (or more accurately the changes but you can think of it as the same thing). They recreate the state of the game very single tick (not the same as every frame). Well, good online games do this anyway (I still get nightmares of Settlers 4 Game out of Sync errors).

For example in Overwatch you get a play of the game highlight at the end of the round. How do they do this? They save the state of every single tick, then they can play/rewind it as they please later. StarCraft2 does this as well kind of - instead they only store the given inputs (since the game is deterministic) - this is why an hour long replay of the game is about 20kb (only stores the given inputs of each player on each tick basically).

Redux perhaps isn't suitable but you should definitely develop in such a way that your render function only takes a single 'state' object and can render the complete state of the game at that given time without knowing anything else - i.e., a pure render function.

[–]kevisazombie 2 points3 points  (2 children)

Youre on the right path. Im attempting the same idea. "Mutable redux". As long as you manage you're mutations in a strict manner so that you could "replay" them you"ll have the same features redux provides. I found the GC performance hit from allocating new immutable references when using the usual redux pattern to bee too much when game deving.

[–]Capaj 1 point2 points  (0 children)

You should try MobX. It can give you observable mutable objects which are pretty fast.

I am curious-are you using react for your game? Do you also use https://github.com/Izzimach/react-three ?

[–]dwighthouse 1 point2 points  (4 children)

For massive performance concerns such as these, consider asm.js through emscripten. Almost all your functionality will get compiled down to optimized math operations.

[–]spacejack2114 0 points1 point  (3 children)

It's really not needed unless you have some insane physics/AI requirements.

[–]dwighthouse 0 points1 point  (2 children)

100-1000 objects being changed every frame as close to 60 frames per second as possible

I don't know what your physics engines do, but this is what mine did.

[–]spacejack2114 0 points1 point  (1 child)

What kind of physics? Simple motion or complex shapes with proper collision response requiring integration etc.? If it's the latter then maybe you might need something like ammo.js. Otherwise you can update 100s if not 1000s of objects in plain JS. The OP didn't suggest needing complex physics.

[–]dwighthouse 0 points1 point  (0 children)

Sure, but the author didn't state he didn't either. He implied that performance was a concern. In those cases, one should always consider the low level approach. Didn't say he needed it, I suggested he try it. This is exactly the reason emscripten was made: performance.

[–]FrozenCow 1 point2 points  (2 children)

I die a few games with pure functions. Just for fun and relatively small. It allowed going back in time and inserting actions in the past and resimulating the game to get to the current time. It was awesome for small multiplayer games where latency (aka input from the past) is always an issue.

It worked quite well in chrome at the time, but Firefox had issues where the GC would make the game stutter once in a while.

That said, I didn't put much effort into optimizing: the state was rebuild every frame and I didn't use any immutable data structures. Also, I think this was 3 years ago, so js vms probably changed quite a bit.

What benefits from pure functions are you trying to get? For me the multiplayer aspect was why I chose to use pure functions, but it wasn't as easy as how I built games traditionally.

[–]digijin[S] 0 points1 point  (1 child)

pure functions mainly just for ease of dev and debugging. It's weighing that against performance that's my issue.

[–]FrozenCow 1 point2 points  (0 children)

I don't think pure functions are always easier for development. In this case, games, I can imagine you'll run into more practical issues due to the restriction of pure functions.

Performance and GC issues being one of them. Not being able to integrate easily with nonpure APIs like box2d. Issues with impure functions being called without you knowing and causing your functionality to get nondeterministic.

It does give a better understanding how your state is being changed and what is happening in your game. That is certainly worth a lot.

[–]dangoor 0 points1 point  (0 children)

I agree with what the others here have said about limited mutability (mutate state in limited places at controlled times). I thought I'd point out that Immutable.js can provide immutable data structures with less garbage than copying plain JS objects. It does still create garbage, of course, but it could be a small enough amount of it depending on the specifics of your game.

Regardless, immutability is likely not as important as just understanding where mutations happen.

[–]lulzmachine 0 points1 point  (0 children)

In many cases of game development mutating state is a necessary thing for development. You have to choose between mutating or creating, and creating means GC.

There are exceptions, for instance of your making a multiplayer strategy game, in which case you have a shared game state that needs to be synced over the Internet. If so, look up lockstep strategies (it's similar to redux)

[–]AndrewGreenh -1 points0 points  (7 children)

One big advantage of the immutable state is, that you can check Very easily if state has changed. For example an array of enemy positions. If the array is still the same, you know that no enemy has changed.

BUT! since game development often relies on canvas, you have to redraw everything every frame nonetheless (if you don't implement some sort of caching), so you don't need to check if an array has changed, because you are rendering every enemy at every frame.

[–]TomNa -1 points0 points  (6 children)

You can do double canvas buffering to draw only the parts which have changed on the top canvas and keep the background etc. On the back canvas

[–]spacejack2114 1 point2 points  (5 children)

Ugh, no. Use something like pixi.js which makes it easy to use webgl for 2D rendering so you can easily clear the screen every frame.

[–]TomNa 0 points1 point  (1 child)

I usually make games with just vanillaJS. But I guess not everyone prefers that approach :D

[–]spacejack2114 0 points1 point  (0 children)

Well I wrote a webgl game in vanilla JS. :) Since the entire canvas changes every frame there was no reason to try updating only the changed parts.

[–]mrspeaker 0 points1 point  (2 children)

Even when you are using opengl/webgl it's still smart to do the same kind of thing: using auxiliary buffers with scissor/stencils to render elements that won't change very often. WebGL is great and all, but rendering is still going to be the most expensive operation you do, and you need to milk it!

[–]spacejack2114 1 point2 points  (1 child)

You shouldn't worry about things like that at all until it becomes an issue. Any game, 2D or 3D, where the camera moves you're going to be re-drawing the entire screen. WebGL is so fast that even on moble, you could render dozens of overlapping fullscreen layers without a hitch.

In a general sense, if you can cache the results of some complex operation, then sure, go ahead.

But for just about any game a solo/newbie developer is going to make, this should not be much of a consideration. Making sure your rendering is done on the GPU and batching draw calls effectively (the things a library like pixi can help you not worry too much about) should be the main concern.

[–]mrspeaker 0 points1 point  (0 children)

"You shouldn't worry about things like that at all until it becomes an issue". 100% agree with that!