Post AkF1lOlLllQaUDbiUa by crash@mastodon.gamedev.place
 (DIR) More posts by crash@mastodon.gamedev.place
 (DIR) Post #AkF1l85zjkqAnK8SoK by crash@mastodon.gamedev.place
       2024-07-23T23:51:46Z
       
       2 likes, 0 repeats
       
       Over on the birdsite, I got into a thread about C++ templatized pointer types and why some engines ban new/malloc outright.( https://x.com/despair/status/1815536090406965582 )It reminded me of the neat memory allocation model we used on the Jak & Daxter PS2. It was very custom-fit to that game, and it relied on a custom programming language, but it had some really cool properties.So, a reminiscence about predictable footprints, fast allocations, and leak-proof deletions in a PS2 engine. 🧵
       
 (DIR) Post #AkF1lA5ML5WKxvqz68 by crash@mastodon.gamedev.place
       2024-07-23T23:52:58Z
       
       1 likes, 0 repeats
       
       For context, the PlayStation 2 gives you 32MB of memory and 295MHz of CPU to work with it. There is no room for fragmentation, slack memory, or leaks. And malloc/free() can chew up a big chunk of your 10 million CPU operations per frame. Destructors add up too. 🧵
       
 (DIR) Post #AkF1lC2x30mb32k5ce by crash@mastodon.gamedev.place
       2024-07-23T23:53:37Z
       
       0 likes, 0 repeats
       
       One common approach in 80s/90s console games was to preallocate fixed-size arrays of everything. You get 128 moving platforms per level, 64 enemies, etc. Very fast and safe, but inflexible. It'd be nice to have entities of different sizes. But how to do it fast and safely? 🧵
       
 (DIR) Post #AkF1lE3NaOJVGwxSZE by crash@mastodon.gamedev.place
       2024-07-23T23:54:33Z
       
       1 likes, 0 repeats
       
       Here's how memory worked in GOAL. Each zone (streaming segment) divided its memory budget across everything it needed. 4MB for textures, 2MB for models, 1MB for entities, etc. We knew which combos could be loaded, so we knew they'd all fit. With completely self-contained zones, allocating is easy — just copy from disk to RAM. Freeing is easy — just 0 the 'this zone is loaded bit' and stop referencing the memory.  (Plus we had 'overlay zones' for stuff shared between segments, etc.) 🧵
       
 (DIR) Post #AkF1lGFrOxUA6EoSIa by crash@mastodon.gamedev.place
       2024-07-23T23:55:20Z
       
       0 likes, 0 repeats
       
       Entities were a first-class object - the base for everything that spawns & ticks in a level. As structs, they could have fixed members. But sometimes we'd want variable-sized things. eg, the 'time trial race' entity could have different # of slalom gates in different setups. (Entities were also coroutines but that's another story.)We did it by having _all_ of an entity's allocations be contiguous with the base struct in memory. Custom compiler enforced these rules:🧵
       
 (DIR) Post #AkF1lIGzthaEMLMOLg by crash@mastodon.gamedev.place
       2024-07-23T23:55:41Z
       
       0 likes, 0 repeats
       
       - Entities only referenced by handles (weak pointers). All gameplay code must be resilient to a handle resolving null.- Entities are self-contained. They own all their allocations. All allocations live as long as the entity.- No pointers to entity members, ever. Only offsets from the entity's base. Therefore relocatable.- Entities allocate memory only once, during their constructor: 🧵
       
 (DIR) Post #AkF1lKKyDtwwlFEaoq by crash@mastodon.gamedev.place
       2024-07-23T23:57:00Z
       
       0 likes, 0 repeats
       
       Remember the zone has a big memory region for all its entities. There's a 'used region' and a contiguous 'free region' (back to that in a bit). When making a new entity, the engine:1. makes a handle, points it at the free region's start address2. Carves out the entity struct's size, and then hands the following address to the constructor.3. Constructor gets a linear allocator! Each "alloc" just  bumps the offset pointer. Internal pointers are offsets.🧵
       
 (DIR) Post #AkF1lMMShKKb2RwoQC by crash@mastodon.gamedev.place
       2024-07-23T23:57:31Z
       
       0 likes, 0 repeats
       
       4. When constructor's done, entity is sealed. No more allocs.Allocations are super fast (bump pointer). No searching for sized blocks. Dealloc is equally fast — just null out the entry in the handle table, and add its memory to a free list.Then we have to make that free space contiguous again. So...🧵
       
 (DIR) Post #AkF1lOlLllQaUDbiUa by crash@mastodon.gamedev.place
       2024-07-23T23:59:26Z
       
       0 likes, 0 repeats
       
       Because all pointers are really "offset from entity base", and all of an entity's allocations are contiguous, they're relocatable! So we just defragmented a little bit every frame. The memmove cost was surprisingly small.So:- Freedom to allocate variable-sized stuff from entity init- All allocs are contiguous- Unambiguous ownership of all allocs- Freeing an entity + its allocs is instantaneous- Freeing a *zone* and its contents is also instantaneous- No leaks, no slop, no crashes.🧵
       
 (DIR) Post #AkF1lQq23KMSvJoU4G by crash@mastodon.gamedev.place
       2024-07-24T00:00:25Z
       
       1 likes, 0 repeats
       
       All this worked because of some very specific circumstances: programming in a custom-fit language, unified memory, PS2-sized game, designed around knowing all streaming combos ahead of time. Would be hard to do today. But it worked very well, for those games.🔚