sig_capsule.h

include/sig/runtime/sig_capsule.h

sig_capsule_instance

typedef struct sig_capsule_instance sig_capsule_instance;

sig_job

typedef struct sig_job sig_job;

sig_job_fn

typedef void (*sig_job_fn)(sig_capsule_instance *instance, void *job_data);

SIG_TERMINAL_DEFAULT

#define SIG_TERMINAL_DEFAULT "__default_terminal_value"

sig_terminal_fn

typedef sig_status_t (*sig_terminal_fn)(sig_capsule_instance *instance, const sig_value *signal,
                                        const char *terminal_name);

sig_terminal

typedef struct {
    const char *name;
    sig_terminal_fn handler; /* meaningful for input terminals only */
} sig_terminal;

sig_capsule_lifecycle_fn

typedef sig_status_t (*sig_capsule_lifecycle_fn)(sig_capsule_instance *instance);

sig_property_type

typedef enum {
    SIG_PROPERTY_INT,
    SIG_PROPERTY_BOOL,
    SIG_PROPERTY_DOUBLE, /* an integer in the configuration is accepted and widened */
    SIG_PROPERTY_STRING,
} sig_property_type;

A configurable property of a capsule. A capsule declares its properties once, in a table; the framework validates a configuration against it, applies the defaults, and hands the capsule typed values (sig_capsule_instance_int() and friends) indexed by the property's position in the table. Declare the table with designated initializers keyed by an enum so a hook reads the property by name in the source and by index at run time: enum { SIM_END, SIM_STEP }; static const sig_property sim_props[] = { [SIM_END] = SIG_PROP_INT_REQUIRED("end", "Inclusive upper bound"), [SIM_STEP] = SIG_PROP_INT_MIN("step", 1, 1, "Increment"), };

sig_property

typedef struct {
    const char *name;
    sig_property_type type;
    bool required; /* if true there is no default */
    union {
        sig_int_t i;
        bool b;
        double d;
        const char *s;
    } def;        /* used when !required */
    bool has_min; /* ints only */
    sig_int_t min;
    bool has_max;
    sig_int_t max;
    const char *choices; /* strings only: NULL, or "a|b|c", compared case-insensitively */
    const char *description;
} sig_property;

SIG_PROP_INT

#define SIG_PROP_INT(nm, dv, ds) \
    { .name = (nm), .type = SIG_PROPERTY_INT, .def.i = (dv), .description = (ds) }

SIG_PROP_INT_REQUIRED

#define SIG_PROP_INT_REQUIRED(nm, ds) \
    { .name = (nm), .type = SIG_PROPERTY_INT, .required = true, .description = (ds) }

SIG_PROP_INT_MIN

#define SIG_PROP_INT_MIN(nm, dv, lo, ds) \
    { .name = (nm),                      \
      .type = SIG_PROPERTY_INT,          \
      .def.i = (dv),                     \
      .has_min = true,                   \
      .min = (lo),                       \
      .description = (ds) }

SIG_PROP_INT_REQUIRED_MIN

#define SIG_PROP_INT_REQUIRED_MIN(nm, lo, ds) \
    { .name = (nm),                           \
      .type = SIG_PROPERTY_INT,               \
      .required = true,                       \
      .has_min = true,                        \
      .min = (lo),                            \
      .description = (ds) }

SIG_PROP_INT_RANGE

#define SIG_PROP_INT_RANGE(nm, dv, lo, hi, ds) \
    { .name = (nm),                            \
      .type = SIG_PROPERTY_INT,                \
      .def.i = (dv),                           \
      .has_min = true,                         \
      .min = (lo),                             \
      .has_max = true,                         \
      .max = (hi),                             \
      .description = (ds) }

SIG_PROP_BOOL

#define SIG_PROP_BOOL(nm, dv, ds) \
    { .name = (nm), .type = SIG_PROPERTY_BOOL, .def.b = (dv), .description = (ds) }

SIG_PROP_DOUBLE

#define SIG_PROP_DOUBLE(nm, dv, ds) \
    { .name = (nm), .type = SIG_PROPERTY_DOUBLE, .def.d = (dv), .description = (ds) }

SIG_PROP_STRING

#define SIG_PROP_STRING(nm, dv, ds) \
    { .name = (nm), .type = SIG_PROPERTY_STRING, .def.s = (dv), .description = (ds) }

