changeset 631:bb67f935a93f

rpg: rework a little tileset/map loaders
author David Demelier <markand@malikania.fr>
date Sun, 27 Aug 2023 11:28:35 +0200
parents 8d8fe99b357c
children 3d238b43a9aa
files examples/example-map/example-map.c examples/example-tileset/example-tileset.c libmlk-rpg/mlk/rpg/loader-file_p.c libmlk-rpg/mlk/rpg/loader-file_p.h libmlk-rpg/mlk/rpg/map-loader-file.c libmlk-rpg/mlk/rpg/map-loader-file.h libmlk-rpg/mlk/rpg/map-loader.c libmlk-rpg/mlk/rpg/map-loader.h libmlk-rpg/mlk/rpg/tileset-loader-file.c libmlk-rpg/mlk/rpg/tileset-loader-file.h libmlk-rpg/mlk/rpg/tileset-loader.c libmlk-rpg/mlk/rpg/tileset-loader.h tests/test-tileset.c
diffstat 13 files changed, 356 insertions(+), 171 deletions(-) [+]
line wrap: on
line diff
--- a/examples/example-map/example-map.c	Sun Aug 27 11:04:57 2023 +0200
+++ b/examples/example-map/example-map.c	Sun Aug 27 11:28:35 2023 +0200
@@ -59,7 +59,7 @@
 static struct mlk_tileset_loader_file tileset_loader;
 static struct mlk_tileset tileset;
 
-static struct mlk_map_loader map_loader;
+static struct mlk_map_loader_file map_loader;
 static struct mlk_map map;
 
 static struct {
@@ -165,11 +165,11 @@
 	 * on disk by default which we would like to avoid. We override the
 	 * init_tileset function.
 	 */
-	mlk_map_loader_file_init(&map_loader, "");
-	map_loader.new_tileset = map_new_tileset;
-	map_loader.new_texture = map_new_texture;
+	mlk_map_loader_file_init(&map_loader, &tileset_loader.iface, "");
+	map_loader.iface.new_tileset = map_new_tileset;
+	map_loader.iface.new_texture = map_new_texture;
 
-	if (mlk_map_loader_openmem(&map_loader, &map, assets_maps_world, sizeof (assets_maps_world)) < 0)
+	if (mlk_map_loader_openmem(&map_loader.iface, &map, assets_maps_world, sizeof (assets_maps_world)) < 0)
 		mlk_panic();
 
 	mlk_map_init(&map);
@@ -250,8 +250,8 @@
 quit(void)
 {
 	mlk_map_finish(&map);
-	mlk_map_loader_file_finish(&map_loader);
-	mlk_tileset_loader_file_finish(&tileset_loader);
+	mlk_map_loader_finish(&map_loader.iface);
+	mlk_tileset_loader_finish(&tileset_loader.iface);
 	mlk_example_finish();
 }
 
--- a/examples/example-tileset/example-tileset.c	Sun Aug 27 11:04:57 2023 +0200
+++ b/examples/example-tileset/example-tileset.c	Sun Aug 27 11:28:35 2023 +0200
@@ -201,7 +201,7 @@
 static void
 quit(void)
 {
-	mlk_tileset_loader_file_finish(&loader);
+	mlk_tileset_loader_finish(&loader.iface);
 	mlk_example_finish();
 }
 
--- a/libmlk-rpg/mlk/rpg/loader-file_p.c	Sun Aug 27 11:04:57 2023 +0200
+++ b/libmlk-rpg/mlk/rpg/loader-file_p.c	Sun Aug 27 11:28:35 2023 +0200
@@ -161,13 +161,19 @@
 }
 
 void
-mlk__loader_file_free(struct mlk__loader_file *loader)
+mlk__loader_file_clear(struct mlk__loader_file *loader)
 {
-	assert(loader);
-
 	free_textures(loader);
 	free_sprites(loader);
 	free_animations(loader);
+}
 
+void
+mlk__loader_file_free(struct mlk__loader_file *loader)
+{
+	if (!loader)
+		return;
+
+	mlk__loader_file_clear(loader);
 	mlk_alloc_free(loader);
 }
