ScalaDays 2014 - Reactive Scala 3D Game Engine

3,797 views

Published on

Slides for the Reactive 3D Game Engine presented at ScalaDays 2014.

Shows the demo of the 3D engine, followed by the description of the reactive 3D game engine - how reactive dependencies between input, time and game logic are expressed, how to deal with GC issues, how to model game state using Reactive Collections.

0 Comments
15 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
3,797
On SlideShare
0
From Embeds
0
Number of Embeds
206
Actions
Shares
0
Downloads
52
Comments
0
Likes
15
Embeds 0
No embeds

No notes for slide

ScalaDays 2014 - Reactive Scala 3D Game Engine

  1. 1. 1 A Reactive 3D Game Engine in Scala Aleksandar Prokopec @_axel22_
  2. 2. 2 What’s a game engine?
  3. 3. 3 Simulation
  4. 4. 4 Real-time simulation
  5. 5. 5 15 ms Real-time simulation
  6. 6. 6 Demo first! http://youtu.be/pRCzSRhifLs
  7. 7. 7 Input Simulator Interaction Renderer
  8. 8. Reactive Collections http://reactive-collections.com 8
  9. 9. 9 Reactive values
  10. 10. Reactive[T] 10
  11. 11. val ticks: Reactive[Long] 11 ticks 1 1 2 2 3 3 4 4 60 60 61 61
  12. 12. ticks onEvent { x => log.debug(s”tick no.$x”) } 12 1 2 3 4 60 61 tick no.1 tick no.2 tick no.3 tick no.4 tick no.60 tick no.61 ...
  13. 13. ticks foreach { x => log.debug(s”tick no.$x”) } 13 1 2 3 4 60 61
  14. 14. 14 for (x <- ticks) { log.debug(s”tick no.$x”) }
  15. 15. 15 Reactive combinators
  16. 16. for (x <- ticks) yield { x / 60 } 16
  17. 17. val seconds: Reactive[Long] = for (x <- ticks) yield { x / 60 } 17
  18. 18. 6061 val seconds: Reactive[Long] = for (x <- ticks) yield { x / 60 } 18 ticks 1 1 2 2 3 3 60 61 seconds 0 0 0 1 1 ticks seconds 00011
  19. 19. val days: Reactive[Long] = seconds.map(_ / 86400) 19
  20. 20. val days: Reactive[Long] = seconds.map(_ / 86400) val secondsToday = 20
  21. 21. val days: Reactive[Long] = seconds.map(_ / 86400) val secondsToday = (seconds zip days) { (s, d) => s – d * 86400 } 21
  22. 22. val days: Reactive[Long] = seconds.map(_ / 86400) val secondsToday = (seconds zip days) { _ – _ * 86400 } 22 seconds days secondsToday
  23. 23. val angle = secondsInDay.map(angleFunc) 23
  24. 24. val angle = secondsInDay.map(angleFunc) val light = secondsInDay.map(lightFunc) 24
  25. 25. 25 https://www.youtube.com/watch?v=5g7DvNEs6K8&feature=youtu.be Preview
  26. 26. 26 val rotate = keys a ↓shift ↓ a ↑ shift ↑pgup ↓ pgup ↑keys
  27. 27. 27 val rotate = keys.filter(_ == PAGEUP) a ↓shift ↓ a ↑ shift ↑pgup ↓ pgup ↑keys pgup ↓ pgup ↑filter
  28. 28. 28 val rotate = keys.filter(_ == PAGEUP) .map(_.down) a ↓shift ↓ a ↑ shift ↑pgup ↓ pgup ↑keys pgup ↓ pgup ↑filter true falsemap
  29. 29. 29 if (rotate()) viewAngle += 1 true falsemap
  30. 30. 30 Signals
  31. 31. 31 Reactives are discrete
  32. 32. 32 Signals are continuous
  33. 33. 33 trait Signal[T] extends Reactive[T] { def apply(): T }
  34. 34. 34 val rotate = keys.filter(_ == PAGEUP) .map(_.down) .signal(false) true falsemap signal
  35. 35. 35 val rotate: Signal[Boolean] = keys.filter(_ == PAGEUP) .map(_.down) .signal(false) true falsemap signal
  36. 36. 36 val rotate: Signal[Boolean] val ticks: Reactive[Long] ticks
  37. 37. 37 val rotate: Signal[Boolean] val ticks: Reactive[Long] ticks rotate
  38. 38. 38 val rotate: Signal[Boolean] val ticks: Reactive[Long] val viewAngle: Signal[Double] = ticks rotate viewAngle
  39. 39. 39 List(1, 2, 3).scanLeft(0)(_ + _)
  40. 40. 40 List(1, 2, 3).scanLeft(0)(_ + _) → List(0, 1, 3, 6)
  41. 41. 41 def scanLeft[S](z: S)(f: (S, T) => S) : List[S]
  42. 42. 42 def scanLeft[S](z: S)(f: (S, T) => S) : List[S] def scanPast[S](z: S)(f: (S, T) => S) : Signal[S]
  43. 43. 43 val rotate: Signal[Boolean] val ticks: Reactive[Long] val viewAngle: Signal[Double] = ticks.scanPast(0.0) ticks rotate viewAngle
  44. 44. 44 val rotate: Signal[Boolean] val ticks: Reactive[Long] val viewAngle: Signal[Double] = ticks.scanPast(0.0) { (a, _) => if (rotate()) a + 1 else a } ticks rotate viewAngle
  45. 45. 45 http://youtu.be/blG95W5uWQ8 Preview
  46. 46. 46 val velocity = ticks.scanPast(0.0) { (v, _) => } val viewAngle =
  47. 47. 47 val velocity = ticks.scanPast(0.0) { (v, _) => if (rotate()) v + 1 } val viewAngle =
  48. 48. 48 val velocity = ticks.scanPast(0.0) { (v, _) => if (rotate()) v + 1 else v – 0.5 } val viewAngle =
  49. 49. 49 val velocity = ticks.scanPast(0.0) { (v, _) => if (rotate()) v + 1 else v – 0.5 } val viewAngle = velocity.scanPast(0.0)(_ + _)
  50. 50. 50 http://youtu.be/NMVhirZLWmA Preview
  51. 51. 51 Higher-order reactive values
  52. 52. 52 (T => S) => (List[S] => List[T])
  53. 53. 53 (T => S) => (List[S] => List[T]) Reactive[Reactive[S]]
  54. 54. 54 val mids = mouse .filter(_.button == MIDDLE) mids ↓ ↓ ↓↑ ↑ ↑
  55. 55. 55 val mids = mouse .filter(_.button == MIDDLE) val up = mids.filter(!_.down) val down = mids.filter(_.down) mids ↓ ↓ ↓ up down ↓ ↓ ↓ ↑ ↑ ↑ ↑ ↑ ↑
  56. 56. 56 val mids = mouse .filter(_.button == MIDDLE) val up = mids.filter(!_.down) val down = mids.filter(_.down) val drags = down .map(_ => mouse) up down ↓ ↓ ↓ ↑ ↑ ↑
  57. 57. 57 val mids = mouse .filter(_.button == MIDDLE) val up = mids.filter(!_.down) val down = mids.filter(_.down) val drags = down .map(_ => mouse) up down ↓ ↓ ↓ ↑ ↑ ↑ Reactive[Reactive[MouseEvent]]
  58. 58. 58 val mids = mouse .filter(_.button == MIDDLE) val up = mids.filter(!_.down) val down = mids.filter(_.down) val drags = down .map(_ => mouse) up down ↓ ↓ ↓ ↑ ↑ ↑ drags
  59. 59. drags up ↑ ↑ ↑ 59 val mids = mouse .filter(_.button == MIDDLE) val up = mids.filter(!_.down) val down = mids.filter(_.down) val drags = down .map(_ => mouse.until(up)) down ↓ ↓ ↓ mouse.until(up)
  60. 60. 60 val mids = mouse .filter(_.button == MIDDLE) val up = mids.filter(!_.down) val down = mids.filter(_.down) val drags = down .map(_ => mouse.until(up)) down ↓ ↓ ↓ up ↑ ↑ ↑ drags
  61. 61. 61 val drags = down .map(_ => mouse.until(up)) .map(_.map(_.xy)) drags 1, 1 2, 3 3, 5 4, 6 6, 9 9, 9
  62. 62. 62 val drags = down .map(_ => mouse.until(up)) .map(_.map(_.xy)) .map(_.diffPast(_.xy - _.xy)) drags 0, 0 1, 2 1, 2 0, 0 2, 3 0, 0
  63. 63. 63 val drags = down .map(_ => mouse.until(up)) .map(_.map(_.xy)) .map(_.diffPast(_.xy - _.xy)) .concat() drags 0, 0 1, 2 1, 2 0, 0 2, 3 0, 0
  64. 64. 64 val drags = down .map(_ => mouse.until(up)) .map(_.map(_.xy)) .map(_.diffPast(_.xy - _.xy)) .concat() val pos = drags.scanPast((0, 0))(_ + _) drags 0, 0 1, 2 1, 2 0, 0 2, 3 0, 0 pos 0, 0 1, 2 2, 4 2, 4 4, 7 4, 7
  65. 65. 65 http://youtu.be/RsMSZ7OH2fo Preview However, a lot of object allocations lead to GC issues.
  66. 66. 66 Reactive mutators
  67. 67. 67 class Matrix { def apply(x: Int, y: Int): Double def update(x: Int, y: Int, v: Double) }
  68. 68. 68 val screenMat: Signal[Matrix] = (projMat zip viewMat)(_ * _) val invScreenMat = screenMat.map(_.inverse)
  69. 69. 69 Reactive[immutable.Matrix[T]]
  70. 70. 70 val screenMat: Signal[Matrix] = (projMat zip viewMat)(_ * _) val invScreenMat = screenMat.map(_.inverse) (4*4*8 + 16 + 16)*4*100 = 64 kb/s
  71. 71. 71 val screenMat = Mutable(new Matrix) (projMat, viewMat).mutate(screenMat) { (p, v) => screenMat().assignMul(p, v) } val invScreenMat = Mutable(new Matrix) screenMat.mutate(invScreenMat) { m => invScreenMat().assignInv(m) }
  72. 72. 72 Reactive collections
  73. 73. 73 http://youtu.be/ebbrAHNsexc Preview How do we model that a character is selected?
  74. 74. 74 val selected: Reactive[Set[Character]]
  75. 75. 75 val selected: ReactSet[Character]
  76. 76. 76 trait ReactSet[T] extends ReactContainer[T] { def apply(x: T): Boolean }
  77. 77. 77 trait ReactContainer[T] { def inserts: Reactive[T] def removes: Reactive[T] }
  78. 78. 78 A reactive collection is a pair of reactive values
  79. 79. 79 val selected = new ReactHashSet[Character]
  80. 80. 80
  81. 81. 81 val selected = new ReactHashSet[Character] val decorations = selected .map(c => (c, decoFor(c)))
  82. 82. 82
  83. 83. 83 class ReactContainer[T] { self => def inserts: Reactive[T] def removes: Reactive[T] def map[S](f: T => S) = new ReactContainer[S] { def inserts: Reactive[T] = self.inserts.map(f) def removes: Reactive[T] = self.removes.map(f) } }
  84. 84. 84 val selected = new ReactHashSet[Character] val decorations = selected .map(c => (c, decoFor(c))) .to[ReactHashMap]
  85. 85. 85 val selected = new ReactHashSet[Character] val decorations = selected .map(c => (c, decoFor(c))) .to[ReactHashMap]
  86. 86. 86 val selected = new ReactHashSet[Character] val decorations = selected .map(c => (c, decoFor(c))) .to[ReactHashMap]
  87. 87. 87 val selected = new ReactHashSet[Character] val decorations = selected .map(c => (c, decoFor(c))) .to[ReactHashMap]
  88. 88. 88 val selected = new ReactHashSet[Character] val decorations = selected .map(c => (c, decoFor(c))) .react.to[ReactHashMap]
  89. 89. 89 val selected = new ReactHashSet[Character] val decorations = selected .map(c => (c, decoFor(c))) .react.to[ReactHashMap]
  90. 90. 90 val selected = new ReactHashSet[Character] val decorations = selected .map(c => (c, decoFor(c))) .react.to[ReactHashMap]
  91. 91. 91 val selected = new ReactHashSet[Character] val decorations = selected .map(c => (c, decoFor(c))) .react.to[ReactHashMap]
  92. 92. 92 • reactive mutators • reactive collections • @specialized • Scala Macros • shipping computations to the GPU
  93. 93. 93 http://storm-enroute.com/macrogl/ MacroGL
  94. 94. 94 https://www.youtube.com/watch?v=UHCeXdxkx70 GC Preview
  95. 95. 95 Is Scala Ready?
  96. 96. 96 YES!
  97. 97. 97 Thank you!

×