changeset 593:f9e85d0aca74

rpg: introduce private loader_file to help allocating map/tileset The structure is just an allocator and owner for various data shared among the map and the tileset such as sprites, textures, animations... While here, simplify use of mlk_(map|tileset)_loader_file so they don't have a public structure anymore but insert themselves into the loader->data.
author David Demelier <markand@malikania.fr>
date Tue, 21 Mar 2023 14:08:15 +0100
parents 1560ef13146c
children e070fdcc592b
files CMakeLists.txt examples/example-map/example-map.c examples/example-tileset/example-tileset.c extern/LICENSE.libutlist.txt extern/VERSION.libutlist.txt extern/libutlist/CMakeLists.txt extern/libutlist/utlist.h libmlk-rpg/CMakeLists.txt 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 19 files changed, 1657 insertions(+), 358 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Mon Mar 20 21:59:02 2023 +0100
+++ b/CMakeLists.txt	Tue Mar 21 14:08:15 2023 +0100
@@ -87,6 +87,7 @@
 
 add_subdirectory(extern/libsqlite)
 add_subdirectory(extern/libdt)
+add_subdirectory(extern/libutlist)
 
 if (MLK_WITH_DOXYGEN)
 	add_subdirectory(doc)
--- a/examples/example-map/example-map.c	Mon Mar 20 21:59:02 2023 +0100
+++ b/examples/example-map/example-map.c	Tue Mar 21 14:08:15 2023 +0100
@@ -20,6 +20,8 @@
 #include <stdio.h>
 #include <string.h>
 
+#include <mlk/util/util.h>
+
 #include <mlk/core/animation.h>
 #include <mlk/core/core.h>
 #include <mlk/core/err.h>
@@ -54,11 +56,9 @@
 #include <assets/maps/world.h>
 #include <assets/tilesets/world.h>
 
-static struct mlk_tileset_loader_file tileset_loader_file;
 static struct mlk_tileset_loader tileset_loader;
 static struct mlk_tileset tileset;
 
-static struct mlk_map_loader_file map_loader_file;
 static struct mlk_map_loader map_loader;
 static struct mlk_map map;
 
@@ -89,14 +89,13 @@
 } table_textures[] = {
 	{ "world.png",                  &mlk_registry_textures[MLK_REGISTRY_TEXTURE_WORLD] },
 	{ "animation-water.png",        &mlk_registry_textures[MLK_REGISTRY_TEXTURE_WATER] },
+	{ "john.png",                   &mlk_registry_textures[MLK_REGISTRY_TEXTURE_JOHN]  },
 	{ NULL,                         NULL                                               }
 };
 
 static struct mlk_texture *
-init_texture(struct mlk_tileset_loader *loader, const char *ident)
+find_texture(const char *ident)
 {
-	(void)loader;
-
 	char filepath[MLK_PATH_MAX], filename[FILENAME_MAX + 1];
 
 	mlk_util_strlcpy(filepath, ident, sizeof (filepath));
@@ -110,8 +109,26 @@
 	return NULL;
 }
 
+static struct mlk_texture *
+tileset_new_texture(struct mlk_tileset_loader *loader, struct mlk_tileset *tileset, const char *ident)
+{
+	(void)loader;
+	(void)tileset;
+
+	return find_texture(ident);
+}
+
+static struct mlk_texture *
+map_new_texture(struct mlk_map_loader *loader, struct mlk_map *map, const char *ident)
+{
+	(void)loader;
+	(void)map;
+
+	return find_texture(ident);
+}
+
 struct mlk_tileset *
-init_tileset(struct mlk_map_loader *loader, struct mlk_map *map, const char *ident)
+map_new_tileset(struct mlk_map_loader *loader, struct mlk_map *map, const char *ident)
 {
 	(void)loader;
 	(void)map;
@@ -140,22 +157,21 @@
 	 * registry which is not supported by itself in
 	 * mlk_tileset_loader_file.
 	 */
-	mlk_tileset_loader_file_init(&tileset_loader_file, &tileset_loader, "");
-	tileset_loader.init_texture = init_texture;
+	mlk_tileset_loader_file_init(&tileset_loader, "");
+	tileset_loader.new_texture = tileset_new_texture;
 
 	/*
 	 * Create our map loader. It will also search for a tileset to be found
 	 * on disk by default which we would like to avoid. We override the
 	 * init_tileset function.
 	 */
-	mlk_map_loader_file_init(&map_loader_file, &map_loader, "");
-	map_loader.init_tileset = init_tileset;
+	mlk_map_loader_file_init(&map_loader, "");
+	map_loader.new_tileset = map_new_tileset;
+	map_loader.new_texture = map_new_texture;
 
 	if (mlk_map_loader_openmem(&map_loader, &map, assets_maps_world, sizeof (assets_maps_world)) < 0)
 		mlk_panic();
 
-	// TODO: this not handled by the map yet.
-	map.player_sprite = &mlk_registry_sprites[MLK_REGISTRY_TEXTURE_JOHN];
 	mlk_map_init(&map);
 }
 
@@ -233,8 +249,9 @@
 static void
 quit(void)
 {
-	mlk_map_loader_file_finish(&map_loader_file);
-	mlk_tileset_loader_file_finish(&tileset_loader_file);
+	mlk_map_finish(&map);
+	mlk_map_loader_file_finish(&map_loader);
+	mlk_tileset_loader_file_finish(&tileset_loader);
 	mlk_example_finish();
 }
 
--- a/examples/example-tileset/example-tileset.c	Mon Mar 20 21:59:02 2023 +0100
+++ b/examples/example-tileset/example-tileset.c	Tue Mar 21 14:08:15 2023 +0100
@@ -20,6 +20,8 @@
 #include <stdio.h>
 #include <string.h>
 
+#include <mlk/util/util.h>
+
 #include <mlk/core/animation.h>
 #include <mlk/core/core.h>
 #include <mlk/core/err.h>
@@ -47,7 +49,6 @@
 
 #include <assets/tilesets/world.h>
 
-static struct mlk_tileset_loader_file loader_file;
 static struct mlk_tileset_loader loader;
 static struct mlk_tileset tileset;
 
@@ -70,9 +71,10 @@
 };
 
 static struct mlk_texture *
-init_texture(struct mlk_tileset_loader *loader, const char *ident)
+new_texture(struct mlk_tileset_loader *loader, struct mlk_tileset *tileset, const char *ident)
 {
 	(void)loader;
+	(void)tileset;
 
 	char filepath[MLK_PATH_MAX], filename[FILENAME_MAX + 1];
 
@@ -88,9 +90,10 @@
 }
 
 static struct mlk_sprite *
-init_sprite(struct mlk_tileset_loader *loader)
+new_sprite(struct mlk_tileset_loader *loader, struct mlk_tileset *tileset)
 {
 	(void)loader;
+	(void)tileset;
 
 	/* Just ensure we haven't reach the limit. */
 	assert(spritesz < MLK_UTIL_SIZE(sprites));
@@ -99,9 +102,10 @@
 }
 
 static struct mlk_animation *