--- a/libmlk-rpg/mlk/rpg/loader-file_p.h	Sun Aug 27 11:04:57 2023 +0200
+++ b/libmlk-rpg/mlk/rpg/loader-file_p.h	Sun Aug 27 11:28:35 2023 +0200
@@ -41,6 +41,9 @@
 mlk__loader_file_animation_new(struct mlk__loader_file *loader);
 
 void
+mlk__loader_file_clear(struct mlk__loader_file *loader);
+
+void
 mlk__loader_file_free(struct mlk__loader_file *loader);
 
 #endif /* !MLK_RPG_LOADER_FILE_P_H */
--- a/libmlk-rpg/mlk/rpg/map-loader-file.c	Sun Aug 27 11:04:57 2023 +0200
+++ b/libmlk-rpg/mlk/rpg/map-loader-file.c	Sun Aug 27 11:28:35 2023 +0200
@@ -26,174 +26,152 @@
 #include <mlk/core/image.h>
 #include <mlk/core/sprite.h>
 #include <mlk/core/texture.h>
+#include <mlk/core/util.h>
 
 #include "loader-file_p.h"
 #include "map-loader-file.h"
-#include "map-loader.h"
 #include "map.h"
 #include "tileset-loader-file.h"
 #include "tileset-loader.h"
-#include "tileset.h"
 
