/* * machinarium. * * cooperative multitasking engine. */ #include #include static int mm_clock_cmp(mm_timer_t *a, mm_timer_t *b) { if (a->timeout == b->timeout) { if (a->seq == b->seq) { assert(a == b); return 0; } return (a->seq > b->seq) ? 1 : -1; } else if (a->timeout > b->timeout) { return 1; } return -1; } void mm_clock_init(mm_clock_t *clock) { mm_buf_init(&clock->timers); clock->timers_count = 0; clock->timers_seq = 0; clock->active = 0; clock->time_ms = 0; clock->time_ns = 0; clock->time_us = 0; clock->time_cached = 0; } void mm_clock_free(mm_clock_t *clock) { mm_buf_free(&clock->timers); } // Binary search routine for timer in sorted array. // We expect that there is at most only one occurrence of timer in list. // If there is not timer in list, we must return first index greater than timer. static int mm_clock_get_insert_position(mm_timer_t **list, int count, mm_timer_t *timer) { int low = 0, high = count; while (low < high) { int med = low + (high - low) / 2; int cmp = mm_clock_cmp(timer, list[med]); if (cmp == 0) return med; if (cmp > 0) { low = med + 1; } else { high = med; } } return low; } static int mm_clock_list_is_sorted(mm_timer_t **list, int count) { for (int i = 1; i < count; i++) { if (mm_clock_cmp(list[i - 1], list[i]) >= 0) { return 0; } } return 1; } int mm_clock_timer_add(mm_clock_t *clock, mm_timer_t *timer) { int count = clock->timers_count; int rc; rc = mm_buf_ensure(&clock->timers, sizeof(mm_timer_t *)); if (rc == -1) return -1; mm_timer_t **list; list = (mm_timer_t **)clock->timers.start; mm_buf_advance(&clock->timers, sizeof(mm_timer_t *)); timer->seq = clock->timers_seq++; timer->timeout = clock->time_ms + timer->interval; timer->active = 1; timer->clock = clock; int insert_position = mm_clock_get_insert_position(list, count, timer); // We are last, or insert position is after us assert((insert_position == count) || mm_clock_cmp(list[insert_position], timer) > 0); memmove(list + insert_position + 1, list + insert_position, sizeof(mm_timer_t *) * (count - insert_position)); list[insert_position] = timer; clock->timers_count = count + 1; assert(mm_clock_list_is_sorted(list, clock->timers_count)); return 0; } int mm_clock_timer_del(mm_clock_t *clock, mm_timer_t *timer) { if (!timer->active) return -1; assert(clock->timers_count >= 1); mm_timer_t **list; list = (mm_timer_t **)clock->timers.start; int i = mm_clock_get_insert_position(list, clock->timers_count, timer); // We should find timer assert(list[i] == timer); for (; i < clock->timers_count; i++) { if (list[i] != timer) continue; memmove(list + i, list + i + 1, sizeof(mm_timer_t *) * (clock->timers_count - i - 1)); clock->timers.pos -= sizeof(mm_timer_t *); clock->timers_count -= 1; break; } assert(mm_clock_list_is_sorted(list, clock->timers_count)); timer->active = 0; return 0; } mm_timer_t * mm_clock_timer_min(mm_clock_t *clock) { if (clock->timers_count == 0) return NULL; mm_timer_t **list; list = (mm_timer_t **)clock->timers.start; return list[0]; } int mm_clock_step(mm_clock_t *clock) { if (clock->timers_count == 0) return 0; mm_timer_t **list; list = (mm_timer_t **)clock->timers.start; int timers_hit = 0; int i = 0; for (; i < clock->timers_count; i++) { mm_timer_t *timer = list[i]; if (timer->timeout > clock->time_ms) break; timer->callback(timer); timer->active = 0; timers_hit++; } if (!timers_hit) return 0; int timers_left = clock->timers_count - timers_hit; if (timers_left == 0) { mm_buf_reset(&clock->timers); clock->timers_count = 0; return timers_hit; } memmove(list, list + timers_hit, sizeof(mm_timer_t *) * timers_left); clock->timers.pos -= sizeof(mm_timer_t *) * timers_hit; clock->timers_count -= timers_hit; return timers_hit; } static uint64_t mm_clock_gettime(void) { struct timespec t; clock_gettime(CLOCK_MONOTONIC, &t); return t.tv_sec * (uint64_t)1e9 + t.tv_nsec; } void mm_clock_update(mm_clock_t *clock) { if (clock->time_cached) return; clock->time_ns = mm_clock_gettime(); clock->time_ms = clock->time_ns / 1000000; clock->time_us = clock->time_ns / 1000; clock->time_cached = 1; }