Table of Contents

Namespace Ofs

Classes

AppScoped<T>

A value of type T that lives in this plugin's global settings: it is loaded once when created and saved back whenever it changes. Get it once in OnLoad() via AppScoped<T>(string), then read and mutate Value directly (its fields/properties bind straight into Ui widgets) — the host flushes edits once per frame and again on app close, so there is nothing to call.

Unlike ProjectScoped<T>, this is global to all projects (it does not reset or reload when a different project is opened); it is persisted in this plugin's own file under the app's settings directory. T is JSON-serialized including public fields, so a plain settings class with public fields works without attributes. Main-thread only.

Axes

The set of funscript axes. Reads are cheap and direct through the indexer; writes go through a buffered Edit() finished with Commit().

Axis

A read-only snapshot of one axis for the current frame: its sorted actions, selection and O(log n) lookup helpers. Begin a write with Edit().

AxisEdit

A buffered, mutable working copy of an axis. Mutators are fluent and accumulate locally — nothing is applied to the project until Commit(). One Commit is one undo step. An uncommitted edit is a clean no-op.

Commands

Command registration. Handlers are supplied inline — there is no central dispatch switch. Call Register(string, string, Action, bool, bool) from OnLoad.

Dialogs

Native file/folder/message dialogs, backed by the host's native dialog integration. Every call is NON-BLOCKING: it queues the dialog and returns a Task that completes — on the main thread, on a later frame — once the user answers. await it from a command or button handler; the frame loop keeps rendering while the dialog is open. Main-thread only (call, not the await).

DiscreteWriter

Write sink for a discrete node callback.

Editing

Registration of alternate edit modes. A mode owns how the user's editing gestures resolve — it can observe, drop, or replace each EditIntent. Registering only publishes the mode; the user activates it from the footer (there is no plugin-callable setter). Call RegisterMode(string, string, EditIntentHandler, Action?, Action?, Action<Ui>?) from OnLoad().

Navigation

Registration of alternate navigators. A navigator owns what the prev/next-step keys do — given a step, it decides where the playhead goes (next action, a custom grid, …). It does not move the playhead any other way and cannot edit actions. Registering only publishes the navigator; the user activates it from the footer (no plugin-callable setter). Call RegisterMode(string, string, NavStepHandler, Action?, Action?, Action<Ui>?) from OnLoad().

Node

A capture-safe handle to a plugin node, obtained from Node() inside the node's NodeUi<TState> callback. A TState value cannot be captured by ref across an await, so async work captures this reference instead and calls Update<TState>(StateMutator<TState>) when it completes. The mutation is queued and applied on the main thread on the node's next UI pass — replace reference fields, don't mutate them in place.

Nodes

Registration of custom processing nodes. A node declares a NodeShape (N named input pins, M named output pins) and one eval delegate; the signal kind (functional vs discrete) is chosen by the delegate shape.

OfsPlugin

Base class for every ofs-ng plugin. Derive from it and override only what you need. The single required member is Name.

Player

Playback state, commands and events. State is exposed as properties; commands are methods; notifications are events. All members must be touched on the main thread.

Project

Access to project-level state: unsaved status, metadata, chapters, bookmarks and processing regions (all read-only snapshots), plus this plugin's own per-project data store (Scoped<T>(string)), which is persisted inside the .ofp and round-trips with save/load. All members are main-thread only and read fresh each call — the returned lists are snapshots, not live views.

ProjectScoped<T>

A value of type T that lives in the current project: it is loaded from the project file on creation, automatically reloaded whenever a different project is opened, and saved back when it changes. Get it once in OnLoad() via Scoped<T>(string), then read and mutate Value directly (its fields/properties bind straight into Ui widgets) — the host flushes edits once per frame, so there is nothing to call. A project with nothing stored under the key resets Value to a fresh new T(), so settings never bleed between projects.

T is JSON-serialized including public fields, so a plain settings class with public fields works without attributes. Main-thread only.

Selection

Registration of alternate selection modes. A mode owns which actions a selection gesture selects — given the region the user gestured over, it decides what becomes selected (only the peaks, only actions above a threshold, every Nth, …). It cannot edit actions, only select existing ones. Registering only publishes the mode; the user activates it from the footer's Select selector (no plugin-callable setter). Call RegisterMode(string, string, SelectHandler, Action?, Action?, Action<Ui>?) from OnLoad().

StandardAxisExtensions

Helpers for StandardAxis.

Ui

Immediate-mode widgets, drawn each frame inside OnRenderUi(Ui). Boolean-returning widgets report "changed this frame"; value widgets bind through ref.

Conventions: widget width is host-owned — there is no pixel-size parameter; a widget fills the available width, and widgets inside Row(string, Action) split it evenly, so plugin UI is automatically font-, DPI- and translation-safe (use Section(string, Action)/Row(string, Action) for layout). Labels and titles are rendered verbatim — localizing them is the plugin's job. maxBytes parameters count UTF-8 bytes, not characters (a CJK glyph is 3 bytes, an emoji 4), so size text buffers generously for non-ASCII input.

Structs

DiscreteReader

