changeset 207:133926e08d6e

examples: use game_loop for all
author David Demelier <markand@malikania.fr>
date Wed, 11 Nov 2020 16:09:43 +0100
parents 4e2bf083759b
children c0e0d4accae8
files examples/example-action.c examples/example-animation.c examples/example-audio.c examples/example-battle.c examples/example-cursor.c examples/example-debug.c examples/example-drawable.c examples/example-font.c examples/example-gridmenu.c examples/example-label.c examples/example-message.c examples/example-sprite.c examples/example-trace.c examples/example-ui.c libcore/core/game.c libcore/core/game.h
diffstat 16 files changed, 846 insertions(+), 627 deletions(-) [+]
line wrap: on
line diff
--- a/examples/example-action.c	Tue Nov 10 17:32:12 2020 +0100
+++ b/examples/example-action.c	Wed Nov 11 16:09:43 2020 +0100
@@ -19,15 +19,16 @@
 #include <assert.h>
 
 #include <core/action.h>
-#include <core/clock.h>
 #include <core/core.h>
 #include <core/event.h>
+#include <core/game.h>
 #include <core/image.h>
 #include <core/maths.h>
 #include <core/painter.h>
 #include <core/panic.h>
 #include <core/script.h>
 #include <core/sprite.h>
+#include <core/state.h>
 #include <core/sys.h>
 #include <core/texture.h>
 #include <core/util.h>
@@ -324,41 +325,56 @@
 }
 
 static void
+handle(struct state *st, const union event *ev)
+{
+	(void)st;
+
+	switch (ev->type) {
+	case EVENT_QUIT:
+		game_quit();
+		break;
+	default:
+		action_stack_handle(&events, ev);
+		action_stack_handle(&modal, ev);
+		break;
+	}
+}
+
+static void
+update(struct state *st, unsigned int ticks)
+{
+	(void)st;
+
+	action_stack_update(&events, ticks);
+	action_stack_update(&modal, ticks);
+}
+
+static void
+draw(struct state *st)
+{
+	(void)st;
+
+	painter_set_color(0xffffffff);
+	painter_clear();
+	action_stack_draw(&events);
+	action_stack_draw(&modal);
+	painter_present();
+}
+
+static void
 run(void)
 {
-	struct clock clock = {0};
+	struct state state = {
+		.handle = handle,
+		.update = update,
+		.draw = draw
+	};
 
-	clock_start(&clock);
 	action_stack_add(&events, &chest.event);
 	action_stack_add(&events, &guide.event);
 
-	for (;;) {
-		unsigned int elapsed = clock_elapsed(&clock);
-
-		clock_start(&clock);
-
-		for (union event ev; event_poll(&ev); ) {
-			switch (ev.type) {
-			case EVENT_QUIT:
-				return;
-			default:
-				action_stack_handle(&events, &ev);
-				action_stack_handle(&modal, &ev);
-				break;
-			}
-		}
-
-		painter_set_color(0xffffffff);
-		painter_clear();
-		action_stack_update(&events, elapsed);
-		action_stack_update(&modal, elapsed);
-		action_stack_draw(&events);
-		action_stack_draw(&modal);
-		painter_present();
-
-		if ((elapsed = clock_elapsed(&clock)) < 20)
-			delay(20 - elapsed);
-	}
+	game_switch(&state, true);
+	game_loop();
 }
 
 static void
--- a/examples/example-animation.c	Tue Nov 10 17:32:12 2020 +0100
+++ b/examples/example-animation.c	Wed Nov 11 16:09:43 2020 +0100
@@ -17,15 +17,16 @@
  */
 
 #include <core/animation.h>
-#include <core/clock.h>
 #include <core/core.h>
 #include <core/event.h>
+#include <core/game.h>
 #include <core/image.h>
 #include <core/sys.h>
 #include <core/window.h>
 #include <core/painter.h>
 #include <core/panic.h>
 #include <core/sprite.h>
+#include <core/state.h>
 #include <core/texture.h>
 #include <core/util.h>
 
@@ -37,8 +38,6 @@
 #define W 1280
 #define H 720
 
-static struct texture numbers;
-
 static struct label label = {
 	.text = "Keys: <Space> start or reset the animation.",
 	.x = 10,
@@ -46,6 +45,11 @@
 	.flags = LABEL_FLAGS_SHADOW
 };
 
+static struct texture numbers;
+static struct animation animation;
+static struct sprite sprite;
+static bool completed = true;
+
 static void
 init(void)
 {
@@ -58,57 +62,67 @@
 }
 
 static void
+handle(struct state *st, const union event *ev)
+{
+	(void)st;
+
+	switch (ev->type) {
+	case EVENT_KEYDOWN:
+		switch (ev->key.key) {
+		case KEY_SPACE:
+			animation_start(&animation);
+			completed = animation_completed(&animation);
+			break;
+		default:
+			break;
+		}
+		break;
+	case EVENT_QUIT:
+		game_quit();
+		break;
+	default:
+		break;
+	}
+}
+
+static void
+update(struct state *st, unsigned int ticks)
+{
+	(void)st;
+
+	if (!completed)
+		completed = animation_update(&animation, ticks);
+}
+
+static void
+draw(struct state *st)
+{
+	(void)st;
+
+	painter_set_color(0x4f8fbaff);
+	painter_clear();
+	label_draw(&label);
+
+	if (!completed)
+		animation_draw(&animation, (window.w - sprite.cellw) / 2, (window.h - sprite.cellh) / 2);
+
+	painter_present();
+}
+
+static void
 run(void)
 {
-	struct clock clock = {0};
-	struct animation animation;
-	struct sprite sprite;
-	bool completed = true;
+	struct state state = {
+		.handle = handle,
+		.update = update,
+		.draw = draw
+	};
 
-	clock_start(&clock);
 	sprite_init(&sprite, &numbers, 48, 48);
 	animation_init(&animation, &sprite, 1000);
 
-	for (;;) {
-		union event ev;
-		unsigned int elapsed = clock_elapsed(&clock);
-
-		clock_start(&clock);
-
-		while (event_poll(&ev)) {
-			switch (ev.type) {
-			case EVENT_KEYDOWN:
-				switch (ev.key.key) {
-				case KEY_SPACE:
-					animation_start(&animation);
-					completed = animation_completed(&animation);
-					break;
-				default:
-					break;
-				}
-				break;
-			case EVENT_QUIT:
-				return;
-			default:
-				break;
-			}
-		}
-
-		if (!completed)
-			completed = animation_update(&animation, elapsed);
-
-		painter_set_color(0x4f8fbaff);
-		painter_clear();
-		label_draw(&label);
-
-		if (!completed)
-			animation_draw(&animation, (window.w - sprite.cellw) / 2, (window.h - sprite.cellh) / 2);
-
-		painter_present();
-
-		if ((elapsed = clock_elapsed(&clock)) < 20)
-			delay(20 - elapsed);
-	}
+	game_switch(&state, true);
+	game_loop();
 }
 
 static void
