# Distributed Dining Philosophers.  Based on the example in Section 13.3 of
# "The SR Programming Language: Concurrency in Practice", by Greg Andrews and
# Ron Olsson, Benjamin/Cummings, 1993.  The algorithm is from Chandy and Misra,
# "Drinking Philosophers Problem", ACM TOPLAS v 6, n 4, Oct 1984, pp 632-646.
# Usage:  a.out [n secs t_secs e_secs t_secs e_secs ...]
# (for n philosophers, secs seconds running time, t_secs max thinking seconds,
# e_secs max eating seconds for each philosopher)
#
resource Philosopher
   import Servant, MPDanimator
body Philosopher(cap Servant myservant, int id, int thinking, int eating)
   process phil  {
      while (true) {
	 nap(int(random(thinking)))
	 write(age(), "philosopher", id, "is hungry")

# Change a hungry philosopher's symbol to be solid green.
	 call A_color(id, "green")
	 call A_fill(id, "solid")

	 myservant.getforks()

# Change an eating philosopher's symbol to be solid blue.
	 call A_color(id, "blue")

	 nap(int(random(eating)))

# Change a thinking philosopher's symbol to an outline black circle.
	 call A_fill(id, "outline")
	 call A_color(id, "black")

	 myservant.relforks()
      }
   }
end Philosopher

resource Servant
   import MPDanimator
   op getforks(), relforks()
   op needR(), needL(), passR(), passL()
   op links(cap Servant r, cap Servant l)
   op forks(bool haveR, bool dirtyR, bool haveL, bool dirtyL)

# This op and the variables below are used to hold the symbol id numbers of
# the left and right fork symbols and where the left and right forks are
# placed next to the philosopher when it possesses them.
   op fork_ids(int forkR, int forkL, int holderR, int holderL )

body Servant(int id)
   op hungry(), eat()
   cap Servant  r, l 
   bool  haveR, dirtyR, haveL, dirtyL 
   int  forkR, forkL, holderR, holderL 

   proc getforks()  {
      send hungry(); receive eat()
   }

   process server  {
      receive links(r, l)
      receive forks(haveR, dirtyR, haveL, dirtyL)

# Move a left or right fork initially given to this philosopher to be next
# to the philosopher.
      receive fork_ids(forkR, forkL, holderR, holderL)
      if (haveR) { call A_jumpto(forkR, holderR) }
      if (haveL) { call A_jumpto(forkL, holderL) }
      if (dirtyR) { call A_color(forkR, "orange")
		   call A_fill(forkR, "solid") }
      if (dirtyL) { call A_color(forkL, "orange")
		   call A_fill(forkL, "solid") }

      while (true) {
	 in hungry() ->
	    if (~haveR) { send r.needL()
	    } else {
	       write(age(), "philosopher", id, "has right fork")
	    }
	    if (~haveL) { send l.needR()
	    } else {
	       write(age(), "philosopher", id, "has left fork")
	    }
	    while (~(haveR & haveL)) {
	       in passR() -> haveR = true; dirtyR = false
		  write(age(), "philosopher", id, "got right fork")

# Move the fork from where it was to be next to this philosopher and then
# change its symbol to be a black outline circle i.e. not dirty.  Also raise
# the fork's symbol to the closest viewing plane to make it more visible.
		  call A_stepjumpto(forkR, holderR, 5, 100)
		  call A_fill(forkR,  "outline")
		  call A_color(forkR, "black")
		  call A_raise(forkR)

	       [] passL() -> haveL = true; dirtyL = false
		  write(age(), "philosopher", id, "got left fork")

# Ditto.
		  call A_stepjumpto(forkL, holderL, 5, 100)
		  call A_fill(forkL,  "outline")
		  call A_color(forkL, "black")
		  call A_raise(forkL)

	       [] needR() st dirtyR -> haveR = false; dirtyR = false
		  send r.passL(); send r.needL()
		  write(age(), "philosopher", id, "sends dirty right fork")
	       [] needL() st dirtyL -> haveL = false; dirtyL = false
		  send l.passR(); send l.needR()
		  write(age(), "philosopher", id, "sends dirty left fork")
	       ni
	    }
	    write(age(), "philosopher", id, "has both forks")
	    send eat(); dirtyR = true; dirtyL = true
	    receive relforks()
	    write(age(), "philosopher", id, "is finished eating")

# Now that the philosopher has finished eating, its forks are dirty so
# change their symbols to be solid orange circles.
	    call A_color(forkL, "orange")
	    call A_fill(forkL, "solid")
	    call A_color(forkR, "orange")
	    call A_fill(forkR, "solid")

	 [] needR() -> haveR = false; dirtyR = false; send r.passL()
	    write(age(), "philosopher", id, "sends right fork")
	 [] needL() -> haveL = false; dirtyL = false; send l.passR()
	    write(age(), "philosopher", id, "sends left fork")
	 ni
      }
   }
