include, plotscr.hsd

define constant, begin
  # Whether to run Plotscripting-specific (as opposed to HS) benchmarks
  # and others not ported to the benchmarks for other languages
  true, PSBENCH
  # Whether to print all times. Else uses same format as benchmarks for other languages
  false, print all times

  100, NUM RUNS

  # Number of loops to run micro-benchmarks
  1000, MICRO LOOPCOUNT

  # Global variable IDs
  5000, TIMES ARRAY
  99, UNUSED GLOBAL
  100, fire array
  

  1, BENCHMARK NAME STRING
  99, ASSERT EXPRESSION STRING
end

global variable, begin
  1, flames container
  2, testarray
  3, bubbles
end


script, init, begin
  testarray := create container
  resize extra (testarray, MICRO LOOPCOUNT)
end

script, assert failure, begin
  script error(ASSERT EXPRESSION STRING)
end

plotscript, main, begin
  variable (score, score2, loops)

  init
  show string($2 = "Running benchmarks.\nResults are logged to g_debug.txt")
  $2 = ""

  wait (2)
  $2 = "Micro-benchmarks"
  trace (2)
  $2 = ""
  trace (2)

  loops := MICRO LOOPCOUNT

  $1 = "for loop"
  score += run benchmark (@benchmark: for loop, loops)
  $1 = "while loop"
  score += run benchmark (@benchmark: while loop, loops)
  $1 = "continue loop"
  score += run benchmark (@benchmark: continue loop, loops)
  if (PSBENCH) then (
    $1 = "if then"
    score += run benchmark (@benchmark: if then, loops)
    $1 = "if else"
    score += run benchmark (@benchmark: if else, loops)
    $1 = "enter for"
    score += run benchmark (@benchmark: enter for, loops)
    $1 = "double break"
    score += run benchmark (@benchmark: double break, loops)
    $1 = "trivial switch"
    score += run benchmark (@benchmark: trivial switch, loops)
    $1 = "bigger switch"
    score += run benchmark (@benchmark: bigger switch, loops)
    $1 = "one arg overhead"
    score += run benchmark (@benchmark: one arg overhead, loops)
    $1 = "two arg overhead"
    score += run benchmark (@benchmark: two arg overhead, loops)
    $1 = "assignment"
    score += run benchmark (@benchmark: assignment, loops)
  )
  $1 = "addition"
  score += run benchmark (@benchmark: addition, loops)
  $1 = "increment"
  score += run benchmark (@benchmark: increment, loops)
  $1 = "array create delete"
  score += run benchmark (@benchmark: array create delete, loops) / 5
  $1 = "array length"
  score += run benchmark (@benchmark: array length, loops)
  $1 = "array index"
  score += run benchmark (@benchmark: array index, loops)
  if (PSBENCH) then (
    $1 = "array index getextra"
    score += run benchmark (@benchmark: array index getextra, loops)
  )
  if (not(PSBENCH)) then (
    trace($1="array foreach\n N/A\n N/A")
  )
  $1 = "array sum"
  score += run benchmark (@benchmark: array sum, loops)
  $1 = "array append"
  score += run benchmark (@benchmark: array append, 100)
  if (PSBENCH) then (
    $1 = "string assignment"
    score += run benchmark (@benchmark: string assignment, loops)
  )
  $1 = "string append"
  score += run benchmark (@benchmark: string append, loops) / 4

  if (PSBENCH) then (
    $1 = "real string append"
    score += run benchmark (@benchmark: real string append, loops) / 4

    $1 = "sfunctions command overhead"
    score += run benchmark (@benchmark: sfunctions command overhead, loops)
    $1 = "milliseconds"
    score += run benchmark (@benchmark: milliseconds, loops)
    $1 = "one arg command"
    score += run benchmark (@benchmark: one arg command, loops)
    $1 = "one arg command with variable"
    score += run benchmark (@benchmark: one arg command with variable, loops)
    $1 = "sqrt"
    score += run benchmark (@benchmark: sqrt, loops)
    $1 = "more complex one arg command"
    score += run benchmark (@benchmark: more complex one arg command, loops)
    $1 = "slice X"
    score += run benchmark (@benchmark: slice X, loops)
    $1 = "first child"
    score += run benchmark (@benchmark: first child, loops)
    $1 = "creating and deleting slices"
    score2 += run benchmark (@benchmark: creating and deleting slices, loops)
  )
  $1 = "call script"
  score += run benchmark (@benchmark: call script, loops)
  $1 = "call multiarg script"
  score += run benchmark (@benchmark: call multiarg script, loops)
  if (PSBENCH) then (
    $1 = "run script by id"
    score += run benchmark (@benchmark: run script by id, loops)
    $1 = "call and return value"
    score += run benchmark (@benchmark: call and return value, loops)
    $1 = "call and exit returning value"
    score += run benchmark (@benchmark: call and exit returning value, loops)
    $1 = "read global"
    score += run benchmark (@benchmark: read global, loops)
    $1 = "write global"
    score += run benchmark (@benchmark: write global, loops)
    $1 = "read global array"  # Compare to "array index"
    score += run benchmark (@benchmark: read global array, loops)
  )
  $1 = "distance"
  score += run benchmark (@benchmark: distance, loops)
  $1 = "distance squared"
  score += run benchmark (@benchmark: distance squared, loops)
  $1 = "manhatten distance"
  score += run benchmark (@benchmark: manhatten distance, loops)

  $2 = ""
  trace (2)
  $2 = "General benchmarks"
  trace (2)
  $2 = ""
  trace (2)

  # General benchmarks get more weight in the total benchmark score.
  # The 'scores' (running times) of these benchmarks are weighted so that they
  # all contribute roughly the same score at time of writing.

  $1 = "recursive fibonacci"
  score += run benchmark (@benchmark: recursive fibonacci, 1) * 2
  $1 = "fixedmul"
  score += run benchmark (@benchmark: fixedmul, 1)
  $1 = "string iter"
  score += run benchmark (@benchmark: string iter, 1)

  if (PSBENCH) then (
    $1 = "create and delete lotsa slices"
    score2 += run benchmark (@benchmark: create and delete lotsa slices, 1) * 2 / 3
  )
  $1 = "crappy sqrt"
  score += run benchmark (@benchmark: crappy sqrt, 1)
  $1 = "bubble fill"
  score += run benchmark (@benchmark: bubble fill, 40)
  $1 = "bubble sort"
  score += run benchmark (@benchmark: bubble sort, 1, @benchmark: bubble fill)
  if (PSBENCH) then (
    $1 = "bubble sort extra"
    score += run benchmark (@benchmark: bubble sort extra, 1, @benchmark: bubble fill)
    $1 = "faster bubble sort"
    score += run benchmark (@benchmark: faster bubble sort, 1, @benchmark: bubble fill)
    $1 = "eat soap mania"
    score2 += run benchmark (@benchmark: eat soap mania, 1) * 2
    $1 = "xor map"
    score2 += run benchmark (@benchmark: xor map, 1) * 2
    $1 = "DOOM flames recalc"
    score += run benchmark (@benchmark: DOOM flames recalc, 1) * 2
    $1 = "DOOM flames create and destroy"
    score2 += run benchmark (@benchmark: DOOM flames create and destroy, 1) * 2
    $1 = "DOOM flames update"
    DOOM initialise flames
    score += run benchmark (@benchmark: DOOM flames update, 1) * 2
    free slice (flames container)

    # Benchmarks excluded from the score

    # The speed of this would depend on state of numlock, how many joysticks are plugged in,
    # and whether gfx_fb is used
    $1 = "check all keys"
    run benchmark (@benchmark: check all keys, 1)
  )


  $2 = "Benchmark times (lower is faster):"
  if (PSBENCH) then (
    # This score doesn't depend on the speed of implementation of any commands
    $2 + "\nScript interpreter: "
    append number (2, score)
  )
  score += score2
  $2 + "\nTotal time: "
  append number (2, score)
  trace (2)
  show string (2)

  wait (80)
  game over
