Skip to content
bizzehdee edited this page Jun 24, 2026 · 2 revisions

Host Objects

engine.SetGlobal lets you expose a C# object to scripts without writing a custom provider. Public members opt in with attributes.

Attributes

Attribute Target Description
[ScriptVisible] Property or Method Makes the member readable from script
[ScriptWritable] Property Also allows script-side assignment

Both attributes are in the DScript namespace.

Example

using DScript;

public class AppConfig
{
    [ScriptVisible]
    public string Environment { get; set; } = "production";

    [ScriptVisible]
    [ScriptWritable]
    public int MaxConnections { get; set; } = 100;

    [ScriptVisible]
    public string GetVersion() => "2.1.0";

    [ScriptVisible]
    public bool IsFeatureEnabled(string name)
        => name == "dark-mode";

    // Not visible to scripts — no attribute
    public string InternalSecret { get; set; } = "hidden";
}
var config = new AppConfig();
engine.SetGlobal("config", config);
// In script
console.log(config.Environment);          // "production"
console.log(config.GetVersion());         // "2.1.0"
console.log(config.IsFeatureEnabled("dark-mode")); // true

config.MaxConnections = 50;               // allowed — [ScriptWritable]
// config.Environment = "staging";        // would throw — not [ScriptWritable]
// config.InternalSecret;                 // undefined — not [ScriptVisible]

Supported return types

Methods and properties may return:

C# type Script value
int, long, byte, short Integer
double, float Float
string String
bool Boolean (1/0)
ScriptVar Passed through directly
void Undefined

Method parameters

Script calls pass arguments positionally. The C# method receives them via normal parameter binding. If fewer arguments are passed than declared parameters, missing values are default(T).

[ScriptVisible]
public int Add(int a, int b) => a + b;
config.Add(3, 4);  // 7

Notes

  • SetGlobal uses reflection at call time. For performance-critical paths, consider writing a typed native function with AddNative instead.
  • [ScriptWritable] without [ScriptVisible] has no effect — the property is not exposed at all.
  • Static methods are not currently supported via SetGlobal; use AddNative for those.

Host Event System

ScriptEngine.RaiseEvent lets the host application fire named events into a running script. Scripts subscribe using the global on / once / off functions registered by EngineFunctionLoader.RegisterFunctions.

This is the primary mechanism for building an application that uses DScript as a scripting host: run the script once to register handlers, then fire events from C# whenever the application state changes.

Setup

Call EngineFunctionLoader.RegisterFunctions before running any script. This automatically creates the global host-event emitter and registers the global event helpers.

var engine = new ScriptEngine();
new EngineFunctionLoader().RegisterFunctions(engine);

// Run the script once — it registers handlers and returns
engine.Run(ScriptEngine.Compile(File.ReadAllText("game.ds")));

// Later, raise events from application code
engine.RaiseEvent("playerDied", new ScriptVar("Alice"), new ScriptVar(3));
engine.RaiseEvent("levelCompleted", new ScriptVar(7));

Script-side API

Function Description
on(event, fn) Register a persistent listener for event
once(event, fn) Register a one-shot listener (auto-removed after first fire)
off(event, fn) Unregister a previously registered listener
removeAllListeners(event?) Remove all listeners for event, or all listeners if omitted
// game.ds
on('playerDied', function(name, lives) {
    console.log(name + ' died, ' + lives + ' lives left');
    if (lives === 0) on('gameOver', handleGameOver);
});

once('levelCompleted', function(level) {
    console.log('Level ' + level + ' complete!');
    // this handler is automatically removed after firing once
});

function handleGameOver() {
    console.log('Game over!');
}

C# API

ScriptEngine.RaiseEvent(string name, params ScriptVar[] args)

Fires name, invoking all matching on / once handlers in registration order. once handlers are removed before being called (so re-registration inside a handler is safe). After all handlers run, the Promise microtask queue is drained, so any async handlers that resolved a Promise will have their .then callbacks executed.

If no handler is registered for name, the call is a no-op.

ScriptEngine.HostEventDispatch

The Action<string, ScriptVar[]> delegate that RaiseEvent calls. Set automatically by RegisterFunctions. You can replace it with a custom implementation if you need different dispatch semantics.

Advanced: accessing the emitter from script

The backing emitter is exposed as __hostEmitter__ on the global scope. Scripts can use it directly if they need features like .listenerCount() or .listeners():

console.log(__hostEmitter__.listenerCount('playerDied')); // 1

The global on / off / once / removeAllListeners functions operate on the same underlying emitter, so mixing both styles is safe.

Clone this wiki locally