end Servant

resource Main()
   import Philosopher, Servant, MPDanimator
   int n = 5;		getarg(1, n)
   int runtime = 60;	getarg(2, runtime)
   cap Servant s[1:n]
   cap Philosopher p[1:n] 
   int think[1:n] = ([n] 5)
   int eat[1:n] = ([n] 2)
   for [ i = 1 to n ] { getarg(2*i+1, think[i]); getarg(2*i+2, eat[i]) }
   write(n, "philosophers; think, eat times in seconds:")
   for [ i = 1 to n ] { writes(" ", think[i]) }; write()
   for [ i = 1 to n ] { writes(" ",   eat[i]) }; write()

##### animator #####v
# Change coordinates so 0,0 is the center, then create a big black outline
# circle to be the table.
   call A_coords(-1.5, -1.5, 1.5, 1.5)
   call A_circle(1000, 0.0, 0.0, 1.0, "black", "outline")
# Put some annotated symbols on the screen.
   call A_circle(1001, -1.4, -0.6, 0.02, "black", "outline")
   call A_text(1002, -1.3, -0.625, 0, "black", "clean fork")
   call A_circle(1003, -1.4, -0.8, 0.02, "orange", "solid")
   call A_text(1004, -1.3, -0.825, 0, "black", "dirty fork")
   call A_circle(1005, -1.4, -1.0, 0.05, "black", "outline")
   call A_text(1006, -1.3, -1.025, 0, "black", "THINKING")
   call A_circle(1007, -1.4, -1.2, 0.05, "green", "solid")
   call A_text(1008, -1.3, -1.225, 0, "black", "HUNGRY")
   call A_circle(1009, -1.4, -1.4, 0.05, "blue",  "solid")
   call A_text(1010, -1.3, -1.425, 0, "black", "EATING")
# Put a clean set of forks, small black outline circles, near the table.
   call A_text(1011, -1.0+0.05*(n+1), -1.41, 0, "black", "forks")
   for [ i = 1 to n ] {
      call A_circle(3000+i-1, -1.0+0.05*i, -1.4, 0.02, "black", "outline")
   }
   const real TWO_PI = 2.0*acos(-1.0)
   for [ i = 1 to n ] {
# Put the philosophers, black outline circles, around the table.
      call A_circle(i, sin(i*(TWO_PI/n)), cos(i*(TWO_PI/n)),
	 0.1, "black", "outline")
      call A_text(2000+i, sin(i*(TWO_PI/n))+0.1, cos(i*(TWO_PI/n))+0.1,
	 1, "black", string(i))
# Put nearly invisible circles (points) on the left and right side of each
# philosopher to be places the forks can be moved to when the philosopher
# gets possession of a fork.
      call A_circle(4000+2*i, sin(i*(TWO_PI/n)-0.12), cos(i*(TWO_PI/n)-0.13),
	 0.001, "black", "outline")
      call A_circle(4000+2*i+1, sin(i*(TWO_PI/n)+0.12), cos(i*(TWO_PI/n)+0.13),
	 0.001, "black", "outline")
   }

   for [ i = 1 to n ] { s[i] = create Servant(i)
      create Philosopher(s[i], i, 1000*think[i], 1000*eat[i])
   }
   for [ i = 1 to n ] { send s[i].links(s[((i-2) mod n)+1], s[(i mod n)+1]) }
   send s[1].forks(true, true, true, true)
   for [ i = 2 to n-1 ] { send s[i].forks(false, false, true, false) }
   send s[n].forks(false, false, false, false)

# Send to each philosopher the xtango animator symbol id's of the two forks
# the philosopher needs to eat and the places where possessed forks are to
# be moved next to the philosopher.
   for [ i = 1 to n ] {
      send s[i].fork_ids(3000+((i-1) mod n), 3000+(i mod n),
	 4000+2*i, 4000+2*i+1)
   }

   nap(1000*runtime); write("must stop now"); call A_end(); stop
end Main
