Embed GES in C#
This walkthrough compiles a script, loads it into a host and receives its result in a C# callback. Your application owns input, persistent state and output; the script supplies the event-driven rule.
This guide tracks the development checkout. Use matching package versions and check the changelog before using new APIs with a published release. The complete API contract is in Public API.
Create a console application
Use a .NET 8 or newer application for this example. The libraries themselves target .NET Standard 2.1.
dotnet new console -n GesExamplecd GesExampledotnet add package GameEventScript.Runtimedotnet add package GameEventScript.Compilerdotnet add package GameEventScript.CSharpBridgeWithout --version, NuGet selects the latest stable package. To pin a release,
add --version <release-version> to each package command, using the same version.
To use unreleased functionality, use project references to a matching repository
checkout instead; see the implementation guide.
| Package | When you need it |
|---|---|
| Runtime | Always: values, programs and execution |
| Compiler | When your application compiles source text |
| CSharpBridge | Delegate callbacks, native bindings and optional background pumping |
| SyntaxHighlighter | Optional editor or terminal highlighting |
Compile and execute a rule
Replace Program.cs with this complete example:
using System;using GameEventScript.Api;using GameEventScript.CSharpBridge;using GameEventScript.Runtime.Values;
var program = GameEventScriptBuilder.Create() .AddScript(""" module tutorial.damage function damage(base, bonus) be base + bonus on Calculate(base, bonus) { emit Done(value: damage(base: base, bonus: bonus)) } """, "damage.ges") .Compile();
var host = GameEventScriptHost.CreateBuilder() .WithRandomSeed(123) .Build();
var output = host.Subscribe("Done", ["value"], (message, _) => Console.WriteLine(message.Arguments.GetAsInteger("value")));var instance = host.Load(program);
if (host.Start().State != GameEventScriptStartState.Ready) throw new InvalidOperationException("Host startup failed.");
host.Receive(GameEventScriptMessage.Create("Calculate", [ new GameEventScriptMessageArgument("base", GesValue.GesInteger(30)), new GameEventScriptMessageArgument("bonus", GesValue.GesInteger(12))]));
var result = host.RunToCompletion();if (result.State != GameEventScriptExecutionState.Completed) throw new InvalidOperationException($"Execution stopped: {result.State}");
instance.Detach();output.Unsubscribe();Run dotnet run. The callback prints 42.
Compile() creates an immutable program that you can reuse in multiple hosts.
Load() registers a program instance; Start() completes the initial loading
phase. Receive() queues the external Calculate(base, bonus) message, and
RunToCompletion() processes the queue. Receiving a message does not by itself
execute the script.
The seed makes this host’s random stream repeatable. Reproducible execution also requires the same program, inputs, ordering and relevant clock observations; see Determinism.
Load several programs before starting
Register initial native handlers and load all initial programs before calling
Start() once. Initializations run in load order; ordinary messages wait until
the initial group has succeeded. If startup fails, create a new host rather than
continuing with a partly initialized group.
After a host is ready, an additional Load() queues that instance’s initialization.
Its StartResult remains absent while pending. Continue pumping and inspect the
result before treating the new instance as ready. A failed later initialization
removes that instance without undoing other running programs.
Keep instance and subscription handles when you need to detach or unsubscribe. The host retains registrations independently of your local handle variables. Ordinary detachment does not retroactively cancel already captured message deliveries; initialization failure has different cancellation rules.
Ship precompiled programs
Compile during your content build instead of inside the shipped application:
dotnet ges compile damage.ges -o artifacts/damage.gesbRead the file in your application and pass its bytes to Runtime:
var bytes = System.IO.File.ReadAllBytes("damage.gesb");var program = GameEventScriptProgramReader.Read(bytes);Replace the builder section of the previous example with this code. The application can now omit Compiler. CSharpBridge still depends only on Runtime. File access remains in your application, not in the portable runtime.
Handle failures and delayed work
Compilation failures throw GameEventScriptCompileException, whose Diagnostics
contain stable codes and source locations. Check startup and execution results;
do not assume a returned result means all handlers succeeded. Record structured
diagnostics rather than parsing English messages.
Since: 0.2.0
A queue containing only future messages returns Waiting.
RunToCompletion() does not sleep. Use NextMessageDelay to arrange a later pump
in your event loop, or use the optional C# host runner. Avoid repeatedly calling
the pump in a busy loop. The simple example above has no delayed work and expects
Completed.
A host accepts one caller at a time. Keep raw host access serialized. The native runner supplies synchronization and automatic pumping; use its API consistently rather than mixing runner calls with unsynchronized raw host access.
Connect native data
CSharpBridge supplies attribute-based external type bindings with GesType,
GesField and GesConstruct. The compiler needs a declarative type catalog;
the host separately needs the executable registry. Passing a catalog to the
compiler does not install runtime bindings.
The C# bridge guide
contains a complete BotState example using these attributes, units and
GesParam, plus annotated extension functions.
Expose only the fields and constructors your script needs. Programs contain portable type declarations, not CLR objects or reflection state. Use native message handlers for application actions, and extensions or external fields for the read-only data your scripts consult.
Next: learn the language, explore the C# implementation and embedding examples, or consult the host lifecycle and external type contract.