end

# Given 100*X, append X to 'string' as a decimal
script, append decimal 100, string, number, begin
  append number (string, number / 100)
  $string + "."
  variable (i)
  for (i, 1, 0, -1) do (
    append ascii (string, 48 + (number / (10 ^ i)), mod, 10)
  )
end

script, run benchmark, script id, loops, init func = 0, begin
  variable (i, j, n, time, best, worst, avg, displayval)

  $2 = "Benchmark: "
  2 $+ 1
  show string (2)
  #wait (1)

  for (i, 0, NUM RUNS -- 1) do (
    if (init func) then (run script by id (init func))
    time := microseconds
    run script by id (script id)
    time := microseconds -- time
    write global (TIMES ARRAY + i, time)
  )

  2 $= 1
  if (print all times) then ($2 + " times:")
  trace (2)

  best := 999999
  $2 = ""
  for (i, 0, NUM RUNS -- 1) do (
    time := read global (TIMES ARRAY + i)
    if (print all times) then (
      $2 + " "
      append number (2, time)
    )
    if (time < best) then (best := time)
  )
  if (print all times) then (trace (2))

  if (loops == 1) then (
    # General benchmark
    $2 = " best microseconds per run: "
    append number (2, best)

  ) else (
    # Micro-benchmark
    $2 = " best nanoseconds per loop: "
    append decimal 100 (2, 100000 * best / loops)
  )
  trace (2)

  # remove top half in a really lazy way
  for (i, 1, NUM RUNS / 2) do (
    worst := 0
    for (j, 0, NUM RUNS -- 1) do (
      time := read global (TIMES ARRAY + j)
      if (time > worst) then (worst := time)
    )

    for (j, 0, NUM RUNS -- 1) do (
      time := read global (TIMES ARRAY + j)
      if (time == worst) then (
        write global (TIMES ARRAY + j, 0)
        break
      )
    )
  )

  # average

  for (j, 0, NUM RUNS -- 1) do (
    time := read global (TIMES ARRAY + j)
    if (time > 0) then (
      avg += time * 100
      n += 1
    )
  )
  avg := avg / n

  if (loops == 1) then (
    # General benchmark
    $2 = " average microseconds per run (excl. outliers): "
    displayval := avg
  ) else (
    # Micro-benchmark
    $2 = " average nanoseconds per loop (excl. outliers): "
    displayval := 1000 * avg / loops
  )
  append decimal 100 (2, displayval)
  trace (2)

  if (print all times) then (
    $2 = ""
    trace (2)
  )

  # Return a score (total score is actually just total time spent)
  return (displayval / 100)
