Or it could do next to nothing, as the data is multiple cache lines long anyway.
A big problem with them is they are so heavyweight you can only spawn a few per frame before causing hitches and have to have pools or instancing to manage things like bullets.
I think in their Robo Recall talk they found they could only spawn 10-20 projectile style bullets per frame before running into hitches, and switched to pools and recycling them.
But they do have a more optimized entity component system now too.
To be fair, a single transform now that things are 64 bit coordinates I think is bigger than a cache line too.
- https://public-api.wordpress.com/wp-admin/rest-proxy/#https:...
- https://s0.wp.com/wp-content/js/rlt-proxy.js?m=20240709
blocking these via regex made the page load up really nice and fast
edit: formatting
I'm having a kid-in-the-tunnel-meeting-Mean-Joe-Green-in-the-commercial moment, I just started my own game development journey about a week ago so it's neat getting to run across a full-on developer!
To stay on topic, I often thought how cool Abzu would have been with multiplayer but it's a good lesson to me that some features that might be desirable might also be a hindrance to some degree.
Okay, enough fanboying!
I wonder how that came to be used. It's a traditional way to distinguish eta and omega in transliteration from Greek, but it's not at all a traditional way to mark long vowels in general.
(I see that wikipedia says this about Akkadian:
> Long vowels are transliterated with a macron (ā, ē, ī, ū) or a circumflex (â, ê, î, û), the latter being used for long vowels arising from the contraction of vowels in hiatus.
But it seems odd for an independent root to contain a contracted double vowel. And the page "Abzu" has the circumflex on the Sumerian transliteration too.)
(Not that I expect the UActor code to have changed much but modifying UActor seemed more common in the early 4.x days.)
They're fantastic for prototyping, but once you have designed some kind of hot-path most people typically start converting blueprints to code as an optimisation.
In such a scenario adding pooling becomes a trivial part of such an effort.
Here's a quote form the article
> I’ve already told you that this method saves 328 bytes per actor, which is not too much at first glance. You can apply the same trick for SceneComponents and save another 32 bytes per SceneComponent. Assuming an average of two SceneComponents per actor, you get up to 392 bytes per actor. Still not an impressive number unless you deal with a lot of actors. A hypothetical example level with 25 000 actors (which is a lot, but not unreasonable) will save about 10 MB.
I've a lot of experience with Unreal, and 25k actors is likely to run into a whole host of problems, such that saving 10MB of RAM is likely to be the least of your worries. You'd get more benefit out of removing a single unneeded texture, or compressing a single animation better.
One of the reasons developers use unreal (and yes, developers do use Unreal, it's not just "big companies" forcing their poor creatives to use the engine) is _because_ unreal has more man hours of development in a year than a small team would ever be able to put into their own engine. Like any tool it has tradeoffs, and it does have a (measureable) overhead. But to say that companies don't care is just disingenuous
If you actually have a million of something you're better off writing a custom manager thing to handle the bulk of the work anyway. For instance, if you're doing a brick building game where users might place a million bricks - maybe you want each brick to be an Actor for certain use cases, but you'd want to centralize all the collision, rendering, update logic. (This is what I did on a project with this exact use case and it worked nicely.)
If it isn't a false cognate, I wonder what the function of "φ" and "ω" are..
The only counterweight I'd add is that if you later decide to add multiplayer, that is very, very hard if the engine wasn't set up for it from the beginning. Multiplayer adds complexity that exceeds simple things like getting on the network and sending messages; synchronization and prediction are meaningful for a realtime experience and are much easier to get to if you've started from "It's multiplayer under the hood but the server is local" than "We have a single-player realtime game and we're making it multiplayer." But that's not a reason never to do this; not all games need to be multiplayer!
All these market forces conspire to heavily incentivize a game studio to release as close to now as possible with as much game as they believe the players will stomach as possible. There are companies that buck this trend (Nintendo has a tradition of maximizing quality out-of-the-box), but that's where incentives point companies. Minecraft was hilariously buggy (and devoid of features) when it came out; its original developer committed it to a price model where the earlier you bought it, the cheaper it would be, and it became one of the most popular mega-games of a generation.
And the incentives come from players. Helldivers 2 doesn't have bugs because Arrowhead is lazy; it has bugs because Arrowhead wants a billion dollars and gamers can be trusted to hand them over for a product that works most of the time, as long as it's more fun than frustrating.
Perhaps a circumflex was easier to typeset, like with logicians switching from Ā to ¬A and the Chomskyan school in linguistics switching from X-bar and X-double-bar to X' and XP?
So let's say you're going through all the actors and updating one thing. If those actors are in an array it's easy. Just a for loop, update the member variable, done. Easy, fast, should be performant right? But each time you're updating one of those the prefetcher is also bringing in extra lines, more data in the object, thinking you might need them next. So if you're only updating a single thing or a couple of things in the object on different cache lines you might really bring in 3-8x the data you actually need.
CPU prefetchers have something called stride detectors which can detect access patterns of N number of steps and stop the prefetcher from grabbing additional lines but at 16 cache lines the AActor object is way too big for the stride detector to keep up with. So you stride in gaps of 16 cache lines at a time through memory and you still get 2-3 extra cache lines after the initial access.
Secondly, a 1016 byte object just doesn't fit. It's word aligned but it's not cache line aligned and it's sure as hell not page aligned.
Best case scenario if you're updating two variables next to each other in memory the prefetcher gets both on the same cache line. Medium case scenario, the prefetcher has to grab the next line every so often. You'll get best most often and medium rarely.
Bad case scenario, the prefetcher has to grab the next cache line on the NEXT PAGE. Which only just became a thing on recent CPUs but also involves translating the virtual address of the next page to its physical page address which takes forever in data access terms. Bunch of pointer chasing, basically a few thousand clock of waiting.
The absolute worst case scenario is that the prefetcher thinks you need the next cache line, it's on the next page, it does the rigamarole of translating the next page's virtual address and you don't actually need it. You've done two orders of magnitude more work than reading a single variable for literally nothing.
So yeah. The prefetcher can do some weird ass shit when you throw weird and massive data structs at it. Slashing and burning the size down helps because the stride detector can start functioning again when the object is small enough. If it can be kept to a multiple of 64 bytes you even get page aligned again.
Lots of things are possible - but speculating on every possibility as though they’re equally probable doesn’t provide any value. Actors in unreal are a fairly low level item, but most games aren’t going to have 25k actors in a world, and if they do, 10MB of memory usage fragmented across actors is likely the least of their worries.
(Note also that "tough" is pronounced t-uh-f /tʌf/, with nothing O-like anywhere in it.)
Significant performance degredation is also possible if at some point a smart (but not wise) developer positioned the data to eliminate false sharing on either side.
Agreed that you shouldn't be using this heavy weight paradigm with large amounts of entities. My intention was just to add a bit of color to the idea that saving memory allocations can have implications beyond just the number of bytes you ultimately malloc.