-struct self {
-	/* Resources allocator. */
-	struct mlk__loader_file *loader;
-
-	/* Own allocated tiles. */
-	unsigned int *tiles[MLK_MAP_LAYER_TYPE_LAST];
+#define THIS(loader) \
+	MLK_CONTAINER_OF(loader, struct mlk_map_loader_file, iface)
 
-	/*
-	 * We use a tileset file loader if new_tileset function isn't present in
-	 * this map loader.
-	 */
-	struct mlk_tileset_loader_file tileset_loader;
-	struct mlk_tileset tileset;
-
-	/* Own allocated blocks. */
-	struct mlk_map_block *blocks;
-};
+static void
+trash(struct mlk_map_loader_file *file)
+{
+	mlk__loader_file_clear(file->lf);
 
-static struct self *
-self_new(const char *path)
-{
-	struct self *self;
-
-	if (!(self = mlk_alloc_new0(1, sizeof (*self))))
-		return NULL;
-	if (!(self->loader = mlk__loader_file_new(path))) {
-		mlk_alloc_free(self);
-		return NULL;
+	for (int i = 0; i < MLK_MAP_LAYER_TYPE_LAST; ++i) {
+		mlk_alloc_free(file->tiles[i]);
+		file->tiles[i] = NULL;
 	}
 
-	return self;
-}
+	mlk_alloc_free(file->blocks);
 
-static void
-self_free(struct self *self)
-{
-	mlk__loader_file_free(self->loader);
-
-	for (int i = 0; i < MLK_MAP_LAYER_TYPE_LAST; ++i)
-		mlk_alloc_free(self->tiles[i]);
-
-	mlk_tileset_loader_file_finish(&self->tileset_loader);
-	mlk_alloc_free(self->blocks);
+	file->blocks = NULL;
 }
 
 static struct mlk_texture *
-new_texture(struct mlk_map_loader *loader,
-            struct mlk_map *map,
-            const char *ident)
+new_texture(struct mlk_map_loader *self, struct mlk_map *map, const char *ident)
 {
 	(void)map;
 
-	struct self *self = loader->data;
+	struct mlk_map_loader_file *file = THIS(self);
 
-	return mlk__loader_file_texture_open(self->loader, ident);
+	return mlk__loader_file_texture_open(file->lf, ident);
 }
 
 static struct mlk_sprite *
-new_sprite(struct mlk_map_loader *loader, struct mlk_map *map)
+new_sprite(struct mlk_map_loader *self, struct mlk_map *map)
 {
 	(void)map;
 
-	struct self *self = loader->data;
+	struct mlk_map_loader_file *file = THIS(self);
 
-	return mlk__loader_file_sprite_new(self->loader);
+	return mlk__loader_file_sprite_new(file->lf);
 }
 
 static struct mlk_tileset *
-new_tileset(struct mlk_map_loader *loader,
-            struct mlk_map *map,
-            const char *ident)
+new_tileset(struct mlk_map_loader *self, struct mlk_map *map, const char *ident)
 {
 	(void)map;
 
-	struct self *self = loader->data;
-	char path[MLK_PATH_MAX];
+	struct mlk_map_loader_file *file = THIS(self);
+	char path[MLK_PATH_MAX] = {};
 
-	snprintf(path, sizeof (path), "%s/%s", mlk__loader_file_directory(self->loader), ident);
+	snprintf(path, sizeof (path), "%s/%s", mlk__loader_file_directory(file->lf), ident);
 
 	/*
-	 * Just make sure that we don't leak in case tileset directory is listed
-	 * more than once.
+	 * Cleanup existing resources in case the tileset appears multiple times
+	 * in the map.
 	 */
-	mlk_tileset_loader_file_finish(&self->tileset_loader);
-	mlk_tileset_loader_file_init(&self->tileset_loader, path);
+	mlk_tileset_loader_clear(file->tileset_loader, &file->tileset);
 
-	if (mlk_tileset_loader_open(&self->tileset_loader.iface, &self->tileset, path) < 0)
+	if (mlk_tileset_loader_open(file->tileset_loader, &file->tileset, path) < 0)
 		return NULL;
 
-	return &self->tileset;
+	return &file->tileset;
 }
 
 static unsigned int *
-new_tiles(struct mlk_map_loader *loader,
+new_tiles(struct mlk_map_loader *self,
           struct mlk_map *map,
           enum mlk_map_layer_type type,
           size_t n)
 {
 	(void)map;
 
-	struct self *self = loader->data;
+	struct mlk_map_loader_file *file = THIS(self);
 
-	return self->tiles[type] = mlk_alloc_new0(n, sizeof (unsigned int));
+	return file->tiles[type] = mlk_alloc_new0(n, sizeof (unsigned int));
 }
 
 static struct mlk_map_block *
-expand_blocks(struct mlk_map_loader *loader,
+expand_blocks(struct mlk_map_loader *self,
               struct mlk_map *map,
               struct mlk_map_block *blocks,
               size_t blocksz)
 {
 	(void)map;
 
-	struct self *self = loader->data;
+	struct mlk_map_loader_file *file = THIS(self);
 	struct mlk_map_block *ptr;
 
-	if (!self->blocks)
+	if (!file->blocks)
 		ptr = mlk_alloc_new0(1, sizeof (*ptr));
 	else
-		ptr = mlk_alloc_expand(self->blocks, blocksz);
+		ptr = mlk_alloc_expand(file->blocks, blocksz);
 
 	if (ptr)
-		self->blocks = blocks;
+		file->blocks = blocks;
 
 	return ptr;
 }
 
+static void
+clear(struct mlk_map_loader *self, struct mlk_map *map)
+{
+	(void)map;
+
+	struct mlk_map_loader_file *file = THIS(self);
+
+	trash(file);
+}
+
+static void
+finish(struct mlk_map_loader *self)
+{
+	struct mlk_map_loader_file *file = THIS(self);
+
+	trash(file);
+	mlk__loader_file_free(file->lf);
+
+	file->lf = NULL;
+}
+
 int
-mlk_map_loader_file_init(struct mlk_map_loader *loader, const char *filename)
+mlk_map_loader_file_init(struct mlk_map_loader_file *file,
+                         struct mlk_tileset_loader *tileset_loader,
+                         const char *filename)
 {
-	assert(loader);
+	assert(file);
+	assert(tileset_loader);
 	assert(filename);
 
-	struct self *self;
+	memset(file, 0, sizeof (*file));
 
-	memset(loader, 0, sizeof (*loader));
-
-	if (!(self = self_new(filename)))
+	if (!(file->lf = mlk__loader_file_new(filename)))
 		return -1;
 
-	loader->data = self;
-	loader->new_tileset = new_tileset;
-	loader->new_texture = new_texture;
-	loader->new_sprite = new_sprite;
-	loader->new_tiles = new_tiles;
-	loader->expand_blocks = expand_blocks;
+	file->tileset_loader = tileset_loader;
+	file->iface.new_tileset = new_tileset;
+	file->iface.new_texture = new_texture;
+	file->iface.new_sprite = new_sprite;
+	file->iface.new_tiles = new_tiles;
+	file->iface.expand_blocks = expand_blocks;
+	file->iface.clear = clear;
+	file->iface.finish = finish;
 
 	return 0;
 }
-
-void
-mlk_map_loader_file_finish(struct mlk_map_loader *loader)
-{
-	assert(loader);
-
-	struct self *self = loader->data;
-
-	if (self)
-		self_free(self);
-
-	memset(loader, 0, sizeof (*loader));
-}
--- a/libmlk-rpg/mlk/rpg/map-loader-file.h	Sun Aug 27 11:04:57 2023 +0200
+++ b/libmlk-rpg/mlk/rpg/map-loader-file.h	Sun Aug 27 11:28:35 2023 +0200
@@ -28,21 +28,20 @@
  * on the go depending on the file map content.
  *
  * For convenience, this loader will also use ::mlk_tileset_loader for loading
- * and associating the tileset. If this behavior is not desired the function
- * ::mlk_map_loader::new_tileset can be overriden after calling
- * ::mlk_map_loader_file_init.
+ * and associating the tileset.
  *
  * Example of use:
  *
  * ```c
- * struct mlk_map_loader loader;
+ * struct mlk_tileset_loader_file tileset_loader;
+ * struct mlk_map_loader_file map_loader;
  * struct mlk_map map;
  *
  * // The loader needs to know the map location to retrieve relative files.
  * const char *map_path = "/path/to/world.map";
  *
  * // Initialize the loader, it will be filled with custom internal functions.
- * if (mlk_map_loader_file_init(&loader, map_path) < 0)
+ * if (mlk_map_loader_file_init(&map_loader, &tileset_loader.iface, map_path) < 0)
  *     mlk_panic();
  *
  * // Load the map from the file on disk.
@@ -54,11 +53,33 @@
  *
  * // Destroy the resources.
  * mlk_map_finish(&map);
- * mlk_map_loader_file_finish(&loader);
+ * mlk_map_loader_file_finish(&map_loader);
+ * mlk_tileset_loader_file_finish(&tileset_loader);
  * ```
  */
 
-struct mlk_map_loader;
+#include "map-loader.h"
+#include "map.h"
+#include "tileset.h"
+
+struct mlk_tileset_loader;
+
+struct mlk_map_loader_file {
+	/**
+	 * (read-write)
+	 *
+	 * Underlying map loader.
+	 */
+	struct mlk_map_loader iface;
+
+	/** \cond MLK_PRIVATE_DECLS */
+	unsigned int *tiles[MLK_MAP_LAYER_TYPE_LAST];
+	struct mlk_tileset_loader *tileset_loader;
+	struct mlk_tileset tileset;
+	struct mlk_map_block *blocks;
+	struct mlk__loader_file *lf;
+	/** \endcond MLK_PRIVATE_DECLS */
+};
 
 /**
  * Initialize the loader with internal functions and internal data to allocate
@@ -67,23 +88,19 @@
  * After loading the map with this underlying loader, it should be kept until
  * the map is no longer used.
  *
- * \pre loader != NULL
+ * The tileset loader is borrowed and not destroyed when this loader is
+ * destroyed, user must do it manually.
+ *
+ * \pre file != NULL
  * \pre filename != NULL
- * \param loader the abstract loader interface
+ * \param file the file loader
+ * \param tileset_loader tileset loader interface (borrowed)
  * \param filename path to the map file
  * \return 0 on success or -1 on error
  */
 int
-mlk_map_loader_file_init(struct mlk_map_loader *loader, const char *filename);
-
-/**
- * Cleanup allocated resources by this file loader.
- *
- * \pre file != NULL
- * \param file the file loader
- * \warning the map loaded with this loader must not be used
- */
-void
-mlk_map_loader_file_finish(struct mlk_map_loader *file);
+mlk_map_loader_file_init(struct mlk_map_loader_file *file,
+                         struct mlk_tileset_loader *tileset_loader,
+                         const char *filename);
 
 #endif /* !MLK_RPG_MAP_LOADER_FILE_H */
--- a/libmlk-rpg/mlk/rpg/map-loader.c	Sun Aug 27 11:04:57 2023 +0200
+++ b/libmlk-rpg/mlk/rpg/map-loader.c	Sun Aug 27 11:28:35 2023 +0200
@@ -327,3 +327,25 @@
 
 	return parse(loader, map, fp);
 }
+
+void
+mlk_map_loader_clear(struct mlk_map_loader *loader,
+                     struct mlk_map *map)
+{
+	assert(loader);
+	assert(map);
+
+	if (loader->clear)
+		loader->clear(loader, map);
+
+	memset(map, 0, sizeof (*map));
+}
+
+void
+mlk_map_loader_finish(struct mlk_map_loader *loader)
+{
+	assert(loader);
+
+	if (loader->finish)
+		loader->finish(loader);
+}
--- a/libmlk-rpg/mlk/rpg/map-loader.h	Sun Aug 27 11:04:57 2023 +0200
+++ b/libmlk-rpg/mlk/rpg/map-loader.h	Sun Aug 27 11:28:35 2023 +0200
@@ -140,6 +140,28 @@
                                                 struct mlk_map *map,
                                                 struct mlk_map_block *blocks,
                                                 size_t blocksz);
+
+	/**
+	 * (read-write, optional)
+	 *
+	 * Cleanup resources allocated for this mao.
+	 *
+	 * This is different than finalizing the loader itself, it should be
+	 * re-usable after calling this function.
+	 *
+	 * \param self this loader
+	 * \param map the underlying map to cleanup
+	 */
+	void (*clear)(struct mlk_map_loader *self, struct mlk_map *map);
+
+	/**
+	 * (read-write, optional)
+	 *
+	 * Cleanup the map loader.
+	 *
+	 * \param self this loader
+	 */
+	void (*finish)(struct mlk_map_loader *self);
 };
 
 #if defined(__cplusplus)
@@ -179,6 +201,34 @@
                        const void *data,
                        size_t datasz);
 