--- a/examples/example-audio.c	Tue Nov 10 17:32:12 2020 +0100
+++ b/examples/example-audio.c	Wed Nov 11 16:09:43 2020 +0100
@@ -1,5 +1,5 @@
 /*
- * example-sound.c -- show how to use sounds
+ * example-audio.c -- show how to use sounds and music
  *
  * Copyright (c) 2020 David Demelier <markand@malikania.fr>
  *
@@ -16,13 +16,14 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
-#include <core/clock.h>
 #include <core/core.h>
 #include <core/event.h>
+#include <core/game.h>
 #include <core/music.h>
 #include <core/painter.h>
 #include <core/panic.h>
 #include <core/sound.h>
+#include <core/state.h>
 #include <core/sys.h>
 #include <core/util.h>
 #include <core/window.h>
@@ -62,9 +63,8 @@
 		panic();
 	if (!window_open("Example - Audio", W, H))
 		panic();
-	if (!music_openmem(&music, musics_vabsounds_romance, sizeof (musics_vabsounds_romance)))
-		panic();
-	if (!sound_openmem(&sound, sounds_fire, sizeof (sounds_fire)))
+	if (!music_openmem(&music, musics_vabsounds_romance, sizeof (musics_vabsounds_romance)) ||
+	    !sound_openmem(&sound, sounds_fire, sizeof (sounds_fire)))
 		panic();
 }
 
@@ -77,67 +77,72 @@
 }
 
 static void
+handle(struct state *st, const union event *ev)
+{
+	(void)st;
+
+	switch (ev->type) {
+	case EVENT_CLICKDOWN:
+		if (!sound_play(&sound, -1, 0))
+			panic();
+		break;
+	case EVENT_KEYDOWN:
+		switch (ev->key.key) {
+		case KEY_f:
+			music_play(&music, 0, 500);
+			break;
+		case KEY_s:
+			music_stop(500);
+			break;
+		case KEY_p:
+			music_pause();
+			break;
+		case KEY_r:
+			music_resume();
+			break;
+		case KEY_q:
+			music_stop(0);
+			break;
+		case KEY_l:
+			music_play(&music, MUSIC_LOOP, 0);
+			break;
+		case KEY_SPACE:
+			music_play(&music, 0, 0);
+			break;
+		default:
+			break;
+		}
+		break;
+	case EVENT_QUIT:
+		game_quit();
+		break;
+	default:
+		break;
+	}
+}
+
+static void
+draw(struct state *st)
+{
+	(void)st;
+
+	painter_set_color(0x006554ff);
+	painter_clear();
+	label_draw(&label_music);
+	label_draw(&label_sound);
+	painter_present();
+}
+
+static void
 run(void)
 {
-	struct clock clock = {0};
-
-	clock_start(&clock);
-
-	for (;;) {
-		union event ev;
-		unsigned int elapsed = clock_elapsed(&clock);
-
-		clock_start(&clock);
+	struct state state = {
+		.handle = handle,
+		.draw = draw
+	};
 
-		while (event_poll(&ev)) {
-			switch (ev.type) {
-			case EVENT_CLICKDOWN:
-				if (!sound_play(&sound, -1, 0))
-					panic();
-				break;
-			case EVENT_KEYDOWN:
-				switch (ev.key.key) {
-				case KEY_f:
-					music_play(&music, 0, 500);
-					break;
-				case KEY_s:
-					music_stop(500);
-					break;
-				case KEY_p:
-					music_pause();
-					break;
-				case KEY_r:
-					music_resume();
-					break;
-				case KEY_q:
-					music_stop(0);
-					break;
-				case KEY_l:
-					music_play(&music, MUSIC_LOOP, 0);
-					break;
-				case KEY_SPACE:
-					music_play(&music, 0, 0);
-					break;
-				default:
-					break;
-				}
-				break;
-			case EVENT_QUIT:
-				return;
-			default:
-				break;
-			}
-		}
-
-		painter_set_color(0x006554ff);
-		painter_clear();
-		label_draw(&label_music);
-		label_draw(&label_sound);
-		painter_present();
-
-		if ((elapsed = clock_elapsed(&clock)) < 20)
-			delay(20 - elapsed);
-	}
+	game_switch(&state, true);
+	game_loop();
 
 	music_finish(&music);
 	sound_finish(&sound);
--- a/examples/example-battle.c	Tue Nov 10 17:32:12 2020 +0100
+++ b/examples/example-battle.c	Wed Nov 11 16:09:43 2020 +0100
@@ -18,15 +18,18 @@
 
 #include <assert.h>
 #include <stdio.h>
+#include <stdlib.h>
 #include <string.h>
 
-#include <core/clock.h>
+#include <core/alloc.h>
 #include <core/core.h>
 #include <core/event.h>
+#include <core/game.h>
 #include <core/image.h>
 #include <core/painter.h>
 #include <core/panic.h>
 #include <core/sprite.h>
+#include <core/state.h>
 #include <core/sys.h>
 #include <core/texture.h>
 #include <core/util.h>
@@ -148,29 +151,23 @@
 static void
 init(void)
 {
-	struct clock clock = {0};
-
 	if (!core_init() || !ui_init() || !rpg_init())
 		panic();
 	if (!window_open("Example - Battle", W, H))
 		panic();
 
-	/* Wait 1 second to let all events processed. */
-	while (clock_elapsed(&clock) < 1000)
-		for (union event ev; event_poll(&ev); )
-			if (ev.type == EVENT_QUIT)
-				return;
-
 	registry_init();
 
 	/* Set cursor in default theme. */
 	theme_default()->sprites[THEME_SPRITE_CURSOR] = &registry_sprites[REGISTRY_TEXTURE_CURSOR];
 }
 
