Why VOID

Why build it
at all?

VOID did not begin with the idea that existing tools were bad. It grew out of years of using them, building earlier engines, and learning which parts I wanted to keep simple and which parts I wanted to own from top to bottom.

The short version

Keep the productive parts. Own the boundaries.

I like high-level game code. I also want native output, direct platform access, replaceable systems, and a runtime whose behavior is part of the project instead of an external dependency. VOID Engine and VOID Language are two answers to that same idea.

Not a rejection

Different priorities, not a war with other tools.

C#, .NET, MonoGame, SFML, and other frameworks solve real problems well. VOID exists because I wanted a narrower set of tradeoffs centered on games, native compilation, control, and the ability to understand the whole stack.

The project

Why VOID exists

These are the questions behind the engine, the language, and the philosophy they share.

01Why does VOID exist?

VOID came from building games and frameworks long enough to notice the same tension repeatedly. Very low-level libraries can give you control but leave a lot of infrastructure to rebuild. Larger engines can remove that work but may also make more decisions about how the project should be structured.

I wanted something in the middle: useful foundations that save time without becoming walls. The normal path should be easy, but the deeper boundaries should stay visible when a project needs to replace, extend, or bypass something.

02Why build another game engine?

VOID is the result of earlier engine work rather than a clean-room decision to make one more framework. Each version taught me what I liked and what became restrictive later. Over time the goal became clearer: make common game work straightforward, keep the architecture understandable, and avoid forcing every project through one workflow.

That is why systems such as rendering, assets, cameras, logging, and other engine boundaries are designed so the built-in implementation can be useful without pretending it is the only implementation a project may ever need.

03Why create a language too?

The engine can solve framework problems, but it cannot change the language runtime underneath the application. VOID Language exists because I wanted control over that layer too: compilation, metadata, garbage collection, exceptions, threading, native interop, tooling, and eventually higher-level task systems.

The language is not required to use VOID Engine, and VOID Engine does not require the language. They share a philosophy, not a dependency.

04Are VOID Engine and VOID Language tied together?

No. VOID Engine remains a .NET engine and can stand on its own. VOID Language is its own compiler and runtime project and can also stand on its own.

The long-term goal is for the two paths to feel familiar when used together, but neither project should become a requirement for the other.

05Why put so much emphasis on control and replaceable systems?

Because projects change. A default that is perfect for the first month can become the wrong boundary later. VOID tries to keep those boundaries explicit so growth does not automatically mean forking the engine, rewriting the runtime, or fighting a hidden subsystem.

The phrase A foundation, not a cage. is not just branding. It is the test I keep applying to the architecture.

VOID Language

Why build the compiler and runtime?

VOID Language keeps a familiar high-level style, but its execution model is designed around native output and a runtime owned by VOID.

06Why make another C#-style language?

I like C#. That is part of why VOID feels familiar. I did not start VOID Language because C# developers were doing something wrong or because the CLR is inherently bad.

My goals were narrower. I wanted a language aimed primarily at games and native applications, with native compilation, direct C interoperability, compiler-owned tooling, and a runtime I could understand and control end to end. VOID keeps the parts of modern managed code that make game systems pleasant to express, while making different choices underneath them.

07Do you think C# or .NET is bad?

No. .NET is a large general-purpose platform that has to support servers, desktop software, web applications, enterprise systems, dynamic runtime features, and many other workloads. That breadth is valuable.

For what I wanted to build, it also meant inheriting far more runtime and platform surface than I wanted to own. VOID deliberately has a smaller target. Game development, native output, predictable deployment, and direct platform access are first-class constraints instead of one workload among many.

08Why compile through C?

C gives VOID a portable native boundary and access to mature system toolchains without making one compiler backend the identity of the language. VOID can lower high-level language features into ordinary C, then let the platform toolchain finish the native build.

It also makes C interoperability a natural part of the design. LLVM, WebAssembly, and other targets can still matter, but the language does not have to give up a simple native representation to reach them.

09Is VOID designed to outperform C#?

Performance matters to VOID, but the architecture is not a promise that every VOID program will beat an equivalent C# or .NET program. Modern .NET and NativeAOT are highly optimized, and performance still has to be measured workload by workload.

VOID does make choices that are useful to measure in game code: native compilation is the normal path, the runtime owns its garbage collector and metadata model, and native interoperability does not have to cross the CLR boundary. Those choices give the project control over where runtime work happens, but they do not make benchmark results automatic.

Comparisons should come from repeatable measurements such as execution time, allocation behavior, GC pauses, startup time, memory use, binary size, and frame-time consistency rather than from the language name or compilation model alone.

