A game engine for top-down 2D RPG games.
rpg game-engine raylib c99

(temp commit) Start on migrating to bini for binary serialization

Changed files
+521 -6
include
keraforge
src
+1
include/keraforge.h
··· 7 7 #include <keraforge/log.h> 8 8 9 9 #include <keraforge/actor.h> 10 + #include <keraforge/bini.h> 10 11 #include <keraforge/editor.h> 11 12 #include <keraforge/error.h> 12 13 #include <keraforge/fs.h>
+472
include/keraforge/bini.h
··· 1 + #ifndef __kf_bini__ 2 + #define __kf_bini__ 3 + 4 + /* 5 + bini.h: endian-independent binary IO. 6 + License: BSD 3-Clause. See EOF for license text. 7 + 8 + Version: 1.0 9 + 10 + Changelog: 11 + 1.0 (Dec 28, 2025): Initial version. 12 + */ 13 + 14 + /* Used to add functions for version-specific types. */ 15 + #define bini_c 1989 16 + #ifdef __STDC_VERSION__ 17 + # undef bini_c 18 + # if __STDC_VERSION__ == 199901 19 + # define bini_c 1999 20 + # elif __STDC_VERSION__ == 201112 21 + # define bini_c 2011 22 + # elif __STDC_VERSION__ == 201710 23 + # define bini_c 2017 24 + # elif __STDC_VERSION__ == 202000 || __STDC_VERSION__ == 202311 25 + # define bini_c 2023 26 + # else 27 + # warning "bini.h: unknown C version. Falling back to C89." 28 + # define bini_c 1989 29 + # endif 30 + #endif 31 + 32 + 33 + #include <stdint.h> 34 + #include <stddef.h> 35 + #include <stdarg.h> 36 + #include <stdio.h> 37 + #if bini_c >= 1999 38 + # include <stdbool.h> 39 + #endif 40 + 41 + 42 + #ifndef BINI_BUFSIZ 43 + /* Starting size for bini_stream buffers. */ 44 + # define BINI_BUFSIZ 8192 45 + #endif 46 + 47 + #ifndef BINI_STRBUFSIZ 48 + /* Starting size for bini_*print buffers. */ 49 + # define BINI_STRBUFSIZ 1024 50 + #endif 51 + 52 + 53 + /* Execute the `x` macro for each type that bini uses. 54 + Excludes string types (char *). */ 55 + #ifndef BINI_X 56 + # if bini_c >= 1999 57 + # define BINI_X(x) \ 58 + x(char, c) \ 59 + x(short, s) \ 60 + x(int, i) \ 61 + x(long, l) \ 62 + x(long long, ll) \ 63 + x(unsigned char, cu) \ 64 + x(unsigned short, su) \ 65 + x(unsigned int, iu) \ 66 + x(unsigned long, lu) \ 67 + x(unsigned long long, llu) \ 68 + x(float, f) \ 69 + x(double, d) \ 70 + x(bool, b) 71 + # else 72 + # define BINI_X(x) \ 73 + x(char, c) \ 74 + x(short, s) \ 75 + x(int, i) \ 76 + x(long, l) \ 77 + x(unsigned char, cu) \ 78 + x(unsigned short, su) \ 79 + x(unsigned int, iu) \ 80 + x(unsigned long, lu) \ 81 + x(float, f) \ 82 + x(double, d) 83 + # endif 84 + #endif 85 + 86 + 87 + enum { BINI_ERR, BINI_OK }; 88 + enum { BINI_BIGENDIAN, BINI_LITTLEENDIAN }; 89 + enum 90 + { 91 + /* Read values from the end of the buffer. */ 92 + BINI_STACK, 93 + /* Read values from the start of the buffer. */ 94 + BINI_STREAM 95 + }; 96 + 97 + 98 + /* Represents a binary IO stream. */ 99 + struct bini_stream 100 + { 101 + /* Read mode for the stream. Should be either BINI_STACK or BINI_STREAM. */ 102 + int mode; 103 + size_t cap; 104 + size_t len; 105 + uint8_t *buffer; 106 + }; 107 + 108 + 109 + int bini_getendian(void); 110 + 111 + 112 + /* Initialize a bini_stream, allocating a buffer and 113 + setting each field to its respective defaults. Also 114 + see bini_new(). Free data using bini_end(). */ 115 + int bini_init(struct bini_stream *); 116 + /* Create a new bini_stream, allocating a starting buffer 117 + of BINI_BUFSIZ bytes. To use your own buffer, initialize 118 + your own bini_stream and call bini_init() to fill in the 119 + necessary fields. Free data using bini_close(). */ 120 + struct bini_stream *bini_new(void); 121 + /* End a bini_stream, flushing it if need be and freeing 122 + its buffer. */ 123 + void bini_end(struct bini_stream *); 124 + /* End a bini_stream, flushing it if need be, freeing its 125 + buffer, and frees the provided variable as well. */ 126 + void bini_close(struct bini_stream *bs); 127 + 128 + /* Flush a binary stream, clearing its buffer. */ 129 + int bini_flush(struct bini_stream *); 130 + /* Dump the binary stream's buffer to fp. */ 131 + void bini_dump(FILE *fp, struct bini_stream *bs); 132 + 133 + /* Read data from a FILE into a binary stream. */ 134 + size_t bini_fread(struct bini_stream *bs, size_t size, size_t n, FILE *fp); 135 + /* Write data to a FILE into a binary stream. This will 136 + NOT consume data from the stream! Stack/stream mode does 137 + not affect this function. 138 + To consume data, use `bs->len -= bini_fwrite(bs, ...);` */ 139 + size_t bini_fwrite(struct bini_stream *bs, size_t size, size_t n, FILE *fp); 140 + 141 + 142 + #define _bini_rwx(_type, _name) \ 143 + /* Write a `typeof(v)` to the stream. */ \ 144 + void bini_w##_name(struct bini_stream *bs, _type v); \ 145 + /* Read a `typeof(v)` from the stream. */ \ 146 + _type bini_r##_name(struct bini_stream *bs); 147 + BINI_X(_bini_rwx) 148 + #undef _bini_rwx 149 + 150 + void bini_wstrn(struct bini_stream *, const char *, size_t); 151 + void bini_wstr(struct bini_stream *, const char *); 152 + 153 + size_t bini_rstr(struct bini_stream *, char *); 154 + 155 + #if 0 /* unimplemented */ 156 + /* Write to a binary stream. */ 157 + int bini_vprint(struct bini_stream *bs, char *fmt, va_list va); 158 + /* Write to a binary stream. */ 159 + int bini_print(struct bini_stream *bs, char *fmt, ...); 160 + 161 + /* Read from a binary stream. */ 162 + int bini_vscan(struct bini_stream *bs, char *fmt, va_list va); 163 + /* Read from a binary stream. */ 164 + int bini_scan(struct bini_stream *bs, char *fmt, ...); 165 + #endif 166 + 167 + 168 + /* Aliased names that are more akin to libc names. */ 169 + #if defined(bini_libc_names) 170 + 171 + typedef struct bini_stream bstream; 172 + 173 + # define bnew bini_new 174 + # define binit bini_init 175 + # define bend bini_end 176 + # define bclose bini_close 177 + 178 + # define bflush bini_flush 179 + # define bdump bini_dump 180 + 181 + # define bputc bini_wc 182 + # define bputs bini_ws 183 + # define bputi bini_wi 184 + # define bputl bini_wl 185 + # define bputll bini_wll 186 + # define bputcu bini_wcu 187 + # define bputsu bini_wsu 188 + # define bputiu bini_wiu 189 + # define bputlu bini_wlu 190 + # define bputllu bini_wllu 191 + # define bputf bini_wf 192 + # define bputd bini_wd 193 + # define bputb bini_wb 194 + # define bputstr bini_wstr 195 + # define bputstrn bini_wstrn 196 + 197 + # define breadc bini_rc 198 + # define breads bini_rs 199 + # define breadi bini_ri 200 + # define breadl bini_rl 201 + # define breadll bini_rll 202 + # define breadcu bini_rcu 203 + # define breadsu bini_rsu 204 + # define breadiu bini_riu 205 + # define breadlu bini_rlu 206 + # define breadllu bini_rllu 207 + # define breadf bini_rf 208 + # define breadd bini_rd 209 + # define breadb bini_rb 210 + # define breadstr bini_rstr 211 + # define breadstrn bini_rstrn 212 + 213 + #if 0 214 + # define bfprint bini_vprint 215 + # define bprint bini_print 216 + # define bvscan bini_vscan 217 + # define bscan bini_scan 218 + #endif 219 + 220 + #endif 221 + 222 + 223 + #if defined(bini_impl) 224 + 225 + 226 + #include <stdlib.h> 227 + #include <string.h> 228 + 229 + 230 + static 231 + void _bini_dbg(char *fmt, ...) 232 + { 233 + # if bini_dbg 234 + va_list ap; 235 + va_start(ap, fmt); 236 + vfprintf(stderr, fmt, ap); 237 + va_end(ap); 238 + # else 239 + (void)fmt; 240 + # endif 241 + } 242 + 243 + 244 + int bini_getendian(void) 245 + { 246 + const int n = 1; 247 + if (*(char *)&n == 1) 248 + return BINI_LITTLEENDIAN; 249 + return BINI_BIGENDIAN; 250 + } 251 + 252 + 253 + int bini_init(struct bini_stream *bs) 254 + { 255 + bs->buffer = calloc(BINI_BUFSIZ, 1); 256 + if (!bs->buffer) 257 + return BINI_ERR; 258 + bs->len = 0; 259 + bs->cap = BINI_BUFSIZ; 260 + bs->mode = BINI_STREAM; 261 + return BINI_OK; 262 + } 263 + 264 + struct bini_stream *bini_new(void) 265 + { 266 + struct bini_stream *bs = malloc(sizeof(struct bini_stream)); 267 + bini_init(bs); 268 + return bs; 269 + } 270 + 271 + void bini_end(struct bini_stream *bs) 272 + { 273 + bini_flush(bs); 274 + free(bs->buffer); 275 + } 276 + 277 + void bini_close(struct bini_stream *bs) 278 + { 279 + bini_end(bs); 280 + free(bs); 281 + } 282 + 283 + int bini_flush(struct bini_stream *bs) 284 + { 285 + size_t i; 286 + for (i = 0 ; i < bs->len ; i++) 287 + bs->buffer[i] = 0; 288 + bs->len = 0; 289 + return BINI_OK; 290 + } 291 + 292 + void bini_dump(FILE *fp, struct bini_stream *bs) 293 + { 294 + size_t i; 295 + fprintf(fp, "bini buffer dump:\n"); 296 + fprintf(fp, "buffer: %lu/%lu bytes\n", bs->len, bs->cap); 297 + fprintf(fp, "---\n"); 298 + for (i = 0 ; i < bs->len ; i++) 299 + fprintf(fp, ". %02x '%c'\n", bs->buffer[i], bs->buffer[i]); 300 + fprintf(fp, "---\n"); 301 + } 302 + 303 + static 304 + #if bini_c >= 1999 305 + inline 306 + #endif 307 + int _bini_grow(struct bini_stream *bs, size_t n) 308 + { 309 + uint8_t *newbuf; 310 + if (bs->len + n < bs->cap) 311 + return BINI_OK; 312 + while (bs->len + n >= bs->cap) 313 + { 314 + newbuf = realloc(bs->buffer, bs->cap * 2); 315 + if (!newbuf) 316 + return BINI_ERR; 317 + bs->buffer = newbuf; 318 + bs->cap *= 2; 319 + } 320 + return BINI_OK; 321 + } 322 + 323 + size_t bini_fread(struct bini_stream *bs, size_t size, size_t n, FILE *fp) 324 + { 325 + size_t nread; 326 + _bini_grow(bs, size*n); 327 + nread = fread(bs->buffer, size, n, fp); 328 + bs->len += nread; 329 + return nread; 330 + } 331 + 332 + size_t bini_fwrite(struct bini_stream *bs, size_t size, size_t n, FILE *fp) 333 + { 334 + return fwrite(bs->buffer, size, n, fp); 335 + } 336 + 337 + static 338 + #if bini_c >= 1999 339 + inline 340 + #endif 341 + void _bini_shiftleft(struct bini_stream *bs, size_t n) 342 + { 343 + size_t i; 344 + _bini_dbg("shift %d: (len=%lu)\n", n, bs->len); 345 + for (i = 0 ; i < bs->len ; i++) 346 + { 347 + if (i+n >= bs->len) 348 + { 349 + _bini_dbg(" %d: [%d] (out of bounds) to [%d]\n", n, i+n, i); 350 + bs->buffer[i] = 0; 351 + continue; 352 + } 353 + _bini_dbg(" %d: [%d] (%02x '%c') to [%d]\n", 354 + n, 355 + i+n, 356 + bs->buffer[i+n], 357 + bs->buffer[i+n], 358 + i); 359 + bs->buffer[i] = bs->buffer[i+n]; 360 + } 361 + } 362 + 363 + #define _bini_rwx(_type, _name) \ 364 + void bini_w##_name(struct bini_stream *bs, _type v) \ 365 + { \ 366 + size_t i; \ 367 + uint8_t *bytes; \ 368 + int l = bini_getendian() == BINI_LITTLEENDIAN; \ 369 + _bini_dbg("%s: %d (%lu bytes)\n", __FUNCTION__, *(int *)&v, sizeof(v)); \ 370 + _bini_grow(bs, sizeof(v)); \ 371 + bytes = (uint8_t *)&v; \ 372 + for (i = 0 ; i < sizeof(v) ; i++) \ 373 + { \ 374 + _bini_dbg("%s: byte index [%lu] (= %02x '%c') to buffer index %lu\n", \ 375 + __FUNCTION__, \ 376 + l ? sizeof(v)-i-1 : i, \ 377 + bytes[l ? sizeof(v)-i-1 : i], \ 378 + bytes[l ? sizeof(v)-i-1 : i], \ 379 + bs->len+i); \ 380 + bs->buffer[bs->len+i] = bytes[l ? sizeof(v)-i-1 : i]; \ 381 + } \ 382 + bs->len += i; \ 383 + _bini_dbg("-> %s: %d (%lu bytes)\n", __FUNCTION__, *(int *)bytes, sizeof(v)); \ 384 + } \ 385 + _type bini_r##_name(struct bini_stream *bs) \ 386 + { \ 387 + size_t i; \ 388 + _type v; \ 389 + uint8_t *bytes = (uint8_t *)&v; \ 390 + int l = bini_getendian() == BINI_LITTLEENDIAN; \ 391 + int m = bs->mode == BINI_STREAM; \ 392 + for (i = 0 ; i < sizeof(v) ; i++) \ 393 + { \ 394 + _bini_dbg("%s: byte index [%lu] from buffer index %lu (= %02x '%c')\n", \ 395 + __FUNCTION__, \ 396 + l ? sizeof(v)-i-1 : i, \ 397 + m ? i : (bs->len-sizeof(v)+i), \ 398 + bs->buffer[m ? i : (bs->len-sizeof(v)+i)], \ 399 + bs->buffer[m ? i : (bs->len-sizeof(v)+i)]); \ 400 + bytes[l ? sizeof(v)-i-1 : i] = bs->buffer[m ? i : (bs->len-sizeof(v)+i)]; \ 401 + } \ 402 + if (m) \ 403 + _bini_shiftleft(bs, i); \ 404 + bs->len -= i; \ 405 + return v; \ 406 + } 407 + BINI_X(_bini_rwx) 408 + #undef _bini_rwx 409 + 410 + 411 + void bini_wstrn(struct bini_stream *bs, const char *str, size_t n) 412 + { 413 + bini_wlu(bs, n); 414 + _bini_grow(bs, n); 415 + while (str) 416 + { 417 + bini_wc(bs, *str); 418 + str++; 419 + } 420 + } 421 + 422 + void bini_wstr(struct bini_stream *bs, const char *str) 423 + { 424 + size_t i, n = strlen(str); 425 + char *s = (char *)str; 426 + bini_wlu(bs, n); 427 + _bini_grow(bs, n); 428 + for (i = 0 ; i < n ; i++) 429 + bini_wc(bs, s[i]); 430 + } 431 + 432 + size_t bini_rstr(struct bini_stream *bs, char *str) 433 + { 434 + size_t i, n = bini_rlu(bs); 435 + for (i = 0 ; i < n ; i++) 436 + str[i] = bini_rc(bs); 437 + return n; 438 + } 439 + 440 + /* 441 + Bini license: BSD 3-Clause. 442 + 443 + Copyright 2025 Emmeline Coats 444 + 445 + Redistribution and use in source and binary forms, with or without 446 + modification, are permitted provided that the following conditions are met: 447 + 448 + 1. Redistributions of source code must retain the above copyright notice, this 449 + list of conditions and the following disclaimer. 450 + 451 + 2. Redistributions in binary form must reproduce the above copyright notice, 452 + this list of conditions and the following disclaimer in the documentation 453 + and/or other materials provided with the distribution. 454 + 455 + 3. Neither the name of the copyright holder nor the names of its contributors 456 + may be used to endorse or promote products derived from this software 457 + without specific prior written permission. 458 + 459 + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND 460 + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 461 + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 462 + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 463 + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 464 + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 465 + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 466 + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 467 + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 468 + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 469 + */ 470 + 471 + #endif /* bini_impl */ 472 + #endif /* __bini__ */
+2
src/bini.c
··· 1 + #define bini_impl 2 + #include <keraforge/bini.h>
+46 -6
src/world.c
··· 1 + #include "keraforge/bini.h" 1 2 #include "keraforge/fs.h" 2 3 #include <keraforge.h> 3 4 #include <raylib.h> ··· 224 225 #define _KF_MAPFILE_XZ "data/map.bin.xz" 225 226 #define _KF_MAPFILE "data/map.bin" 226 227 227 - int kf_world_save(struct kf_world *world, bool compress) 228 + static 229 + void _kf_world_save_bs(struct kf_world *world, struct bini_stream *bs) 228 230 { 229 - char *outfile = compress ? _KF_MAPFILE_TMP : _KF_MAPFILE; 230 - if (!kf_writebin(outfile, (u8 *)world, kf_world_getsize(world))) 231 + bini_wiu(bs, world->revision); 232 + bini_wiu(bs, world->width); 233 + bini_wiu(bs, world->height); 234 + struct kf_tile *t = &world->map[0]; 235 + for (size_t i = 0 ; i < world->width*world->height ; i++) 231 236 { 232 - KF_THROW("failed to write to %s", outfile); 233 - return 0; /* unreachable */ 237 + bini_wsu(bs, t->subid); 238 + bini_wsu(bs, t->id); 239 + bini_wcu(bs, t->data); 240 + t++; 234 241 } 242 + } 243 + 244 + int kf_world_save(struct kf_world *world, bool compress) 245 + { 246 + char *outfile = compress ? _KF_MAPFILE_TMP : _KF_MAPFILE; 247 + struct bini_stream *bs = bini_new(); 248 + _kf_world_save_bs(world, bs); 249 + if (kf_writebin(outfile, bs->buffer, bs->len)) 250 + KF_THROW("failed to open %s", outfile); 251 + bini_close(bs); 235 252 236 253 if (compress) 237 254 { ··· 247 264 return 1; 248 265 } 249 266 267 + static 268 + void _kf_world_load_bs(struct kf_world *world, struct bini_stream *bs) 269 + { 270 + world->revision = bini_riu(bs); 271 + world->width = bini_riu(bs); 272 + world->height = bini_riu(bs); 273 + struct kf_tile *t = &world->map[0]; 274 + for (size_t i = 0 ; i < world->width*world->height ; i++) 275 + { 276 + t->subid = bini_rsu(bs); 277 + t->id = bini_rsu(bs); 278 + t->data = bini_rcu(bs); 279 + t++; 280 + } 281 + } 282 + 250 283 int kf_world_load(struct kf_world **pworld, bool compressed) 251 284 { 252 285 if (compressed) /* decompress before loading */ ··· 269 302 kf_logdbg("loading world: %s", infile); 270 303 271 304 size_t len = 0; 272 - *pworld = (struct kf_world *)kf_readbin(infile, &len); 305 + struct bini_stream *bs = bini_new(); 306 + free(bs->buffer); 307 + bs->buffer = kf_readbin(infile, &len); 308 + if (!bs->buffer) 309 + KF_THROW("failed to read/open %s", infile); 310 + bs->len = bs->cap = len; 311 + _kf_world_load_bs(*pworld, bs); 312 + bini_close(bs); 273 313 kf_logdbg("loaded world (%p): r=%d, wh=%dx%d, len=%lu", *pworld, (*pworld)->revision, (*pworld)->width, (*pworld)->height, len); 274 314 275 315 if (compressed)