end


###################################### MICRO BENCHMARKS #######################################


script, benchmark: for loop, begin
  variable (i)
  for (i, 1, MICRO LOOPCOUNT) do ()
end

script, benchmark: while loop, begin
  variable (i)
  i := MICRO LOOPCOUNT
  while (i) do (i -= 1)
end

script, benchmark: continue loop, begin
  variable (i)
  i := MICRO LOOPCOUNT
  do (
    i -= 1
    if (i) then (continue)
  )
end

# script, benchmark: for loop overhead, begin
#   variable (i)
#   for (i, 0, MICRO LOOPCOUNT) do ()
# end

script, benchmark: if then, begin
  variable (i)
  for (i, 0, MICRO LOOPCOUNT / 10) do (
    if (i) then ()
    if (i) then ()
    if (i) then ()
    if (i) then ()
    if (i) then ()
    if (i) then ()
    if (i) then ()
    if (i) then ()
    if (i) then ()
    if (i) then ()
  )
end

script, benchmark: if else, begin
  variable (i, dummy)
  for (i, 0, MICRO LOOPCOUNT / 10) do (
    if (dummy) then ()
    if (dummy) then ()
    if (dummy) then ()
    if (dummy) then ()
    if (dummy) then ()
    if (dummy) then ()
    if (dummy) then ()
    if (dummy) then ()
    if (dummy) then ()
    if (dummy) then ()
  )
end

script, benchmark: enter for, begin
  variable (i, j)
  for (i, 0, MICRO LOOPCOUNT / 10) do (
    for (j, 0, 0, 1) do ()
    for (j, 0, 0, 1) do ()
    for (j, 0, 0, 1) do ()
    for (j, 0, 0, 1) do ()
    for (j, 0, 0, 1) do ()
    for (j, 0, 0, 1) do ()
    for (j, 0, 0, 1) do ()
    for (j, 0, 0, 1) do ()
    for (j, 0, 0, 1) do ()
    for (j, 0, 0, 1) do ()
  )
end

script, benchmark: double break, begin
  variable (i)
  for (i, 0, MICRO LOOPCOUNT / 10) do (
    do( do(break(2)) )
    do( do(break(2)) )
    do( do(break(2)) )
    do( do(break(2)) )
    do( do(break(2)) )
    do( do(break(2)) )
    do( do(break(2)) )
    do( do(break(2)) )
    do( do(break(2)) )
    do( do(break(2)) )
  )
end

script, benchmark: trivial switch, begin
  variable (i)
  for (i, 0, MICRO LOOPCOUNT / 10) do (
    switch (i) do ( case(0) do() )
    switch (i) do ( case(0) do() )
    switch (i) do ( case(0) do() )
    switch (i) do ( case(0) do() )
    switch (i) do ( case(0) do() )
    switch (i) do ( case(0) do() )
    switch (i) do ( case(0) do() )
    switch (i) do ( case(0) do() )
    switch (i) do ( case(0) do() )
    switch (i) do ( case(0) do() )
  )
end

script, benchmark: bigger switch, begin
  variable (i)
  for (i, 0, MICRO LOOPCOUNT / 10) do (
    switch (i) do ( case(0)do() case(1)do() case(2)do() case(3)do() case(4)do() )
    switch (i) do ( case(0)do() case(1)do() case(2)do() case(3)do() case(4)do() )
    switch (i) do ( case(0)do() case(1)do() case(2)do() case(3)do() case(4)do() )
    switch (i) do ( case(0)do() case(1)do() case(2)do() case(3)do() case(4)do() )
    switch (i) do ( case(0)do() case(1)do() case(2)do() case(3)do() case(4)do() )
    switch (i) do ( case(0)do() case(1)do() case(2)do() case(3)do() case(4)do() )
    switch (i) do ( case(0)do() case(1)do() case(2)do() case(3)do() case(4)do() )
    switch (i) do ( case(0)do() case(1)do() case(2)do() case(3)do() case(4)do() )
    switch (i) do ( case(0)do() case(1)do() case(2)do() case(3)do() case(4)do() )
    switch (i) do ( case(0)do() case(1)do() case(2)do() case(3)do() case(4)do() )
  )