10Why does avoiding JIT compilation matter for a game?

Modern .NET uses techniques such as tiered compilation and dynamic profile-guided optimization to balance startup speed and long-running performance. Those systems are sophisticated and can produce excellent code, but they also mean some compilation and optimization decisions happen while the application is running.

VOID moves that work to the build. Native application code is already compiled before launch, so gameplay does not need to wait for application methods to be JIT-compiled or transition between optimization tiers. That does not make JIT bad. It removes one category of runtime behavior VOID does not need to own.

Reference: Microsoft .NET compilation configuration.

11Why not just use .NET NativeAOT?

NativeAOT is a real solution and removes the JIT from deployed .NET applications. It can improve startup and produce native binaries, and it is a good fit for many projects.

VOID has a different goal. Native compilation is not an alternate deployment mode added after the language model was designed. The compiler, reflection system, garbage collector, ABI, standard library, threading model, and tooling are being designed around native output from the beginning.

NativeAOT also has intentional restrictions around dynamic loading, runtime code generation, trimming, and some reflection patterns. VOID can define its own native metadata and runtime contracts instead of adapting a general-purpose managed runtime model to AOT later.

Reference: Microsoft Native AOT deployment overview.

12Does VOID have reflection?

Yes. VOID already has the foundation for native reflection through compiler-generated metadata. The compiler knows the program's types, members, attributes, and related metadata and can emit the information that needs to remain available in the compiled native program.

C itself does not provide reflection, and VOID does not depend on IL, a JIT, or the .NET reflection runtime to recover that information later. The reflection API is still a focused surface and should not be treated as equivalent to the entire System.Reflection API today.

NativeAOT also supports reflection when the required targets can be preserved, with restrictions around some dynamic patterns. VOID's difference is architectural: its metadata format is owned by the compiler and designed around native output from the beginning.

Reference: Microsoft Native AOT reflection notes.

13Does VOID still have a runtime and garbage collector?

Yes. Saying VOID has no runtime at all would be inaccurate. Garbage collection, exceptions, threading, synchronization, type metadata, and other managed features need runtime support.

The difference is ownership. VOID does not depend on the CLR or an external managed VM. Its runtime support belongs to VOID and can evolve with the language. That lets the compiler and runtime agree directly on roots, object layout, exception flow, thread state, metadata, and other contracts.

14What is different about VOID's garbage collector?

.NET's garbage collector is extremely mature, but any tracing collector has latency and throughput tradeoffs. Microsoft exposes multiple GC latency modes because collections can interrupt time-sensitive work.

VOID also has to deal with collection costs. The difference is that VOID owns the collector and the contracts around it. Roots, thread coordination, allocation behavior, safepoints, and future tuning can be designed around VOID's runtime instead of configuring a collector that must serve the entire .NET ecosystem.

Reference: Microsoft .NET GC latency modes.

15Can VOID call native C libraries directly?

Yes. Native interoperability is part of the language rather than a separate bridge layered on later. VOID can declare compatible C functions through its native/extern surface, call them through the platform C ABI, allocate explicitly owned unmanaged memory, and work with raw native function-pointer values and C function tables.

The boundary remains deliberate. NativeMemory allocations are manually owned rather than GC objects, Span views over native storage are non-owning, and raw native function pointers are distinct from managed delegates and captured closures. Managed string now crosses that boundary through explicit UTF-8 ABI contracts for borrowing, pointer+length forms, ownership, retention, returns, callbacks, and exports instead of pretending a GC string is a raw C pointer.

.NET also has strong native interoperability support, including source-generated LibraryImport. VOID starts from a different implementation point because its normal output is already native code.

Reference: Microsoft P/Invoke source generation.

16Can VOID code become a native library too?

Yes. VOID projects can produce reusable native library output and expose selected public static methods through stable C symbols. Native interop is intended to work in both directions instead of requiring the rest of a program to move into VOID first.

The compiler and runtime still preserve the language contracts that library code depends on, including static initialization and managed roots, while the exported boundary remains understandable to native callers.

17Why does the web matter?

I do not want a game to become a completely different kind of project merely because the target changes. .NET can run in browsers through WebAssembly, and standalone Blazor WebAssembly applications can be deployed as static files. The browser still runs a WebAssembly-based .NET runtime, and desktop-oriented dependencies must support the browser environment too.

That last part is important. A desktop C# game does not become a browser game simply because its source language is C#. Its graphics, windowing, input, native libraries, filesystem assumptions, and other platform dependencies also need a browser-capable implementation.

VOID's long-term goal is to treat native desktop and WebAssembly as compilation targets of the same language architecture. Platform-specific work will still exist, especially around windowing, graphics, input, and browser APIs, but that complexity belongs at the platform and backend boundary rather than changing the identity of the language.

