Want to dive even deeper?

Take the course Stairway to Scala Applied Training - 3 by Bill Venners and Dick Wall and become an expert!
Stairway to Scala Applied Training - 3
by Bill Venners , Dick Wall

Check it out!
You're watching a preview of this video, click the button on the left to puchase the full version from Richard Warburton's Channel.

Experiment: A Reactive 3D Game Engine in Scala

Most modern 3D game engines are written close to the metal in C++ to achieve smooth performance and stunning. Managed languages and runtimes are usually avoided for this task since they incur garbage collection lags and other performance penalties.

We decided to put this conventional wisdom to test with an experiment - we used an Rx-style reactive programming framework enriched with reactive collections and isolates in unison with a high-level OpenGL framework to build a modern 3D engine.

Game engines are traditionally written in low-level imperative style to achieve optimal performance. Such code can be hard to understand and maintain - the uprising reactive programming is much more natural for writing games, since game engines are in essence discrete event simulations. However, reactive programming comes with performance penalties that we overcome using Scala Specialization, inlining and efficient reactive data containers. Similarly, the OpenGL API exposes a plethora of low-level routines unfit for large scale development - a more structured approach to graphics programming with higher level programming abstractions is desired, but yields a higher performance cost. Through the use of Scala Macros we eliminate these inefficiencies while in the same time retaining the advantages of a structured graphics programming framework.

Result? A high-throughput reactive 3D real-time game engine achieving smooth 60FPS on modern hardware with high polygon counts, texture blending, GPU-based object instancing, and effects like ambient occlusion, shadow mapping and image filtering.

No need to spread Fear, Uncertainty and Doubt about performance anymore - Scala is ready for modern real-time game engines, delivering great performance with the convenience of the modern managed language.