+/**
+ * Cleanup data for this map.
+ *
+ * The loader is re-usable after calling this function to load a new map.
+ *
+ * Invokes ::mlk_map_loader::clear.
+ *
+ * \pre loader != NULL
+ * \param loader the loader interface
+ * \param map the map used with this loader
+ */
+void
+mlk_map_loader_clear(struct mlk_map_loader *loader, struct mlk_map *map);
+
+/**
+ * Finalize the loader itself.
+ *
+ * The underlying interface should also clear map resources by convenience
+ * if the user forgot to call ::mlk_map_loader_clear.
+ *
+ * Invokes ::mlk_map_loader::finish.
+ *
+ * \pre loader != NULL
+ * \param loader the loader to finalize
+ */
+void
+mlk_map_loader_finish(struct mlk_map_loader *loader);
+
 #if defined(__cplusplus)
 }
 #endif
--- a/libmlk-rpg/mlk/rpg/tileset-loader-file.c	Sun Aug 27 11:04:57 2023 +0200
+++ b/libmlk-rpg/mlk/rpg/tileset-loader-file.c	Sun Aug 27 11:28:35 2023 +0200
@@ -1,5 +1,5 @@
 /*
- * tileset-file.c -- tileset file loader implementation
+ * tileset-loader-file.c -- tileset file loader implementation
  *
  * Copyright (c) 2020-2023 David Demelier <markand@malikania.fr>
  *
@@ -34,6 +34,18 @@
 #define THIS(loader) \
 	MLK_CONTAINER_OF(loader, struct mlk_tileset_loader_file, iface)
 
+static void
+trash(struct mlk_tileset_loader_file *file)
+{
+	mlk__loader_file_clear(file->lf);
+
+	mlk_alloc_free(file->tilecollisions);
+	mlk_alloc_free(file->tileanimations);
+
+	file->tilecollisions = NULL;
+	file->tileanimations = NULL;
+}
+
 static void *
 expand(void **array, size_t n, size_t w)
 {
@@ -51,37 +63,37 @@
 }
 
 static struct mlk_texture *
-new_texture(struct mlk_tileset_loader *loader, struct mlk_tileset *tileset, const char *ident)
+new_texture(struct mlk_tileset_loader *self, struct mlk_tileset *tileset, const char *ident)
 {
 	(void)tileset;
 
-	struct mlk_tileset_loader_file *self = THIS(loader);
+	struct mlk_tileset_loader_file *file = THIS(self);
 
-	return mlk__loader_file_texture_open(self->lf, ident);
+	return mlk__loader_file_texture_open(file->lf, ident);
 }
 
 static struct mlk_sprite *
-new_sprite(struct mlk_tileset_loader *loader, struct mlk_tileset *tileset)
+new_sprite(struct mlk_tileset_loader *self, struct mlk_tileset *tileset)
 {
 	(void)tileset;
 
-	struct mlk_tileset_loader_file *self = THIS(loader);
+	struct mlk_tileset_loader_file *file = THIS(self);
 
-	return mlk__loader_file_sprite_new(self->lf);
+	return mlk__loader_file_sprite_new(file->lf);
 }
 
 static struct mlk_animation *
-new_animation(struct mlk_tileset_loader *loader, struct mlk_tileset *tileset)
+new_animation(struct mlk_tileset_loader *self, struct mlk_tileset *tileset)
 {
 	(void)tileset;
 
-	struct mlk_tileset_loader_file *self = THIS(loader);
+	struct mlk_tileset_loader_file *file = THIS(self);
 
-	return mlk__loader_file_animation_new(self->lf);
+	return mlk__loader_file_animation_new(file->lf);
 }
 
 struct mlk_tileset_collision *
-expand_collisions(struct mlk_tileset_loader *loader,
+expand_collisions(struct mlk_tileset_loader *self,
                   struct mlk_tileset *tileset,
                   struct mlk_tileset_collision *array,
                   size_t arraysz)
@@ -89,13 +101,13 @@
 	(void)tileset;
 	(void)array;
 
-	struct mlk_tileset_loader_file *self = THIS(loader);
+	struct mlk_tileset_loader_file *file = THIS(self);
 
-	return expand((void **)&self->tilecollisions, arraysz, sizeof (struct mlk_tileset_collision));
+	return expand((void **)&file->tilecollisions, arraysz, sizeof (struct mlk_tileset_collision));
 }
 
 struct mlk_tileset_animation *
-expand_animations(struct mlk_tileset_loader *loader,
+expand_animations(struct mlk_tileset_loader *self,
                   struct mlk_tileset *tileset,
                   struct mlk_tileset_animation *array,
                   size_t arraysz)
@@ -103,9 +115,30 @@
 	(void)tileset;
 	(void)array;
 
-	struct mlk_tileset_loader_file *self = THIS(loader);
+	struct mlk_tileset_loader_file *file = THIS(self);
+
+	return expand((void **)&file->tileanimations, arraysz, sizeof (struct mlk_tileset_animation));
+}
+
+static void
+clear(struct mlk_tileset_loader *self, struct mlk_tileset *tileset)
+{
+	(void)tileset;
+
+	struct mlk_tileset_loader_file *file = THIS(self);
 
-	return expand((void **)&self->tileanimations, arraysz, sizeof (struct mlk_tileset_animation));
+	trash(file);
+}
+
+static void
+finish(struct mlk_tileset_loader *self)
+{
+	struct mlk_tileset_loader_file *file = THIS(self);
+
+	trash(file);
+	mlk__loader_file_free(file->lf);
+
+	file->lf = NULL;
 }
 
 int
@@ -124,19 +157,8 @@
 	file->iface.new_animation = new_animation;
 	file->iface.expand_collisions = expand_collisions;
 	file->iface.expand_animations = expand_animations;
+	file->iface.clear = clear;
+	file->iface.finish = finish;
 
 	return 0;
 }
-
-void
-mlk_tileset_loader_file_finish(struct mlk_tileset_loader_file *file)
-{
-	assert(file);
-
-	mlk__loader_file_free(file->lf);
-
-	mlk_alloc_free(file->tilecollisions);
-	mlk_alloc_free(file->tileanimations);
-
-	memset(file, 0, sizeof (*file));
-}
--- a/libmlk-rpg/mlk/rpg/tileset-loader-file.h	Sun Aug 27 11:04:57 2023 +0200
+++ b/libmlk-rpg/mlk/rpg/tileset-loader-file.h	Sun Aug 27 11:28:35 2023 +0200
@@ -37,6 +37,10 @@
 struct mlk_tileset_animation;
 struct mlk__loader_file;
 
+/**
+ * \struct mlk_tileset_loader_file
+ * \brief Tileset loader from file.
+ */
 struct mlk_tileset_loader_file {
 	/**
 	 * (read-write)
@@ -57,30 +61,21 @@
 #endif
 
 /**
- * Initialize the loader with internal functions and internal data to allocate
- * and find appropriate resources relative to the tileset filename.
+ * Initialize this file loader and the underlying abstract tileset loader
+ * interface.
  *
  * After loading the tileset with this underlying loader, it should be kept
  * until the tileset is no longer used.
  *
  * \pre file != NULL
  * \pre filename != NULL
- * \param file the abstract loader interface
+ * \param file the file loader
  * \param filename path to the tileset file
  * \return 0 on success or -1 on error
  */
 int
 mlk_tileset_loader_file_init(struct mlk_tileset_loader_file *file, const char *filename);
 
-/**
- * Cleanup allocated resources by this file loader.
- *
- * \pre file != NULL
- * \param file the file loader
- */
-void
-mlk_tileset_loader_file_finish(struct mlk_tileset_loader_file *file);
-
 #if defined(__cplusplus)
 }
 #endif