-init_animation(struct mlk_tileset_loader *loader)
+new_animation(struct mlk_tileset_loader *loader, struct mlk_tileset *tileset)
 {
 	(void)loader;
+	(void)tileset;
 
 	/* Just ensure we haven't reach the limit. */
 	assert(animationsz < MLK_UTIL_SIZE(animations));
@@ -122,10 +126,10 @@
 	 * Images are loaded from the libmlk-example registry from RAM and
 	 * sprites animations are statically allocated.
 	 */
-	mlk_tileset_loader_file_init(&loader_file, &loader, "");
-	loader.init_texture = init_texture;
-	loader.init_sprite = init_sprite;
-	loader.init_animation = init_animation;
+	mlk_tileset_loader_file_init(&loader, "");
+	loader.new_texture = new_texture;
+	loader.new_sprite = new_sprite;
+	loader.new_animation = new_animation;
 
 	if (mlk_tileset_loader_openmem(&loader, &tileset, assets_tilesets_world, sizeof (assets_tilesets_world)) < 0)
 		mlk_panic();
@@ -197,7 +201,7 @@
 static void
 quit(void)
 {
-	mlk_tileset_loader_file_finish(&loader_file);
+	mlk_tileset_loader_file_finish(&loader);
 	mlk_example_finish();
 }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/extern/LICENSE.libutlist.txt	Tue Mar 21 14:08:15 2023 +0100
@@ -0,0 +1,21 @@
+Copyright (c) 2005-2022, Troy D. Hanson  https://troydhanson.github.io/uthash/
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/extern/VERSION.libutlist.txt	Tue Mar 21 14:08:15 2023 +0100
@@ -0,0 +1,1 @@
+2.3.0
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/extern/libutlist/CMakeLists.txt	Tue Mar 21 14:08:15 2023 +0100
@@ -0,0 +1,27 @@
+#
+# CMakeLists.txt -- CMake build system for utlist
+#
+# Copyright (c) 2016-2022 David Demelier <markand@malikania.fr>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+cmake_minimum_required(VERSION 3.0)
+project(libmlk-utlist)
+add_library(libmlk-utlist INTERFACE)
+target_include_directories(
+	libmlk-utlist
+	INTERFACE
+		$<BUILD_INTERFACE:${libmlk-utlist_SOURCE_DIR}>
+)
+target_sources(libmlk-utlist INTERFACE ${libmlk-utlist_SOURCE_DIR}/utlist.h)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/extern/libutlist/utlist.h	Tue Mar 21 14:08:15 2023 +0100
@@ -0,0 +1,1073 @@
+/*
+Copyright (c) 2007-2021, Troy D. Hanson   http://troydhanson.github.com/uthash/
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#ifndef UTLIST_H
+#define UTLIST_H
+
+#define UTLIST_VERSION 2.3.0
+
+#include <assert.h>
+
+/*
+ * This file contains macros to manipulate singly and doubly-linked lists.
+ *
+ * 1. LL_ macros:  singly-linked lists.
+ * 2. DL_ macros:  doubly-linked lists.
+ * 3. CDL_ macros: circular doubly-linked lists.
+ *
+ * To use singly-linked lists, your structure must have a "next" pointer.
+ * To use doubly-linked lists, your structure must "prev" and "next" pointers.
+ * Either way, the pointer to the head of the list must be initialized to NULL.
+ *
+ * ----------------.EXAMPLE -------------------------
+ * struct item {
+ *      int id;
+ *      struct item *prev, *next;
+ * }
+ *
+ * struct item *list = NULL:
+ *
+ * int main() {
+ *      struct item *item;
+ *      ... allocate and populate item ...
+ *      DL_APPEND(list, item);
+ * }
+ * --------------------------------------------------
+ *
+ * For doubly-linked lists, the append and delete macros are O(1)
+ * For singly-linked lists, append and delete are O(n) but prepend is O(1)
+ * The sort macro is O(n log(n)) for all types of single/double/circular lists.
+ */
+
+/* These macros use decltype or the earlier __typeof GNU extension.
+   As decltype is only available in newer compilers (VS2010 or gcc 4.3+
+   when compiling c++ source) this code uses whatever method is needed
+   or, for VS2008 where neither is available, uses casting workarounds. */
+#if !defined(LDECLTYPE) && !defined(NO_DECLTYPE)
+#if defined(_MSC_VER)   /* MS compiler */
+#if _MSC_VER >= 1600 && defined(__cplusplus)  /* VS2010 or newer in C++ mode */
+#define LDECLTYPE(x) decltype(x)
+#else                   /* VS2008 or older (or VS2010 in C mode) */
+#define NO_DECLTYPE
+#endif
+#elif defined(__BORLANDC__) || defined(__ICCARM__) || defined(__LCC__) || defined(__WATCOMC__)
+#define NO_DECLTYPE
+#else                   /* GNU, Sun and other compilers */
+#define LDECLTYPE(x) __typeof(x)
+#endif
+#endif
+
+/* for VS2008 we use some workarounds to get around the lack of decltype,
+ * namely, we always reassign our tmp variable to the list head if we need
+ * to dereference its prev/next pointers, and save/restore the real head.*/
+#ifdef NO_DECLTYPE
+#define IF_NO_DECLTYPE(x) x
+#define LDECLTYPE(x) char*
+#define UTLIST_SV(elt,list) _tmp = (char*)(list); {char **_alias = (char**)&(list); *_alias = (elt); }
+#define UTLIST_NEXT(elt,list,next) ((char*)((list)->next))
+#define UTLIST_NEXTASGN(elt,list,to,next) { char **_alias = (char**)&((list)->next); *_alias=(char*)(to); }
+/* #define UTLIST_PREV(elt,list,prev) ((char*)((list)->prev)) */
+#define UTLIST_PREVASGN(elt,list,to,prev) { char **_alias = (char**)&((list)->prev); *_alias=(char*)(to); }
+#define UTLIST_RS(list) { char **_alias = (char**)&(list); *_alias=_tmp; }
+#define UTLIST_CASTASGN(a,b) { char **_alias = (char**)&(a); *_alias=(char*)(b); }
+#else
+#define IF_NO_DECLTYPE(x)
+#define UTLIST_SV(elt,list)
+#define UTLIST_NEXT(elt,list,next) ((elt)->next)
+#define UTLIST_NEXTASGN(elt,list,to,next) ((elt)->next)=(to)
+/* #define UTLIST_PREV(elt,list,prev) ((elt)->prev) */
+#define UTLIST_PREVASGN(elt,list,to,prev) ((elt)->prev)=(to)
+#define UTLIST_RS(list)
+#define UTLIST_CASTASGN(a,b) (a)=(b)
+#endif
+
+/******************************************************************************
+ * The sort macro is an adaptation of Simon Tatham's O(n log(n)) mergesort    *
+ * Unwieldy variable names used here to avoid shadowing passed-in variables.  *
+ *****************************************************************************/
+#define LL_SORT(list, cmp)                                                                     \
+    LL_SORT2(list, cmp, next)
+
+#define LL_SORT2(list, cmp, next)                                                              \
+do {                                                                                           \
+  LDECLTYPE(list) _ls_p;                                                                       \
+  LDECLTYPE(list) _ls_q;                                                                       \
+  LDECLTYPE(list) _ls_e;                                                                       \
+  LDECLTYPE(list) _ls_tail;                                                                    \
+  IF_NO_DECLTYPE(LDECLTYPE(list) _tmp;)                                                        \
+  int _ls_insize, _ls_nmerges, _ls_psize, _ls_qsize, _ls_i, _ls_looping;                       \
+  if (list) {                                                                                  \
+    _ls_insize = 1;                                                                            \
+    _ls_looping = 1;                                                                           \
+    while (_ls_looping) {                                                                      \
+      UTLIST_CASTASGN(_ls_p,list);                                                             \
+      (list) = NULL;                                                                           \
+      _ls_tail = NULL;                                                                         \
+      _ls_nmerges = 0;                                                                         \
+      while (_ls_p) {                                                                          \
+        _ls_nmerges++;                                                                         \
+        _ls_q = _ls_p;                                                                         \
+        _ls_psize = 0;                                                                         \
+        for (_ls_i = 0; _ls_i < _ls_insize; _ls_i++) {                                         \
+          _ls_psize++;                                                                         \
+          UTLIST_SV(_ls_q,list); _ls_q = UTLIST_NEXT(_ls_q,list,next); UTLIST_RS(list);        \
+          if (!_ls_q) break;                                                                   \
+        }                                                                                      \
+        _ls_qsize = _ls_insize;                                                                \
+        while (_ls_psize > 0 || (_ls_qsize > 0 && _ls_q)) {                                    \
+          if (_ls_psize == 0) {                                                                \
+            _ls_e = _ls_q; UTLIST_SV(_ls_q,list); _ls_q =                                      \
+              UTLIST_NEXT(_ls_q,list,next); UTLIST_RS(list); _ls_qsize--;                      \
+          } else if (_ls_qsize == 0 || !_ls_q) {                                               \
+            _ls_e = _ls_p; UTLIST_SV(_ls_p,list); _ls_p =                                      \
+              UTLIST_NEXT(_ls_p,list,next); UTLIST_RS(list); _ls_psize--;                      \
+          } else if (cmp(_ls_p,_ls_q) <= 0) {                                                  \
+            _ls_e = _ls_p; UTLIST_SV(_ls_p,list); _ls_p =                                      \
+              UTLIST_NEXT(_ls_p,list,next); UTLIST_RS(list); _ls_psize--;                      \
+          } else {                                                                             \
+            _ls_e = _ls_q; UTLIST_SV(_ls_q,list); _ls_q =                                      \
+              UTLIST_NEXT(_ls_q,list,next); UTLIST_RS(list); _ls_qsize--;                      \
+          }                                                                                    \
+          if (_ls_tail) {                                                                      \
+            UTLIST_SV(_ls_tail,list); UTLIST_NEXTASGN(_ls_tail,list,_ls_e,next); UTLIST_RS(list); \
+          } else {                                                                             \
+            UTLIST_CASTASGN(list,_ls_e);                                                       \
+          }                                                                                    \
+          _ls_tail = _ls_e;                                                                    \
+        }                                                                                      \
+        _ls_p = _ls_q;                                                                         \
+      }                                                                                        \
+      if (_ls_tail) {                                                                          \
+        UTLIST_SV(_ls_tail,list); UTLIST_NEXTASGN(_ls_tail,list,NULL,next); UTLIST_RS(list);   \
+      }                                                                                        \
+      if (_ls_nmerges <= 1) {                                                                  \
+        _ls_looping=0;                                                                         \
+      }                                                                                        \
+      _ls_insize *= 2;                                                                         \
+    }                                                                                          \
+  }                                                                                            \
+} while (0)
+
+
+#define DL_SORT(list, cmp)                                                                     \
+    DL_SORT2(list, cmp, prev, next)
+
+#define DL_SORT2(list, cmp, prev, next)                                                        \
+do {                                                                                           \
+  LDECLTYPE(list) _ls_p;                                                                       \
+  LDECLTYPE(list) _ls_q;                                                                       \
+  LDECLTYPE(list) _ls_e;                                                                       \
+  LDECLTYPE(list) _ls_tail;                                                                    \
+  IF_NO_DECLTYPE(LDECLTYPE(list) _tmp;)                                                        \
+  int _ls_insize, _ls_nmerges, _ls_psize, _ls_qsize, _ls_i, _ls_looping;                       \
+  if (list) {                                                                                  \
+    _ls_insize = 1;                                                                            \
+    _ls_looping = 1;                                                                           \
+    while (_ls_looping) {                                                                      \
+      UTLIST_CASTASGN(_ls_p,list);                                                             \
+      (list) = NULL;                                                                           \
+      _ls_tail = NULL;                                                                         \
+      _ls_nmerges = 0;                                                                         \
+      while (_ls_p) {                                                                          \
+        _ls_nmerges++;                                                                         \
+        _ls_q = _ls_p;                                                                         \
+        _ls_psize = 0;                                                                         \
+        for (_ls_i = 0; _ls_i < _ls_insize; _ls_i++) {                                         \
+          _ls_psize++;                                                                         \
+          UTLIST_SV(_ls_q,list); _ls_q = UTLIST_NEXT(_ls_q,list,next); UTLIST_RS(list);        \
+          if (!_ls_q) break;                                                                   \
+        }                                                                                      \
+        _ls_qsize = _ls_insize;                                                                \
+        while ((_ls_psize > 0) || ((_ls_qsize > 0) && _ls_q)) {                                \
+          if (_ls_psize == 0) {                                                                \
+            _ls_e = _ls_q; UTLIST_SV(_ls_q,list); _ls_q =                                      \
+              UTLIST_NEXT(_ls_q,list,next); UTLIST_RS(list); _ls_qsize--;                      \
+          } else if ((_ls_qsize == 0) || (!_ls_q)) {                                           \
+            _ls_e = _ls_p; UTLIST_SV(_ls_p,list); _ls_p =                                      \
+              UTLIST_NEXT(_ls_p,list,next); UTLIST_RS(list); _ls_psize--;                      \
+          } else if (cmp(_ls_p,_ls_q) <= 0) {                                                  \
+            _ls_e = _ls_p; UTLIST_SV(_ls_p,list); _ls_p =                                      \
+              UTLIST_NEXT(_ls_p,list,next); UTLIST_RS(list); _ls_psize--;                      \
+          } else {                                                                             \
+            _ls_e = _ls_q; UTLIST_SV(_ls_q,list); _ls_q =                                      \
+              UTLIST_NEXT(_ls_q,list,next); UTLIST_RS(list); _ls_qsize--;                      \
+          }                                                                                    \
+          if (_ls_tail) {                                                                      \
+            UTLIST_SV(_ls_tail,list); UTLIST_NEXTASGN(_ls_tail,list,_ls_e,next); UTLIST_RS(list); \
+          } else {                                                                             \
+            UTLIST_CASTASGN(list,_ls_e);                                                       \
+          }                                                                                    \
+          UTLIST_SV(_ls_e,list); UTLIST_PREVASGN(_ls_e,list,_ls_tail,prev); UTLIST_RS(list);   \
+          _ls_tail = _ls_e;                                                                    \
+        }                                                                                      \
+        _ls_p = _ls_q;                                                                         \
+      }                                                                                        \
+      UTLIST_CASTASGN((list)->prev, _ls_tail);                                                 \
+      UTLIST_SV(_ls_tail,list); UTLIST_NEXTASGN(_ls_tail,list,NULL,next); UTLIST_RS(list);     \
+      if (_ls_nmerges <= 1) {                                                                  \
+        _ls_looping=0;                                                                         \
+      }                                                                                        \
+      _ls_insize *= 2;                                                                         \
+    }                                                                                          \
+  }                                                                                            \
+} while (0)
+
+#define CDL_SORT(list, cmp)                                                                    \
+    CDL_SORT2(list, cmp, prev, next)
+
+#define CDL_SORT2(list, cmp, prev, next)                                                       \
+do {                                                                                           \
+  LDECLTYPE(list) _ls_p;                                                                       \
+  LDECLTYPE(list) _ls_q;                                                                       \
+  LDECLTYPE(list) _ls_e;                                                                       \
+  LDECLTYPE(list) _ls_tail;                                                                    \
+  LDECLTYPE(list) _ls_oldhead;                                                                 \
+  LDECLTYPE(list) _tmp;                                                                        \
+  int _ls_insize, _ls_nmerges, _ls_psize, _ls_qsize, _ls_i, _ls_looping;                       \
+  if (list) {                                                                                  \
+    _ls_insize = 1;                                                                            \
+    _ls_looping = 1;                                                                           \
+    while (_ls_looping) {                                                                      \
+      UTLIST_CASTASGN(_ls_p,list);                                                             \
+      UTLIST_CASTASGN(_ls_oldhead,list);                                                       \
+      (list) = NULL;                                                                           \
+      _ls_tail = NULL;                                                                         \
+      _ls_nmerges = 0;                                                                         \
+      while (_ls_p) {                                                                          \
+        _ls_nmerges++;                                                                         \
+        _ls_q = _ls_p;                                                                         \
+        _ls_psize = 0;                                                                         \
+        for (_ls_i = 0; _ls_i < _ls_insize; _ls_i++) {                                         \
+          _ls_psize++;                                                                         \
+          UTLIST_SV(_ls_q,list);                                                               \
+          if (UTLIST_NEXT(_ls_q,list,next) == _ls_oldhead) {                                   \
+            _ls_q = NULL;                                                                      \
+          } else {                                                                             \
+            _ls_q = UTLIST_NEXT(_ls_q,list,next);                                              \
+          }                                                                                    \
+          UTLIST_RS(list);                                                                     \
+          if (!_ls_q) break;                                                                   \
+        }                                                                                      \
+        _ls_qsize = _ls_insize;                                                                \
+        while (_ls_psize > 0 || (_ls_qsize > 0 && _ls_q)) {                                    \
+          if (_ls_psize == 0) {                                                                \
+            _ls_e = _ls_q; UTLIST_SV(_ls_q,list); _ls_q =                                      \
+              UTLIST_NEXT(_ls_q,list,next); UTLIST_RS(list); _ls_qsize--;                      \
+            if (_ls_q == _ls_oldhead) { _ls_q = NULL; }                                        \
+          } else if (_ls_qsize == 0 || !_ls_q) {                                               \
+            _ls_e = _ls_p; UTLIST_SV(_ls_p,list); _ls_p =                                      \
+              UTLIST_NEXT(_ls_p,list,next); UTLIST_RS(list); _ls_psize--;                      \
+            if (_ls_p == _ls_oldhead) { _ls_p = NULL; }                                        \
+          } else if (cmp(_ls_p,_ls_q) <= 0) {                                                  \
+            _ls_e = _ls_p; UTLIST_SV(_ls_p,list); _ls_p =                                      \
+              UTLIST_NEXT(_ls_p,list,next); UTLIST_RS(list); _ls_psize--;                      \
+            if (_ls_p == _ls_oldhead) { _ls_p = NULL; }                                        \
+          } else {                                                                             \
+            _ls_e = _ls_q; UTLIST_SV(_ls_q,list); _ls_q =                                      \
+              UTLIST_NEXT(_ls_q,list,next); UTLIST_RS(list); _ls_qsize--;                      \
+            if (_ls_q == _ls_oldhead) { _ls_q = NULL; }                                        \
+          }                                                                                    \
+          if (_ls_tail) {                                                                      \
+            UTLIST_SV(_ls_tail,list); UTLIST_NEXTASGN(_ls_tail,list,_ls_e,next); UTLIST_RS(list); \
+          } else {                                                                             \
+            UTLIST_CASTASGN(list,_ls_e);                                                       \
+          }                                                                                    \
+          UTLIST_SV(_ls_e,list); UTLIST_PREVASGN(_ls_e,list,_ls_tail,prev); UTLIST_RS(list);   \
+          _ls_tail = _ls_e;                                                                    \
+        }                                                                                      \
+        _ls_p = _ls_q;                                                                         \
+      }                                                                                        \
+      UTLIST_CASTASGN((list)->prev,_ls_tail);                                                  \
+      UTLIST_CASTASGN(_tmp,list);                                                              \
+      UTLIST_SV(_ls_tail,list); UTLIST_NEXTASGN(_ls_tail,list,_tmp,next); UTLIST_RS(list);     \
+      if (_ls_nmerges <= 1) {                                                                  \
+        _ls_looping=0;                                                                         \
+      }                                                                                        \
+      _ls_insize *= 2;                                                                         \
+    }                                                                                          \
+  }                                                                                            \
+} while (0)
+
+/******************************************************************************
+ * singly linked list macros (non-circular)                                   *
+ *****************************************************************************/
+#define LL_PREPEND(head,add)                                                                   \
+    LL_PREPEND2(head,add,next)
+
+#define LL_PREPEND2(head,add,next)                                                             \
+do {                                                                                           \
+  (add)->next = (head);                                                                        \
+  (head) = (add);                                                                              \
+} while (0)
+
+#define LL_CONCAT(head1,head2)                                                                 \
+    LL_CONCAT2(head1,head2,next)
+
+#define LL_CONCAT2(head1,head2,next)                                                           \
+do {                                                                                           \
+  LDECLTYPE(head1) _tmp;                                                                       \
+  if (head1) {                                                                                 \
+    _tmp = (head1);                                                                            \
+    while (_tmp->next) { _tmp = _tmp->next; }                                                  \
+    _tmp->next=(head2);                                                                        \
+  } else {                                                                                     \
+    (head1)=(head2);                                                                           \
+  }                                                                                            \
+} while (0)
+
+#define LL_APPEND(head,add)                                                                    \
+    LL_APPEND2(head,add,next)
+
+#define LL_APPEND2(head,add,next)                                                              \
+do {                                                                                           \
+  LDECLTYPE(head) _tmp;                                                                        \
+  (add)->next=NULL;                                                                            \
+  if (head) {                                                                                  \
+    _tmp = (head);                                                                             \
+    while (_tmp->next) { _tmp = _tmp->next; }                                                  \
+    _tmp->next=(add);                                                                          \
+  } else {                                                                                     \
+    (head)=(add);                                                                              \
+  }                                                                                            \
+} while (0)
+
+#define LL_INSERT_INORDER(head,add,cmp)                                                        \
+    LL_INSERT_INORDER2(head,add,cmp,next)
+
+#define LL_INSERT_INORDER2(head,add,cmp,next)                                                  \
+do {                                                                                           \
+  LDECLTYPE(head) _tmp;                                                                        \
+  if (head) {                                                                                  \
+    LL_LOWER_BOUND2(head, _tmp, add, cmp, next);                                               \
+    LL_APPEND_ELEM2(head, _tmp, add, next);                                                    \
+  } else {                                                                                     \
+    (head) = (add);                                                                            \
+    (head)->next = NULL;                                                                       \
+  }                                                                                            \
+} while (0)
+
+#define LL_LOWER_BOUND(head,elt,like,cmp)                                                      \
+    LL_LOWER_BOUND2(head,elt,like,cmp,next)
+
+#define LL_LOWER_BOUND2(head,elt,like,cmp,next)                                                \
+  do {                                                                                         \
+    if ((head) == NULL || (cmp(head, like)) >= 0) {                                            \
+      (elt) = NULL;                                                                            \
+    } else {                                                                                   \
+      for ((elt) = (head); (elt)->next != NULL; (elt) = (elt)->next) {                         \
+        if (cmp((elt)->next, like) >= 0) {                                                     \
+          break;                                                                               \
+        }                                                                                      \
+      }                                                                                        \
+    }                                                                                          \
+  } while (0)
+
+#define LL_DELETE(head,del)                                                                    \
+    LL_DELETE2(head,del,next)
+
+#define LL_DELETE2(head,del,next)                                                              \
+do {                                                                                           \
+  LDECLTYPE(head) _tmp;                                                                        \
+  if ((head) == (del)) {                                                                       \
+    (head)=(head)->next;                                                                       \
+  } else {                                                                                     \
+    _tmp = (head);                                                                             \
+    while (_tmp->next && (_tmp->next != (del))) {                                              \
+      _tmp = _tmp->next;                                                                       \
+    }                                                                                          \
+    if (_tmp->next) {                                                                          \
+      _tmp->next = (del)->next;                                                                \
+    }                                                                                          \
+  }                                                                                            \
+} while (0)
+
+#define LL_COUNT(head,el,counter)                                                              \
+    LL_COUNT2(head,el,counter,next)                                                            \
+
+#define LL_COUNT2(head,el,counter,next)                                                        \
+do {                                                                                           \
+  (counter) = 0;                                                                               \
+  LL_FOREACH2(head,el,next) { ++(counter); }                                                   \
+} while (0)
+
+#define LL_FOREACH(head,el)                                                                    \
+    LL_FOREACH2(head,el,next)
+
+#define LL_FOREACH2(head,el,next)                                                              \
+    for ((el) = (head); el; (el) = (el)->next)
+
+#define LL_FOREACH_SAFE(head,el,tmp)                                                           \
+    LL_FOREACH_SAFE2(head,el,tmp,next)
+
+#define LL_FOREACH_SAFE2(head,el,tmp,next)                                                     \
+  for ((el) = (head); (el) && ((tmp) = (el)->next, 1); (el) = (tmp))
+
+#define LL_SEARCH_SCALAR(head,out,field,val)                                                   \
+    LL_SEARCH_SCALAR2(head,out,field,val,next)
+
+#define LL_SEARCH_SCALAR2(head,out,field,val,next)                                             \
+do {                                                                                           \
+    LL_FOREACH2(head,out,next) {                                                               \
+      if ((out)->field == (val)) break;                                                        \
+    }                                                                                          \
+} while (0)
+
+#define LL_SEARCH(head,out,elt,cmp)                                                            \
+    LL_SEARCH2(head,out,elt,cmp,next)
+
+#define LL_SEARCH2(head,out,elt,cmp,next)                                                      \
+do {                                                                                           \
+    LL_FOREACH2(head,out,next) {                                                               \
+      if ((cmp(out,elt))==0) break;                                                            \
+    }                                                                                          \
+} while (0)
+
+#define LL_REPLACE_ELEM2(head, el, add, next)                                                  \
+do {                                                                                           \
+ LDECLTYPE(head) _tmp;                                                                         \
+ assert((head) != NULL);                                                                       \
+ assert((el) != NULL);                                                                         \
+ assert((add) != NULL);                                                                        \
+ (add)->next = (el)->next;                                                                     \
+ if ((head) == (el)) {                                                                         \
+  (head) = (add);                                                                              \
+ } else {                                                                                      \
+  _tmp = (head);                                                                               \
+  while (_tmp->next && (_tmp->next != (el))) {                                                 \
+   _tmp = _tmp->next;                                                                          \
+  }                                                                                            \
+  if (_tmp->next) {                                                                            \
+    _tmp->next = (add);                                                                        \
+  }                                                                                            \
+ }                                                                                             \
+} while (0)
+
+#define LL_REPLACE_ELEM(head, el, add)                                                         \
+    LL_REPLACE_ELEM2(head, el, add, next)
+
+#define LL_PREPEND_ELEM2(head, el, add, next)                                                  \
+do {                                                                                           \
+ if (el) {                                                                                     \
+  LDECLTYPE(head) _tmp;                                                                        \
+  assert((head) != NULL);                                                                      \
+  assert((add) != NULL);                                                                       \
+  (add)->next = (el);                                                                          \
+  if ((head) == (el)) {                                                                        \
+   (head) = (add);                                                                             \
+  } else {                                                                                     \
+   _tmp = (head);                                                                              \
+   while (_tmp->next && (_tmp->next != (el))) {                                                \
+    _tmp = _tmp->next;                                                                         \
+   }                                                                                           \
+   if (_tmp->next) {                                                                           \
+     _tmp->next = (add);                                                                       \
+   }                                                                                           \
+  }                                                                                            \
+ } else {                                                                                      \
+  LL_APPEND2(head, add, next);                                                                 \
+ }                                                                                             \
+} while (0)                                                                                    \
+
+#define LL_PREPEND_ELEM(head, el, add)                                                         \
+    LL_PREPEND_ELEM2(head, el, add, next)
+
+#define LL_APPEND_ELEM2(head, el, add, next)                                                   \
+do {                                                                                           \
+ if (el) {                                                                                     \
+  assert((head) != NULL);                                                                      \
+  assert((add) != NULL);                                                                       \
+  (add)->next = (el)->next;                                                                    \
+  (el)->next = (add);                                                                          \
+ } else {                                                                                      \
+  LL_PREPEND2(head, add, next);                                                                \
+ }                                                                                             \
+} while (0)                                                                                    \
+
+#define LL_APPEND_ELEM(head, el, add)                                                          \
+    LL_APPEND_ELEM2(head, el, add, next)
+
+#ifdef NO_DECLTYPE
+/* Here are VS2008 / NO_DECLTYPE replacements for a few functions */
+
+#undef LL_CONCAT2
+#define LL_CONCAT2(head1,head2,next)                                                           \
+do {                                                                                           \
+  char *_tmp;                                                                                  \
+  if (head1) {                                                                                 \
+    _tmp = (char*)(head1);                                                                     \
+    while ((head1)->next) { (head1) = (head1)->next; }                                         \
+    (head1)->next = (head2);                                                                   \
+    UTLIST_RS(head1);                                                                          \
+  } else {                                                                                     \
+    (head1)=(head2);                                                                           \
+  }                                                                                            \
+} while (0)
+
+#undef LL_APPEND2
+#define LL_APPEND2(head,add,next)                                                              \
+do {                                                                                           \
+  if (head) {                                                                                  \
+    (add)->next = head;     /* use add->next as a temp variable */                             \
+    while ((add)->next->next) { (add)->next = (add)->next->next; }                             \
+    (add)->next->next=(add);                                                                   \
+  } else {                                                                                     \
+    (head)=(add);                                                                              \
+  }                                                                                            \
+  (add)->next=NULL;                                                                            \
+} while (0)
+
+#undef LL_INSERT_INORDER2
+#define LL_INSERT_INORDER2(head,add,cmp,next)                                                  \
+do {                                                                                           \
+  if ((head) == NULL || (cmp(head, add)) >= 0) {                                               \
+    (add)->next = (head);                                                                      \
+    (head) = (add);                                                                            \
+  } else {                                                                                     \
+    char *_tmp = (char*)(head);                                                                \
+    while ((head)->next != NULL && (cmp((head)->next, add)) < 0) {                             \
+      (head) = (head)->next;                                                                   \
+    }                                                                                          \
+    (add)->next = (head)->next;                                                                \
+    (head)->next = (add);                                                                      \
+    UTLIST_RS(head);                                                                           \
+  }                                                                                            \
+} while (0)
+
+#undef LL_DELETE2
+#define LL_DELETE2(head,del,next)                                                              \
+do {                                                                                           \
+  if ((head) == (del)) {                                                                       \
+    (head)=(head)->next;                                                                       \
+  } else {                                                                                     \
+    char *_tmp = (char*)(head);                                                                \
+    while ((head)->next && ((head)->next != (del))) {                                          \
+      (head) = (head)->next;                                                                   \
+    }                                                                                          \
+    if ((head)->next) {                                                                        \
+      (head)->next = ((del)->next);                                                            \
+    }                                                                                          \
+    UTLIST_RS(head);                                                                           \
+  }                                                                                            \
+} while (0)
+
+#undef LL_REPLACE_ELEM2
+#define LL_REPLACE_ELEM2(head, el, add, next)                                                  \
+do {                                                                                           \
+  assert((head) != NULL);                                                                      \
+  assert((el) != NULL);                                                                        \
+  assert((add) != NULL);                                                                       \
+  if ((head) == (el)) {                                                                        \
+    (head) = (add);                                                                            \
+  } else {                                                                                     \
+    (add)->next = head;                                                                        \
+    while ((add)->next->next && ((add)->next->next != (el))) {                                 \
+      (add)->next = (add)->next->next;                                                         \
+    }                                                                                          \
+    if ((add)->next->next) {                                                                   \
+      (add)->next->next = (add);                                                               \
+    }                                                                                          \
+  }                                                                                            \
+  (add)->next = (el)->next;                                                                    \
+} while (0)
+
+#undef LL_PREPEND_ELEM2
+#define LL_PREPEND_ELEM2(head, el, add, next)                                                  \
+do {                                                                                           \
+  if (el) {                                                                                    \
+    assert((head) != NULL);                                                                    \
+    assert((add) != NULL);                                                                     \
+    if ((head) == (el)) {                                                                      \
+      (head) = (add);                                                                          \
+    } else {                                                                                   \
+      (add)->next = (head);                                                                    \
+      while ((add)->next->next && ((add)->next->next != (el))) {                               \
+        (add)->next = (add)->next->next;                                                       \
+      }                                                                                        \
+      if ((add)->next->next) {                                                                 \
+        (add)->next->next = (add);                                                             \
+      }                                                                                        \
+    }                                                                                          \
+    (add)->next = (el);                                                                        \
+  } else {                                                                                     \
+    LL_APPEND2(head, add, next);                                                               \
+  }                                                                                            \
+} while (0)                                                                                    \
+
+#endif /* NO_DECLTYPE */
+
+/******************************************************************************
+ * doubly linked list macros (non-circular)                                   *
+ *****************************************************************************/
+#define DL_PREPEND(head,add)                                                                   \
+    DL_PREPEND2(head,add,prev,next)
+
+#define DL_PREPEND2(head,add,prev,next)                                                        \
+do {                                                                                           \
+ (add)->next = (head);                                                                         \
+ if (head) {                                                                                   \
+   (add)->prev = (head)->prev;                                                                 \
+   (head)->prev = (add);                                                                       \
+ } else {                                                                                      \
+   (add)->prev = (add);                                                                        \
+ }                                                                                             \
+ (head) = (add);                                                                               \
+} while (0)
+
+#define DL_APPEND(head,add)                                                                    \
+    DL_APPEND2(head,add,prev,next)
+
+#define DL_APPEND2(head,add,prev,next)                                                         \
+do {                                                                                           \
+  if (head) {                                                                                  \
+      (add)->prev = (head)->prev;                                                              \
+      (head)->prev->next = (add);                                                              \
+      (head)->prev = (add);                                                                    \
+      (add)->next = NULL;                                                                      \
+  } else {                                                                                     \
+      (head)=(add);                                                                            \
+      (head)->prev = (head);                                                                   \
+      (head)->next = NULL;                                                                     \
+  }                                                                                            \
+} while (0)
+
+#define DL_INSERT_INORDER(head,add,cmp)                                                        \
+    DL_INSERT_INORDER2(head,add,cmp,prev,next)
+
+#define DL_INSERT_INORDER2(head,add,cmp,prev,next)                                             \
+do {                                                                                           \
+  LDECLTYPE(head) _tmp;                                                                        \
+  if (head) {                                                                                  \
+    DL_LOWER_BOUND2(head, _tmp, add, cmp, next);                                               \
+    DL_APPEND_ELEM2(head, _tmp, add, prev, next);                                              \
+  } else {                                                                                     \
+    (head) = (add);                                                                            \
+    (head)->prev = (head);                                                                     \
+    (head)->next = NULL;                                                                       \
+  }                                                                                            \
+} while (0)
+
+#define DL_LOWER_BOUND(head,elt,like,cmp)                                                      \
+    DL_LOWER_BOUND2(head,elt,like,cmp,next)
+
+#define DL_LOWER_BOUND2(head,elt,like,cmp,next)                                                \
+do {                                                                                           \
+  if ((head) == NULL || (cmp(head, like)) >= 0) {                                              \
+    (elt) = NULL;                                                                              \
+  } else {                                                                                     \
+    for ((elt) = (head); (elt)->next != NULL; (elt) = (elt)->next) {                           \
+      if ((cmp((elt)->next, like)) >= 0) {                                                     \
+        break;                                                                                 \
+      }                                                                                        \
+    }                                                                                          \
+  }                                                                                            \
+} while (0)
+
+#define DL_CONCAT(head1,head2)                                                                 \
+    DL_CONCAT2(head1,head2,prev,next)
+
+#define DL_CONCAT2(head1,head2,prev,next)                                                      \
+do {                                                                                           \
+  LDECLTYPE(head1) _tmp;                                                                       \
+  if (head2) {                                                                                 \
+    if (head1) {                                                                               \
+        UTLIST_CASTASGN(_tmp, (head2)->prev);                                                  \
+        (head2)->prev = (head1)->prev;                                                         \
+        (head1)->prev->next = (head2);                                                         \
+        UTLIST_CASTASGN((head1)->prev, _tmp);                                                  \
+    } else {                                                                                   \
+        (head1)=(head2);                                                                       \
+    }                                                                                          \
+  }                                                                                            \
+} while (0)
+
+#define DL_DELETE(head,del)                                                                    \
+    DL_DELETE2(head,del,prev,next)
+
+#define DL_DELETE2(head,del,prev,next)                                                         \
+do {                                                                                           \
+  assert((head) != NULL);                                                                      \
+  assert((del)->prev != NULL);                                                                 \
+  if ((del)->prev == (del)) {                                                                  \
+      (head)=NULL;                                                                             \
+  } else if ((del)==(head)) {                                                                  \
+      (del)->next->prev = (del)->prev;                                                         \
+      (head) = (del)->next;                                                                    \
+  } else {                                                                                     \
+      (del)->prev->next = (del)->next;                                                         \
+      if ((del)->next) {                                                                       \
+          (del)->next->prev = (del)->prev;                                                     \
+      } else {                                                                                 \
+          (head)->prev = (del)->prev;                                                          \
+      }                                                                                        \
+  }                                                                                            \
+} while (0)
+
+#define DL_COUNT(head,el,counter)                                                              \
+    DL_COUNT2(head,el,counter,next)                                                            \
+
+#define DL_COUNT2(head,el,counter,next)                                                        \
+do {                                                                                           \
+  (counter) = 0;                                                                               \
+  DL_FOREACH2(head,el,next) { ++(counter); }                                                   \
+} while (0)
+
+#define DL_FOREACH(head,el)                                                                    \
+    DL_FOREACH2(head,el,next)
+
+#define DL_FOREACH2(head,el,next)                                                              \
+    for ((el) = (head); el; (el) = (el)->next)
+
+/* this version is safe for deleting the elements during iteration */
+#define DL_FOREACH_SAFE(head,el,tmp)                                                           \
+    DL_FOREACH_SAFE2(head,el,tmp,next)
+
+#define DL_FOREACH_SAFE2(head,el,tmp,next)                                                     \
+  for ((el) = (head); (el) && ((tmp) = (el)->next, 1); (el) = (tmp))
+
+/* these are identical to their singly-linked list counterparts */
+#define DL_SEARCH_SCALAR LL_SEARCH_SCALAR
+#define DL_SEARCH LL_SEARCH
+#define DL_SEARCH_SCALAR2 LL_SEARCH_SCALAR2
+#define DL_SEARCH2 LL_SEARCH2
+
+#define DL_REPLACE_ELEM2(head, el, add, prev, next)                                            \
+do {                                                                                           \
+ assert((head) != NULL);                                                                       \
+ assert((el) != NULL);                                                                         \
+ assert((add) != NULL);                                                                        \
+ if ((head) == (el)) {                                                                         \
+  (head) = (add);                                                                              \
+  (add)->next = (el)->next;                                                                    \
+  if ((el)->next == NULL) {                                                                    \
+   (add)->prev = (add);                                                                        \
+  } else {                                                                                     \
+   (add)->prev = (el)->prev;                                                                   \
+   (add)->next->prev = (add);                                                                  \
+  }                                                                                            \
+ } else {                                                                                      \
+  (add)->next = (el)->next;                                                                    \
+  (add)->prev = (el)->prev;                                                                    \
+  (add)->prev->next = (add);                                                                   \
+  if ((el)->next == NULL) {                                                                    \
+   (head)->prev = (add);                                                                       \
+  } else {                                                                                     \
+   (add)->next->prev = (add);                                                                  \
+  }                                                                                            \
+ }                                                                                             \
+} while (0)
+
+#define DL_REPLACE_ELEM(head, el, add)                                                         \
+    DL_REPLACE_ELEM2(head, el, add, prev, next)
+
+#define DL_PREPEND_ELEM2(head, el, add, prev, next)                                            \
+do {                                                                                           \
+ if (el) {                                                                                     \
+  assert((head) != NULL);                                                                      \
+  assert((add) != NULL);                                                                       \
+  (add)->next = (el);                                                                          \
+  (add)->prev = (el)->prev;                                                                    \
+  (el)->prev = (add);                                                                          \
+  if ((head) == (el)) {                                                                        \
+   (head) = (add);                                                                             \
+  } else {                                                                                     \
+   (add)->prev->next = (add);                                                                  \
+  }                                                                                            \
+ } else {                                                                                      \
+  DL_APPEND2(head, add, prev, next);                                                           \
+ }                                                                                             \
+} while (0)                                                                                    \
+
+#define DL_PREPEND_ELEM(head, el, add)                                                         \
+    DL_PREPEND_ELEM2(head, el, add, prev, next)
+
+#define DL_APPEND_ELEM2(head, el, add, prev, next)                                             \
+do {                                                                                           \
+ if (el) {                                                                                     \
+  assert((head) != NULL);                                                                      \
+  assert((add) != NULL);                                                                       \
+  (add)->next = (el)->next;                                                                    \
+  (add)->prev = (el);                                                                          \
+  (el)->next = (add);                                                                          \
+  if ((add)->next) {                                                                           \
+   (add)->next->prev = (add);                                                                  \
+  } else {                                                                                     \
+   (head)->prev = (add);                                                                       \
+  }                                                                                            \
+ } else {                                                                                      \
+  DL_PREPEND2(head, add, prev, next);                                                          \
+ }                                                                                             \
+} while (0)                                                                                    \
+
+#define DL_APPEND_ELEM(head, el, add)                                                          \
+   DL_APPEND_ELEM2(head, el, add, prev, next)
+
+#ifdef NO_DECLTYPE
+/* Here are VS2008 / NO_DECLTYPE replacements for a few functions */
+
+#undef DL_INSERT_INORDER2
+#define DL_INSERT_INORDER2(head,add,cmp,prev,next)                                             \
+do {                                                                                           \
+  if ((head) == NULL) {                                                                        \
+    (add)->prev = (add);                                                                       \
+    (add)->next = NULL;                                                                        \
+    (head) = (add);                                                                            \
+  } else if ((cmp(head, add)) >= 0) {                                                          \
+    (add)->prev = (head)->prev;                                                                \
+    (add)->next = (head);                                                                      \
+    (head)->prev = (add);                                                                      \
+    (head) = (add);                                                                            \
+  } else {                                                                                     \
+    char *_tmp = (char*)(head);                                                                \
+    while ((head)->next && (cmp((head)->next, add)) < 0) {                                     \
+      (head) = (head)->next;                                                                   \
+    }                                                                                          \
+    (add)->prev = (head);                                                                      \
+    (add)->next = (head)->next;                                                                \
+    (head)->next = (add);                                                                      \
+    UTLIST_RS(head);                                                                           \
+    if ((add)->next) {                                                                         \
+      (add)->next->prev = (add);                                                               \
+    } else {                                                                                   \
+      (head)->prev = (add);                                                                    \
+    }                                                                                          \
+  }                                                                                            \
+} while (0)
+#endif /* NO_DECLTYPE */
+
+/******************************************************************************
+ * circular doubly linked list macros                                         *
+ *****************************************************************************/
+#define CDL_APPEND(head,add)                                                                   \
+    CDL_APPEND2(head,add,prev,next)
+
+#define CDL_APPEND2(head,add,prev,next)                                                        \
+do {                                                                                           \
+ if (head) {                                                                                   \
+   (add)->prev = (head)->prev;                                                                 \
+   (add)->next = (head);                                                                       \
+   (head)->prev = (add);                                                                       \
+   (add)->prev->next = (add);                                                                  \
+ } else {                                                                                      \
+   (add)->prev = (add);                                                                        \
+   (add)->next = (add);                                                                        \
+   (head) = (add);                                                                             \
+ }                                                                                             \
+} while (0)
+
+#define CDL_PREPEND(head,add)                                                                  \
+    CDL_PREPEND2(head,add,prev,next)
+
+#define CDL_PREPEND2(head,add,prev,next)                                                       \
+do {                                                                                           \
+ if (head) {                                                                                   \
+   (add)->prev = (head)->prev;                                                                 \
+   (add)->next = (head);                                                                       \
+   (head)->prev = (add);                                                                       \
+   (add)->prev->next = (add);                                                                  \
+ } else {                                                                                      \
+   (add)->prev = (add);                                                                        \
+   (add)->next = (add);                                                                        \
+ }                                                                                             \
+ (head) = (add);                                                                               \
+} while (0)
+
+#define CDL_INSERT_INORDER(head,add,cmp)                                                       \
+    CDL_INSERT_INORDER2(head,add,cmp,prev,next)
+
+#define CDL_INSERT_INORDER2(head,add,cmp,prev,next)                                            \
+do {                                                                                           \
+  LDECLTYPE(head) _tmp;                                                                        \
+  if (head) {                                                                                  \
+    CDL_LOWER_BOUND2(head, _tmp, add, cmp, next);                                              \
+    CDL_APPEND_ELEM2(head, _tmp, add, prev, next);                                             \
+  } else {                                                                                     \
+    (head) = (add);                                                                            \
+    (head)->next = (head);                                                                     \
+    (head)->prev = (head);                                                                     \
+  }                                                                                            \
+} while (0)
+
+#define CDL_LOWER_BOUND(head,elt,like,cmp)                                                     \
+    CDL_LOWER_BOUND2(head,elt,like,cmp,next)
+
+#define CDL_LOWER_BOUND2(head,elt,like,cmp,next)                                               \
+do {                                                                                           \
+  if ((head) == NULL || (cmp(head, like)) >= 0) {                                              \
+    (elt) = NULL;                                                                              \
+  } else {                                                                                     \
+    for ((elt) = (head); (elt)->next != (head); (elt) = (elt)->next) {                         \
+      if ((cmp((elt)->next, like)) >= 0) {                                                     \
+        break;                                                                                 \
+      }                                                                                        \
+    }                                                                                          \
+  }                                                                                            \
+} while (0)
+
+#define CDL_DELETE(head,del)                                                                   \
+    CDL_DELETE2(head,del,prev,next)
+
+#define CDL_DELETE2(head,del,prev,next)                                                        \
+do {                                                                                           \
+  if (((head)==(del)) && ((head)->next == (head))) {                                           \
+      (head) = NULL;                                                                           \
+  } else {                                                                                     \
+     (del)->next->prev = (del)->prev;                                                          \
+     (del)->prev->next = (del)->next;                                                          \
+     if ((del) == (head)) (head)=(del)->next;                                                  \
+  }                                                                                            \
+} while (0)
+
+#define CDL_COUNT(head,el,counter)                                                             \
+    CDL_COUNT2(head,el,counter,next)                                                           \
+
+#define CDL_COUNT2(head, el, counter,next)                                                     \
+do {                                                                                           \
+  (counter) = 0;                                                                               \
+  CDL_FOREACH2(head,el,next) { ++(counter); }                                                  \
+} while (0)
+
+#define CDL_FOREACH(head,el)                                                                   \
+    CDL_FOREACH2(head,el,next)
+
+#define CDL_FOREACH2(head,el,next)                                                             \
+    for ((el)=(head);el;(el)=(((el)->next==(head)) ? NULL : (el)->next))
+
+#define CDL_FOREACH_SAFE(head,el,tmp1,tmp2)                                                    \
+    CDL_FOREACH_SAFE2(head,el,tmp1,tmp2,prev,next)
+
+#define CDL_FOREACH_SAFE2(head,el,tmp1,tmp2,prev,next)                                         \
+  for ((el) = (head), (tmp1) = (head) ? (head)->prev : NULL;                                   \
+       (el) && ((tmp2) = (el)->next, 1);                                                       \
+       (el) = ((el) == (tmp1) ? NULL : (tmp2)))
+
+#define CDL_SEARCH_SCALAR(head,out,field,val)                                                  \
+    CDL_SEARCH_SCALAR2(head,out,field,val,next)
+
+#define CDL_SEARCH_SCALAR2(head,out,field,val,next)                                            \
+do {                                                                                           \
+    CDL_FOREACH2(head,out,next) {                                                              \
+      if ((out)->field == (val)) break;                                                        \
+    }                                                                                          \
+} while (0)
+
+#define CDL_SEARCH(head,out,elt,cmp)                                                           \
+    CDL_SEARCH2(head,out,elt,cmp,next)
+
+#define CDL_SEARCH2(head,out,elt,cmp,next)                                                     \
+do {                                                                                           \
+    CDL_FOREACH2(head,out,next) {                                                              \
+      if ((cmp(out,elt))==0) break;                                                            \
+    }                                                                                          \
+} while (0)
+
+#define CDL_REPLACE_ELEM2(head, el, add, prev, next)                                           \
+do {                                                                                           \
+ assert((head) != NULL);                                                                       \
+ assert((el) != NULL);                                                                         \
+ assert((add) != NULL);                                                                        \
+ if ((el)->next == (el)) {                                                                     \
+  (add)->next = (add);                                                                         \
+  (add)->prev = (add);                                                                         \
+  (head) = (add);                                                                              \
+ } else {                                                                                      \
+  (add)->next = (el)->next;                                                                    \
+  (add)->prev = (el)->prev;                                                                    \
+  (add)->next->prev = (add);                                                                   \
+  (add)->prev->next = (add);                                                                   \
+  if ((head) == (el)) {                                                                        \
+   (head) = (add);                                                                             \
+  }                                                                                            \
+ }                                                                                             \
+} while (0)
+
+#define CDL_REPLACE_ELEM(head, el, add)                                                        \
+    CDL_REPLACE_ELEM2(head, el, add, prev, next)
+
+#define CDL_PREPEND_ELEM2(head, el, add, prev, next)                                           \
+do {                                                                                           \
+  if (el) {                                                                                    \
+    assert((head) != NULL);                                                                    \
+    assert((add) != NULL);                                                                     \
+    (add)->next = (el);                                                                        \
+    (add)->prev = (el)->prev;                                                                  \
+    (el)->prev = (add);                                                                        \
+    (add)->prev->next = (add);                                                                 \
+    if ((head) == (el)) {                                                                      \
+      (head) = (add);                                                                          \
+    }                                                                                          \
+  } else {                                                                                     \
+    CDL_APPEND2(head, add, prev, next);                                                        \
+  }                                                                                            \
+} while (0)
+
+#define CDL_PREPEND_ELEM(head, el, add)                                                        \
+    CDL_PREPEND_ELEM2(head, el, add, prev, next)
+
+#define CDL_APPEND_ELEM2(head, el, add, prev, next)                                            \
+do {                                                                                           \
+ if (el) {                                                                                     \
+  assert((head) != NULL);                                                                      \
+  assert((add) != NULL);                                                                       \
+  (add)->next = (el)->next;                                                                    \
+  (add)->prev = (el);                                                                          \
+  (el)->next = (add);                                                                          \
+  (add)->next->prev = (add);                                                                   \
+ } else {                                                                                      \
+  CDL_PREPEND2(head, add, prev, next);                                                         \
+ }                                                                                             \
+} while (0)
+
+#define CDL_APPEND_ELEM(head, el, add)                                                         \
+    CDL_APPEND_ELEM2(head, el, add, prev, next)
+
+#ifdef NO_DECLTYPE
+/* Here are VS2008 / NO_DECLTYPE replacements for a few functions */
+
+#undef CDL_INSERT_INORDER2
+#define CDL_INSERT_INORDER2(head,add,cmp,prev,next)                                            \
+do {                                                                                           \
+  if ((head) == NULL) {                                                                        \
+    (add)->prev = (add);                                                                       \
+    (add)->next = (add);                                                                       \
+    (head) = (add);                                                                            \
+  } else if ((cmp(head, add)) >= 0) {                                                          \
+    (add)->prev = (head)->prev;                                                                \
+    (add)->next = (head);                                                                      \
+    (add)->prev->next = (add);                                                                 \
+    (head)->prev = (add);                                                                      \
+    (head) = (add);                                                                            \
+  } else {                                                                                     \
+    char *_tmp = (char*)(head);                                                                \
+    while ((char*)(head)->next != _tmp && (cmp((head)->next, add)) < 0) {                      \
+      (head) = (head)->next;                                                                   \
+    }                                                                                          \
+    (add)->prev = (head);                                                                      \
+    (add)->next = (head)->next;                                                                \
+    (add)->next->prev = (add);                                                                 \
+    (head)->next = (add);                                                                      \
+    UTLIST_RS(head);                                                                           \
+  }                                                                                            \
+} while (0)
+#endif /* NO_DECLTYPE */
+
+#endif /* UTLIST_H */
--- a/libmlk-rpg/CMakeLists.txt	Mon Mar 20 21:59:02 2023 +0100
+++ b/libmlk-rpg/CMakeLists.txt	Tue Mar 21 14:08:15 2023 +0100
@@ -20,6 +20,8 @@
 
 set(
 	SOURCES
+	${libmlk-rpg_SOURCE_DIR}/mlk/rpg/loader-file_p.c
+	${libmlk-rpg_SOURCE_DIR}/mlk/rpg/loader-file_p.h
 	${libmlk-rpg_SOURCE_DIR}/mlk/rpg/map-loader-file.c
 	${libmlk-rpg_SOURCE_DIR}/mlk/rpg/map-loader.c
 	${libmlk-rpg_SOURCE_DIR}/mlk/rpg/map.c
@@ -80,6 +82,7 @@
 	LIBRARIES
 		libmlk-ui
 		libmlk-sqlite
+		libmlk-utlist
 	INCLUDES
 		PRIVATE $<BUILD_INTERFACE:${libmlk-rpg_BINARY_DIR}>
 		PUBLIC $<BUILD_INTERFACE:${libmlk-rpg_SOURCE_DIR}>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libmlk-rpg/mlk/rpg/loader-file_p.c	Tue Mar 21 14:08:15 2023 +0100
@@ -0,0 +1,173 @@
+/*
+ * loader-file_p.c -- resource loader for map/tileset
+ *
+ * Copyright (c) 2020-2023 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <assert.h>
+#include <stdio.h>
+
+#include <utlist.h>
+
+#include <mlk/util/util.h>
+
+#include <mlk/core/alloc.h>
+#include <mlk/core/animation.h>
+#include <mlk/core/image.h>
+#include <mlk/core/sprite.h>
+#include <mlk/core/texture.h>
+
+#include "loader-file_p.h"
+
+struct texture_node {
+	struct mlk_texture texture;
+	struct texture_node *next;
+};
+
+struct sprite_node {
+	struct mlk_sprite sprite;
+	struct sprite_node *next;
+};
+
+struct animation_node {
+	struct mlk_animation animation;
+	struct animation_node *next;
+};
+
+struct mlk__loader_file {
+	char directory[MLK_PATH_MAX];
+	struct texture_node *textures;
+	struct sprite_node *sprites;
+	struct animation_node *animations;
+};
+
+static inline void
+free_textures(struct mlk__loader_file *loader)
+{
+	struct texture_node *node, *tmp;
+
+	LL_FOREACH_SAFE(loader->textures, node, tmp) {
+		mlk_texture_finish(&node->texture);
+		mlk_alloc_free(node);
+	}
+
+	loader->textures = NULL;
+}
+
+static inline void
+free_sprites(struct mlk__loader_file *loader)
+{
+	struct sprite_node *node, *tmp;
+
+	LL_FOREACH_SAFE(loader->sprites, node, tmp)
+		mlk_alloc_free(node);
+
+	loader->sprites = NULL;
+}
+
+static inline void
+free_animations(struct mlk__loader_file *loader)
+{
+	struct animation_node *node, *tmp;
+
+	LL_FOREACH_SAFE(loader->animations, node, tmp)
+		mlk_alloc_free(node);
+
+	loader->animations = NULL;
+}
+
+struct mlk__loader_file *
+mlk__loader_file_new(const char *file)
+{
+	assert(file);
+
+	struct mlk__loader_file *loader;
+	char filepath[MLK_PATH_MAX];
+
+	if (!(loader = mlk_alloc_new0(1, sizeof (*loader))))
+		return NULL;
+
+	mlk_util_strlcpy(filepath, file, sizeof (filepath));
+	mlk_util_strlcpy(loader->directory, mlk_util_dirname(filepath), sizeof (loader->directory));
+
+	return loader;
+}
+
+const char *
+mlk__loader_file_directory(const struct mlk__loader_file *loader)
+{
+	assert(loader);
+
+	return loader->directory;
+}
+
+struct mlk_texture *
+mlk__loader_file_texture_open(struct mlk__loader_file *loader, const char *ident)
+{
+	struct texture_node *node;
+	char path[MLK_PATH_MAX];
+
+	if (!(node = mlk_alloc_new0(1, sizeof (*node))))
+		return NULL;
+
+	snprintf(path, sizeof (path), "%s/%s", loader->directory, ident);
+
+	if (mlk_image_open(&node->texture, path) < 0) {
+		mlk_alloc_free(node);
+		return NULL;
+	}
+
+	LL_APPEND(loader->textures, node);
+
+	return &node->texture;
+}
+
+struct mlk_sprite *
+mlk__loader_file_sprite_new(struct mlk__loader_file *loader)
+{
+	struct sprite_node *node;
+
+	if (!(node = mlk_alloc_new0(1, sizeof (*node))))
+		return NULL;
+
+	LL_APPEND(loader->sprites, node);
+
+	return &node->sprite;
+}
+
+struct mlk_animation *
+mlk__loader_file_animation_new(struct mlk__loader_file *loader)
+{
+	struct animation_node *node;
+
+	if (!(node = mlk_alloc_new0(1, sizeof (*node))))
+		return NULL;
+
+	LL_APPEND(loader->animations, node);
+
+	return &node->animation;
+}
+
+void
+mlk__loader_file_free(struct mlk__loader_file *loader)
+{
+	assert(loader);
+
+	free_textures(loader);
+	free_sprites(loader);
+	free_animations(loader);
+
+	mlk_alloc_free(loader);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libmlk-rpg/mlk/rpg/loader-file_p.h	Tue Mar 21 14:08:15 2023 +0100
@@ -0,0 +1,46 @@
+/*
+ * loader-file_p.h -- resource loader for map/tileset
+ *
+ * Copyright (c) 2020-2023 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef MLK_RPG_LOADER_FILE_P_H
+#define MLK_RPG_LOADER_FILE_P_H
+
+struct mlk_animation;
+struct mlk_sprite;
+struct mlk_texture;
+
+struct mlk__loader_file;
+
+struct mlk__loader_file *
+mlk__loader_file_new(const char *path);
+
+const char *
+mlk__loader_file_directory(const struct mlk__loader_file *loader);
+
+struct mlk_texture *
+mlk__loader_file_texture_open(struct mlk__loader_file *loader, const char *ident);
+
+struct mlk_sprite *
+mlk__loader_file_sprite_new(struct mlk__loader_file *loader);
+
+struct mlk_animation *
+mlk__loader_file_animation_new(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	Mon Mar 20 21:59:02 2023 +0100
+++ b/libmlk-rpg/mlk/rpg/map-loader-file.c	Tue Mar 21 14:08:15 2023 +0100
@@ -17,203 +17,183 @@
  */
 
 #include <assert.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <mlk/util/util.h>
 
 #include <mlk/core/alloc.h>
 #include <mlk/core/image.h>
 #include <mlk/core/sprite.h>
 #include <mlk/core/texture.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];
 
-static inline void *
-allocate(void ***array, size_t width)
-{
-	void **ptr, *elem;
+	/*
+	 * We use a tileset file loader if new_tileset function isn't present in
+	 * this map loader.
+	 */
+	struct mlk_tileset_loader tileset_loader;
+	struct mlk_tileset tileset;
 
-	/* Not yet allocated? Allocate a new pointer element. */
-	if (!*array)
-		ptr = mlk_alloc_new0(1, sizeof (void *));
-	else
-		ptr = mlk_alloc_expand(*array, 1);
+	/* Own allocated blocks. */
+	struct mlk_map_block *blocks;
+};
 
-	if (!ptr)
-		return NULL;
+static struct self *
+self_new(const char *path)
+{
+	struct self *self;
 
-	/* Now allocate the element itself because. */
-	if (!(elem = mlk_alloc_new0(1, width)))
+	if (!(self = mlk_alloc_new0(1, sizeof (*self))))
+		return NULL;
+	if (!(self->loader = mlk__loader_file_new(path))) {
+		mlk_alloc_free(self);
 		return NULL;
+	}
 
-	/* Store it into the array of elements. */
-	ptr[mlk_alloc_getn(ptr) - 1] = elem;
-	*array = ptr;
+	return self;
+}
 
-	return elem;
+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);
 }
 
 static struct mlk_texture *
