Running Simulations
Drive Modes
Section titled “Drive Modes”Oakfield contexts can be driven in three common ways:
- Call
step(ctx)yourself in a script-controlled loop. - Call
run(ctx, [config])and let the scheduler own the loop. - Claim external-driver ownership and cooperate with host or embedder control APIs.
Script-controlled Stepping
Section titled “Script-controlled Stepping”When running simulations from the command line, you will usually own the loop directly:
local ctx = ooc.create({ worker_count = 1 })
-- add fields/operators...
local integrator = ooc.create_context_integrator(ctx, "rkf45", { initial_dt = 0.01, adaptive = true,})
ooc.set_integrator(ctx, integrator)
for step = 1, 10000 do if not ooc.step(ctx) then ooc.log(ooc.LOG_LEVEL_ERROR, "step %d failed", step) break end
if step % 100 == 0 then local m = ooc.step_metrics_latest(ctx) if m then ooc.log("t=%.3f dt=%.4f rms=%.2e", ooc.get_time(ctx), m.accepted_dt, m.rms_error) end endend
ooc.shutdown(ctx)Scheduler Ownership
Section titled “Scheduler Ownership”These helpers start and control the long-running scheduler loop:
run(ctx, [config])pause(ctx)resume(ctx)shutdown(ctx)is_running(ctx) -> booleanrun(ctx, [config]) reuses the context’s stored config and optionally patches it with the same recognized keys as create, such as worker_count, enable_logging, enable_profiling, enable_timestep_heuristics, frame_time_budget_ms, backend, backend_fallback, and log_path.
ooc.run(ctx, { worker_count = 4, enable_logging = true, log_path = "logs/scheduler.log",})Use pause / resume only when the scheduler currently owns the loop. shutdown stops the context and releases runtime resources.
Driver State Snapshot
Section titled “Driver State Snapshot”For host integration and debugging, use:
driver_state(ctx) -> state|nilThe returned table includes:
active_modeandownerfor the current driver arrangementscheduler_state,running, andpausedscheduler_initializedintegrator_attachedandintegrator_sequence_countstep_metrics_availableandstep_metrics_countexternal_driver_depthinspect_allowed,export_allowed, andmutation_allowedcaller_loop_control_allowedandscheduler_control_allowedaccess_reasonloop_progresswhen a bounded or cooperative loop is active
Minimal example:
local state = ooc.driver_state(ctx)if state then ooc.log("owner=%s running=%s paused=%s", state.owner, tostring(state.running), tostring(state.paused))endExternal Driver Control
Section titled “External Driver Control”Embedders can explicitly claim loop ownership:
begin_external_driver(ctx) -> depthend_external_driver(ctx) -> depthwith_external_driver(ctx, fn) -> ...with_external_driver is the safest pattern. It acquires ownership, calls fn(ctx), then releases ownership even if you nest driver scopes elsewhere.
ooc.with_external_driver(ctx, function(context) for i = 1, 128 do if not ooc.step(context) then break end endend)Cooperative Loop Helpers
Section titled “Cooperative Loop Helpers”These APIs help external drivers coordinate cancellation, pause, and bounded sleeps:
loop_checkpoint(ctx) -> checkpointrequest_loop_stop(ctx) -> checkpointclear_loop_stop(ctx) -> checkpointloop_cooperate(ctx, [opts]) -> checkpointThe checkpoint table includes:
step_indexsim_timepending_cancelshould_stoplast_stop_reasonhas_errorlast_errorprogressdriver
loop_cooperate(ctx, { sleep_seconds = x }) clamps the cooperative sleep request into the runtime’s safe range before sleeping, then returns the same checkpoint table plus:
sleep_requested_secondssleep_secondssleep_appliedsleep_clamped
Example:
ooc.with_external_driver(ctx, function(context) while true do if not ooc.step(context) then break end
local cp = ooc.loop_cooperate(context, { sleep_seconds = 0.01 }) if cp.should_stop then break end endend)Scheduler Logs
Section titled “Scheduler Logs”scheduler_drain_logs(ctx, [min_level], [callback]) -> booleanAccepted call patterns:
scheduler_drain_logs(ctx)drains and discards all pending recordsscheduler_drain_logs(ctx, "warn")drains onlywarnand abovescheduler_drain_logs(ctx, function(level, message) ... end)drains and invokes the callbackscheduler_drain_logs(ctx, "info", function(level, message) ... end)combines filtering and callbacks
This only produces records when scheduler logging is enabled.
GUI / Host-returned Contexts
Section titled “GUI / Host-returned Contexts”In GUI or embedder scripts, the common pattern is to build a context and return it:
local ctx = ooc.create()ooc.set_timestep(ctx, 0.025)
local field = ooc.add_field(ctx, {512}, { type = "complex_double", fill = {0.0, 0.0},})
ooc.add_zero_field_operator(ctx, field)ooc.set_integrator(ctx, ooc.create_context_integrator(ctx, "euler"))
return ctxThe host can then inspect driver_state, claim external ownership, or run the scheduler around that returned context.
Applying Snapshots
Section titled “Applying Snapshots”apply_snapshot(ctx, snapshot_table) -> applied_countEach snapshot entry must include:
nameschemablobas a hex string
Optional fields:
indexas an operator index hintsizeas the expected decoded blob size
Entries that do not decode cleanly are skipped. If the native snapshot apply fails, the binding raises a Lua error and reports how many overrides were already applied.
local applied = ooc.apply_snapshot(ctx, { { name = "stimulus_sine#0", schema = "stimulus_sine", blob = "<hex...>", }})
ooc.log("applied %d snapshot entries", applied)