SIG_PROP_STRING_CHOICES

#define SIG_PROP_STRING_CHOICES(nm, dv, list, ds) \
    { .name = (nm), .type = SIG_PROPERTY_STRING, .def.s = (dv), .choices = (list), .description = (ds) }

sig_capsule

typedef struct {
    const char *name;
    size_t num_input_terminals;
    const sig_terminal *input_terminals;
    size_t num_output_terminals;
    const sig_terminal *output_terminals;
    sig_capsule_lifecycle_fn start; /* nullable */
    sig_capsule_lifecycle_fn stop;  /* nullable */
    size_t num_properties;          /* 0: no table, any configuration properties are accepted */
    const sig_property *properties; /* the capsule's property table, see sig_property */
    /* What the capsule is for, shown by the management API: "source" (only
     * emits), "sink" (only consumes), "flow" (routes or filters a stream) or
     * "state" (keeps state across signals). NULL: uncategorized. */
    const char *category;
} sig_capsule;

sig_capsule_registry

typedef struct sig_capsule_registry sig_capsule_registry;

sig_capsule_registry_create

sig_status_t sig_capsule_registry_create(sig_capsule_registry ** out)
ParameterType
outsig_capsule_registry **

sig_capsule_registry_destroy

void sig_capsule_registry_destroy(sig_capsule_registry * reg)
ParameterType
regsig_capsule_registry *

sig_capsule_registry_register

sig_status_t sig_capsule_registry_register(sig_capsule_registry * reg, const sig_capsule * capsule)
ParameterType
regsig_capsule_registry *
capsuleconst sig_capsule *

sig_capsule_fn

typedef sig_status_t (*sig_capsule_fn)(const sig_capsule *capsule, void *ctx);

Visits every registered capsule, in unspecified order. A non-SIG_OK return from fn stops the walk and becomes the result.

sig_capsule_registry_walk

sig_status_t sig_capsule_registry_walk(const sig_capsule_registry * reg, sig_capsule_fn fn, void * ctx)
ParameterType
regconst sig_capsule_registry *
fnsig_capsule_fn
ctxvoid *

shared with sig_capsule_fn

Visits every registered capsule, in unspecified order. A non-SIG_OK return from fn stops the walk and becomes the result.

sig_capsule_registry_lookup

sig_status_t sig_capsule_registry_lookup(const sig_capsule_registry * reg, const char * name, const sig_capsule ** out)
ParameterType
regconst sig_capsule_registry *
nameconst char *
outconst sig_capsule **

shared with sig_capsule_fn

Visits every registered capsule, in unspecified order. A non-SIG_OK return from fn stops the walk and becomes the result.

sig_capsule_configuration

typedef struct {
    char *name;
    const sig_capsule *capsule; /* borrowed, not owned: capsule must
                                       * outlive every sig_capsule_configuration that
                                       * references it, since the pointer
                                       * is never copied. */
    sig_ht *properties;         /* string -> boxed sig_value, dtor = sig_value_dtor_v.
                                           * Like all sig_ht instances, this table is NOT
                                           * internally synchronized (see sig_ht.h). A
                                           * sig_capsule_configuration shared across instances/threads needs
                                           * caller-provided locking (e.g. sig_mutex from
                                           * sig_thread.h) around property access. */
} sig_capsule_configuration;

sig_capsule_configuration_create

sig_status_t sig_capsule_configuration_create(sig_capsule_configuration ** out, const char * name, const sig_capsule * module)
ParameterType
outsig_capsule_configuration **
nameconst char *
moduleconst sig_capsule *

sig_capsule_configuration_destroy

void sig_capsule_configuration_destroy(sig_capsule_configuration * capsule)
ParameterType
capsulesig_capsule_configuration *

sig_capsule_configuration_set_property

sig_status_t sig_capsule_configuration_set_property(sig_capsule_configuration * capsule, const char * key, const sig_value * value)
ParameterType
capsulesig_capsule_configuration *
keyconst char *
valueconst sig_value *

Shares, does not take ownership of *value: value is boxed via sig_value_copy's share semantics (see sig_value.h), not consumed. The caller still owns *value and must still call sig_value_dtor() on it themselves.

sig_capsule_configuration_get_property

