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
6
Demo first!
http://youtu.be/pRCzSRhifLs
7
Input Simulator
Interaction
Renderer
Reactive Collections
http://reactive-collections.com
8
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.1
tick no.2
tick no.3
tick no.4
tick no.60
tick 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 are
discrete
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
45
http://youtu.be/blG95W5uWQ8
Preview
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)(_ + _)
50
http://youtu.be/NMVhirZLWmA
Preview
51
Higher-order
reactive 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, 1
2, 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, 0
1, 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 collection
is 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!

ScalaDays 2014 - Reactive Scala 3D Game Engine