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
there's no optimization there at all
but 1000 is not a lot
Hmm, and that’s per second… even per frame that’s not a lot.
Just curious, is this Clojure or ClojureScript?
Not that it matters much
clojure
it might be too clever
too many maps
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.
yeah
There’s this, but I haven’t looked at the code yet: https://github.com/markmandel/brute
the basic design is a hash-map where the keys are component names and values are maps of entity ids to component values
yeah that looks familiar
Hey, that's mine 😎
that's basically what I'm doing
Let me know if there is anything you'd like to see in brute
is that (transient system) really doing anything in add-entity?
since it's just one assoc?
I would imagine you'd want to batch a lot of assoc! calls together
Hmnn... I wrote that a while ago, have to take a peek
I hit system twice in that function
but great minds think alike :simple_smile:
this is exactly what I'm doing
twice in add-component but once in add-entity
There an Assoc and a retrieval
There's two threading macros there
does transient affect get
too?
I have to look at it, but I wonder if I can just make it an update-in
probably
would have to benchmark the difference
@markmandel This is neat. I'm curious what the performance is like. I'll see if I can give it a try this weekend.
Be easier to read at least
And if there’s room for optimizations.
Please do!
I'm writing a top down racer with it, but it's not exceptionally complicated (yet?)
Update-in would be far more readable though
this is my basic approach https://gist.github.com/jcromartie/a6c7ecc1402bb29214e9
On my phone atm , can't look 🙁
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
Seems to yield good results
the benchmarks are at the bottom
Specter is cool
OK so, some more relevant numbers
I can update 8000 entities 60 times per second
that would be really hard to keep in sync with graphics at 60 fps but it could work
of course that's a really simple system with just random velocity and position += velocity
with the velocity changing every tick
just found this
commenting on the "problems" with some Clojurescript game demo
I don't know how someone got the idea that just iterating over a 100 element list would take 16 ms
I can reduce over a 1 million element vector in less than that
but this post was a long time ago
almost 4 years ago
4 years, wow. ClojureScript was around then? But yeah, both ClojureScript and JS engines have improved greatly since then.
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
one thing we did in C/C++ was to keep a free list
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
it's not that hard to do, just a little mind bending at first, but an old old trick
I strongly discourage using hashmaps if you can avoid it..
i suppose for components themselves, it's fine
but when you iterate and pool things, you probably want to be using tightly packed arrays
I wish i knew more about how clojure data structures interact with cache lines, but I suspect not so well
anyway, somewhere there's a GDC or similar conference paper where a guy has good info about most of what I just mentioned
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.
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
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
And unchecked math can help
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.
hope that helps some
@solicode July 2011 http://clojure.com/blog/2011/07/22/introducing-clojurescript.html
@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 ...} ...}
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
if at all
that avoids iterating per entity, etc.
@jcromartie sounds good, assuming you are doing the diff check efficiently with clojure and not a deep comparison
yeah the component values are clojure values so equality is very cheap to check
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
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
I believe a couple of Java entity systems use UUID
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
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
I want to see how this system works for a Zelda-style game, I’m not building Skyrim yet :simple_smile:
but yes, those were my thoughts exactly about integers vs guids
though in Clojure the standard number is a 64-bit signed Long
the standard integer type
so when you type a literal 42
you get a long
not that you’d be typing entity ids into source code
but I am sure I could get away with guids and never see performance issues