--- a/libmlk-rpg/mlk/rpg/tileset-loader.c	Sun Aug 27 11:04:57 2023 +0200
+++ b/libmlk-rpg/mlk/rpg/tileset-loader.c	Sun Aug 27 11:28:35 2023 +0200
@@ -316,3 +316,25 @@
 
 	return parse(loader, tileset, fp);
 }
+
+void
+mlk_tileset_loader_clear(struct mlk_tileset_loader *loader,
+                         struct mlk_tileset *tileset)
+{
+	assert(loader);
+	assert(tileset);
+
+	if (loader->clear)
+		loader->clear(loader, tileset);
+
+	memset(tileset, 0, sizeof (*tileset));
+}
+
+void
+mlk_tileset_loader_finish(struct mlk_tileset_loader *loader)
+{
+	assert(loader);
+
+	if (loader->finish)
+		loader->finish(loader);
+}
--- a/libmlk-rpg/mlk/rpg/tileset-loader.h	Sun Aug 27 11:04:57 2023 +0200
+++ b/libmlk-rpg/mlk/rpg/tileset-loader.h	Sun Aug 27 11:28:35 2023 +0200
@@ -25,20 +25,17 @@
  *
  * This module provides a generic way to open tilesets. It uses a callback
  * system whenever an action has to be taken by the user. by itself, this