end

# Note this is not a test of actual overhead (difference between two commands
# taking one and two args and not doing anything would be better)
script, benchmark: one arg overhead, begin
  variable (i, x)
  for (i, 0, MICRO LOOPCOUNT / 10) do (
    x
    x
    x
    x
    x
    x
    x
    x
    x
    x
  )
end

script, benchmark: two arg overhead, begin
  variable (i, x)
  for (i, 0, MICRO LOOPCOUNT / 10) do (
    x, 0
    x, 0
    x, 0
    x, 0
    x, 0
    x, 0
    x, 0
    x, 0
    x, 0
    x, 0
  )
end

script, benchmark: assignment, begin
  variable (i, x)
  for (i, 0, MICRO LOOPCOUNT / 10) do (
    x := 0
    x := 0
    x := 0
    x := 0
    x := 0
    x := 0
    x := 0
    x := 0
    x := 0
    x := 0
  )
end

script, benchmark: addition, begin
  variable (i, x, y)
  for (i, 0, MICRO LOOPCOUNT / 10) do (
    x + y
    x + y
    x + y
    x + y
    x + y
    x + y
    x + y
    x + y
    x + y
    x + y
  )
end

script, benchmark: increment, begin
  variable (i, x, y)
  for (i, 0, MICRO LOOPCOUNT / 10) do (
    x += y
    x += y
    x += y
    x += y
    x += y
    x += y
    x += y
    x += y
    x += y
    x += y
  )
end

script, benchmark: array index, begin
  variable (i)
  for (i, 0, MICRO LOOPCOUNT / 10) do (
    get slice extra(testarray, i)
    get slice extra(testarray, i)
    get slice extra(testarray, i)
    get slice extra(testarray, i)
    get slice extra(testarray, i)
    get slice extra(testarray, i)
    get slice extra(testarray, i)
    get slice extra(testarray, i)
    get slice extra(testarray, i)
    get slice extra(testarray, i)
  )
end

script, benchmark: array index getextra, begin
  variable (i)
  for (i, 0, MICRO LOOPCOUNT / 10) do (
    get extra(testarray, i)
    get extra(testarray, i)
    get extra(testarray, i)
    get extra(testarray, i)
    get extra(testarray, i)
    get extra(testarray, i)
    get extra(testarray, i)
    get extra(testarray, i)
    get extra(testarray, i)
    get extra(testarray, i)
  )
end

script, benchmark: array sum, begin
  variable (i, total)
  for (i, 0, MICRO LOOPCOUNT -- 1) do (
    total += get slice extra(testarray, i)
  )
end

script, benchmark: array append, begin
  variable (arr, i)
  arr := create container
  for (i, 0, 100) do (
    append extra(arr, i)
  )
  free slice(arr)
end

script, benchmark: array length, begin
  variable (i)
  for (i, 0, MICRO LOOPCOUNT / 10) do (
    extra length(testarray)
    extra length(testarray)
    extra length(testarray)
    extra length(testarray)
    extra length(testarray)
    extra length(testarray)
    extra length(testarray)
    extra length(testarray)
    extra length(testarray)
    extra length(testarray)
  )
end

# compare to "creating and deleting slices" which is the same except for the resize
script, benchmark: array create delete, begin
  variable (i, sl)
  for (i, 1, MICRO LOOPCOUNT) do (
    sl := create container
    resize extra(sl, 8)
    free slice(sl)
  )
end

script, benchmark: string assignment, begin
  variable (i)
  for (i, 0, MICRO LOOPCOUNT / 10) do (
    $3 = "foo"
    $3 = "foo"
    $3 = "foo"
    $3 = "foo"
    $3 = "foo"
    $3 = "foo"
    $3 = "foo"
    $3 = "foo"
    $3 = "foo"
    $3 = "foo"
  )
end

# This tries to append a string in a way mechanically similar to how appending
# string literals works in other languages
script, benchmark: string append, begin
  variable (i)
  clear string(3)
  for (i, 0, MICRO LOOPCOUNT / 10) do (
    3 $+ $4="a"
    3 $+ $4="a"
    3 $+ $4="a"
    3 $+ $4="a"
    3 $+ $4="a"
    3 $+ $4="a"
    3 $+ $4="a"
    3 $+ $4="a"
    3 $+ $4="a"
    3 $+ $4="a"
  )
  clear string(3)
end

script, benchmark: real string append, begin
  variable (i)
  clear string(3)
  for (i, 0, MICRO LOOPCOUNT / 10) do (
    $3+"a"
    $3+"a"
    $3+"a"
    $3+"a"
    $3+"a"
    $3+"a"
    $3+"a"
    $3+"a"
    $3+"a"
    $3+"a"
  )
  clear string(3)
end

script, benchmark: sfunctions command overhead, begin
  variable (i)
  for (i, 0, MICRO LOOPCOUNT / 10) do (
    # a command with trivial implementation
    current map
    current map
    current map
    current map
    current map
    current map
    current map
    current map
    current map
    current map
  )