+static struct state fight_state;
+
 static void
-prepare_to_fight(struct battle *bt)
+prepare_to_fight(void)
 {
-	assert(bt);
+	struct battle *bt = alloc_zero(1, sizeof (*bt));
 
 //	bt->enemies[0].ch = &haunted_wood;
 	bt->team[0].ch = &team[0];
@@ -187,60 +184,96 @@
 	bt->enemies[1].y = 100;
 
 	battle_start(bt);
+
+	fight_state.data = bt;
+	game_switch(&fight_state, false);
+}
+
+
+static void
+empty_handle(struct state *st, const union event *ev)
+{
+	(void)st;
+
+	switch (ev->type) {
+	case EVENT_QUIT:
+		game_quit();
+		break;
+	case EVENT_KEYDOWN:
+		if (ev->key.key == KEY_SPACE)
+			prepare_to_fight();
+		break;
+	default:
+		break;
+	}
 }
 
 static void
-run(void)
+empty_draw(struct state *st)
 {
-	struct clock clock;
-	struct battle battle = {0};
-	struct label info = {
+	(void)st;
+
+	static const struct label info = {
 		.text = "Press <Space> to start a battle.",
 		.x = 10,
 		.y = 10,
 		.flags = LABEL_FLAGS_SHADOW
 	};
 
-	clock_start(&clock);
+	painter_set_color(0x4f8fbaff);
+	painter_clear();
+	label_draw(&info);
+	painter_present();
+}
+
+static struct state empty_state = {
+	.handle = empty_handle,
+	.draw = empty_draw
+};
 
-	for (;;) {
-		unsigned int elapsed = clock_elapsed(&clock);
+static void
+fight_handle(struct state *st, const union event *ev)
+{
+	battle_handle(st->data, ev);
+}
 
-		clock_start(&clock);
+static void
+fight_update(struct state *st, unsigned int ticks)
+{
+	struct battle *bt = st->data;
+
+	if (battle_update(bt, ticks))
+		game_switch(&empty_state, false);
+}
 
-		for (union event ev; event_poll(&ev); ) {
-			switch (ev.type) {
-			case EVENT_QUIT:
-				return;
-			case EVENT_KEYDOWN:
-				if (ev.key.key == KEY_SPACE && !battle.state)
-					prepare_to_fight(&battle);
-				else if (battle.state)
-					battle_handle(&battle, &ev);
-				break;
-			default:
-				if (battle.state)
-					battle_handle(&battle, &ev);
-				break;
-			}
-		}
+static void
+fight_draw(struct state *st)
+{
+	painter_set_color(0x000000ff);
+	painter_clear();
+	battle_draw(st->data);
+	painter_present();
+}
 
-		if (battle.state) {
-			if (battle_update(&battle, elapsed))
-				battle_finish(&battle);
-			else
-				battle_draw(&battle);
-		} else {
-			painter_set_color(0x4f8fbaff);
-			painter_clear();
-			label_draw(&info);
-		}
+static void
+fight_finish(struct state *st)
+{
+	battle_finish(st->data);
+	free(st->data);
+}
 
-		painter_present();
+static struct state fight_state = {
+	.handle = fight_handle,
+	.update = fight_update,
+	.draw = fight_draw,
+	.finish = fight_finish,
+};
 
-		if ((elapsed = clock_elapsed(&clock)) < 20)
-			delay(1);
-	}
+static void
+run(void)
+{
+	game_switch(&empty_state, true);
+	game_loop();
 }
 
 static void
--- a/examples/example-cursor.c	Tue Nov 10 17:32:12 2020 +0100
+++ b/examples/example-cursor.c	Wed Nov 11 16:09:43 2020 +0100
@@ -18,12 +18,13 @@
 
 #include <stdio.h>
 
-#include <core/clock.h>
 #include <core/core.h>
 #include <core/event.h>
+#include <core/game.h>
 #include <core/key.h>
 #include <core/painter.h>
 #include <core/panic.h>
+#include <core/state.h>
 #include <core/sys.h>
 #include <core/util.h>
 #include <core/window.h>
@@ -35,6 +36,7 @@
 #define H 720
 
 static char help_text[128];
+static enum window_cursor cursor = WINDOW_CURSOR_ARROW;
 
 static struct label help = {
 	.x = 10,
@@ -70,53 +72,58 @@
 }
 
 static void
+handle(struct state *st, const union event *ev)
+{
+	(void)st;
+
+	switch (ev->type) {
+	case EVENT_KEYDOWN:
+		switch (ev->key.key) {
+		case KEY_LEFT:
+			if (cursor > 0)
+				change(--cursor);
+			break;
+		case KEY_RIGHT:
+			if (cursor + 1 < WINDOW_CURSOR_LAST)
+				change(++cursor);
+			break;
+		default:
+			break;
+		}
+
+
+		break;
+	case EVENT_QUIT:
+		game_quit();
+		break;
+	default:
+		break;
+	}
+}
+
+static void
+draw(struct state *st)
+{
+	(void)st;
+
+	painter_set_color(0xebede9ff);
+	painter_clear();
+	label_draw(&help);
+	painter_present();
+}
+
+static void
 run(void)
 {
-	struct clock clock = {0};
-	enum window_cursor cursor = WINDOW_CURSOR_ARROW;
+	struct state state = {
+		.handle = handle,
+		.draw = draw
+	};
 
-	clock_start(&clock);
 	change(cursor);
 
-	for (;;) {
-		union event ev;
-		unsigned int elapsed = clock_elapsed(&clock);
-
-		clock_start(&clock);
-
-		while (event_poll(&ev)) {
-			switch (ev.type) {
-			case EVENT_KEYDOWN:
-				switch (ev.key.key) {
-				case KEY_LEFT:
-					if (cursor > 0)
-						change(--cursor);
-					break;
-				case KEY_RIGHT:
-					if (cursor + 1 < WINDOW_CURSOR_LAST)
-						change(++cursor);
-					break;
-				default:
-					break;
-				}
-
-
-				break;
-			case EVENT_QUIT:
-				return;
-			default:
-				break;
-			}
-		}
-
-		painter_set_color(0xebede9ff);
-		painter_clear();
-		label_draw(&help);
-		painter_present();
-
-		if ((elapsed = clock_elapsed(&clock)) < 20)
-			delay(20 - elapsed);
-	}
+	game_switch(&state, true);
+	game_loop();
 }
 
 static void
@@ -137,4 +144,3 @@
 	run();
 	quit();
 }