- * module does not alloate nor owns any data.
+ * module does not allocate nor owns any data.
  *
  * It is designed in mind that the loader knows how to decode a tileset data
  * format file but has no indication on how it should allocate, arrange and
  * find tileset images and other resources.
  *
- * See tileset-file.h for an implementation of this module using files.
+ * See tileset-loader-file.h for an implementation of this module using files.
  */
 
 #include <stddef.h>
 
-struct mlk_animation;
-struct mlk_sprite;
-struct mlk_texture;
 struct mlk_tileset;
 struct mlk_tileset_animations;
 struct mlk_tileset_collision;
@@ -126,6 +123,28 @@
 	                                                    struct mlk_tileset_animation *array,
 	                                                    size_t arraysz);
 
+	/**
+	 * (read-write, optional)
+	 *
+	 * Cleanup resources allocated for this tileset.
+	 *
+	 * This is different than finalizing the loader itself, it should be
+	 * re-usable after calling this function.
+	 *
+	 * \param self this loader
+	 * \param tileset the underlying tileset to cleanup
+	 */
+	void (*clear)(struct mlk_tileset_loader *self, struct mlk_tileset *tileset);
+
+	/**
+	 * (read-write, optional)
+	 *
+	 * Cleanup the tileset loader.
+	 *
+	 * \param self this loader
+	 */
+	void (*finish)(struct mlk_tileset_loader *self);
+
 	/** \cond MLK_PRIVATE_DECLS */
 	unsigned int tilewidth;
 	unsigned int tileheight;
