Mercurial
comparison infinite_canvas/scene_store.c @ 281:c57149ad216e default tip
Copilot-Session: f68442b1-fa8f-46a0-9689-81710613bbd4
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Tue, 18 Aug 2026 22:18:15 -0700 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| 280:49e9e591c9bb | 281:c57149ad216e |
|---|---|
| 1 #include "infinite_canvas/scene_store.h" | |
| 2 | |
| 3 #include <ctype.h> | |
| 4 #include <math.h> | |
| 5 #include <stdio.h> | |
| 6 #include <stdlib.h> | |
| 7 #include <string.h> | |
| 8 | |
| 9 #if defined(_WIN32) | |
| 10 #include <windows.h> | |
| 11 #elif !defined(PLATFORM_WEB) | |
| 12 #include <unistd.h> | |
| 13 #endif | |
| 14 | |
| 15 #define CANVAS_SCENE_STORE_VERSION 1 | |
| 16 #define CANVAS_SCENE_STORE_HEADER_SIZE 48 | |
| 17 #define CANVAS_SCENE_STORE_ENTITY_SIZE 2240 | |
| 18 #define CANVAS_SCENE_STORE_MAX_BYTES \ | |
| 19 (CANVAS_SCENE_STORE_HEADER_SIZE + \ | |
| 20 CANVAS_MAX_ENTITIES * CANVAS_SCENE_STORE_ENTITY_SIZE) | |
| 21 | |
| 22 static const uint8 CANVAS_SCENE_STORE_MAGIC[8] = { | |
| 23 'Z', 'E', 'N', 'B', 'U', 'M', 'A', 'P', | |
| 24 }; | |
| 25 | |
| 26 _Static_assert(sizeof(Color) == 4, "scene format requires RGBA8 Color"); | |
| 27 _Static_assert( | |
| 28 CANVAS_ENTITY_TEXT_CAPACITY == 2048, | |
| 29 "scene format version must change when text capacity changes"); | |
| 30 _Static_assert( | |
| 31 CANVAS_SCENE_STORE_ENTITY_SIZE == | |
| 32 2 * 4 + 4 * 4 + 4 + 128 + 2048 + 3 * 4 + 6 * 4, | |
| 33 "scene entity record size is inconsistent"); | |
| 34 | |
| 35 typedef struct { | |
| 36 uint8 *p_data; | |
| 37 size_t size; | |
| 38 size_t offset; | |
| 39 boolean valid; | |
| 40 } Canvas_Scene_Writer; | |
| 41 | |
| 42 typedef struct { | |
| 43 const uint8 *p_data; | |
| 44 size_t size; | |
| 45 size_t offset; | |
| 46 boolean valid; | |
| 47 } Canvas_Scene_Reader; | |
| 48 | |
| 49 static uint64 Canvas_Scene_Store_FNV( | |
| 50 uint64 hash, | |
| 51 const uint8 *p_data, | |
| 52 size_t size) | |
| 53 { | |
| 54 for (size_t index = 0; index < size; index++) { | |
| 55 hash ^= p_data[index]; | |
| 56 hash *= 1099511628211ULL; | |
| 57 } | |
| 58 return hash; | |
| 59 } | |
| 60 | |
| 61 static void Canvas_Scene_Store_Write( | |
| 62 Canvas_Scene_Writer *p_writer, | |
| 63 const void *p_data, | |
| 64 size_t size) | |
| 65 { | |
| 66 if (!p_writer->valid || | |
| 67 p_writer->offset > p_writer->size || | |
| 68 size > p_writer->size - p_writer->offset) { | |
| 69 p_writer->valid = FALSE; | |
| 70 return; | |
| 71 } | |
| 72 memcpy(p_writer->p_data + p_writer->offset, p_data, size); | |
| 73 p_writer->offset += size; | |
| 74 } | |
| 75 | |
| 76 static void Canvas_Scene_Store_Write_U32( | |
| 77 Canvas_Scene_Writer *p_writer, | |
| 78 uint32 value) | |
| 79 { | |
| 80 uint8 bytes[4] = { | |
| 81 (uint8)(value & 0xff), | |
| 82 (uint8)((value >> 8) & 0xff), | |
| 83 (uint8)((value >> 16) & 0xff), | |
| 84 (uint8)((value >> 24) & 0xff), | |
| 85 }; | |
| 86 Canvas_Scene_Store_Write(p_writer, bytes, sizeof(bytes)); | |
| 87 } | |
| 88 | |
| 89 static void Canvas_Scene_Store_Write_F32( | |
| 90 Canvas_Scene_Writer *p_writer, | |
| 91 float value) | |
| 92 { | |
| 93 uint32 bits = 0; | |
| 94 memcpy(&bits, &value, sizeof(bits)); | |
| 95 Canvas_Scene_Store_Write_U32(p_writer, bits); | |
| 96 } | |
| 97 | |
| 98 static void Canvas_Scene_Store_Read( | |
| 99 Canvas_Scene_Reader *p_reader, | |
| 100 void *p_output, | |
| 101 size_t size) | |
| 102 { | |
| 103 if (!p_reader->valid || | |
| 104 p_reader->offset > p_reader->size || | |
| 105 size > p_reader->size - p_reader->offset) { | |
| 106 p_reader->valid = FALSE; | |
| 107 return; | |
| 108 } | |
| 109 memcpy(p_output, p_reader->p_data + p_reader->offset, size); | |
| 110 p_reader->offset += size; | |
| 111 } | |
| 112 | |
| 113 static uint32 Canvas_Scene_Store_Read_U32( | |
| 114 Canvas_Scene_Reader *p_reader) | |
| 115 { | |
| 116 uint8 bytes[4] = {0}; | |
| 117 Canvas_Scene_Store_Read(p_reader, bytes, sizeof(bytes)); | |
| 118 return (uint32)bytes[0] | | |
| 119 ((uint32)bytes[1] << 8) | | |
| 120 ((uint32)bytes[2] << 16) | | |
| 121 ((uint32)bytes[3] << 24); | |
| 122 } | |
| 123 | |
| 124 static float Canvas_Scene_Store_Read_F32( | |
| 125 Canvas_Scene_Reader *p_reader) | |
| 126 { | |
| 127 uint32 bits = Canvas_Scene_Store_Read_U32(p_reader); | |
| 128 float value = 0.0f; | |
| 129 memcpy(&value, &bits, sizeof(value)); | |
| 130 return value; | |
| 131 } | |
| 132 | |
| 133 static uint32 Canvas_Scene_Store_Entity_Flags( | |
| 134 const Canvas_Entity *p_entity) | |
| 135 { | |
| 136 uint32 flags = 0; | |
| 137 if (p_entity->active) flags |= 1u << 0; | |
| 138 if (p_entity->pinned) flags |= 1u << 1; | |
| 139 if (p_entity->text_muted) flags |= 1u << 2; | |
| 140 if (p_entity->parked) flags |= 1u << 3; | |
| 141 return flags; | |
| 142 } | |
| 143 | |
| 144 static void Canvas_Scene_Store_Write_Entity( | |
| 145 Canvas_Scene_Writer *p_writer, | |
| 146 const Canvas_Entity *p_entity) | |
| 147 { | |
| 148 Canvas_Scene_Store_Write_U32(p_writer, p_entity->id); | |
| 149 Canvas_Scene_Store_Write_U32(p_writer, (uint32)p_entity->type); | |
| 150 Canvas_Scene_Store_Write_F32(p_writer, p_entity->position.x); | |
| 151 Canvas_Scene_Store_Write_F32(p_writer, p_entity->position.y); | |
| 152 Canvas_Scene_Store_Write_F32(p_writer, p_entity->size.x); | |
| 153 Canvas_Scene_Store_Write_F32(p_writer, p_entity->size.y); | |
| 154 Canvas_Scene_Store_Write(p_writer, &p_entity->color, 4); | |
| 155 Canvas_Scene_Store_Write( | |
| 156 p_writer, p_entity->label, sizeof(p_entity->label)); | |
| 157 Canvas_Scene_Store_Write( | |
| 158 p_writer, p_entity->text, sizeof(p_entity->text)); | |
| 159 Canvas_Scene_Store_Write_U32(p_writer, (uint32)p_entity->value); | |
| 160 Canvas_Scene_Store_Write_F32(p_writer, p_entity->text_scroll_y); | |
| 161 Canvas_Scene_Store_Write_U32( | |
| 162 p_writer, Canvas_Scene_Store_Entity_Flags(p_entity)); | |
| 163 Canvas_Scene_Store_Write_F32( | |
| 164 p_writer, p_entity->pinned_screen_position.x); | |
| 165 Canvas_Scene_Store_Write_F32( | |
| 166 p_writer, p_entity->pinned_screen_position.y); | |
| 167 Canvas_Scene_Store_Write_F32( | |
| 168 p_writer, p_entity->pinned_screen_size.x); | |
| 169 Canvas_Scene_Store_Write_F32( | |
| 170 p_writer, p_entity->pinned_screen_size.y); | |
| 171 Canvas_Scene_Store_Write_F32( | |
| 172 p_writer, p_entity->parked_restore_position.x); | |
| 173 Canvas_Scene_Store_Write_F32( | |
| 174 p_writer, p_entity->parked_restore_position.y); | |
| 175 } | |
| 176 | |
| 177 static Canvas_Entity Canvas_Scene_Store_Read_Entity( | |
| 178 Canvas_Scene_Reader *p_reader) | |
| 179 { | |
| 180 Canvas_Entity entity = {0}; | |
| 181 entity.id = Canvas_Scene_Store_Read_U32(p_reader); | |
| 182 entity.type = | |
| 183 (Canvas_Entity_Type)Canvas_Scene_Store_Read_U32(p_reader); | |
| 184 entity.position.x = Canvas_Scene_Store_Read_F32(p_reader); | |
| 185 entity.position.y = Canvas_Scene_Store_Read_F32(p_reader); | |
| 186 entity.size.x = Canvas_Scene_Store_Read_F32(p_reader); | |
| 187 entity.size.y = Canvas_Scene_Store_Read_F32(p_reader); | |
| 188 Canvas_Scene_Store_Read(p_reader, &entity.color, 4); | |
| 189 Canvas_Scene_Store_Read( | |
| 190 p_reader, entity.label, sizeof(entity.label)); | |
| 191 Canvas_Scene_Store_Read( | |
| 192 p_reader, entity.text, sizeof(entity.text)); | |
| 193 entity.label[sizeof(entity.label) - 1] = '\0'; | |
| 194 entity.text[sizeof(entity.text) - 1] = '\0'; | |
| 195 entity.value = (int32)Canvas_Scene_Store_Read_U32(p_reader); | |
| 196 entity.text_scroll_y = Canvas_Scene_Store_Read_F32(p_reader); | |
| 197 uint32 flags = Canvas_Scene_Store_Read_U32(p_reader); | |
| 198 entity.active = flags & (1u << 0) ? TRUE : FALSE; | |
| 199 entity.pinned = flags & (1u << 1) ? TRUE : FALSE; | |
| 200 entity.text_muted = flags & (1u << 2) ? TRUE : FALSE; | |
| 201 entity.parked = flags & (1u << 3) ? TRUE : FALSE; | |
| 202 entity.pinned_screen_position.x = | |
| 203 Canvas_Scene_Store_Read_F32(p_reader); | |
| 204 entity.pinned_screen_position.y = | |
| 205 Canvas_Scene_Store_Read_F32(p_reader); | |
| 206 entity.pinned_screen_size.x = | |
| 207 Canvas_Scene_Store_Read_F32(p_reader); | |
| 208 entity.pinned_screen_size.y = | |
| 209 Canvas_Scene_Store_Read_F32(p_reader); | |
| 210 entity.parked_restore_position.x = | |
| 211 Canvas_Scene_Store_Read_F32(p_reader); | |
| 212 entity.parked_restore_position.y = | |
| 213 Canvas_Scene_Store_Read_F32(p_reader); | |
| 214 entity.text_cursor = (int32)strlen(entity.text); | |
| 215 entity.text_selection_anchor = entity.text_cursor; | |
| 216 entity.visibility_amount = 1.0f; | |
| 217 entity.screen_space_initialized = entity.pinned; | |
| 218 entity.animation_amount = | |
| 219 entity.type == CANVAS_ENTITY_CONVERSATION | |
| 220 ? (entity.active ? 0.0f : 1.0f) | |
| 221 : (entity.active ? 1.0f : 0.0f); | |
| 222 return entity; | |
| 223 } | |
| 224 | |
| 225 static boolean Canvas_Scene_Store_Entity_Valid( | |
| 226 const Canvas_Entity *p_entity) | |
| 227 { | |
| 228 boolean base_valid = p_entity->id != 0 && | |
| 229 p_entity->type >= 0 && | |
| 230 p_entity->type < CANVAS_ENTITY_TYPE_COUNT && | |
| 231 isfinite(p_entity->position.x) && | |
| 232 isfinite(p_entity->position.y) && | |
| 233 isfinite(p_entity->size.x) && | |
| 234 isfinite(p_entity->size.y) && | |
| 235 p_entity->size.x > 0.0f && | |
| 236 p_entity->size.y > 0.0f && | |
| 237 fabsf(p_entity->position.x) <= 1000000000.0f && | |
| 238 fabsf(p_entity->position.y) <= 1000000000.0f && | |
| 239 p_entity->size.x <= 10000000.0f && | |
| 240 p_entity->size.y <= 10000000.0f; | |
| 241 if (!base_valid) return FALSE; | |
| 242 if (p_entity->pinned && | |
| 243 (!isfinite(p_entity->pinned_screen_position.x) || | |
| 244 !isfinite(p_entity->pinned_screen_position.y) || | |
| 245 !isfinite(p_entity->pinned_screen_size.x) || | |
| 246 !isfinite(p_entity->pinned_screen_size.y) || | |
| 247 p_entity->pinned_screen_size.x <= 0.0f || | |
| 248 p_entity->pinned_screen_size.y <= 0.0f)) { | |
| 249 return FALSE; | |
| 250 } | |
| 251 return !p_entity->parked || | |
| 252 (isfinite(p_entity->parked_restore_position.x) && | |
| 253 isfinite(p_entity->parked_restore_position.y)); | |
| 254 } | |
| 255 | |
| 256 boolean Canvas_Scene_Store_Path( | |
| 257 const char *p_name, | |
| 258 char *p_path, | |
| 259 size_t path_capacity) | |
| 260 { | |
| 261 #if defined(PLATFORM_WEB) | |
| 262 (void)p_name; | |
| 263 (void)p_path; | |
| 264 (void)path_capacity; | |
| 265 return FALSE; | |
| 266 #else | |
| 267 if (!p_name || !p_name[0] || !p_path || path_capacity == 0) { | |
| 268 return FALSE; | |
| 269 } | |
| 270 size_t name_length = strlen(p_name); | |
| 271 if (name_length >= CANVAS_SCENE_STORE_NAME_CAPACITY) return FALSE; | |
| 272 for (size_t index = 0; index < name_length; index++) { | |
| 273 uint8 character = (uint8)p_name[index]; | |
| 274 if (!isalnum(character) && | |
| 275 character != '-' && | |
| 276 character != '_') { | |
| 277 return FALSE; | |
| 278 } | |
| 279 } | |
| 280 const char *p_root = getenv("XDG_STATE_HOME"); | |
| 281 const char *p_suffix = "/zenbu/infinite-canvas/snapshots"; | |
| 282 char fallback[CANVAS_SCENE_STORE_PATH_CAPACITY]; | |
| 283 if (!p_root || !p_root[0]) { | |
| 284 const char *p_home = getenv("HOME"); | |
| 285 if (!p_home || !p_home[0]) return FALSE; | |
| 286 int fallback_written = snprintf( | |
| 287 fallback, | |
| 288 sizeof(fallback), | |
| 289 "%s/.local/state", | |
| 290 p_home); | |
| 291 if (fallback_written <= 0 || | |
| 292 (size_t)fallback_written >= sizeof(fallback)) { | |
| 293 return FALSE; | |
| 294 } | |
| 295 p_root = fallback; | |
| 296 } | |
| 297 int written = snprintf( | |
| 298 p_path, | |
| 299 path_capacity, | |
| 300 "%s%s/%s.zmap", | |
| 301 p_root, | |
| 302 p_suffix, | |
| 303 p_name); | |
| 304 return written > 0 && (size_t)written < path_capacity; | |
| 305 #endif | |
| 306 } | |
| 307 | |
| 308 boolean Canvas_Scene_Store_Save( | |
| 309 const char *p_path, | |
| 310 const Canvas_Scene *p_scene, | |
| 311 const Canvas_Camera *p_camera) | |
| 312 { | |
| 313 #if defined(PLATFORM_WEB) | |
| 314 (void)p_path; | |
| 315 (void)p_scene; | |
| 316 (void)p_camera; | |
| 317 return FALSE; | |
| 318 #else | |
| 319 if (!p_path || !p_scene || !p_camera) return FALSE; | |
| 320 size_t entity_count = 0; | |
| 321 for (size_t index = 0; | |
| 322 index < Dowa_Array_Length(p_scene->p_entities); | |
| 323 index++) { | |
| 324 if (!p_scene->p_entities[index].removing) entity_count++; | |
| 325 } | |
| 326 if (entity_count > CANVAS_MAX_ENTITIES) return FALSE; | |
| 327 size_t size = CANVAS_SCENE_STORE_HEADER_SIZE + | |
| 328 entity_count * CANVAS_SCENE_STORE_ENTITY_SIZE; | |
| 329 uint8 *p_data = (uint8 *)calloc(size, 1); | |
| 330 if (!p_data) return FALSE; | |
| 331 Canvas_Scene_Writer writer = { | |
| 332 .p_data = p_data, | |
| 333 .size = size, | |
| 334 .valid = TRUE, | |
| 335 }; | |
| 336 Canvas_Scene_Store_Write( | |
| 337 &writer, CANVAS_SCENE_STORE_MAGIC, sizeof(CANVAS_SCENE_STORE_MAGIC)); | |
| 338 Canvas_Scene_Store_Write_U32(&writer, CANVAS_SCENE_STORE_VERSION); | |
| 339 Canvas_Scene_Store_Write_U32(&writer, (uint32)entity_count); | |
| 340 Canvas_Scene_Store_Write_U32(&writer, p_scene->next_entity); | |
| 341 Canvas_Scene_Store_Write_U32(&writer, p_scene->next_entity_id); | |
| 342 Canvas_Scene_Store_Write_U32( | |
| 343 &writer, p_scene->show_grid ? 1u : 0u); | |
| 344 Canvas_Scene_Store_Write_F32(&writer, p_camera->target.x); | |
| 345 Canvas_Scene_Store_Write_F32(&writer, p_camera->target.y); | |
| 346 Canvas_Scene_Store_Write_F32(&writer, p_camera->zoom); | |
| 347 Canvas_Scene_Store_Write_U32( | |
| 348 &writer, (uint32)(size - CANVAS_SCENE_STORE_HEADER_SIZE)); | |
| 349 Canvas_Scene_Store_Write_U32(&writer, 0); | |
| 350 for (size_t index = 0; | |
| 351 index < Dowa_Array_Length(p_scene->p_entities); | |
| 352 index++) { | |
| 353 if (!p_scene->p_entities[index].removing) { | |
| 354 Canvas_Scene_Store_Write_Entity( | |
| 355 &writer, &p_scene->p_entities[index]); | |
| 356 } | |
| 357 } | |
| 358 if (!writer.valid || writer.offset != size) { | |
| 359 Dowa_Free(p_data); | |
| 360 return FALSE; | |
| 361 } | |
| 362 uint64 checksum_hash = Canvas_Scene_Store_FNV( | |
| 363 1469598103934665603ULL, | |
| 364 p_data, | |
| 365 44); | |
| 366 checksum_hash = Canvas_Scene_Store_FNV( | |
| 367 checksum_hash, | |
| 368 p_data + CANVAS_SCENE_STORE_HEADER_SIZE, | |
| 369 size - CANVAS_SCENE_STORE_HEADER_SIZE); | |
| 370 uint32 checksum = (uint32)checksum_hash; | |
| 371 p_data[44] = (uint8)(checksum & 0xff); | |
| 372 p_data[45] = (uint8)((checksum >> 8) & 0xff); | |
| 373 p_data[46] = (uint8)((checksum >> 16) & 0xff); | |
| 374 p_data[47] = (uint8)((checksum >> 24) & 0xff); | |
| 375 | |
| 376 char directory[CANVAS_SCENE_STORE_PATH_CAPACITY]; | |
| 377 char temporary[CANVAS_SCENE_STORE_PATH_CAPACITY]; | |
| 378 snprintf(directory, sizeof(directory), "%s", p_path); | |
| 379 char *p_separator = strrchr(directory, '/'); | |
| 380 #if defined(_WIN32) | |
| 381 char *p_windows_separator = strrchr(directory, '\\'); | |
| 382 if (!p_separator || p_windows_separator > p_separator) { | |
| 383 p_separator = p_windows_separator; | |
| 384 } | |
| 385 #endif | |
| 386 if (!p_separator) { | |
| 387 Dowa_Free(p_data); | |
| 388 return FALSE; | |
| 389 } | |
| 390 *p_separator = '\0'; | |
| 391 if (MakeDirectory(directory) != 0 && !DirectoryExists(directory)) { | |
| 392 Dowa_Free(p_data); | |
| 393 return FALSE; | |
| 394 } | |
| 395 int temporary_written = snprintf( | |
| 396 temporary, sizeof(temporary), "%s.tmp", p_path); | |
| 397 if (temporary_written <= 0 || | |
| 398 (size_t)temporary_written >= sizeof(temporary)) { | |
| 399 Dowa_Free(p_data); | |
| 400 return FALSE; | |
| 401 } | |
| 402 FILE *p_file = fopen(temporary, "wb"); | |
| 403 if (!p_file) { | |
| 404 Dowa_Free(p_data); | |
| 405 return FALSE; | |
| 406 } | |
| 407 boolean success = | |
| 408 fwrite(p_data, 1, size, p_file) == size && | |
| 409 fflush(p_file) == 0; | |
| 410 #if !defined(_WIN32) | |
| 411 if (success) success = fsync(fileno(p_file)) == 0; | |
| 412 #endif | |
| 413 if (fclose(p_file) != 0) success = FALSE; | |
| 414 Dowa_Free(p_data); | |
| 415 if (!success) { | |
| 416 remove(temporary); | |
| 417 return FALSE; | |
| 418 } | |
| 419 #if defined(_WIN32) | |
| 420 success = MoveFileExA( | |
| 421 temporary, | |
| 422 p_path, | |
| 423 MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH) | |
| 424 ? TRUE | |
| 425 : FALSE; | |
| 426 #else | |
| 427 success = rename(temporary, p_path) == 0 ? TRUE : FALSE; | |
| 428 #endif | |
| 429 if (!success) remove(temporary); | |
| 430 return success; | |
| 431 #endif | |
| 432 } | |
| 433 | |
| 434 boolean Canvas_Scene_Store_Load( | |
| 435 const char *p_path, | |
| 436 Canvas_Scene *p_scene, | |
| 437 Canvas_Camera *p_camera) | |
| 438 { | |
| 439 #if defined(PLATFORM_WEB) | |
| 440 (void)p_path; | |
| 441 (void)p_scene; | |
| 442 (void)p_camera; | |
| 443 return FALSE; | |
| 444 #else | |
| 445 if (!p_path || !p_scene || !p_camera) return FALSE; | |
| 446 FILE *p_file = fopen(p_path, "rb"); | |
| 447 if (!p_file) return FALSE; | |
| 448 if (fseek(p_file, 0, SEEK_END) != 0) { | |
| 449 fclose(p_file); | |
| 450 return FALSE; | |
| 451 } | |
| 452 long file_size = ftell(p_file); | |
| 453 if (file_size < CANVAS_SCENE_STORE_HEADER_SIZE || | |
| 454 file_size > CANVAS_SCENE_STORE_MAX_BYTES || | |
| 455 fseek(p_file, 0, SEEK_SET) != 0) { | |
| 456 fclose(p_file); | |
| 457 return FALSE; | |
| 458 } | |
| 459 uint8 *p_data = (uint8 *)malloc((size_t)file_size); | |
| 460 if (!p_data) { | |
| 461 fclose(p_file); | |
| 462 return FALSE; | |
| 463 } | |
| 464 boolean success = | |
| 465 fread(p_data, 1, (size_t)file_size, p_file) == (size_t)file_size; | |
| 466 fclose(p_file); | |
| 467 if (!success) { | |
| 468 Dowa_Free(p_data); | |
| 469 return FALSE; | |
| 470 } | |
| 471 | |
| 472 Canvas_Scene_Reader reader = { | |
| 473 .p_data = p_data, | |
| 474 .size = (size_t)file_size, | |
| 475 .valid = TRUE, | |
| 476 }; | |
| 477 uint8 magic[8] = {0}; | |
| 478 Canvas_Scene_Store_Read(&reader, magic, sizeof(magic)); | |
| 479 uint32 version = Canvas_Scene_Store_Read_U32(&reader); | |
| 480 uint32 entity_count = Canvas_Scene_Store_Read_U32(&reader); | |
| 481 uint32 next_entity = Canvas_Scene_Store_Read_U32(&reader); | |
| 482 uint32 next_entity_id = Canvas_Scene_Store_Read_U32(&reader); | |
| 483 uint32 scene_flags = Canvas_Scene_Store_Read_U32(&reader); | |
| 484 float camera_x = Canvas_Scene_Store_Read_F32(&reader); | |
| 485 float camera_y = Canvas_Scene_Store_Read_F32(&reader); | |
| 486 float camera_zoom = Canvas_Scene_Store_Read_F32(&reader); | |
| 487 uint32 payload_size = Canvas_Scene_Store_Read_U32(&reader); | |
| 488 uint32 stored_checksum = Canvas_Scene_Store_Read_U32(&reader); | |
| 489 size_t expected_size = CANVAS_SCENE_STORE_HEADER_SIZE + | |
| 490 (size_t)entity_count * CANVAS_SCENE_STORE_ENTITY_SIZE; | |
| 491 uint64 checksum_hash = Canvas_Scene_Store_FNV( | |
| 492 1469598103934665603ULL, | |
| 493 p_data, | |
| 494 44); | |
| 495 checksum_hash = Canvas_Scene_Store_FNV( | |
| 496 checksum_hash, | |
| 497 p_data + CANVAS_SCENE_STORE_HEADER_SIZE, | |
| 498 (size_t)file_size - CANVAS_SCENE_STORE_HEADER_SIZE); | |
| 499 uint32 actual_checksum = (uint32)checksum_hash; | |
| 500 if (!reader.valid || | |
| 501 memcmp(magic, CANVAS_SCENE_STORE_MAGIC, sizeof(magic)) != 0 || | |
| 502 version != CANVAS_SCENE_STORE_VERSION || | |
| 503 entity_count > CANVAS_MAX_ENTITIES || | |
| 504 expected_size != (size_t)file_size || | |
| 505 payload_size != (uint32)( | |
| 506 (size_t)file_size - CANVAS_SCENE_STORE_HEADER_SIZE) || | |
| 507 stored_checksum != actual_checksum || | |
| 508 next_entity == 0xffffffffu || | |
| 509 next_entity_id == 0xffffffffu || | |
| 510 !isfinite(camera_x) || | |
| 511 !isfinite(camera_y) || | |
| 512 !isfinite(camera_zoom) || | |
| 513 fabsf(camera_x) > 1000000000.0f || | |
| 514 fabsf(camera_y) > 1000000000.0f || | |
| 515 camera_zoom < p_camera->min_zoom || | |
| 516 camera_zoom > p_camera->max_zoom) { | |
| 517 Dowa_Free(p_data); | |
| 518 return FALSE; | |
| 519 } | |
| 520 | |
| 521 Canvas_Entity *p_entities = (Canvas_Entity *)calloc( | |
| 522 entity_count ? entity_count : 1, | |
| 523 sizeof(*p_entities)); | |
| 524 if (!p_entities) { | |
| 525 Dowa_Free(p_data); | |
| 526 return FALSE; | |
| 527 } | |
| 528 uint32 max_id = 0; | |
| 529 for (uint32 index = 0; index < entity_count; index++) { | |
| 530 p_entities[index] = Canvas_Scene_Store_Read_Entity(&reader); | |
| 531 if (!reader.valid || | |
| 532 !Canvas_Scene_Store_Entity_Valid(&p_entities[index])) { | |
| 533 Dowa_Free(p_entities); | |
| 534 Dowa_Free(p_data); | |
| 535 return FALSE; | |
| 536 } | |
| 537 for (uint32 previous = 0; previous < index; previous++) { | |
| 538 if (p_entities[previous].id == p_entities[index].id) { | |
| 539 Dowa_Free(p_entities); | |
| 540 Dowa_Free(p_data); | |
| 541 return FALSE; | |
| 542 } | |
| 543 } | |
| 544 if (p_entities[index].id > max_id) { | |
| 545 max_id = p_entities[index].id; | |
| 546 } | |
| 547 } | |
| 548 Dowa_Free(p_data); | |
| 549 if (!reader.valid || reader.offset != (size_t)file_size) { | |
| 550 Dowa_Free(p_entities); | |
| 551 return FALSE; | |
| 552 } | |
| 553 | |
| 554 Canvas_Scene_Clear(p_scene); | |
| 555 for (uint32 index = 0; index < entity_count; index++) { | |
| 556 Dowa_Array_Push_Arena( | |
| 557 p_scene->p_entities, p_entities[index], p_scene->p_arena); | |
| 558 } | |
| 559 Dowa_Free(p_entities); | |
| 560 p_scene->next_entity = | |
| 561 next_entity < entity_count ? entity_count : next_entity; | |
| 562 p_scene->next_entity_id = | |
| 563 next_entity_id < max_id ? max_id : next_entity_id; | |
| 564 p_scene->show_grid = scene_flags & 1u ? TRUE : FALSE; | |
| 565 p_camera->target = (Vector2){camera_x, camera_y}; | |
| 566 p_camera->zoom = camera_zoom; | |
| 567 return TRUE; | |
| 568 #endif | |
| 569 } |