Reference: Microsoft Blazor overview and Blazor WebAssembly hosting model.

18Why own both the compiler and runtime?

A compiler alone would still leave many of the important policy decisions somewhere else. Garbage collection, exceptions, reflection, threading, synchronization, cancellation, native interoperability, metadata, and asynchronous suspension all interact with language semantics.

Owning both sides lets those contracts evolve together. The completed async work is a good example: the compiler can generate Task-backed state machines because it already understands VOID's Task states, GC roots, exception cleanup, cancellation, and object model instead of adapting to an unrelated runtime contract.

19Why build VOID's own threading and ThreadPool model?

.NET already has a mature threading system and ThreadPool. VOID is not rebuilding those ideas because .NET lacks them. It is doing so because workers interact with nearly every other part of a managed runtime.

A VOID worker can allocate memory, participate in garbage collection, enter monitors, wait on synchronization primitives, observe cancellation, propagate exceptions, and eventually execute higher-level task work. Building the model inside VOID keeps those behaviors under the same runtime contract.

The order was deliberate: native threads and managed thread state first, then synchronization and timed waits, then cancellation, then ThreadPool and Task foundations, and only then async state-machine lowering. That layering now supports structural awaiters, Task and ValueTask, allocation-free yielding, async lambdas, await using, await foreach, async iterators, nonblocking Task composition, timer-backed Task.Delay, delayed/linked cancellation, and Task.WaitAsync without replacing the scheduler underneath them or sleeping workers for asynchronous deadlines.

20Is VOID claiming its ThreadPool is better than .NET's?

No. .NET's ThreadPool has years of optimization behind it and serves a huge range of workloads. Writing a new one does not automatically make it faster.

The advantage for VOID is scope and ownership. Its scheduler only needs to serve VOID's runtime model and the kinds of programs VOID is intended to run. That gives the project freedom to make different tradeoffs later if game workloads need them.

21Does VOID use operating-system threads?

Yes. VOID is not trying to emulate threads inside the language. Managed VOID threads ultimately run on native platform threads.

The operating system provides the underlying thread. VOID owns how that thread participates in managed runtime state, garbage collection, exceptions, synchronization, cancellation, and higher-level scheduling.

22Does VOID support generalized async/await, and why did it start with Task?

Yes. VOID now has one structural await mechanism based on GetAwaiter(), IsCompleted, OnCompleted(Action), and GetResult(). Task participates through TaskAwaiter rather than a private compiler-only await path, and the same binder works with custom class, struct, generic, and extension awaiters.

Task.Yield() provides an allocation-free yield awaitable, while ValueTask/ValueTask<T> provide true value-type completed paths plus Task-backed asynchronous paths. async ValueTask methods reuse the existing state-machine and completion machinery.

The order was deliberate. VOID first built threads, synchronization, cancellation, ThreadPool scheduling, Task completion, exceptions, GC coordination, continuations, and Task-backed state machines. Generalizing the await contract after those pieces existed removed the Task special case without creating a second scheduler or async runtime.

That same mechanism now powers async lambdas and structured await using, while Task completion factories, async Task.Run unwrapping, WhenAll, and WhenAny compose work through continuations instead of adding another async execution model.

23Why build async timing around one shared timer service?

Because delays, delayed cancellation, and timeout waiting are the same runtime problem at the bottom: schedule work against a monotonic deadline, then resolve a race exactly once. Giving each API its own timer thread or sleeping a ThreadPool worker would duplicate lifetime, shutdown, GC, and race handling while consuming execution resources that should stay available for real work.

VOID now uses one cancellable timer queue for Task.Delay, CancellationTokenSource.CancelAfter, and Task.WaitAsync timeouts. Linked cancellation stays registration-based, source disposal removes retained registrations deterministically, and a timed or canceled WaitAsync affects only the proxy wait, not the antecedent Task itself.

24Why does the compiler power the editor too?

Because the compiler already knows what the program means. Diagnostics, hover information, signatures, completion, definitions, references, and symbols should come from that same semantic model instead of being reimplemented by an editor extension.

That keeps tooling aligned with real builds. A thin editor client can ask the compiler questions instead of carrying a second, slightly different version of the language.

25Why expose semantic queries?

Tooling should not have to scrape syntax and guess what a symbol means. VOID's compiler can expose answers about types, members, definitions, references, overloads, parameters, and source locations from an analyzed project session.

That makes editors and future tools easier to build correctly because semantic knowledge stays in one place.

26Why build source mapping and diagnostics into the compiler?