-
--- a/examples/example-debug.c	Tue Nov 10 17:32:12 2020 +0100
+++ b/examples/example-debug.c	Wed Nov 11 16:09:43 2020 +0100
@@ -16,14 +16,13 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
-#include <core/clock.h>
 #include <core/core.h>
 #include <core/event.h>
-#include <core/sys.h>
+#include <core/game.h>
 #include <core/window.h>
 #include <core/painter.h>
 #include <core/panic.h>
-#include <core/trace.h>
+#include <core/state.h>
 #include <core/util.h>
 
 #include <ui/debug.h>
@@ -33,6 +32,9 @@
 #define W 1280
 #define H 720
 
+static int mouse_x;
+static int mouse_y;
+
 static void
 init(void)
 {
@@ -45,42 +47,47 @@
 }
 
 static void
+handle(struct state *st, const union event *ev)
+{
+	(void)st;
+
+	switch (ev->type) {
+	case EVENT_MOUSE:
+		mouse_x = ev->mouse.x;
+		mouse_y = ev->mouse.y;
+		break;
+	case EVENT_QUIT:
+		game_quit();
+		break;
+	default:
+		break;
+	}
+}
+
+static void
+draw(struct state *st)
+{
+	(void)st;
+
+	struct debug_report report = {0};
+
+	painter_set_color(0x4f8fbaff);
+	painter_clear();
+	debugf(&report, "Game running.");
+	debugf(&report, "mouse: %d, %d", mouse_x, mouse_y);
+	painter_present();
+}
+
+static void
 run(void)
 {
-	struct clock clock = {0};
-	int x = 0, y = 0;
-
-	clock_start(&clock);
-
-	for (;;) {
-		struct debug_report report = {0};
-		union event ev;
-		unsigned int elapsed = clock_elapsed(&clock);
-
-		clock_start(&clock);
+	struct state state = {
+		.handle = handle,
+		.draw = draw
+	};
 
-		while (event_poll(&ev)) {
-			switch (ev.type) {
-			case EVENT_MOUSE:
-				x = ev.mouse.x;
-				y = ev.mouse.y;
-				break;
-			case EVENT_QUIT:
-				return;
-			default:
-				break;
-			}
-		}
-
-		painter_set_color(0x4f8fbaff);
-		painter_clear();
-		debugf(&report, "Game running.");
-		debugf(&report, "mouse: %d, %d", x, y);
-		painter_present();
-
-		if ((elapsed = clock_elapsed(&clock)) < 20)
-			delay(20 - elapsed);
-	}
+	game_switch(&state, true);
+	game_loop();
 }
 
 static void
--- a/examples/example-drawable.c	Tue Nov 10 17:32:12 2020 +0100
+++ b/examples/example-drawable.c	Wed Nov 11 16:09:43 2020 +0100
@@ -16,13 +16,14 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
+#include <stdio.h>
 #include <stdlib.h>
 
 #include <core/alloc.h>
 #include <core/animation.h>
-#include <core/clock.h>
 #include <core/core.h>
 #include <core/event.h>
+#include <core/game.h>
 #include <core/drawable.h>
 #include <core/key.h>
 #include <core/painter.h>
@@ -30,6 +31,7 @@
 #include <core/sys.h>
 #include <core/image.h>
 #include <core/sprite.h>
+#include <core/state.h>
 #include <core/texture.h>
 #include <core/util.h>
 #include <core/window.h>
@@ -81,6 +83,22 @@
 	sprite_init(&explosion_sprite, &explosion_tex, 256, 256);
 }
 
+static bool
+explosion_update(struct drawable *dw, unsigned int ticks)
+{
+	struct explosion *ex = dw->data;
+
+	return animation_update(&ex->anim, ticks);
+}
+
+static void
+explosion_draw(struct drawable *dw)
+{
+	struct explosion *ex = dw->data;
+
+	animation_draw(&ex->anim, ex->dw.x, ex->dw.y);
+}
+
 static void
 explosion_finish(struct drawable *dw)
 {
@@ -90,67 +108,78 @@
 static void
 spawn(int x, int y)
 {
-	struct explosion *expl = alloc(1, sizeof (struct explosion));
+	struct explosion *ex = alloc_zero(1, sizeof (*ex));
+
+	animation_init(&ex->anim, &explosion_sprite, 15);
 
-	animation_init(&expl->anim, &explosion_sprite, 10);
-	animation_drawable(&expl->anim, &expl->dw, x, y);
+	ex->dw.data = ex;
+	ex->dw.x = x - (int)(explosion_sprite.cellw / 2);
+	ex->dw.y = y - (int)(explosion_sprite.cellh / 2);
+	ex->dw.update = explosion_update;
+	ex->dw.draw = explosion_draw;
+	ex->dw.finish = explosion_finish;
+
+	drawable_stack_add(&stack, &ex->dw);
+}
+
+static void
+handle(struct state *st, const union event *ev)
+{
+	(void)st;
 
-	/*
-	 * This work because the drawable->data field expects a struct animation
-	 * pointer which is the first member of struct explosion.
-	 *
-	 * Thus this "poor man inheritance" trick works perfectly in our case
-	 * and we simply need to free the whole explosion struct afterwards.
-	 */
-	expl->dw.finish = explosion_finish;
+	switch (ev->type) {
+	case EVENT_KEYDOWN:
+		switch (ev->key.key) {
+		case KEY_ESCAPE:
+			drawable_stack_finish(&stack);
+			break;
+		default:
+			break;
+		}
+		break;
+	case EVENT_CLICKDOWN:
+		spawn(ev->click.x, ev->click.y);
+		break;
+	case EVENT_QUIT:
+		game_quit();
+		break;
+	default:
+		break;
+	}
+}
 
-	drawable_stack_add(&stack, &expl->dw);
+static void
+update(struct state *st, unsigned int ticks)
+{
+	(void)st;
+
+	drawable_stack_update(&stack, ticks);
+
+}
+
+static void
+draw(struct state *st)
+{
+	(void)st;
+
+	painter_set_color(0xebede9ff);
+	painter_clear();
+	label_draw(&help);
+	drawable_stack_draw(&stack);
+	painter_present();
 }
 
 static void
 run(void)
 {
-	struct clock clock = {0};
-
-	clock_start(&clock);
-
-	for (;;) {
-		union event ev;
-		unsigned int elapsed = clock_elapsed(&clock);
-
-		clock_start(&clock);
+	struct state state = {
+		.handle = handle,
+		.update = update,
+		.draw = draw
+	};
 
-		while (event_poll(&ev)) {
-			switch (ev.type) {
-			case EVENT_KEYDOWN:
-				switch (ev.key.key) {
-				case KEY_ESCAPE:
-					drawable_stack_finish(&stack);
-					break;
-				default:
-					break;
-				}
-				break;
-			case EVENT_CLICKDOWN:
-				spawn(ev.click.x, ev.click.y);
-				break;
-			case EVENT_QUIT:
-				return;
-			default:
-				break;
-			}
-		}
-
-		drawable_stack_update(&stack, elapsed);
-		painter_set_color(0xebede9ff);
-		painter_clear();
-		label_draw(&help);
-		drawable_stack_draw(&stack);
-		painter_present();
-
-		if ((elapsed = clock_elapsed(&clock)) < 20)
-			delay(20 - elapsed);
-	}
+	game_switch(&state, true);
+	game_loop();
 }
 
 static void
--- a/examples/example-font.c	Tue Nov 10 17:32:12 2020 +0100
+++ b/examples/example-font.c	Wed Nov 11 16:09:43 2020 +0100
@@ -16,12 +16,13 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
-#include <core/clock.h>
 #include <core/core.h>
 #include <core/event.h>
 #include <core/font.h>
+#include <core/game.h>
 #include <core/painter.h>
 #include <core/panic.h>
+#include <core/state.h>
 #include <core/sys.h>
 #include <core/texture.h>
 #include <core/util.h>
@@ -45,6 +46,9 @@
 	0xc7cfccff,     /* Christian Grey. */
 };
 