end

script, benchmark: milliseconds, begin
  variable (i)
  for (i, 0, MICRO LOOPCOUNT / 10) do (
    milliseconds
    milliseconds
    milliseconds
    milliseconds
    milliseconds
    milliseconds
    milliseconds
    milliseconds
    milliseconds
    milliseconds
  )
end

script, benchmark: one arg command, begin
  variable (i)
  for (i, 0, MICRO LOOPCOUNT / 10) do (
    # a command that does a minimal amount of error checking/processing with its argument
    set money (0)
    set money (0)
    set money (0)
    set money (0)
    set money (0)
    set money (0)
    set money (0)
    set money (0)
    set money (0)
    set money (0)
  )
end

script, benchmark: one arg command with variable, begin
  variable (i, x)
  x := 0
  for (i, 0, MICRO LOOPCOUNT / 10) do (
    # I expect there to be no difference to the above
    set money (x)
    set money (x)
    set money (x)
    set money (x)
    set money (x)
    set money (x)
    set money (x)
    set money (x)
    set money (x)
    set money (x)
  )
end

script, benchmark: sqrt, begin
  variable (i)
  for (i, 0, MICRO LOOPCOUNT / 10) do (
    sqrt (i)
    sqrt (i)
    sqrt (i)
    sqrt (i)
    sqrt (i)
    sqrt (i)
    sqrt (i)
    sqrt (i)
    sqrt (i)
    sqrt (i)
  )
end

script, benchmark: more complex one arg command, begin
  variable (i, sl)
  for (i, 0, MICRO LOOPCOUNT / 10) do (
    item in slot (0)
    item in slot (0)
    item in slot (0)
    item in slot (0)
    item in slot (0)
    item in slot (0)
    item in slot (0)
    item in slot (0)
    item in slot (0)
    item in slot (0)
  )
end

script, benchmark: slice X, begin
  variable (i, sl)
  sl := sprite layer
  for (i, 0, MICRO LOOPCOUNT / 10) do (
    # testing the overhead of a typical slice command
    slice x (sl)
    slice x (sl)
    slice x (sl)
    slice x (sl)
    slice x (sl)
    slice x (sl)
    slice x (sl)
    slice x (sl)
    slice x (sl)
    slice x (sl)
  )
end

script, benchmark: first child, begin
  variable (i, sl)
  sl := sprite layer
  for (i, 0, MICRO LOOPCOUNT / 10) do (
    # testing the overhead of a slice command returning a slice handle
    first child (sl)
    first child (sl)
    first child (sl)
    first child (sl)
    first child (sl)
    first child (sl)
    first child (sl)
    first child (sl)
    first child (sl)
    first child (sl)
  )
end

script, benchmark: creating and deleting slices, begin
  variable (i, sl)
  for (i, 1, MICRO LOOPCOUNT) do (
    # testing the overhead of a slice command returning a slice handle
    sl := create container (0, 0)
    free slice (sl)
  )
end

script, empty script, begin
end

script, empty multiarg script, a, b, c, d, begin
end

script, return value, begin
  return (0)
end

script, exit return value, begin
  exit returning (0)
end

script, benchmark: call script, begin
  variable (i)
  for (i, 0, MICRO LOOPCOUNT / 10) do (
    empty script
    empty script
    empty script
    empty script
    empty script
    empty script
    empty script
    empty script
    empty script
    empty script
  )
end

script, benchmark: call multiarg script, begin
  variable (i)
  for (i, 0, MICRO LOOPCOUNT / 10) do (
    empty multiarg script (i, i, i, i)
    empty multiarg script (i, i, i, i)
    empty multiarg script (i, i, i, i)
    empty multiarg script (i, i, i, i)
    empty multiarg script (i, i, i, i)
    empty multiarg script (i, i, i, i)
    empty multiarg script (i, i, i, i)
    empty multiarg script (i, i, i, i)
    empty multiarg script (i, i, i, i)
    empty multiarg script (i, i, i, i)
  )
end

script, benchmark: run script by id, begin
  variable (i)
  for (i, 0, MICRO LOOPCOUNT / 10) do (
    run script by id (@empty script)
    run script by id (@empty script)
    run script by id (@empty script)
    run script by id (@empty script)
    run script by id (@empty script)
    run script by id (@empty script)
    run script by id (@empty script)
    run script by id (@empty script)
    run script by id (@empty script)
    run script by id (@empty script)
  )
end

script, benchmark: call and return value, begin
  variable (i)
  for (i, 0, MICRO LOOPCOUNT / 10) do (
    return value
    return value
    return value
    return value
    return value
    return value
    return value
    return value
    return value
    return value
  )
end

script, benchmark: call and exit returning value, begin
  variable (i)
  for (i, 0, MICRO LOOPCOUNT / 10) do (
    exit return value
    exit return value
    exit return value
    exit return value
    exit return value
    exit return value
    exit return value
    exit return value
    exit return value
    exit return value
  )
