~/openengine
$ cat docs/core/scripting.md
# C# scripting with CoreCLR

OpenEngine hosts the .NET CoreCLR to enable C# scripting for game logic. The scripting system supports hot-reloading, asset interaction, and bidirectional C++/C# interop.

How It Works

C# Script (Assets/Scripts/*.cs)
    โ†“
CoreCLR Host (in engine)
    โ†“
C++ โ†” C# P/Invoke Interop
    โ†“
Engine Systems (rendering, physics, assets)

The engine initializes CoreCLR at startup, loads compiled assemblies from build/bin/<Config>/ScriptAssemblies/, and invokes C# entry points each frame.

Script Assembly Pipeline

  1. Place .cs files in Assets/
  2. Build the C++ project โ€” CMake generates OpenEngine.Scripts.csproj and builds it
  3. Output: build/bin/<Config>/ScriptAssemblies/OpenEngine.Scripts.dll
  4. Engine loads the assembly at runtime

No manual dotnet build needed โ€” the ScriptAssemblies pipeline is part of the CMake build.

C++ to C# Interop

The engine registers native functions that C# can call via P/Invoke:

csharp
// NativeInterop.cs โ€” provided by the engine
public static class NativeInterop
{
    [DllImport("Engine")]
    public static extern void LogMessage(string message);

    [DllImport("Engine")]
    public static extern int GetAssetCount();

    [DllImport("Engine")]
    public static extern int AddNumbers(int a, int b);
}

Hot-Reloading

The engine monitors Assets/ for file changes using platform-native file watchers:

  • Windows: ReadDirectoryChangesW API
  • Linux/macOS: inotify / FSEvents

When a .cs file changes, the engine recompiles the assembly and reloads it โ€” no restart required.

tip

Hot-reload detection is sub-millisecond. Changes appear in the next frame after compilation completes.

Lifecycle

  1. OnInitialize() โ€” called once at startup
  2. OnUpdate(float deltaTime) โ€” called every frame
  3. OnShutdown() โ€” called during engine shutdown
  4. [InitializeOnLoad] โ€” attribute for editor-time initialization

Next Steps

enesfrar