J2ME Algorithms Performance - Slides
J2ME Algorithms Performance - Slides
Simon Kågström
http://www.ipd.bth.se/ska
Limiations
On the REX, you only have 8KB to play with in terms of code and data
Graphical adventures normally use drawn backdrops
Doesn’t fit on the REX - (240*120/8), 3KB per image
Space quest uses keyboard input
The REX has a touch screen
Monkey island-style mouse/touchscreen input, tile-based, Zelda-style graphics
Limiations
On the REX, you only have 8KB to play with in terms of code and data
Graphical adventures normally use drawn backdrops
Doesn’t fit on the REX - (240*120/8), 3KB per image
Space quest uses keyboard input
The REX has a touch screen
Monkey island-style mouse/touchscreen input, tile-based, Zelda-style graphics
Description
The library/game implementation split turned out good, 8KB limit no problem
The event-based API can produce fairly compact code
Screens are loaded from textfiles in a simple format
The game interface feels logical
Code
static uint8_t handle_scene_cabin(advg_game_t *p_game, advg_event_t event) {
switch (advg_event_nr(event)) {
case ADVG_EVENT_ENTER_SCENE:
/* Add the garden and the door as events */
advg_init_scene_event(&p_game->scene_events[0], 88, 60, 16, 8, CABIN_DOOR_EVENT);
...
case CABIN_DOOR_EVENT:
if (p_game->action == ADVG_ACTION_LOOK) /* Player looks at the door */
advg_display_message(p_game, EV_HOUSE_DOOR_LOOK, 1);
if (p_game->action == ADVG_ACTION_USE &&
p_game->obj_use == OBJ_KEY) /* The door is unlocked - enter! */
advg_goto_scene(p_game, SCENE_INSIDE_CABIN, 120, 60);
...
The book (Chapter 13) presents some basic building blocks: roaming,
evading and chasing AI
Simple behavior can be constructed by assigning weights to different
behaviors, e.g.,
50% chasing the player
10% trying to evade the player
30% moving in a pattern
10% random roaming
Read this chapter through and try the examples
The rest of the lecture will focus on more general concepts
transition
State 1 State 2
to self
(Align)
obstacles? [Crash-and-turn
Lost sight and too far
See enemy and pathfinding/A*]
close enough
move chase
How do we move to a waypoint?
to intruder
[Another FSM]
See enemy and close enough
How do we know if the intruder
pos != waypoint is visible? [Bresenhams
algorithm]
(Align)
obstacles? [Crash-and-turn
Lost sight and too far
See enemy and pathfinding/A*]
close enough
move chase
How do we move to a waypoint?
to intruder
[Another FSM]
See enemy and close enough
How do we know if the intruder
pos != waypoint is visible? [Bresenhams
algorithm]
(Align)
obstacles? [Crash-and-turn
Lost sight and too far
See enemy and pathfinding/A*]
close enough
move chase
How do we move to a waypoint?
to intruder
[Another FSM]
See enemy and close enough
How do we know if the intruder
pos != waypoint is visible? [Bresenhams
algorithm]
(Align)
obstacles? [Crash-and-turn
Lost sight and too far
See enemy and pathfinding/A*]
close enough
move chase
How do we move to a waypoint?
to intruder
[Another FSM]
See enemy and close enough
How do we know if the intruder
pos != waypoint is visible? [Bresenhams
algorithm]
Description
The FSM is implemented in a switch-statement
Default actions specify what to do in the state
Transition specifies the state to change to
The FSM is called in each frame of the game loop
Description
The FSM is implemented in a switch-statement
Default actions specify what to do in the state
Transition specifies the state to change to
The FSM is called in each frame of the game loop
Description
The FSM is implemented in a switch-statement
Default actions specify what to do in the state
Transition specifies the state to change to
The FSM is called in each frame of the game loop
Description
The FSM is implemented in a switch-statement
Default actions specify what to do in the state
Transition specifies the state to change to
The FSM is called in each frame of the game loop
guardFsm.state = GuardFsm.SELECT_WAYPOINT;
pos == waypoint
break;
case GuardFsm.CHASE_INTRUDER: (Align)
move chase
if (dist(this, guardFsm.intruder) > 100 || to intruder
!inLineOfSight(this.guardFsm.intruder))
guardFsm.state = GuardFsm.MOVE_TO;
break; See enemy and close enough
} pos != waypoint
guardFsm.state = GuardFsm.SELECT_WAYPOINT;
pos == waypoint
break;
case GuardFsm.CHASE_INTRUDER: (Align)
move chase
if (dist(this, guardFsm.intruder) > 100 || to intruder
!inLineOfSight(this.guardFsm.intruder))
guardFsm.state = GuardFsm.MOVE_TO;
break; See enemy and close enough
} pos != waypoint
guardFsm.state = GuardFsm.SELECT_WAYPOINT;
pos == waypoint
break;
case GuardFsm.CHASE_INTRUDER: (Align)
move chase
if (dist(this, guardFsm.intruder) > 100 || to intruder
!inLineOfSight(this.guardFsm.intruder))
guardFsm.state = GuardFsm.MOVE_TO;
break; See enemy and close enough
} pos != waypoint
guardFsm.state = GuardFsm.SELECT_WAYPOINT;
pos == waypoint
break;
case GuardFsm.CHASE_INTRUDER: (Align)
move chase
if (dist(this, guardFsm.intruder) > 100 || to intruder
!inLineOfSight(this.guardFsm.intruder))
guardFsm.state = GuardFsm.MOVE_TO;
break; See enemy and close enough
} pos != waypoint
guardFsm.state = GuardFsm.SELECT_WAYPOINT;
pos == waypoint
break;
case GuardFsm.CHASE_INTRUDER: (Align)
move chase
if (dist(this, guardFsm.intruder) > 100 || to intruder
!inLineOfSight(this.guardFsm.intruder))
guardFsm.state = GuardFsm.MOVE_TO;
break; See enemy and close enough
} pos != waypoint
guardFsm.state = GuardFsm.SELECT_WAYPOINT;
pos == waypoint
break;
case GuardFsm.CHASE_INTRUDER: (Align)
move chase
if (dist(this, guardFsm.intruder) > 100 || to intruder
!inLineOfSight(this.guardFsm.intruder))
guardFsm.state = GuardFsm.MOVE_TO;
break; See enemy and close enough
} pos != waypoint
guardFsm.state = GuardFsm.SELECT_WAYPOINT;
pos == waypoint
break;
case GuardFsm.CHASE_INTRUDER: (Align)
move chase
if (dist(this, guardFsm.intruder) > 100 || to intruder
!inLineOfSight(this.guardFsm.intruder))
guardFsm.state = GuardFsm.MOVE_TO;
break; See enemy and close enough
} pos != waypoint
Description
Manhattan distance (example below)
Euclidic distance guarantees shortest paths
Diagonal movement (watch out for speed!)
Manhattan
Diagonal
Euclidic
Description
Movement can also be implemented in state machines
Direction and actual movement can be split in two parallel FSMs
The movement FSM only sets the speed, the turn FSM sets the direction
turn
turn Idle
Idle towards
towards
move
move towards
Idle
towards
pos != dst_pos
pos == dst_pos pos != dst_pos
pos == dst_pos
Description
An easy way of determining if an object is visible is
using Bresenham’s algorithm
The algorithm is used to draw solid lines
Example
class Guard implements BresenhamCallback {
...
public boolean bresenhamStep(int x, int y) {
if (this.playfield.isObstacle(x,y))
return true;
return false;
}
public boolean inLineOfSight(int x, y) {
return !Bresenham.run(this.getX(), this.getY(), x, y);
}
}
Example
class Guard implements BresenhamCallback {
...
public boolean bresenhamStep(int x, int y) {
if (this.playfield.isObstacle(x,y))
return true;
return false;
}
public boolean inLineOfSight(int x, y) {
return !Bresenham.run(this.x, this.y, x, y);
}
}
Example
class Guard implements BresenhamCallback {
...
public boolean bresenhamStep(int x, int y) {
if (this.playfield.isObstacle(x,y))
return true;
return false;
}
public boolean inLineOfSight(int x, y) {
return !Bresenham.run(this.x, this.y, x, y);
}
}
Example
class Guard implements BresenhamCallback {
...
public boolean bresenhamStep(int x, int y) {
if (this.playfield.isObstacle(x,y))
return true;
return false;
}
public boolean inLineOfSight(int x, y) {
return !Bresenham.run(this.x, this.y, x, y);
}
}
Example
class Guard implements BresenhamCallback {
...
public boolean bresenhamStep(int x, int y) {
if (this.playfield.isObstacle(x,y))
return true;
return false;
}
public boolean inLineOfSight(int x, y) {
return !Bresenham.run(this.x, this.y, x, y);
}
}
Example
class Guard implements BresenhamCallback {
...
public boolean bresenhamStep(int x, int y) {
if (this.playfield.isObstacle(x,y))
return true;
return false;
}
public boolean inLineOfSight(int x, y) {
return !Bresenham.run(this.x, this.y, x, y);
}
}
Non-determinism
transition
state
(0.3)
1
Multiple transitions with weights
30% chance state0 → state1
state
70% chance state0 → state2
0
Parallel state machines
With too many states and transitions, state
transition
state machines become very complex
2
(0.7) Better then to separate behavior into multiple
state machines
Wall
Select bump wall
Walk select
V/H
way
bump wall
!wall
Wall
walk
wall
void crashAndTurnFsm() {
switch(this.ctFsm.state) {
case SELECT_V_H:
/* Select vertical or horizontal */
this.ctFsm.selectWay = !this.ctFsm.selectWay;
/* Set dir depending on selectWay (up/down, left/right) */
break;
case WALK:
/* Walk towards the selected direction */
if (this.move(this.ctFsm.dir) == false )
this.ctFsm.state = WALL_SELECT_WAY; /* Bump! */
else if (this.x == this.dstX && this.y == this.dstY)
this.ctFsm.state = SELECT_V_H; /* Walk OK - have we reached the dst? */
break;
case WALL_SELECT_WAY:
/* Select the direction along the wall */
if (this.ctFsm.dir == UP || this.ctFsm.dir == DOWN) {
this.ctFsm.h_way_right = !this.ctFsm.h_way_right;
this.ctFsm.wall_dir = this.ctFsm.h_way_right ? RIGHT : LEFT;
}
else if (p_ai->ct_data.dir == LEFT || p_ai->ct_data.dir == RIGHT)
...
this.ctFsm.state = WALL_WALK;
break;
case WALL_WALK:
/* Walk along the wall */
if (!this.is_wall(this.ctFsm.dir))
this.ctFsm.state = WALK;
else if (this.move(this.ctFsm.wall_dir) == false)
this.ctFsm.state = WALL_SELECT_WAY; /* Bump! */
break;
} Simon Kågström (BTH) Algorithms for games 29th June 2006 36 / 96
Outline
}
G:0 H:3
Closed = {
F:3
}
G:0 H:3
Closed = {
F:3
G:1 H:2 6
F:3 }
G:2 H:4 G:0 H:3 G:2 H:2
Closed = { 3
F:6 }
G:3 H:1
F:4
G:3 H:3 G:1 H:2 G:2 H:1 66 6 6
Closed = { 3 3
F:6 }
F:4 F:3
G:3 H:3 G:1 H:2 G:2 H:1 G:4 H:2 66 6 6
6
F:6 F:3 F:3 F:6 }
G:2 H:4 G:0 H:3 G:2 H:2
Closed = { 3 3
F:6 }
node.g
(Same with f )
The Finder class uses this (see the instructions for lab 3)
2 Heuristic can be implemented as Manhattan distance between the
nodes (i.e., distance between the tiles)
3 The predecessor π is a reference to the previous node
4 The Open set should provide fast extraction of the lowest-f node and
quick insertion
The Vector class is not the best, but will do initially
Unfortunately, A* can quickly eat up all your CPU cycles, ruin your
game predictability, waste all your memory and chase away all your
potential game players
The performance of A* will vary with the structure of your map
No/few obstacles: good performance
Short distance start to goal: good performance
Labyrinths: bad performance
Long distance start to goal: bad performance
Calculating a new path for your NPCs every frame will be prohibitly
expensive, and probably make your game unplayable
We might need some optimizations
Performance
6 Introduction
Architecture
Book performance chapter
6 Introduction
Architecture
Book performance chapter
Description
Performance is fine on a slow phone (more a few slides ahead)
Many features
Extensible design, easy to add levels, nice graphics!
The implementation of lamp visibility became short and efficient (below)
Code
/* (to the right) (up)
* . 3 4 5 6
* . 7 8 2 2 3 . .
* . 4 5 6 1 1 . . .
* X 1 2 3 0 X . . .
* 1 2 3
*
* 1,2,3 1,2,4
* 4,5,6 1,3,5
* 4,5,7,8 1,3,6
*/
6 Introduction
Architecture
Book performance chapter
6 Introduction
Architecture
Book performance chapter
Description
In a single-threaded game, the game loop is always the base of the
performance issues
Here, either the update() or the draw() functions
But don’t rely on code inspection
Example
public void run() {
Graphics g = getGraphics();
while (true) {
long before = System.currentTimeMillis();
this.update();
draw(g);
try {
long sleepTime = 33 - (System.currentTimeMillis() - before);
Thread.sleep(sleepTime);
} catch (InterruptedException e) {}
}
}
6 Introduction
Architecture
Book performance chapter
Description
The easy(?) way of implementing boulder falling
The tilemap is used to store boulders directly
However: the complexity is O(sizelevel )
I.e. even with few boulders this will be slow - we can do better!
Why am I looping backwards in the y-direction?
Example
for (y = LEVEL_H; y >= 0; y--) {
for (x = 0; x < LEVEL_W; x++) {
if (map.getTileType(x,y) == BOULDER &&
(map.getTileType(x,y+1) == EMPTY || /* case 1 */
(map.getTileType(x,y+1) == BOULDER && /* case 2, 3 */))) {
Boulder boulder = map.getTile(x,y);
boulder.fall();
}
}
} Simon Kågström (BTH) Algorithms for games 29th June 2006 74 / 96
Boulder dash, first try
Description
The easy(?) way of implementing boulder falling
The tilemap is used to store boulders directly
However: the complexity is O(sizelevel )
I.e. even with few boulders this will be slow - we can do better!
Why am I looping backwards in the y-direction?
Example
for (y = LEVEL_H; y >= 0; y--) {
for (x = 0; x < LEVEL_W; x++) {
if (map.getTileType(x,y) == BOULDER &&
(map.getTileType(x,y+1) == EMPTY || /* case 1 */
(map.getTileType(x,y+1) == BOULDER && /* case 2, 3 */))) {
Boulder boulder = map.getTile(x,y);
boulder.fall();
}
}
} Simon Kågström (BTH) Algorithms for games 29th June 2006 74 / 96
Second try, boulders in a vector
Description
Keeping the boulders in a vector lets us traverse the vector for checking case 1
However, cases 2 and 3 require a traversal over the boulders again, making the
complexity O(sizevec 2 )
So this will be slow when we increase the number of boulders
Example
/* Case 1 */
for (i = 0; i < n_boulders; i++) {
if (map.getTileType(boulder[i].x,boulder[i].y + 1) == EMPTY) /* case 1 *
boulder[i].fall();
for (j = 0; j < n_boulders; j++) {
/* case 2,3 */
}
}
}
Description
Using a boulder vector and a matrix for the playfield, we can get O(sizevec ) on
average!
The matrix consists of references to boulders
Memory can be reduced by holding indices to the boulder vector
Conclusion: with clever use of data structures, the performance can be noticeably
increased
Example
for (i = 0; i < n_boulders; i++) {
/* Case 1 */
if (map.getTileType(boulder[i].x, boulder[i].y+1) == EMPTY ||
(map.getTileType(boulder[i].x, boulder[i].y+1) == BOULDER &&
/* Case 2 and 3 */ )
boulder[i].fall();
}
}
Java differs from C/C++ in that the compiler only do a small part of
the optimization - the JVM is responsible for most of the code
optimizations
Examples
public int test_code_motion() public int test_code_motion(); <Test::test_code_motion()>:
{ Code: 1e0: push %ebp
int out = 0; 0: iconst_0 1e1: mov %esp,%ebp
1: istore_1 1e3: mov 0x8(%ebp),%eax
for (int i = 0; i < 10; i++) 2: iconst_0 1e6: mov 0x4(%eax),%eax
{ 3: istore_2 1e9: lea (%eax,%eax,4),%eax
int tjoho = 5 * a; 4: iload_2 1ec: lea (%eax,%eax,8),%edx
out += tjoho; 5: bipush 10 1ef: add %edx,%eax
} 7: if_icmpge 27 1f1: pop %ebp
return out; 10: iconst_5 1f2: ret
} 11: aload_0
12: getfield #4; //Field a:I
15: imul
16: istore_3
17: iload_1
18: iload_3
19: iadd
20: istore_1
21: iinc 2, 1
24: goto 4
27: iload_1
28: ireturn
Simon Kågström (BTH) Algorithms for games 29th June 2006 81 / 96
Java compiler/JVM
Java differs from C/C++ in that the compiler only do a small part of
the optimization - the JVM is responsible for most of the code
optimizations
Examples
public int test_code_motion() public int test_code_motion(); <Test::test_code_motion()>:
{ Code: 1e0: push %ebp
int out = 0; 0: iconst_0 1e1: mov %esp,%ebp
1: istore_1 1e3: mov 0x8(%ebp),%eax
for (int i = 0; i < 10; i++) 2: iconst_0 1e6: mov 0x4(%eax),%eax
{ 3: istore_2 1e9: lea (%eax,%eax,4),%eax
int tjoho = 5 * a; 4: iload_2 1ec: lea (%eax,%eax,8),%edx
out += tjoho; 5: bipush 10 1ef: add %edx,%eax
} 7: if_icmpge 27 1f1: pop %ebp
return out; 10: iconst_5 1f2: ret
} 11: aload_0
12: getfield #4; //Field a:I
15: imul
16: istore_3
17: iload_1
18: iload_3
19: iadd
20: istore_1
21: iinc 2, 1
24: goto 4
27: iload_1
28: ireturn
Simon Kågström (BTH) Algorithms for games 29th June 2006 81 / 96
Java compiler/JVM
Java differs from C/C++ in that the compiler only do a small part of
the optimization - the JVM is responsible for most of the code
optimizations
Examples
public int test_code_motion() public int test_code_motion(); <Test::test_code_motion()>:
{ Code: 1e0: push %ebp
int out = 0; 0: iconst_0 1e1: mov %esp,%ebp
1: istore_1 1e3: mov 0x8(%ebp),%eax
for (int i = 0; i < 10; i++) 2: iconst_0 1e6: mov 0x4(%eax),%eax
{ 3: istore_2 1e9: lea (%eax,%eax,4),%eax
int tjoho = 5 * a; 4: iload_2 1ec: lea (%eax,%eax,8),%edx
out += tjoho; 5: bipush 10 1ef: add %edx,%eax
} 7: if_icmpge 27 1f1: pop %ebp
return out; 10: iconst_5 1f2: ret
} 11: aload_0
12: getfield #4; //Field a:I
15: imul
16: istore_3
17: iload_1
18: iload_3
19: iadd
20: istore_1
21: iinc 2, 1
24: goto 4
27: iload_1
28: ireturn
Simon Kågström (BTH) Algorithms for games 29th June 2006 81 / 96
Java compiler/JVM, II
Description
In my A* implementation, I use multithreading to hide the latency
The game runs in one thread and a worker thread computes paths in the
background
The game loop checks for finished paths and handles them when they are ready
Code
Description
In my A* implementation, I use multithreading to hide the latency
The game runs in one thread and a worker thread computes paths in the
background
The game loop checks for finished paths and handles them when they are ready
Code
Description
The A* thread just waits for items, handles them and then enqueues
the result
The thread blocks if no work is available
Code
public class AstarThread ... {
public void run()
{
while (true) {
/* Get a request (block if no work available) */
Request r = this.dequeueRequest();
Path p = astar.runAlgorithm(...);
p.setId(r.id);
this.enqueuePath(p);
}
}
}
Description
The A* thread just waits for items, handles them and then enqueues
the result
The thread blocks if no work is available
Code
public class AstarThread ... {
public void run()
{
while (true) {
/* Get a request (block if no work available) */
Request r = this.dequeueRequest();
Path p = astar.runAlgorithm(...);
p.setId(r.id);
this.enqueuePath(p);
}
}
}
Description
The algorithm is aborted after a certain number of milliseconds
This gives non-optimal paths, but reduces starvation
(Another possibility is to only search a fixed number of nodes)
Code
class Astar {
public Path runAlgorithm(Node start, Node end) {
long before = System.currentTimeMillis();
...
this.putInOpen(start);
The code works fine, but as the profile below shows, a lot of time is
spent in scheduling the threads
Description
I’d like to use coroutines
Manually switching and restarting the “threads” - no concurrency issues
The A* thread should not run instead of the main thread anyway
... but J2ME doesn’t allow this
Code
runAlgorithm(self):
node = self.getFromOpen()
...
if curTime - before > self.limit:
yield()
...
run(self):
update()
draw()
yield(25 - (now-before))