sig_status_t sig_capsule_configuration_get_property(const sig_capsule_configuration * capsule, const char * key, const sig_value ** out)
ParameterType
capsuleconst sig_capsule_configuration *
keyconst char *
outconst sig_value **

out is a borrowed pointer into the capsule's property store: valid only until the next sig_capsule_configuration_set_property() call on the SAME key, which destroys the previously boxed value in place (via sig_ht_update) and invalidates any pointer this function returned for that key. Not owned by the caller; do not free.

sig_capsule_configuration_property_fn

typedef sig_status_t (*sig_capsule_configuration_property_fn)(const char *key, const sig_value *value,
                                                              void *ctx);

Read-only enumeration of a capsule configuration's configured properties (the config-time table; this is not live capsule state). Order is unspecified. A non-SIG_OK return from fn stops the walk and becomes its result. `value` is borrowed for the duration of the callback. A configuration's properties are not mutated after project load, so this is safe from any thread then.

sig_capsule_configuration_property_count

uint32_t sig_capsule_configuration_property_count(const sig_capsule_configuration * capsule)
ParameterType
capsuleconst sig_capsule_configuration *

sig_capsule_configuration_walk_properties

sig_status_t sig_capsule_configuration_walk_properties(const sig_capsule_configuration * capsule, sig_capsule_configuration_property_fn fn, void * ctx)
ParameterType
capsuleconst sig_capsule_configuration *
fnsig_capsule_configuration_property_fn
ctxvoid *

sig_service

typedef struct sig_service sig_service;

sig_capsule_instance

struct sig_capsule_instance {
    char *name;
    const sig_capsule_configuration *configuration; /* borrowed, not owned: configuration must outlive
                               * every sig_capsule_instance that references
                               * it, since the pointer is never copied. */
    sig_ht *storage;                                /* string -> boxed sig_value, dtor = sig_value_dtor_v.
                                 * Like all sig_ht instances, this table is NOT
                                 * internally synchronized (see sig_ht.h). A
                                 * sig_capsule_instance touched from more than one
                                 * thread needs caller-provided locking (e.g.
                                 * sig_mutex from sig_thread.h) around storage
                                 * access. */
    sig_service *service;                           /* borrowed; set by sig_service_add_instance().
                                 * NULL until the instance is added to a
                                 * service. Once non-NULL, the instance is
                                 * "running": only that service's own thread
                                 * may safely touch this instance's storage/
                                 * state directly, from inside a handler
                                 * or lifecycle hook. sig_capsule_instance_notify()
                                 * (added later in this sub-project) is the
                                 * only thread-safe way to affect a running
                                 * instance from any other thread. */
    void *state;
    void (*state_dtor)(void *state); /* nullable; called with state by
                                           * sig_capsule_instance_destroy()
                                           * if state is non-NULL. */
    sig_ht *routes;                  /* output terminal name -> sig_ll of destination
                                 * routes (private sig_route entries, defined in
                                 * sig_capsule.c). Populated by
                                 * sig_capsule_instance_connect(). Same sig_ht
                                 * synchronization caveat as storage -- connect()
                                 * calls before the instance is added to a
                                 * started service, or from that service's own
                                 * thread, are safe; concurrent connect() calls
                                 * from other threads on a running instance are
                                 * not. */
    /* The capsule's property values, resolved at start and read with
     * sig_capsule_instance_int() and friends. NULL until resolved. */
    sig_value *properties;
    size_t num_properties;
};

sig_capsule_configuration_validate

sig_status_t sig_capsule_configuration_validate(const sig_capsule_configuration * configuration, char * err, size_t err_cap)
ParameterType
configurationconst sig_capsule_configuration *
errchar *
err_capsize_t

Checks the configuration's properties against its capsule's table: required present, types, int ranges, string choices, and no unknown names. SIG_OK, or SIG_ERR_INVALID_ARG with a message such as "end: required" or "step: must be >= 1" copied (truncated) into err[0..err_cap) when err is non-NULL. A capsule without a table accepts everything.

sig_capsule_instance_create

sig_status_t sig_capsule_instance_create(sig_capsule_instance ** out, const char * name, const sig_capsule_configuration * capsule)
ParameterType
outsig_capsule_instance **
nameconst char *
capsuleconst sig_capsule_configuration *

sig_capsule_instance_destroy

void sig_capsule_instance_destroy(sig_capsule_instance * instance)
ParameterType
instancesig_capsule_instance *

