# taskswitch -- Cooperative Task Switching Library

Cooperative (non-preemptive) multitasking library for DJGPP/DPMI (DOS
protected mode). Built as `libtasks.lib` -- a DXE3 module loaded by
the DVX loader.


## Why Cooperative

DOS is single-threaded with no kernel scheduler. DPMI provides no
thread or timer-based preemption. The DVX GUI event model is
inherently single-threaded (one compositor, one input queue, one
window stack). Cooperative switching lets each task yield at safe
points, avoiding the need for synchronization primitives entirely.


## Scheduling Algorithm

Credit-based weighted fair-share, round-robin within a credit epoch.

Each task receives `(priority + 1)` credits per scheduling round.
Tasks run round-robin, consuming one credit per turn. When all
credits are spent, every ready task is refilled.

| Priority | Constant | Credits/Round | Relative Share |
|----------|----------|---------------|----------------|
| 0 | `TS_PRIORITY_LOW` | 1 | ~4% |
| 5 | `TS_PRIORITY_NORMAL` | 6 | ~22% |
| 10 | `TS_PRIORITY_HIGH` | 11 | ~41% |

Higher-priority tasks run proportionally more often but never starve
lower ones. A priority-10 task gets 11 turns per round while a
priority-0 task gets 1.


## Task States

| State | Description |
|-------|-------------|
| `TaskStateReady` | Eligible for scheduling |
| `TaskStateRunning` | Currently executing (cosmetic marker) |
| `TaskStatePaused` | Skipped until resumed |
| `TaskStateTerminated` | Slot available for reuse |


## API Reference

| Function | Description |
|----------|-------------|
| `tsInit()` | Initialize task system; calling context becomes task 0 (main) |
| `tsShutdown()` | Shut down and free all resources |
| `tsCreate(name, entry, arg, stackSize, priority)` | Create a new task; returns task ID |
| `tsYield()` | Yield CPU to next eligible task (credit-based round-robin) |
| `tsPause(taskId)` | Pause a task (implicit yield if self) |
| `tsResume(taskId)` | Resume a paused task (refills credits) |
| `tsSetPriority(taskId, priority)` | Set priority (refills credits immediately) |
| `tsGetPriority(taskId)` | Get task priority |
| `tsGetState(taskId)` | Get task state |
| `tsCurrentId()` | Get currently running task's ID |
| `tsGetName(taskId)` | Get task name |
| `tsExit()` | Terminate calling task (never returns) |
| `tsKill(taskId)` | Forcibly terminate another task |
| `tsRecoverToMain()` | Force scheduler back to task 0 after crash longjmp |
| `tsActiveCount()` | Count of non-terminated tasks |

### Error Codes

| Constant | Value | Description |
|----------|-------|-------------|
| `TS_OK` | 0 | Success |
| `TS_ERR_INIT` | -1 | Initialization failure |
| `TS_ERR_PARAM` | -2 | Invalid parameter |
| `TS_ERR_FULL` | -3 | No available task slots |
| `TS_ERR_NOMEM` | -4 | Stack allocation failed |
| `TS_ERR_STATE` | -5 | Invalid state transition |

### Constants

| Constant | Value | Description |
|----------|-------|-------------|
| `TS_DEFAULT_STACK_SIZE` | 32768 | Default stack size (32KB) |
| `TS_NAME_MAX` | 32 | Maximum task name length |


## Task 0 (Main Task)

Task 0 is special:
* Cannot be killed or paused
* Uses the process stack directly (no separate allocation)
* `tsRecoverToMain()` always returns control here after a crash
* The DVX shell runs as task 0 with `TS_PRIORITY_HIGH`


## Crash Recovery

`tsRecoverToMain()` is called after `longjmp` from a signal handler
that fired in a non-main task. It fixes the scheduler's `currentIdx`
to point back to task 0. The crashed task is NOT cleaned up by this
call -- `tsKill()` must be called separately afterward.


## Files

| File | Description |
|------|-------------|
| `taskswitch.h` | Public API header |
| `taskswitch.c` | Complete implementation (context switch, scheduler, stack management) |
| `Makefile` | Builds `bin/libs/libtasks.lib` |


## Build

```
make            # builds bin/libs/libtasks.lib
make clean      # removes objects and library
```

No dependencies. Exports all symbols matching `_ts*`.