end

script, benchmark: read global, begin
  variable (i)
  for (i, 0, MICRO LOOPCOUNT / 10) do (
    read global (0)
    read global (0)
    read global (0)
    read global (0)
    read global (0)
    read global (0)
    read global (0)
    read global (0)
    read global (0)
    read global (0)
  )
end

# Comparable to "array index"
script, benchmark: read global array, begin
  variable (i, array)
  array := 0
  for (i, 0, MICRO LOOPCOUNT / 10) do (
    read global (array + i)
    read global (array + i)
    read global (array + i)
    read global (array + i)
    read global (array + i)
    read global (array + i)
    read global (array + i)
    read global (array + i)
    read global (array + i)
    read global (array + i)
  )
end


script, benchmark: write global, begin
  variable (i)
  for (i, 0, MICRO LOOPCOUNT / 10) do (
    write global (UNUSED GLOBAL, 0)
    write global (UNUSED GLOBAL, 0)
    write global (UNUSED GLOBAL, 0)
    write global (UNUSED GLOBAL, 0)
    write global (UNUSED GLOBAL, 0)
    write global (UNUSED GLOBAL, 0)
    write global (UNUSED GLOBAL, 0)
    write global (UNUSED GLOBAL, 0)
    write global (UNUSED GLOBAL, 0)
    write global (UNUSED GLOBAL, 0)
  )
end

script, benchmark: distance, begin
  variable (i, x, y, res)
  x := 300, y := 400
  for (i, 0, MICRO LOOPCOUNT / 10) do (
    res := sqrt((i -- x)^2 + (i -- y)^2)
    res := sqrt((i -- x)^2 + (i -- y)^2)
    res := sqrt((i -- x)^2 + (i -- y)^2)
    res := sqrt((i -- x)^2 + (i -- y)^2)
    res := sqrt((i -- x)^2 + (i -- y)^2)
    res := sqrt((i -- x)^2 + (i -- y)^2)
    res := sqrt((i -- x)^2 + (i -- y)^2)
    res := sqrt((i -- x)^2 + (i -- y)^2)
    res := sqrt((i -- x)^2 + (i -- y)^2)
    res := sqrt((i -- x)^2 + (i -- y)^2)
  )
end

script, benchmark: distance squared, begin
  variable (i, x, y, res)
  x := 300, y := 400
  for (i, 0, MICRO LOOPCOUNT / 10) do (
    res := (i -- x)^2 + (i -- y)^2
    res := (i -- x)^2 + (i -- y)^2
    res := (i -- x)^2 + (i -- y)^2
    res := (i -- x)^2 + (i -- y)^2
    res := (i -- x)^2 + (i -- y)^2
    res := (i -- x)^2 + (i -- y)^2
    res := (i -- x)^2 + (i -- y)^2
    res := (i -- x)^2 + (i -- y)^2
    res := (i -- x)^2 + (i -- y)^2
    res := (i -- x)^2 + (i -- y)^2
  )
end

script, benchmark: manhatten distance, begin
  variable (i, x, y, res)
  x := 300, y := 400
  for (i, 0, MICRO LOOPCOUNT / 10) do (
    res := abs(i -- x) + abs(i -- y)
    res := abs(i -- x) + abs(i -- y)
    res := abs(i -- x) + abs(i -- y)
    res := abs(i -- x) + abs(i -- y)
    res := abs(i -- x) + abs(i -- y)
    res := abs(i -- x) + abs(i -- y)
    res := abs(i -- x) + abs(i -- y)
    res := abs(i -- x) + abs(i -- y)
    res := abs(i -- x) + abs(i -- y)
    res := abs(i -- x) + abs(i -- y)
  )
end


###################################### GENERAL BENCHMARKS #######################################


script, fibonacci, n, begin
  if (n <= 1) then (return (1))
  else (
    return (fibonacci (n -- 1) + fibonacci (n -- 2))
  )
end

script, benchmark: recursive fibonacci, begin
  fibonacci (14)
end

#----

# a and b are 16 bit fixed point numbers, where b >= 1.0
# Return a*b
script, FixedMul, a, b (
  if (a >= 0) then (
    return((a / 0x10000) * b + ((a, and, 0xffff) * b + 0x8000) / 0x10000 )
  ) else (
    #trace value( (a / 0x10000), (a / 0x10000) * b,  (a, and, 0xffff))
    # Recall HS rounds to zero
    # fixme: not sure the rounding here is correct
    return((a / 0x10000) * b -- (((0 -- a), and, 0xffff) * b + 0x8000) / 0x10000 )
  )
)

script, benchmark: fixedmul, begin
  variable (i, j, total)
  for (i, -100 000, 100 000, 8000) do (
    for (j, -100 000, 100 000, 8000) do (
      total += FixedMul(i, j)
    )
  )
end

#----

