!-----------------------------------------------------------------------
! Priority-Daemons
! Release 1
! Andrew Plotkin (erkyrath@cmu.edu)
! This file is in the public domain.
! 
! This is a module 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 "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. However, you cannot use an embedded
! routine which returns a number, as is usual in Inform. This is because it
! would be very bad if the priority changed in mid-stream; priorities are
! only checked when a daemon starts up. For the same reason, you should not
! assign a new value to a priority property. If you must do it, do it while
! the object's daemon/timer is *not* running.
! 
! These routines have one restriction that the standard library routines do
! not: It is illegal to call StartDaemon or StartTimer from inside a daemon
! or timer routine, if the daemon you're starting has a priority greater
! than or equal to the calling daemon. It *is* legal to start a
! daemon/timer of lower priority (and that daemon will be called for the
! first time that same turn.) It is also legal to call StopDaemon or
! StopTimer at any time.
! 
! To use this thing, include this file after you include the Inform
! "parser" file. Before the "parser" include, drop in these four lines
! (uncommented):
! 
! Replace StartDaemon;
! Replace StartTimer;
! Replace StopDaemon;
! Replace StopTimer;
!--------------------------------------------------------------------------

Property daemon_priority 0;

! StartDaemonOrTimer 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.
! 
! This is not complicated, just a little ugly.

[ StartDaemonOrTimer obj flag i j;
	! check to make sure it's not already running
	for (i=0:i<active_timers:i++)
	    if (the_timers-->i==obj)
	    {   if (timer_flags->i~=flag) TimerE3(obj);
	        rfalse;
	    }
	    
	! find a slot
	j = (-1);
	for (i=0:i<active_timers:i++) {
		if (the_timers-->i ~= 0 
			&& (the_timers-->i).daemon_priority >= obj.daemon_priority) {
			j = i;
		}
		if (the_timers-->i ~= 0 
			&& (the_timers-->i).daemon_priority < obj.daemon_priority) {
			break;
		}
	}

	! We must now put obj between j (-1 .. a_t-1) and i (0 .. a_t). Both 
	! are objects (not empty spaces.) j has >= priority, i has < priority. 
	! If there's a blank space between them, that's perfect. 
	! Otherwise we'll have to shift i upwards.
	j++;
	while (the_timers-->j ~= 0 && j < i)
		j++;
	if (j < i) {
		! j is open. Use it.
		i = j;
	}
	else {
		! We'll use j, but we have to move i up first.
		while (i < active_timers && the_timers-->i ~= 0)
			i++;
		if (i == active_timers) {
			if (active_timers*2 >= MAX_TIMERS) {
				TimerE();
				return;
			}
			active_timers++;
		}
		while (i > j) {
			the_timers-->i  = the_timers-->(i-1);
			timer_flags->i = timer_flags->(i-1);
			i--;
		}
	}
	! i is now set correctly (in both cases.)
	
	the_timers-->i=obj; timer_flags->i=flag;
];

[ StartDaemon obj;
    StartDaemonOrTimer(obj,1);
];

[ StartTimer obj timer;
	if (obj.&time_left==0) TimerE2(obj);
    else {
        StartDaemonOrTimer(obj,2);
        obj.time_left = timer;
    }
];

! StopTimer and StopDaemon are exactly the same as in the standard
! library (from release 8 up to at least release 11.) I include them
! here just in case you're using a really old (or distant future) 
! library.

[ StopTimer obj i;
   for (i=0:i<active_timers:i++)
       if (the_timers-->i==obj) jump FoundTSlot2;
   rfalse;
   .FoundTSlot2;
   if (obj.&time_left==0) TimerE2(obj);
   the_timers-->i=0; obj.time_left=0;
];

[ StopDaemon obj i;
   for (i=0:i<active_timers:i++)
       if (the_timers-->i==obj) jump FoundTSlot4;
   rfalse;
   .FoundTSlot4;
   the_timers-->i=0;
];

! End of file -------------------------------------------------------------