Repository: github.com/IlVin/QMan
Version: 1.0.7-beta
Author: Ilia Vinokurov
License: MIT

=== WHAT IS QMAN ===
QMan is an ultra-lightweight cooperative task queue manager for Arduino. It is not an OS, not protothreads, not a classic round-robin scheduler. It is a tick-based priority queue that executes tasks in discrete time slots with automatic dynamic reprioritization of late tasks.

=== CORE ARCHITECTURAL PRINCIPLE ===
QMan does NOT use hardware timer interrupts for scheduling. The system tick is advanced ONLY by calling QMAN_TICK() manually in loop() or manually via QMAN_TICK(N_tick) for testing. The granularity of a tick defaults to 32 microseconds because:
1. An empty loop() on typical AVR takes approximately 32 microseconds, so finer granularity is physically meaningless when tick is advanced from loop().
2. 32 microseconds is convenient for bit-shift arithmetic on AVR, STM32, and ESP platforms.
3. The 'now' counter bit width was chosen so that a SLEEP of up to one hour is safe from overflow artifacts.
4. Granularity reduces the required bit width of internal time variables compared to using raw microseconds.

=== HOW THE TICK SYSTEM WORKS ===
QMan time is measured in internal "ticks". The conversion chain is:
  Raw hardware ticks (e.g. 4us on 16MHz AVR from Timer0, or DWT cycle counter on STM32)
       |
       v
  System ticks = group of raw ticks, sized to be <= QMAN_TICK_MAX_US (default 32us)
       |
       v
  User time: 500_ms, 100_us, etc. converted to system ticks at compile time via constexpr

Key formulas for AVR (16MHz):
  QMAN_RAW_US = 64000000UL / F_CPU = 4us per raw tick
  QMAN_TICK_SHIFT = selects how many raw ticks per system tick to stay under QMAN_TICK_MAX_US
  For default 32us max: shift = 3, so 1 system tick = 8 raw ticks = 32us
  Q_US(us) = us / 32
  Q_MS(ms) = Q_US(ms * 1000)

The user NEVER works with raw ticks. All time is expressed through type-safe QTime objects created by literals:
  _ms for milliseconds
  _us for microseconds
  _tick for raw system ticks (rarely used)

=== TIME PROGRESSION ===
qman_get_delta_ticks() reads elapsed raw hardware ticks since last call, then divides by tick size to get delta in system ticks. On AVR this reads Timer0 overflow count and TCNT0 atomically using QManGuard (RAII interrupt lock). On STM32 it reads DWT->CYCCNT. The delta is added to qman.now via Sync().

=== TWO FORMS OF QMAN_TICK ===
QMAN_TICK()      — auto: reads hardware delta via qman_get_delta_ticks(), adds to 'now', runs Tick()
QMAN_TICK(N_tick) — manual: adds exactly N ticks to 'now' via Sync(), runs Tick()

The manual form is the testing entry point. It bypasses hardware entirely, giving the test harness complete control over time progression.

=== THE "SLOT" MODEL OF SCHEDULING ===
QMan thinks of time as discrete SLOTS, not a continuous timeline. Each slot equals one system tick. Tasks are scheduled INTO specific future slots. When QMAN_TICK() is called, it computes which slot 'now' has reached. ALL tasks whose nextRun slot is <= now are eligible to run. They execute sequentially, one per Tick() call.

=== HOW SLEEP AND DUTY FILL SLOTS ===
QMAN_SLEEP(time):
  nextRun = now + time_to_ticks(time)
  Task is placed in the future slot exactly 'time' ticks from now.
  If the task was late in starting, it loses that time — the sleep is from NOW, not from when it SHOULD have run.

QMAN_DUTY(period):
  elapsed = now - taskStartTime
  wait = (elapsed < period_ticks) ? (period_ticks - elapsed) : 0
  Task is placed in slot exactly 'period_ticks' from its LAST START, not from now.
  This compensates for the time the task spent executing, preserving a fixed period.
  If elapsed >= period_ticks (task overran), wait = 0 and task goes to the immediate slot.

=== INTERNAL QUEUE STRUCTURE ===
The task queue is a flat array pool[POOL_SIZE] of Task structs, sorted by nextRun DESCENDING. The most urgent task (smallest nextRun) is at pool[count-1]. This is intentional: Tick() checks only the LAST element, making the "who is next" check O(1).

Each Task contains:
  TaskFunc func;      // pointer to task function
  uint32_t nextRun;   // slot number when this task should run

=== QUEUE INSERTION ALGORITHM (DYNAMIC PRIORITIZATION) ===
When a task is added or rescheduled (via go(), sleep(), duty()), the insertion algorithm finds its position in the sorted array:

