clojure-gamedev

2016-02-25T01:33:25.000006Z

so, my totally naive entity system has 1000 entities with position and velocity components, with all of those components being updated in one tick, and it can do that 1000 times per second

2016-02-25T01:33:51.000007Z

there's no optimization there at all

2016-02-25T01:36:03.000008Z

but 1000 is not a lot

2016-02-25T01:45:48.000009Z

Hmm, and that’s per second… even per frame that’s not a lot.

2016-02-25T01:45:56.000010Z

Just curious, is this Clojure or ClojureScript?

2016-02-25T01:46:00.000011Z

Not that it matters much

2016-02-25T01:48:14.000012Z

clojure

2016-02-25T01:49:10.000013Z

it might be too clever

2016-02-25T01:49:11.000014Z

too many maps

2016-02-25T01:50:48.000015Z

I was going to attempt this with ClojureScript (just a simple game using ECS), and I have a feeling I'm going to run into the same issues you're having. I'm curious where the bottleneck is.

2016-02-25T01:50:57.000016Z

yeah

2016-02-25T01:51:55.000017Z

There’s this, but I haven’t looked at the code yet: https://github.com/markmandel/brute

2016-02-25T01:51:58.000019Z

the basic design is a hash-map where the keys are component names and values are maps of entity ids to component values

2016-02-25T01:52:30.000020Z

yeah that looks familiar

markmandel 2016-02-25T01:52:37.000021Z

Hey, that's mine 😎

2016-02-25T01:52:37.000022Z

that's basically what I'm doing

markmandel 2016-02-25T01:53:44.000023Z

Let me know if there is anything you'd like to see in brute

2016-02-25T01:53:46.000024Z

is that (transient system) really doing anything in add-entity?

2016-02-25T01:53:53.000025Z

since it's just one assoc?

2016-02-25T01:54:11.000026Z

I would imagine you'd want to batch a lot of assoc! calls together

markmandel 2016-02-25T01:54:28.000027Z

Hmnn... I wrote that a while ago, have to take a peek

markmandel 2016-02-25T01:55:24.000028Z

I hit system twice in that function

2016-02-25T01:55:25.000029Z

but great minds think alike :simple_smile:

2016-02-25T01:55:28.000030Z

this is exactly what I'm doing

2016-02-25T01:55:52.000031Z

twice in add-component but once in add-entity

markmandel 2016-02-25T01:56:14.000032Z

There an Assoc and a retrieval

markmandel 2016-02-25T01:56:45.000033Z

There's two threading macros there

2016-02-25T01:58:32.000034Z

does transient affect get too?

markmandel 2016-02-25T01:58:48.000035Z

I have to look at it, but I wonder if I can just make it an update-in

2016-02-25T01:59:32.000036Z

probably

2016-02-25T01:59:43.000037Z

would have to benchmark the difference

2016-02-25T02:00:06.000038Z

@markmandel This is neat. I'm curious what the performance is like. I'll see if I can give it a try this weekend.

markmandel 2016-02-25T02:00:07.000039Z

Be easier to read at least

2016-02-25T02:00:12.000040Z

And if there’s room for optimizations.

markmandel 2016-02-25T02:01:06.000041Z

Please do!

markmandel 2016-02-25T02:01:36.000042Z

I'm writing a top down racer with it, but it's not exceptionally complicated (yet?)

markmandel 2016-02-25T02:02:06.000043Z

Update-in would be far more readable though

2016-02-25T02:08:38.000044Z

this is my basic approach https://gist.github.com/jcromartie/a6c7ecc1402bb29214e9

markmandel 2016-02-25T02:13:22.000045Z

On my phone atm , can't look 🙁

2016-02-25T02:18:46.000046Z

I really don't know much about Specter or if this would even be applicable, but its precompilation feature looks interesting: http://nathanmarz.com/blog/functional-navigational-programming-in-clojurescript-with-sp.html

2016-02-25T02:19:10.000048Z

Seems to yield good results

2016-02-25T02:19:19.000049Z

the benchmarks are at the bottom

2016-02-25T02:25:50.000050Z

Specter is cool

2016-02-25T02:34:07.000051Z

OK so, some more relevant numbers

2016-02-25T02:34:19.000052Z

I can update 8000 entities 60 times per second

2016-02-25T02:34:40.000053Z

that would be really hard to keep in sync with graphics at 60 fps but it could work

2016-02-25T02:35:09.000054Z

of course that's a really simple system with just random velocity and position += velocity

2016-02-25T02:35:19.000055Z

with the velocity changing every tick

2016-02-25T02:54:29.000057Z

lol @ https://news.ycombinator.com/item?id=5116615

2016-02-25T02:54:31.000058Z

just found this

2016-02-25T02:54:44.000059Z

commenting on the "problems" with some Clojurescript game demo

2016-02-25T02:55:34.000060Z

I don't know how someone got the idea that just iterating over a 100 element list would take 16 ms

2016-02-25T02:55:54.000061Z

I can reduce over a 1 million element vector in less than that

2016-02-25T02:56:06.000062Z