+static int ci = 0;
+static enum font_style style = FONT_STYLE_ANTIALIASED;
+
 static void
 init(void)
 {
@@ -55,65 +59,69 @@
 }
 
 static void
+handle(struct state *st, const union event *ev)
+{
+	(void)st;
+
+	switch (ev->type) {
+	case EVENT_KEYDOWN:
+		switch (ev->key.key) {
+		case KEY_LEFT:
+			if (ci > 0)
+				ci--;
+			break;
+		case KEY_RIGHT:
+			if ((size_t)ci < NELEM(colors))
+				ci++;
+			break;
+		case KEY_SPACE:
+			if (style == FONT_STYLE_ANTIALIASED)
+				style = FONT_STYLE_NONE;
+			else
+				style = FONT_STYLE_ANTIALIASED;
+
+			theme_default()->fonts[THEME_FONT_INTERFACE]->style = style;
+		default:
+			break;
+		}
+		break;
+	case EVENT_QUIT:
+		game_quit();
+		break;
+	default:
+		break;
+	}
+}
+
+static void
+draw(struct state *st)
+{
+	(void)st;
+
+	struct font *font = theme_default()->fonts[THEME_FONT_INTERFACE];
+	struct texture tex;
+
+	painter_set_color(0xffffffff);
+	painter_clear();
+
+	if (!font_render(font, &tex, "Example of text. Use <Left>/<Right> to change color and <Space> to toggle antialiasing.", colors[ci]))
+		panic();
+
+	texture_draw(&tex, 10, 10);
+	painter_present();
+	texture_finish(&tex);
+}
+
+static void
 run(void)
 {
-	struct clock clock = {0};
-	struct font *font = theme_default()->fonts[THEME_FONT_INTERFACE];
-	int ci = 0;
-	enum font_style style = font->style;
-
-	clock_start(&clock);
-
-	for (;;) {
-		struct texture tex;
-		union event ev;
-		unsigned int elapsed = clock_elapsed(&clock);
-
-		clock_start(&clock);
+	struct state state = {
+		.handle = handle,
+		.draw = draw
+	};
 
-		while (event_poll(&ev)) {
-			switch (ev.type) {
-			case EVENT_KEYDOWN:
-				switch (ev.key.key) {
-				case KEY_LEFT:
-					if (ci > 0)
-						ci--;
-					break;
-				case KEY_RIGHT:
-					if ((size_t)ci < NELEM(colors))
-						ci++;
-					break;
-				case KEY_SPACE:
-					if (style == FONT_STYLE_ANTIALIASED)
-						style = FONT_STYLE_NONE;
-					else
-						style = FONT_STYLE_ANTIALIASED;
-				default:
-					break;
-				}
-				break;
-			case EVENT_QUIT:
-				return;
-			default:
-				break;
-			}
-		}
-
-		painter_set_color(0xffffffff);
-		painter_clear();
-
-		font->style = style;
-
-		if (!font_render(font, &tex, "Example of text. Use <Left>/<Right> to change color and <Space> to toggle antialiasing.", colors[ci]))
-			panic();
-
-		texture_draw(&tex, 10, 10);
-		painter_present();
-		texture_finish(&tex);
-
-		if ((elapsed = clock_elapsed(&clock)) < 20)
-			delay(20 - elapsed);
-	}
+	game_switch(&state, true);
+	game_loop();
 }
 
 static void
--- a/examples/example-gridmenu.c	Tue Nov 10 17:32:12 2020 +0100
+++ b/examples/example-gridmenu.c	Wed Nov 11 16:09:43 2020 +0100
@@ -16,11 +16,12 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
-#include <core/clock.h>
 #include <core/core.h>
 #include <core/event.h>
+#include <core/game.h>
 #include <core/painter.h>
 #include <core/panic.h>
+#include <core/state.h>
 #include <core/sys.h>
 #include <core/trace.h>
 #include <core/util.h>
@@ -53,9 +54,43 @@
 }
 
 static void
