225 lines
9.0 KiB
C
225 lines
9.0 KiB
C
/*
|
|
* Copyright (c) 2026, Alessandro Mauri <alemauri001@gmail.com>.
|
|
* All rights reserved.
|
|
*
|
|
* This file is a heavily modified, single-header derivative of the
|
|
* protothreads library.
|
|
*
|
|
* =============================================================================
|
|
* Original Protothreads License:
|
|
* =============================================================================
|
|
* Copyright (c) 2004-2005, Swedish Institute of Computer Science.
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* 3. Neither the name of the Institute nor the names of its contributors
|
|
* may be used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*
|
|
* Author: Adam Dunkels <adam@sics.se>
|
|
*/
|
|
|
|
|
|
#ifndef _COROUTINE_H
|
|
#define _COROUTINE_H
|
|
|
|
/* This is an implementation of stackless coroutines that uses the labels as
|
|
* values [1] feature of GNU C, similar to protothreads [2] with the lc-addrlabels.h
|
|
* include, the only difference being better ergonomics and minimal type checking.
|
|
*
|
|
* Since coroutines implemented this way need to return to wait for a condition
|
|
* stack-local variables are NOT preserved between wait()s, as such these functions
|
|
* have to be considered stackless. Any state that has to be preserved between
|
|
* waits have to be declared static or have to be preserved outside the routine.
|
|
*
|
|
* This library's intended use is in microcontrollers and memory-constrained
|
|
* systems where having a full rtos and/or having stackful coroutines is either
|
|
* too heavy or overkill. Stackless coroutines implemented this way are an
|
|
* alternative way of expressing state machines. Since coroutines have to be
|
|
* repeatedly called to be "scheduled", the code maintains a main loop, but state
|
|
* changes can be expressed with waits on conditions, timers, semaphores and other
|
|
* more expressive constructs.
|
|
*
|
|
* A simple example program is:
|
|
*
|
|
* #include "coroutine.h"
|
|
*
|
|
* // Updates the timer when called, returns true when it expires
|
|
* static int timer_expired(struct timer *t);
|
|
* // Sets a timer in milliseconds
|
|
* static void timer_set(struct timer *t, int interval);
|
|
*
|
|
* // First coroutine, waits for 1s and prints something
|
|
* coroutine coro_1(coro_state_t *s)
|
|
* {
|
|
* static struct timer t;
|
|
* coro_begin(s);
|
|
* for (;;) {
|
|
* timer_set(&t, 1000);
|
|
* coro_wait_until(s, timer_expired(&t));
|
|
* printf("coroutine 1\n");
|
|
* }
|
|
* coro_end();
|
|
* }
|
|
*
|
|
* // Second coroutine, waits for 500ms and prints something
|
|
* coroutine coro_2(coro_state_t *s)
|
|
* {
|
|
* static struct timer t;
|
|
* coro_begin(s);
|
|
* for (;;) {
|
|
* timer_set(&t, 500);
|
|
* coro_wait_until(s, timer_expired(&t));
|
|
* printf("coroutine 2\n");
|
|
* }
|
|
* coro_end();
|
|
* }
|
|
*
|
|
* int main(void)
|
|
* {
|
|
* coro_state_t s1, s2;
|
|
* coro_init(&s1);
|
|
* coro_init(&s2);
|
|
*
|
|
* // Main loop, calls each coroutine periodically
|
|
* for (;;) {
|
|
* coro_1(&s1);
|
|
* coro_2(&s2);
|
|
* usleep(100);
|
|
* }
|
|
* }
|
|
*
|
|
* [1]: https://www.gnu.org/software/c-intro-and-ref/manual/html_node/Labels-as-Values.html
|
|
* [2]: https://dunkels.com/adam/pt/index.html
|
|
*/
|
|
|
|
|
|
// Useful macros
|
|
#define CORO_CONCAT2(s1, s2) s1##s2
|
|
#define CORO_CONCAT(s1, s2) CORO_CONCAT2(s1, s2)
|
|
#define CORO_CHECK_TYPE(var,type) { typedef void (*type_t)(type); type_t tmp = (type_t)0; if(0) tmp(var); }
|
|
|
|
|
|
// Return value of a coroutine, a coroutine should be scheduled (called) until
|
|
// it's return value becomes DONE
|
|
typedef enum {
|
|
CORO_INIT = 0,
|
|
CORO_CONT = CORO_INIT,
|
|
CORO_DONE = 1,
|
|
CORO_YIELDED = 2,
|
|
CORO_WAITING = 3,
|
|
} coroutine;
|
|
|
|
// Local continuation of the coroutine, where it left off
|
|
typedef void* coro_state_t;
|
|
|
|
|
|
/* -------------------------- COROUTINE MANAGEMENT -------------------------- */
|
|
|
|
// Initialize the coroutine state
|
|
#define coro_init(s) CORO_CHECK_TYPE(s, coro_state_t*) \
|
|
do { *s = NULL; } while (0)
|
|
|
|
// Jump to the previous state where the coroutine left off, __yield flag indicates
|
|
// wether the coroutine should yield this cycle
|
|
#define coro_begin(s) CORO_CHECK_TYPE(s, coro_state_t*) \
|
|
unsigned char __yield = 0; \
|
|
do { if(*s != NULL) goto *(*s); } while (0)
|
|
|
|
#define coro_end()
|
|
|
|
// Set a jump with a gcc label as value feature, this is a GNU C extension
|
|
#define CORO_SET_JUMP(s) CORO_CHECK_TYPE(s, coro_state_t*) \
|
|
do { \
|
|
CORO_CONCAT(LC_LABEL, __LINE__): \
|
|
*s = &&CORO_CONCAT(LC_LABEL, __LINE__); \
|
|
} while (0)
|
|
|
|
// Yield from a coroutine
|
|
#define coro_yield(s) CORO_CHECK_TYPE(s, coro_state_t*) \
|
|
do { \
|
|
__yield = 1; \
|
|
CORO_SET_JUMP(s); \
|
|
if (__yield == 1) return CORO_YIELDED; \
|
|
} while (0)
|
|
|
|
// Wait until a condition is met (becomes true)
|
|
#define coro_wait_until(s, cond) CORO_CHECK_TYPE(s, coro_state_t*) \
|
|
do { \
|
|
CORO_SET_JUMP(s); \
|
|
if (!(cond)) return CORO_WAITING; \
|
|
} while (0)
|
|
|
|
// Shorthand for wait_until
|
|
#define coro_wait(s, cond) coro_wait_until(s, cond)
|
|
|
|
// Wait while a condition remains true
|
|
#define coro_wait_while(s, cond) coro_wait_until(s, !(cond))
|
|
|
|
// Exit from the coroutine, resets the state
|
|
#define coro_exit(s) CORO_CHECK_TYPE(s, coro_state_t*) \
|
|
do { \
|
|
coro_init(s); \
|
|
return CORO_DONE; \
|
|
} while (0)
|
|
|
|
// Restart a coroutine, when called it will not resume from where it left off,
|
|
// instead it will start from the beginning, the only difference with exit is
|
|
// the return value. To determine wether a coroutine has exited or restarted
|
|
// is to check it's return value
|
|
#define coro_restart(s) CORO_CHECK_TYPE(s, coro_state_t*) \
|
|
do { \
|
|
coro_init(s); \
|
|
return CORO_INIT; \
|
|
} while(0)
|
|
|
|
|
|
/* ------------------------- SEMAPHORE IMPLEMENTATION ----------------------- */
|
|
// Implements a basic semaphore type with the classic wait and signal operations
|
|
// semaphores are not bound checked
|
|
|
|
typedef int coro_semaphore_t;
|
|
|
|
// Initializes a semaphore with a starting value of "count"
|
|
#define coro_sem_init(sem, count) CORO_CHECK_TYPE(sem, coro_semaphore_t*) \
|
|
do { \
|
|
*sem = (int)count; \
|
|
} while (0)
|
|
|
|
// Wait until a semaphore has a value other than zero
|
|
#define coro_sem_wait(state, sem) \
|
|
CORO_CHECK_TYPE(state, coro_state_t*) \
|
|
CORO_CHECK_TYPE(sem, coro_semaphore_t*) \
|
|
do { \
|
|
coro_wait_until(state, *(sem) > 0); \
|
|
--(*sem); \
|
|
} while (0)
|
|
|
|
// Signal a semaphore
|
|
#define coro_sem_signal(state, sem) \
|
|
CORO_CHECK_TYPE(state, coro_state_t*) \
|
|
CORO_CHECK_TYPE(sem, coro_semaphore_t*) \
|
|
++(*sem)
|
|
|
|
|
|
#endif
|