Skip to content

For NET Developers

ffredyk edited this page Jul 24, 2026 · 3 revisions

For .NET Developers

How to embed SQ# scripting into your .NET application or game engine.

Packages

Package Purpose
SQSharp.Hosting High-level host API — all you usually need
SQSharp.Core SqValue, SqArray, bytecode types
SQSharp.Compiler Lexer, parser, bytecode compiler
SQSharp.VM Stack VM + runtime
SQSharp.Scheduler Fiber scheduler

Quick Start (C#)

using SQSharp.Host;

var host = new SqHost();
host.OnPrint += msg => Console.WriteLine(msg);

host.ExecuteString(@"
    _arr = [1, 2, 3, 4, 5];
    _arr pushBack 6;
    print format ['Array: %1 elements', count _arr];
");

host.TickMain();

Command Registration

Nular (No Args)

host.RegisterNular("getFPS", () => new SqValue(1.0 / deltaTime));

Unary (One Right Arg)

host.RegisterUnary("spawnEnemy", type => {
    SpawnEnemy(type.AsString());
    return SqValue.Nil;
});

Binary (Left + Right)

host.RegisterBinary("damageEntity", (entity, amount) => {
    var id = (int)entity.AsNumber();
    _entities[id].Damage(amount.AsNumber());
    return SqValue.Nil;
}, precedence: 4);

Thread Safety

// ReadOnly — safe from any thread (pure computation)
host.RegisterUnary("getPosition", entity => {
    return PackVector3(_entities[(int)entity.AsNumber()].Position);
}, ThreadSafety.ReadOnly);

// Exclusive — host must serialize access
host.RegisterBinary("modifyWorld", (target, action) => {
    _world.Modify(target.AsNumber(), action.AsString());
    return SqValue.Nil;
}, threadSafety: ThreadSafety.Exclusive);

Host Lifecycle

var host = new SqHost();

// 1. Register custom commands
host.RegisterNular("playerCount", () => new SqValue(_players.Count));
host.RegisterUnary("getPlayer", id => new SqValue(_players[(int)id.AsNumber()].ToSqValue()));

// 2. Create additional schedulers (optional)
host.CreateScheduler("AI", budgetMs: 4.0);
host.CreateScheduler("Physics", budgetMs: 5.0);

// 3. Load and execute scripts
host.ExecuteFile("scripts/init.sqf");
host.ExecuteString("print 'Hello from C#';");

// 4. Game loop
while (_running)
{
    host.TickAll();            // pump all schedulers
    UpdateGameLogic(deltaTime);
    Render();
}

SqValue — The Core Type

// Create values
var num = new SqValue(42.0);
var str = new SqValue("hello");
var boolean = new SqValue(true);
var nil = SqValue.Nil;

// Read values
double d = num.AsNumber();
string s = str.AsString();
bool b = boolean.AsBool();

// Check types
SqType type = value.Type;
if (value.Type == SqType.Number) { ... }
if (value.Type == SqType.Array) { ... }

Working with Arrays

// Create
var arr = new SqArray();
arr.PushBack(new SqValue(1.0));
arr.PushBack(new SqValue(2.0));

// Access
SqValue elem = arr[0];

// From C# collection
var fromList = SqArray.FromEnumerable(myList.Select(x => new SqValue(x)));

Script Execution

// Execute string — synchronous (compiles and starts, doesn't wait)
host.ExecuteString("sleep 2; print 'done';");

// Execute file
var handle = host.ExecuteFile("scripts/mission.sqf");

// Await completion (C# async)
var result = await handle.ToTask();
Console.WriteLine($"Result: {result.AsString()}");

// Compile without executing
var chunk = host.CompileString("1 + 2");
// chunk is BytecodeChunk — can be cached, serialized, executed later

Error Handling

try
{
    host.ExecuteString("_undefinedVar;");
}
catch (SqParseException ex)
{
    // Compile-time error
    Console.WriteLine($"Parse error at line {ex.Line}: {ex.Message}");
}
catch (SqRuntimeException ex)
{
    // Runtime error
    Console.WriteLine($"Runtime error: {ex.Message}");
    Console.WriteLine($"  at {ex.File}:{ex.Line}:{ex.Col}");
}

Custom Object Types

// Register an opaque type
host.RegisterObjectType("Entity", entityId => new SqValue((double)entityId));

// Commands that work with the custom type
host.RegisterUnary("getHealth", entity => {
    var e = ResolveEntity((int)entity.AsNumber());
    return new SqValue(e.Health);
}, ThreadSafety.ReadOnly);

Multiple Schedulers

// Create schedulers
host.CreateScheduler("AI", budgetMs: 3.0);
host.CreateScheduler("Physics", budgetMs: 5.0);

// Tick all in game loop
host.TickAll();

// Or tick individually
host.TickMain();               // Main scheduler only
host.TickScheduler(2);         // Specific scheduler by ID

CLI Integration

# Build and run from command line
dotnet run --project src/SQSharp.CLI -- run script.sqf

# Compile to binary
dotnet run --project src/SQSharp.CLI -- compile script.sqf --binary -o script.sqfc

Thread Safety Architecture

SQ# enforces data ownership:

  • Each mutable array/hashmap is owned by one scheduler
  • Cross-scheduler access throws errors
  • Use freeze/thaw for immutable sharing
  • Use shared for CAS-based atomics
// When registering commands, declare thread safety:
// ReadOnly — safe from any scheduler
// Exclusive — host handles serialization

Next Steps

  1. Read the Host API for complete embedding guide
  2. Explore the samples/ directory
  3. Study HostMinimal/ and HostGame/ for real-world host examples
  4. Read Concurrency for multi-scheduler architecture

See Also

SQ# Wiki

Home

Engine Docs

Migration

Commands

Value Constructors

Arithmetic

Comparison

Logic

Array

String

Math

Random

Type & Introspection

HashMap

Code Execution

Concurrency

Scheduler

Thread Safety

Error

Output

Time

Multiplayer

Compiler

Clone this wiki locally