Before canceling tasks, find all events with subscribers and cancel
them. This wakes the subscribers, allowing them to run additional
cleanup code if necessary.
Coroutines are also taken down earlier in the stage shutdown sequence,
before removing any entities. This avoids the problem of entity removal
code waking up coroutines by cancelling events, which in turn may spawn
more entities. This issue sometimes caused the elusive "%i entities were
not properly unregistered" bug.
* Smarter generic entity macros
The list of "core" entities is now defined in one macro, and hardcoded
_Generic dispatch tables are eliminated
* Get rid of "custom" entities
All entities are now "first-class". The list of known entity types has
been moved to known_entities.h. The system no longer needs to know the
definition of all entity structs.
* Refactor guts of ENT_BOX/ENT_UNBOX
Made the functions inline, Box::ent is now a proper pointer type (but
please don't use it directly), ENT_UNBOX returns NULL if the box is
"empty" (references NULL entity)
* Merge TASK_BIND_UNBOXED with TASK_BIND
* s/YoumuMyon/YoumuAMyon for consistency
TASK_HOST_CUSTOM_ENT(type) allocates a custom entity from a reserved
region on the task's stack (same as TASK_MALLOC) and registers it. The
entity will be automatically unregistered when the task terminates for
any reason. Only one entity can be hosted by any particular task. This
macro returns a pointer to the hosted entity.
TASK_HOST_EVENTS(events) associates a custom array of events (created
with COEVENTS_ARRAY) with the task. All events in the array are
automatically initialized when this macro is called, and cancelled when
the task terminates for any reason. Only one array of events can be
hosted by any particular task.
TASK_MALLOC(size) allocates a memory region with a lifetime bound to the
active task. It can't be free'd manually.
Allocation requests are served from a dedicated region on the task's
stack whenever possible, which is fast and essentially free. If there is
not enough free space to serve the request, then the memory is allocated
on the heap. Such heap allocations are automatically free'd when the
task expires.
The allocated memory is zero-initialized and aligned as strictly as
max_align_t.
Previously tasks that were invoked by an initial invocation of another
task were scheduler before that parent task. Now they are scheduled
after the parent task, following the sequential order of invocation