Add reconciler and ingest modules with CLI driver
reconciler: generic resource state machine — BFS pathfinding from current to wanted state, dependency constraints, event/periodic tick model. reconciler_cli exercises it with simulated device/transport/stream resources. ingest: V4L2 capture module — open device, negotiate MJPEG format, MMAP buffer pool, capture thread with on_frame callback. start/stop lifecycle designed for reconciler management. Transport-agnostic: caller wires on_frame to proto_write_video_frame. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
19
src/modules/ingest/Makefile
Normal file
19
src/modules/ingest/Makefile
Normal file
@@ -0,0 +1,19 @@
|
||||
ROOT := $(abspath ../../..)
|
||||
include $(ROOT)/common.mk
|
||||
|
||||
MODULE_BUILD = $(BUILD)/ingest
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: $(MODULE_BUILD)/ingest.o
|
||||
|
||||
$(MODULE_BUILD)/ingest.o: ingest.c | $(MODULE_BUILD)
|
||||
$(CC) $(CFLAGS) $(DEPFLAGS) -c -o $@ $<
|
||||
|
||||
$(MODULE_BUILD):
|
||||
mkdir -p $@
|
||||
|
||||
clean:
|
||||
rm -f $(MODULE_BUILD)/ingest.o $(MODULE_BUILD)/ingest.d
|
||||
|
||||
-include $(MODULE_BUILD)/ingest.d
|
||||
292
src/modules/ingest/ingest.c
Normal file
292
src/modules/ingest/ingest.c
Normal file
@@ -0,0 +1,292 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/select.h>
|
||||
#include <pthread.h>
|
||||
#include <stdatomic.h>
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
#include "ingest.h"
|
||||
#include "v4l2_fmt.h"
|
||||
#include "error.h"
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Internal types
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
#define INGEST_N_BUFS 4
|
||||
|
||||
struct Mmap_Buf {
|
||||
void *start;
|
||||
size_t length;
|
||||
};
|
||||
|
||||
struct Ingest_Handle {
|
||||
int fd;
|
||||
struct Mmap_Buf bufs[INGEST_N_BUFS];
|
||||
int buf_count;
|
||||
|
||||
int width, height;
|
||||
uint32_t pixfmt;
|
||||
int fps_n, fps_d;
|
||||
|
||||
Ingest_Frame_Fn on_frame;
|
||||
Ingest_Error_Fn on_error;
|
||||
void *userdata;
|
||||
|
||||
pthread_t thread;
|
||||
atomic_int running; /* 1 = thread should keep going; 0 = stop */
|
||||
int started; /* 1 = pthread_create was called */
|
||||
};
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Capture thread
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
static void *capture_thread(void *arg)
|
||||
{
|
||||
struct Ingest_Handle *h = arg;
|
||||
|
||||
while (atomic_load(&h->running)) {
|
||||
fd_set fds;
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(h->fd, &fds);
|
||||
struct timeval tv = { 0, 100000 }; /* 100 ms — keeps stop latency short */
|
||||
|
||||
int r = select(h->fd + 1, &fds, NULL, NULL, &tv);
|
||||
if (r < 0) {
|
||||
if (errno == EINTR) { continue; }
|
||||
if (h->on_error) { h->on_error("select failed", h->userdata); }
|
||||
break;
|
||||
}
|
||||
if (r == 0) {
|
||||
continue; /* timeout — recheck running flag */
|
||||
}
|
||||
|
||||
struct v4l2_buffer buf = {0};
|
||||
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
buf.memory = V4L2_MEMORY_MMAP;
|
||||
if (v4l2_xioctl(h->fd, VIDIOC_DQBUF, &buf) < 0) {
|
||||
if (errno == EAGAIN) { continue; }
|
||||
if (h->on_error) { h->on_error("VIDIOC_DQBUF failed", h->userdata); }
|
||||
break;
|
||||
}
|
||||
|
||||
h->on_frame(
|
||||
(const uint8_t *)h->bufs[buf.index].start,
|
||||
buf.bytesused,
|
||||
h->width, h->height, h->pixfmt,
|
||||
h->userdata);
|
||||
|
||||
if (v4l2_xioctl(h->fd, VIDIOC_QBUF, &buf) < 0) {
|
||||
if (h->on_error) { h->on_error("VIDIOC_QBUF failed", h->userdata); }
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
atomic_store(&h->running, 0);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Public API
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
struct App_Error ingest_open(const struct Ingest_Config *cfg, Ingest_Handle **out)
|
||||
{
|
||||
struct Ingest_Handle *h = calloc(1, sizeof(*h));
|
||||
if (!h) { return APP_SYSCALL_ERROR(); }
|
||||
|
||||
h->fd = -1;
|
||||
h->on_frame = cfg->on_frame;
|
||||
h->on_error = cfg->on_error;
|
||||
h->userdata = cfg->userdata;
|
||||
atomic_init(&h->running, 0);
|
||||
|
||||
/* Open device */
|
||||
h->fd = open(cfg->device, O_RDWR | O_NONBLOCK);
|
||||
if (h->fd < 0) {
|
||||
free(h);
|
||||
return APP_SYSCALL_ERROR();
|
||||
}
|
||||
|
||||
/* Verify capture + streaming capability */
|
||||
struct v4l2_capability cap = {0};
|
||||
if (v4l2_xioctl(h->fd, VIDIOC_QUERYCAP, &cap) < 0) {
|
||||
close(h->fd); free(h);
|
||||
return APP_SYSCALL_ERROR();
|
||||
}
|
||||
if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) ||
|
||||
!(cap.capabilities & V4L2_CAP_STREAMING)) {
|
||||
close(h->fd); free(h);
|
||||
return APP_INVALID_ERROR_MSG(0, "device does not support MJPEG streaming capture");
|
||||
}
|
||||
|
||||
/* Format selection */
|
||||
uint32_t want_pixfmt = cfg->pixfmt ? cfg->pixfmt : V4L2_PIX_FMT_MJPEG;
|
||||
|
||||
V4l2_Fmt_Option opts[V4L2_FMT_MAX_OPTS];
|
||||
int n = v4l2_enumerate_formats(h->fd, opts, V4L2_FMT_MAX_OPTS, want_pixfmt);
|
||||
if (n == 0) {
|
||||
close(h->fd); free(h);
|
||||
return APP_INVALID_ERROR_MSG(0, "no matching formats found on device");
|
||||
}
|
||||
|
||||
/* If caller specified exact w/h use that, otherwise auto-select best */
|
||||
const V4l2_Fmt_Option *chosen;
|
||||
if (cfg->width > 0 && cfg->height > 0) {
|
||||
chosen = NULL;
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (opts[i].w == cfg->width && opts[i].h == cfg->height) {
|
||||
if (!chosen || v4l2_fmt_fps_gt(&opts[i], chosen)) {
|
||||
chosen = &opts[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!chosen) {
|
||||
/* Exact size not found — fall back to best available */
|
||||
chosen = v4l2_select_best(opts, n);
|
||||
}
|
||||
} else {
|
||||
chosen = v4l2_select_best(opts, n);
|
||||
}
|
||||
|
||||
/* Apply format */
|
||||
struct v4l2_format fmt = {0};
|
||||
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
fmt.fmt.pix.pixelformat = chosen->pixfmt;
|
||||
fmt.fmt.pix.width = (uint32_t)chosen->w;
|
||||
fmt.fmt.pix.height = (uint32_t)chosen->h;
|
||||
fmt.fmt.pix.field = V4L2_FIELD_ANY;
|
||||
if (v4l2_xioctl(h->fd, VIDIOC_S_FMT, &fmt) < 0) {
|
||||
close(h->fd); free(h);
|
||||
return APP_SYSCALL_ERROR();
|
||||
}
|
||||
|
||||
h->width = (int)fmt.fmt.pix.width;
|
||||
h->height = (int)fmt.fmt.pix.height;
|
||||
h->pixfmt = fmt.fmt.pix.pixelformat;
|
||||
|
||||
/* Apply frame rate */
|
||||
{
|
||||
struct v4l2_streamparm parm = {0};
|
||||
parm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
parm.parm.capture.timeperframe.numerator = (uint32_t)chosen->fps_d;
|
||||
parm.parm.capture.timeperframe.denominator = (uint32_t)chosen->fps_n;
|
||||
v4l2_xioctl(h->fd, VIDIOC_S_PARM, &parm);
|
||||
if (v4l2_xioctl(h->fd, VIDIOC_G_PARM, &parm) == 0 &&
|
||||
parm.parm.capture.timeperframe.denominator > 0) {
|
||||
h->fps_n = (int)parm.parm.capture.timeperframe.denominator;
|
||||
h->fps_d = (int)parm.parm.capture.timeperframe.numerator;
|
||||
} else {
|
||||
h->fps_n = chosen->fps_n;
|
||||
h->fps_d = chosen->fps_d;
|
||||
}
|
||||
}
|
||||
|
||||
/* Allocate MMAP buffers */
|
||||
struct v4l2_requestbuffers req = {0};
|
||||
req.count = INGEST_N_BUFS;
|
||||
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
req.memory = V4L2_MEMORY_MMAP;
|
||||
if (v4l2_xioctl(h->fd, VIDIOC_REQBUFS, &req) < 0) {
|
||||
close(h->fd); free(h);
|
||||
return APP_SYSCALL_ERROR();
|
||||
}
|
||||
|
||||
h->buf_count = (int)req.count;
|
||||
for (int i = 0; i < h->buf_count; i++) {
|
||||
struct v4l2_buffer buf = {0};
|
||||
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
buf.memory = V4L2_MEMORY_MMAP;
|
||||
buf.index = (uint32_t)i;
|
||||
if (v4l2_xioctl(h->fd, VIDIOC_QUERYBUF, &buf) < 0) {
|
||||
/* Unmap already-mapped buffers before returning */
|
||||
for (int j = 0; j < i; j++) {
|
||||
munmap(h->bufs[j].start, h->bufs[j].length);
|
||||
}
|
||||
close(h->fd); free(h);
|
||||
return APP_SYSCALL_ERROR();
|
||||
}
|
||||
h->bufs[i].length = buf.length;
|
||||
h->bufs[i].start = mmap(NULL, buf.length,
|
||||
PROT_READ | PROT_WRITE, MAP_SHARED, h->fd, buf.m.offset);
|
||||
if (h->bufs[i].start == MAP_FAILED) {
|
||||
for (int j = 0; j < i; j++) {
|
||||
munmap(h->bufs[j].start, h->bufs[j].length);
|
||||
}
|
||||
close(h->fd); free(h);
|
||||
return APP_SYSCALL_ERROR();
|
||||
}
|
||||
}
|
||||
|
||||
*out = h;
|
||||
return APP_OK;
|
||||
}
|
||||
|
||||
struct App_Error ingest_start(Ingest_Handle *h)
|
||||
{
|
||||
/* Queue all buffers */
|
||||
for (int i = 0; i < h->buf_count; i++) {
|
||||
struct v4l2_buffer buf = {0};
|
||||
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
buf.memory = V4L2_MEMORY_MMAP;
|
||||
buf.index = (uint32_t)i;
|
||||
if (v4l2_xioctl(h->fd, VIDIOC_QBUF, &buf) < 0) {
|
||||
return APP_SYSCALL_ERROR();
|
||||
}
|
||||
}
|
||||
|
||||
/* Enable streaming */
|
||||
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
if (v4l2_xioctl(h->fd, VIDIOC_STREAMON, &type) < 0) {
|
||||
return APP_SYSCALL_ERROR();
|
||||
}
|
||||
|
||||
/* Start capture thread */
|
||||
atomic_store(&h->running, 1);
|
||||
if (pthread_create(&h->thread, NULL, capture_thread, h) != 0) {
|
||||
atomic_store(&h->running, 0);
|
||||
v4l2_xioctl(h->fd, VIDIOC_STREAMOFF, &type);
|
||||
return APP_SYSCALL_ERROR();
|
||||
}
|
||||
h->started = 1;
|
||||
return APP_OK;
|
||||
}
|
||||
|
||||
struct App_Error ingest_stop(Ingest_Handle *h)
|
||||
{
|
||||
if (!h->started) {
|
||||
return APP_OK;
|
||||
}
|
||||
|
||||
atomic_store(&h->running, 0);
|
||||
pthread_join(h->thread, NULL);
|
||||
h->started = 0;
|
||||
|
||||
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
v4l2_xioctl(h->fd, VIDIOC_STREAMOFF, &type);
|
||||
|
||||
return APP_OK;
|
||||
}
|
||||
|
||||
void ingest_close(Ingest_Handle *h)
|
||||
{
|
||||
if (!h) { return; }
|
||||
for (int i = 0; i < h->buf_count; i++) {
|
||||
if (h->bufs[i].start && h->bufs[i].start != MAP_FAILED) {
|
||||
munmap(h->bufs[i].start, h->bufs[i].length);
|
||||
}
|
||||
}
|
||||
if (h->fd >= 0) { close(h->fd); }
|
||||
free(h);
|
||||
}
|
||||
|
||||
int ingest_width(const Ingest_Handle *h) { return h->width; }
|
||||
int ingest_height(const Ingest_Handle *h) { return h->height; }
|
||||
uint32_t ingest_pixfmt(const Ingest_Handle *h) { return h->pixfmt; }
|
||||
int ingest_fps_n(const Ingest_Handle *h) { return h->fps_n; }
|
||||
int ingest_fps_d(const Ingest_Handle *h) { return h->fps_d; }
|
||||
19
src/modules/reconciler/Makefile
Normal file
19
src/modules/reconciler/Makefile
Normal file
@@ -0,0 +1,19 @@
|
||||
ROOT := $(abspath ../../..)
|
||||
include $(ROOT)/common.mk
|
||||
|
||||
MODULE_BUILD = $(BUILD)/reconciler
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: $(MODULE_BUILD)/reconciler.o
|
||||
|
||||
$(MODULE_BUILD)/reconciler.o: reconciler.c | $(MODULE_BUILD)
|
||||
$(CC) $(CFLAGS) $(DEPFLAGS) -c -o $@ $<
|
||||
|
||||
$(MODULE_BUILD):
|
||||
mkdir -p $@
|
||||
|
||||
clean:
|
||||
rm -f $(MODULE_BUILD)/reconciler.o $(MODULE_BUILD)/reconciler.d
|
||||
|
||||
-include $(MODULE_BUILD)/reconciler.d
|
||||
274
src/modules/reconciler/reconciler.c
Normal file
274
src/modules/reconciler/reconciler.c
Normal file
@@ -0,0 +1,274 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "reconciler.h"
|
||||
|
||||
#define REC_MAX_RESOURCES 32
|
||||
#define REC_MAX_STATES 16
|
||||
#define REC_MAX_DEPS 8
|
||||
|
||||
struct Rec_Dep {
|
||||
struct Rec_Resource *dep;
|
||||
int dep_min_state;
|
||||
int blocked_below;
|
||||
};
|
||||
|
||||
struct Rec_Resource {
|
||||
char name[32];
|
||||
const struct Rec_Transition *transitions;
|
||||
int state_count;
|
||||
const char **state_names;
|
||||
int current_state;
|
||||
int wanted_state;
|
||||
void *userdata;
|
||||
struct Rec_Dep deps[REC_MAX_DEPS];
|
||||
int dep_count;
|
||||
};
|
||||
|
||||
struct Reconciler {
|
||||
struct Rec_Resource resources[REC_MAX_RESOURCES];
|
||||
int count;
|
||||
Rec_Log_Fn log_fn;
|
||||
void *log_userdata;
|
||||
};
|
||||
|
||||
struct Reconciler *reconciler_create(void) {
|
||||
struct Reconciler *r = calloc(1, sizeof(struct Reconciler));
|
||||
return r;
|
||||
}
|
||||
|
||||
void reconciler_destroy(struct Reconciler *r) {
|
||||
free(r);
|
||||
}
|
||||
|
||||
void reconciler_set_log(struct Reconciler *r, Rec_Log_Fn fn, void *userdata) {
|
||||
r->log_fn = fn;
|
||||
r->log_userdata = userdata;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (r->count >= REC_MAX_RESOURCES) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct Rec_Resource *res = &r->resources[r->count++];
|
||||
memset(res, 0, sizeof(*res));
|
||||
strncpy(res->name, name, sizeof(res->name) - 1);
|
||||
res->transitions = transitions;
|
||||
res->state_count = state_count;
|
||||
res->state_names = state_names;
|
||||
res->current_state = initial_state;
|
||||
res->wanted_state = initial_state;
|
||||
res->userdata = userdata;
|
||||
res->dep_count = 0;
|
||||
return res;
|
||||
}
|
||||
|
||||
void reconciler_add_dep(
|
||||
struct Rec_Resource *resource,
|
||||
int blocked_below,
|
||||
struct Rec_Resource *dep,
|
||||
int dep_min_state)
|
||||
{
|
||||
if (resource->dep_count >= REC_MAX_DEPS) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct Rec_Dep *d = &resource->deps[resource->dep_count++];
|
||||
d->dep = dep;
|
||||
d->dep_min_state = dep_min_state;
|
||||
d->blocked_below = blocked_below;
|
||||
}
|
||||
|
||||
void reconciler_set_wanted(struct Rec_Resource *r, int wanted_state) {
|
||||
r->wanted_state = wanted_state;
|
||||
}
|
||||
|
||||
int reconciler_get_current(const struct Rec_Resource *r) {
|
||||
return r->current_state;
|
||||
}
|
||||
|
||||
int reconciler_get_wanted(const struct Rec_Resource *r) {
|
||||
return r->wanted_state;
|
||||
}
|
||||
|
||||
const char *reconciler_get_name(const struct Rec_Resource *r) {
|
||||
return r->name;
|
||||
}
|
||||
|
||||
const char *reconciler_state_name(const struct Rec_Resource *r, int state) {
|
||||
if (r->state_names != NULL && state >= 0 && state < r->state_count) {
|
||||
return r->state_names[state];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* BFS over the transition graph to find the shortest path from
|
||||
* current_state to wanted_state. Returns the first transition on
|
||||
* that path, or NULL if no path exists (or already stable).
|
||||
*/
|
||||
static const struct Rec_Transition *find_next_transition(const struct Rec_Resource *res) {
|
||||
if (res->current_state == res->wanted_state) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* prev[s] = index of transition in res->transitions that leads into state s,
|
||||
* or -1 if not yet visited. */
|
||||
int prev_trans[REC_MAX_STATES];
|
||||
int visited[REC_MAX_STATES];
|
||||
for (int i = 0; i < REC_MAX_STATES; i++) {
|
||||
prev_trans[i] = -1;
|
||||
visited[i] = 0;
|
||||
}
|
||||
|
||||
/* BFS queue — state indices. */
|
||||
int queue[REC_MAX_STATES];
|
||||
int head = 0;
|
||||
int tail = 0;
|
||||
|
||||
visited[res->current_state] = 1;
|
||||
queue[tail++] = res->current_state;
|
||||
|
||||
int found = 0;
|
||||
|
||||
while (head < tail && !found) {
|
||||
int cur = queue[head++];
|
||||
|
||||
for (int i = 0; ; i++) {
|
||||
const struct Rec_Transition *t = &res->transitions[i];
|
||||
if (t->from == -1 && t->to == -1 && t->action == NULL) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (t->from != cur) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int next = t->to;
|
||||
if (next < 0 || next >= REC_MAX_STATES) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (visited[next]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
visited[next] = 1;
|
||||
prev_trans[next] = i;
|
||||
queue[tail++] = next;
|
||||
|
||||
if (next == res->wanted_state) {
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Walk back from wanted_state to find the first step. */
|
||||
int state = res->wanted_state;
|
||||
int first_trans_idx = prev_trans[state];
|
||||
|
||||
while (1) {
|
||||
int ti = prev_trans[state];
|
||||
if (ti == -1) {
|
||||
break;
|
||||
}
|
||||
int from_state = res->transitions[ti].from;
|
||||
if (from_state == res->current_state) {
|
||||
first_trans_idx = ti;
|
||||
break;
|
||||
}
|
||||
first_trans_idx = ti;
|
||||
state = from_state;
|
||||
}
|
||||
|
||||
return &res->transitions[first_trans_idx];
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns 1 if all dependencies allow the resource to enter next_state.
|
||||
* Returns 0 if any dependency blocks it.
|
||||
*/
|
||||
static int deps_allow(const struct Rec_Resource *res, int next_state) {
|
||||
for (int i = 0; i < res->dep_count; i++) {
|
||||
const struct Rec_Dep *d = &res->deps[i];
|
||||
if (next_state >= d->blocked_below && d->dep->current_state < d->dep_min_state) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
Rec_Status reconciler_get_status(const struct Rec_Resource *r) {
|
||||
if (r->current_state == r->wanted_state) {
|
||||
return REC_STATUS_STABLE;
|
||||
}
|
||||
|
||||
const struct Rec_Transition *t = find_next_transition(r);
|
||||
if (t == NULL) {
|
||||
return REC_STATUS_NO_PATH;
|
||||
}
|
||||
|
||||
if (!deps_allow(r, t->to)) {
|
||||
return REC_STATUS_BLOCKED;
|
||||
}
|
||||
|
||||
return REC_STATUS_WORKING;
|
||||
}
|
||||
|
||||
int reconciler_tick(struct Reconciler *r) {
|
||||
int attempted = 0;
|
||||
|
||||
for (int i = 0; i < r->count; i++) {
|
||||
struct Rec_Resource *res = &r->resources[i];
|
||||
|
||||
if (res->current_state == res->wanted_state) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const struct Rec_Transition *t = find_next_transition(res);
|
||||
if (t == NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!deps_allow(res, t->to)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int from = res->current_state;
|
||||
int to = t->to;
|
||||
int success = t->action(res->userdata);
|
||||
attempted++;
|
||||
|
||||
if (success) {
|
||||
res->current_state = to;
|
||||
}
|
||||
|
||||
if (r->log_fn != NULL) {
|
||||
r->log_fn(res, from, to, success, r->log_userdata);
|
||||
}
|
||||
}
|
||||
|
||||
return attempted;
|
||||
}
|
||||
|
||||
int reconciler_is_stable(const struct Reconciler *r) {
|
||||
for (int i = 0; i < r->count; i++) {
|
||||
if (r->resources[i].current_state != r->resources[i].wanted_state) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
Reference in New Issue
Block a user