! 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.
