diff options
| author | KonAki <konaki@anokonaki.org> | 2026-10-09 18:21:19 +0800 |
|---|---|---|
| committer | KonAki <konaki@anokonaki.org> | 2026-10-09 18:21:19 +0800 |
| commit | 0d73e4712f8472bf8e004faadc79dab453684866 (patch) | |
| tree | eb1ddfc88a23eb96c9dfbff481ad9051b3b27c70 /kon | |
| parent | 29ac0888951c423d6673fff10c93e60b0434f204 (diff) | |
Add asset groups and automatic model textures
Diffstat (limited to 'kon')
| -rw-r--r-- | kon/konassets.c | 177 | ||||
| -rw-r--r-- | kon/konassets.h | 35 | ||||
| -rw-r--r-- | kon/konmodel.c | 12 | ||||
| -rw-r--r-- | kon/konmodel.h | 6 |
4 files changed, 214 insertions, 16 deletions
diff --git a/kon/konassets.c b/kon/konassets.c index 173e085..75b2fae 100644 --- a/kon/konassets.c +++ b/kon/konassets.c @@ -47,6 +47,7 @@ typedef struct kon_assetEntry_ { int refs; size_t bytes; int loose; + int unbound; /* set while collecting, once the object has given back what it was using */ } kon_assetEntry_t; struct kon_assets { @@ -310,6 +311,8 @@ static void kon_addLoader_(kon_assets_t *assets, int type, const char *name, con assets->loaders[type].extensions = extensions; assets->loaders[type].load = load; assets->loaders[type].unload = unload; + assets->loaders[type].bind = NULL; + assets->loaders[type].unbind = NULL; assets->loaderUsed[type] = 1; } @@ -362,6 +365,8 @@ int kon_assetsRegister(kon_assets_t *assets, const kon_assetLoader_t *loader) { type = assets->nextCustom++; kon_addLoader_(assets, type, loader->name, loader->extensions, loader->load, loader->unload); + assets->loaders[type].bind = loader->bind; + assets->loaders[type].unbind = loader->unbind; return type; } @@ -433,8 +438,12 @@ void *kon_assetGetAs(kon_assets_t *assets, const char *name, int type) { entry->refs = 1; entry->bytes = bytes; entry->loose = loose; + entry->unbound = 0; assets->count++; + /* this can load more assets and move the entries, so entry is not used after it */ + if (assets->loaders[found].bind) assets->loaders[found].bind(object, assets); + return object; } @@ -447,19 +456,37 @@ void kon_assetRelease(kon_assets_t *assets, const char *name) { } int kon_assetsCollect(kon_assets_t *assets) { - int kept = 0, freed = 0; + int freed = 0, again; if (!assets) return 0; - for (int i = 0; i < assets->count; i++) { - if (assets->entries[i].refs > 0) { - assets->entries[kept++] = assets->entries[i]; - } else { - kon_unloadEntry_(assets, &assets->entries[i]); - freed++; + /* an object that is freed can give back the last use of another one, so go around until nothing changes */ + do { + int kept = 0, before = freed; + + /* first let everything that is not used say what it was using, which only lowers the use counts of others */ + for (int i = 0; i < assets->count; i++) { + kon_assetEntry_t *entry = &assets->entries[i]; + if (entry->refs > 0 || entry->unbound) continue; + entry->unbound = 1; + if (assets->loaders[entry->type].unbind) assets->loaders[entry->type].unbind(entry->object, assets); } - } - assets->count = kept; + + for (int i = 0; i < assets->count; i++) { + if (assets->entries[i].refs > 0 || !assets->entries[i].unbound) { + assets->entries[kept++] = assets->entries[i]; + } else { + kon_unloadEntry_(assets, &assets->entries[i]); + freed++; + } + } + assets->count = kept; + + /* something that just became unused has to be looked at again */ + again = freed != before; + for (int i = 0; i < assets->count && !again; i++) again = assets->entries[i].refs <= 0 && !assets->entries[i].unbound; + } while (again); + return freed; } @@ -501,6 +528,138 @@ void kon_assetsDump(const kon_assets_t *assets, FILE *out) { } } +/*** groups ***/ + +struct kon_assetGroup { + kon_assets_t *assets; + char **names; + char *state; /* 0 waiting, 1 loaded, 2 failed */ + int count, capacity, next; +}; + +kon_assetGroup_t *kon_assetGroupCreate(kon_assets_t *assets) { + kon_assetGroup_t *group; + + if (!assets) return NULL; + group = calloc(1, sizeof *group); + if (group) group->assets = assets; + return group; +} + +int kon_assetGroupQueue(kon_assetGroup_t *group, const char *name) { + char *copy; + + if (!group || !name) return 0; + if (group->count == group->capacity) { + int capacity = group->capacity ? group->capacity * 2 : 16; + char **names = realloc(group->names, (size_t)capacity * sizeof *names); + char *state; + + if (!names) return 0; + group->names = names; + state = realloc(group->state, (size_t)capacity); + if (!state) return 0; + group->state = state; + group->capacity = capacity; + } + copy = malloc(strlen(name) + 1); + if (!copy) return 0; + strcpy(copy, name); + group->names[group->count] = copy; + group->state[group->count] = 0; + group->count++; + return 1; +} + +int kon_assetGroupQueueList(kon_assetGroup_t *group, const char *listName) { + kon_blob_t *blob; + char *line; + int queued = 0; + + if (!group || !listName) return -1; + blob = kon_assetGetAs(group->assets, listName, KON_ASSET_BLOB); + if (!blob) return -1; + + /* the blob has a 0 after the end, so each line is cut out in place and put back */ + line = (char *)blob->data; + while (*line) { + char *end = line; + char *start, *stop; + + while (*end && *end != '\n') end++; + start = line; + stop = end; + while (start < stop && (*start == ' ' || *start == '\t' || *start == '\r')) start++; + while (stop > start && (stop[-1] == ' ' || stop[-1] == '\t' || stop[-1] == '\r')) stop--; + + if (stop > start && *start != '#') { + char saved = *stop; + int ok; + *stop = 0; + ok = kon_assetGroupQueue(group, start); + *stop = saved; + if (!ok) break; + queued++; + } + line = *end ? end + 1 : end; + } + kon_assetRelease(group->assets, listName); + return queued; +} + +int kon_assetGroupStep(kon_assetGroup_t *group) { + if (!group) return 0; + + while (group->next < group->count && group->state[group->next] != 0) group->next++; + if (group->next < group->count) { + int i = group->next++; + + if (kon_assetGet(group->assets, group->names[i])) { + group->state[i] = 1; + } else { + group->state[i] = 2; +#ifdef KON_DEVELOPER + fprintf(stderr, "asset group: could not load %s: %s\n", group->names[i], kon_assetsError(group->assets)); +#endif + } + } + + while (group->next < group->count && group->state[group->next] != 0) group->next++; + return group->count - group->next; +} + +int kon_assetGroupLoadAll(kon_assetGroup_t *group) { + int failed = 0, i; + + if (!group) return 0; + while (kon_assetGroupStep(group) > 0) {} + for (i = 0; i < group->count; i++) if (group->state[i] == 2) failed++; + return failed; +} + +void kon_assetGroupProgress(const kon_assetGroup_t *group, int *loaded, int *total) { + int i, done = 0; + + if (group) for (i = 0; i < group->count; i++) if (group->state[i] == 1) done++; + if (loaded) *loaded = done; + if (total) *total = group ? group->count : 0; +} + +void kon_assetGroupFree(kon_assetGroup_t *group) { + int i; + + if (!group) return; + for (i = 0; i < group->count; i++) { + if (group->state[i] == 1) kon_assetRelease(group->assets, group->names[i]); + free(group->names[i]); + } + kon_assetsCollect(group->assets); + free(group->names); + free(group->state); + free(group); +} + + const char *kon_assetsError(const kon_assets_t *assets) { return assets ? assets->error : "no assets"; } diff --git a/kon/konassets.h b/kon/konassets.h index 6dd65eb..3b35629 100644 --- a/kon/konassets.h +++ b/kon/konassets.h @@ -65,6 +65,10 @@ typedef struct kon_assetLoader { const char *extensions; void *(*load)(const uint8_t *data, size_t size, size_t *bytes); void (*unload)(void *object); + /* both can be NULL. bind is called right after load and is the place to get other assets the object needs, like the textures of a model. + unbind is called by kon_assetsCollect before the object is freed, and gives those assets back. assets that depend on each other in a circle never get freed */ + void (*bind)(void *object, kon_assets_t *assets); + void (*unbind)(void *object, kon_assets_t *assets); } kon_assetLoader_t; typedef struct kon_assetInfo { @@ -89,7 +93,7 @@ int kon_assetsRegister(kon_assets_t *assets, const kon_assetLoader_t *loader); NULL if it is not found or fails to load, see kon_assetsError */ void *kon_assetGet(kon_assets_t *assets, const char *name); -/* same but NULL if the file is not of that type, without counting a use */ +/* same, but NULL if the file is not of that type. counts a use the same way, and a file of the wrong type isn't loaded */ void *kon_assetGetAs(kon_assets_t *assets, const char *name, int type); #define kon_assetBlob(assets, name) ((kon_blob_t *)kon_assetGetAs((assets), (name), KON_ASSET_BLOB)) @@ -99,9 +103,36 @@ void *kon_assetGetAs(kon_assets_t *assets, const char *name, int type); /* gives back one use. nothing is freed until kon_assetsCollect */ void kon_assetRelease(kon_assets_t *assets, const char *name); -/* frees everything nobody is using, returns how many it freed */ +/* frees everything nobody is using, and then what only that was using, returns how many it freed */ int kon_assetsCollect(kon_assets_t *assets); +/*** groups ***/ + +/* a group is a list of assets that live and die together, like everything one level needs. + loading the next level's group before freeing the old one keeps what both use */ +typedef struct kon_assetGroup kon_assetGroup_t; + +kon_assetGroup_t *kon_assetGroupCreate(kon_assets_t *assets); + +/* adds a name to the waiting list, nothing is loaded yet. returns 0 if there was no memory */ +int kon_assetGroupQueue(kon_assetGroup_t *group, const char *name); + +/* queues every name in a text asset, one per line. empty lines and lines starting with # are skipped. + returns how many it queued, or -1 if the list can't be read */ +int kon_assetGroupQueueList(kon_assetGroup_t *group, const char *listName); + +/* loads one waiting asset and returns how many are still waiting, so a loading bar can call it once per frame */ +int kon_assetGroupStep(kon_assetGroup_t *group); + +/* loads everything that is waiting and returns how many could not be loaded */ +int kon_assetGroupLoadAll(kon_assetGroup_t *group); + +/* loaded counts the assets that are in, total also counts the ones waiting and the ones that failed */ +void kon_assetGroupProgress(const kon_assetGroup_t *group, int *loaded, int *total); + +/* gives back every use the group holds and frees what nothing else uses. the group is gone after this */ +void kon_assetGroupFree(kon_assetGroup_t *group); + /* for the debug view */ int kon_assetsCount(const kon_assets_t *assets); int kon_assetsInfo(const kon_assets_t *assets, int index, kon_assetInfo_t *info); diff --git a/kon/konmodel.c b/kon/konmodel.c index a92fb89..78995c8 100644 --- a/kon/konmodel.c +++ b/kon/konmodel.c @@ -307,7 +307,15 @@ static void kon_modelAssetUnload_(void *object) { kon_modelFree((kon_model_t *)object); } -static const kon_assetLoader_t kon_modelLoader_ = {"model", ".kmdl", kon_modelAssetLoad_, kon_modelAssetUnload_}; +static void kon_modelAssetBind_(void *object, kon_assets_t *assets) { + kon_modelBindTextures((kon_model_t *)object, assets); +} + +static void kon_modelAssetUnbind_(void *object, kon_assets_t *assets) { + kon_modelUnbindTextures((kon_model_t *)object, assets); +} + +static const kon_assetLoader_t kon_modelLoader_ = {"model", ".kmdl", kon_modelAssetLoad_, kon_modelAssetUnload_, kon_modelAssetBind_, kon_modelAssetUnbind_}; int kon_modelRegister(kon_assets_t *assets) { return kon_assetsRegister(assets, &kon_modelLoader_); @@ -320,9 +328,7 @@ void kon_modelBindTextures(kon_model_t *model, kon_assets_t *assets) { for (i = 0; i < model->materialCount; i++) { kon_modelMaterial_t *m = &model->materials[i]; if (!m->texture[0] || m->image) continue; - if (!kon_assetGet(assets, m->texture)) continue; m->image = kon_assetImage(assets, m->texture); - if (!m->image) kon_assetRelease(assets, m->texture); } } diff --git a/kon/konmodel.h b/kon/konmodel.h index 1c64891..8ce36c8 100644 --- a/kon/konmodel.h +++ b/kon/konmodel.h @@ -107,10 +107,12 @@ typedef struct kon_model { kon_model_t *kon_modelLoad(const uint8_t *data, size_t size); void kon_modelFree(kon_model_t *model); -/* loads .kmdl files through the asset manager, returns the type id for kon_assetGetAs or -1 */ +/* loads .kmdl files through the asset manager, returns the type id for kon_assetGetAs or -1. + a model gets its textures when it is loaded and gives them back when it is collected, you don't need to bind them yourself */ int kon_modelRegister(kon_assets_t *assets); -/* gets every texture the materials name and keeps it in use until unbind. missing ones are left untextured */ +/* gets every texture the materials name and keeps it in use until unbind. missing ones are left untextured. + only needed for models you loaded without the asset manager */ void kon_modelBindTextures(kon_model_t *model, kon_assets_t *assets); void kon_modelUnbindTextures(kon_model_t *model, kon_assets_t *assets); |
