! ---------------------------------------------------------------------------- !
!   Daemons.h by Andrew Plotkin and Roger Firth ([email protected],
!                                   to whom all problems should be notified).
!
!       2.0 Oct 2001
!           Converted to Inform 6 and Glulx by Roger Firth.
!
!       1.0 Oct 1995
!           Original version for Inform 5.5 by Andrew Plotkin.
!
!   This file is in the public domain.
! ---------------------------------------------------------------------------- !
!   Installation: add the lines:
!
!       Replace StartDaemon;
!       Replace StartTimer;
!
!   before your Include "Parser" line. Also, add the line:
!
!       Include "daemons";
!
!   near the end of your game AFTER you've defined an object which uses a
!   "daemon_priority" property.
!
! ---------------------------------------------------------------------------- !
!
!   This is a package which you can include to make the daemons and timers of
!   the Inform libraries behave predictably. They will execute in the order
!   defined by the optional "daemon_priority" property of each object in the
!   daemon/timer list. Higher priorities go first; if priorities are equal,
!   they execute in the order in which they were started up. The default
!   daemon_priority is zero, but you can assign any numerical value to the
!   property, including negative ones.
!
!   There are a few restrictions:
!
!   - daemon_priority must be a simple variable containing a number; it cannot
!     be an embedded routine which returns a number.
!   - you should assign a new value to a daemon_priority property only while
!     the object's daemon/timer is *not* running. This means that if an object
!     has both a daemon and a timer, they must run at the same priority.
!   - it is illegal to call StartDaemon() or StartTimer() from inside a
!     daemon or timer routine. It *is* legal to call StopDaemon() or
!     StopTimer() at any time.
!
!   A bonus utility routine IsDaemonActive(obj) returns 1 if obj currently has
!   an active daemon, 2 for an active timer, 3 for both, 0 for neither.
!
!   Here is an example daemon:
!
!   Object  myDaemon
!     with  daemon_priority 1000,
!           daemon [;
!               ...
!               ];
!
! ---------------------------------------------------------------------------- !

#ifndef WORD_HIGHBIT;               ! Is already defined in Glulx compiler.
Constant WORD_HIGHBIT $8000;        ! Topmost bit in Z-machine word, used by
#endif;                             ! the Library to flag daemons in the_timers.

!   GetPriority() is a utility routine which masks out the daemon-or-timer flag
!   and fetches the value of the daemon_priority property.
!
[ GetPriority obj;
   obj = obj & ~WORD_HIGHBIT;
   if (obj provides daemon_priority) return obj.daemon_priority;
   return 0;
   ];

!   StartDaemonOrTimer() is a utility routine which inserts the given object
!   in the_timers list, at the position defined by its daemon_priority. That is,
!   the object is inserted after all objects with a greater-or-equal priority,
!   and before all objects with a lesser priority.
!
[ StartDaemonOrTimer obj
   i j;

   ! If obj already in the list, do nothing
   for (i=0 : i<active_timers : i++)
       if (the_timers-->i == obj) rfalse;

   ! Compress the list, removing empty slots
   for (i=0,j=0 : i<active_timers : i++)
       if (the_timers-->i) {
           if (i > j) {
               the_timers-->j = the_timers-->i;
               the_timers-->i = 0;
               }
           j++;
           }

   ! Check for capacity to run another daemon/timer
   active_timers = j;
   if (active_timers == MAX_TIMERS) { RunTimeError(4); rfalse; }

   ! Find the first slot at a lower priority
   for (i=0 : i<active_timers : i++)
       if (GetPriority(the_timers-->i) <  GetPriority(obj)) break;

   ! Shift upwards that slot and those above it
   for ( : j>i : j--) the_timers-->j = the_timers-->(j-1);

   ! Insert the new object
   the_timers-->i = obj;
   return ++active_timers;
   ];

!    Replacement for the standard Library version of StartDaemon().
!
[ StartDaemon obj;
   StartDaemonOrTimer(obj+WORD_HIGHBIT);
   ];

!    Replacement for the standard Library version of StartTimer().
!
[ StartTimer obj timer;
   if (obj.&time_left == 0) { RunTimeError(5, obj); return; }
   if (StartDaemonOrTimer(obj)) obj.time_left = timer;
   ];

! ---------------------------------------------------------------------------- !

System_file;

!   IsDaemonActive() tests whether an object's daemon and/or timer is
!   currently scheduled to run.
!
[ IsDaemonActive obj
   i j;
   for (i=0,j=0 : i<active_timers : i++) {
       if (the_timers-->i == obj) j = 2;
       else if (the_timers-->i == obj + WORD_HIGHBIT) j++;
       }
   return j;
   ];

! ---------------------------------------------------------------------------- !