News for hackers. Stuff that matters.
Re:Stride – Open-source C# Game Engine by alkonaut on Sunday February 05, 2023 @10:51AM (#34665835)
Not sure how this works under the hood but if I made a game in .NET I'd basically have a ban on any per-frame allocations. That means you have to be quite non-idiomatic when it comes so C# within the game loop, e.g. no dynamic collections (resizing will allocate), no "foreach" risks allocating enumerators. There are very few "zerp cost abstractions" in C# so when you want zero cost, you must also avoid some abstractions. Basically you use a very C-looking subset of C# for the parts of the code that run in the game loop. Allocate everything up front, use pools and arenas, etc. etc. It's easy to trip up but tooling is good so it's easy to notice when you accidentally introduced allocation. The engine isn't the problem, it's that when you expose C# as your scripting language then users might think they can write "normal" C# in their game scripts. Which you probably don't want to do.
Re:Stride – Open-source C# Game Engine by bob1029 on Sunday February 05, 2023 @11:11AM (#34666018)
> I'd basically have a ban on any per-frame allocations.

I have a prototype where I intentionally invoke GC after every frame. My reasoning is that some amount of GC will eventually need to occur, so why not do it as frequently as possible and at the exact moment where we can compensate with other measures, such as the frame scheduler & time step abstractions.

Even with heavy allocation (i.e. deserializing the scene graph from a wire protocol before every frame), this approach seems to keep GC pauses bounded to ~1-5 milliseconds in my implementations.

If I did not explicitly call GC when I thought best, the runtime would decide to run a 100-1000 ms collection at its convenience and the entire experience would become worthless to human perception.

Re:Stride – Open-source C# Game Engine by kevingadd on Sunday February 05, 2023 @01:13PM (#34667202)
This is a great strategy as long as you can keep the pause times low enough - especially if you manage to hide the gc behind vertical sync. Getting the exact timing for that is a little tricky, but if your game is running fast enough you've probably got upwards of 5ms waiting to be used while your main thread blocks inside Present/SwapBuffers. (Because those functions are native OS APIs, there won't be managed code on the stack and the GC should be able to run.)
Re:Stride – Open-source C# Game Engine by starburst on Sunday February 05, 2023 @02:29PM (#34668171)
This is NOT a great strategy, although better than waiting for the 100-1000ms collection you're still at the mercy of the GC which can be very random and you will miss some VSync for sure doing that even if you're supposed to have some "room".

Missing a VSync in mobile means that the refresh rate will switch to 30hz for a brief moment, there is no in-between (ie; 54 FPS or something like that) which is pretty noticeable.

The only "great strategy" is to have zero alloc (or close) while gameplay is running and if you want to go the extra miles, disable GC completely and call it only in some appropriate moment (level loading, bringing up the menu, etc.).

Re:Stride – Open-source C# Game Engine by bob1029 on Sunday February 05, 2023 @03:08PM (#34668714)
> The only "great strategy" is to have zero alloc (or close)

This is the path I am taking.

Ultimately, I would try as hard as I can to not allocate, but having a good plan for dealing with some per-frame blips is going to be essential. It gives me a reasonable starting point to work with on the journey to zero alloc.

Re:Stride – Open-source C# Game Engine by pjc50 on Sunday February 05, 2023 @02:02PM (#34667796)
Hmm. How feasible is it to write Roslyn analyzers that tell you whether a function is going to allocate?
Re:Stride – Open-source C# Game Engine by notanaverageman on Sunday February 05, 2023 @03:33PM (#34669020)
Re:Stride – Open-source C# Game Engine by olvy0 on Sunday February 05, 2023 @07:43PM (#34671706)
That Roslyn analyzer linked by notanaverageman below is really cool, I didn't know about it. But I don't think that even that analyzer will be able to tell if you call a method which itself allocates memory.

But it's not very complicated to write a simple IL analyzer, using Mono.Cecil to go over the raw IL, instead of using Roslyn. The idea is to look for the newobj or box IL opcodes, but also to recurse to called methods (when meeting the callX opcodes), by finding using Cecil where the call leads us, and loading that method.

This could be used after compilation instead of during compilation, but it can be used without actually running the code, so it's a sort of static analysis.

I threw together a simple demo, that has some bugs but is generic enough and should work. It's not very efficient though.

See here :

https://dotnetfiddle.net/EyuYSh

(It can also be run from there since dotnetfiddle supports nugets).

And also in godbolt:

https://godbolt.org/z/P3rK1voKP

(it doesn't run there since Godbolt apparently doesn't support nugets)

It's self contained and will run as a console application by just copying and pasting the code into an IDE. You need to add Mono.Cecil as a nuget package though.