@@ -173,6 +192,35 @@
                            const void *data,
                            size_t datasz);
 
+/**
+ * Cleanup data for this tileset.
+ *
+ * The loader is re-usable after calling this function to load a new tileset.
+ *
+ * Invokes ::mlk_tileset_loader::clear.
+ *
+ * \pre loader != NULL
+ * \param loader the loader interface
+ * \param tileset the tileset used with this loader
+ */
+void
+mlk_tileset_loader_clear(struct mlk_tileset_loader *loader,
+                         struct mlk_tileset *tileset);
+
+/**
+ * Finalize the loader itself.
+ *
+ * The underlying interface should also clear tileset resources by convenience
+ * if the user forgot to call ::mlk_tileset_loader_clear.
+ *
+ * Invokes ::mlk_tileset_loader::finish.
+ *
+ * \pre loader != NULL
+ * \param loader the loader to finalize
+ */
+void
+mlk_tileset_loader_finish(struct mlk_tileset_loader *loader);
+
 #if defined(__cplusplus)
 }
 #endif
--- a/tests/test-tileset.c	Sun Aug 27 11:04:57 2023 +0200
+++ b/tests/test-tileset.c	Sun Aug 27 11:28:35 2023 +0200
@@ -46,7 +46,7 @@
 static inline void
 tileset_finish(struct tileset *ts)
 {
-	mlk_tileset_loader_file_finish(&ts->loader);
+	mlk_tileset_loader_finish(&ts->loader.iface);
 }
 
 static void
