# Cooperative multi-tasking in Dusk

The aim of this unit is to allow multiple runloops that consume I/O events to
coexist peacefully without having to be, themselves, aware of other consumers of
I/O events.

Dusk OS started as a single console system. Under such a system, multi-tasking
isn't a problem. If, for example, you launch [app/ed], it becomes the runloop
until you quit it. All fine.

However, if you run Duskberon [doc/oberon], then you have the possibility of
running multiple instances of, for example, [app/ed]. At that point, runloops
fight each other and they fight Duskberon's runloop. That's a problem.

Therefore, we need to say "hey! there's only ever going to be one runloop around
here, and it's lib/coop! Respect my authoritah!" and have everyone play nice
with this idea.

## Cooperation is optional and implicit

To be clear: lib/coop is optional. You can have a Dusk system without it. In
this case, the runloop is most probably [io/prompt].

If you use a "cooperative" application under such system, lib/coop will be
implicitely loaded, and the runloop implicitly set up. As a system operator, you
don't have to set up lib/coop.

## The runloop

This runloop is:

1. Is the "running?" field of root context zero? Stop the loop
2. Poll for a I/O event
3. For each I/O event generated, call the handler
4. Idle [lib/idle] and goto 1

## I/O events

An I/O event will at some point be diverse (think TCP/IP), but at the moment,
it's two things: Keyboard input and mouse input.

Therefore, polling for I/O events is to repeatedly poll [io/kbd] and [io/mouse].

## Event slots

This unit define a handful of "internal" events (REGISTER, INITIALIZE, FINALIZE,
IDLE) that aren't really I/O events, but otherwise lets other unit come here and
define their own event.

They do so by filling what we call an event "slot". A slot is only an index that
is then associated with an event type constant. There's a limited number of
them, because slots correspond to an entry in the handler array that we allocate
for each Application.

In parallel to that, those units will also register a "generator" word which is
repeatedly called and has the responsibility of dispatching events through
"rootdispatch" when there is one.

No actual I/O events are generated by this unit *except* KEYPRESS, which should
normally be defined in [io/kbd], but is defined here because of circular
dependency.

## Event handlers

When events are generated, they'll be passed to what we call a event handler, a
xt with the signature ( event -- ).

"event" is a pointer to an Event structure. Most of the time, it will correspond
to the global "curevent" variable, but with dispatching, it's possible that it's
not.

## Applications

What we call an application is quite simply a list of handlers for all possible
events. You create an application with "createapplication". Example:

  createapplication myapp
  : keypress ... ;
  current KEYPRESS myapp sethandler
  \ a single global ctx means we don't support multiple instances of the same
  \ app.
  myapp newcontext const myctx
  : runmyapp myctx launchcontext ;

Unspecified handlers are filled with appropriate noops (or errors, in the
case of REGISTER).

## Context

An application is something static. A context is an instance of an application.
If your application "myapp" above runs in two simulatneous copies, they'll both
point to the same Application, but have different contexts.

lib/coop defines a base Context structure which contains only a pointer to its
Application. Applications are expected to extend that structure with their own
specific contextual information.

There is a global "context" value which is a pointer to the context currently
being dispatched an event.

TODO: right now, all our apps are static, so we don't do that, but soon, we
will.

## Event dispatching

When lib/coop polls for event, it ever only dispatches them to a single
application which we call the "root application". This can immediately be seen
as a problem: how do we run multiple applications?

For this, we need "dispatcher applications", that is, applications that are made
specifically to handle events for sub-applications. Duskberon [doc/oberon]
defines such an application.

But how do "regular" applications register themselves with a dispatcher? They
don't, because we want any dispatcher to be able to work with any regular
application. They thus need to be unaware of each other.

This registering happens through the REGISTER event, which is generated when
"launchcontext" is ran at a moment where there's already a runloop running.

The REGISTER event, when unhandled by an application, will generate an error
(disorderly runloops!), but when handled, will let the dispatcher do its thing
how it wants.

## The runloop and idling

Regardless of whether a system has a cooperative runloop running or not, the
"idle" word [lib/idle] always behaves as expected.

When "coop$" is called, a idling handler is added which then dispatches a IDLE
event to the root context. By dispatching convention, this should mean that
everyone in the system gets a IDLE event dispatched to.

