// // Created by epagris on 2022.12.21.. // #include "timer.h" #include "dynmem.h" #include "utils.h" #include int64_t time_to_us(const TimePoint *t) { return (int64_t)t->s * 1000000 + (int64_t)t->us; } void time_from_us(TimePoint *t, int64_t us) { t->s = us / 1000000; t->us = us - ((int64_t)t->s * 1000000); } void time_add_us(TimePoint *t, int64_t us) { time_from_us(t, time_to_us(t) + us); } // ------------------------------ Timer *timer_new(uint32_t maxSched) { Timer *tmr = (Timer *)dynmem_alloc(sizeof(Timer) + maxSched * sizeof(AlarmAssignment)); ASSERT_NULL(tmr); ETHLIB_OS_MTX_CREATE(&tmr->tabMtx); tmr->maxSched = maxSched; tmr->nSched = 0; tmr->nextAlarm = NULL; tmr->time.s = 0; tmr->time.us = 0; memset(tmr->alarms, 0, maxSched * sizeof(AlarmAssignment)); return tmr; } static AlarmAssignment *timer_get_alarm_by_id(Timer *tmr, uint32_t id) { AlarmAssignment *slot = NULL; for (uint32_t i = 0; i < tmr->maxSched; i++) { if (tmr->alarms[i].id == id) { slot = (tmr->alarms) + i; break; } } return slot; } static uint32_t timer_generate_id(Timer *tmr) { uint32_t newId = 0; do { newId = ((uint32_t)rand()) | 0x01; // ID cannot be 0 } while (timer_get_alarm_by_id(tmr, newId) != NULL); return newId; } static void timer_update_nearest_alarm(Timer *tmr) { if (tmr->nSched == 0) { tmr->nextAlarm = NULL; return; } int64_t t_c_us = time_to_us(&tmr->time); // current time in microseconds int64_t min_delta_t_us = 0; // minimal time difference AlarmAssignment *nearest = NULL; // nearest alarm for (uint32_t i = 0; i < tmr->maxSched; i++) { AlarmAssignment * iter = tmr->alarms + i; if (iter->id == 0) { // do not consider empty slots continue; } int64_t t_i_us = time_to_us(&(iter->time)); if (nearest == NULL) { // if it's the first one nearest = iter; min_delta_t_us = t_i_us - t_c_us; } else { // if it's not the first one int64_t delta_t_us = t_i_us - t_c_us; // calculate time difference if (delta_t_us < min_delta_t_us) { min_delta_t_us = delta_t_us; // replace minimum nearest = iter; } } } tmr->nextAlarm = nearest; // replace next alarm } uint32_t timer_sched(Timer *tmr, const TimePoint *t, TimerAlarmCb cb, AlarmUserData params) { if (tmr->nSched == tmr->maxSched) { // if no more alarm can be scheduled return TIMER_SCHED_FAILED; } ETHLIB_OS_MTX_LOCK(&(tmr->tabMtx)); // if we can schedule get the first unused block AlarmAssignment *slot = timer_get_alarm_by_id(tmr, 0); // allocate on the first free slot slot->id = timer_generate_id(tmr); slot->cb = cb; slot->time = *t; slot->params = params; // increase allocation count tmr->nSched++; MSG("%d %s\n", tmr->nSched, __FUNCTION__); // replace nearest if needed if (tmr->nSched > 1) { timer_update_nearest_alarm(tmr); } else { // this is merely for optimization tmr->nextAlarm = slot; } //timer_report(tmr); ETHLIB_OS_MTX_UNLOCK(&(tmr->tabMtx)); return slot->id; } uint32_t timer_sched_rel(Timer *tmr, int64_t us, TimerAlarmCb cb, AlarmUserData params) { TimePoint t = tmr->time; time_add_us(&t, us); return timer_sched(tmr, &t, cb, params); } void timer_unsched(Timer *tmr, uint32_t id) { ETHLIB_OS_MTX_LOCK(&(tmr->tabMtx)); if (tmr->nSched > 0) { AlarmAssignment *alarm = timer_get_alarm_by_id(tmr, id); if (alarm != NULL) { memset(alarm, 0, sizeof(AlarmAssignment)); tmr->nSched--; MSG("%d %s\n", tmr->nSched, __FUNCTION__); timer_update_nearest_alarm(tmr); } } ETHLIB_OS_MTX_UNLOCK(&(tmr->tabMtx)); } void timer_set_time(Timer *tmr, const TimePoint *t) { tmr->time = *t; } int64_t timer_get_time_us(const Timer *tmr) { return time_to_us(&tmr->time); } TimePoint timer_get_time(const Timer *tmr) { return tmr->time; } void timer_tick(Timer *tmr, int64_t us) { // advance time time_add_us(&tmr->time, us); /*t_us += us; time_from_us(&tmr->time, t_us);*/ int64_t t_us = timer_get_time_us(tmr); // convert time to microseconds if ((tmr->nSched > 0) && (tmr->nextAlarm != NULL)) { int64_t t_alarm = time_to_us(&(tmr->nextAlarm->time)); while (((t_alarm - t_us) <= 0) && (tmr->nSched > 0)) { if (ETHLIB_OS_MTX_LOCK(&(tmr->tabMtx)) != 0) { return; // break from the loop, since we cannot lock the mutex } //timer_report(tmr); // fetch alarm (obtain a COPY) AlarmAssignment alarm = *(tmr->nextAlarm); // clear alarm descriptor memset(tmr->nextAlarm, 0, sizeof(AlarmAssignment)); // decrease scheduled alarm count tmr->nSched--; MSG("%d %s\n", tmr->nSched, __FUNCTION__); // update nearest alarm timer_update_nearest_alarm(tmr); // invoke callback if (alarm.cb != NULL) { alarm.cb(tmr, alarm.params); } if (tmr->nextAlarm != NULL) { t_alarm = time_to_us(&(tmr->nextAlarm->time)); } ETHLIB_OS_MTX_UNLOCK(&(tmr->tabMtx)); } } } void timer_report(const Timer *tmr) { MSG("Time: %u.%06u\n\n", tmr->time.s, tmr->time.us); int64_t t_c_us = timer_get_time_us(tmr); if (tmr->nSched > 0) { for (uint32_t i = 0; i < tmr->maxSched; i++) { if (tmr->alarms[i].id == 0) { continue; } int64_t t_delta_us = time_to_us(&tmr->alarms[i].time) - t_c_us; if (t_delta_us < 1000000) { MSG("─ alarm (#%X) in %li us", tmr->alarms[i].id, t_delta_us); } else { TimePoint dt; time_from_us(&dt, t_delta_us); MSG("─ alarm (#%X) in %i.%06i s", tmr->alarms[i].id, dt.s, dt.us); } if ((tmr->alarms + i) == tmr->nextAlarm) { MSG(" <-- NEXT ALARM\n"); } else { MSG("\n"); } } } MSG("\nSchedules: %u/%u\n", tmr->nSched, tmr->maxSched); }