Generating C should not make developers debug generated C. The generated representation is an implementation detail. Errors, source ranges, native debug information, and editor tooling should still point back to the original VOID source wherever possible.

That is why source mapping, precise source spans, source-only checking, and compiler-backed diagnostics are part of the compiler architecture rather than separate convenience tools.

27Why is AOT the normal model instead of a special publishing mode?

Because design assumptions spread. If native output is the default assumption, reflection, metadata, generics, native interop, exception handling, libraries, and runtime services can all be designed with that constraint visible from the beginning.

VOID is not managed-first and native-later. Native output is the ordinary path.

28Is VOID just a C# transpiler?

No. The surface is intentionally familiar, but VOID owns its own language semantics, compiler, runtime, garbage collector, reflection model, threading, exception system, standard library, diagnostics, and tooling.

The goal is not to accept arbitrary C# source and translate it to C. Familiarity lowers the learning cost, while the implementation underneath follows VOID's own rules.

Direction

What VOID is trying to become

The scope matters as much as the feature list.

29Why is VOID deliberately game-focused?

Because focus makes tradeoffs easier to understand. VOID does not need to become an enterprise application platform, cloud stack, database framework, or replacement for every part of .NET.

It can still have networking, libraries, tooling, and general-purpose features. The difference is that game development and native applications remain the center of gravity when design choices compete.

30Is VOID trying to replace C# or .NET?

No. VOID exists because I wanted a different set of tradeoffs, not because every C# developer should abandon .NET.

If .NET already fits a project, there is no reason for VOID to pretend otherwise. VOID is for developers who find value in its combination of familiar high-level code, native output, compiler and runtime ownership, and a game-focused scope.

31What does VOID deliberately not want to become?

It does not need to absorb every application model imaginable. More surface is not automatically more useful. Every major feature increases the amount of behavior the compiler, runtime, documentation, tooling, and users have to carry.

The goal is to grow where the project gains leverage, not to collect features simply because another ecosystem has them.

32Can VOID evolve faster because it owns the stack?

While the language is young, coordinated compiler and runtime changes are easier because VOID does not carry decades of CLR compatibility requirements. A change to metadata, threading, code generation, or tooling can be designed across the whole stack instead of fitting one layer around another.

That freedom still has to be balanced with stability. As people build real projects on VOID, compatibility becomes part of the design responsibility too.

33Who is VOID for?

VOID is for game developers who like useful defaults but still want to understand and control the systems underneath them. It is also for developers who like the feel of modern managed code but are interested in a native-first compiler and runtime model.

You do not need to want every part of VOID. Use the engine, the language, or both. The point is to provide a foundation you can grow from without making the foundation own your project.

34Will VOID support LINQ-style APIs?

The language already has most of the machinery needed for LINQ-style collection APIs: generics, delegates, lambdas, closures, IEnumerable<T>, IEnumerator<T>, extension methods, and iterator methods using yield return.

That means method-style operations such as Where, Select, Any, First, Take, and Skip can be ordinary standard-library APIs rather than compiler features. A complete built-in operator set is not being claimed as current functionality yet.

C#-style query syntax such as from, where, and select is a separate language feature because it would require parser and lowering support.

35Will VOID have source generators or macros?

Source-generator-style tooling is a possible future direction. VOID already has compiler-owned metadata, semantic queries, source spans, and a normal source compilation pipeline, so generated code could be built around stable compiler information instead of text scraping.

A likely model would inspect declarations or metadata and emit ordinary VOID source that then goes through the same parser, semantic analysis, diagnostics, and native compilation path as handwritten code.

A general-purpose macro system is not a current goal. The preference is to keep generated code understandable and avoid creating a second, less predictable language inside the language.

36Will VOID support Span<T>?

Yes. Span<T>/ReadOnlySpan<T> are ordinary standard-library ref struct types built on compiler-tracked refs, readonly rules, escape analysis, and stack-only ref-like lifetime semantics. They compose with Index/Range slicing, normal and ref foreach, overlap-safe memory operations, managed arrays, stack storage, and unsafe pointer+length views over unmanaged memory.

The ownership rules stay explicit even now that VOID has native heap allocation. Memory<T>/ReadOnlyMemory<T> remain heap-storable managed array-backed values; NativeMemory allocations are manually owned outside the GC; and a Span over either kind of storage is still only a view. Scoped fixed pinning remains the lifetime-aware bridge from managed buffers to pointer+length native calls.

37Why keep native heap ownership explicit?

Because allocation and ownership are different concerns from viewing memory. VOID can now allocate, reallocate, align, and free unmanaged storage, but that storage is not a managed object and the garbage collector does not own its lifetime.

