! zclock.h
! A nearly exact port of the daemons used in Infocom games.
! Ported by Allen Garvin, December 6, 2003
! Use freely!

! Some useful constants. Their names come from the ZIL source for minizork
Constant C_ENABLED 0;
Constant C_TICK    1;
Constant C_RTN     2;
Constant C_INTLEN  6;

! Modify this to increase the number of possible daemons.
! It must be a multiple of C_INTLEN (by default,  there can be 30 daemons)
Constant C_TABLELEN 180;

Array   C_table --> C_TABLELEN;
Global  C_ints = C_TABLELEN;

! Queue is the programmer's interface to the daemons. It takes 2 arguments.
!    rtn:    The routine to be queued as a timer or daemon
!    ticks:  If positive, the timer countdown length. After 'ticks' turns
!               rtn will be called.
!            If negative, then behave like an Inform daemon. 'rtn' will be
!               called every turn.
!            If zero, the daemon or timer will be disabled.
! A queued event must be explicitly enabled. The return value of the
!   function is the interrupt array for the event. If the first byte is
!   set to 1, it will be enabled. If 0, it will be disabled.
!   Thus:
!          Queue(Foo, 5)-->C_ENABLED = true;      Call 'Foo' in 5 turns
!          Queue(Bar, -1)-->C_ENABLED = true;     Call 'Bar' every turn
!          Queue(Biff, 20);     Place 'Biff' in the queue, but do not start
!                               the timer yet. The timer will not decrement.
!                               No real purpose I can see, but Infocom did
!                               this frequently in their Main routines
!          Queue(Foo); or Queue(Foo, 0);          Stop the 'Foo' timer
[ Queue rtn ticks cint ;
 cint = QueueInterrupt(rtn);
 cint-->C_TICK = ticks;
 StartDaemon(zork_daemon);
 return cint;
];

! QueueInterrupt was rarely called directly in Infocom games
[ QueueInterrupt rtn end c int ;
 end = C_table + C_TABLELEN;
 c = C_table + C_ints;
 while( true ) {
   if( c ~= end ) {
     if( (c-->C_RTN) == rtn )
       return c;
     c = c + C_INTLEN;
   } else {
     C_ints = C_ints - C_INTLEN;
     int = C_table + C_ints;
     int-->C_RTN = rtn;
     return int;
   }
 }
];

! I decided to implement the queue by using an object with an Inform daemon.
Object  zork_daemon "Daemon"
 with  name 'zdaemon',
       daemon [ c end tick flag;
         c = C_table + C_ints;
         end = C_table + C_TABLELEN;

         while( true ) {
           if( c == end ) {
             return flag;
           }
           if( c-->C_ENABLED ) {
             tick = c-->C_TICK;
             if( tick ~= 0 ) {
               c-->C_TICK = (tick - 1);
               if( tick <= 1 && (c-->C_RTN)() )
                 flag = 1;
             }
           }
           c = c + C_INTLEN;
         }
       ];

! Notes:
! Once a routine is queued, there is no method to remove it from the
!   queue. Thus, there is an absolute number of queues in a game. But
!   Requeueing a routine will use the same slot, instead of using a new one.
! If you queue a routine with a negative value, there is a bug. When 32767
!   turns have passed, the number will overflow to positive, and the queue
!   will then be a countdown instead of a daemon event. Though easy to fix
!   I decided to leave it bug-compatible with Infocom games. In Zork I, for
!   instance, after 2^15 turns the thief will quit moving around the maze.
!   And neither the thief nor troll will defend themselves when attacked.