-init_texture(struct mlk_map_loader *self,
-             struct mlk_map *map,
-             const char *ident)
+new_texture(struct mlk_map_loader *loader,
+            struct mlk_map *map,
+            const char *ident)
 {
 	(void)map;
 
-	struct mlk_map_loader_file *file = self->data;
-	struct mlk_texture *texture;
-	char path[MLK_PATH_MAX];
-
-	snprintf(path, sizeof (path), "%s/%s", file->directory, ident);
+	struct self *self = loader->data;
 
-	/* No need to deallocate, already done in finish anyway. */
-	if (!(texture = allocate((void ***)&file->textures, sizeof (struct mlk_texture))))
-		return NULL;
-	if (mlk_image_open(texture, path) < 0)
-		return NULL;
-
-	return texture;
+	return mlk__loader_file_texture_open(self->loader, ident);
 }
 
 static struct mlk_sprite *
-init_sprite(struct mlk_map_loader *self, struct mlk_map *map)
+new_sprite(struct mlk_map_loader *loader, struct mlk_map *map)
 {
 	(void)map;
 
-	struct mlk_map_loader_file *file = self->data;
+	struct self *self = loader->data;
 
-	return allocate((void ***)&file->sprites, sizeof (struct mlk_sprite));
+	return mlk__loader_file_sprite_new(self->loader);
 }
 
 static struct mlk_tileset *