Keeping NativeMemory explicitly unsafe makes that responsibility visible. A Span can provide safe indexing rules over a valid native region, but it does not become an owner and cannot make a freed allocation valid again.

38Why keep native function pointers separate from managed delegates?

They represent different lifetime and runtime contracts. A raw native function pointer is an unmanaged code address with an ABI-compatible signature. A managed delegate can carry managed target state, participate in GC lifetime, and represent closures or multicast behavior.

VOID supports both, but it does not pretend they are interchangeable. That keeps C function tables and indirect native calls explicit without making captured managed state look like a permanently valid C callback address.

39Why did VOID generalize await before adding scheduler or context features?

Because the semantic boundary had to come first. VOID now treats awaiting as a structural language protocol, so Task, custom awaitables, and ValueTask all use one compiler path while continuation scheduling still rests on the existing runtime.

That makes later scheduler or context features meaningful instead of cosmetic. A future synchronization-context or scheduler abstraction can influence a general await mechanism rather than being bolted onto a Task-only compiler special case.

40Why does VOID support async lambdas without async void?

Async lambdas now compose ordinary delegates and closures with the same async state-machine machinery used by named methods. Task- and ValueTask-returning delegate targets keep completion, results, cancellation, and faults observable through the awaitable value the caller receives.

async void would change that observability contract because there is no returned Task or ValueTask through which normal code can observe completion or faults. VOID therefore keeps async lambda targets awaitable for now and leaves fire-and-forget error policy to a later design instead of making unobserved failure behavior implicit.

41Why does await using use the structural awaiter model?

Because asynchronous disposal should not create a private Task-only cleanup rule. VOID resolves DisposeAsync() structurally and awaits its result through the same generalized binder used everywhere else, then feeds disposal back into the existing structured cleanup state machine.

That keeps Task, ValueTask, and valid custom disposal awaitables on one semantic path while preserving the ordinary cleanup requirements: LIFO order, exceptions, early exits, GC lifetime, and suspension.

42Why did Task composition come before timer-backed async waits?

Task.Run unwrapping, WhenAll, and WhenAny are composition problems over a continuation system VOID already owns. They remain nonblocking and reuse existing Task completion, cancellation, fault, GC, and ThreadPool rules.

Task.Delay is different because a good implementation needs a real monotonic timer service rather than sleeping a worker or creating a thread per delay. VOID now has that shared timer infrastructure, and it also powers CancelAfter and WaitAsync timeouts. Synchronization contexts and scheduler customization remain separate because neither was required to make timing and cancellation composition correct.

43Why should VOID have only one string type instead of adding cstring?

Because C's text representations describe an ABI contract, not a second meaning of text inside VOID. Ordinary VOID code should work with one managed string type whose lifetime and semantics belong to the language, while a native declaration describes whether that value crosses the boundary as borrowed NUL-terminated UTF-8, pointer plus length, an owned return, or another explicit representation.

VOID now keeps string as the only user-facing text type and moves native ownership, borrowing, retention, release, and length into native-boundary metadata. That avoids teaching normal game code to carry C lifetime conventions everywhere just because one API happens to use them.

44Why make managed strings length-aware if VOID already emits C?

Because C output is an implementation path, not the semantic definition of a VOID string. A managed length-aware UTF-8 representation can preserve embedded NUL bytes, participate in GC like other managed references, and make equality, hashing, output, closures, generics, async state, exceptions, and arrays follow VOID rules instead of accidentally inheriting strlen-style termination rules.

A trailing NUL can still be useful as an interop optimization, but it should not define string length. At an actual native boundary, the ABI contract can reject embedded NUL for a borrowed C-string parameter, preserve it with pointer-plus-length forms, or copy an owned/borrowed native return into an ordinary managed string. The boundary chooses the representation; the language keeps one text model.

45Why should string positions count Unicode scalar values instead of UTF-8 bytes?

Because raw UTF-8 byte offsets are useful for storage and native boundaries, but they are a poor default for ordinary language-level text operations. VOID already has a 32-bit char, so treating indexing, enumeration, ranges, and search positions as Unicode scalar positions gives those APIs one coherent meaning without exposing partial UTF-8 code units.

The active roadmap keeps the byte count explicit through ByteLength. It also deliberately stops short of claiming grapheme-cluster, culture, case-folding, or normalization behavior; those require separate text policies rather than changing what core Length and indexing mean later.

The idea underneath all of it

A foundation, not a cage.

VOID is an attempt to keep the productive parts of high-level game development while making the important boundaries visible and controllable. The engine does that at the framework level. The language does it at the compiler and runtime level.