Skip to content

File syn_timer.c

File List > sched > syn_timer.c

Go to the documentation of this file

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

#if !defined(SYN_USE_TIMER) || SYN_USE_TIMER

#include "../port/syn_port_system.h"
#include "../util/syn_assert.h"
#include "syn_timer.h"

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

void syn_timer_init(SYN_Timer *timer, uint32_t period_ms, bool periodic, SYN_TimerCallback callback,
                    void *user_data)
{
    SYN_ASSERT(timer != NULL);
    SYN_ASSERT(period_ms > 0);

    timer->period_ms = period_ms;
    timer->target_tick = 0;
    timer->callback = callback;
    timer->user_data = user_data;
    timer->periodic = periodic;
    timer->active = false;
}

void syn_timer_start(SYN_Timer *timer)
{
    SYN_ASSERT(timer != NULL);

    timer->target_tick = syn_port_get_tick_ms() + timer->period_ms;
    timer->active = true;
}

void syn_timer_stop(SYN_Timer *timer)
{
    SYN_ASSERT(timer != NULL);
    timer->active = false;
}

void syn_timer_set_period(SYN_Timer *timer, uint32_t period_ms)
{
    SYN_ASSERT(timer != NULL);
    SYN_ASSERT(period_ms > 0);
    timer->period_ms = period_ms;
}

bool syn_timer_expired(SYN_Timer *timer)
{
    SYN_ASSERT(timer != NULL);

    if (!timer->active) {
        return false;
    }

    uint32_t now = syn_port_get_tick_ms();

    /* Handle tick wrap-around: use unsigned subtraction */
    if ((int32_t)(now - timer->target_tick) >= 0) {
        if (timer->periodic) {
            /* Re-arm relative to the previous target to avoid drift */
            timer->target_tick += timer->period_ms;

            /*
             * If we've fallen behind by more than one period (e.g.,
             * the callback took too long), snap forward to avoid a
             * burst of rapid-fire expirations.
             */
            if ((int32_t)(now - timer->target_tick) >= 0) {
                timer->target_tick = now + timer->period_ms;
            }
        } else {
            timer->active = false;
        }
        return true;
    }

    return false;
}

void syn_timer_service(SYN_Timer *timers, size_t count)
{
    SYN_ASSERT(timers != NULL || count == 0);

    for (size_t i = 0; i < count; i++) {
        SYN_Timer *t = &timers[i];

        if (!t->active) {
            continue;
        }

        if (syn_timer_expired(t)) {
            if (t->callback != NULL) {
                t->callback(t, t->user_data);
            }
        }
    }
}

uint32_t syn_timer_remaining(const SYN_Timer *timer)
{
    SYN_ASSERT(timer != NULL);

    if (!timer->active) {
        return 0;
    }

    uint32_t now = syn_port_get_tick_ms();
    int32_t diff = (int32_t)(timer->target_tick - now);

    return (diff > 0) ? (uint32_t)diff : 0;
}

uint32_t syn_timer_next_expiry(const SYN_Timer *timers, size_t count)
{
    SYN_ASSERT(timers != NULL || count == 0);

    uint32_t now = syn_port_get_tick_ms();
    uint32_t earliest = UINT32_MAX;
    bool any_ready_now = false;

    for (size_t i = 0; i < count; i++) {
        if (!timers[i].active)
            continue;

        /* Check if this timer has already expired */
        if ((int32_t)(now - timers[i].target_tick) >= 0) {
            any_ready_now = true;
            continue;
        }

        /* Timer is in the future — track the earliest */
        if ((int32_t)(timers[i].target_tick - earliest) < 0 || earliest == UINT32_MAX) {
            earliest = timers[i].target_tick;
        }
    }

    /* If any timer is ready right now, return 'now' */
    if (any_ready_now) {
        return now;
    }

    return earliest;
}

#endif /* SYN_USE_TIMER */