Read-only view of a discrete node input.

EditIntent

A single edit gesture an active edit mode observes and may transform. The host hands one of these to onIntent; only the fields named for Kind are meaningful. To replace a gesture, build new intents with the static factory methods and return Replace(params EditIntent[]).

EditResult

What an edit mode decides for an intent: apply it unchanged, drop it, or replace it with other intents.

Nav

A navigator's answer to a NavStep: seek to a time, do nothing, or pass the step to the native resolution for its granularity.

NavStep

A step request handed to an active navigator: which way, how many steps, which channel.

NodeContext

Evaluation context passed to every node callback. Pure value — safe on any thread. The params buffer is valid only for the duration of the call; do not store it.

NodeShape

A node's pin shape: the input and output pin names. Each array's length is that direction's pin count. Pin names double as labels (and, for scripts, injected locals). Construct with named pins, e.g. new NodeShape(inputs: ["a", "b"], outputs: ["mix"]).

ProjectBookmark

A point-in-time bookmark.

ProjectChapter

A named chapter: a time range with a color.

ProjectRegion

A processing region: a named time range with its own node graph.

ScriptAction

A point in a funscript: a timestamp (At, seconds) and a position (Pos, 0–100). Value type with value equality, deconstruction and with-expressions. The sequential layout keeps it blittable so it can cross the native ABI without marshaling.

SelectRequest

One selection gesture handed to an active selection mode, once per editable axis (the host fans out per-axis). The mode reads the region and axis, enumerates candidates itself through Host.Axes[req.Axis], and returns the actions it keeps.

SelectResult

What a selection mode decides for a request: select the native candidates, select nothing, or select exactly the named actions instead.

Interfaces

IOfsHost

The host surface available to a plugin, organized into cohesive sub-objects.

Enums

ConfirmKind

The button set for Confirm(string, string, ConfirmKind, CancellationToken).

EditIntentKind

The kind of an EditIntent — what authoring gesture the user made. Mirrors the host's edit-intent vocabulary.

FunscriptVersion

The funscript on-disk format version selectable in FunscriptJson(FunscriptVersion, params StandardAxis[]).

LogLevel

Severity of a host log message written via Log(LogLevel, string).

NavGranularity

Which step a NavStep targets. A navigator may redefine one channel and Pass the rest back to the native (overlay / adjacent-action) resolution.

NodeIcon

The glyph a node shows in the add-node palette and on its title bar — a curated subset of the host's icons, chosen by value so a plugin never needs the host font's codepoints. Pass one to AddNode<TState>(string, string, NodeShape, EvalFunctional<TState>, NodeUi<TState>?, string?, NodeIcon, string?). Default lets the host pick by arity (Generate / Modify / Combine). The list is append-only; an older host that doesn't know a newer value falls back to Default.

NotifyLevel

Severity of a user-facing toast raised via Notify(NotifyLevel, string).

SelectGesture

Which selection-authoring gesture a SelectRequest carries. A mode reinterprets what the gesture means; the native resolution selects every candidate it covers.

StandardAxis

A funscript axis role, including the trailing Count sentinel. The values are part of the frozen plugin ABI and never change.

StepDirection

A step / nudge direction shared by Direction and Direction. None is an EditIntent-only state: a position nudge rather than a time nudge (Pos carries the delta). A NavStep only ever carries Backward or Forward.

Delegates

EditIntentHandler

Handles one edit intent for an active mode. Runs on the main thread.

EvalDiscrete

Discrete eval (worker thread) for a stateless node: read the N input action lists and write the M output action lists once per region.

EvalDiscrete<TState>

Discrete eval (worker thread): read the N input action lists and write the M output action lists once per region.

EvalFunctional

Functional eval (worker thread) for a stateless node: given time t, read the N input values in ins and write the M output values (0..100) into outs.

EvalFunctional<TState>

Functional eval (worker thread): given time t, read the N input values in ins and write the M output values (0..100) into outs.

FunctionalSample

The per-sample closure a functional factory returns: read the N input values, write the M output values. Built once per region eval, sampled per output sample.

NavStepHandler

Resolves a NavStep into a Nav. Runs on the main thread.

NodeUi<TState>

The node's body UI (main thread). Draws the node's widgets — sliders/checkboxes for its TState fields, buttons, file pickers, status labels. s is the live state by ref: mutate it synchronously, replacing reference fields with new arrays/lists rather than mutating in place. For a deferred/async write (across an await) grab a capture-safe handle with Node() and call Update<TState>(StateMutator<TState>) when the work completes — a value type cannot be captured by ref across an await.

PrepareFunctional

Functional factory (worker thread) for a stateless node: runs once per region eval and returns the per-sample closure.

PrepareFunctional<TState>

Functional factory (worker thread): runs once per region eval and returns the per-sample closure. Build a non-serializable artifact (LUT, interpolator, loaded dataset) here rather than rebuilding it every sample.

SelectHandler

Resolves one SelectRequest into a SelectResult. Runs on the main thread.

StateMutator<TState>

A deferred mutation applied on the main thread to a node's live state, via Update<TState>(StateMutator<TState>).