-
Notifications
You must be signed in to change notification settings - Fork 0
Host API
ffredyk edited this page Jul 23, 2026
·
1 revision
How to embed SQ# scripting into your .NET application or game.
| Package | Purpose | Dependencies |
|---|---|---|
SQSharp.Core |
SqValue, SqType, SqArray, bytecode types |
None |
SQSharp.VM |
Stack VM + runtime | Core |
SQSharp.Compiler |
Lexer + parser + bytecode compiler | Core, Language |
SQSharp.Scheduler |
Fiber scheduler | Core, VM |
SQSharp.Host |
High-level host API + 100+ StdLib commands | All above |
SQSharp.CLI |
Command-line tool (dotnet sqf) |
All above |
Most apps only need SQSharp.Host:
dotnet add package SQSharp.Hostingusing SQSharp.Host;
// Create the scripting host
var host = new SqHost();
// Capture script output
host.OnPrint += msg => Console.WriteLine(msg);
// Run a script
host.ExecuteString(@"
_arr = [1, 2, 3, 4, 5];
_arr pushBack 6;
print format ['Array has %1 elements', count _arr];
");
// Pump the scheduler (in your game loop)
host.TickMain();var host = new SqHost();
// 1. Register custom commands (before spawning scripts)
host.RegisterNular("getPlayerCount", () => new SqValue((double)_players.Count));
host.RegisterUnary("getPlayerName", id => new SqValue(_players[(int)id.AsNumber()].Name));
host.RegisterBinary("damagePlayer", (id, amount) => {
_players[(int)id.AsNumber()].Health -= amount.AsNumber();
return SqValue.Nil;
}, precedence: 4, threadSafety: ThreadSafety.Exclusive);
// 2. Execute scripts
host.ExecuteString(@"
_count = getPlayerCount;
for '_i' from 0 to (_count - 1) do {
_name = getPlayerName _i;
print format ['Player %1: %2', _i, _name];
};
");
// 3. Game loop — tick every frame
while (_running)
{
host.TickMain(); // Run scheduled scripts (3ms budget per tick)
UpdateGameLogic(); // Your game logic
Render(); // Your rendering
}// Nular — no arguments, returns value
host.RegisterNular("getFPS", () => new SqValue(1.0 / deltaTime));
// Unary — one argument on the RIGHT
host.RegisterUnary("spawnEnemy", type => {
SpawnEnemy(type.AsString());
return SqValue.Nil;
});
// Binary — left and right operands
host.RegisterBinary("damageEntity", (entity, amount) => {
var id = (int)entity.AsNumber();
_entities[id].Damage(amount.AsNumber());
return SqValue.Nil;
}, precedence: 4);// ReadOnly — safe from any scheduler (pure computation)
host.RegisterUnary("getPosition", entity => {
return PackVector3(_entities[(int)entity.AsNumber()].Position);
}, ThreadSafety.ReadOnly);
// Exclusive — not thread-safe, host must serialize access
host.RegisterBinary("modifyWorld", (target, action) => {
_world.Modify(target.AsNumber(), action.AsString());
return SqValue.Nil;
}, threadSafety: ThreadSafety.Exclusive);// Override print to use your logging system
host.RegisterUnary("print", msg => {
Logger.Log(msg.AsString());
return SqValue.Nil;
});| Method | What | When |
|---|---|---|
RegisterCoreCommands() |
Math, string, array, logic, type checks | Called automatically by new SqHost()
|
DeclareArmaCompatCommands() |
hint, systemChat, diag_log
|
Opt-in. Skip for non-Arma hosts |
DeclareSchedulerCommands() |
currentScheduler, allSchedulers, etc. |
Called automatically |
DeclareMultiplayerCommands() |
remoteExec, publicVariable, etc. |
Opt-in. Host must call explicitly |
// Minimal host (core only, no Arma compat):
var host = new SqHost(includeArmaCompat: false);
host.RegisterUnary("log", msg => { Logger.Log(msg.AsString()); return SqValue.Nil; });
// Full Arma compat (default):
var host = new SqHost(); // or new SqHost(includeArmaCompat: true)var host = new SqHost();
// Create additional schedulers
host.CreateScheduler("AI", budgetMs: 3.0);
host.CreateScheduler("Physics", budgetMs: 5.0);
// Register multiplayer commands (makes scheduler=machine)
host.DeclareMultiplayerCommands();
// Scripts can now use spawnOn:
host.ExecuteString(@"
spawnOn ['AI', { heavyPathfinding(); }];
spawnOn ['Physics', { simulate(_this); }];
");
// Tick all schedulers in game loop:
while (_running)
{
host.TickAll(); // Pumps Main + any auto-pumped schedulers
Render();
}// Capture all script output
host.OnPrint += msg => {
// msg is prefixed: "[HINT] ...", "[CHAT] ...", "[DIAG] ...", or raw
if (msg.StartsWith("[HINT] "))
UIManager.ShowHint(msg.Substring(7));
else if (msg.StartsWith("[CHAT] "))
ChatPanel.AddMessage(msg.Substring(7));
else if (msg.StartsWith("[DIAG] "))
File.AppendAllText("debug.log", msg.Substring(7) + "\n");
else
Console.WriteLine(msg);
};// From string
host.ExecuteString("print 'Hello';");
// From file
var handle = host.ExecuteFile("scripts/init.sqf");
// Await completion (C# side)
var result = await handle.ToTask();
Console.WriteLine($"Script returned: {result.AsString()}");Register opaque host types:
// Register an Entity type
host.RegisterObjectType("Entity", entityId => new SqValue((double)entityId));
// Now scripts can receive and pass around Entity references
host.RegisterUnary("getEntityHealth", entity => {
var id = (int)entity.AsNumber();
return new SqValue(_entities[id].Health);
});
host.RegisterUnary("damageEntity", (entity, amount) => {
var id = (int)entity.AsNumber();
_entities[id].Damage(amount.AsNumber());
return SqValue.Nil;
});try
{
host.ExecuteString("_undefinedVar;"); // throws
}
catch (SqRuntimeException ex)
{
Console.WriteLine($"Error: {ex.Message}");
Console.WriteLine($" at {ex.File}:{ex.Line}:{ex.Col}");
}public class GameHost
{
private SqHost _sq;
private List<Entity> _entities = new();
public void Initialize()
{
_sq = new SqHost();
// Capture output
_sq.OnPrint += msg => Debug.Log(msg);
// Register game commands
_sq.RegisterNular("entityCount",
() => new SqValue((double)_entities.Count));
_sq.RegisterUnary("getHealth", entity => {
var e = _entities[(int)entity.AsNumber()];
return new SqValue(e.Health);
}, ThreadSafety.ReadOnly);
_sq.RegisterBinary("damage", (entity, amount) => {
var e = _entities[(int)entity.AsNumber()];
e.Health -= amount.AsNumber();
return SqValue.Nil;
}, precedence: 4, threadSafety: ThreadSafety.Exclusive);
// Create AI scheduler
_sq.CreateScheduler("AI", budgetMs: 4.0);
_sq.DeclareMultiplayerCommands();
// Load mission scripts
_sq.ExecuteFile("scripts/init.sqf");
}
public void Update(float deltaTime)
{
// Tick all schedulers
_sq.TickAll();
// Game logic
UpdateEntities(deltaTime);
}
}- Getting Started — architecture overview, quick start
- Concurrency — schedulers, fibers, spawnOn
- Thread Safety — freeze/thaw, shared, ownership
- Multiplayer — remoteExec, publicVariable, scheduler=machine
- Getting Started
- Language Guide
- Type System
- Control Flow
- Functions & Code
- Strings & Text
- Arrays & Collections
- Concurrency
- Promise System
- Thread Safety
- Host API & Embedding
- CLI Tool
- Bytecode Reference
- Multiplayer
- Syntax Sugar
- Optimization Guide
- Benchmarks
- count
- select
- pushBack
- append
- deleteAt
- deleteRange
- resize
- reverse
- sort
- find
- in
- forEach
- freeze
- thaw
- isFrozen
- currentScheduler
- clientOwner
- allSchedulers
- schedulerName
- schedulerExists
- schedulerBudget
- setSchedulerBudget
- fiberCount
- readyFiberCount
- waitingFiberCount
- schedulerLoad
- sendTo