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


! Here's the code:

! Define the property.
Property daemon_priority 0;

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

[ StartDaemon obj 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==1) 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=2;
];

! StartTimer is exactly parallel to StartDaemon; see above for comments.

[ StartTimer obj timer i j;
	for (i=0:i<active_timers:i++)
	    if (the_timers-->i==obj)
	    {   if (timer_flags->i==2) TimerE3(obj);
	        rfalse;
	    }
	    
	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;
		}
	}
	j++;
	while (the_timers-->j ~= 0 && j < i)
		j++;
	if (j < i) {
		i = j;
	}
	else {
		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--;
		}
	}

	if (obj.&time_left==0) TimerE2(obj);
	the_timers-->i=obj; timer_flags->i=1; 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;
];

