Each ingest stream gets two reconciler resources (device, transport) with dependencies: transport waits for device OPEN (needs format for STREAM_OPEN), device waits for transport CONNECTED before starting capture. START_INGEST sets wanted state and triggers a tick; the reconciler drives device CLOSED→OPEN→STREAMING and transport DISCONNECTED→CONNECTED over subsequent ticks. STOP_INGEST reverses both. External events (transport drop, ingest thread error) use reconciler_force_current to push state backward; the periodic 500ms timer thread re-drives toward wanted state automatically. All 8 stream slots are pre-allocated at startup. on_ingest_frame sends VIDEO_FRAME messages over the outbound transport connection, protected by a per-stream conn_mutex. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
109 lines
3.9 KiB
C
109 lines
3.9 KiB
C
#pragma once
|
|
|
|
/*
|
|
* Generic declarative state machine reconciler.
|
|
*
|
|
* Each managed resource is described as a directed graph of states
|
|
* with labelled transitions. The reconciler finds the shortest path
|
|
* (BFS) from a resource's current state to its wanted state and
|
|
* executes one transition per tick.
|
|
*
|
|
* Dependencies between resources prevent a resource from advancing
|
|
* past a threshold state until a prerequisite resource reaches a
|
|
* minimum state.
|
|
*
|
|
* Usage:
|
|
* struct Reconciler *r = reconciler_create();
|
|
*
|
|
* static const struct Rec_Transition dev_trans[] = {
|
|
* {0, 1, open_device},
|
|
* {1, 0, close_device},
|
|
* {1, 2, start_capture},
|
|
* {2, 1, stop_capture},
|
|
* {-1, -1, NULL}
|
|
* };
|
|
* static const char *dev_states[] = {"CLOSED", "OPEN", "STREAMING"};
|
|
* struct Rec_Resource *dev = reconciler_add_resource(r, "device",
|
|
* dev_trans, 3, dev_states, 0, &my_device);
|
|
*
|
|
* reconciler_set_wanted(dev, 2);
|
|
* while (!reconciler_is_stable(r)) {
|
|
* reconciler_tick(r);
|
|
* }
|
|
*/
|
|
|
|
/* Transition table entry. Sentinel: {-1, -1, NULL}.
|
|
* action: return 1 on success, 0 on failure.
|
|
* On failure the resource stays in 'from' state. */
|
|
struct Rec_Transition {
|
|
int from;
|
|
int to;
|
|
int (*action)(void *userdata);
|
|
};
|
|
|
|
typedef enum {
|
|
REC_STATUS_STABLE, /* current == wanted */
|
|
REC_STATUS_WORKING, /* current != wanted, next transition is eligible */
|
|
REC_STATUS_BLOCKED, /* current != wanted, a dependency is unsatisfied */
|
|
REC_STATUS_NO_PATH, /* current != wanted, no transition path exists */
|
|
} Rec_Status;
|
|
|
|
struct Reconciler;
|
|
struct Rec_Resource;
|
|
|
|
/* Optional log callback — called after each transition attempt. */
|
|
typedef void (*Rec_Log_Fn)(
|
|
const struct Rec_Resource *res,
|
|
int from, int to, int success,
|
|
void *userdata);
|
|
|
|
struct Reconciler *reconciler_create(void);
|
|
void reconciler_destroy(struct Reconciler *r);
|
|
|
|
/* Set a log callback. Called after every transition attempt. */
|
|
void reconciler_set_log(struct Reconciler *r, Rec_Log_Fn fn, void *userdata);
|
|
|
|
/* Add a resource.
|
|
* transitions: caller-owned, sentinel-terminated {-1,-1,NULL}.
|
|
* state_names: optional array of state_count strings; NULL for numeric display.
|
|
* initial_state: sets both current and wanted initially. */
|
|
struct Rec_Resource *reconciler_add_resource(
|
|
struct Reconciler *r,
|
|
const char *name,
|
|
const struct Rec_Transition *transitions,
|
|
int state_count,
|
|
const char **state_names,
|
|
int initial_state,
|
|
void *userdata);
|
|
|
|
/* Add a dependency: resource cannot reach state >= blocked_below
|
|
* unless dep is currently in state >= dep_min_state. */
|
|
void reconciler_add_dep(
|
|
struct Rec_Resource *resource,
|
|
int blocked_below,
|
|
struct Rec_Resource *dep,
|
|
int dep_min_state);
|
|
|
|
void reconciler_set_wanted(struct Rec_Resource *r, int wanted_state);
|
|
|
|
/*
|
|
* Force current state without executing a transition.
|
|
* Use when an external event pushes a resource into a new state —
|
|
* e.g. a transport connection drops unexpectedly, or a device error
|
|
* causes the capture thread to exit. The reconciler will drive back
|
|
* toward wanted state on the next tick.
|
|
*/
|
|
void reconciler_force_current(struct Rec_Resource *r, int state);
|
|
int reconciler_get_current(const struct Rec_Resource *r);
|
|
int reconciler_get_wanted(const struct Rec_Resource *r);
|
|
const char *reconciler_get_name(const struct Rec_Resource *r);
|
|
const char *reconciler_state_name(const struct Rec_Resource *r, int state);
|
|
Rec_Status reconciler_get_status(const struct Rec_Resource *r);
|
|
|
|
/* Run one reconciliation pass over all resources.
|
|
* Returns number of transitions attempted (success or failure). */
|
|
int reconciler_tick(struct Reconciler *r);
|
|
|
|
/* Returns 1 if all resources have current == wanted. */
|
|
int reconciler_is_stable(const struct Reconciler *r);
|