+handle(struct state *st, const union event *ev)
+{
+	switch (ev->type) {
+	case EVENT_QUIT:
+		game_quit();
+		break;
+	default:
+		gridmenu_handle(st->data, ev);
+		break;
+	}
+}
+
+static void
+update(struct state *st, unsigned int ticks)
+{
+	(void)ticks;
+
+	struct gridmenu *menu = st->data;
+
+	if (menu->state == GRIDMENU_STATE_ACTIVATED) {
+		tracef("selected index: %u", (unsigned int)menu->selected);
+		gridmenu_reset(menu);
+	}
+}
+
+static void
+draw(struct state *st)
+{
+	painter_set_color(0x4f8fbaff);
+	painter_clear();
+	gridmenu_draw(st->data);
+	painter_present();
+}
+
+static void
 run(void)
 {
-	struct clock clock = {0};
 	struct gridmenu menu = {
 		.menu = {
 			"Feu mineur",
@@ -76,42 +111,20 @@
 		.nrows = 3,
 		.ncols = 2
 	};
+	struct state state = {
+		.data = &menu,
+		.handle = handle,
+		.update = update,
+		.draw = draw,
+	};
 
-	clock_start(&clock);
 	align(ALIGN_CENTER, &menu.x, &menu.y, menu.w, menu.h, 0, 0, W, H);
 
 	/* Need to repaint at least once. */
 	gridmenu_repaint(&menu);
 
-	for (;;) {
-		union event ev;
-		unsigned int elapsed = clock_elapsed(&clock);
-
-		clock_start(&clock);
-
-		while (event_poll(&ev)) {
-			switch (ev.type) {
-			case EVENT_QUIT:
-				return;
-			default:
-				gridmenu_handle(&menu, &ev);
-				break;
-			}
-		}
-
-		if (menu.state == GRIDMENU_STATE_ACTIVATED) {
-			tracef("selected index: %u", (unsigned int)menu.selected);
-			gridmenu_reset(&menu);
-		}
-
-		painter_set_color(0x4f8fbaff);
-		painter_clear();
-		gridmenu_draw(&menu);
-		painter_present();
-
-		if ((elapsed = clock_elapsed(&clock)) < 20)
-			delay(20 - elapsed);
-	}
+	game_switch(&state, true);
+	game_loop();
 }
 
 int
--- a/examples/example-label.c	Tue Nov 10 17:32:12 2020 +0100
+++ b/examples/example-label.c	Wed Nov 11 16:09:43 2020 +0100
@@ -16,11 +16,12 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
-#include <core/clock.h>
 #include <core/core.h>
 #include <core/event.h>
+#include <core/game.h>
 #include <core/painter.h>
 #include <core/panic.h>
+#include <core/state.h>
 #include <core/sys.h>
 #include <core/util.h>
 #include <core/window.h>
@@ -94,6 +95,10 @@
 	}
 };
 
+static struct label mlabel = {
+	.text = "This one follows your mouse and is not aligned."
+};
+
 static void
 init(void)
 {
@@ -120,46 +125,48 @@
 }
 
 static void
+handle(struct state *st, const union event *ev)
+{
+	(void)st;
+
+	switch (ev->type) {
+	case EVENT_MOUSE:
+		mlabel.x = ev->mouse.x;
+		mlabel.y = ev->mouse.y;
+		break;
+	case EVENT_QUIT:
+		game_quit();
+		break;
+	default:
+		break;
+	}
+}
+
+static void
+draw(struct state *st)
+{
+	(void)st;
+
+	painter_set_color(0x4f8fbaff);
+	painter_clear();
+
+	for (size_t i = 0; i < NELEM(table); ++i)
+		label_draw(&table[i].label);
+
+	label_draw(&mlabel);
+	painter_present();
+}
+
+static void
 run(void)
 {
-	struct clock clock = {0};
-	struct label mlabel = {
-		.text = "This one follows your mouse and is not aligned."
+	struct state state = {
+		.handle = handle,
+		.draw = draw
 	};
 
-	clock_start(&clock);
-
-	for (;;) {
-		union event ev;
-		unsigned int elapsed = clock_elapsed(&clock);
-
-		clock_start(&clock);
-
-		while (event_poll(&ev)) {
-			switch (ev.type) {
-			case EVENT_MOUSE:
-				mlabel.x = ev.mouse.x;
-				mlabel.y = ev.mouse.y;
-				break;
-			case EVENT_QUIT:
-				return;
-			default:
-				break;
-			}
-		}
-
-		painter_set_color(0x4f8fbaff);
-		painter_clear();
-
-		for (size_t i = 0; i < NELEM(table); ++i)
-			label_draw(&table[i].label);
-
-		label_draw(&mlabel);
-		painter_present();
-
-		if ((elapsed = clock_elapsed(&clock)) < 20)
-			delay(20 - elapsed);
-	}
+	game_switch(&state, true);
+	game_loop();
 }
 
 int
--- a/examples/example-message.c	Tue Nov 10 17:32:12 2020 +0100
+++ b/examples/example-message.c	Wed Nov 11 16:09:43 2020 +0100
@@ -16,13 +16,12 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
-#include <string.h>
-
-#include <core/clock.h>
 #include <core/core.h>
 #include <core/event.h>
+#include <core/game.h>
 #include <core/painter.h>
 #include <core/panic.h>
+#include <core/state.h>
 #include <core/sys.h>
 #include <core/util.h>
 #include <core/window.h>
@@ -61,39 +60,48 @@
 }
 
 static void
+handle(struct state *st, const union event *ev)
+{
+	switch (ev->type) {
+	case EVENT_QUIT:
+		game_quit();
+		break;
+	default:
+		message_handle(st->data, ev);
+		break;
+	}
+}
+
+static void
+update(struct state *st, unsigned int ticks)
+{
+	if (message_update(st->data, ticks))
+		game_quit();
+}
+
+static void
+draw(struct state *st)
+{
+	painter_set_color(0xffffffff);
+	painter_clear();
+	message_draw(st->data);
+	painter_present();
+}
+
+static void
 run(struct message *msg)
 {
-	struct clock clock = {0};
+	struct state state = {
+		.data = msg,
+		.handle = handle,
+		.update = update,
+		.draw = draw
+	};
 
 	message_start(msg);
-	clock_start(&clock);
 
-	while (msg->state) {
-		union event ev;
-		unsigned int elapsed = clock_elapsed(&clock);
-
-		clock_start(&clock);
-
-		while (event_poll(&ev)) {
-			switch (ev.type) {
-			case EVENT_QUIT:
-				msg->state = MESSAGE_STATE_NONE;
-				break;
-			default:
-				message_handle(msg, &ev);
-				break;
-			}
-		}
-
-		message_update(msg, elapsed);
-		painter_set_color(0xffffffff);
-		painter_clear();
-		message_draw(msg);
-		painter_present();
-
-		if ((elapsed = clock_elapsed(&clock)) < 20)
-			delay(20 - elapsed);
-	}
+	game_switch(&state, true);
+	game_loop();
 }
 
 static void