script, benchmark: string iter, begin
  variable (it, i, ch, words, hash)
  for (it, 1, 8) do (
    $5="The quick onyx goblin jumps over the lazy dwarf. Bright vixens jump, lazy fowl quack. Amazingly few discotheques provide jukeboxes."
    words := 1
    hash := 0
    for (i, 1, string length(5)) do (
      ch := ascii from string(5, i)
      if (ch == 32) then (words += 1)
      hash += words * ch
    )
  )
end

#----

script, benchmark: create and delete lotsa slices, begin
  variable (i, parent, sl)
  parent := create container
  for (i, 0, 2000) do (
    set parent(create container, parent)
    if (i, mod, 5) else (
      free slice(first child(parent))
    )
  )
  free slice(parent)
end


#------------------- some scripts from Don't Eat Soap -----------------------------

define constant(2, bubble push speed)

script, get force x, sl, begin
  exit returning(get force(sl, x axis))
end

script, get force y, sl, begin
  exit returning(get force(sl, y axis))
end

script, set force x, sl, n, sign_=1, begin
  set force(sl, x axis, n, sign_)
end

script, set force y, sl, n, sign_=1, begin
  set force(sl, y axis, n, sign_)
end

script, set force, sl, axis, value, sign_=1, begin
  set slice extra(sl, get force extra slot(axis), value * sign_)
end

script, get force, sl, axis, begin
  exit returning(get slice extra(sl, get force extra slot(axis)))
end

script, get force extra slot, axis, begin
  switch(axis) do(
    case(x axis) do(exit returning(extra 0))
    case(y axis) do(exit returning(extra 1))
  )
end

script, sgn, n, begin
  if(n >> 0) then(exit returning(1))
  if(n << 0) then(exit returning(-1))
  exit returning(0)
end

script, push bubble away, who, bub, begin
  variable(xdiff, ydiff)
  xdiff := slice edge x(bub, edge:center) -- slice edge x(who, edge:center)
  ydiff := slice edge y(bub, edge:center) -- slice edge y(who, edge:center)
  set force x(bub, get force x(bub) + bubble push speed * sgn(xdiff))
  set force y(bub, get force y(bub) + bubble push speed * sgn(ydiff))
end

#------------------------------------------------------------------------

# Test lots of calls of tiny scripts
script, benchmark: eat soap mania, begin
  variable (i, sl)
  sl := create container
  for (i, 0, 60) do (
    push bubble away (sl, sl)
  )
end

script, benchmark: xor map, begin
  variable (i, x, y, layer)
  #for (i, 0, 29) do (
    for (y, 0, 15) do (
      for (x, 0, 15) do (
        for (layer, 0, 2) do (
          write map block (x, y, (255, xor, read map block (x, y, layer)), layer)
        )
        write pass block (x, y, (255, xor, read pass block (x, y)))
      )
    )
  #)
end

script, crappy sqrt, fi, begin
  variable (start, divi, approx)
  approx := -1
  if (fi >= 32581) then (return (181)) else, begin  #Prevent overflows

    if (fi << 100) then (start := 0, divi := start ^ 2) else (

      if (fi >= 22500) then (start := 150) else (
        if (fi >= 14400) then (start := 120) else (
          if (fi >= 8100) then (start := 90) else (
            if (fi >= 4225) then (start := 65) else (
              if (fi >= 1600) then (start := 40) else (
                if (fi >= 900) then (start := 30) else (
                  if (fi >= 400) then (start := 20) else (start := 10)
      ))))))
      divi := start ^ 2
      if (fi / 3 >> divi / 2) then (start := (start / 5) * 6, divi := start ^ 2)
      if (fi / 4 >> divi / 3) then (start := (start / 7) * 8, divi := start ^ 2)
    )
    while (approx == -1) do, begin
      if (divi >= fi) then (approx := start) else (
        increment (start)
        divi := start ^ 2
      )
    end
    if (divi == fi) then (return (approx)) else, begin
      if ((divi -- approx) == fi) then (return (approx -- 1)) else (return (fi / approx + 1))
    end
  end
end

# Test flow control
script, benchmark: crappy sqrt, begin
  variable (i)
  for (i, 0, 80) do (crappy sqrt (i))
end

script, benchmark: bubble fill, begin
  variable (i)
  if (bubbles) then (free slice (bubbles))
  bubbles := create container
  resize extra (bubbles, 40)
  for (i, 0, extra length(bubbles) -- 1) do (
    set slice extra (bubbles, i, (24461 * i) ,mod, 32767)
  )
end

# Version using "get/set slice extra"
script, benchmark: bubble sort, begin
  variable (size, i, j, tmp)
  size := extra length (bubbles)
  for (i, 1, size -- 1) do (
    for (j, 0, i -- 1) do (
      # This could be written with fewer function calls (see faster bubble
      # sort), but the intent is to test the speed of array extra data access
      if (get slice extra(bubbles, j) > get slice extra(bubbles, i)) then (
        tmp := get slice extra(bubbles, j)
        set slice extra (bubbles, j, get slice extra(bubbles, i))
        set slice extra (bubbles, i, tmp)
      )
    )
  )
  # variable(total)
  # for (i, 0, size -- 1) do (
  #   total += get slice extra (bubbles, i)
  #   if (i > 0) then (
  #     assert (get slice extra (bubbles, i--1) <= get slice extra (bubbles, i))
  #   )
  # )
  # trace value of(total)
  # assert(total == 867629)