For SLEEP (via sleep()):
  Tasks that are LATE (their new nextRun is <= now) have a nextRun SMALLER than tasks waiting in future slots. They get inserted further RIGHT, closer to the urgent end. This is DYNAMIC REPRIORITIZATION: late tasks automatically get higher priority in subsequent scheduling cycles.

For DUTY (via duty()):
  Similar logic but with an additional nuance: if nextRun equals another task's nextRun AND the task is in the future, it gets placed to the RIGHT of all tasks in the same slot. This prevents duty-bound tasks from starving each other when their slots align.

=== HOW TICK() EXECUTES TASKS ===
QMAN_TICK() does:
  1. Sync time: add delta hardware ticks to 'now' (or use manual delta)
  2. Check the URGENT end: if pool[count-1].nextRun <= now, pop that task
  3. Set currentTask and lastTaskStart = now
  4. Execute currentTask() ONCE
  5. Task function is void. It automatically disappears when function ends.
  6. To reschedule, call QMAN_SLEEP or QMAN_DUTY (they do NOT exit permanently)

Only ONE task runs per Tick() call. This ensures cooperative behavior: no task can monopolize the CPU.

=== COROUTINE MECHANISM: COMPUTED GOTO ===
QMan uses GCC's "labels as values" extension (computed goto). This is the same technique used by AceRoutine and Protothreads.

=== CRITICAL: HOW QMAN_INIT AND QMAN_LOOP WORK ===

When QMAN_TASK is defined, the macro expands to a function containing a static void* __resumeAddr variable. This variable stores the address of the code line to jump to when the task resumes after a sleep.

QMAN_INIT macro expands to:
  L_INIT:
  static void* __resumeAddr = nullptr;
  if (__resumeAddr > &&L_INIT) {
      goto *__resumeAddr;
  }
  if (!__resumeAddr && (__resumeAddr = &&L_INIT))

QMAN_LOOP macro expands to:
  L_LOOP:
  if (__resumeAddr > &&L_LOOP) {
      goto *__resumeAddr;
  }
  for (;;)

QMAN_SLEEP and QMAN_DUTY macros do:
  1. Save current label address (where to resume after sleep) to __resumeAddr
  2. Call qman.Sleep() or qman.Duty() to reschedule
  3. return (yield control)
  4. After the macro, there is a label (the resume point)
  5. IMPORTANT: After returning from sleep, the macro sets __resumeAddr = &&L_INIT

=== TASK LIFECYCLE ===

First call (task never ran before):
  - __resumeAddr == nullptr
  - First if condition (__resumeAddr > &&L_INIT) is false (nullptr not greater than address)
  - Second if condition triggers: !__resumeAddr is true, so __resumeAddr = &&L_INIT
  - Then execution continues into QMAN_INIT block (your initialization code)
  - Then into QMAN_LOOP, executes loop body until SLEEP/DUTY or loop ends

After SLEEP or DUTY (task resumes):
  - SLEEP macro sets __resumeAddr to a label after the macro (e.g., &&L_123)
  - The task returns, then later the scheduler calls it again
  - Upon re-entry: __resumeAddr points to &&L_123 (not nullptr)
  - First if condition: &&L_123 > &&L_INIT is true (label addresses increase downward in function)
  - goto *__resumeAddr jumps directly to the label after SLEEP/DUTY
  - The code after SLEEP/DUTY executes
  - At the end of SLEEP/DUTY macro, __resumeAddr = &&L_INIT is set

When task runs to completion (natural end of function, no SLEEP/DUTY):
  - __resumeAddr still contains &&L_INIT (from the last SLEEP/DUTY or from INIT)
  - Next time QMAN_GO(task) is called, the task is added to the pool
  - Upon execution: __resumeAddr == &&L_INIT (not nullptr)
  - First if condition: &&L_INIT > &&L_INIT is false (equal, not greater)
  - Second if condition: !__resumeAddr is false (__resumeAddr is not nullptr)
  - QMAN_INIT block is SKIPPED!
  - Execution continues directly into QMAN_LOOP
  - This means INITIALIZATION CODE IS NOT EXECUTED on task restart!

=== QMAN_END MACRO (USER MUST ADD) ===

To properly restart a task that has completed, use QMAN_END() instead of return:

#define QMAN_END() \
    do { \
        __resumeAddr = nullptr; \
        return; \
    } while(0)

This:
  1. Sets __resumeAddr = nullptr
  2. Returns from task

When QMAN_GO(task) is called after QMAN_END():
  - __resumeAddr == nullptr
  - First if condition false
  - Second if condition true: __resumeAddr = &&L_INIT
  - QMAN_INIT block EXECUTES (task starts fresh)

