Skip to content
Oakfield Operator Calculus Function Reference Site

Custom Operators

You can register your own operator at runtime from Lua by providing a callback that mutates fields in the context. Custom operators work alongside built-in ones and participate in the scheduler plan. The binding does not infer write masks; keep callbacks lightweight and explicit about which fields they touch.

  1. Registersim.sim_add_operator(ctx, name, fn[, dependencies]) installs your callback.
  2. Expose params (optional)sim.sim_operator_set_params(ctx, op, params) publishes typed params for UI/introspection.
  3. Tweak params — adjust at runtime with sim.sim_operator_param_set/enum_set/field_set.
local op = sim.sim_add_operator(ctx, "bias", function(c)
-- do work; return true/false or a SimResult code
end)
-- the newest operator index is sim_operator_count(ctx) - 1
local op_index = sim.sim_operator_count(ctx) - 1
  • dependencies (optional 4th arg) is an array of operator handles this operator depends on.
  • The callback receives the context only; read params via sim_operator_param* using the operator index you captured.

Expose typed parameters for UIs and introspection with sim_operator_set_params:

sim.sim_operator_set_params(ctx, op, {
{ name = "gain", type = "float", default = 1.0, min = 0.0, max = 10.0 },
{ name = "field", type = "field", default = 0 },
{ name = "mode", type = "enum", options = {"fast", "accurate"}, default = "fast" },
})

Supported type values: float, integer, boolean, enum, field. Enums require an options array of strings. Optional keys: default, min, max, required, description.

Update values later with:

sim.sim_operator_param_set(ctx, op_index, "gain", 0.5)
sim.sim_operator_param_enum_set(ctx, op_index, "mode", "accurate")
sim.sim_operator_param_field_set(ctx, op_index, "field", field_handle)
local field = sim.sim_add_field(ctx, {256}, { fill = 0.0 })
local bias_idx
local bias_op = sim.sim_add_operator(ctx, "bias", function(c)
local gain = sim.sim_operator_param(c, bias_idx or 0, "gain") or 0.0
local f = sim.sim_get_field(c, field)
local v = f:values()
for i = 1, #v do v[i] = v[i] + gain end
end)
bias_idx = sim.sim_operator_count(ctx) - 1
sim.sim_operator_set_params(ctx, bias_op, {
{ name = "gain", type = "float", default = 0.1, min = -10.0, max = 10.0 },
})
local clamp_idx
local clamp_op = sim.sim_add_operator(ctx, "clamp", function(c)
local lo = sim.sim_operator_param(c, clamp_idx or 0, "lo") or -1.0
local hi = sim.sim_operator_param(c, clamp_idx or 0, "hi") or 1.0
local f = sim.sim_get_field(c, 0)
local v = f:values()
for i = 1, #v do
if v[i] < lo then v[i] = lo end
if v[i] > hi then v[i] = hi end
end
end)
clamp_idx = sim.sim_operator_count(ctx) - 1
sim.sim_operator_set_params(ctx, clamp_op, {
{ name = "lo", type = "float", default = -1.0 },
{ name = "hi", type = "float", default = 1.0 },
})
local phase = 0.0
local sine_idx
local sine_op = sim.sim_add_operator(ctx, "sine_drive", function(c)
local f = sim.sim_get_field(c, 0)
local v = f:values()
phase = phase + sim.sim_get_timestep(c) * 1.0
local s = math.sin(phase)
for i = 1, #v do v[i] = v[i] + s end
end)
sine_idx = sim.sim_operator_count(ctx) - 1
sim.sim_operator_set_params(ctx, sine_op, {
{ name = "freq", type = "float", default = 1.0 },
})
  • Keep Lua operators lightweight; heavy per-element loops can bottleneck CPU execution. Use native operators when performance matters.
  • The scheduler does not infer write masks for Lua operators; avoid side effects outside the fields you explicitly manage.
  • Pair schemas with sim_operator_param_* helpers to make callbacks configurable from UI or scripts.
  • If you need GPU or backend-specific kernels, implement a native operator instead of Lua callbacks.