Settings

Theme

Building a self-contained game in C# under 2 kilobytes

migeel.sk

212 points by shanselman 2 years ago · 41 comments

Reader

Genbox 2 years ago

I love the low level .NET stuff Michael Strehovsky makes. Microsoft has built a feature-rich and easy-to-use environment, but it makes .NET developers think there is only one way to use it: create csproj and call dotnet build.

But underneath the covers, there is an extremely powerful compiler (Roslyn) and build-system (msbuild). Combine that with the flexibility of C# and you can develop pretty much anything.

  • flohofwoe 2 years ago

    Don't forget the Win32 API which allows to create a window and run an event loop with a handful of simple function calls (even in C# apparently). Then compare that to the APIs that came after (like UWP and WinUI) and shudder in horror.

    • dgellow 2 years ago

      Win32 and WinUI serve different purposes, they aren’t meant to replace one another. Win32 is simple but also really low level. It’s great to get a window handler and handle low level events but building a modern graphical UI requires at minimum a layout system, that’s where WinUI comes handy.

      Also you can now mix Win32 with WinUI via the concept of XAML island. It’s not perfect but you don’t have to be blocked in one world or the other.

      Though I personally prefer dealing with WPF via AvaloniaUI, styling with winUI is often too frustrating…

      https://www.avaloniaui.net/

  • pjmlp 2 years ago

    We know the other ways, in fact stuff like NGEN have been a CLI set of commands since the early days.

    However .NET (alongside Java), has a culture of great tooling, we only do stones and sticks programming when there is no way around it.

delta_p_delta_x 2 years ago

It is pretty impressive that even without third-party tools, the binary went from 64 MB to ~1 MB. IMO future versions of .NET should add more of such optimisation flags as default in a Release build.

  • orthoxerox 2 years ago

    You don't want these optimisations to be on by default.

    Trimming can cause bugs when your program or one of its libraries relies on reflection and Native AOT is good for startup time (FaaS), but tiered JIT will likely outperform it in long-running applications.

    • delta_p_delta_x 2 years ago

      > when your program or one of its libraries relies on reflection

      Fair enough; perhaps it'd be worthwhile to throw a compiler error if both reflection and trimming were attempted.

      > Native AOT is good for startup time (FaaS), but tiered JIT will likely outperform it in long-running applications

      This is intriguing; could you elaborate on the latter? Would tiered JIT perhaps have more runtime information which would be more useful when optimising frequently-accessed code paths or tight loops?

      • MStrehovsky 2 years ago

        > Fair enough; perhaps it'd be worthwhile to throw a compiler error if both reflection and trimming were attempted.

        The tooling already generates warnings for any spot in the program that uses reflection _in a way that cannot be statically analyzed_. Fixing code to make it work with trimming is equivalent to fixing warnings. Here are a couple case studies: https://devblogs.microsoft.com/dotnet/creating-aot-compatibl...

      • orthoxerox 2 years ago

        > This is intriguing; could you elaborate on the latter? Would tiered JIT perhaps have more runtime information which would be more useful when optimising frequently-accessed code paths or tight loops?

        Exactly. In addition to that, it's hard for the compiler to guess which branch is the frequent one in code. Or if it's the only one, if, for example, you link against implementations of some interface but only use one at runtime.

      • soulbadguy 2 years ago

        > Fair enough; perhaps it'd be worthwhile to throw a compiler error if both reflection and trimming were attempted.

        In language/platform with dynamic loading, it's hard for the compiler to "perfectly" predict which piece of code will ending up being executed at compile time. I think would result in a lot of false positives.

    • SirMaster 2 years ago

      No, but I learned from this that self-contained builds can be compressed. Why isn't that an option in the publish gui of vs 2022?

  • rkagerer 2 years ago

    Agreed, it feels like there's still low-hanging fruit there.

  • mobiuscog 2 years ago

    It would be really interesting to see how small it could stay whilst adding in support for OpenGL.

    • pjmlp 2 years ago

      Side note, one of the first commercial games written in .NET used OpenGL, released in 2004 long before XNA.

      It was a RTS game and quite cool, Arena Wars.

askiiart 2 years ago

In a similar vein, there's Can you fit a whole game into a QR code? by MattKC, which is limited to about 3 kilobytes (2,953 bytes)

https://youtu.be/ExwqNreocpg

TillE 2 years ago

This is really cool, but it's funny to use C# primarily as an awkward substitute for C as you call Win32.

