-- Copyright: Adam Sampson, Mario Schweigler (C) 2006
-- Institution: Computing Laboratory, University of Kent, Canterbury, UK
-- Description: Demo app for pony: using farming to draw the Mandelbrot set

-- This program is free software; you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation; either version 2 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program; if not, write to the Free Software
-- Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
-- MA 02110-1301, USA.

--{{{  Preprocessor definitions
#DEFINE USE.SDL
--}}}
--{{{  USE/INCLUDE declarations
#INCLUDE "course.module"
#INCLUDE "pony.module"
#INCLUDE "convert.module"
#INCLUDE "trace.inc"
#IF DEFINED (USE.SDL)
#INCLUDE "sdlraster.module"
#ENDIF
--}}}
--{{{  Constants
VAL INT Min.iterations IS 40:
VAL INT Max.iterations IS 4000:
--}}}
--{{{  Type definitions
DATA TYPE WORK
  RECORD
    INT frame:
    REAL64 x:
    INT x.pixel:
    REAL64 y:
    INT y.pixel:
    REAL64 x.stride:
    INT width:
    REAL64 y.stride:
    INT height:
    INT iterations:
:

PROTOCOL WORK.RESPONSE IS WORK ; MOBILE []INT:

--{{{  channel types for shared model
CHAN TYPE FARM.REQ.CT
  MOBILE RECORD
    CHAN WORK req!:
:

CHAN TYPE FARM.RESP.CT
  MOBILE RECORD
    CHAN WORK.RESPONSE resp?:
:
--}}}

--{{{  channel types for ALTing model
CHAN TYPE WORKER.CT
  MOBILE RECORD
    CHAN WORK req!:
    CHAN WORK.RESPONSE resp?:
:

CHAN TYPE FARM.CT
  MOBILE RECORD
    CHAN WORKER.CT? in?:
:
--}}}

--{{{  DATA TYPE COMPLEX
DATA TYPE COMPLEX
  RECORD
    REAL64 r:
    REAL64 i:
:
--}}}

--{{{  DATA TYPE OPTIONS (and initialiser)
DATA TYPE OPTIONS
  RECORD
    INT width:
    INT height:
    INT xchunks:
    INT ychunks:
    BOOL benchmark:
    BOOL incremental:
    BOOL no.frame.sync:
    BOOL no.stop:
    BOOL use.c:
    BOOL use.shared:
    BOOL use.pri:
:
VAL OPTIONS init.options IS [640, 480, 4, 4, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE]:
--}}}

--{{{  DATA TYPE RENDER.PARAMS (and initialiser)
DATA TYPE RENDER.PARAMS
  RECORD
    BOOL running:
    REAL64 x:
    REAL64 y:
    REAL64 diameter:
    INT iterations:
:
VAL RENDER.PARAMS init.render.params IS [TRUE, -1.0, 0.0, 3.0, Min.iterations]:
--}}}
--}}}
--{{{  EXTERNALs
#PRAGMA EXTERNAL "PROC C.calculate.mandel (VAL REAL64 x.init, y.init, x.stride, y.stride, VAL INT width, height, iterations, []INT data) = 0"
--}}}
--{{{  PROC calculate.mandel
PROC calculate.mandel (VAL REAL64 x.init, y.init, x.stride, y.stride, VAL INT width, height, iterations, []INT data)
  INITIAL INT n IS 0:
  SEQ yi = 0 FOR height
    VAL REAL64 y IS y.init + ((REAL64 ROUND yi) * y.stride):
    SEQ xi = 0 FOR width
      SEQ
        VAL REAL64 x IS x.init + ((REAL64 ROUND xi) * x.stride):
        INITIAL COMPLEX z IS [x, y]:
        INITIAL INT count IS 0:
        INITIAL BOOL running IS TRUE:
        WHILE running
          SEQ
            z := [(z[r] * z[r]) - (z[i] * z[i]), 2.0 * (z[r] * z[i])]
            z[r] := z[r] + x
            z[i] := z[i] + y
            IF
              ((z[r] * z[r]) + (z[i] * z[i])) > 4.0
                SEQ
                  data[n] := count
                  running := FALSE
              count = iterations
                SEQ
                  data[n] := -1
                  running := FALSE
              TRUE
                count := count + 1
        n := n + 1
:
--}}}
--{{{ PROC start.pony
PROC start.pony (VAL []BYTE name, VAL INT node.type, RESULT SHARED PONY.NETHANDLE! net.handle)
  MOBILE []BYTE ans.name, app.name, node.name:
  INT node.id, result:
  SEQ
    ans.name := ""
    app.name := "mandelbauer"
    node.name := name
    C.trace ("starting pony")
    pony.startup.snh (PONYC.NETTYPE.TCPIP, ans.name, app.name, node.name, node.type, node.id, net.handle, result)
    C.trace.i ("pony.startup result: ", result)
    ASSERT (result = PONYC.RESULT.STARTUP.OK)
:
--}}}
--{{{  INT FUNCTION hsv.to.rgb
-- Convert HSV to RGB (all values as floats between 0 and 1).
-- Algorithm from http://www.cs.rit.edu/~ncs/color/t_convert.html
INT FUNCTION hsv.to.rgb (VAL REAL32 h, s, v)
  REAL32 r, g, b:
  VALOF
    IF
      s = 0.0
        r, g, b := v, v, v
      TRUE
        INT i:
        REAL32 hh, f, p, q, t:
        SEQ
          hh := h * 6.0
          i := INT TRUNC hh
          f := hh - (REAL32 ROUND i)
          p := v * (1.0 - s)
          q := v * (1.0 - (s * f))
          t := v * (1.0 - (s * (1.0 - f)))
          CASE i
            0
              r, g, b := v, t, p
            1
              r, g, b := q, v, p
            2
              r, g, b := p, v, t
            3
              r, g, b := p, q, v
            4
              r, g, b := t, p, v
            ELSE
              r, g, b := v, p, q
    RESULT (((INT ROUND (r * 255.0)) << 16) \/ ((INT ROUND (g * 255.0)) << 8)) \/ (INT ROUND (b * 255.0))
:
--}}}
--{{{  PROC fake.raster
PROC fake.raster (VAL INT width, height, CHAN RASTER raster.out!, CHAN INT control?)
  SEQ
    RASTER r:
    SEQ
      r := MOBILE [height][width]INT
      raster.out ! r
    INITIAL BOOL running IS TRUE:
    WHILE running
      INT i:
      SEQ
        control ? i
        IF
          i = CTL.SHUTDOWN
            running := FALSE
          TRUE
            SKIP
:
--}}}
--{{{  PROC server
PROC server (VAL []BYTE name, CHAN BYTE out!, VAL OPTIONS options, VAL RENDER.PARAMS params.in)
  SHARED PONY.NETHANDLE! net.handle:
  FARM.REQ.CT? s.req:
  FARM.RESP.CT? s.resp:
  FARM.CT? farm:
  INT result:
  SEQ
    --{{{  Start up
    start.pony (name, PONYC.NODETYPE.MASTERRESET, net.handle)
    CLAIM net.handle
      IF
        options[use.shared]
          SEQ
            pony.alloc.us (net.handle, "req", PONYC.SHARETYPE.SHARED, s.req, result)
            ASSERT (result = PONYC.RESULT.ALLOC.OK)
            pony.alloc.us (net.handle, "resp", PONYC.SHARETYPE.SHARED, s.resp, result)
            ASSERT (result = PONYC.RESULT.ALLOC.OK)
        TRUE
          SEQ
            pony.alloc.us (net.handle, "farm", PONYC.SHARETYPE.SHARED, farm, result)
            ASSERT (result = PONYC.RESULT.ALLOC.OK)
    --}}}
    C.trace ("started")
    SHARED CHAN WORK a.req:
    SHARED CHAN WORK.RESPONSE a.resp:
    CHAN RASTER raster.in, raster.out:
    CHAN SDLEVENT events:
    CHAN INT control:
    CHAN BOOL want.params:
    CHAN RENDER.PARAMS params.out:
    CHAN RENDER.PARAMS frame.sync:
    PAR
      IF
        options[benchmark]
          fake.raster (options[width], options[height], raster.out!, control?)
        TRUE
          #IF DEFINED (USE.SDL)
          SDLRaster ("Mandelbauer", options[width], options[height], 1, raster.in?, raster.out!, events!, control?)
          #ELSE
          STOP
          #ENDIF
      IF
        options[use.shared]
          SKIP
        TRUE
          --{{{  Talk to workers
          SEQ -- FIXME: Having FORKING here causes a segfault
            INITIAL INT i IS 0:
            WHILE TRUE
              WORKER.CT? worker:
              SEQ
                farm[in] ? worker
                C.trace.i ("got worker ", i)
                i := i + 1
                PROC worker.handler (WORKER.CT? worker, SHARED CHAN WORK a.req?, SHARED CHAN WORK.RESPONSE a.resp!)
                  PAR
                    WHILE TRUE
                      WORK work:
                      SEQ
                        CLAIM a.req?
                          a.req ? work
                        worker[req] ! work
                    WHILE TRUE
                      WORK work:
                      MOBILE []INT data:
                      SEQ
                        worker[resp] ? work; data
                        CLAIM a.resp!
                          a.resp ! work; data
                :
                FORK worker.handler (worker, a.req?, a.resp!)
          --}}}
      --{{{  Handle SDL events
      INITIAL REAL64 mouse.x IS 0.0:
      INITIAL REAL64 mouse.y IS 0.0:
      INITIAL INT mouse.buttons IS 0:
      INITIAL RENDER.PARAMS params IS params.in:
      WHILE params[running]
        ALT
          SDLEVENT event:
          events ? event
            CASE event[type]
              --{{{  Mouse event
              EVENT.MOTION, EVENT.BUTTON.PRESS, EVENT.BUTTON.RELEASE
                VAL INT px IS (event[data] >> 20) /\ #FFF:
                VAL INT py IS (event[data] >> 8) /\ #FFF:
                VAL REAL64 scale IS REAL64 ROUND options[height]:
                SEQ
                  mouse.x := ((REAL64 ROUND px) / scale) - 0.5
                  mouse.y := ((REAL64 ROUND py) / scale) - 0.5
                  IF
                    event[type] <> EVENT.MOTION
                      VAL INT button IS 1 << ((event[data] /\ #FF) - 1):
                      CASE event[type]
                        EVENT.BUTTON.PRESS
                          mouse.buttons := mouse.buttons \/ button
                        EVENT.BUTTON.RELEASE
                          mouse.buttons := mouse.buttons /\ (~button)
                    TRUE
                      SKIP
              --}}}
              --{{{  Keyboard event
              EVENT.KEY.PRESS
                SEQ
                  CASE BYTE (event[data] /\ #FF)
                    'u'
                      params[iterations] := params[iterations] + 10
                    'd'
                      params[iterations] := params[iterations] - 10
                    ELSE
                      SKIP
                  IF
                    params[iterations] > Max.iterations
                      params[iterations] := Max.iterations
                    params[iterations] < Min.iterations
                      params[iterations] := Min.iterations
                    TRUE
                      SKIP
              --}}}
              --{{{  Quit event
              EVENT.QUIT
                SEQ
                  control ! CTL.SHUTDOWN
                  params[running] := FALSE
                  BOOL b:
                  want.params ? b
                  params.out ! params
              --}}}
              ELSE
                SKIP
          BOOL b:
          want.params ? b
            --{{{  Update tick from request-sender
            SEQ
              --{{{  Handle zooming
              VAL REAL64 zoom.speed IS 0.9:
              INITIAL REAL64 new.diameter IS params[diameter]:
              SEQ
                IF
                  --{{{  LMB zooms in
                  (mouse.buttons /\ 1) <> 0
                    new.diameter := new.diameter * zoom.speed
                  --}}}
                  --{{{  RMB zooms out
                  (mouse.buttons /\ 4) <> 0
                    new.diameter := new.diameter / zoom.speed
                  --}}}
                  TRUE
                    SKIP
                --{{{  Adjust the centre after zooming so the mouse pointer stays in the same place
                params[x] := params[x] + (mouse.x * (params[diameter] - new.diameter))
                params[y] := params[y] + (mouse.y * (params[diameter] - new.diameter))
                --}}}
                params[diameter] := new.diameter
              --}}}
              params.out ! params
            --}}}
      --}}}
      --{{{  Send requests
      INITIAL RENDER.PARAMS params IS params.in:
      INITIAL INT frame IS 0:
      TIMER tim:
      INITIAL INT last IS 0:
      VAL INT History.size IS 10:
      INITIAL [History.size]REAL32 calc.rates IS [i = 0 FOR History.size | 0.0]:
      WHILE params[running]
        INT t:
        SEQ
          IF
            options[no.frame.sync]
              SKIP
            TRUE
              SEQ
                want.params ! TRUE
                params.out ? params
                frame.sync ! params
          out.string ("Start frame #", 0, out!)
          out.int (frame, 0, out!)
          out.string ("*n  Centre = (", 0, out!)
          out.real64 (params[x], 4, 16, out!)
          out.string (", ", 0, out!)
          out.real64 (params[y], 4, 16, out!)
          out.string (")*n  Diameter = ", 0, out!)
          out.real64 (params[diameter], 4, 16, out!)
          out.string ("*n  Limit = ", 0, out!)
          out.int (params[iterations], 0, out!)
          out ! '*n'
          VAL REAL64 scale IS REAL64 ROUND options[height]:
          VAL REAL64 stride IS (1.0 / scale) * params[diameter]:
          VAL INT xchunk IS options[width] / options[xchunks]:
          VAL INT ychunk IS options[height] / options[ychunks]:
          SEQ py = 0 FOR options[height] / ychunk STEP ychunk
            SEQ px = 0 FOR options[width] / xchunk STEP xchunk
              VAL REAL64 off.x IS ((REAL64 ROUND px) / scale) - 0.5:
              VAL REAL64 off.y IS ((REAL64 ROUND py) / scale) - 0.5:
              VAL REAL64 x IS params[x] + (off.x * params[diameter]):
              VAL REAL64 y IS params[y] + (off.y * params[diameter]):
              VAL WORK work IS [frame, x, px, y, py, stride, xchunk, stride, ychunk, params[iterations]]:
              IF
                options[use.shared]
                  s.req[req] ! work
                TRUE
                  CLAIM a.req!
                    a.req ! work
          tim ? t
          --{{{  Calculate and display speed statistics
          VAL REAL32 frame.time IS (REAL32 ROUND (t MINUS last)) / 1000000.0:
          SEQ
            out.string ("  Frame time = ", 0, out!)
            out.real32 (frame.time, 8, 2, out!)
            out.string (" s*n", 0, out!)
            out.string ("  Frame rate = ", 0, out!)
            out.real32 (1.0 / frame.time, 8, 2, out!)
            out.string (" fps*n", 0, out!)
            VAL REAL32 pixels IS (REAL32 ROUND (options[height] * options[width])):
            VAL REAL32 calc.rate IS pixels / (frame.time * 1000.0):
            SEQ
              out.string ("  Calculation rate = ", 0, out!)
              out.real32 (calc.rate, 8, 2, out!)
              out.string (" kpps*n", 0, out!)
              SEQ i = 1 FOR History.size - 1
                calc.rates[i - 1] := calc.rates[i]
              calc.rates[History.size - 1] := calc.rate
              IF
                frame < History.size
                  out.string ("  Calculation rate mean not available yet*n", 0, out!)
                TRUE
                  INITIAL REAL32 sum IS 0.0:
                  SEQ
                    SEQ i = 0 FOR History.size
                      sum := sum + calc.rates[i]
                    out.string ("  Calculation rate mean over ", 0, out!)
                    out.int (History.size, 0, out!)
                    out.string (" frames = ", 0, out!)
                    out.real32 (sum / (REAL32 ROUND History.size), 8, 2, out!)
                    out.string (" kpps*n", 0, out!)
          --}}}
          last := t
          frame := frame PLUS 1
      --}}}
      --{{{  Receive responses
      INITIAL RENDER.PARAMS params IS params.in:
      INITIAL INT old.iterations IS -1:
      RASTER raster:
      [Max.iterations + 1]INT colours:
      SEQ
        raster.out ? raster
        WHILE params[running]
          WORK work:
          MOBILE []INT data:
          PROC update ()
            IF
              options[benchmark]
                SKIP
              TRUE
                #IF DEFINED (USE.SDL)
                SDLRasterUpdateBuf (0)
                #ELSE
                STOP
                #ENDIF
          :
          SEQ
            IF
              options[no.frame.sync]
                SKIP
              TRUE
                frame.sync ? params
            VAL INT mc IS params[iterations]:
            IF
              old.iterations <> mc
                SEQ
                  SEQ i = 0 FOR mc
                    VAL REAL32 v IS (REAL32 ROUND i) / (REAL32 ROUND mc):
                    colours[i] := hsv.to.rgb (v, 1.0, 1.0)
                  old.iterations := mc
              TRUE
                SKIP
            SEQ i = 0 FOR options[xchunks] * options[ychunks]
              SEQ
                IF
                  options[use.shared]
                    s.resp[resp] ? work ; data
                  TRUE
                    CLAIM a.resp?
                      a.resp ? work ; data
                INITIAL INT n IS 0:
                SEQ y = 0 FOR work[height]
                  SEQ x = 0 FOR work[width]
                    SEQ
                      VAL INT d IS data[n]:
                      INT pixel IS raster[work[y.pixel] + y][work[x.pixel] + x]:
                      IF
                        d = (-1)
                          pixel := #000000
                        TRUE
                          pixel := colours[d]
                      n := n + 1
                IF
                  options[incremental]
                    update ()
                  TRUE
                    SKIP
            IF
              NOT options[incremental]
                update ()
              TRUE
                SKIP
      --}}}
    --{{{  Shut down
    CLAIM net.handle
      pony.shutdown (net.handle)
    --}}}
:
--}}}
--{{{  PROC client
PROC client (VAL []BYTE name, CHAN BYTE in?, CHAN BYTE out!, VAL OPTIONS options)
  SHARED PONY.NETHANDLE! net.handle:
  SHARED FARM.REQ.CT! req:
  SHARED FARM.RESP.CT! resp:
  SHARED FARM.CT! farm:
  WORKER.CT! worker:
  INT result:
  SEQ
    --{{{  Start up
    start.pony (name, PONYC.NODETYPE.SLAVEWAIT, net.handle)
    CLAIM net.handle
      IF
        options[use.shared]
          SEQ
            pony.alloc.sc (net.handle, "req", PONYC.SHARETYPE.UNSHARED, req, result)
            ASSERT (result = PONYC.RESULT.ALLOC.OK)
            pony.alloc.sc (net.handle, "resp", PONYC.SHARETYPE.UNSHARED, resp, result)
            ASSERT (result = PONYC.RESULT.ALLOC.OK)
        TRUE
          SEQ
            pony.alloc.sc (net.handle, "farm", PONYC.SHARETYPE.UNSHARED, farm, result)
            ASSERT (result = PONYC.RESULT.ALLOC.OK)
    --}}}
    --{{{  register worker
    IF
      options[use.shared]
        SKIP
      TRUE
        WORKER.CT? worker.s:
        SEQ
          worker, worker.s := MOBILE WORKER.CT
          CLAIM farm!
            farm[in] ! worker.s
    --}}}
    VAL INT Buf.size IS 1:
    PROTOCOL MAYBE.WORK
      CASE
        work ; WORK
        done
    :
    PROTOCOL MAYBE.DATA
      CASE
        data ; WORK ; MOBILE []INT
        done
    :
    [Buf.size + 1]CHAN MAYBE.WORK buf.in:
    [Buf.size + 1]CHAN MAYBE.DATA buf.out:
    PAR
      --{{{  Read incoming requests
      SEQ
        INITIAL BOOL running IS TRUE:
        WHILE running
          WORK work:
          SEQ
            IF
              options[use.shared]
                CLAIM req
                  req[req] ? work
              TRUE
                SEQ
                  worker[req] ? work
            buf.in[0] ! work ; work
            PRI ALT
              BYTE b:
              (NOT options[no.stop]) & in ? b
                running := FALSE
              TRUE & SKIP
                SKIP
        buf.in[0] ! done
      --}}}
      --{{{  Buffer incoming requests
      PAR i = 0 FOR Buf.size
        INITIAL BOOL running IS TRUE:
        WHILE running
          buf.in[i] ? CASE
            WORK work:
            work ; work
              buf.in[i + 1] ! work ; work
            done
              SEQ
                buf.in[i + 1] ! done
                running := FALSE
      --}}}
      --{{{  Process requests
      SEQ
        IF
          options[use.pri]
            SETPRI (10)
          TRUE
            SKIP
        INITIAL INT num.requests IS 0:
        INITIAL INT num.pixels IS 0:
        INITIAL INT time.used IS 0:
        INITIAL BOOL running IS TRUE:
        WHILE running
          buf.in[Buf.size] ? CASE
            WORK work:
            work ; work
              INT start, end:
              TIMER tim:
              MOBILE []INT data:
              SEQ
                data := MOBILE [work[width] * work[height]]INT
                tim ? start
                IF
                  options[use.c]
                    C.calculate.mandel (work[x], work[y], work[x.stride], work[y.stride], work[width], work[height], work[iterations], data)
                  TRUE
                    calculate.mandel (work[x], work[y], work[x.stride], work[y.stride], work[width], work[height], work[iterations], data)
                tim ? end
                --{{{  Calculate and display speed statistics
                time.used := time.used + (end MINUS start)
                num.pixels := num.pixels + (work[width] * work[height])
                num.requests := num.requests + 1
                IF
                  num.requests = 1000
                    SEQ
                      out.string ("Calculation rate = ", 0, out!)
                      VAL REAL32 kp IS (REAL32 ROUND num.pixels) / 1000.0:
                      VAL REAL32 s IS (REAL32 ROUND time.used) / 1000000.0:
                      out.real32 (kp / s, 8, 2, out!)
                      out.string (" kpps*n", 0, out!)
                      time.used := 0
                      num.pixels := 0
                      num.requests := 0
                  TRUE
                    SKIP
                --}}}
                buf.out[0] ! data ; work ; data
            done
              SEQ
                buf.out[0] ! done
                running := FALSE
      --}}}
      --{{{  Buffer outgoing responses
      PAR i = 0 FOR Buf.size
        INITIAL BOOL running IS TRUE:
        WHILE running
          buf.out[i] ? CASE
            WORK work:
            MOBILE []INT data:
            data ; work ; data
              buf.out[i + 1] ! data ; work ; data
            done
              SEQ
                buf.out[i + 1] ! done
                running := FALSE
      --}}}
      --{{{  Send outgoing responses
      INITIAL BOOL running IS TRUE:
      WHILE running
        buf.out[Buf.size] ? CASE
          WORK work:
          MOBILE []INT data:
          data ; work ; data
            IF
              options[use.shared]
                CLAIM resp
                  resp[resp] ! work ; data
              TRUE
                worker[resp] ! work ; data
          done
            running := FALSE
      --}}}
    --{{{  Shut down
    CLAIM net.handle
      pony.shutdown (net.handle)
    --}}}
:
--}}}
--{{{  FUNCTION parse.real64 (VAL []BYTE s)
BOOL, REAL64 FUNCTION parse.real64 (VAL []BYTE s)
  INT64 a:
  INT64 b:
  BOOL ok:
  VALOF
    INITIAL BOOL found IS FALSE:
    INITIAL BOOL negative IS FALSE:
    INITIAL INT i IS 0:
    SEQ
      a := 0
      b := 1
      ok := TRUE
      IF
        (SIZE s) = 0
          ok := FALSE
        TRUE
          SEQ
            IF
              s[i] = '-'
                SEQ
                  i := i + 1
                  negative := TRUE
              TRUE
                SKIP
            WHILE i < (SIZE s)
              SEQ
                IF
                  (s[i] >= '0') AND (s[i] <= '9')
                    VAL INT64 digit IS (INT64 (s[i] - '0')):
                    SEQ
                      a := (a * 10) + digit
                      IF
                        found
                          b := b * 10
                        TRUE
                          SKIP
                  (s[i] = '.') AND (NOT found)
                    found := TRUE
                  TRUE
                    SEQ
                      ok := FALSE
                      i := (SIZE s)
                i := i + 1
            IF
              negative
                a := (-a)
              TRUE
                SKIP
    RESULT ok, ((REAL64 ROUND a) / (REAL64 ROUND b))
:
--}}}
--{{{  PROC mandelbauer
PROC mandelbauer (CHAN BYTE in?, out!, err!)
  INITIAL MOBILE []BYTE node.name IS "":
  INITIAL BYTE mode IS 's':
  INITIAL OPTIONS options IS init.options:
  INITIAL RENDER.PARAMS params IS init.render.params:
  SEQ
    --{{{  Parse options
    CHAN GETOPT opts:
    PAR
      file.get.options ("n:micbFSpw:h:x:y:I:X:Y:D:s", opts!)
      INITIAL BOOL running IS TRUE:
      WHILE running
        BYTE b:
        MOBILE []BYTE s:
        opts ? CASE
          opt ; b
            CASE b
              'm'
                mode := 'm'
              'b'
                options[benchmark] := TRUE
              'i'
                options[incremental] := TRUE
              'F'
                options[no.frame.sync] := TRUE
              's'
                options[no.stop] := TRUE
              'c'
                options[use.c] := TRUE
              'S'
                options[use.shared] := TRUE
              'p'
                options[use.pri] := TRUE
          opt.arg ; b ; s
            CASE b
              'n'
                node.name := s
              'w'
                BOOL error:
                SEQ
                  STRINGTOINT (error, options[width], s)
                  ASSERT (NOT error)
              'h'
                BOOL error:
                SEQ
                  STRINGTOINT (error, options[height], s)
                  ASSERT (NOT error)
              'x'
                BOOL error:
                SEQ
                  STRINGTOINT (error, options[xchunks], s)
                  ASSERT (NOT error)
              'y'
                BOOL error:
                SEQ
                  STRINGTOINT (error, options[ychunks], s)
                  ASSERT (NOT error)
              'I'
                BOOL error:
                SEQ
                  STRINGTOINT (error, params[iterations], s)
                  ASSERT (NOT error)
                  ASSERT (params[iterations] >= Min.iterations)
                  ASSERT (params[iterations] <= Max.iterations)
              'X'
                BOOL ok:
                SEQ
                  ok, params[x] := parse.real64 (s)
                  ASSERT (ok)
              'Y'
                BOOL ok:
                SEQ
                  ok, params[y] := parse.real64 (s)
                  ASSERT (ok)
              'D'
                BOOL ok:
                SEQ
                  ok, params[diameter] := parse.real64 (s)
                  ASSERT (ok)
          arg ; s
            mode := '?'
          bad ; b
            mode := '?'
          done
            running := FALSE
    --}}}
    CASE mode
      '?'
        out.string ("Usage: mandelbauer [-m] [-i] [-c] [-b] [-s] [-F] [-S] [-p] [-n NODENAME] [-w WIDTH] [-h HEIGHT] [-x XCHUNKS] [-y YCHUNKS] [-I ITERATIONS] [-X X-CENTRE] [-Y Y-CENTRE] [-D DIAMETER]*n", 0, err!)
      'm'
        server (node.name, out!, options, params)
      's'
        client (node.name, in?, out!, options)
:
--}}}