@@ -289,7 +297,7 @@
 	};
 
 	/* Borrow default theme and change its frame drawing. */
-	memcpy(&theme, theme_default(), sizeof (theme));
+	theme_shallow(&theme, NULL);
 	theme.draw_frame = my_draw_frame;
 	theme.colors[THEME_COLOR_NORMAL] = 0x0000ffff;
 
--- a/examples/example-sprite.c	Tue Nov 10 17:32:12 2020 +0100
+++ b/examples/example-sprite.c	Wed Nov 11 16:09:43 2020 +0100
@@ -18,15 +18,16 @@
 
 #include <stdio.h>
 
-#include <core/clock.h>
 #include <core/core.h>
 #include <core/event.h>
+#include <core/game.h>
 #include <core/image.h>
 #include <core/key.h>
 #include <core/painter.h>
 #include <core/panic.h>
 #include <core/image.h>
 #include <core/sprite.h>
+#include <core/state.h>
 #include <core/texture.h>
 #include <core/util.h>
 #include <core/window.h>
@@ -73,63 +74,68 @@
 }
 
 static void
-run(void)
+handle(struct state *st, const union event *ev)
 {
-	struct clock clock = {0};
+	(void)st;
+
+	switch (ev->type) {
+	case EVENT_KEYDOWN:
+		switch (ev->key.key) {
+		case KEY_LEFT:
+			if (column > 0)
+				column--;
+			break;
+		case KEY_RIGHT:
+			if (column + 1 < sprite.ncols)
+				column++;
+			break;
+		case KEY_UP:
+			if (row > 0)
+				row--;
+			break;
+		case KEY_DOWN:
+			if (row + 1 < sprite.nrows)
+				row++;
+			break;
+		default:
+			break;
+		}
+
+		changed();
+		break;
+	case EVENT_QUIT:
+		game_quit();
+		break;
+	default:
+		break;
+	}
+}
+
+static void
+draw(struct state *st)
+{
+	(void)st;
+
 	int x, y;
 
-	clock_start(&clock);
-	changed();
-
-	for (;;) {
-		union event ev;
-		unsigned int elapsed = clock_elapsed(&clock);
-
-		clock_start(&clock);
+	painter_set_color(0xebede9ff);
+	painter_clear();
+	align(ALIGN_CENTER, &x, &y, sprite.cellw, sprite.cellh, 0, 0, W, H);
+	sprite_draw(&sprite, row, column, x, y);
+	label_draw(&help);
+	painter_present();
+}
 
-		while (event_poll(&ev)) {
-			switch (ev.type) {
-			case EVENT_KEYDOWN:
-				switch (ev.key.key) {
-				case KEY_LEFT:
-					if (column > 0)
-						column--;
-					break;
-				case KEY_RIGHT:
-					if (column + 1 < sprite.ncols)
-						column++;
-					break;
-				case KEY_UP:
-					if (row > 0)
-						row--;
-					break;
-				case KEY_DOWN:
-					if (row + 1 < sprite.nrows)
-						row++;
-					break;
-				default:
-					break;
-				}
+static void
+run(void)
+{
+	struct state state = {
+		.handle = handle,
+		.draw = draw
+	};
 
-				changed();
-				break;
-			case EVENT_QUIT:
-				return;
-			default:
-				break;
-			}
-		}
-
-		painter_set_color(0xebede9ff);
-		painter_clear();
-		align(ALIGN_CENTER, &x, &y, sprite.cellw, sprite.cellh, 0, 0, W, H);
-		sprite_draw(&sprite, row, column, x, y);
-		label_draw(&help);
-		painter_present();
-
-		if ((elapsed = clock_elapsed(&clock)) < 20)
-			delay(20 - elapsed);
-	}
+	game_switch(&state, true);
+	game_loop();
 }
 
 static void
--- a/examples/example-trace.c	Tue Nov 10 17:32:12 2020 +0100
+++ b/examples/example-trace.c	Wed Nov 11 16:09:43 2020 +0100
@@ -16,13 +16,14 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
-#include <core/clock.h>
 #include <core/core.h>
 #include <core/event.h>
+#include <core/game.h>
 #include <core/sys.h>
 #include <core/window.h>
 #include <core/painter.h>
 #include <core/panic.h>
+#include <core/state.h>
 #include <core/trace.h>
 #include <core/util.h>
 
@@ -46,49 +47,62 @@
 }
 
 static void
+handle(struct state *st, const union event *ev)
+{
+	(void)st;
+
+	switch (ev->type) {
+	case EVENT_KEYDOWN:
+		switch (ev->key.key) {
+		case KEY_ESCAPE:
+			trace_hud_clear();
+			break;
+		default:
+			tracef("keydown pressed: %d", ev->key.key);
+			break;
+		}
+		break;
+	case EVENT_CLICKDOWN:
+		tracef("click at %d,%d", ev->click.x, ev->click.y);
+		break;
+	case EVENT_QUIT:
+		game_quit();
+		break;
+	default:
+		break;
+	}
+}
+
+static void
+update(struct state *st, unsigned int ticks)
+{
+	(void)st;
+
+	trace_hud_update(ticks);
+}
+
+static void
+draw(struct state *st)
+{
+	(void)st;
+
+	painter_set_color(0x4f8fbaff);
+	painter_clear();
+	trace_hud_draw();
+	painter_present();
+}
+
+static void
 run(void)
 {
-	struct clock clock = {0};
-
-	clock_start(&clock);
-
-	for (;;) {
-		union event ev;
-		unsigned int elapsed = clock_elapsed(&clock);
-
-		clock_start(&clock);
+	struct state state = {
+		.handle = handle,
+		.update = update,
+		.draw = draw
+	};
 
-		while (event_poll(&ev)) {
-			switch (ev.type) {
-			case EVENT_KEYDOWN:
-				switch (ev.key.key) {
-				case KEY_ESCAPE:
-					trace_hud_clear();
-					break;
-				default:
-					tracef("keydown pressed: %d", ev.key.key);
-					break;
-				}
-				break;
-			case EVENT_CLICKDOWN:
-				tracef("click at %d,%d", ev.click.x, ev.click.y);
-				break;
-			case EVENT_QUIT:
-				return;
-			default:
-				break;
-			}
-		}
-
-		painter_set_color(0x4f8fbaff);
-		painter_clear();
-		trace_hud_update(elapsed);
-		trace_hud_draw();
-		painter_present();
-
-		if ((elapsed = clock_elapsed(&clock)) < 20)
-			delay(20 - elapsed);
-	}
+	game_switch(&state, true);
+	game_loop();
 }
 
 static void
