Skip to content

File syn_fsm.c

File List > src > syntropic > util > syn_fsm.c

Go to the documentation of this file

#if __has_include("syn_config.h")
#include "syn_config.h"
#endif

#if !defined(SYN_USE_FSM) || SYN_USE_FSM

#include "../log/syn_log.h"
#include "syn_assert.h"
#include "syn_fsm.h"

#include <string.h>

/* ── Internal helpers ───────────────────────────────────────────────────── */

static const SYN_FSM_StateDesc *find_state_desc(const SYN_FSM *fsm, SYN_FSM_State state)
{
    if (fsm->state_descs == NULL)
        return NULL;

    const SYN_FSM_StateDesc *d = fsm->state_descs;
    while (d->state != SYN_FSM_STATE_NONE) {
        if (d->state == state)
            return d;
        d++;
    }
    return NULL;
}

static void fire_exit(const SYN_FSM *fsm, SYN_FSM_State state)
{
    const SYN_FSM_StateDesc *d = find_state_desc(fsm, state);
    if (d != NULL && d->on_exit != NULL) {
        d->on_exit(fsm->ctx);
    }
}

static void fire_enter(const SYN_FSM *fsm, SYN_FSM_State state)
{
    const SYN_FSM_StateDesc *d = find_state_desc(fsm, state);
    if (d != NULL && d->on_enter != NULL) {
        d->on_enter(fsm->ctx);
    }
}

/* ── API ────────────────────────────────────────────────────────────────── */

void syn_fsm_init(SYN_FSM *fsm, const SYN_FSM_Transition *transitions, SYN_FSM_State initial,
                  const char *tag)
{
    SYN_ASSERT(fsm != NULL);
    SYN_ASSERT(transitions != NULL);

    if (fsm == NULL || transitions == NULL) {
        return;
    }

    memset(fsm, 0, sizeof(*fsm));
    fsm->transitions = transitions;
    fsm->current = initial;
    fsm->tag = tag;
}

void syn_fsm_set_state_descs(SYN_FSM *fsm, const SYN_FSM_StateDesc *descs)
{
    SYN_ASSERT(fsm != NULL);

    if (fsm == NULL) {
        return;
    }
    fsm->state_descs = descs;
}

void syn_fsm_set_state_names(SYN_FSM *fsm, const char *const *names)
{
    SYN_ASSERT(fsm != NULL);

    if (fsm == NULL) {
        return;
    }
    fsm->state_names = names;
}

void syn_fsm_set_context(SYN_FSM *fsm, void *ctx)
{
    SYN_ASSERT(fsm != NULL);

    if (fsm == NULL) {
        return;
    }
    fsm->ctx = ctx;
}

bool syn_fsm_dispatch(SYN_FSM *fsm, SYN_FSM_Event event)
{
    SYN_ASSERT(fsm != NULL);

    if (fsm == NULL || fsm->transitions == NULL) {
        return false;
    }

    const SYN_FSM_Transition *t = fsm->transitions;

    while (t->from != SYN_FSM_STATE_NONE) {
        if (t->from == fsm->current && t->event == event) {
            /* Check guard */
            if (t->guard != NULL && !t->guard(fsm->ctx)) {
                t++;
                continue; /* try next matching row */
            }

            SYN_FSM_State old_state = fsm->current;

            /* Exit old state */
            fire_exit(fsm, old_state);

            /* Transition action */
            if (t->action != NULL) {
                t->action(fsm->ctx);
            }

            /* Change state */
            fsm->current = t->to;

            /* Log transition */
#if !defined(SYN_USE_LOG) || SYN_USE_LOG
            if (fsm->tag != NULL) {
                if (fsm->state_names != NULL) {
                    SYN_LOG_D(fsm->tag, "%s -> %s (evt %d)", fsm->state_names[old_state],
                              fsm->state_names[t->to], (int)event);
                } else {
                    SYN_LOG_D(fsm->tag, "S%d -> S%d (evt %d)", (int)old_state, (int)t->to,
                              (int)event);
                }
            }
#endif
            /* Enter new state */
            fire_enter(fsm, t->to);

            return true;
        }
        t++;
    }

    return false; /* no matching transition */
}

void syn_fsm_set_state(SYN_FSM *fsm, SYN_FSM_State state)
{
    SYN_ASSERT(fsm != NULL);

    if (fsm->current != state) {
        fire_exit(fsm, fsm->current);
        fsm->current = state;
        fire_enter(fsm, state);
    }
}

#endif /* SYN_USE_FSM */