For most practical purposes, the default .NET 8 AOT is great if you really need native code.

  • pjmlp 2 years ago

    Windows Forms is basically this, a better MFC version, and even if it was only for faster startup times, NGEN has been there since day 1.

    The biggest issue has been WinDev, that contrary to Google's culture in ChromeOS and Android, they will kill any attempt to touch their precious COM and C++ tools, thus the way the Longhorn project went down, only to see its ideas redone in COM, and WinRT coming to existence.

    • gwervc 2 years ago

      WinRT is one of the biggest fail of Microsoft in the last decades. Instead of pushing for a multiplatform .NET implementation with a portable UI (Silverlight OOB was just that), or even running on most Windows versions, we got framework over framework (WinRT, UWP, WinUI) that are only compatible with the last version of Windows and don't bring value over WPF.

      • pjmlp 2 years ago

        Speaking as someone that bought into it, WinRT was kind of the .NET idea before they went with a Java clone (Ext-VOS), they didn't fail only on features, they failed on how it was managed.

        From the forced rewrites across Windows 8, 8.1 and 10, as the platform was maturing, the deprecation of C++/CX, replaced by less capable C++/WinRT (now in maintainance, while they are having fun in Rust/WinRT), deprecation of .NET Native without parity in Native AOT, no designer, and plenty of other broken stuff.

        It was for this that WinDev kind of sabotaged Longhorn efforts, thanks very much.

        Meanwhile Google shows what happens when everyone pushes into the same direction, even if it takes some growing pains (with lots of cash) to get there.

  • flohofwoe 2 years ago

    Heh, I thought exactly the same seeing the code: "This looks almost exactly like C talking to Win32, except slightly more awkward" :)

MStrehovsky 2 years ago

Someone with an iPhone told me the webm video embedded in the article still doesn't work on iPhones in 2024 so here's another link to the video: https://twitter.com/MStrehovsky/status/1742101904250060842

deafpolygon 2 years ago

"Win32 is the only stable ABI on Linux" ;)

  • littlecranky67 2 years ago

    "This will produce a single EXE file that is whopping 64 MB in size. [...] You might say “still better than Electron” and call it good, but let’s see if we can do better."

tiberriver256 2 years ago

He makes this look easy! Surprising how far you can take things with OOTB .NET.

nwellinghoff 2 years ago

Very cool. What are the practical implications for this? Would it ever be wise to deploy these techniques for a commercial program? Is the end result more memory efficient during execution?

  • flohofwoe 2 years ago

    A small binary size is at least useful when running code in web browsers via WASM. Granted, 2 KByte would be way below diminishing returns, but even a couple of hundred KBytes is rare with high level languages like C# which usually require a big runtime.

    Even "we care about binary size at all" would be a major step forward for most "professional" applications.

    • mlhpdx 2 years ago

      I’m using some of these techniques to minimize the size of my executables for use with a AWS Lambda and on embedded Linux projects.

    • Snow_Falls 2 years ago

      The problem with video games and binary sizes is that the assets (3d models, audio, etc) take far more space than the binaries. And its much harder to compress then while still getting good loading times and high quality.

      • flohofwoe 2 years ago

        That's a bit of a separate problem since assets can be downloaded as needed in the background while the game is already running, but to even start the game, the first thing that's required is the WASM asset blob so that should be small.

        • Snow_Falls 2 years ago

          Ah that is true, I forgot that you could stream assets via web in a browser. But, surely if you can stream the large assets quickly enough for a decent experience (even with caching) then the initial blob size isn't going to take too long to download in the first place?

  • ncr100 2 years ago

    Back in 2000 my team and I compressed our c++ web browser that ran on BlackBerry pagers down to I think it was 56 kilobytes.

    We needed it to be that small because he pager didn't have very much storage. Neomar microbrowser, we called it. We were quite pleased to get this thing so tiny, with basically compiler flags and a few recoding steps.

WhereIsTheTruth 2 years ago

Michal and the whole CoreRT team saved C# for me, one of the very few people i respect from Microsoft

mgerullis 2 years ago

Sadly the video won’t play on my iPhone

TheRealPomax 2 years ago

Pretty sure you just joined the demo scene.

  • TheRealPomax 2 years ago

    (also pretty sure some folks apparently have no idea what the demo scene is, or how much of a compliment that was)

Keyboard Shortcuts

j
Next item
k
Previous item
o / Enter
Open selected item
?
Show this help
Esc
Close modal / clear selection