When you write your lib/coop app, you can theoretically register a idle handler
directly to [lib/idle]. However, you're encouraged not to, because idle handlers
can't be easily deactivated, where app contexts can. So, use IDLE handlers when
you can.

## The runloop and blocking handlers

This event loop that this unit runs, it's supposed to be smooth. This means that
event handlers are supposed to return immediately, in the sense that they
shouldn't block waiting for input.

Sometimes, it's inconvenient. For example, if you want the user to type
something and then act on the typed string, it adds complexity to restructure
the app around context states. The most straightforward solution is to
repeatedly call "key" [io/kbd], which is blocking.

But don't do it, your app won't behave well. Instead, design a system that takes
advantage of Context.state. See ":bottomtype>" in [app/ed] for an example.

## API

### Structures

struct Event
  fields type arg1 arg2 arg3 arg4
endstruct

type   one of the event types enum below
arg1-4 Arbitrary values, depending on event type

struct Application
  method dispatch ( event app -- )
  reference handlers(
  SLOTCNT 4* skip
endstruct

dispatch ( event app -- )
  Process specified event as appropriate for this app. Most of the time, points
  to ":selfdispatch".

handlers(
  Pointer to an array of event handlers, with event types being the index.

struct Context
  field app state
endstruct

app   Pointer to the Application of the context
state One of the state constants (STOPPED, RUNNING or an app=specific one)

### Constants

SLOTCNT  16. The maximum number of slots that can be filled.
STOPPED   0. For Context.state
RUNNING   1. For Context.state

### Event types

REGISTER   ( arg1=context )
  Dispatched when a new application is ran but there's already a root app
  running.

INITIALIZE ( noarg )
  Dispatched before starting the runloop on the root application, or by the root
  application to a newly registered sub-application.

FINALIZE   ( noarg )
  Dispatched to a context after it has been put in STOPPED state. This is the
  last event this context is going to receive unless it is launched again.

IDLE       ( noarg )
  Dispatched on idling [lib/idle]. Unlike other events, which are generally
  sent only to the "active" application, multi-dispatchers are expected to send
  the IDLE event to all applications.

KEYPRESS   ( arg1=c arg2=melted-nkc )
  Dispatched when a key is pressed on the keyboard.

External units also defining events (see their docs for details):

MOUSEMOVE    [io/mouse]
MOUSECLICK   [io/mouse]

### Values

curevent    Event struct where generated events go
rootcontext Context of our root application
context     Context being currently dispatched an event

### Words

addeventtype ( -- slotidx )
  Fill an event slot, thus creating a new event type.

addgenerator ( xt -- )
  Register a new generator word, which must have the signature ( -- ) and
  dispatch event when they are generated. A generator call can result in more
  than one dispatch.

  This xt can use "curevent".

:selfdispatch ( event app -- )
  Dispatch specified event to "self", that is, the current context.

createapplication ( -- )
  Create a new application with its "dispatch" method set to :selfdispatch and
  dummy handlers set to it.

cloneapplication ( app -- )
  Create a new application, cloning the dispatch method and handlers from the
  specified app.

sethandler ( xt eventtype app -- )
  Set specified handler to specified app.

newcontext ( app -- context )
  Create a new context for specified app.

ctxrunloop ( context -- )
  Run a coop loop as long as context's state is not STOPPED. Note that even
  though this can be called on something else than the root context, I/O events
  are still dispatched the regular way, that is, to the root context.

  You will find this word useful in cases where you want to block until the
  specified context is closed.

launchcontext ( context -- )
  If there is no root application running, start a runloop with "context" as
  root. Otherwise, generate a "REGISTER" event with "context" as argument.

launchrootcontext ( context -- )
  Run specified context, forcing it to be a root application. This guarantees
  that this call results in a runloop running (so it doesn't return until that
  runloop is over). After the runloop is over, this word restores old root
  context.

stopcontext ( context -- )
  Set state field of context to STOPPED.

stopcurrent ( -- )
  Shortcut for "context stopcontext".

coop$ ( -- )
  Install the cooperative runloop into "main", which means that whenever we
  abort, we go back to this runloop instead of, for example, [io/prompt]'s loop.