--- a/examples/example-ui.c	Tue Nov 10 17:32:12 2020 +0100
+++ b/examples/example-ui.c	Wed Nov 11 16:09:43 2020 +0100
@@ -17,12 +17,13 @@
  */
 
 #include <core/action.h>
-#include <core/clock.h>
 #include <core/core.h>
 #include <core/event.h>
+#include <core/game.h>
 #include <core/maths.h>
 #include <core/panic.h>
 #include <core/painter.h>
+#include <core/state.h>
 #include <core/sys.h>
 #include <core/util.h>
 #include <core/window.h>
@@ -226,62 +227,79 @@
 }
 
 static void
+handle(struct state *st, const union event *ev)
+{
+	(void)st;
+
+	switch (ev->type) {
+	case EVENT_QUIT:
+		game_quit();
+		break;
+	case EVENT_MOUSE:
+		if (ui.motion.active) {
+			ui.panel.frame.x += ev->mouse.x - ui.motion.x;
+			ui.panel.frame.y += ev->mouse.y - ui.motion.y;
+			ui.motion.x = ev->mouse.x;
+			ui.motion.y = ev->mouse.y;
+			resize();
+		}
+		break;
+	case EVENT_CLICKDOWN:
+		if (headerclick(ev->click.x, ev->click.y)) {
+			ui.motion.active = true;
+			ui.motion.x = ev->click.x;
+			ui.motion.y = ev->click.y;
+			window_set_cursor(WINDOW_CURSOR_SIZE);
+		}
+		else
+			action_stack_handle(&ui.st, ev);
+		break;
+	case EVENT_CLICKUP:
+		ui.motion.active = false;
+		window_set_cursor(WINDOW_CURSOR_ARROW);
+		/* Fallthrough. */
+	default:
+		action_stack_handle(&ui.st, ev);
+		break;
+	}
+}
+
+static void
+update(struct state *st, unsigned int ticks)
+{
+	(void)st;
+
+	if (ui.quit.button.state == BUTTON_STATE_ACTIVATED)
+		game_quit();
+	else
+		action_stack_update(&ui.st, ticks);
+}
+
+static void
+draw(struct state *st)
+{
+	(void)st;
+
+	painter_set_color(0xffffffff);
+	painter_clear();
+	action_stack_draw(&ui.st);
+	painter_present();
+}
+
+static void
 run(void)
 {
-	struct clock clock = {0};
-
-	clock_start(&clock);
+	struct state state = {
+		.handle = handle,
+		.update = update,
+		.draw = draw
+	};
 
 	prepare();
 	resize();
 
-	while (ui.quit.button.state != BUTTON_STATE_ACTIVATED) {
-		unsigned int elapsed = clock_elapsed(&clock);
-
-		clock_start(&clock);
-
-		for (union event ev; event_poll(&ev); ) {
-			switch (ev.type) {
-			case EVENT_QUIT:
-				return;
-			case EVENT_MOUSE:
-				if (ui.motion.active) {
-					ui.panel.frame.x += ev.mouse.x - ui.motion.x;
-					ui.panel.frame.y += ev.mouse.y - ui.motion.y;
-					ui.motion.x = ev.mouse.x;
-					ui.motion.y = ev.mouse.y;
-					resize();
-				}
-				break;
-			case EVENT_CLICKDOWN:
-				if (headerclick(ev.click.x, ev.click.y)) {
-					ui.motion.active = true;
-					ui.motion.x = ev.click.x;
-					ui.motion.y = ev.click.y;
-					window_set_cursor(WINDOW_CURSOR_SIZE);
-				}
-				else
-					action_stack_handle(&ui.st, &ev);
-				break;
-			case EVENT_CLICKUP:
-				ui.motion.active = false;
-				window_set_cursor(WINDOW_CURSOR_ARROW);
-				/* Fallthrough. */
-			default:
-				action_stack_handle(&ui.st, &ev);
-				break;
-			}
-		}
-
-		painter_set_color(0xffffffff);
-		painter_clear();
-		action_stack_update(&ui.st, elapsed);
-		action_stack_draw(&ui.st);
-		painter_present();
-
-		if ((elapsed = clock_elapsed(&clock)) < 20)
-			delay(20 - elapsed);
-	}
+	game_switch(&state, true);
+	game_loop();
 }
 
 static void
--- a/libcore/core/game.c	Tue Nov 10 17:32:12 2020 +0100
+++ b/libcore/core/game.c	Wed Nov 11 16:09:43 2020 +0100
@@ -20,9 +20,12 @@
 #include <stddef.h>
 #include <string.h>
 
+#include "clock.h"
+#include "event.h"
 #include "game.h"
 #include "state.h"
 #include "painter.h"
+#include "util.h"
 
 struct game game;
 
@@ -94,8 +97,33 @@
 {
 	if (game.state && !(game.inhibit & INHIBIT_STATE_DRAW))
 		state_draw(game.state);
+}
 
-	painter_present();
+void
+game_loop(void)
+{
+	struct clock clock = {0};
+	double frametimemax = 1000.0 / 60.0;
+	unsigned int frametime = 0;
+
+	while (game.state) {
+		clock_start(&clock);
+
+		for (union event ev; event_poll(&ev); )
+			game_handle(&ev);
+
+		game_update(frametime);
+		game_draw();
+
+		/*
+		 * Sleep not the full left time to allow context switches and
+		 * such.
+		 */
+		if (frametime < frametimemax)
+			delay((frametimemax - frametime) / 4);
+
+		frametime = clock_elapsed(&clock);
+	}
 }
 
 void
--- a/libcore/core/game.h	Tue Nov 10 17:32:12 2020 +0100
+++ b/libcore/core/game.h	Wed Nov 11 16:09:43 2020 +0100
@@ -87,6 +87,13 @@
 game_draw(void);
 
 /**
+ * Start a game loop that calls game_handle, game_update and game_draw until
+ * game_quit has been called.
+ */
+void
+game_loop(void);
+
+/**
  * Stop the game.
  *
  * This will effectively stop the current state but the main loop may continue