-init_tileset(struct mlk_map_loader *self,
-             struct mlk_map *map,
-             const char *ident)
+new_tileset(struct mlk_map_loader *loader,
+            struct mlk_map *map,
+            const char *ident)
 {
 	(void)map;
 
-	struct mlk_map_loader_file *file = self->data;
-	struct mlk_tileset_loader tileset_loader;
+	struct self *self = loader->data;
 	char path[MLK_PATH_MAX];
 
-	snprintf(path, sizeof (path), "%s/%s", file->directory, ident);
+	snprintf(path, sizeof (path), "%s/%s", mlk__loader_file_directory(self->loader), ident);
 
 	/*
 	 * Just make sure that we don't leak in case tileset directory is listed
 	 * more than once.
 	 */
-	mlk_tileset_loader_file_finish(&file->tileset_loader_file);
-	mlk_tileset_loader_file_init(&file->tileset_loader_file, &tileset_loader, path);
+	mlk_tileset_loader_file_finish(&self->tileset_loader);
+	mlk_tileset_loader_file_init(&self->tileset_loader, path);
 
-	if (mlk_tileset_loader_open(&tileset_loader, &file->tileset, path) < 0)
+	if (mlk_tileset_loader_open(&self->tileset_loader, &self->tileset, path) < 0)
 		return NULL;
 
-	return &file->tileset;
+	return &self->tileset;
 }
 
 static unsigned int *