=== RULE FOR TASK RESTART ===

When a task is meant to restart from initialization on next activation, always use:
  QMAN_END();

When a task naturally completes without needing restart (e.g., one-shot task), return; is fine.

=== CRITICAL USAGE RULES ===

ALL VARIABLES INSIDE QMAN_TASK MUST BE STATIC IF USING QMAN_SLEEP/DUTY
QMan does not allocate a stack for tasks. When a task yields (SLEEP/DUTY) and later resumes, the stack frame is gone. Any non-static local variable will contain garbage after resume.

  CORRECT:
    QMAN_TASK(myTask) {
        QMAN_INIT {
            static int counter = 0;
        }
        QMAN_LOOP {
            counter++;
            QMAN_SLEEP(100_ms);
        }
    }

  WRONG (will break on resume):
    QMAN_TASK(myTask) {
        QMAN_INIT {}
        QMAN_LOOP {
            int counter = 0;  // LOST after SLEEP!
            counter++;
            QMAN_SLEEP(100_ms);
        }
    }

If you don't use QMAN_SLEEP or QMAN_DUTY, you don't need static variables. The task runs once and disappears.

=== TASK TERMINATION AND RESTART RULES ===

1. Task that calls QMAN_SLEEP or QMAN_DUTY must have QMAN_INIT and QMAN_LOOP
2. Task that runs once (one-shot) does not need QMAN_INIT/QMAN_LOOP
3. To restart a task from INIT, always use QMAN_END() at termination point
4. Simple return; will keep __resumeAddr pointing to &&L_INIT, skipping INIT on next run
5. QMAN_END() sets __resumeAddr = nullptr, ensuring INIT runs on next activation

SLEEP/DUTY MUST NOT EXCEED ONE HOUR
The internal 'now' counter has finite bit width. A sleep duration of over one hour can cause overflow artifacts. If you need longer delays, break them into multiple shorter SLEEP calls.

  SAFE: QMAN_SLEEP(3600000_ms);   // exactly 1 hour
  WORKAROUND for longer sleeps: chain multiple SLEEP(3600000_ms) calls

=== TESTING QMAN ===

QMan is uniquely testable among all cooperative multitasking libraries for microcontrollers. This follows directly from decoupling time advancement from scheduling decisions.

The manual form QMAN_TICK(N_tick) is the testing entry point:
  QMAN_TICK(10_tick);   // advances time by exactly 10 ticks, runs scheduler

Example test on hardware (Arduino sketch in setup()):
  qman.Clear();
  QMAN_GO(testTask);
  QMAN_TICK(1_tick);    // run one tick
  // assertions
  Serial.println("PASS");

Tests can verify: basic scheduling, DUTY period, dynamic reprioritization, queue order, pool overflow, now overflow, task stop/restart, concurrent task interleaving.

=== DSL MACROS QUICK REFERENCE ===
QMAN_TASK(name)         - define a task function named 'name' (returns void)
QMAN_INIT               - one-time init block inside task (needed for SLEEP/DUTY)
QMAN_LOOP               - repeating loop block inside task (needed for SLEEP/DUTY)
QMAN_END()              - terminate task and reset state (next GO will re-run INIT)
QMAN_SLEEP(time)        - yield for 'time' using _ms or _us literal (needs INIT+LOOP)
QMAN_DUTY(period)       - maintain fixed period with automatic compensation (needs INIT+LOOP)
QMAN_GO(task)           - start task immediately
QMAN_GO(task, delay)    - start task after delay (delay must use _ms, _us, or _tick)
QMAN_TICK()             - call in loop() to drive the manager (auto time)
QMAN_TICK(N_tick)       - manual tick for testing

User-configurable defines (set before #include <QMan.h>):
  #define QMAN_POOL_SIZE 32   - change max concurrent tasks (default 16)
  #define QMAN_TICK_MAX_US 16 - change tick granularity (default 32)

=== COMPARISON SUMMARY TABLE ===

Feature                        QMan        AceRoutine    TaskScheduler   FreeRTOS
Context switch mechanism       goto        goto          function call   HW context
Dynamic reprioritization       YES         NO            NO              YES (preemption)
Period compensation (DUTY)     YES         NO            partial         YES
Testable without hardware      YES         NO            NO              NO
RAM per task (AVR)             8 bytes     16-28 bytes   low             100s-1000s bytes
8-bit AVR support              YES         YES           YES             NO
Learning curve                 Medium      Medium        Medium          High

=== LICENSE ===
MIT License. Free for commercial and open source projects.