5 ms·
I was looking at EnTT's API when building a little toy ECS of my own in JavaScript. Since I'm working in JS, I haven't really tried to copy its internal archite
by avolcano 6y ago
I was looking at EnTT's API when building a little toy ECS of my own in JavaScript. Since I'm working in JS, I haven't really tried to copy its internal architecture since it doesn't quite make sense for a higher-level GC'd runtime, but I think the public API was a really handy reference for the kinds of things a practical ECS needs to have. It's a really impressive and robust project, and the documentation is super thorough and worth a peek if you have any interest in game architecture.
(slightly off-topic, but I'm excited to have a venue to ramble a bit about this:) ECS is really cool, and I'm excited about using it more for my games. The "cheap" (de)serialization by moving all state into pure, data-only components is really fascinating - I've been playing around with building networked multiplayer games for a while now, and I'm currently experimenting with rollback netcode.
A key part of rollback is saving your state every frame so you can load it if you need to roll back, and ECS makes saving/loading easier to reason about, since you can "just" grab the components and their associated entity IDs and load if needed (EnTT, for the record, has an API for this[1], though they leave the actual (de)serialization up to you). Of course, JS's lack of a memcpy equivalent makes this much harder than what you could do in C++, which has lead me to experiment with immer[2] in my ECS, which uses structural sharing to avoid mutation, so you can get a "copy" of your state by just keeping a reference to it, as future updates will make new objects. This, of course, theoretically could make a ton of garbage (e.g. updating your position every frame would create a new Position object every frame), which is not great for high performance games. I'm not sure how bad this will be in practice - JS GC is relatively smart and fast these days, but I haven't tried doing much beyond little pong or platformer demos yet. I'd also imagine that, like, doing a deep clone of my state tree every frame (or doing the whole JSON.stringify/parse dance if I stick to primitive values) probably generates just as much garbage. Maybe if I could integrate immer with some kind of object pool it'd avoid these issues, but I have no idea how useful object pooling is in practice in JS...
[1] https://github.com/skypjack/entt/wiki/Crash-Course:-entity-component-system#snapshot-complete-vs-continuous https://github.com/skypjack/entt/wiki/Crash-Course:-entity-c...
[2] https://github.com/immerjs/immer https://github.com/immerjs/immer
- nicoburns 6y ago> but I have no idea how useful object pooling is in practice in JS... I'm pretty sure most JavaScript games use object pooling extensively. GC is definitely an issue if you want to hit 60fps. Even for non-games actually.
- gameswithgo 6y agoand 60fps is not even the gold standard anymore, 140+ is, the pickiest people for competitive shooters will want ~200+ You don't have a lot of ms
- avolcano 6y agoI'm not actually sure requestAnimationFrame() in a browser ever gets you >60 fps. At that point, honestly, you'd probably need an independent render thread (doing some degree of interpolation) and stick to a 60fps logic tick. Which you can theoretically do in JS using web workers, though I know message-passing incurs some serious overhead in that case.
- detaro 6y agoDepends on the browser, but at least in Firefox and Chrome it should match actual refresh rate if the graphics stack plays properly. (i.e. I don't know if it does if GPU acceleration doesn't fully work)
- modeless 6y agorequestAnimationFrame absolutely supports higher than 60 FPS. You can also do all rendering off the main thread with OffscreenCanvas, though you can certainly hit 144 Hz or higher without doing that.
- pjmlp 6y agoCurrently OffscreenCanvas is only supported on Chrome. :\
- fenomas 6y agoI made an ECS in JS and have been using it for some time. It's not particularly designed for the kind of rollback you're talking about, but you might find it useful as a comparison: https://github.com/andyhall/ent-comp https://github.com/andyhall/ent-comp In mine I store each entity's state for a given component as an object (e.g. `{ mass:1, velocity:[0,0,0] }`, so the internal storage of the ECS for a given component is an array of such objects. To really optimize for the "cache and rollback" kind of behavior you're talking about I guess it would be ideal for the internal storage to be flat arrays of numbers, but at first blush it seems like that would make the implementation of the ECS itself kind of hairy.
- mtsr 6y agoSince you generally want to keep a set number of time steps the commonly used data structure for this is a ring buffer. No garbage and all you do is increment the index (with wraparound) every step. Not a lot of technical details, but this video has a great general overview https://www.youtube.com/watch?v=W3aieHjyNvw https://www.youtube.com/watch?v=W3aieHjyNvw