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