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;
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;
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.
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.
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.
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;
};
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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).
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).
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).
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).
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.
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.