-alloc_tiles(struct mlk_map_loader *self,
-            struct mlk_map *map,
-            enum mlk_map_layer_type type,
-            size_t n)
+new_tiles(struct mlk_map_loader *loader,
+          struct mlk_map *map,
+          enum mlk_map_layer_type type,
+          size_t n)
 {
 	(void)map;
 
-	struct mlk_map_loader_file *file = self->data;
+	struct self *self = loader->data;
 
-	return file->tiles[type] = mlk_alloc_new0(n, sizeof (unsigned int));
+	return self->tiles[type] = mlk_alloc_new0(n, sizeof (unsigned int));
 }
 
 static struct mlk_map_block *
-expand_blocks(struct mlk_map_loader *self,
+expand_blocks(struct mlk_map_loader *loader,
               struct mlk_map *map,
               struct mlk_map_block *blocks,
               size_t blocksz)
 {
 	(void)map;
 
-	struct mlk_map_loader_file *file = self->data;
+	struct self *self = loader->data;
 	struct mlk_map_block *ptr;
 
-	if (!file->blocks)
+	if (!self->blocks)
 		ptr = mlk_alloc_new0(1, sizeof (*ptr));
 	else
-		ptr = mlk_alloc_expand(file->blocks, blocksz);
+		ptr = mlk_alloc_expand(self->blocks, blocksz);
 
 	if (ptr)
-		file->blocks = blocks;
+		self->blocks = blocks;
 
 	return ptr;
 }
 
-static void
-finish(void ***ptr, void (*finish)(void *))
+int
+mlk_map_loader_file_init(struct mlk_map_loader *loader, const char *filename)
 {
-	size_t len;
+	assert(loader);
+	assert(filename);
 
-	/* Already cleared. */
-	if (!*ptr)
-		return;
+	struct self *self;
 
-	len = mlk_alloc_getn(*ptr);
+	memset(loader, 0, sizeof (*loader));
 
-	for (size_t i = 0; i < len; ++i)
-		finish((*ptr)[i]);
+	if (!(self = self_new(filename)))
+		return -1;
 
-	mlk_alloc_free(*ptr);
-	*ptr = NULL;
-}
+	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;
 
-static void
-finish_texture(void *element)
-{
-	mlk_texture_finish(element);
-	mlk_alloc_free(element);
+	return 0;
 }
 
 void
-mlk_map_loader_file_init(struct mlk_map_loader_file *file,
-                         struct mlk_map_loader *loader,
-                         const char *filename)
+mlk_map_loader_file_finish(struct mlk_map_loader *loader)
 {
-	assert(file);
 	assert(loader);
 
-	char filepath[MLK_PATH_MAX];
+	struct self *self = loader->data;
 
-	if (!file->directory[0]) {
-		/* Determine base filename base directory. */
-		mlk_util_strlcpy(filepath, filename, sizeof (filepath));
-		mlk_util_strlcpy(file->directory, mlk_util_dirname(filepath), sizeof (file->directory));
-	}
-	if (!loader->data)
-		loader->data = file;
-	if (!loader->init_tileset)
-		loader->init_tileset = init_tileset;
-	if (!loader->init_texture)
-		loader->init_texture = init_texture;
-	if (!loader->init_sprite)
-		loader->init_sprite = init_sprite;
-	if (!loader->alloc_tiles)
-		loader->alloc_tiles = alloc_tiles;
-	if (!loader->expand_blocks)
-		loader->expand_blocks = expand_blocks;
+	if (self)
+		self_free(self);
+
+	memset(loader, 0, sizeof (*loader));
 }
-
-void
-mlk_map_loader_file_finish(struct mlk_map_loader_file *file)
-{
-	assert(file);
-
-	for (int i = 0; i < MLK_MAP_LAYER_TYPE_LAST; ++i) {
-		mlk_alloc_free(file->tiles[i]);
-		file->tiles[i] = NULL;
-	}
-
-	mlk_tileset_loader_file_finish(&file->tileset_loader_file);
-	mlk_alloc_free(file->blocks);
-	file->blocks = NULL;
-
-	finish((void ***)&file->sprites, mlk_alloc_free);
-	finish((void ***)&file->textures, finish_texture);
-}
--- a/libmlk-rpg/mlk/rpg/map-loader-file.h	Mon Mar 20 21:59:02 2023 +0100
+++ b/libmlk-rpg/mlk/rpg/map-loader-file.h	Tue Mar 21 14:08:15 2023 +0100
@@ -22,64 +22,59 @@
 /**
  * \file map-loader-file.h
  * \brief Map file loader implementation
+ *
+ * This loader implements the map loader interface from a file into a directory
+ * (usually ending with `.map` extension).  It will find and allocate resources
+ * 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.
+ *
+ * Example of use:
+ *
+ * ```c
+ * struct mlk_map_loader 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)
+ *     mlk_panic();
+ *
+ * // Load the map from the file on disk.
+ * if (mlk_map_loader_open(&loader, &map, map_path) < 0)
+ *     mlk_panic();
+ *
+ * // Don't forget to initialize the map before use.
+ * mlk_map_init(&map);
+ *
+ * // Destroy the resources.
+ * mlk_map_finish(&map);
+ * mlk_map_loader_file_finish(&loader);
+ * ```
  */
 
-#include <mlk/util/util.h>
-
-#include "map.h"
-#include "tileset-loader-file.h"
-#include "tileset.h"
-
 struct mlk_map_loader;
-struct mlk_sprite;
-struct mlk_texture;
 
 /**
- * \struct mlk_map_loader_file
- * \brief Map loader file structure
- */
-struct mlk_map_loader_file {
-	/**
-	 * (read-only)
-	 *
-	 * Computed map file directory.
-	 */
-	char directory[MLK_PATH_MAX];
-
-	/** \cond MLK_PRIVATE_DECLS */
-	unsigned int *tiles[MLK_MAP_LAYER_TYPE_LAST];
-
-	struct mlk_texture **textures;
-	struct mlk_sprite **sprites;
-
-	/*
-	 * We use a tileset file loader if init_tileset function isn't present
-	 * in this map loader.
-	 */
-	struct mlk_tileset_loader_file tileset_loader_file;
-	struct mlk_tileset tileset;
-
-	struct mlk_map_block *blocks;
-	/** \endcond MLK_PRIVATE_DECLS */
-};
-
-/**
- * Initialize the map loader.
+ * Initialize the loader with internal functions and internal data to allocate
+ * and find appropriate resources relative to the map filename.
  *
- * All loader member functions will be set and ::mlk_map_loader::data will be
- * set to file loader.
+ * After loading the map with this underlying loader, it should be kept until
+ * the map is no longer used.
  *
- * \pre file != NULL
  * \pre loader != NULL
  * \pre filename != NULL
- * \param file the file loader
  * \param loader the abstract loader interface
  * \param filename path to the map file
+ * \return 0 on success or -1 on error
  */
-void
-mlk_map_loader_file_init(struct mlk_map_loader_file *file,
-                         struct mlk_map_loader *loader,
-                         const char *filename);
+int
+mlk_map_loader_file_init(struct mlk_map_loader *loader, const char *filename);
 
 /**
  * Cleanup allocated resources by this file loader.
@@ -89,6 +84,6 @@
  * \warning the map loaded with this loader must not be used
  */
 void
-mlk_map_loader_file_finish(struct mlk_map_loader_file *file);
+mlk_map_loader_file_finish(struct mlk_map_loader *file);
 
 #endif /* !MLK_RPG_MAP_LOADER_FILE_H */
--- a/libmlk-rpg/mlk/rpg/map-loader.c	Mon Mar 20 21:59:02 2023 +0100
+++ b/libmlk-rpg/mlk/rpg/map-loader.c	Tue Mar 21 14:08:15 2023 +0100
@@ -54,7 +54,7 @@
 	 * The next line after a layer declaration is a list of plain integer
 	 * that fill the layer tiles.
 	 */
-	if (!(tiles = loader->alloc_tiles(loader, map, layer_type, amount)))
+	if (!(tiles = loader->new_tiles(loader, map, layer_type, amount)))
 		return -1;
 
 	for (unsigned int tile; fscanf(fp, "%u\n", &tile) && current < amount; ++current)
@@ -82,12 +82,12 @@
 	snprintf(fmt, sizeof (fmt), "%%d|%%d|%%u|%%u|%%d|%%%zu[^\n]\n", sizeof (exec) - 1);
 
 	while (fscanf(fp, fmt, &x, &y, &w, &h, &isblock, exec) >= 5) {
-		if (!loader->load_object) {
+		if (!loader->new_object) {
 			mlk_tracef("ignoring object %d,%d,%u,%u,%d,%s", x, y, w, h, isblock, exec);
 			continue;
 		}
 
-		loader->load_object(loader, map, x, y, w, h, exec);
+		loader->new_object(loader, map, x, y, w, h, exec);
 
 		/*
 		 * Actions do not have concept of collisions because they are
@@ -151,7 +151,7 @@
 
 	if (!(p = strchr(line, '|')))
 		return mlk_errf("could not parse tileset");
-	if (!(map->tileset = loader->init_tileset(loader, map, p + 1)))
+	if (!(map->tileset = loader->new_tileset(loader, map, p + 1)))
 		return -1;
 
 	return 0;
@@ -219,9 +219,9 @@
 
 	if (sscanf(line, format, &w, &h, ident) != 3)
 		return mlk_errf("invalid player sprite");
-	if (!(texture = loader->init_texture(loader, map, ident)))
+	if (!(texture = loader->new_texture(loader, map, ident)))
 		return -1;
-	if (!(sprite = loader->init_sprite(loader, map)))
+	if (!(sprite = loader->new_sprite(loader, map)))
 		return -1;
 
 	sprite->cellw = w;
--- a/libmlk-rpg/mlk/rpg/map-loader.h	Mon Mar 20 21:59:02 2023 +0100
+++ b/libmlk-rpg/mlk/rpg/map-loader.h	Tue Mar 21 14:08:15 2023 +0100
@@ -36,6 +36,11 @@
  * This module provides a generic way to open maps. It uses a callback similar
  * to the ::mlk_tileset_loader.
  */
+
+/**
+ * \struct mlk_map_loader
+ * \brief Abstract map loader structure
+ */
 struct mlk_map_loader {
 	/**
 	 * (read-write, borrowed, optional)
@@ -54,9 +59,9 @@
 	 * \param ident the texture name (or path)
 	 * \return the tileset to use or NULL on failure
 	 */
-	struct mlk_tileset * (*init_tileset)(struct mlk_map_loader *self,
-	                                     struct mlk_map *map,
-	                                     const char *ident);
+	struct mlk_tileset * (*new_tileset)(struct mlk_map_loader *self,
+	                                    struct mlk_map *map,
+	                                    const char *ident);
 
 	/**
 	 * (read-write)
@@ -67,9 +72,9 @@
 	 * \param ident the texture name (or path)
 	 * \return a borrowed texture or NULL on failure
 	 */
-	struct mlk_texture * (*init_texture)(struct mlk_map_loader *self,
-	                                     struct mlk_map *map,
-	                                     const char *ident);
+	struct mlk_texture * (*new_texture)(struct mlk_map_loader *self,
+	                                    struct mlk_map *map,
+	                                    const char *ident);
 
 	/**
 	 * (read-write)
@@ -80,8 +85,8 @@
 	 * \return a unused sprite
 	 * \return a borrowed sprite or NULL on failure
 	 */
-	struct mlk_sprite * (*init_sprite)(struct mlk_map_loader *self,
-	                                   struct mlk_map *map);
+	struct mlk_sprite * (*new_sprite)(struct mlk_map_loader *self,
+	                                  struct mlk_map *map);
 
 	/**
 	 * (read-write)
@@ -94,10 +99,10 @@
 	 * \param n the number of tile items (rows * columns)
 	 * \return a pointer to a usable area or NULL on failure
 	 */
-	unsigned int * (*alloc_tiles)(struct mlk_map_loader *self,
-	                              struct mlk_map *map,
-	                              enum mlk_map_layer_type type,
-	                              size_t n);
+	unsigned int * (*new_tiles)(struct mlk_map_loader *self,
+	                            struct mlk_map *map,
+	                            enum mlk_map_layer_type type,
+	                            size_t n);
 
 	/**
 	 * (read-write, optional)
@@ -112,13 +117,13 @@
 	 * \param h the object height
 	 * \param argument optional data to pass to the object
 	 */
-	void (*load_object)(struct mlk_map_loader *self,
-	                    struct mlk_map *map,
-	                    int x,
-	                    int y,
-	                    unsigned int w,
-	                    unsigned int h,
-	                    const char *argument);
+	void (*new_object)(struct mlk_map_loader *self,
+	                   struct mlk_map *map,
+	                   int x,
+	                   int y,
+	                   unsigned int w,
+	                   unsigned int h,
+	                   const char *argument);
 
 	/**
 	 * (read-write)
--- a/libmlk-rpg/mlk/rpg/tileset-loader-file.c	Mon Mar 20 21:59:02 2023 +0100
+++ b/libmlk-rpg/mlk/rpg/tileset-loader-file.c	Tue Mar 21 14:08:15 2023 +0100
@@ -25,35 +25,46 @@
 #include <mlk/core/sprite.h>
 #include <mlk/core/texture.h>
 
+#include "loader-file_p.h"
 #include "tileset-loader-file.h"
 #include "tileset-loader.h"
 #include "tileset.h"
 
-static inline void *
-allocate(void ***array, size_t width)
-{
-	void **ptr, *elem;
+struct self {
+	/* Resources allocator. */
+	struct mlk__loader_file *loader;
 
-	/* Not yet allocated? Allocate a new pointer element. */
-	if (!*array)
-		ptr = mlk_alloc_new0(1, sizeof (void *));
-	else
-		ptr = mlk_alloc_expand(*array, 1);
+	/* Arrays reallocated on purpose. */
+	struct mlk_tileset_collision *tilecollisions;
+	struct mlk_tileset_animation *tileanimations;
+};
+
+static struct self *
+self_new(const char *path)
+{
+	struct self *self;
 
-	if (!ptr)
+	if (!(self = mlk_alloc_new0(1, sizeof (*self))))
 		return NULL;
+	if (!(self->loader = mlk__loader_file_new(path))) {
+		mlk_alloc_free(self);
+		return NULL;
+	}
 
-	/* Now allocate the element itself because. */
-	if (!(elem = mlk_alloc_new0(1, width)))
-		return NULL;
+	return self;
+}
 
-	/* Store it into the array of elements. */
-	ptr[mlk_alloc_getn(ptr) - 1] = elem;
-	*array = ptr;
+static void
+self_free(struct self *self)
+{
+	mlk__loader_file_free(self->loader);
 
-	return elem;
+	/* Clear array of collisions/animations .*/
+	mlk_alloc_free(self->tilecollisions);
+	mlk_alloc_free(self->tileanimations);
 }
 
+
 static void *
 expand(void **array, size_t n, size_t w)
 {
@@ -70,128 +81,96 @@
 	return ptr;
 }
 
-static void
-finish(void ***ptr, void (*finish)(void *))
-{
-	size_t len;
-
-	/* Already cleared. */
-	if (!*ptr)
-		return;
-
-	len = mlk_alloc_getn(*ptr);
-
-	for (size_t i = 0; i < len; ++i)
-		finish((*ptr)[i]);
-
-	mlk_alloc_free(*ptr);
-	*ptr = NULL;
-}
-
-static void
-finish_texture(void *element)
+static struct mlk_texture *
+new_texture(struct mlk_tileset_loader *loader, struct mlk_tileset *tileset, const char *ident)
 {
-	mlk_texture_finish(element);
-	mlk_alloc_free(element);
-}
+	(void)tileset;
 
-static struct mlk_texture *
-init_texture(struct mlk_tileset_loader *self, const char *ident)
-{
-	struct mlk_tileset_loader_file *file = self->data;
-	struct mlk_texture *texture;
-	char path[MLK_PATH_MAX];
+	struct self *self = loader->data;
 
-	snprintf(path, sizeof (path), "%s/%s", file->directory, ident);
-
-	/* No need to deallocate, already done in finish anyway. */
-	if (!(texture = allocate((void ***)&file->textures, sizeof (struct mlk_texture))))
-		return NULL;
-	if (mlk_image_open(texture, path) < 0)
-		return NULL;
-
-	return texture;
+	return mlk__loader_file_texture_open(self->loader, ident);
 }
 
 static struct mlk_sprite *
-init_sprite(struct mlk_tileset_loader *self)
+new_sprite(struct mlk_tileset_loader *loader, struct mlk_tileset *tileset)
 {
-	struct mlk_tileset_loader_file *file = self->data;
+	(void)tileset;
 
-	return allocate((void ***)&file->sprites, sizeof (struct mlk_sprite));
+	struct self *self = loader->data;
+
+	return mlk__loader_file_sprite_new(self->loader);
 }
 
 static struct mlk_animation *
-init_animation(struct mlk_tileset_loader *self)
+new_animation(struct mlk_tileset_loader *loader, struct mlk_tileset *tileset)
 {
-	struct mlk_tileset_loader_file *file = self->data;
+	(void)tileset;
 
-	return allocate((void ***)&file->animations, sizeof (struct mlk_animation));
+	struct self *self = loader->data;
+
+	return mlk__loader_file_animation_new(self->loader);
 }
 
 struct mlk_tileset_collision *
-expand_collisions(struct mlk_tileset_loader *self,
+expand_collisions(struct mlk_tileset_loader *loader,
+                  struct mlk_tileset *tileset,
                   struct mlk_tileset_collision *array,
                   size_t arraysz)
 {
+	(void)tileset;
 	(void)array;
 
-	struct mlk_tileset_loader_file *file = self->data;
+	struct self *self = loader->data;
 
-	return expand((void **)&file->tilecollisions, arraysz, sizeof (struct mlk_tileset_collision));
+	return expand((void **)&self->tilecollisions, arraysz, sizeof (struct mlk_tileset_collision));
 }
 
 struct mlk_tileset_animation *
-expand_animations(struct mlk_tileset_loader *self,
+expand_animations(struct mlk_tileset_loader *loader,
+                  struct mlk_tileset *tileset,
                   struct mlk_tileset_animation *array,
                   size_t arraysz)
 {
+	(void)tileset;
 	(void)array;
 
-	struct mlk_tileset_loader_file *file = self->data;
+	struct self *self = loader->data;
 
-	return expand((void **)&file->tileanimations, arraysz, sizeof (struct mlk_tileset_animation));
+	return expand((void **)&self->tileanimations, arraysz, sizeof (struct mlk_tileset_animation));
 }
 
-void
-mlk_tileset_loader_file_init(struct mlk_tileset_loader_file *file,
-                      struct mlk_tileset_loader *loader,
-                      const char *filename)
+int
+mlk_tileset_loader_file_init(struct mlk_tileset_loader *loader, const char *filename)
 {
-	assert(file);
 	assert(loader);
 	assert(filename);
 
-	char filepath[MLK_PATH_MAX];
+	struct self *self;
 
-	memset(file, 0, sizeof (*file));
 	memset(loader, 0, sizeof (*loader));
 
-	/* Determine base filename base directory. */
-	mlk_util_strlcpy(filepath, filename, sizeof (filepath));
-	mlk_util_strlcpy(file->directory, mlk_util_dirname(filepath), sizeof (file->directory));
+	if (!(self = self_new(filename)))
+		return -1;
 
-	loader->data = file;
-	loader->init_texture = init_texture;
-	loader->init_sprite = init_sprite;
-	loader->init_animation = init_animation;
+	loader->data = self;
+	loader->new_texture = new_texture;
+	loader->new_sprite = new_sprite;
+	loader->new_animation = new_animation;
 	loader->expand_collisions = expand_collisions;
 	loader->expand_animations = expand_animations;
+
+	return 0;
 }
 
 void
-mlk_tileset_loader_file_finish(struct mlk_tileset_loader_file *file)
+mlk_tileset_loader_file_finish(struct mlk_tileset_loader *loader)
 {
-	assert(file);
+	assert(loader);
+
+	struct self *self = loader->data;
 
-	/* Finalize individual elements. */
-	finish((void ***)&file->textures, finish_texture);
-	finish((void ***)&file->sprites, mlk_alloc_free);
-	finish((void ***)&file->animations, mlk_alloc_free);
+	if (self)
+		self_free(self);
 
-	/* Clear array of collisions/animations .*/
-	mlk_alloc_free(file->tilecollisions);
-	mlk_alloc_free(file->tileanimations);
-
-	memset(file, 0, sizeof (*file));
+	memset(loader, 0, sizeof (*loader));
 }
--- a/libmlk-rpg/mlk/rpg/tileset-loader-file.h	Mon Mar 20 21:59:02 2023 +0100
+++ b/libmlk-rpg/mlk/rpg/tileset-loader-file.h	Tue Mar 21 14:08:15 2023 +0100
@@ -31,59 +31,27 @@
  * the allocator functions can be changed.
  */
 
-#include <mlk/util/util.h>
-
-struct mlk_animation;
-struct mlk_sprite;
-struct mlk_texture;
-struct mlk_tileset_animation;
-struct mlk_tileset_collision;
 struct mlk_tileset_loader;
 
-/**
- * \struct mlk_tileset_loader_file
- * \brief Tileset file loader structure
- */
-struct mlk_tileset_loader_file {
-	/**
-	 * (read-only)
-	 *
-	 * Computed tileset file directory.
-	 */
-	char directory[MLK_PATH_MAX];
-
-	/** \cond MLK_PRIVATE_DECLS */
-	struct mlk_texture **textures;
-	struct mlk_sprite **sprites;
-	struct mlk_animation **animations;
-	struct mlk_tileset_collision *tilecollisions;
-	struct mlk_tileset_animation *tileanimations;
-	/** \endcond MLK_PRIVATE_DECLS */
-};
-
 #if defined(__cplusplus)
 extern "C" {
 #endif
 
 /**
- * Fill the abstract loader with appropriate implementation.
+ * Initialize the loader with internal functions and internal data to allocate
+ * and find appropriate resources relative to the tileset filename.
  *
- * All loader member functions will be set and ::mlk_tileset_loader::data will
- * be set to file loader.
+ * After loading the tileset with this underlying loader, it should be kept
+ * until the tileset is no longer used.
  *
- * The file and loader structure are zero'ed before being initialized.
- *
- * \pre file != NULL
  * \pre loader != NULL
  * \pre filename != NULL
- * \param file the file loader
  * \param loader the abstract loader interface
  * \param filename path to the tileset file
+ * \return 0 on success or -1 on error
  */
-void
-mlk_tileset_loader_file_init(struct mlk_tileset_loader_file *file,
-                             struct mlk_tileset_loader *loader,
-                             const char *filename);
+int
+mlk_tileset_loader_file_init(struct mlk_tileset_loader *loader, const char *filename);
 
 /**
  * Cleanup allocated resources by this file loader.
@@ -92,7 +60,7 @@
  * \param file the file loader
  */
 void
-mlk_tileset_loader_file_finish(struct mlk_tileset_loader_file *file);
+mlk_tileset_loader_file_finish(struct mlk_tileset_loader *file);
 
 #if defined(__cplusplus)
 }
--- a/libmlk-rpg/mlk/rpg/tileset-loader.c	Mon Mar 20 21:59:02 2023 +0100
+++ b/libmlk-rpg/mlk/rpg/tileset-loader.c	Tue Mar 21 14:08:15 2023 +0100
@@ -106,7 +106,7 @@
 	size_t collisionsz = 0;
 
 	while (fscanf(fp, "%u|%d|%d|%u|%u\n", &id, &x, &y, &w, &h) == 5) {
-		if (!(array = loader->expand_collisions(loader, collisions, collisionsz + 1)))
+		if (!(array = loader->expand_collisions(loader, tileset, collisions, collisionsz + 1)))
 			return -1;
 
 		collisions = array;
@@ -158,13 +158,13 @@
 	 * 4. Link the animation to the tileset animation.
 	 */
 	while (fscanf(fp, fmt, &id, filename, &delay) == 3) {
-		if (!(texture = loader->init_texture(loader, filename)))
+		if (!(texture = loader->new_texture(loader, tileset, filename)))
 			return -1;
-		if (!(sprite = loader->init_sprite(loader)))
+		if (!(sprite = loader->new_sprite(loader, tileset)))
 			return -1;
-		if (!(animation = loader->init_animation(loader)))
+		if (!(animation = loader->new_animation(loader, tileset)))
 			return -1;
-		if (!(array = loader->expand_animations(loader, tileanimations, tileanimationsz + 1)))
+		if (!(array = loader->expand_animations(loader, tileset, tileanimations, tileanimationsz + 1)))
 			return -1;
 
 		/* Bind the texture to the new sprite. */
@@ -211,9 +211,9 @@
 		return mlk_errf("missing tile dimensions before image");
 	if (!(p = strchr(line, '|')))
 		return mlk_errf("could not parse image");
-	if (!(texture = loader->init_texture(loader, p + 1)))
+	if (!(texture = loader->new_texture(loader, tileset, p + 1)))
 		return -1;
-	if (!(sprite = loader->init_sprite(loader)))
+	if (!(sprite = loader->new_sprite(loader, tileset)))
 		return -1;
 
 	/* Initialize the sprite with the texture. */
--- a/libmlk-rpg/mlk/rpg/tileset-loader.h	Mon Mar 20 21:59:02 2023 +0100
+++ b/libmlk-rpg/mlk/rpg/tileset-loader.h	Tue Mar 21 14:08:15 2023 +0100
@@ -45,7 +45,7 @@
 
 /**
  * \struct mlk_tileset_loader
- * \brief Abstract loader structure
+ * \brief Abstract tileset loader structure
  *
  * All function pointers must be set.
  */
@@ -63,11 +63,13 @@
 	 * Open a texture from the given ident name.
 	 *
 	 * \param self this loader
+	 * \param tileset the underlying tileset being loaded
 	 * \param ident the texture name (or path)
 	 * \return a borrowed texture or NULL on failure
 	 */
-	struct mlk_texture * (*init_texture)(struct mlk_tileset_loader *self,
-	                                     const char *ident);
+	struct mlk_texture * (*new_texture)(struct mlk_tileset_loader *self,
+	                                    struct mlk_tileset *tileset,
+	                                    const char *ident);
 
 	/**
 	 * (read-write)
@@ -75,10 +77,11 @@
 	 * Return a sprite that the loader needs.
 	 *
 	 * \param self this loader
-	 * \return a unused sprite
+	 * \param tileset the underlying tileset being loaded
 	 * \return a borrowed sprite or NULL on failure
 	 */
-	struct mlk_sprite * (*init_sprite)(struct mlk_tileset_loader *self);
+	struct mlk_sprite * (*new_sprite)(struct mlk_tileset_loader *self,
+	                                  struct mlk_tileset *tileset);
 
 	/**
 	 * (read-write)
@@ -86,10 +89,11 @@
 	 * Return an animation that the loader needs.
 	 *
 	 * \param self this loader
-	 * \return a unused animation
+	 * \param tileset the underlying tileset being loaded
 	 * \return a borrowed animation or NULL on failure
 	 */
-	struct mlk_animation * (*init_animation)(struct mlk_tileset_loader *self);
+	struct mlk_animation * (*new_animation)(struct mlk_tileset_loader *self,
+	                                        struct mlk_tileset *tileset);
 
 	/**
 	 * (read-write)
@@ -97,18 +101,20 @@
 	 * Expand the collision array by one element.
 	 *
 	 * \param self this loader
+	 * \param tileset the underlying tileset being loaded
 	 * \param array the old array (can be NULL) to reallocate
 	 * \param arraysz the new array size (usually +1 than before)
 	 * \return a unused animation
 	 */
 	struct mlk_tileset_collision * (*expand_collisions)(struct mlk_tileset_loader *self,
+	                                                    struct mlk_tileset *tileset,
 	                                                    struct mlk_tileset_collision *array,
 	                                                    size_t arraysz);
 
 	/**
 	 * (read-write)
 	 *
-	 * Expand the animation array by one element.
+	 * Expand the tileset animation array by one element.
 	 *
 	 * \param self this loader
 	 * \param array the old array (can be NULL) to reallocate
@@ -116,6 +122,7 @@
 	 * \return a unused animation
 	 */
 	struct mlk_tileset_animation * (*expand_animations)(struct mlk_tileset_loader *self,
+	                                                    struct mlk_tileset *tileset,
 	                                                    struct mlk_tileset_animation *array,
 	                                                    size_t arraysz);
 
--- a/tests/test-tileset.c	Mon Mar 20 21:59:02 2023 +0100
+++ b/tests/test-tileset.c	Tue Mar 21 14:08:15 2023 +0100
@@ -31,7 +31,6 @@
  * Convenient struct that pack all the required data.
  */
 struct tileset {
-	struct mlk_tileset_loader_file file;
 	struct mlk_tileset_loader loader;
 	struct mlk_tileset tileset;
 };
@@ -39,7 +38,7 @@
 static inline int
 tileset_open(struct tileset *ts, const char *path)
 {
-	mlk_tileset_loader_file_init(&ts->file, &ts->loader, path);
+	mlk_tileset_loader_file_init(&ts->loader, path);
 
 	return mlk_tileset_loader_open(&ts->loader, &ts->tileset, path);
 }
@@ -47,7 +46,7 @@
 static inline void
 tileset_finish(struct tileset *ts)
 {
-	mlk_tileset_loader_file_finish(&ts->file);
+	mlk_tileset_loader_file_finish(&ts->loader);
 }
 
 static void