but this post was a long time ago

2016-02-25T02:56:09.000063Z

almost 4 years ago

2016-02-25T03:03:07.000064Z

4 years, wow. ClojureScript was around then? But yeah, both ClojureScript and JS engines have improved greatly since then.

2016-02-25T03:53:36.000065Z

I may have some tips for you guys speeding up your iterations and about the entity system structure, but I am kind of beat for the day

2016-02-25T03:53:45.000066Z

one thing we did in C/C++ was to keep a free list

2016-02-25T03:54:32.000067Z

we used tricks where we could index back into an array and keep it nice and packed by mucking about with the ids of entities and such matching array indicies

2016-02-25T03:54:45.000068Z

it's not that hard to do, just a little mind bending at first, but an old old trick

2016-02-25T03:55:03.000069Z

I strongly discourage using hashmaps if you can avoid it..

2016-02-25T03:55:11.000070Z

i suppose for components themselves, it's fine

2016-02-25T03:55:31.000071Z

but when you iterate and pool things, you probably want to be using tightly packed arrays

2016-02-25T03:56:00.000072Z

I wish i knew more about how clojure data structures interact with cache lines, but I suspect not so well

2016-02-25T03:56:39.000073Z

anyway, somewhere there's a GDC or similar conference paper where a guy has good info about most of what I just mentioned

2016-02-25T04:04:13.000074Z

used to be some good info here - http://bitsquid.blogspot.com/2011/09/managing-decoupling-part-4-id-lookup.html. It's about C++, but a lot of it is applicable. Actually I'd pay a lot more attention to anything about C++ rather than Clojure/Java when thinking about entity systems. It's been a bit since I looked, but most of the info is pretty bad and has awful practices like using uuids for entity ids, tons of boxing, iterating per entity instead of per component type, pub-sub, etc. and things like that. 95% of what is out there is misinformation or overly naive and is either just wrong or will kill your performance.

2016-02-25T04:07:12.000075Z

I'd quickly add on the clojure side to avoid sequences and do ugly things like loop/recur rather than some of the other "good clojure" practices if you need performance in your loops

2016-02-25T04:07:43.000076Z

also try to use any bitwise operations when possible. Lots of great tricks here for more than you might think to squeeze some juice out of java/clojure

2016-02-25T04:07:50.000077Z

And unchecked math can help

2016-02-25T04:09:15.000078Z

finally, use pooling as much as possible as we discussed earlier, whether cljs or clj. It really does make a difference. First pool everything, then bench. And be careful of your benching techniques, i.e. don't rely on using (time), cold jvm, etc.

2016-02-25T04:09:23.000079Z

hope that helps some

2016-02-25T13:22:42.000082Z

@hugesandwich: I think the basic API I have leaves room for optimizations later; I am using integers for entity ids, and hash maps to store {component-name {entity-id component-value ...} ...}

2016-02-25T13:24:40.000083Z

when updating a component map (that is a map of all entity ids to component values for single component, like position or health etc.) I use a transient and only assoc! a new value when it's non-nil and different from the old value, so that for one tick there's only one new hash-map created per component type

2016-02-25T13:24:45.000084Z

if at all

2016-02-25T13:25:32.000085Z

that avoids iterating per entity, etc.

2016-02-25T14:18:49.000086Z

@jcromartie sounds good, assuming you are doing the diff check efficiently with clojure and not a deep comparison

2016-02-25T14:55:15.000087Z

yeah the component values are clojure values so equality is very cheap to check

markmandel 2016-02-25T18:59:22.000089Z

So I use UUIDs in brute - but that's probably because of my naivety... it's not been a performance bottleneck for me - but would be interesting to do some benchmarks

2016-02-25T19:35:07.000090Z

the reason to not use UUIDs is not just speed using them/generating them, but to more importantly have the opportunity to use them as a cheat to index back into your pools/freelists for entities

2016-02-25T19:35:22.000091Z

I believe a couple of Java entity systems use UUID

2016-02-25T19:36:25.000092Z

another reason is for better or worse, you can make integers play nicely with tools and pre-define some ids if needed. Obviously things can go very wrong with regard to collisions and such, but your tools and id allocator should be using the same code anyway so in practice it shouldn't be an issue

2016-02-25T19:37:44.000093Z

integers are in the end much lighter weight in every way from memory onwards. That said, you need to write a good id manager that safely allocates across threads if needed, so UUID doesn't have this issue at all

2016-02-25T20:12:44.000094Z

I want to see how this system works for a Zelda-style game, I’m not building Skyrim yet :simple_smile:

2016-02-25T20:12:57.000095Z

but yes, those were my thoughts exactly about integers vs guids

2016-02-25T20:13:54.000096Z

though in Clojure the standard number is a 64-bit signed Long

2016-02-25T20:14:05.000097Z

the standard integer type

2016-02-25T20:14:14.000098Z

so when you type a literal 42 you get a long

2016-02-25T20:14:26.000099Z

not that you’d be typing entity ids into source code

2016-02-25T20:15:30.000100Z

but I am sure I could get away with guids and never see performance issues