sig_capsule_instance_resolve

sig_status_t sig_capsule_instance_resolve(sig_capsule_instance * instance)
ParameterType
instancesig_capsule_instance *

Resolves the instance's configuration against the capsule's property table: checks every value, applies defaults, and fills the values read by the accessors below. Replaces any earlier resolution. sig_service_start() calls this before the capsule's start hook, so a hook may rely on it; a capsule's unit test that calls a hook directly must call it first. SIG_ERR_INVALID_ARG (logged, naming the property) if a value is invalid or a required one is missing. An unknown property only logs a warning here (sig_capsule_ configuration_validate() rejects it). A capsule without a table resolves to nothing and the accessors are unused.

sig_capsule_instance_int

sig_int_t sig_capsule_instance_int(const sig_capsule_instance * instance, size_t index)
ParameterType
instanceconst sig_capsule_instance *
indexsize_t

Typed reads of a resolved property by its index in the capsule's table. Safe from the capsule's hooks, terminal handlers and job callbacks. An index out of range, a property of another type, or an instance that has not been resolved logs an error and returns 0 / false / 0.0 / "" -- it never crashes. The string is owned by the instance and valid until the next resolve or the instance's destruction.

sig_capsule_instance_bool

bool sig_capsule_instance_bool(const sig_capsule_instance * instance, size_t index)
ParameterType
instanceconst sig_capsule_instance *
indexsize_t

shared with sig_capsule_instance_int

Typed reads of a resolved property by its index in the capsule's table. Safe from the capsule's hooks, terminal handlers and job callbacks. An index out of range, a property of another type, or an instance that has not been resolved logs an error and returns 0 / false / 0.0 / "" -- it never crashes. The string is owned by the instance and valid until the next resolve or the instance's destruction.

sig_capsule_instance_double

double sig_capsule_instance_double(const sig_capsule_instance * instance, size_t index)
ParameterType
instanceconst sig_capsule_instance *
indexsize_t

shared with sig_capsule_instance_int

Typed reads of a resolved property by its index in the capsule's table. Safe from the capsule's hooks, terminal handlers and job callbacks. An index out of range, a property of another type, or an instance that has not been resolved logs an error and returns 0 / false / 0.0 / "" -- it never crashes. The string is owned by the instance and valid until the next resolve or the instance's destruction.

sig_capsule_instance_string

const char * sig_capsule_instance_string(const sig_capsule_instance * instance, size_t index)
ParameterType
instanceconst sig_capsule_instance *
indexsize_t

shared with sig_capsule_instance_int

Typed reads of a resolved property by its index in the capsule's table. Safe from the capsule's hooks, terminal handlers and job callbacks. An index out of range, a property of another type, or an instance that has not been resolved logs an error and returns 0 / false / 0.0 / "" -- it never crashes. The string is owned by the instance and valid until the next resolve or the instance's destruction.

sig_capsule_instance_set_storage

sig_status_t sig_capsule_instance_set_storage(sig_capsule_instance * instance, const char * key, const sig_value * value)
ParameterType
instancesig_capsule_instance *
keyconst char *
valueconst sig_value *

Shares, does not take ownership of *value: value is boxed via sig_value_copy's share semantics (see sig_value.h), not consumed. The caller still owns *value and must still call sig_value_dtor() on it themselves.

sig_capsule_instance_get_storage

sig_status_t sig_capsule_instance_get_storage(const sig_capsule_instance * instance, const char * key, const sig_value ** out)
ParameterType
instanceconst sig_capsule_instance *
keyconst char *
outconst sig_value **

out is a borrowed pointer into the instance's storage: valid only until the next sig_capsule_instance_set_storage() call on the SAME key, which destroys the previously boxed value in place (via sig_ht_update) and invalidates any pointer this function returned for that key. Not owned by the caller; do not free.

sig_capsule_instance_set_state

sig_status_t sig_capsule_instance_set_state(sig_capsule_instance * instance, void * state, void (*)(void *) dtor)
ParameterType
instancesig_capsule_instance *
statevoid *
dtorvoid (*)(void *)

Takes ownership of `state` conceptually: `dtor` (nullable) is called with `state` by sig_capsule_instance_destroy(), or by a later sig_capsule_instance_set_state() call that replaces it. Use this for per-instance resources that aren't sig_value data (a socket fd, a timer handle, a scratch buffer) — sig_capsule_instance's storage map only holds sig_value.