Published on
  • 4.130
  • 12
  • 0
  • 13
  • 0
  • A Reactive 3D Game Engine in Scala Aleksandar Prokopec @_axel22_
  • Parallel Collections ScalaMeter ScalaBlitz
  • What’s a game engine?
  • Simulation
  • Real-time simulation
  • Real-time simulation 15 ms
  • Demo first!
  • Renderer Interaction Input Simulator
  • Reactive Collections http://reactive-collections.com
  • Reactive mutators
  • Reactive[T]
  • 1 2 3 4 ticks 60 61 60 4 3 2 1 val ticks: Reactive[Long] 61
  • 1 2 3 4 tick no.1 tick no.2 tick no.3 tick no.4 ... tick no.60 tick no.61 ticks onEvent { x => log.debug(s”tick no.$x”) } 60 61
  • 1 2 3 4 ticks foreach { x => log.debug(s”tick no.$x”) } 60 61
  • for (x <- ticks) { log.debug(s”tick no.$x”) }
  • Reactive combinators
  • for (x <- ticks) yield { x / 60 }
  • val seconds: Reactive[Long] = for (x <- ticks) yield { x / 60 }
  • ticks 1 2 3 60 61 seconds 0 0 0 1 1 ticks 61 60 3 2 1 seconds 0 1 val seconds: Reactive[Long] = for (x <- ticks) yield { x / 60 }
  • val days: Reactive[Long] = seconds.map(_ / 86400)
  • val days: Reactive[Long] = seconds.map(_ / 86400) val secondsToday =
  • val days: Reactive[Long] = seconds.map(_ / 86400) val secondsToday = (seconds zip days) { (s, d) => s – d * 86400 }
  • seconds days secondsToday val days: Reactive[Long] = seconds.map(_ / 86400) val secondsToday = (seconds zip days) { _ – _ * 86400 }
  • val angle = secondsInDay.map(angleFunc)
  • val angle = secondsInDay.map(angleFunc) val light = secondsInDay.map(lightFunc)
  • keys shift ↓ a↓ val rotate = keys pgup ↓ a↑ shift ↑ pgup ↑
  • keys filter shift ↓ a↓ pgup ↓ a↑ shift ↑ pgup ↓ val rotate = keys.filter(_ == PAGEUP) pgup ↑ pgup ↑
  • keys filter map shift ↓ a↓ pgup ↓ a↑ shift ↑ pgup ↑ pgup ↓ pgup ↑ true false val rotate = keys.filter(_ == PAGEUP) .map(_.down)
  • map true if (rotate()) viewAngle += 1 false
  • Signals
  • Reactives are discrete
  • Signals are continuous
  • trait Signal[T] extends Reactive[T] { def apply(): T }
  • map true signal val rotate = keys.filter(_ == PAGEUP) .map(_.down) .signal(false) false
  • map true signal val rotate: Signal[Boolean] = keys.filter(_ == PAGEUP) .map(_.down) .signal(false) false
  • ticks rotate val rotate: Signal[Boolean] val ticks: Reactive[Long]
  • ticks val rotate: Signal[Boolean] val ticks: Reactive[Long]
  • ticks rotate val rotate: Signal[Boolean] val ticks: Reactive[Long]
  • ticks rotate viewAngle val rotate: Signal[Boolean] val ticks: Reactive[Long] val viewAngle: Signal[Double] =
  • List(1, 2, 3).scanLeft(0)(_ + _)
  • List(1, 2, 3).scanLeft(0)(_ + _) → List(0, 1, 3, 6)
  • def scanLeft[S](z: S)(f: (S, T) => S) : List[S]
  • def scanLeft[S](z: S)(f: (S, T) => S) : List[S] def scanPast[S](z: S)(f: (S, T) => S) : Signal[S]
  • ticks rotate viewAngle val rotate: Signal[Boolean] val ticks: Reactive[Long] val viewAngle: Signal[Double] = ticks.scanPast(0.0)
  • ticks rotate viewAngle val rotate: Signal[Boolean] val ticks: Reactive[Long] val viewAngle: Signal[Double] = ticks.scanPast(0.0) { (a, _) => if (rotate()) a + 1 else a }
  • val velocity = ticks.scanPast(0.0) { (v, _) => } val viewAngle =
  • val velocity = ticks.scanPast(0.0) { (v, _) => if (rotate()) v + 1 } val viewAngle =
  • val velocity = ticks.scanPast(0.0) { (v, _) => if (rotate()) v + 1 else v – 0.5 } val viewAngle =
  • val velocity = ticks.scanPast(0.0) { (v, _) => if (rotate()) v + 1 else v – 0.5 } val viewAngle = velocity.scanPast(0.0)(_ + _)
  • Higher-order reactive values
  • (T => S) => (List[S] => List[T])
  • (T => S) => (List[S] => List[T]) Reactive[Reactive[S]]
  • mids ↓ ↑ ↓ ↑ val mids = mouse .filter(_.button == MIDDLE) ↓ ↑
  • mids ↓ ↓ ↑ up down ↑ ↓ ↑ ↓ ↑ ↓ val mids = mouse .filter(_.button == MIDDLE) val up = mids.filter(!_.down) val down = mids.filter(_.down) ↑ ↑ ↓
  • ↑ up down ↓ ↑ ↓ val mids = mouse .filter(_.button == MIDDLE) val up = mids.filter(!_.down) val down = mids.filter(_.down) val drags = down .map(_ => mouse) ↑ ↓
  • ↑ up down ↓ ↑ ↓ ↑ ↓ val mids = mouse .filter(_.button == MIDDLE) val up = mids.filter(!_.down) val down = mids.filter(_.down) val drags = down .map(_ => mouse) Reactive[Reactive[MouseEvent]]
  • ↑ up down ↓ ↑ ↓ drags val mids = mouse .filter(_.button == MIDDLE) val up = mids.filter(!_.down) val down = mids.filter(_.down) val drags = down .map(_ => mouse) ↑ ↓
  • ↑ up down ↓ ↑ ↓ ↑ ↓ drags val mids = mouse .filter(_.button == MIDDLE) val up = mids.filter(!_.down) val down = mids.filter(_.down) val drags = down mouse.until(up) .map(_ => mouse.until(up))
  • ↑ up down ↓ ↑ ↓ drags val mids = mouse .filter(_.button == MIDDLE) val up = mids.filter(!_.down) val down = mids.filter(_.down) val drags = down .map(_ => mouse.until(up)) ↑ ↓
  • drags 1, 1 4, 6 2, 3 3, 5 9, 9 6, 9 val drags = down .map(_ => mouse.until(up)) .map(_.map(_.xy))
  • drags 0, 0 0, 0 1, 2 1, 2 0, 0 2, 3 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 val drags = down .map(_ => mouse.until(up)) .map(_.map(_.xy)) .map(_.diffPast(_.xy - _.xy)) .concat() 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 val drags = down .map(_ => mouse.until(up)) .map(_.map(_.xy)) .map(_.diffPast(_.xy - _.xy)) .concat() val pos = drags.scanPast((0, 0))(_ + _)
  • Reactive mutators
  • class Matrix { def apply(x: Int, y: Int): Double def update(x: Int, y: Int, v: Double) }
  • val screenMat: Signal[Matrix] = (projMat zip viewMat)(_ * _) val invScreenMat = screenMat.map(_.inverse)
  • Reactive[immutable.Matrix[T]]
  • val screenMat: Signal[Matrix] = (projMat zip viewMat)(_ * _) val invScreenMat = screenMat.map(_.inverse) (4*4*8 + 16 + 16)*4*100 = 64 kb/s
  • 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) }
  • Reactive collections
  • val selected: Reactive[Set[Character]]
  • val selected: ReactSet[Character]
  • trait ReactSet[T] extends ReactContainer[T] { def apply(x: T): Boolean }
  • trait ReactContainer[T] { def inserts: Reactive[T] def removes: Reactive[T] }
  • A reactive collection is a pair of reactive values
  • val selected = new ReactHashSet[Character]
  • val selected = new ReactHashSet[Character] val decorations = selected .map(c => (c, decoFor(c)))
  • val selected = new ReactHashSet[Character] val decorations = selected .map(c => (c, decoFor(c)))
  • 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) } }
  • val selected = new ReactHashSet[Character] val decorations = selected .map(c => (c, decoFor(c))) .to[ReactHashMap]
  • val selected = new ReactHashSet[Character] val decorations = selected .map(c => (c, decoFor(c))) .to[ReactHashMap]
  • val selected = new ReactHashSet[Character] val decorations = selected .map(c => (c, decoFor(c))) .to[ReactHashMap]
  • val selected = new ReactHashSet[Character] val decorations = selected .map(c => (c, decoFor(c))) .to[ReactHashMap]
  • val selected = new ReactHashSet[Character] val decorations = selected .map(c => (c, decoFor(c))) .react.to[ReactHashMap]
  • val selected = new ReactHashSet[Character] val decorations = selected .map(c => (c, decoFor(c))) .react.to[ReactHashMap]
  • val selected = new ReactHashSet[Character] val decorations = selected .map(c => (c, decoFor(c))) .react.to[ReactHashMap]
  • • reactive mutators • reactive collections • @specialized • Scala Macros • shipping computations to the GPU
  • MacroGL http://storm-enroute.com/macrogl/
  • YES!
  • Thank you!