Whatever, some minor map chunking improvements
This commit is contained in:
@@ -23,7 +23,7 @@
|
|||||||
#define ASSET_FILE_NAME "dusk.dsk"
|
#define ASSET_FILE_NAME "dusk.dsk"
|
||||||
#define ASSET_HEADER_SIZE 3
|
#define ASSET_HEADER_SIZE 3
|
||||||
|
|
||||||
#define ASSET_LOADING_COUNT_MAX 32
|
#define ASSET_LOADING_COUNT_MAX 10
|
||||||
#define ASSET_ENTRY_COUNT_MAX 64
|
#define ASSET_ENTRY_COUNT_MAX 64
|
||||||
|
|
||||||
typedef struct asset_s {
|
typedef struct asset_s {
|
||||||
|
|||||||
@@ -21,21 +21,13 @@ errorret_t mapInit() {
|
|||||||
memoryZero(&MAP, sizeof(map_t));
|
memoryZero(&MAP, sizeof(map_t));
|
||||||
MAP.loaded = true;
|
MAP.loaded = true;
|
||||||
|
|
||||||
MAP.loadPosition = (chunkpos_t){
|
|
||||||
-(MAP_CHUNK_SKIN),
|
|
||||||
-(MAP_CHUNK_SKIN),
|
|
||||||
-(MAP_CHUNK_SKIN)
|
|
||||||
};
|
|
||||||
|
|
||||||
chunkindex_t i = 0;
|
chunkindex_t i = 0;
|
||||||
for(chunkunit_t z = 0; z < MAP_LOADED_CHUNK_DEPTH; z++) {
|
for(chunkunit_t z = 0; z < MAP_CHUNK_DEPTH; z++) {
|
||||||
for(chunkunit_t y = 0; y < MAP_LOADED_CHUNK_HEIGHT; y++) {
|
for(chunkunit_t y = 0; y < MAP_CHUNK_HEIGHT; y++) {
|
||||||
for(chunkunit_t x = 0; x < MAP_LOADED_CHUNK_WIDTH; x++) {
|
for(chunkunit_t x = 0; x < MAP_CHUNK_WIDTH; x++) {
|
||||||
chunk_t *chunk = &MAP.chunks[i++];
|
chunk_t *chunk = &MAP.chunks[i++];
|
||||||
chunk->position = (chunkpos_t){
|
chunk->position = (chunkpos_t){
|
||||||
MAP.loadPosition.x + (chunkunit_t)x,
|
(chunkunit_t)x, (chunkunit_t)y, (chunkunit_t)z
|
||||||
MAP.loadPosition.y + (chunkunit_t)y,
|
|
||||||
MAP.loadPosition.z + (chunkunit_t)z
|
|
||||||
};
|
};
|
||||||
errorChain(mapChunkLoad(chunk));
|
errorChain(mapChunkLoad(chunk));
|
||||||
}
|
}
|
||||||
@@ -54,48 +46,23 @@ errorret_t mapPositionSet(const chunkpos_t newPos) {
|
|||||||
if(!mapIsLoaded()) errorThrow("No map loaded");
|
if(!mapIsLoaded()) errorThrow("No map loaded");
|
||||||
if(chunkPositionIsEqual(newPos, MAP.chunkPosition)) errorOk();
|
if(chunkPositionIsEqual(newPos, MAP.chunkPosition)) errorOk();
|
||||||
|
|
||||||
// If the new render window still fits inside the current loaded area,
|
|
||||||
// just remap chunkOrder — no asset I/O needed.
|
|
||||||
const chunkpos_t lp = MAP.loadPosition;
|
|
||||||
if(
|
|
||||||
newPos.x >= lp.x &&
|
|
||||||
newPos.y >= lp.y &&
|
|
||||||
newPos.z >= lp.z &&
|
|
||||||
newPos.x + MAP_CHUNK_WIDTH <= lp.x + MAP_LOADED_CHUNK_WIDTH &&
|
|
||||||
newPos.y + MAP_CHUNK_HEIGHT <= lp.y + MAP_LOADED_CHUNK_HEIGHT &&
|
|
||||||
newPos.z + MAP_CHUNK_DEPTH <= lp.z + MAP_LOADED_CHUNK_DEPTH
|
|
||||||
) {
|
|
||||||
MAP.chunkPosition = newPos;
|
|
||||||
mapRebuildChunkOrder();
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render window fell outside the loaded area — re-centre the load
|
|
||||||
// window on the new render position (skin buffer on every side).
|
|
||||||
const chunkpos_t newLoadPos = {
|
|
||||||
newPos.x - MAP_CHUNK_SKIN,
|
|
||||||
newPos.y - MAP_CHUNK_SKIN,
|
|
||||||
newPos.z - MAP_CHUNK_SKIN
|
|
||||||
};
|
|
||||||
|
|
||||||
// Separate loaded chunks into "keep" and "free" buckets.
|
// Separate loaded chunks into "keep" and "free" buckets.
|
||||||
chunkindex_t chunksFreed[MAP_LOADED_CHUNK_COUNT];
|
chunkindex_t chunksFreed[MAP_CHUNK_COUNT];
|
||||||
uint32_t freedCount = 0;
|
uint32_t freedCount = 0;
|
||||||
|
|
||||||
// Use a boolean grid so the inner load loop can check O(1).
|
// Use a boolean grid so the inner load loop can check O(1).
|
||||||
bool_t posLoaded[MAP_LOADED_CHUNK_WIDTH][MAP_LOADED_CHUNK_HEIGHT]
|
bool_t posLoaded[MAP_CHUNK_WIDTH][MAP_CHUNK_HEIGHT][MAP_CHUNK_DEPTH];
|
||||||
[MAP_LOADED_CHUNK_DEPTH];
|
|
||||||
memoryZero(posLoaded, sizeof(posLoaded));
|
memoryZero(posLoaded, sizeof(posLoaded));
|
||||||
|
|
||||||
for(chunkindex_t i = 0; i < MAP_LOADED_CHUNK_COUNT; i++) {
|
for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) {
|
||||||
chunk_t *chunk = &MAP.chunks[i];
|
chunk_t *chunk = &MAP.chunks[i];
|
||||||
chunkunit_t rx = chunk->position.x - newLoadPos.x;
|
chunkunit_t rx = chunk->position.x - newPos.x;
|
||||||
chunkunit_t ry = chunk->position.y - newLoadPos.y;
|
chunkunit_t ry = chunk->position.y - newPos.y;
|
||||||
chunkunit_t rz = chunk->position.z - newLoadPos.z;
|
chunkunit_t rz = chunk->position.z - newPos.z;
|
||||||
if(
|
if(
|
||||||
rx >= 0 && rx < MAP_LOADED_CHUNK_WIDTH &&
|
rx >= 0 && rx < MAP_CHUNK_WIDTH &&
|
||||||
ry >= 0 && ry < MAP_LOADED_CHUNK_HEIGHT &&
|
ry >= 0 && ry < MAP_CHUNK_HEIGHT &&
|
||||||
rz >= 0 && rz < MAP_LOADED_CHUNK_DEPTH
|
rz >= 0 && rz < MAP_CHUNK_DEPTH
|
||||||
) {
|
) {
|
||||||
posLoaded[rx][ry][rz] = true;
|
posLoaded[rx][ry][rz] = true;
|
||||||
} else {
|
} else {
|
||||||
@@ -104,23 +71,22 @@ errorret_t mapPositionSet(const chunkpos_t newPos) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for(chunkunit_t z = 0; z < MAP_LOADED_CHUNK_DEPTH; z++) {
|
for(chunkunit_t z = 0; z < MAP_CHUNK_DEPTH; z++) {
|
||||||
for(chunkunit_t y = 0; y < MAP_LOADED_CHUNK_HEIGHT; y++) {
|
for(chunkunit_t y = 0; y < MAP_CHUNK_HEIGHT; y++) {
|
||||||
for(chunkunit_t x = 0; x < MAP_LOADED_CHUNK_WIDTH; x++) {
|
for(chunkunit_t x = 0; x < MAP_CHUNK_WIDTH; x++) {
|
||||||
if(posLoaded[x][y][z]) continue;
|
if(posLoaded[x][y][z]) continue;
|
||||||
assertTrue(freedCount > 0, "No free chunk slot available.");
|
assertTrue(freedCount > 0, "No free chunk slot available.");
|
||||||
chunk_t *chunk = &MAP.chunks[chunksFreed[--freedCount]];
|
chunk_t *chunk = &MAP.chunks[chunksFreed[--freedCount]];
|
||||||
chunk->position = (chunkpos_t){
|
chunk->position = (chunkpos_t){
|
||||||
newLoadPos.x + (chunkunit_t)x,
|
newPos.x + (chunkunit_t)x,
|
||||||
newLoadPos.y + (chunkunit_t)y,
|
newPos.y + (chunkunit_t)y,
|
||||||
newLoadPos.z + (chunkunit_t)z
|
newPos.z + (chunkunit_t)z
|
||||||
};
|
};
|
||||||
errorChain(mapChunkLoad(chunk));
|
errorChain(mapChunkLoad(chunk));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MAP.loadPosition = newLoadPos;
|
|
||||||
MAP.chunkPosition = newPos;
|
MAP.chunkPosition = newPos;
|
||||||
mapRebuildChunkOrder();
|
mapRebuildChunkOrder();
|
||||||
errorOk();
|
errorOk();
|
||||||
@@ -131,13 +97,16 @@ errorret_t mapUpdate() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
errorret_t mapDispose() {
|
errorret_t mapDispose() {
|
||||||
for(chunkindex_t i = 0; i < MAP_LOADED_CHUNK_COUNT; i++) {
|
for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) {
|
||||||
mapChunkUnload(&MAP.chunks[i]);
|
mapChunkUnload(&MAP.chunks[i]);
|
||||||
}
|
}
|
||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|
||||||
void mapChunkUnload(chunk_t *chunk) {
|
void mapChunkUnload(chunk_t *chunk) {
|
||||||
|
mapChunkLoadQueueRemove(chunk);
|
||||||
|
if(MAP.loadingChunk == chunk) MAP.loadingChunk = NULL;
|
||||||
|
|
||||||
for(uint8_t i = 0; i < CHUNK_ENTITY_COUNT_MAX; i++) {
|
for(uint8_t i = 0; i < CHUNK_ENTITY_COUNT_MAX; i++) {
|
||||||
if(chunk->entities[i] == 0xFF) continue;
|
if(chunk->entities[i] == 0xFF) continue;
|
||||||
entity_t *entity = &ENTITIES[chunk->entities[i]];
|
entity_t *entity = &ENTITIES[chunk->entities[i]];
|
||||||
@@ -169,6 +138,9 @@ void mapChunkUnload(chunk_t *chunk) {
|
|||||||
errorret_t mapChunkLoad(chunk_t *chunk) {
|
errorret_t mapChunkLoad(chunk_t *chunk) {
|
||||||
if(!mapIsLoaded()) errorThrow("No map loaded");
|
if(!mapIsLoaded()) errorThrow("No map loaded");
|
||||||
|
|
||||||
|
mapChunkLoadQueueRemove(chunk);
|
||||||
|
if(MAP.loadingChunk == chunk) MAP.loadingChunk = NULL;
|
||||||
|
|
||||||
if(chunk->dcfEntry != NULL) {
|
if(chunk->dcfEntry != NULL) {
|
||||||
eventUnsubscribe(&chunk->dcfEntry->onLoaded, mapChunkLoaded);
|
eventUnsubscribe(&chunk->dcfEntry->onLoaded, mapChunkLoaded);
|
||||||
eventUnsubscribe(&chunk->dcfEntry->onError, mapChunkLoadError);
|
eventUnsubscribe(&chunk->dcfEntry->onError, mapChunkLoadError);
|
||||||
@@ -196,6 +168,35 @@ errorret_t mapChunkLoad(chunk_t *chunk) {
|
|||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assertTrue(
|
||||||
|
MAP.loadQueueCount < MAP_CHUNK_COUNT,
|
||||||
|
"Chunk load queue overflow"
|
||||||
|
);
|
||||||
|
MAP.loadQueue[MAP.loadQueueCount++] = chunk;
|
||||||
|
mapChunkLoadNext();
|
||||||
|
errorOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
void mapChunkLoadNext() {
|
||||||
|
if(MAP.loadingChunk != NULL) return;
|
||||||
|
if(MAP.loadQueueCount == 0) return;
|
||||||
|
|
||||||
|
chunk_t *chunk = MAP.loadQueue[0];
|
||||||
|
for(uint32_t i = 1; i < MAP.loadQueueCount; i++) {
|
||||||
|
MAP.loadQueue[i - 1] = MAP.loadQueue[i];
|
||||||
|
}
|
||||||
|
MAP.loadQueueCount--;
|
||||||
|
MAP.loadingChunk = chunk;
|
||||||
|
|
||||||
|
char_t name[64];
|
||||||
|
stringFormat(
|
||||||
|
name, sizeof(name),
|
||||||
|
"chunks/%d_%d_%d.dcf",
|
||||||
|
(int32_t)chunk->position.x,
|
||||||
|
(int32_t)chunk->position.y,
|
||||||
|
(int32_t)chunk->position.z
|
||||||
|
);
|
||||||
|
|
||||||
assetentry_t *entry = assetLock(name, ASSET_LOADER_TYPE_CHUNK, NULL);
|
assetentry_t *entry = assetLock(name, ASSET_LOADER_TYPE_CHUNK, NULL);
|
||||||
assertNotNull(entry, "Failed to get chunk asset entry");
|
assertNotNull(entry, "Failed to get chunk asset entry");
|
||||||
chunk->dcfEntry = entry;
|
chunk->dcfEntry = entry;
|
||||||
@@ -206,16 +207,26 @@ errorret_t mapChunkLoad(chunk_t *chunk) {
|
|||||||
// a subscription that would never trigger.
|
// a subscription that would never trigger.
|
||||||
if(entry->state == ASSET_ENTRY_STATE_LOADED) {
|
if(entry->state == ASSET_ENTRY_STATE_LOADED) {
|
||||||
mapChunkLoaded(entry, chunk);
|
mapChunkLoaded(entry, chunk);
|
||||||
errorOk();
|
return;
|
||||||
}
|
}
|
||||||
if(entry->state == ASSET_ENTRY_STATE_ERROR) {
|
if(entry->state == ASSET_ENTRY_STATE_ERROR) {
|
||||||
mapChunkLoadError(entry, chunk);
|
mapChunkLoadError(entry, chunk);
|
||||||
errorOk();
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
eventSubscribe(&entry->onLoaded, mapChunkLoaded, chunk);
|
eventSubscribe(&entry->onLoaded, mapChunkLoaded, chunk);
|
||||||
eventSubscribe(&entry->onError, mapChunkLoadError, chunk);
|
eventSubscribe(&entry->onError, mapChunkLoadError, chunk);
|
||||||
errorOk();
|
}
|
||||||
|
|
||||||
|
void mapChunkLoadQueueRemove(chunk_t *chunk) {
|
||||||
|
for(uint32_t i = 0; i < MAP.loadQueueCount; i++) {
|
||||||
|
if(MAP.loadQueue[i] != chunk) continue;
|
||||||
|
for(uint32_t j = i + 1; j < MAP.loadQueueCount; j++) {
|
||||||
|
MAP.loadQueue[j - 1] = MAP.loadQueue[j];
|
||||||
|
}
|
||||||
|
MAP.loadQueueCount--;
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -327,7 +338,7 @@ entity_t * mapSpawnEntity(
|
|||||||
|
|
||||||
void mapRebuildChunkOrder() {
|
void mapRebuildChunkOrder() {
|
||||||
memoryZero(MAP.chunkOrder, sizeof(MAP.chunkOrder));
|
memoryZero(MAP.chunkOrder, sizeof(MAP.chunkOrder));
|
||||||
for(chunkindex_t i = 0; i < MAP_LOADED_CHUNK_COUNT; i++) {
|
for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) {
|
||||||
chunk_t *chunk = &MAP.chunks[i];
|
chunk_t *chunk = &MAP.chunks[i];
|
||||||
const chunkpos_t rel = {
|
const chunkpos_t rel = {
|
||||||
chunk->position.x - MAP.chunkPosition.x,
|
chunk->position.x - MAP.chunkPosition.x,
|
||||||
@@ -360,6 +371,9 @@ void mapChunkLoadError(void *params, void *user) {
|
|||||||
assetUnlockEntry(chunk->dcfEntry);
|
assetUnlockEntry(chunk->dcfEntry);
|
||||||
chunk->dcfEntry = NULL;
|
chunk->dcfEntry = NULL;
|
||||||
memorySet(chunk->tiles, 0x00, sizeof(chunk->tiles));
|
memorySet(chunk->tiles, 0x00, sizeof(chunk->tiles));
|
||||||
|
|
||||||
|
if(MAP.loadingChunk == chunk) MAP.loadingChunk = NULL;
|
||||||
|
mapChunkLoadNext();
|
||||||
}
|
}
|
||||||
|
|
||||||
void mapChunkLoaded(void *params, void *user) {
|
void mapChunkLoaded(void *params, void *user) {
|
||||||
@@ -418,4 +432,7 @@ void mapChunkLoaded(void *params, void *user) {
|
|||||||
// chunk asset entry (and therefore its model locks) alive for as long as
|
// chunk asset entry (and therefore its model locks) alive for as long as
|
||||||
// this chunk_t is displaying it. Released in mapChunkUnload instead.
|
// this chunk_t is displaying it. Released in mapChunkUnload instead.
|
||||||
chunk->meshCount = meshCount;
|
chunk->meshCount = meshCount;
|
||||||
|
|
||||||
|
if(MAP.loadingChunk == chunk) MAP.loadingChunk = NULL;
|
||||||
|
mapChunkLoadNext();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,10 +15,15 @@
|
|||||||
typedef struct map_s {
|
typedef struct map_s {
|
||||||
bool_t loaded;
|
bool_t loaded;
|
||||||
|
|
||||||
chunk_t chunks[MAP_LOADED_CHUNK_COUNT];
|
chunk_t chunks[MAP_CHUNK_COUNT];
|
||||||
chunk_t *chunkOrder[MAP_CHUNK_COUNT];
|
chunk_t *chunkOrder[MAP_CHUNK_COUNT];
|
||||||
chunkpos_t chunkPosition;
|
chunkpos_t chunkPosition;
|
||||||
chunkpos_t loadPosition;
|
|
||||||
|
// Only one chunk may be mid-load (asset locked & awaiting onLoaded/
|
||||||
|
// onError) at any given time - everything else waits here in FIFO order.
|
||||||
|
chunk_t *loadQueue[MAP_CHUNK_COUNT];
|
||||||
|
uint32_t loadQueueCount;
|
||||||
|
chunk_t *loadingChunk;
|
||||||
} map_t;
|
} map_t;
|
||||||
|
|
||||||
extern map_t MAP;
|
extern map_t MAP;
|
||||||
@@ -74,6 +79,21 @@ void mapChunkUnload(chunk_t* chunk);
|
|||||||
*/
|
*/
|
||||||
errorret_t mapChunkLoad(chunk_t* chunk);
|
errorret_t mapChunkLoad(chunk_t* chunk);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts loading the next queued chunk, if no chunk is currently mid-load.
|
||||||
|
* Called after mapChunkLoad enqueues a chunk, and again after the
|
||||||
|
* currently-loading chunk finishes (or is unloaded) to advance the queue.
|
||||||
|
*/
|
||||||
|
void mapChunkLoadNext();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a chunk from the load queue if present. Used when a chunk is
|
||||||
|
* re-queued or unloaded before its turn to load has come up.
|
||||||
|
*
|
||||||
|
* @param chunk The chunk to remove from the load queue.
|
||||||
|
*/
|
||||||
|
void mapChunkLoadQueueRemove(chunk_t *chunk);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback invoked when a chunk DCF asset fails to load. Fills the
|
* Callback invoked when a chunk DCF asset fails to load. Fills the
|
||||||
* chunk tiles with TILE_SHAPE_GROUND as a fallback.
|
* chunk tiles with TILE_SHAPE_GROUND as a fallback.
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ bool_t mapAreaIsChunkOverlappingOrInside(
|
|||||||
bool_t mapAreaCanUnload(const maparea_t *area) {
|
bool_t mapAreaCanUnload(const maparea_t *area) {
|
||||||
assertNotNull(area, "Map area pointer cannot be NULL");
|
assertNotNull(area, "Map area pointer cannot be NULL");
|
||||||
|
|
||||||
for(chunkindex_t i = 0; i < MAP_LOADED_CHUNK_COUNT; i++) {
|
for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) {
|
||||||
if(mapAreaIsChunkOverlappingOrInside(area, &MAP.chunks[i])) return false;
|
if(mapAreaIsChunkOverlappingOrInside(area, &MAP.chunks[i])) return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,16 +30,6 @@
|
|||||||
#define MAP_CHUNK_DEPTH 4
|
#define MAP_CHUNK_DEPTH 4
|
||||||
#define MAP_CHUNK_COUNT (MAP_CHUNK_WIDTH * MAP_CHUNK_HEIGHT * MAP_CHUNK_DEPTH)
|
#define MAP_CHUNK_COUNT (MAP_CHUNK_WIDTH * MAP_CHUNK_HEIGHT * MAP_CHUNK_DEPTH)
|
||||||
|
|
||||||
// Extra chunks loaded on every side beyond the render window.
|
|
||||||
// The render window can drift MAP_CHUNK_SKIN chunks in any direction
|
|
||||||
// before a load/unload cycle is triggered.
|
|
||||||
#define MAP_CHUNK_SKIN 1
|
|
||||||
#define MAP_LOADED_CHUNK_WIDTH (MAP_CHUNK_WIDTH + MAP_CHUNK_SKIN)
|
|
||||||
#define MAP_LOADED_CHUNK_HEIGHT (MAP_CHUNK_HEIGHT + MAP_CHUNK_SKIN)
|
|
||||||
#define MAP_LOADED_CHUNK_DEPTH (MAP_CHUNK_DEPTH + MAP_CHUNK_SKIN)
|
|
||||||
#define MAP_LOADED_CHUNK_COUNT \
|
|
||||||
(MAP_LOADED_CHUNK_WIDTH * MAP_LOADED_CHUNK_HEIGHT * MAP_LOADED_CHUNK_DEPTH)
|
|
||||||
|
|
||||||
#define ENTITY_COUNT 32
|
#define ENTITY_COUNT 32
|
||||||
|
|
||||||
typedef int16_t worldunit_t;
|
typedef int16_t worldunit_t;
|
||||||
|
|||||||
Reference in New Issue
Block a user