#include "./store.h" #include #include #include #include #include #include #include #include #include #include #include "util/memory/memory.h" #include "util/mpkutil/file.h" #include "./chunk.h" #include "./generator.h" #include "./poolset.h" #define LOWORLD_STORE_PATH_MAX_LENGTH 256 #define LOWORLD_STORE_UNPACKER_BUFFER_SIZE (1024*1024) typedef struct { loworld_chunk_t data; bool loaded; bool used; uint64_t last_tick; } loworld_store_chunk_t; struct loworld_store_t { char path[LOWORLD_STORE_PATH_MAX_LENGTH+LOWORLD_CHUNK_FILENAME_MAX]; size_t basepath_length; const loworld_poolset_t* pools; const loworld_generator_t* generator; msgpack_unpacker unpacker; msgpack_unpacked unpacked; uint64_t tick; bool error; size_t chunks_length; loworld_store_chunk_t chunks[1]; }; static bool loworld_store_find_chunk_index_( const loworld_store_t* store, size_t* index, int32_t x, int32_t y) { assert(store != NULL); assert(index != NULL); for (size_t i = 0; i < store->chunks_length; ++i) { if (!store->chunks[i].loaded) continue; const loworld_chunk_t* chunk = &store->chunks[i].data; if (chunk->pos.x == x && chunk->pos.y == y) { *index = i; return true; } } return false; } static bool loworld_store_find_unused_chunk_index_( const loworld_store_t* store, size_t* index) { assert(store != NULL); assert(index != NULL); for (size_t i = 0; i < store->chunks_length; ++i) { if (!store->chunks[i].loaded) { *index = i; return true; } } bool found = false; uint64_t oldest = UINT64_MAX; for (size_t i = 0; i < store->chunks_length; ++i) { const loworld_store_chunk_t* chunk = &store->chunks[i]; if (!chunk->used && chunk->last_tick <= oldest) { *index = i; oldest = chunk->last_tick; found = true; } } return found; } /* Builds filename on the store->path as zero-terminated string. */ static void loworld_store_build_chunk_filename_( loworld_store_t* store, const loworld_chunk_t* chunk) { assert(store != NULL); assert(chunk != NULL); loworld_chunk_build_filename(chunk, &store->path[store->basepath_length], LOWORLD_CHUNK_FILENAME_MAX); } static bool loworld_store_load_chunk_from_file_( loworld_store_t* store, loworld_chunk_t* chunk) { assert(store != NULL); assert(chunk != NULL); loworld_store_build_chunk_filename_(store, chunk); FILE* fp = fopen(store->path, "rb"); if (fp == NULL) return false; bool success = false; msgpack_unpacker_reset(&store->unpacker); if (mpkutil_file_unpack_with_unpacker( &store->unpacked, fp, &store->unpacker)) { loworld_chunk_clear(chunk); success = loworld_chunk_unpack(chunk, &store->unpacked.data, store->pools); } fclose(fp); if (!success) { fprintf(stderr, "failed to load chunk (%"PRId32", %"PRId32")\n", chunk->pos.x, chunk->pos.y); } return success; } static bool loworld_store_save_chunk_to_file_( loworld_store_t* store, const loworld_chunk_t* chunk) { assert(store != NULL); assert(chunk != NULL); loworld_store_build_chunk_filename_(store, chunk); FILE* fp = fopen(store->path, "wb"); if (fp == NULL) return false; msgpack_packer packer; msgpack_packer_init(&packer, fp, msgpack_fbuffer_write); loworld_chunk_pack(chunk, &packer); const bool success = (ferror(fp) == 0); fclose(fp); return success; } loworld_store_t* loworld_store_new( const loworld_poolset_t* pools, const loworld_generator_t* generator, size_t chunks_length, const char* basepath, size_t basepath_length) { assert(pools != NULL); assert(generator != NULL); assert(chunks_length > 0); if (basepath_length >= LOWORLD_STORE_PATH_MAX_LENGTH) { fprintf(stderr, "too long path name\n"); abort(); } loworld_store_t* store = memory_new( sizeof(*store) + (chunks_length-1)*sizeof(store->chunks[0])); *store = (typeof(*store)) { .basepath_length = basepath_length, .pools = pools, .generator = generator, .chunks_length = chunks_length, }; strncpy(store->path, basepath, LOWORLD_STORE_PATH_MAX_LENGTH); if (!msgpack_unpacker_init( &store->unpacker, LOWORLD_STORE_UNPACKER_BUFFER_SIZE)) { fprintf(stderr, "failed to initialize unpacker\n"); abort(); } msgpack_unpacked_init(&store->unpacked); for (size_t i = 0; i < store->chunks_length; ++i) { loworld_store_chunk_t* chunk = &store->chunks[i]; *chunk = (typeof(*chunk)) {0}; loworld_chunk_initialize(&chunk->data); } return store; } void loworld_store_delete(loworld_store_t* store) { if (store == NULL) return; msgpack_unpacker_destroy(&store->unpacker); msgpack_unpacked_destroy(&store->unpacked); for (size_t i = 0; i < store->chunks_length; ++i) { loworld_chunk_deinitialize(&store->chunks[i].data); } memory_delete(store); } loworld_chunk_t* loworld_store_load_chunk( loworld_store_t* store, int32_t chunk_x, int32_t chunk_y) { assert(store != NULL); size_t index; if (!loworld_store_find_chunk_index_(store, &index, chunk_x, chunk_y)) { if (!loworld_store_find_unused_chunk_index_(store, &index)) { fprintf(stderr, "world store chunk overflow\n"); abort(); } } loworld_store_chunk_t* chunk = &store->chunks[index]; if (chunk->loaded && (chunk->data.pos.x != chunk_x || chunk->data.pos.y != chunk_y)) { assert(!chunk->used); store->error = !loworld_store_save_chunk_to_file_(store, &chunk->data); chunk->loaded = false; } if (!chunk->loaded) { chunk->data.pos.x = chunk_x; chunk->data.pos.y = chunk_y; if (!loworld_store_load_chunk_from_file_(store, &chunk->data)) { loworld_chunk_clear(&chunk->data); loworld_generator_generate(store->generator, &chunk->data); } chunk->loaded = true; } chunk->used = true; chunk->last_tick = store->tick++; return &chunk->data; } void loworld_store_unload_chunk( loworld_store_t* store, loworld_chunk_t* chunk) { assert(store != NULL); assert(chunk != NULL); loworld_store_chunk_t* c = (typeof(c)) chunk; assert(store->chunks <= c && c < store->chunks+store->chunks_length); c->used = false; } void loworld_store_flush(loworld_store_t* store) { assert(store != NULL); for (size_t i = 0; i < store->chunks_length; ++i) { loworld_store_chunk_t* chunk = &store->chunks[i]; if (!chunk->loaded) continue; store->error = !loworld_store_save_chunk_to_file_(store, &chunk->data); } } bool loworld_store_is_error_happened(const loworld_store_t* store) { assert(store != NULL); return store->error; }