;;;; -*- Scheme -*-
;;
;;;; "find" example for HLSIM
;;
;; David DeRoure June 12 1997, revised Sep 10 1997
;; dder@martigny.ai.mit.edu
;;
;; This Gunk file should be compiled with (cps "find1") followed by
;; (cbf "find1").  It uses definitions in util1d.scm.  See find1s.scm
;; for simulation setup.  See the HLSIM documentation for further details.
;;
;; A "source" processor (processor 0) transmits an UP message containing
;; a "time-to-live" of 50 hops.  When a processor receives an UP message
;; it rebroadcasts it (with ttl decremented) iff the ttl is greater than 
;; anything previously rebroadcast and greater than zero; when rebroadcast 
;; occurs, the process remembers the ID of the processor from which the 
;; message was received (the UP neighbor).  Hence the UP message travels
;; away from the source, and each processor knows the id of its UP neighbor.
;; This is a similar algorithm to the potential example.
;;
;; When the message reaches the destination processor(s), the destination
;; processor sends a DOWN message carrying the id of its UP neighbor.  
;;
;; When a processor receives a DOWN message carrying its id, it
;; broadcasts a new DOWN message to its own UP neighbor, hence the
;; DOWN message travels back to the source, following the chain of
;; UP neighbors.  DOWN messages also carry the id of their sender.
;;
;; Whenever a message is broadcast, retries will occur automatically
;; until such time as they are turned off.  Retries UP cause chains to 
;; be optimised and they continue to be sent until any DOWN message is
;; received.  Retries DOWN are turned off when a DOWN message is received 
;; that was sent by the UP neighbor - this acknowledgement mechanism
;; assumes a symmetric communication model.  NB It is possible to leave 
;; UP retries running, perhaps delaying them rather than stopping them when
;; a DOWN has been seen, but beware that a processor's record of its
;; UP neighbor could then be updated while waiting for an acknowledgement,
;; which with the current program would prevent the acknowledgement 
;; being recognised.
;;
;; Processors change color to magenta when they rebroadcast UP, so that the
;; extent of propagation is evident.  As the DOWN message travels back from
;; destination(s) to source, the processors en route change color to
;; white.  Comms activity is shown by receives flashing green and collisions
;; flashing red.  The source is yellow and the destination white.

(declare (usual-integrations))

;; My processor ID

(define processor-id (random 1000000))

;; Is this a source processor?

(define source?
  (member (processor-number)
          (simulation.get (the-simulation) 'source-processors)))

;; Is this a destination processor?

(define destination?
  (member (processor-number)
          (simulation.get (the-simulation) 'destination-processors)))

;; The global timeout, at which event loop will stop

(define global-timeout (make-timeout-event 100000))

;; The continue event, which is signalled to trigger the event loop
;; to loop when we wish select to re-evaluate the events in each clause.

(define continue (make-event))

;; The break event, which is signalled to break the event-loop

(define break (make-event))

;; The never event, which is never signalled. 

(define never (make-event))

;; The transmit timers

(define retry-down-timer never)
(define retry-up-timer never)

;; Procedures to set timers.  The continue event is signaled
;; so that select re-evaluates the timer events.

(define (set-retry-down-timer! delay)
  (set! retry-down-timer
	(if delay (make-timeout-event delay) never))
  (event.signal! continue #T))

(define (set-retry-up-timer! delay)
  (set! retry-up-timer
	(if delay (make-timeout-event delay) never))
  (event.signal! continue #T))

(define primitive-message-event (make-event))

;; The event loop

(define (loop max-ttl up-neighbor down-neighbor)
  (color-me (if source? "yellow"
		(if down-neighbor "white" 
		    (if up-neighbor "magenta" "blue"))))
  (select
    (primitive-message-event
     => (lambda (message)
          (event.clear! primitive-message-event)
	  (color-me "green")
;;
;; Collision.  If sending UP messages, back off before trying again.
;;
          (cond ( (eq? message 'collision) 
		  (color-me "red")
		  (if up-neighbor
		      (set-retry-up-timer! (+ 1000 (random 5000))))
		  (loop max-ttl up-neighbor down-neighbor) )
;;
;; UP message with higher TTL than previously received.  Rebroadcast
;; it and record who sent it (as my up-neighbor) UNLESS I am the
;; destination, in which case pause for any more imminent UPs and then
;; commence DOWN messages.
;;
		( (and (eq? (first message) 'up) (> (second message) max-ttl))
		  (if destination?
		      (set-retry-down-timer! 500) ; wait before DOWN
		      (set-retry-up-timer! (random 100)))
		  (loop (second message) (third message) down-neighbor) )
;;
;; DOWN message.  Always disable UP messages when a DOWN message
;; has been received.
;;
		( (eq? (first message) 'down)
		  (set-retry-up-timer! #F)
;;
;; If this DOWN message is for me, rebroadcast it and record who
;; sent is (as my down-neighbor) UNLESS I am the source, in which
;; case do not rebroadcast.
;;
		  (cond ( (= (second message) processor-id) ; for me
			  (if source? (beep) (set-retry-down-timer! 100))
			  (loop max-ttl up-neighbor (third message)) )
;;
;; If I have sent a DOWN message to my up-neighbor, and the message
;; just received came from my up-neighbor, then treat this as an
;; acknowledgement and stop rebroadcasting.  Finish the loop.
;;
			( (and down-neighbor up-neighbor    ; acknowledgment
			       (= (third message) up-neighbor))
			  (set-retry-down-timer! #F)
			  (color-me "white")
			  (event.signal! break #T) )
;;
;; If none of the above, ignore this message and go round the loop again.
;;
			( else (loop max-ttl up-neighbor down-neighbor) )) )
		( else (loop max-ttl up-neighbor down-neighbor) ))))
;;
;; When the retry-down-timer times out, broadcast a DOWN message
;; and schedule the retry.
;;
    (retry-down-timer (broadcast `(down ,up-neighbor ,processor-id))
		      (set-retry-down-timer! (+ 100 (random 200)))
		      (loop max-ttl up-neighbor down-neighbor))
;;
;; When the retry-up-timer times out, broadcast an UP message
;; and schedule the retry.
;;
    (retry-up-timer   (broadcast `(up ,(-1+ max-ttl) ,processor-id))
		      (set-retry-up-timer! (+ 1000 (random 10000)))
		      (loop max-ttl up-neighbor down-neighbor))
;;
;; When the continue event is signaled, go round the loop.  This forces
;; select to re-evaluate the clauses, hence the mutated timers take effect.
;;
    (continue (event.clear! continue)
	      (loop max-ttl up-neighbor down-neighbor))
;;
;; The break event causes the loop to finish, as does gloabl-timeout.
;;
    ((or break global-timeout) 'done)))

;; Run the loop

;; loop max-ttl up-neighbor down-neighbor

(if source? (set-retry-up-timer! 0))

(loop (if source? 50 0) source? destination?)

;; end of find1.scm