end

# Version using "get/set extra"
script, benchmark: bubble sort extra, begin
  variable (size, i, j, tmp)
  size := extra length (bubbles)
  for (i, 1, size -- 1) do (
    for (j, 0, i -- 1) do (
      if (get extra(bubbles, j) > get extra(bubbles, i)) then (
        tmp := get extra(bubbles, j)
        set extra (bubbles, j, get extra(bubbles, i))
        set extra (bubbles, i, tmp)
      )
    )
  )
end

# This is actually hardly faster despite the swap condition being true ~65% of the time
script, benchmark: faster bubble sort, begin
  variable (size, i, j)
  size := extra length (bubbles)
  for (i, 1, size -- 1) do (
    for (j, 0, i -- 1) do (
      variable (bi, bj)
      if ((bj := get slice extra(bubbles, j)) > (bi := get slice extra(bubbles, i))) then (
        set slice extra (bubbles, j, bi)
        set slice extra (bubbles, i, bj)
      )
    )
  )
end



#------------------- some scripts from DoomRPG (stripped down) -----------------------------

define constant, begin
  # Starting ID of a 2D array of FIRE HEIGHT * FIRE WIDTH global variables.
  # Cells from bottom to top, left to right. Each contains a value from 0 to 255
  #300, fire array

  3, CELL WIDTH
  3, CELL HEIGHT
  107, FIRE WIDTH
  5, FIRE HEIGHT
  15, FIELD HEIGHT
  383, DECAY
  12, COOLING MIN MIN
  40, COOLING MIN MAX
  12, COOLING RANGE
end

script, DOOM initialise flames, begin
  variable (x, y, i, sl)
  flames container := create container (320, FIELD HEIGHT)
  set slice visible (flames container, false)

  # create the "pixel" slices
  for (i, 0, FIRE HEIGHT -- 1) do (
    for (x, 0, 320 -- 1, CELL WIDTH) do (
      y := FIELD HEIGHT -- i * CELL HEIGHT
      sl := load border sprite (0)
      set parent (sl, flames container)
      put slice (sl, x, y)
    )
  )
end

script, DOOM flames recalc, begin
  variable (xi, yi, val, ptr, ptr below, new val, low limit)
  for (yi, 1, FIRE HEIGHT -- 1) do (
    ptr := fire array + yi * FIRE WIDTH + 1
    ptr below := ptr -- FIRE WIDTH

    low limit := COOLING MIN MIN + (COOLING MIN MAX -- COOLING MIN MIN) * yi / (FIREHEIGHT -- 1)
      
    for (xi, 1, FIRE WIDTH -- 2) do (
      write global (ptr, 
          67 * (2 * (read global(ptr) + read global(ptr below)) + read global(ptr below -- 1) + read global(ptr below + 1)
                 -- random(low limit, low limit + COOLING RANGE)
                ) / (DECAY)
            )
      ptr += 1
      ptr below += 1
    )
  )
end

script, DOOM flames update, begin
  # update slices (bottom to top)
  variable (sl, ptr, i, x, y, val)
  sl := slice child (flames container, FIRE WIDTH)   #skip first row
  ptr := fire array + FIRE WIDTH
  for (i, FIRE WIDTH, FIRE WIDTH * FIRE HEIGHT -- 1) do (
    y := FIREHEIGHT -- i / FIRE WIDTH
    x := i, mod, FIRE WIDTH
    val := read global (ptr) 
 
    if (val >= 16 * 16) then (
      set sprite frame (sl, 0)
    ) else (
      val += read map block (x, y) *60/ 100
      set sprite frame (sl, 15 -- (abs(val) + val) / (16 * 2))
    )
    
    ptr += 1
    sl := next sibling (sl)
  )
end

#------------------------------------------------------------------------

# Test reading and writing globals with quite a bit of math
script, benchmark: DOOM flames recalc, begin
  variable (i)
  #for (i, 0, 49) do (
    DOOM flames recalc
  #)
end

# Test creating and deleting lots of slices
script, benchmark: DOOM flames create and destroy, begin
  DOOM initialise flames
  free slice (flames container)
end

# Test iterating over slices, reading globals and map blocks, and "set sprite frame", with some math/logic
script, benchmark: DOOM flames update, begin
  variable (i)
  #for (i, 0, 49) do (
    DOOM flames update
  #)
end

#------------------------------------------------------------------------

script, benchmark: check all keys, begin
  variable (i)
  for (i, cancel key, joy: button 32) do (keyval(i))
end
