Fork me on GitHub
#core-async
<
2016-02-24
>
hugesandwich17:02:00

@jcromartie: I've done a lot of game programming in my past. I'm speaking more from my experience in other languages and applying what I know of Clojure, so I am sure some of what I am about to say isn't 100% true. Anyway, I don't feel Clojure is that great for games. I wish it were, but it leaves a lot to be desired due to garbage, the JVM, and tuning. Mutability is a dirty, but necessary part of most game development. If you're building a browser game or small, non-performance critical game, no problem. Otherwise, you're in for some pain, especially calling things like opengl and other libraries. Mostly my complaints are JVM related combined with additional Clojure issues, but I'm speaking from more from building larger games and working on engines. That said, where Clojure does shine in relation to game dev is that I think is it's not too hard to build a nice entity system-like pattern. In my recent code, I've modeled a lot of my architecture of what I used to do with entity systems. Game code that sees the same data structures again and again in a straight path tends to do well, so channels + threads/go-loops are good for that in theory. If you think in terms of ticks and doing that work in threads/go-loops, it's quite easy to get some beautiful looking code. You can then use multi-methods and protocols to dispatch to "system" like constructs with your other logic, which also could contain their own threads/go blocks/plain loops. Couple that with building maps as components and use atoms and such for pooling to help with the thrashing, and you have a decent architecture. You can loop through your systems and call them each tick, and modify your component maps inside each, looping and recuring with the map data if you want. You could further tune things with transients to squeeze out some more performance. Where I see some trouble with that approach is you absolutely need to be sure your processing code doesn't spend more than x time per frame or at least is able to abort pre-emptive style. It's doable with introducing a few constructs like alts, timeouts, control channels, etc. It's working for me in my code, but it's not a game. I do however use a very crude notion of a scheduler pulsing each channel I'm taking from to "wake it up" as best I can in a more deterministic fashion.

hugesandwich17:02:08

I know that's a lot and some nonsense, but hope it gets you thinking at least.

jcromartie18:02:48

wow, @hugesandwich thanks for all of that simple_smile really!

hugesandwich18:02:50

@jcromartie: no problem. Generally, just be practical. If you want to build a game in Clojure, it's perfectly fine. But you will not be able to build a 60fps behemoth AAA quality game with no stuttering. Or at least not without great pain. Everything about C++ game dev pretty much still applies, you just have to learn it's OK to not do some things you are used to doing in Clojure. Oddly enough, game development in C, C++, and other object oriented languages is becoming highly functional. In other words, people are avoiding many of the tools in their languages to develop closer to something like Clojure. The big difference is mutability is still king in a lot of places, or until we see more architecture changes on the average consumer-facing CPU especially.

jcromartie18:02:08

oh absolutely

jcromartie18:02:22

I only aspire to make something like FTL or maybe Don’t Starve

jcromartie18:02:30

that sort of level of complexity

jcromartie18:02:48

I have some ideas for how to leverage mutability and caching

jcromartie18:02:57

and the entity system idea is intriguing

hugesandwich18:02:01

That's doable, just be sure that you are aware of frame timing

hugesandwich18:02:12

You need to be predictable

jcromartie18:02:41

I was thinking of an entity system based on a map of component names to maps of ids to component values

hugesandwich18:02:44

Pooling is the easiest win with Clojure

jcromartie18:02:53

I’m not familiar with pooling in a game dev context

jcromartie18:02:57

I’ve made pong and tetris simple_smile

hugesandwich18:02:59

well very simply...

hugesandwich18:02:15

I'd keep a fixed sized array somewhere

hugesandwich18:02:45

it's fixed size because you want to explicitly control how much stuff it can have in it and so the performance and environment is predictable

hugesandwich18:02:56

also so you can cheat and use array indicies in tricky ways

hugesandwich18:02:12

so every time you allocate a component, you will remove it from the array

hugesandwich18:02:23

when you are done with it, don't just let it fall out of scope and get gc'd

hugesandwich18:02:26

instead, put it back

hugesandwich18:02:45

If you load a level or something like in FTL, each time you load a level, init the array, and when the level ends, dispose of it

hugesandwich18:02:06

same will go for any other resources that are level specific

hugesandwich18:02:12

the point is you don't allocate on demand wherever you can avoid it

hugesandwich18:02:16

you allocate on load

hugesandwich18:02:23

and de-allocate on a transition

hugesandwich18:02:32

hence why even fast games seem to take awhile to load something

jcromartie18:02:54

yeah, avoid malloc

hugesandwich18:02:57

allocating memory is super slow in general

jcromartie18:02:10

that makes sense

hugesandwich18:02:11

and it will keep the JVM behaving better

hugesandwich18:02:22

so you will also need to be careful about go blocks in this regard

hugesandwich18:02:50

FTL is doable in clojure though

hugesandwich18:02:10

You may just want to write a lot of things in Java and use interop though

hugesandwich18:02:27

probably for your graphics calls

hugesandwich18:02:24

and my last tip for you will be to batch your calls as much as possible, especially for gfx, but also for other operations. So build up something with transient! then call persistent! if you want it persistent, then send it on its way rather than sending things as you create them

slipset19:02:23

probably old news, but this talk on games in clojure is worth your time: https://www.youtube.com/watch?v=0GzzFeS5cMc

jcromartie19:02:55

I just wrote an experiment that draws a bunch of sprites with Graphics2D, and it was really slow, and then I realized I forgot to memoize my function that called ImageIO/read

jcromartie19:02:53

lol, the “time rewinding is really cliche at game dev conferences"

jcromartie19:02:57

I just demoed that last night

jcromartie19:02:01

at a game dev meetup

jcromartie19:02:35

I’m getting way off core/async here simple_smile

jcromartie19:02:50

but I also showed off “forking” a game from a given point

jcromartie19:02:15

i.e. cloning the window with the current state

jcromartie19:02:20

and continuing down a different path

jcromartie19:02:24

and throwing away windows you don’t want

hugesandwich20:02:32

@jcromartie: well we can continue the discussion some privately if you want, feel free to msg me. Trying to keep it on topic with core.async. On a semi-related note, if you use the entity system approach I describe, you should be able to put data on channels and throw it around between threads and such if you want. The added bonus is it makes time-rewinding very easy as your game's state is 100% data. Since your entities will have components attached from a pool, it's trivial to gather them all in a single pass. This is exactly how a lot of games implement saving/loading actually.

hugesandwich20:02:05

so the same would work with time rewinding. If you use channels, it should be really easy to keep everything and play back in order or a specific rate. Also useful for things like instant replay

hugesandwich20:02:43

A lot of things like this have crossover with CQRS as well. Same train of thought. You can snapshot your game state and then compress the footprint if everything has roll-forward semantics like that.

jcromartie20:02:11

how about #game-dev

jcromartie20:02:44

oh, #C066UV2MV

hugesandwich20:02:53

#C066UV2MV sounds perfect