sig_capsule_instance_get_state

void * sig_capsule_instance_get_state(const sig_capsule_instance * instance)
ParameterType
instanceconst sig_capsule_instance *

shared with sig_capsule_instance_set_state

Takes ownership of `state` conceptually: `dtor` (nullable) is called with `state` by sig_capsule_instance_destroy(), or by a later sig_capsule_instance_set_state() call that replaces it. Use this for per-instance resources that aren't sig_value data (a socket fd, a timer handle, a scratch buffer) — sig_capsule_instance's storage map only holds sig_value.

sig_capsule_instance_connect

sig_status_t sig_capsule_instance_connect(sig_capsule_instance * src, const char * out_terminal, sig_capsule_instance * dest, const char * in_terminal)
ParameterType
srcsig_capsule_instance *
out_terminalconst char *
destsig_capsule_instance *
in_terminalconst char *

Records that signals emitted on src's out_terminal should be delivered to dest's in_terminal. May be called multiple times for the same out_terminal to fan out to multiple destinations. Safe to call before either instance is added to a service; unsafe to call concurrently with sig_capsule_instance_notify() on the same src from a different thread once src's instance is running in a started service (see the routes field comment above).

sig_capsule_route_fn

typedef sig_status_t (*sig_capsule_route_fn)(const char *out_terminal, const sig_capsule_instance *dest,
                                             const char *in_terminal, void *ctx);

Visits every route recorded by sig_capsule_instance_connect() on `instance`, in unspecified order. out_terminal, dest and in_terminal are borrowed for the duration of the callback. A non-SIG_OK return from fn stops the walk and becomes this function's result. Reads the same unsynchronized routes table as notify(): safe once the project is loaded (routes are only added at load time).

sig_capsule_instance_walk_routes

sig_status_t sig_capsule_instance_walk_routes(const sig_capsule_instance * instance, sig_capsule_route_fn fn, void * ctx)
ParameterType
instanceconst sig_capsule_instance *
fnsig_capsule_route_fn
ctxvoid *

shared with sig_capsule_route_fn

Visits every route recorded by sig_capsule_instance_connect() on `instance`, in unspecified order. out_terminal, dest and in_terminal are borrowed for the duration of the callback. A non-SIG_OK return from fn stops the walk and becomes this function's result. Reads the same unsynchronized routes table as notify(): safe once the project is loaded (routes are only added at load time).

sig_capsule_instance_notify

sig_status_t sig_capsule_instance_notify(sig_capsule_instance * src, const char * out_terminal, const sig_value * signal)
ParameterType
srcsig_capsule_instance *
out_terminalconst char *
signalconst sig_value *

Emits signal on src's out_terminal, delivering (asynchronously, via each destination's owning service's queue -- see sig_service.h) to every instance connected via sig_capsule_instance_connect(). Not an error if out_terminal has no connected routes -- the signal is simply dropped. Best-effort across multiple routes: a failure delivering to one connected destination does not stop delivery to the others; returns SIG_OK only if every route succeeded, otherwise the status of the last route that failed (e.g. SIG_ERR_INVALID_ARG if a destination instance was never added to any service).

sig_capsule_instance_schedule

sig_status_t sig_capsule_instance_schedule(sig_capsule_instance * instance, struct timespec interval, bool repeat, sig_job_fn callback, void * job_data, sig_job ** out_job)
ParameterType
instancesig_capsule_instance *
intervalstruct timespec
repeatbool
callbacksig_job_fn
job_datavoid *
out_jobsig_job **

Schedules callback(instance, job_data) to run on instance's owning service's own worker thread. instance must already have been added to a service (sig_service_add_instance()) -- SIG_ERR_INVALID_ARG otherwise. See sig_service_schedule_job() in sig_service.h for the full contract (timing, interval floor, handle lifetime); this is a thin wrapper that derives the service from instance->service.

sig_capsule_instance_cancel

sig_status_t sig_capsule_instance_cancel(sig_capsule_instance * instance, sig_job * job)
ParameterType
instancesig_capsule_instance *
jobsig_job *

Cancels job (previously returned by sig_capsule_instance_schedule() on this same instance) so it will not fire again. See sig_service_cancel_job() for the full contract.