@@ -85,6 +85,27 @@
 }
 
 static void
+test_basics_clear(struct tileset *ts)
+{
+	DT_EQ_INT(tileset_open(ts, DIRECTORY "/maps/sample-tileset.tileset"), 0);
+
+	/* Do not test everything, already done in basics. */
+	DT_EQ_UINT(ts->tileset.sprite->cellw, 64U);
+	DT_EQ_UINT(ts->tileset.sprite->cellh, 32U);
+
+	/* Make sure we can clear and open again. */
+	mlk_tileset_loader_clear(&ts->loader.iface, &ts->tileset);
+
+	/* Check that it was zero'ed. */
+	DT_EQ_PTR(ts->tileset.sprite, NULL);
+
+	/* Now it should open our tileset again. */
+	DT_EQ_INT(mlk_tileset_loader_open(&ts->loader.iface, &ts->tileset, DIRECTORY "/maps/sample-tileset.tileset"), 0);
+	DT_EQ_UINT(ts->tileset.sprite->cellw, 64U);
+	DT_EQ_UINT(ts->tileset.sprite->cellh, 32U);
+}
+
+static void
 test_error_tilewidth(struct tileset *ts)
 {
 	DT_EQ_INT(tileset_open(ts, DIRECTORY "/maps/error-tilewidth.tileset"), -1);
@@ -126,6 +147,7 @@
 		return 1;
 
 	DT_RUN_EX(test_basics_sample, setup, teardown, &ts);
+	DT_RUN_EX(test_basics_clear, setup, teardown, &ts);
 	DT_RUN_EX(test_error_tilewidth, setup, teardown, &ts);
 	DT_RUN_EX(test_error_tileheight, setup, teardown, &ts);
 	DT_RUN_EX(test_error_image, setup, teardown, &ts);