diff options
| author | KonAki <konaki@anokonaki.org> | 2026-10-09 03:11:08 +0800 |
|---|---|---|
| committer | KonAki <konaki@anokonaki.org> | 2026-10-09 03:11:08 +0800 |
| commit | e7dcff5cb68c7cd9e499d7d1986e5c7fdc19fc09 (patch) | |
| tree | 52c95b90759bee50667a5256c58b080cd2576820 | |
| parent | 72ab7350ba8d5a1471f2bbbadd7f225065dd125d (diff) | |
Split engine modules into .h and .c pairs
| -rw-r--r-- | Makefile | 24 | ||||
| -rw-r--r-- | kon/konassets.c | 506 | ||||
| -rw-r--r-- | kon/konassets.h | 485 | ||||
| -rw-r--r-- | kon/konaudio.c | 277 | ||||
| -rw-r--r-- | kon/konaudio.h | 256 | ||||
| -rw-r--r-- | kon/kondebug.c | 739 | ||||
| -rw-r--r-- | kon/kondebug.h | 714 | ||||
| -rw-r--r-- | kon/konpack.c | 598 | ||||
| -rw-r--r-- | kon/konpack.h | 577 | ||||
| -rw-r--r-- | kon/kontile.c | 101 | ||||
| -rw-r--r-- | kon/kontile.h | 80 | ||||
| -rw-r--r-- | kon/konui.c | 556 | ||||
| -rw-r--r-- | kon/konui.h | 535 | ||||
| -rw-r--r-- | src/engine.c | 126 | ||||
| -rw-r--r-- | src/impl/konassets.c | 2 | ||||
| -rw-r--r-- | src/impl/konaudio.c | 2 | ||||
| -rw-r--r-- | src/impl/kondebug.c | 2 | ||||
| -rw-r--r-- | src/impl/konpack.c | 2 | ||||
| -rw-r--r-- | src/impl/kontile.c | 2 | ||||
| -rw-r--r-- | src/impl/konui.c | 2 |
20 files changed, 2845 insertions, 2741 deletions
@@ -53,20 +53,9 @@ $(OBJDIR)/konfont.o: $(SRCDIR)/impl/konfont.c $(KONDIR)/konfont.h $(KONDIR)/kons $(OBJDIR)/kontoml.o: $(SRCDIR)/impl/kontoml.c $(KONDIR)/kontoml.h $(COMPILE) $(SRCDIR)/impl/kontoml.c -o $@ -$(OBJDIR)/kontile.o: $(SRCDIR)/impl/kontile.c $(KONDIR)/kontile.h $(KONDIR)/konsofren.h - $(COMPILE) $(SRCDIR)/impl/kontile.c -o $@ - -$(OBJDIR)/konassets.o: $(SRCDIR)/impl/konassets.c $(KONDIR)/konassets.h $(KONDIR)/konpack.h $(KONDIR)/konsofren.h $(EXTDIR)/*.h - $(COMPILE) $(SRCDIR)/impl/konassets.c -o $@ - -$(OBJDIR)/konui.o: $(SRCDIR)/impl/konui.c $(KONDIR)/konui.h $(KONDIR)/konfont.h $(KONDIR)/konsofren.h $(KONDIR)/konwinlib.h - $(COMPILE) $(SRCDIR)/impl/konui.c -o $@ - -$(OBJDIR)/kondebug.o: $(SRCDIR)/impl/kondebug.c $(KONDIR)/kondebug.h $(KONDIR)/konassets.h $(KONDIR)/konfont.h $(KONDIR)/konsofren.h $(KONDIR)/kontime.h - $(COMPILE) $(SRCDIR)/impl/kondebug.c -o $@ - -$(OBJDIR)/konaudio.o: $(SRCDIR)/impl/konaudio.c $(KONDIR)/konaudio.h $(KONDIR)/konassets.h $(EXTDIR)/miniaudio.h - $(COMPILE) $(SRCDIR)/impl/konaudio.c -o $@ +# modules with a .c next to their header are normal modules, the single header libs go through src/impl +$(OBJDIR)/%.o: $(KONDIR)/%.c $(KONDIR)/*.h $(EXTDIR)/*.h + $(COMPILE) $< -o $@ $(OBJDIR)/stb_image.o: $(SRCDIR)/impl/stb_image.c $(EXTDIR)/stb_image.h $(COMPILEEXT) $(SRCDIR)/impl/stb_image.c -o $@ @@ -74,15 +63,12 @@ $(OBJDIR)/stb_image.o: $(SRCDIR)/impl/stb_image.c $(EXTDIR)/stb_image.h $(OBJDIR)/miniaudio.o: $(SRCDIR)/impl/miniaudio.c $(EXTDIR)/miniaudio.h $(COMPILEEXT) $(SRCDIR)/impl/miniaudio.c -o $@ -$(OBJDIR)/konpack.o: $(SRCDIR)/impl/konpack.c $(KONDIR)/konpack.h - $(COMPILE) $(SRCDIR)/impl/konpack.c -o $@ - tools: $(PACKER) -$(PACKER): tools/konpack.c $(KONDIR)/konpack.h +$(PACKER): tools/konpack.c $(KONDIR)/konpack.c $(KONDIR)/konpack.h @mkdir -p $(OBJDIR) @echo "# link $@" - @$(CC) $(KONFLAGS) -o $@ tools/konpack.c + @$(CC) $(KONFLAGS) -DKONPACK_WRITER -o $@ tools/konpack.c $(KONDIR)/konpack.c pack: $(PACKER) @find $(ASSETDIR) -type f | sort > $(OBJDIR)/assets.lst diff --git a/kon/konassets.c b/kon/konassets.c new file mode 100644 index 0000000..173e085 --- /dev/null +++ b/kon/konassets.c @@ -0,0 +1,506 @@ +/* SPDX-License-Identifier: MIT */ +/* + * konassets.c + * + * Copyright (c) 2026 KonAki + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + + +#include "konassets.h" + +#include <stdlib.h> +#include <string.h> +#include <limits.h> + +#ifdef KON_MODULE_IMAGE +#include "stb_image.h" +#endif + +#ifdef KON_MODULE_AUDIO +#include "miniaudio.h" +#endif + +#define KON_ASSET_LOADERS_ 32 + +typedef struct kon_assetEntry_ { + char *name; + void *object; + int type; + int refs; + size_t bytes; + int loose; +} kon_assetEntry_t; + +struct kon_assets { + kon_pack_t *pack; + char *looseDir; + kon_assetLoader_t loaders[KON_ASSET_LOADERS_]; + int loaderUsed[KON_ASSET_LOADERS_]; + int nextCustom; + kon_assetEntry_t *entries; + int count, capacity; + const char *error; +}; + +/*** built in loaders ***/ + +static void *kon_loadBlob_(const uint8_t *data, size_t size, size_t *bytes) { + kon_blob_t *blob = malloc(sizeof(kon_blob_t)); + if (!blob) return NULL; + + blob->data = malloc(size + 1); + if (!blob->data) { + free(blob); + return NULL; + } + + memcpy(blob->data, data, size); + blob->data[size] = 0; + blob->size = size; + *bytes = size; + return blob; +} + +static void kon_unloadBlob_(void *object) { + kon_blob_t *blob = object; + free(blob->data); + free(blob); +} + +#ifdef KON_MODULE_IMAGE + +static void *kon_loadImageAsset_(const uint8_t *data, size_t size, size_t *bytes) { + int width, height, channels; + unsigned char *pixels; + kon_image *image; + + if (size > INT_MAX) return NULL; + + pixels = stbi_load_from_memory(data, (int)size, &width, &height, &channels, 4); + if (!pixels) return NULL; + + image = kon_loadImage(pixels, width, height, konFormatRGBA8); + stbi_image_free(pixels); + + if (image) *bytes = (size_t)width * (size_t)height * 4; + return image; +} + +static void kon_unloadImageAsset_(void *object) { + kon_freeImage(object); +} + +#endif + +#ifdef KON_MODULE_AUDIO + +#define KON_SOUND_MAX_BYTES_ ((uint64_t)1 << 30) + +static void *kon_loadSoundAsset_(const uint8_t *data, size_t size, size_t *bytes) { + ma_decoder_config config = ma_decoder_config_init(ma_format_s16, 0, 0); + ma_decoder decoder; + kon_sound_t *sound; + int16_t *frames = NULL; + uint64_t total = 0, capacity = 0; + uint32_t channels; + + if (ma_decoder_init_memory(data, size, &config, &decoder) != MA_SUCCESS) return NULL; + + channels = (uint32_t)decoder.outputChannels; + if (channels == 0) { + ma_decoder_uninit(&decoder); + return NULL; + } + + /* the length is not always known up front, so read until the decoder runs dry */ + for (;;) { + ma_uint64 got = 0; + + if (total == capacity) { + uint64_t grown = capacity ? capacity * 2 : 16384; + int16_t *more; + + if (grown * channels * sizeof(int16_t) > KON_SOUND_MAX_BYTES_) break; + more = realloc(frames, (size_t)(grown * channels * sizeof(int16_t))); + if (!more) { + free(frames); + ma_decoder_uninit(&decoder); + return NULL; + } + frames = more; + capacity = grown; + } + + if (ma_decoder_read_pcm_frames(&decoder, frames + total * channels, capacity - total, &got) != MA_SUCCESS && got == 0) break; + if (got == 0) break; + total += got; + } + + sound = malloc(sizeof(kon_sound_t)); + if (!sound || total == 0) { + free(sound); + free(frames); + ma_decoder_uninit(&decoder); + return NULL; + } + + sound->frames = frames; + sound->frameCount = total; + sound->channels = channels; + sound->sampleRate = (uint32_t)decoder.outputSampleRate; + *bytes = (size_t)(total * channels * sizeof(int16_t)); + + ma_decoder_uninit(&decoder); + return sound; +} + +static void kon_unloadSoundAsset_(void *object) { + kon_sound_t *sound = object; + free(sound->frames); + free(sound); +} + +#endif + +/*** private helper ***/ + +static int kon_extensionMatches_(const char *name, const char *extensions) { + const char *dot = strrchr(name, '.'); + const char *slash = strrchr(name, '/'); + size_t length; + + if (!dot || (slash && dot < slash) || !extensions) return 0; + length = strlen(dot); + + while (*extensions) { + const char *end; + size_t tokenLength; + + while (*extensions == ' ') extensions++; + end = extensions; + while (*end && *end != ' ') end++; + tokenLength = (size_t)(end - extensions); + + if (tokenLength == length) { + size_t i; + for (i = 0; i < length; i++) { + char c = dot[i]; + if (c >= 'A' && c <= 'Z') c = (char)(c - 'A' + 'a'); + if (c != extensions[i]) break; + } + if (i == length) return 1; + } + extensions = end; + } + return 0; +} + +static int kon_nameIsSafe_(const char *name) { + const char *segment = name; + + if (!name || name[0] == '\0' || name[0] == '/') return 0; + + for (const char *c = name; ; c++) { + if (*c == '\\' || *c == ':') return 0; + if (*c == '/' || *c == '\0') { + if (c - segment == 2 && segment[0] == '.' && segment[1] == '.') return 0; + if (*c == '\0') break; + segment = c + 1; + } + } + return 1; +} + +static int kon_typeForName_(const kon_assets_t *assets, const char *name) { + for (int i = 1; i < KON_ASSET_LOADERS_; i++) { + if (assets->loaderUsed[i] && kon_extensionMatches_(name, assets->loaders[i].extensions)) return i; + } + return KON_ASSET_BLOB; +} + +static kon_assetEntry_t *kon_findEntry_(const kon_assets_t *assets, const char *name) { + for (int i = 0; i < assets->count; i++) { + if (strcmp(assets->entries[i].name, name) == 0) return &assets->entries[i]; + } + return NULL; +} + +static uint8_t *kon_readFile_(kon_assets_t *assets, const char *name, size_t *size, int *loose) { + *loose = 0; + +#ifdef KON_DEVELOPER + if (assets->looseDir) { + size_t length = strlen(assets->looseDir) + strlen(name) + 2; + char *path = malloc(length); + FILE *f; + + if (!path) { + assets->error = "out of memory"; + return NULL; + } + snprintf(path, length, "%s/%s", assets->looseDir, name); + f = fopen(path, "rb"); + free(path); + + if (f) { + long end; + uint8_t *data = NULL; + + if (fseek(f, 0, SEEK_END) == 0 && (end = ftell(f)) >= 0 && fseek(f, 0, SEEK_SET) == 0) { + data = malloc((size_t)end + 1); + if (data && fread(data, 1, (size_t)end, f) == (size_t)end) { + data[end] = 0; + *size = (size_t)end; + *loose = 1; + fclose(f); + return data; + } + free(data); + } + fclose(f); + assets->error = "could not read the loose file"; + return NULL; + } + } +#endif + + if (assets->pack) { + int index = kon_packFind(assets->pack, name); + if (index >= 0) { + uint8_t *data = kon_packRead(assets->pack, index, size); + if (!data) assets->error = kon_packError(); + return data; + } + } + + assets->error = "file not found"; + return NULL; +} + +static void kon_unloadEntry_(kon_assets_t *assets, kon_assetEntry_t *entry) { + if (assets->loaderUsed[entry->type] && assets->loaders[entry->type].unload) { + assets->loaders[entry->type].unload(entry->object); + } + free(entry->name); +} + +/*** assets ***/ + +static void kon_addLoader_(kon_assets_t *assets, int type, const char *name, const char *extensions, + void *(*load)(const uint8_t *, size_t, size_t *), void (*unload)(void *)) { + assets->loaders[type].name = name; + assets->loaders[type].extensions = extensions; + assets->loaders[type].load = load; + assets->loaders[type].unload = unload; + assets->loaderUsed[type] = 1; +} + +kon_assets_t *kon_assetsCreate(kon_pack_t *pack, const char *looseDir) { + kon_assets_t *assets = calloc(1, sizeof(kon_assets_t)); + if (!assets) return NULL; + + assets->pack = pack; + assets->error = "no error"; + assets->nextCustom = KON_ASSET_CUSTOM; + +#ifdef KON_DEVELOPER + if (looseDir) { + assets->looseDir = malloc(strlen(looseDir) + 1); + if (!assets->looseDir) { + free(assets); + return NULL; + } + strcpy(assets->looseDir, looseDir); + } +#else + (void)looseDir; +#endif + + kon_addLoader_(assets, KON_ASSET_BLOB, "blob", NULL, kon_loadBlob_, kon_unloadBlob_); +#ifdef KON_MODULE_IMAGE + kon_addLoader_(assets, KON_ASSET_IMAGE, "image", ".png .jpg .jpeg .bmp .tga .gif", kon_loadImageAsset_, kon_unloadImageAsset_); +#endif +#ifdef KON_MODULE_AUDIO + kon_addLoader_(assets, KON_ASSET_SOUND, "sound", ".wav .mp3 .flac", kon_loadSoundAsset_, kon_unloadSoundAsset_); +#endif + + return assets; +} + +void kon_assetsDestroy(kon_assets_t *assets) { + if (!assets) return; + + for (int i = 0; i < assets->count; i++) kon_unloadEntry_(assets, &assets->entries[i]); + free(assets->entries); + free(assets->looseDir); + free(assets); +} + +int kon_assetsRegister(kon_assets_t *assets, const kon_assetLoader_t *loader) { + int type; + + if (!assets || !loader || !loader->load || !loader->extensions || !loader->name) return -1; + if (assets->nextCustom >= KON_ASSET_LOADERS_) return -1; + + type = assets->nextCustom++; + kon_addLoader_(assets, type, loader->name, loader->extensions, loader->load, loader->unload); + return type; +} + +void *kon_assetGet(kon_assets_t *assets, const char *name) { + return kon_assetGetAs(assets, name, -1); +} + +void *kon_assetGetAs(kon_assets_t *assets, const char *name, int type) { + kon_assetEntry_t *entry; + uint8_t *data; + size_t size = 0, bytes = 0; + int loose, found; + void *object; + + if (!assets) return NULL; + if (!kon_nameIsSafe_(name)) { + assets->error = "bad asset name"; + return NULL; + } + + entry = kon_findEntry_(assets, name); + if (entry) { + if (type >= 0 && entry->type != type) { + assets->error = "asset is a different type"; + return NULL; + } + entry->refs++; + return entry->object; + } + + found = kon_typeForName_(assets, name); + if (type >= 0 && found != type) { + assets->error = "asset is a different type"; + return NULL; + } + + data = kon_readFile_(assets, name, &size, &loose); + if (!data) return NULL; + + object = assets->loaders[found].load(data, size, &bytes); + free(data); + if (!object) { + assets->error = "could not decode the file"; + return NULL; + } + + if (assets->count == assets->capacity) { + int capacity = assets->capacity ? assets->capacity * 2 : 32; + kon_assetEntry_t *grown = realloc(assets->entries, (size_t)capacity * sizeof(kon_assetEntry_t)); + if (!grown) { + assets->loaders[found].unload(object); + assets->error = "out of memory"; + return NULL; + } + assets->entries = grown; + assets->capacity = capacity; + } + + entry = &assets->entries[assets->count]; + entry->name = malloc(strlen(name) + 1); + if (!entry->name) { + assets->loaders[found].unload(object); + assets->error = "out of memory"; + return NULL; + } + strcpy(entry->name, name); + entry->object = object; + entry->type = found; + entry->refs = 1; + entry->bytes = bytes; + entry->loose = loose; + assets->count++; + + return object; +} + +void kon_assetRelease(kon_assets_t *assets, const char *name) { + kon_assetEntry_t *entry; + + if (!assets || !name) return; + entry = kon_findEntry_(assets, name); + if (entry && entry->refs > 0) entry->refs--; +} + +int kon_assetsCollect(kon_assets_t *assets) { + int kept = 0, freed = 0; + + 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++; + } + } + assets->count = kept; + return freed; +} + +int kon_assetsCount(const kon_assets_t *assets) { + return assets ? assets->count : 0; +} + +int kon_assetsInfo(const kon_assets_t *assets, int index, kon_assetInfo_t *info) { + const kon_assetEntry_t *entry; + + if (!assets || !info || index < 0 || index >= assets->count) return 0; + entry = &assets->entries[index]; + + info->name = entry->name; + info->typeName = assets->loaderUsed[entry->type] ? assets->loaders[entry->type].name : "?"; + info->type = entry->type; + info->refs = entry->refs; + info->bytes = entry->bytes; + info->loose = entry->loose; + return 1; +} + +size_t kon_assetsBytes(const kon_assets_t *assets) { + size_t total = 0; + + if (!assets) return 0; + for (int i = 0; i < assets->count; i++) total += assets->entries[i].bytes; + return total; +} + +void kon_assetsDump(const kon_assets_t *assets, FILE *out) { + kon_assetInfo_t info; + + if (!assets || !out) return; + + fprintf(out, "%d assets, %lu bytes\n", assets->count, (unsigned long)kon_assetsBytes(assets)); + for (int i = 0; kon_assetsInfo(assets, i, &info); i++) { + fprintf(out, "%-32s %-8s refs %-3d %10lu bytes%s\n", info.name, info.typeName, info.refs, (unsigned long)info.bytes, info.loose ? " loose" : ""); + } +} + +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 0971166..6dd65eb 100644 --- a/kon/konassets.h +++ b/kon/konassets.h @@ -110,489 +110,4 @@ void kon_assetsDump(const kon_assets_t *assets, FILE *out); const char *kon_assetsError(const kon_assets_t *assets); -/*** implementation ***/ - -#ifdef KONASSETS_IMPLEMENTATION - -#include <stdlib.h> -#include <string.h> -#include <limits.h> - -#ifdef KON_MODULE_IMAGE -#include "stb_image.h" -#endif - -#ifdef KON_MODULE_AUDIO -#include "miniaudio.h" -#endif - -#define KON_ASSET_LOADERS_ 32 - -typedef struct kon_assetEntry_ { - char *name; - void *object; - int type; - int refs; - size_t bytes; - int loose; -} kon_assetEntry_t; - -struct kon_assets { - kon_pack_t *pack; - char *looseDir; - kon_assetLoader_t loaders[KON_ASSET_LOADERS_]; - int loaderUsed[KON_ASSET_LOADERS_]; - int nextCustom; - kon_assetEntry_t *entries; - int count, capacity; - const char *error; -}; - -/*** built in loaders ***/ - -static void *kon_loadBlob_(const uint8_t *data, size_t size, size_t *bytes) { - kon_blob_t *blob = malloc(sizeof(kon_blob_t)); - if (!blob) return NULL; - - blob->data = malloc(size + 1); - if (!blob->data) { - free(blob); - return NULL; - } - - memcpy(blob->data, data, size); - blob->data[size] = 0; - blob->size = size; - *bytes = size; - return blob; -} - -static void kon_unloadBlob_(void *object) { - kon_blob_t *blob = object; - free(blob->data); - free(blob); -} - -#ifdef KON_MODULE_IMAGE - -static void *kon_loadImageAsset_(const uint8_t *data, size_t size, size_t *bytes) { - int width, height, channels; - unsigned char *pixels; - kon_image *image; - - if (size > INT_MAX) return NULL; - - pixels = stbi_load_from_memory(data, (int)size, &width, &height, &channels, 4); - if (!pixels) return NULL; - - image = kon_loadImage(pixels, width, height, konFormatRGBA8); - stbi_image_free(pixels); - - if (image) *bytes = (size_t)width * (size_t)height * 4; - return image; -} - -static void kon_unloadImageAsset_(void *object) { - kon_freeImage(object); -} - -#endif - -#ifdef KON_MODULE_AUDIO - -#define KON_SOUND_MAX_BYTES_ ((uint64_t)1 << 30) - -static void *kon_loadSoundAsset_(const uint8_t *data, size_t size, size_t *bytes) { - ma_decoder_config config = ma_decoder_config_init(ma_format_s16, 0, 0); - ma_decoder decoder; - kon_sound_t *sound; - int16_t *frames = NULL; - uint64_t total = 0, capacity = 0; - uint32_t channels; - - if (ma_decoder_init_memory(data, size, &config, &decoder) != MA_SUCCESS) return NULL; - - channels = (uint32_t)decoder.outputChannels; - if (channels == 0) { - ma_decoder_uninit(&decoder); - return NULL; - } - - /* the length is not always known up front, so read until the decoder runs dry */ - for (;;) { - ma_uint64 got = 0; - - if (total == capacity) { - uint64_t grown = capacity ? capacity * 2 : 16384; - int16_t *more; - - if (grown * channels * sizeof(int16_t) > KON_SOUND_MAX_BYTES_) break; - more = realloc(frames, (size_t)(grown * channels * sizeof(int16_t))); - if (!more) { - free(frames); - ma_decoder_uninit(&decoder); - return NULL; - } - frames = more; - capacity = grown; - } - - if (ma_decoder_read_pcm_frames(&decoder, frames + total * channels, capacity - total, &got) != MA_SUCCESS && got == 0) break; - if (got == 0) break; - total += got; - } - - sound = malloc(sizeof(kon_sound_t)); - if (!sound || total == 0) { - free(sound); - free(frames); - ma_decoder_uninit(&decoder); - return NULL; - } - - sound->frames = frames; - sound->frameCount = total; - sound->channels = channels; - sound->sampleRate = (uint32_t)decoder.outputSampleRate; - *bytes = (size_t)(total * channels * sizeof(int16_t)); - - ma_decoder_uninit(&decoder); - return sound; -} - -static void kon_unloadSoundAsset_(void *object) { - kon_sound_t *sound = object; - free(sound->frames); - free(sound); -} - -#endif - -/*** private helper ***/ - -static int kon_extensionMatches_(const char *name, const char *extensions) { - const char *dot = strrchr(name, '.'); - const char *slash = strrchr(name, '/'); - size_t length; - - if (!dot || (slash && dot < slash) || !extensions) return 0; - length = strlen(dot); - - while (*extensions) { - const char *end; - size_t tokenLength; - - while (*extensions == ' ') extensions++; - end = extensions; - while (*end && *end != ' ') end++; - tokenLength = (size_t)(end - extensions); - - if (tokenLength == length) { - size_t i; - for (i = 0; i < length; i++) { - char c = dot[i]; - if (c >= 'A' && c <= 'Z') c = (char)(c - 'A' + 'a'); - if (c != extensions[i]) break; - } - if (i == length) return 1; - } - extensions = end; - } - return 0; -} - -static int kon_nameIsSafe_(const char *name) { - const char *segment = name; - - if (!name || name[0] == '\0' || name[0] == '/') return 0; - - for (const char *c = name; ; c++) { - if (*c == '\\' || *c == ':') return 0; - if (*c == '/' || *c == '\0') { - if (c - segment == 2 && segment[0] == '.' && segment[1] == '.') return 0; - if (*c == '\0') break; - segment = c + 1; - } - } - return 1; -} - -static int kon_typeForName_(const kon_assets_t *assets, const char *name) { - for (int i = 1; i < KON_ASSET_LOADERS_; i++) { - if (assets->loaderUsed[i] && kon_extensionMatches_(name, assets->loaders[i].extensions)) return i; - } - return KON_ASSET_BLOB; -} - -static kon_assetEntry_t *kon_findEntry_(const kon_assets_t *assets, const char *name) { - for (int i = 0; i < assets->count; i++) { - if (strcmp(assets->entries[i].name, name) == 0) return &assets->entries[i]; - } - return NULL; -} - -static uint8_t *kon_readFile_(kon_assets_t *assets, const char *name, size_t *size, int *loose) { - *loose = 0; - -#ifdef KON_DEVELOPER - if (assets->looseDir) { - size_t length = strlen(assets->looseDir) + strlen(name) + 2; - char *path = malloc(length); - FILE *f; - - if (!path) { - assets->error = "out of memory"; - return NULL; - } - snprintf(path, length, "%s/%s", assets->looseDir, name); - f = fopen(path, "rb"); - free(path); - - if (f) { - long end; - uint8_t *data = NULL; - - if (fseek(f, 0, SEEK_END) == 0 && (end = ftell(f)) >= 0 && fseek(f, 0, SEEK_SET) == 0) { - data = malloc((size_t)end + 1); - if (data && fread(data, 1, (size_t)end, f) == (size_t)end) { - data[end] = 0; - *size = (size_t)end; - *loose = 1; - fclose(f); - return data; - } - free(data); - } - fclose(f); - assets->error = "could not read the loose file"; - return NULL; - } - } -#endif - - if (assets->pack) { - int index = kon_packFind(assets->pack, name); - if (index >= 0) { - uint8_t *data = kon_packRead(assets->pack, index, size); - if (!data) assets->error = kon_packError(); - return data; - } - } - - assets->error = "file not found"; - return NULL; -} - -static void kon_unloadEntry_(kon_assets_t *assets, kon_assetEntry_t *entry) { - if (assets->loaderUsed[entry->type] && assets->loaders[entry->type].unload) { - assets->loaders[entry->type].unload(entry->object); - } - free(entry->name); -} - -/*** assets ***/ - -static void kon_addLoader_(kon_assets_t *assets, int type, const char *name, const char *extensions, - void *(*load)(const uint8_t *, size_t, size_t *), void (*unload)(void *)) { - assets->loaders[type].name = name; - assets->loaders[type].extensions = extensions; - assets->loaders[type].load = load; - assets->loaders[type].unload = unload; - assets->loaderUsed[type] = 1; -} - -kon_assets_t *kon_assetsCreate(kon_pack_t *pack, const char *looseDir) { - kon_assets_t *assets = calloc(1, sizeof(kon_assets_t)); - if (!assets) return NULL; - - assets->pack = pack; - assets->error = "no error"; - assets->nextCustom = KON_ASSET_CUSTOM; - -#ifdef KON_DEVELOPER - if (looseDir) { - assets->looseDir = malloc(strlen(looseDir) + 1); - if (!assets->looseDir) { - free(assets); - return NULL; - } - strcpy(assets->looseDir, looseDir); - } -#else - (void)looseDir; -#endif - - kon_addLoader_(assets, KON_ASSET_BLOB, "blob", NULL, kon_loadBlob_, kon_unloadBlob_); -#ifdef KON_MODULE_IMAGE - kon_addLoader_(assets, KON_ASSET_IMAGE, "image", ".png .jpg .jpeg .bmp .tga .gif", kon_loadImageAsset_, kon_unloadImageAsset_); -#endif -#ifdef KON_MODULE_AUDIO - kon_addLoader_(assets, KON_ASSET_SOUND, "sound", ".wav .mp3 .flac", kon_loadSoundAsset_, kon_unloadSoundAsset_); -#endif - - return assets; -} - -void kon_assetsDestroy(kon_assets_t *assets) { - if (!assets) return; - - for (int i = 0; i < assets->count; i++) kon_unloadEntry_(assets, &assets->entries[i]); - free(assets->entries); - free(assets->looseDir); - free(assets); -} - -int kon_assetsRegister(kon_assets_t *assets, const kon_assetLoader_t *loader) { - int type; - - if (!assets || !loader || !loader->load || !loader->extensions || !loader->name) return -1; - if (assets->nextCustom >= KON_ASSET_LOADERS_) return -1; - - type = assets->nextCustom++; - kon_addLoader_(assets, type, loader->name, loader->extensions, loader->load, loader->unload); - return type; -} - -void *kon_assetGet(kon_assets_t *assets, const char *name) { - return kon_assetGetAs(assets, name, -1); -} - -void *kon_assetGetAs(kon_assets_t *assets, const char *name, int type) { - kon_assetEntry_t *entry; - uint8_t *data; - size_t size = 0, bytes = 0; - int loose, found; - void *object; - - if (!assets) return NULL; - if (!kon_nameIsSafe_(name)) { - assets->error = "bad asset name"; - return NULL; - } - - entry = kon_findEntry_(assets, name); - if (entry) { - if (type >= 0 && entry->type != type) { - assets->error = "asset is a different type"; - return NULL; - } - entry->refs++; - return entry->object; - } - - found = kon_typeForName_(assets, name); - if (type >= 0 && found != type) { - assets->error = "asset is a different type"; - return NULL; - } - - data = kon_readFile_(assets, name, &size, &loose); - if (!data) return NULL; - - object = assets->loaders[found].load(data, size, &bytes); - free(data); - if (!object) { - assets->error = "could not decode the file"; - return NULL; - } - - if (assets->count == assets->capacity) { - int capacity = assets->capacity ? assets->capacity * 2 : 32; - kon_assetEntry_t *grown = realloc(assets->entries, (size_t)capacity * sizeof(kon_assetEntry_t)); - if (!grown) { - assets->loaders[found].unload(object); - assets->error = "out of memory"; - return NULL; - } - assets->entries = grown; - assets->capacity = capacity; - } - - entry = &assets->entries[assets->count]; - entry->name = malloc(strlen(name) + 1); - if (!entry->name) { - assets->loaders[found].unload(object); - assets->error = "out of memory"; - return NULL; - } - strcpy(entry->name, name); - entry->object = object; - entry->type = found; - entry->refs = 1; - entry->bytes = bytes; - entry->loose = loose; - assets->count++; - - return object; -} - -void kon_assetRelease(kon_assets_t *assets, const char *name) { - kon_assetEntry_t *entry; - - if (!assets || !name) return; - entry = kon_findEntry_(assets, name); - if (entry && entry->refs > 0) entry->refs--; -} - -int kon_assetsCollect(kon_assets_t *assets) { - int kept = 0, freed = 0; - - 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++; - } - } - assets->count = kept; - return freed; -} - -int kon_assetsCount(const kon_assets_t *assets) { - return assets ? assets->count : 0; -} - -int kon_assetsInfo(const kon_assets_t *assets, int index, kon_assetInfo_t *info) { - const kon_assetEntry_t *entry; - - if (!assets || !info || index < 0 || index >= assets->count) return 0; - entry = &assets->entries[index]; - - info->name = entry->name; - info->typeName = assets->loaderUsed[entry->type] ? assets->loaders[entry->type].name : "?"; - info->type = entry->type; - info->refs = entry->refs; - info->bytes = entry->bytes; - info->loose = entry->loose; - return 1; -} - -size_t kon_assetsBytes(const kon_assets_t *assets) { - size_t total = 0; - - if (!assets) return 0; - for (int i = 0; i < assets->count; i++) total += assets->entries[i].bytes; - return total; -} - -void kon_assetsDump(const kon_assets_t *assets, FILE *out) { - kon_assetInfo_t info; - - if (!assets || !out) return; - - fprintf(out, "%d assets, %lu bytes\n", assets->count, (unsigned long)kon_assetsBytes(assets)); - for (int i = 0; kon_assetsInfo(assets, i, &info); i++) { - fprintf(out, "%-32s %-8s refs %-3d %10lu bytes%s\n", info.name, info.typeName, info.refs, (unsigned long)info.bytes, info.loose ? " loose" : ""); - } -} - -const char *kon_assetsError(const kon_assets_t *assets) { - return assets ? assets->error : "no assets"; -} - -#endif /* KONASSETS_IMPLEMENTATION */ - #endif diff --git a/kon/konaudio.c b/kon/konaudio.c new file mode 100644 index 0000000..94fb811 --- /dev/null +++ b/kon/konaudio.c @@ -0,0 +1,277 @@ +/* SPDX-License-Identifier: MIT */ +/* + * konaudio.c + * + * Copyright (c) 2026 KonAki + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + + +#include "konaudio.h" + +#include <stdlib.h> +#include <string.h> + +#include "miniaudio.h" + +typedef struct kon_voiceSlot { + const kon_sound_t *sound; + double position; + double step; + float volume; + float pan; + int loop; + int active; + uint32_t generation; +} kon_voiceSlot_t; + +struct kon_audio { + kon_voiceSlot_t voices[KON_AUDIO_VOICES]; + float master; + uint32_t rate; + int hasDevice; + ma_mutex lock; + ma_device device; +}; + +static void kon_audioCallback_(ma_device *device, void *out, const void *in, ma_uint32 frames) +{ + kon_audio_t *audio = device->pUserData; + + (void)in; + kon_audioMix(audio, out, frames); +} + +static kon_audio_t *kon_audioNew_(uint32_t sampleRate) +{ + kon_audio_t *audio = calloc(1, sizeof(kon_audio_t)); + + if (!audio) return NULL; + if (ma_mutex_init(&audio->lock) != MA_SUCCESS) { + free(audio); + return NULL; + } + audio->master = 1.0f; + audio->rate = sampleRate ? sampleRate : 48000; + return audio; +} + +kon_audio_t *kon_audioCreateOffline(uint32_t sampleRate) +{ + return kon_audioNew_(sampleRate); +} + +kon_audio_t *kon_audioCreate(uint32_t sampleRate) +{ + kon_audio_t *audio = kon_audioNew_(sampleRate); + ma_device_config config; + + if (!audio) return NULL; + + config = ma_device_config_init(ma_device_type_playback); + config.playback.format = ma_format_f32; + config.playback.channels = 2; + config.sampleRate = audio->rate; + config.dataCallback = kon_audioCallback_; + config.pUserData = audio; + + if (ma_device_init(NULL, &config, &audio->device) != MA_SUCCESS) { + ma_mutex_uninit(&audio->lock); + free(audio); + return NULL; + } + audio->rate = audio->device.sampleRate; + audio->hasDevice = 1; + + if (ma_device_start(&audio->device) != MA_SUCCESS) { + kon_audioDestroy(audio); + return NULL; + } + return audio; +} + +void kon_audioDestroy(kon_audio_t *audio) +{ + if (!audio) return; + if (audio->hasDevice) ma_device_uninit(&audio->device); + ma_mutex_uninit(&audio->lock); + free(audio); +} + +uint32_t kon_audioRate(const kon_audio_t *audio) +{ + return audio->rate; +} + +kon_voice_t kon_audioPlay(kon_audio_t *audio, const kon_sound_t *sound, float volume, float pan, int loop) +{ + kon_voice_t handle = 0; + int i; + + if (!audio || !sound || !sound->frames || sound->frameCount == 0 || sound->channels == 0 || sound->sampleRate == 0) return 0; + + if (pan < -1.0f) pan = -1.0f; + if (pan > 1.0f) pan = 1.0f; + + ma_mutex_lock(&audio->lock); + for (i = 0; i < KON_AUDIO_VOICES; i++) { + kon_voiceSlot_t *v = &audio->voices[i]; + + if (v->active) continue; + v->generation++; + if (v->generation == 0) v->generation = 1; + v->sound = sound; + v->position = 0.0; + v->step = (double)sound->sampleRate / (double)audio->rate; + v->volume = volume; + v->pan = pan; + v->loop = loop; + v->active = 1; + handle = (v->generation << 8) | (uint32_t)(i + 1); + break; + } + ma_mutex_unlock(&audio->lock); + return handle; +} + +static kon_voiceSlot_t *kon_audioFind_(kon_audio_t *audio, kon_voice_t handle) +{ + uint32_t index = handle & 0xFF; + kon_voiceSlot_t *v; + + if (index == 0 || index > KON_AUDIO_VOICES) return NULL; + v = &audio->voices[index - 1]; + if (!v->active || v->generation != (handle >> 8)) return NULL; + return v; +} + +void kon_audioStop(kon_audio_t *audio, kon_voice_t voice) +{ + kon_voiceSlot_t *v; + + ma_mutex_lock(&audio->lock); + v = kon_audioFind_(audio, voice); + if (v) v->active = 0; + ma_mutex_unlock(&audio->lock); +} + +void kon_audioStopSound(kon_audio_t *audio, const kon_sound_t *sound) +{ + int i; + + ma_mutex_lock(&audio->lock); + for (i = 0; i < KON_AUDIO_VOICES; i++) { + if (audio->voices[i].active && audio->voices[i].sound == sound) audio->voices[i].active = 0; + } + ma_mutex_unlock(&audio->lock); +} + +void kon_audioStopAll(kon_audio_t *audio) +{ + int i; + + ma_mutex_lock(&audio->lock); + for (i = 0; i < KON_AUDIO_VOICES; i++) audio->voices[i].active = 0; + ma_mutex_unlock(&audio->lock); +} + +int kon_audioIsPlaying(kon_audio_t *audio, kon_voice_t voice) +{ + int playing; + + ma_mutex_lock(&audio->lock); + playing = kon_audioFind_(audio, voice) != NULL; + ma_mutex_unlock(&audio->lock); + return playing; +} + +int kon_audioVoiceCount(kon_audio_t *audio) +{ + int i, count = 0; + + ma_mutex_lock(&audio->lock); + for (i = 0; i < KON_AUDIO_VOICES; i++) count += audio->voices[i].active; + ma_mutex_unlock(&audio->lock); + return count; +} + +void kon_audioSetVolume(kon_audio_t *audio, float volume) +{ + if (volume < 0.0f) volume = 0.0f; + ma_mutex_lock(&audio->lock); + audio->master = volume; + ma_mutex_unlock(&audio->lock); +} + +void kon_audioMix(kon_audio_t *audio, float *out, uint32_t frames) +{ + uint32_t f; + int i; + + memset(out, 0, (size_t)frames * 2 * sizeof(float)); + + ma_mutex_lock(&audio->lock); + for (i = 0; i < KON_AUDIO_VOICES; i++) { + kon_voiceSlot_t *v = &audio->voices[i]; + const kon_sound_t *s = v->sound; + float gainL, gainR; + + if (!v->active) continue; + + gainL = v->volume * (v->pan > 0.0f ? 1.0f - v->pan : 1.0f); + gainR = v->volume * (v->pan < 0.0f ? 1.0f + v->pan : 1.0f); + + for (f = 0; f < frames; f++) { + uint64_t a = (uint64_t)v->position, b; + float t, l0, r0, l1, r1; + const int16_t *p; + + if (a >= s->frameCount) { + if (!v->loop) { + v->active = 0; + break; + } + v->position -= (double)s->frameCount; + a = (uint64_t)v->position; + } + b = a + 1; + if (b >= s->frameCount) b = v->loop ? 0 : a; + t = (float)(v->position - (double)a); + + p = s->frames + a * s->channels; + l0 = p[0] / 32768.0f; + r0 = (s->channels > 1 ? p[1] : p[0]) / 32768.0f; + p = s->frames + b * s->channels; + l1 = p[0] / 32768.0f; + r1 = (s->channels > 1 ? p[1] : p[0]) / 32768.0f; + + out[f * 2] += (l0 + (l1 - l0) * t) * gainL; + out[f * 2 + 1] += (r0 + (r1 - r0) * t) * gainR; + v->position += v->step; + } + } + + for (f = 0; f < frames * 2; f++) { + float x = out[f] * audio->master; + + out[f] = x > 1.0f ? 1.0f : (x < -1.0f ? -1.0f : x); + } + ma_mutex_unlock(&audio->lock); +} diff --git a/kon/konaudio.h b/kon/konaudio.h index fcb675e..f40e190 100644 --- a/kon/konaudio.h +++ b/kon/konaudio.h @@ -62,259 +62,3 @@ void kon_audioSetVolume(kon_audio_t *audio, float volume); void kon_audioMix(kon_audio_t *audio, float *out, uint32_t frames); #endif - -/*** implementation ***/ - -#ifdef KONAUDIO_IMPLEMENTATION - -#include <stdlib.h> -#include <string.h> - -#include "miniaudio.h" - -typedef struct kon_voiceSlot { - const kon_sound_t *sound; - double position; - double step; - float volume; - float pan; - int loop; - int active; - uint32_t generation; -} kon_voiceSlot_t; - -struct kon_audio { - kon_voiceSlot_t voices[KON_AUDIO_VOICES]; - float master; - uint32_t rate; - int hasDevice; - ma_mutex lock; - ma_device device; -}; - -static void kon_audioCallback_(ma_device *device, void *out, const void *in, ma_uint32 frames) -{ - kon_audio_t *audio = device->pUserData; - - (void)in; - kon_audioMix(audio, out, frames); -} - -static kon_audio_t *kon_audioNew_(uint32_t sampleRate) -{ - kon_audio_t *audio = calloc(1, sizeof(kon_audio_t)); - - if (!audio) return NULL; - if (ma_mutex_init(&audio->lock) != MA_SUCCESS) { - free(audio); - return NULL; - } - audio->master = 1.0f; - audio->rate = sampleRate ? sampleRate : 48000; - return audio; -} - -kon_audio_t *kon_audioCreateOffline(uint32_t sampleRate) -{ - return kon_audioNew_(sampleRate); -} - -kon_audio_t *kon_audioCreate(uint32_t sampleRate) -{ - kon_audio_t *audio = kon_audioNew_(sampleRate); - ma_device_config config; - - if (!audio) return NULL; - - config = ma_device_config_init(ma_device_type_playback); - config.playback.format = ma_format_f32; - config.playback.channels = 2; - config.sampleRate = audio->rate; - config.dataCallback = kon_audioCallback_; - config.pUserData = audio; - - if (ma_device_init(NULL, &config, &audio->device) != MA_SUCCESS) { - ma_mutex_uninit(&audio->lock); - free(audio); - return NULL; - } - audio->rate = audio->device.sampleRate; - audio->hasDevice = 1; - - if (ma_device_start(&audio->device) != MA_SUCCESS) { - kon_audioDestroy(audio); - return NULL; - } - return audio; -} - -void kon_audioDestroy(kon_audio_t *audio) -{ - if (!audio) return; - if (audio->hasDevice) ma_device_uninit(&audio->device); - ma_mutex_uninit(&audio->lock); - free(audio); -} - -uint32_t kon_audioRate(const kon_audio_t *audio) -{ - return audio->rate; -} - -kon_voice_t kon_audioPlay(kon_audio_t *audio, const kon_sound_t *sound, float volume, float pan, int loop) -{ - kon_voice_t handle = 0; - int i; - - if (!audio || !sound || !sound->frames || sound->frameCount == 0 || sound->channels == 0 || sound->sampleRate == 0) return 0; - - if (pan < -1.0f) pan = -1.0f; - if (pan > 1.0f) pan = 1.0f; - - ma_mutex_lock(&audio->lock); - for (i = 0; i < KON_AUDIO_VOICES; i++) { - kon_voiceSlot_t *v = &audio->voices[i]; - - if (v->active) continue; - v->generation++; - if (v->generation == 0) v->generation = 1; - v->sound = sound; - v->position = 0.0; - v->step = (double)sound->sampleRate / (double)audio->rate; - v->volume = volume; - v->pan = pan; - v->loop = loop; - v->active = 1; - handle = (v->generation << 8) | (uint32_t)(i + 1); - break; - } - ma_mutex_unlock(&audio->lock); - return handle; -} - -static kon_voiceSlot_t *kon_audioFind_(kon_audio_t *audio, kon_voice_t handle) -{ - uint32_t index = handle & 0xFF; - kon_voiceSlot_t *v; - - if (index == 0 || index > KON_AUDIO_VOICES) return NULL; - v = &audio->voices[index - 1]; - if (!v->active || v->generation != (handle >> 8)) return NULL; - return v; -} - -void kon_audioStop(kon_audio_t *audio, kon_voice_t voice) -{ - kon_voiceSlot_t *v; - - ma_mutex_lock(&audio->lock); - v = kon_audioFind_(audio, voice); - if (v) v->active = 0; - ma_mutex_unlock(&audio->lock); -} - -void kon_audioStopSound(kon_audio_t *audio, const kon_sound_t *sound) -{ - int i; - - ma_mutex_lock(&audio->lock); - for (i = 0; i < KON_AUDIO_VOICES; i++) { - if (audio->voices[i].active && audio->voices[i].sound == sound) audio->voices[i].active = 0; - } - ma_mutex_unlock(&audio->lock); -} - -void kon_audioStopAll(kon_audio_t *audio) -{ - int i; - - ma_mutex_lock(&audio->lock); - for (i = 0; i < KON_AUDIO_VOICES; i++) audio->voices[i].active = 0; - ma_mutex_unlock(&audio->lock); -} - -int kon_audioIsPlaying(kon_audio_t *audio, kon_voice_t voice) -{ - int playing; - - ma_mutex_lock(&audio->lock); - playing = kon_audioFind_(audio, voice) != NULL; - ma_mutex_unlock(&audio->lock); - return playing; -} - -int kon_audioVoiceCount(kon_audio_t *audio) -{ - int i, count = 0; - - ma_mutex_lock(&audio->lock); - for (i = 0; i < KON_AUDIO_VOICES; i++) count += audio->voices[i].active; - ma_mutex_unlock(&audio->lock); - return count; -} - -void kon_audioSetVolume(kon_audio_t *audio, float volume) -{ - if (volume < 0.0f) volume = 0.0f; - ma_mutex_lock(&audio->lock); - audio->master = volume; - ma_mutex_unlock(&audio->lock); -} - -void kon_audioMix(kon_audio_t *audio, float *out, uint32_t frames) -{ - uint32_t f; - int i; - - memset(out, 0, (size_t)frames * 2 * sizeof(float)); - - ma_mutex_lock(&audio->lock); - for (i = 0; i < KON_AUDIO_VOICES; i++) { - kon_voiceSlot_t *v = &audio->voices[i]; - const kon_sound_t *s = v->sound; - float gainL, gainR; - - if (!v->active) continue; - - gainL = v->volume * (v->pan > 0.0f ? 1.0f - v->pan : 1.0f); - gainR = v->volume * (v->pan < 0.0f ? 1.0f + v->pan : 1.0f); - - for (f = 0; f < frames; f++) { - uint64_t a = (uint64_t)v->position, b; - float t, l0, r0, l1, r1; - const int16_t *p; - - if (a >= s->frameCount) { - if (!v->loop) { - v->active = 0; - break; - } - v->position -= (double)s->frameCount; - a = (uint64_t)v->position; - } - b = a + 1; - if (b >= s->frameCount) b = v->loop ? 0 : a; - t = (float)(v->position - (double)a); - - p = s->frames + a * s->channels; - l0 = p[0] / 32768.0f; - r0 = (s->channels > 1 ? p[1] : p[0]) / 32768.0f; - p = s->frames + b * s->channels; - l1 = p[0] / 32768.0f; - r1 = (s->channels > 1 ? p[1] : p[0]) / 32768.0f; - - out[f * 2] += (l0 + (l1 - l0) * t) * gainL; - out[f * 2 + 1] += (r0 + (r1 - r0) * t) * gainR; - v->position += v->step; - } - } - - for (f = 0; f < frames * 2; f++) { - float x = out[f] * audio->master; - - out[f] = x > 1.0f ? 1.0f : (x < -1.0f ? -1.0f : x); - } - ma_mutex_unlock(&audio->lock); -} - -#endif diff --git a/kon/kondebug.c b/kon/kondebug.c new file mode 100644 index 0000000..a905adc --- /dev/null +++ b/kon/kondebug.c @@ -0,0 +1,739 @@ +/* SPDX-License-Identifier: MIT */ +/* + * kondebug.c + * + * Copyright (c) 2026 KonAki + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + + +#include "kondebug.h" + +#ifdef KON_DEVELOPER + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <stdarg.h> + +#include "kontime.h" + +#define KON_DEBUG_LOGLINES 256 +#define KON_DEBUG_LINELEN 160 +#define KON_DEBUG_INPUT 256 +#define KON_DEBUG_HISTORY 16 +#define KON_DEBUG_COMMANDS 64 +#define KON_DEBUG_VARS 64 +#define KON_DEBUG_ARGS 16 +#define KON_DEBUG_GRAPH 120 +#define KON_DEBUG_TYPES 8 + +typedef struct kon_debugCmd { + const char *name; + const char *help; + kon_debugCommandFn fn; + void *user; +} kon_debugCmd_t; + +typedef struct kon_debugVarEntry { + const char *name; + kon_debugVarType_t type; + void *value; + int watched; +} kon_debugVarEntry_t; + +struct kon_debug { + kon_font_t *font; + kon_assets_t *assets; + int lineH, ascent; + float baseSize, size; + int ctrl; + + int overlayKey, consoleKey; + int overlay; + int open; + float slide; + int swallowTick; + int quit; + + char log[KON_DEBUG_LOGLINES][KON_DEBUG_LINELEN]; + int logCount, logHead, scroll; + + char input[KON_DEBUG_INPUT]; + int length, cursor; + char history[KON_DEBUG_HISTORY][KON_DEBUG_INPUT]; + int historyCount, historyPos; + + kon_debugCmd_t commands[KON_DEBUG_COMMANDS]; + int commandCount; + kon_debugVarEntry_t vars[KON_DEBUG_VARS]; + int varCount; + + kon_time last; + double frameDt; + double accumTime; + int accumFrames; + float fps; + float graph[KON_DEBUG_GRAPH]; + int graphHead; +}; + +/*** private helpers ***/ + +static void kon_debugMetrics_(kon_debug_t *d) +{ + int h = 0; + + kon_measureText(d->font, "A", NULL, &h); + d->ascent = h * 4 / 5; + d->lineH = h + 2; +} + +static void kon_debugResize_(kon_debug_t *d, float size) +{ + if (size < 8.0f) size = 8.0f; + if (size > 64.0f) size = 64.0f; + if (size == d->size) return; + if (kon_fontResize(d->font, size)) { + d->size = size; + kon_debugMetrics_(d); + } +} + +static void kon_debugText_(const kon_debug_t *d, kon_framebuffer_t *fb, int x, int y, const char *s, uint32_t color) +{ + kon_drawText(fb, d->font, x, y + d->ascent, s, color); +} + +static void kon_debugPushLine_(kon_debug_t *d, const char *line) +{ + int slot = (d->logHead + d->logCount) % KON_DEBUG_LOGLINES; + + if (d->logCount == KON_DEBUG_LOGLINES) { + slot = d->logHead; + d->logHead = (d->logHead + 1) % KON_DEBUG_LOGLINES; + } else { + d->logCount++; + } + snprintf(d->log[slot], KON_DEBUG_LINELEN, "%s", line); + if (d->scroll > 0 && d->scroll < d->logCount) d->scroll++; +} + +static const char *kon_debugVarText_(const kon_debugVarEntry_t *v, char *buffer, size_t size) +{ + switch (v->type) { + case KON_VAR_BOOL: snprintf(buffer, size, "%s", *(int *)v->value ? "true" : "false"); break; + case KON_VAR_INT: snprintf(buffer, size, "%d", *(int *)v->value); break; + default: snprintf(buffer, size, "%g", (double)*(float *)v->value); break; + } + return buffer; +} + +static int kon_debugTokenize_(char *line, char **argv, int max) +{ + int argc = 0; + + while (*line && argc < max) { + while (*line == ' ' || *line == '\t') line++; + if (!*line) break; + if (*line == '"') { + argv[argc++] = ++line; + while (*line && *line != '"') line++; + } else { + argv[argc++] = line; + while (*line && *line != ' ' && *line != '\t') line++; + } + if (*line) *line++ = 0; + } + return argc; +} + +static void kon_debugSetVar_(kon_debug_t *d, kon_debugVarEntry_t *v, const char *text) +{ + char out[64]; + + switch (v->type) { + case KON_VAR_BOOL: + if (!strcmp(text, "1") || !strcmp(text, "true") || !strcmp(text, "on")) *(int *)v->value = 1; + else if (!strcmp(text, "0") || !strcmp(text, "false") || !strcmp(text, "off")) *(int *)v->value = 0; + else { + kon_debugLog(d, "%s expects 0 or 1", v->name); + return; + } + break; + case KON_VAR_INT: *(int *)v->value = atoi(text); break; + default: *(float *)v->value = (float)atof(text); break; + } + kon_debugLog(d, "%s = %s", v->name, kon_debugVarText_(v, out, sizeof(out))); +} + +static void kon_debugExecute_(kon_debug_t *d, const char *line) +{ + char copy[KON_DEBUG_INPUT], *argv[KON_DEBUG_ARGS]; + int argc, i; + + snprintf(copy, sizeof(copy), "%s", line); + argc = kon_debugTokenize_(copy, argv, KON_DEBUG_ARGS); + if (argc == 0) return; + + for (i = 0; i < d->commandCount; i++) { + if (!strcmp(argv[0], d->commands[i].name)) { + d->commands[i].fn(d, d->commands[i].user, argc, argv); + return; + } + } + for (i = 0; i < d->varCount; i++) { + kon_debugVarEntry_t *v = &d->vars[i]; + char out[64]; + + if (strcmp(argv[0], v->name)) continue; + if (argc > 1) kon_debugSetVar_(d, v, argv[1]); + else kon_debugLog(d, "%s = %s", v->name, kon_debugVarText_(v, out, sizeof(out))); + return; + } + kon_debugLog(d, "unknown command '%s', try help", argv[0]); +} + +static void kon_debugComplete_(kon_debug_t *d) +{ + const char *matches[KON_DEBUG_COMMANDS + KON_DEBUG_VARS]; + int count = 0, i, common; + size_t prefix = (size_t)d->cursor; + + for (i = 0; i < d->cursor; i++) if (d->input[i] == ' ') return; + if (prefix == 0) return; + + for (i = 0; i < d->commandCount; i++) if (!strncmp(d->commands[i].name, d->input, prefix)) matches[count++] = d->commands[i].name; + for (i = 0; i < d->varCount; i++) if (!strncmp(d->vars[i].name, d->input, prefix)) matches[count++] = d->vars[i].name; + if (count == 0) return; + + common = (int)strlen(matches[0]); + for (i = 1; i < count; i++) { + int n = 0; + + while (n < common && matches[i][n] == matches[0][n]) n++; + common = n; + } + + if (count > 1) { + char line[KON_DEBUG_LINELEN] = ""; + + for (i = 0; i < count; i++) { + if (strlen(line) + strlen(matches[i]) + 2 >= sizeof(line)) break; + strcat(line, matches[i]); + strcat(line, " "); + } + kon_debugLog(d, "%s", line); + } + + { + char rest[KON_DEBUG_INPUT]; + int add = common - d->cursor + (count == 1 ? 1 : 0); + + snprintf(rest, sizeof(rest), "%s", d->input + d->cursor); + if (d->length + add >= KON_DEBUG_INPUT) return; + memcpy(d->input, matches[0], (size_t)common); + if (count == 1) d->input[common] = ' '; + memcpy(d->input + common + (count == 1 ? 1 : 0), rest, strlen(rest) + 1); + d->length = (int)strlen(d->input); + d->cursor = common + (count == 1 ? 1 : 0); + } +} + +static void kon_debugInsert_(kon_debug_t *d, char c) +{ + if (d->length + 1 >= KON_DEBUG_INPUT) return; + memmove(d->input + d->cursor + 1, d->input + d->cursor, (size_t)(d->length - d->cursor) + 1); + d->input[d->cursor++] = c; + d->length++; +} + +static void kon_debugSetInput_(kon_debug_t *d, const char *text) +{ + snprintf(d->input, sizeof(d->input), "%s", text); + d->length = (int)strlen(d->input); + d->cursor = d->length; +} + +static void kon_debugSubmit_(kon_debug_t *d) +{ + char line[KON_DEBUG_INPUT + 4]; + + if (d->length == 0) return; + snprintf(line, sizeof(line), "> %s", d->input); + kon_debugPushLine_(d, line); + + if (d->historyCount == 0 || strcmp(d->history[d->historyCount - 1], d->input)) { + if (d->historyCount == KON_DEBUG_HISTORY) { + memmove(d->history[0], d->history[1], sizeof(d->history[0]) * (KON_DEBUG_HISTORY - 1)); + d->historyCount--; + } + snprintf(d->history[d->historyCount++], KON_DEBUG_INPUT, "%s", d->input); + } + d->historyPos = d->historyCount; + + { + char copy[KON_DEBUG_INPUT]; + + snprintf(copy, sizeof(copy), "%s", d->input); + d->input[0] = 0; + d->length = d->cursor = 0; + d->scroll = 0; + kon_debugExecute_(d, copy); + } +} + +/*** builtin commands ***/ + +static void kon_debugCmdHelp_(kon_debug_t *d, void *user, int argc, char **argv) +{ + int i; + + (void)user; + if (argc > 1) { + for (i = 0; i < d->commandCount; i++) { + if (!strcmp(argv[1], d->commands[i].name)) { + kon_debugLog(d, "%s: %s", d->commands[i].name, d->commands[i].help); + return; + } + } + kon_debugLog(d, "no help for '%s'", argv[1]); + return; + } + kon_debugLog(d, "commands:"); + for (i = 0; i < d->commandCount; i++) kon_debugLog(d, " %-10s %s", d->commands[i].name, d->commands[i].help); + kon_debugLog(d, "variables (type the name to read, name value to set):"); + for (i = 0; i < d->varCount; i++) kon_debugLog(d, " %s", d->vars[i].name); +} + +static void kon_debugCmdClear_(kon_debug_t *d, void *user, int argc, char **argv) +{ + (void)user; (void)argc; (void)argv; + d->logCount = d->logHead = d->scroll = 0; +} + +static void kon_debugCmdEcho_(kon_debug_t *d, void *user, int argc, char **argv) +{ + char line[KON_DEBUG_LINELEN] = ""; + int i; + + (void)user; + for (i = 1; i < argc; i++) { + if (strlen(line) + strlen(argv[i]) + 2 >= sizeof(line)) break; + strcat(line, argv[i]); + strcat(line, " "); + } + kon_debugLog(d, "%s", line); +} + +static void kon_debugCmdFps_(kon_debug_t *d, void *user, int argc, char **argv) +{ + (void)user; + d->overlay = argc > 1 ? atoi(argv[1]) & 3 : (d->overlay + 1) & 3; +} + +static void kon_debugCmdWatch_(kon_debug_t *d, void *user, int argc, char **argv) +{ + int i; + + (void)user; + if (argc < 2) { + kon_debugLog(d, "usage: watch <variable>"); + return; + } + for (i = 0; i < d->varCount; i++) { + if (!strcmp(argv[1], d->vars[i].name)) { + d->vars[i].watched = !d->vars[i].watched; + if (d->overlay == 0) d->overlay = 1; + return; + } + } + kon_debugLog(d, "no variable '%s'", argv[1]); +} + +static void kon_debugCmdQuit_(kon_debug_t *d, void *user, int argc, char **argv) +{ + (void)user; (void)argc; (void)argv; + d->quit = 1; +} + +static void kon_debugCmdFontSize_(kon_debug_t *d, void *user, int argc, char **argv) +{ + (void)user; + if (argc > 1) kon_debugResize_(d, (float)atof(argv[1])); + kon_debugLog(d, "fontsize = %g", (double)d->size); +} + +static void kon_debugCmdAssets_(kon_debug_t *d, void *user, int argc, char **argv) +{ + int i, count; + kon_assetInfo_t info; + + (void)user; (void)argc; (void)argv; + if (!d->assets) { + kon_debugLog(d, "no asset manager attached"); + return; + } + count = kon_assetsCount(d->assets); + for (i = 0; i < count; i++) { + if (!kon_assetsInfo(d->assets, i, &info)) continue; + kon_debugLog(d, "%-7s %3d ref %8lu b %s%s", info.typeName, info.refs, (unsigned long)info.bytes, info.name, info.loose ? " (loose)" : ""); + } + kon_debugLog(d, "%d assets, %lu bytes", count, (unsigned long)kon_assetsBytes(d->assets)); +} + +/*** debug ***/ + +kon_debug_t *kon_debugCreate(kon_font_t *font) +{ + kon_debug_t *d; + + if (!font) return NULL; + d = calloc(1, sizeof(kon_debug_t)); + if (!d) return NULL; + + d->font = font; + d->baseSize = d->size = kon_fontPixelHeight(font); + kon_debugMetrics_(d); + d->overlayKey = KON_KEY_F1; + d->consoleKey = KON_KEY_GRAVE; + d->last = kon_getTime(); + + kon_debugCommand(d, "help", "list commands, or help <command>", kon_debugCmdHelp_, NULL); + kon_debugCommand(d, "clear", "clear the console", kon_debugCmdClear_, NULL); + kon_debugCommand(d, "echo", "print text", kon_debugCmdEcho_, NULL); + kon_debugCommand(d, "fps", "overlay mode 0-3, or cycle", kon_debugCmdFps_, NULL); + kon_debugCommand(d, "watch", "show a variable in the overlay", kon_debugCmdWatch_, NULL); + kon_debugCommand(d, "assets", "list loaded assets", kon_debugCmdAssets_, NULL); + kon_debugCommand(d, "fontsize", "text size in pixels, ctrl + and ctrl - too", kon_debugCmdFontSize_, NULL); + kon_debugCommand(d, "quit", "close the game", kon_debugCmdQuit_, NULL); + kon_debugCommand(d, "exit", "close the game", kon_debugCmdQuit_, NULL); + kon_debugLog(d, "type help for commands, tab completes"); + return d; +} + +void kon_debugDestroy(kon_debug_t *d) +{ + free(d); +} + +void kon_debugSetKeys(kon_debug_t *d, int overlayKey, int consoleKey) +{ + if (!d) return; + d->overlayKey = overlayKey; + d->consoleKey = consoleKey; +} + +void kon_debugSetAssets(kon_debug_t *d, kon_assets_t *assets) +{ + if (!d) return; + d->assets = assets; +} + +int kon_debugCommand(kon_debug_t *d, const char *name, const char *help, kon_debugCommandFn fn, void *user) +{ + if (!d) return 0; + kon_debugCmd_t *c; + + if (d->commandCount == KON_DEBUG_COMMANDS) return 0; + c = &d->commands[d->commandCount++]; + c->name = name; + c->help = help; + c->fn = fn; + c->user = user; + return 1; +} + +int kon_debugVar(kon_debug_t *d, const char *name, kon_debugVarType_t type, void *value) +{ + if (!d) return 0; + kon_debugVarEntry_t *v; + + if (d->varCount == KON_DEBUG_VARS) return 0; + v = &d->vars[d->varCount++]; + v->name = name; + v->type = type; + v->value = value; + v->watched = 0; + return 1; +} + +void kon_debugLog(kon_debug_t *d, const char *format, ...) +{ + if (!d) return; + char line[KON_DEBUG_LINELEN]; + va_list args; + + va_start(args, format); + vsnprintf(line, sizeof(line), format, args); + va_end(args); + kon_debugPushLine_(d, line); +} + +int kon_debugConsoleOpen(const kon_debug_t *d) +{ + if (!d) return 0; + return d->open; +} + +int kon_debugQuitRequested(const kon_debug_t *d) +{ + return d ? d->quit : 0; +} + +float kon_debugFps(const kon_debug_t *d) +{ + if (!d) return 0.0f; + return d->fps; +} + +int kon_debugFeedEvent(kon_debug_t *d, const kon_event_t *e) +{ + if (!d) return 0; + + if (e->key == KON_KEY_LEFT_CTRL || e->key == KON_KEY_RIGHT_CTRL) { + if (e->type == KON_EVENT_KEY_DOWN) d->ctrl = 1; + if (e->type == KON_EVENT_KEY_UP) d->ctrl = 0; + } + + if (e->type == KON_EVENT_KEY_DOWN) { + if (d->ctrl && (d->open || d->overlay)) { + if (e->key == KON_KEY_EQUAL) { kon_debugResize_(d, d->size + 2.0f); return 1; } + if (e->key == KON_KEY_MINUS) { kon_debugResize_(d, d->size - 2.0f); return 1; } + if (e->key == KON_KEY_0) { kon_debugResize_(d, d->baseSize); return 1; } + } + if (e->key == d->consoleKey) { + d->open = !d->open; + d->swallowTick = 1; + return 1; + } + d->swallowTick = 0; + if (e->key == d->overlayKey) { + d->overlay = (d->overlay + 1) & 3; + return 1; + } + } + if (!d->open) return 0; + + if (e->type == KON_EVENT_TEXT) { + if (d->ctrl) return 1; + if (d->swallowTick && e->codepoint == '`') { + d->swallowTick = 0; + return 1; + } + if (e->codepoint >= 32 && e->codepoint <= 126) kon_debugInsert_(d, (char)e->codepoint); + return 1; + } + if (e->type == KON_EVENT_KEY_UP) return 1; + if (e->type != KON_EVENT_KEY_DOWN) return 0; + + switch (e->key) { + case KON_KEY_ENTER: kon_debugSubmit_(d); break; + case KON_KEY_ESCAPE: d->open = 0; break; + case KON_KEY_TAB: kon_debugComplete_(d); break; + case KON_KEY_LEFT: if (d->cursor > 0) d->cursor--; break; + case KON_KEY_RIGHT: if (d->cursor < d->length) d->cursor++; break; + case KON_KEY_BACKSPACE: + if (d->cursor > 0) { + memmove(d->input + d->cursor - 1, d->input + d->cursor, (size_t)(d->length - d->cursor) + 1); + d->cursor--; + d->length--; + } + break; + case KON_KEY_DELETE: + if (d->cursor < d->length) { + memmove(d->input + d->cursor, d->input + d->cursor + 1, (size_t)(d->length - d->cursor)); + d->length--; + } + break; + case KON_KEY_UP: + if (d->historyPos > 0) kon_debugSetInput_(d, d->history[--d->historyPos]); + break; + case KON_KEY_DOWN: + if (d->historyPos < d->historyCount - 1) kon_debugSetInput_(d, d->history[++d->historyPos]); + else { + d->historyPos = d->historyCount; + kon_debugSetInput_(d, ""); + } + break; + case KON_KEY_PAGE_UP: d->scroll += 8; break; + case KON_KEY_PAGE_DOWN: d->scroll -= 8; break; + default: break; + } + if (d->scroll < 0) d->scroll = 0; + if (d->scroll > d->logCount - 1) d->scroll = d->logCount > 0 ? d->logCount - 1 : 0; + return 1; +} + +void kon_debugFrame(kon_debug_t *d) +{ + if (!d) return; + kon_time now = kon_getTime(); + double dt = now - d->last; + + d->last = now; + d->frameDt = dt; + d->accumTime += dt; + d->accumFrames++; + if (d->accumTime >= 0.5) { + d->fps = (float)((double)d->accumFrames / d->accumTime); + d->accumTime = 0.0; + d->accumFrames = 0; + } + d->graph[d->graphHead] = (float)(dt * 1000.0); + d->graphHead = (d->graphHead + 1) % KON_DEBUG_GRAPH; +} + +static void kon_debugBox_(const kon_debug_t *d, kon_framebuffer_t *fb, int x, int y, const char *s, uint32_t color) +{ + int w = 0; + + kon_measureText(d->font, s, &w, NULL); + kon_fillRectangle(fb, x - 2, y - 1, w + 4, d->lineH, 0xB0000000); + kon_debugText_(d, fb, x, y, s, color); +} + +static void kon_debugDrawOverlay_(kon_debug_t *d, kon_framebuffer_t *fb) +{ + char line[128]; + int x = 4, y = 4, i, rows; + uint32_t white = 0xFFFFFFFF, dim = 0xFFB0B0C0; + float worst = 0.0f; + + snprintf(line, sizeof(line), "%.0f fps %.2f ms", (double)d->fps, d->frameDt * 1000.0); + kon_debugBox_(d, fb, x, y, line, white); + y += d->lineH + 3; + + if (d->overlay >= 1) { + kon_fillRectangle(fb, x - 2, y - 2, KON_DEBUG_GRAPH + 4, 34, 0xB0000000); + for (i = 0; i < KON_DEBUG_GRAPH; i++) { + float ms = d->graph[(d->graphHead + i) % KON_DEBUG_GRAPH]; + int h = (int)(ms * 30.0f / 33.3f); + uint32_t color = ms < 17.5f ? 0xFF50D060 : (ms < 34.0f ? 0xFFE0C040 : 0xFFE05050); + + if (ms > worst) worst = ms; + if (h > 30) h = 30; + if (h < 1) h = 1; + kon_fillRectangle(fb, x + i, y + 30 - h, 1, h, color); + } + y += 36; + } + + for (i = 0; i < d->varCount; i++) { + char value[64]; + + if (!d->vars[i].watched) continue; + snprintf(line, sizeof(line), "%s = %s", d->vars[i].name, kon_debugVarText_(&d->vars[i], value, sizeof(value))); + kon_debugBox_(d, fb, x, y, line, dim); + y += d->lineH; + } + + if (d->overlay >= 2 && d->assets) { + const char *names[KON_DEBUG_TYPES]; + int counts[KON_DEBUG_TYPES], types = 0, total = kon_assetsCount(d->assets); + size_t bytes[KON_DEBUG_TYPES]; + kon_assetInfo_t info; + + for (i = 0; i < total; i++) { + int t; + + if (!kon_assetsInfo(d->assets, i, &info)) continue; + for (t = 0; t < types; t++) if (names[t] == info.typeName) break; + if (t == types) { + if (types == KON_DEBUG_TYPES) continue; + names[types] = info.typeName; + counts[types] = 0; + bytes[types] = 0; + types++; + } + counts[t]++; + bytes[t] += info.bytes; + } + + y += 2; + snprintf(line, sizeof(line), "assets %d, %.1f KB", total, (double)kon_assetsBytes(d->assets) / 1024.0); + kon_debugBox_(d, fb, x, y, line, white); + y += d->lineH; + for (i = 0; i < types; i++) { + snprintf(line, sizeof(line), " %-7s %3d %8.1f KB", names[i], counts[i], (double)bytes[i] / 1024.0); + kon_debugBox_(d, fb, x, y, line, dim); + y += d->lineH; + } + + if (d->overlay >= 3) { + rows = (fb->height - y) / d->lineH - 1; + for (i = 0; i < total && i < rows; i++) { + if (!kon_assetsInfo(d->assets, i, &info)) continue; + snprintf(line, sizeof(line), "%-6s x%d %7.1fK %s%s", info.typeName, info.refs, (double)info.bytes / 1024.0, info.name, info.loose ? " *" : ""); + kon_debugBox_(d, fb, x, y, line, dim); + y += d->lineH; + } + } + } else if (d->overlay >= 2) { + kon_debugBox_(d, fb, x, y + 2, "no asset manager attached", dim); + } +} + +static void kon_debugDrawConsole_(kon_debug_t *d, kon_framebuffer_t *fb) +{ + int full = fb->height / 2, height = (int)((float)full * d->slide); + int inputY, rows, i, line, cx, w = 0; + char shown[KON_DEBUG_INPUT + 4]; + + if (height < d->lineH + 6) return; + + kon_fillRectangle(fb, 0, 0, fb->width, height, 0xE0101018); + kon_fillRectangle(fb, 0, height - 1, fb->width, 1, 0xFF7088E0); + + inputY = height - d->lineH - 4; + kon_fillRectangle(fb, 0, inputY - 2, fb->width, d->lineH + 4, 0xFF181824); + + rows = (inputY - 4) / d->lineH; + for (i = 0; i < rows; i++) { + int index = d->logCount - 1 - d->scroll - i; + + if (index < 0) break; + line = (d->logHead + index) % KON_DEBUG_LOGLINES; + kon_debugText_(d, fb, 6, inputY - 4 - (i + 1) * d->lineH, d->log[line], d->log[line][0] == '>' ? 0xFF9098C0 : 0xFFE0E0E8); + } + + snprintf(shown, sizeof(shown), "> %s", d->input); + kon_debugText_(d, fb, 6, inputY, shown, 0xFFFFFFFF); + + snprintf(shown, sizeof(shown), "> %.*s", d->cursor, d->input); + kon_measureText(d->font, shown, &w, NULL); + cx = 6 + w; + kon_fillRectangle(fb, cx, inputY, 2, d->lineH - 2, 0xFFFFFFFF); +} + +void kon_debugDraw(kon_debug_t *d, kon_framebuffer_t *fb) +{ + if (!d) return; + float target = d->open ? 1.0f : 0.0f; + float step = (float)d->frameDt * 8.0f; + + if (d->slide < target) d->slide = d->slide + step > target ? target : d->slide + step; + else if (d->slide > target) d->slide = d->slide - step < target ? target : d->slide - step; + + if (d->overlay) kon_debugDrawOverlay_(d, fb); + if (d->slide > 0.0f) kon_debugDrawConsole_(d, fb); +} + +#endif diff --git a/kon/kondebug.h b/kon/kondebug.h index 6ac51c8..ecee066 100644 --- a/kon/kondebug.h +++ b/kon/kondebug.h @@ -93,717 +93,3 @@ static inline int kon_debugQuitRequested(const kon_debug_t *debug) { (void)debug #endif #endif - -/*** implementation ***/ - -#if defined(KONDEBUG_IMPLEMENTATION) && defined(KON_DEVELOPER) - -#include <stdlib.h> -#include <string.h> -#include <stdio.h> -#include <stdarg.h> - -#include "kontime.h" - -#define KON_DEBUG_LOGLINES 256 -#define KON_DEBUG_LINELEN 160 -#define KON_DEBUG_INPUT 256 -#define KON_DEBUG_HISTORY 16 -#define KON_DEBUG_COMMANDS 64 -#define KON_DEBUG_VARS 64 -#define KON_DEBUG_ARGS 16 -#define KON_DEBUG_GRAPH 120 -#define KON_DEBUG_TYPES 8 - -typedef struct kon_debugCmd { - const char *name; - const char *help; - kon_debugCommandFn fn; - void *user; -} kon_debugCmd_t; - -typedef struct kon_debugVarEntry { - const char *name; - kon_debugVarType_t type; - void *value; - int watched; -} kon_debugVarEntry_t; - -struct kon_debug { - kon_font_t *font; - kon_assets_t *assets; - int lineH, ascent; - float baseSize, size; - int ctrl; - - int overlayKey, consoleKey; - int overlay; - int open; - float slide; - int swallowTick; - int quit; - - char log[KON_DEBUG_LOGLINES][KON_DEBUG_LINELEN]; - int logCount, logHead, scroll; - - char input[KON_DEBUG_INPUT]; - int length, cursor; - char history[KON_DEBUG_HISTORY][KON_DEBUG_INPUT]; - int historyCount, historyPos; - - kon_debugCmd_t commands[KON_DEBUG_COMMANDS]; - int commandCount; - kon_debugVarEntry_t vars[KON_DEBUG_VARS]; - int varCount; - - kon_time last; - double frameDt; - double accumTime; - int accumFrames; - float fps; - float graph[KON_DEBUG_GRAPH]; - int graphHead; -}; - -/*** private helpers ***/ - -static void kon_debugMetrics_(kon_debug_t *d) -{ - int h = 0; - - kon_measureText(d->font, "A", NULL, &h); - d->ascent = h * 4 / 5; - d->lineH = h + 2; -} - -static void kon_debugResize_(kon_debug_t *d, float size) -{ - if (size < 8.0f) size = 8.0f; - if (size > 64.0f) size = 64.0f; - if (size == d->size) return; - if (kon_fontResize(d->font, size)) { - d->size = size; - kon_debugMetrics_(d); - } -} - -static void kon_debugText_(const kon_debug_t *d, kon_framebuffer_t *fb, int x, int y, const char *s, uint32_t color) -{ - kon_drawText(fb, d->font, x, y + d->ascent, s, color); -} - -static void kon_debugPushLine_(kon_debug_t *d, const char *line) -{ - int slot = (d->logHead + d->logCount) % KON_DEBUG_LOGLINES; - - if (d->logCount == KON_DEBUG_LOGLINES) { - slot = d->logHead; - d->logHead = (d->logHead + 1) % KON_DEBUG_LOGLINES; - } else { - d->logCount++; - } - snprintf(d->log[slot], KON_DEBUG_LINELEN, "%s", line); - if (d->scroll > 0 && d->scroll < d->logCount) d->scroll++; -} - -static const char *kon_debugVarText_(const kon_debugVarEntry_t *v, char *buffer, size_t size) -{ - switch (v->type) { - case KON_VAR_BOOL: snprintf(buffer, size, "%s", *(int *)v->value ? "true" : "false"); break; - case KON_VAR_INT: snprintf(buffer, size, "%d", *(int *)v->value); break; - default: snprintf(buffer, size, "%g", (double)*(float *)v->value); break; - } - return buffer; -} - -static int kon_debugTokenize_(char *line, char **argv, int max) -{ - int argc = 0; - - while (*line && argc < max) { - while (*line == ' ' || *line == '\t') line++; - if (!*line) break; - if (*line == '"') { - argv[argc++] = ++line; - while (*line && *line != '"') line++; - } else { - argv[argc++] = line; - while (*line && *line != ' ' && *line != '\t') line++; - } - if (*line) *line++ = 0; - } - return argc; -} - -static void kon_debugSetVar_(kon_debug_t *d, kon_debugVarEntry_t *v, const char *text) -{ - char out[64]; - - switch (v->type) { - case KON_VAR_BOOL: - if (!strcmp(text, "1") || !strcmp(text, "true") || !strcmp(text, "on")) *(int *)v->value = 1; - else if (!strcmp(text, "0") || !strcmp(text, "false") || !strcmp(text, "off")) *(int *)v->value = 0; - else { - kon_debugLog(d, "%s expects 0 or 1", v->name); - return; - } - break; - case KON_VAR_INT: *(int *)v->value = atoi(text); break; - default: *(float *)v->value = (float)atof(text); break; - } - kon_debugLog(d, "%s = %s", v->name, kon_debugVarText_(v, out, sizeof(out))); -} - -static void kon_debugExecute_(kon_debug_t *d, const char *line) -{ - char copy[KON_DEBUG_INPUT], *argv[KON_DEBUG_ARGS]; - int argc, i; - - snprintf(copy, sizeof(copy), "%s", line); - argc = kon_debugTokenize_(copy, argv, KON_DEBUG_ARGS); - if (argc == 0) return; - - for (i = 0; i < d->commandCount; i++) { - if (!strcmp(argv[0], d->commands[i].name)) { - d->commands[i].fn(d, d->commands[i].user, argc, argv); - return; - } - } - for (i = 0; i < d->varCount; i++) { - kon_debugVarEntry_t *v = &d->vars[i]; - char out[64]; - - if (strcmp(argv[0], v->name)) continue; - if (argc > 1) kon_debugSetVar_(d, v, argv[1]); - else kon_debugLog(d, "%s = %s", v->name, kon_debugVarText_(v, out, sizeof(out))); - return; - } - kon_debugLog(d, "unknown command '%s', try help", argv[0]); -} - -static void kon_debugComplete_(kon_debug_t *d) -{ - const char *matches[KON_DEBUG_COMMANDS + KON_DEBUG_VARS]; - int count = 0, i, common; - size_t prefix = (size_t)d->cursor; - - for (i = 0; i < d->cursor; i++) if (d->input[i] == ' ') return; - if (prefix == 0) return; - - for (i = 0; i < d->commandCount; i++) if (!strncmp(d->commands[i].name, d->input, prefix)) matches[count++] = d->commands[i].name; - for (i = 0; i < d->varCount; i++) if (!strncmp(d->vars[i].name, d->input, prefix)) matches[count++] = d->vars[i].name; - if (count == 0) return; - - common = (int)strlen(matches[0]); - for (i = 1; i < count; i++) { - int n = 0; - - while (n < common && matches[i][n] == matches[0][n]) n++; - common = n; - } - - if (count > 1) { - char line[KON_DEBUG_LINELEN] = ""; - - for (i = 0; i < count; i++) { - if (strlen(line) + strlen(matches[i]) + 2 >= sizeof(line)) break; - strcat(line, matches[i]); - strcat(line, " "); - } - kon_debugLog(d, "%s", line); - } - - { - char rest[KON_DEBUG_INPUT]; - int add = common - d->cursor + (count == 1 ? 1 : 0); - - snprintf(rest, sizeof(rest), "%s", d->input + d->cursor); - if (d->length + add >= KON_DEBUG_INPUT) return; - memcpy(d->input, matches[0], (size_t)common); - if (count == 1) d->input[common] = ' '; - memcpy(d->input + common + (count == 1 ? 1 : 0), rest, strlen(rest) + 1); - d->length = (int)strlen(d->input); - d->cursor = common + (count == 1 ? 1 : 0); - } -} - -static void kon_debugInsert_(kon_debug_t *d, char c) -{ - if (d->length + 1 >= KON_DEBUG_INPUT) return; - memmove(d->input + d->cursor + 1, d->input + d->cursor, (size_t)(d->length - d->cursor) + 1); - d->input[d->cursor++] = c; - d->length++; -} - -static void kon_debugSetInput_(kon_debug_t *d, const char *text) -{ - snprintf(d->input, sizeof(d->input), "%s", text); - d->length = (int)strlen(d->input); - d->cursor = d->length; -} - -static void kon_debugSubmit_(kon_debug_t *d) -{ - char line[KON_DEBUG_INPUT + 4]; - - if (d->length == 0) return; - snprintf(line, sizeof(line), "> %s", d->input); - kon_debugPushLine_(d, line); - - if (d->historyCount == 0 || strcmp(d->history[d->historyCount - 1], d->input)) { - if (d->historyCount == KON_DEBUG_HISTORY) { - memmove(d->history[0], d->history[1], sizeof(d->history[0]) * (KON_DEBUG_HISTORY - 1)); - d->historyCount--; - } - snprintf(d->history[d->historyCount++], KON_DEBUG_INPUT, "%s", d->input); - } - d->historyPos = d->historyCount; - - { - char copy[KON_DEBUG_INPUT]; - - snprintf(copy, sizeof(copy), "%s", d->input); - d->input[0] = 0; - d->length = d->cursor = 0; - d->scroll = 0; - kon_debugExecute_(d, copy); - } -} - -/*** builtin commands ***/ - -static void kon_debugCmdHelp_(kon_debug_t *d, void *user, int argc, char **argv) -{ - int i; - - (void)user; - if (argc > 1) { - for (i = 0; i < d->commandCount; i++) { - if (!strcmp(argv[1], d->commands[i].name)) { - kon_debugLog(d, "%s: %s", d->commands[i].name, d->commands[i].help); - return; - } - } - kon_debugLog(d, "no help for '%s'", argv[1]); - return; - } - kon_debugLog(d, "commands:"); - for (i = 0; i < d->commandCount; i++) kon_debugLog(d, " %-10s %s", d->commands[i].name, d->commands[i].help); - kon_debugLog(d, "variables (type the name to read, name value to set):"); - for (i = 0; i < d->varCount; i++) kon_debugLog(d, " %s", d->vars[i].name); -} - -static void kon_debugCmdClear_(kon_debug_t *d, void *user, int argc, char **argv) -{ - (void)user; (void)argc; (void)argv; - d->logCount = d->logHead = d->scroll = 0; -} - -static void kon_debugCmdEcho_(kon_debug_t *d, void *user, int argc, char **argv) -{ - char line[KON_DEBUG_LINELEN] = ""; - int i; - - (void)user; - for (i = 1; i < argc; i++) { - if (strlen(line) + strlen(argv[i]) + 2 >= sizeof(line)) break; - strcat(line, argv[i]); - strcat(line, " "); - } - kon_debugLog(d, "%s", line); -} - -static void kon_debugCmdFps_(kon_debug_t *d, void *user, int argc, char **argv) -{ - (void)user; - d->overlay = argc > 1 ? atoi(argv[1]) & 3 : (d->overlay + 1) & 3; -} - -static void kon_debugCmdWatch_(kon_debug_t *d, void *user, int argc, char **argv) -{ - int i; - - (void)user; - if (argc < 2) { - kon_debugLog(d, "usage: watch <variable>"); - return; - } - for (i = 0; i < d->varCount; i++) { - if (!strcmp(argv[1], d->vars[i].name)) { - d->vars[i].watched = !d->vars[i].watched; - if (d->overlay == 0) d->overlay = 1; - return; - } - } - kon_debugLog(d, "no variable '%s'", argv[1]); -} - -static void kon_debugCmdQuit_(kon_debug_t *d, void *user, int argc, char **argv) -{ - (void)user; (void)argc; (void)argv; - d->quit = 1; -} - -static void kon_debugCmdFontSize_(kon_debug_t *d, void *user, int argc, char **argv) -{ - (void)user; - if (argc > 1) kon_debugResize_(d, (float)atof(argv[1])); - kon_debugLog(d, "fontsize = %g", (double)d->size); -} - -static void kon_debugCmdAssets_(kon_debug_t *d, void *user, int argc, char **argv) -{ - int i, count; - kon_assetInfo_t info; - - (void)user; (void)argc; (void)argv; - if (!d->assets) { - kon_debugLog(d, "no asset manager attached"); - return; - } - count = kon_assetsCount(d->assets); - for (i = 0; i < count; i++) { - if (!kon_assetsInfo(d->assets, i, &info)) continue; - kon_debugLog(d, "%-7s %3d ref %8lu b %s%s", info.typeName, info.refs, (unsigned long)info.bytes, info.name, info.loose ? " (loose)" : ""); - } - kon_debugLog(d, "%d assets, %lu bytes", count, (unsigned long)kon_assetsBytes(d->assets)); -} - -/*** debug ***/ - -kon_debug_t *kon_debugCreate(kon_font_t *font) -{ - kon_debug_t *d; - - if (!font) return NULL; - d = calloc(1, sizeof(kon_debug_t)); - if (!d) return NULL; - - d->font = font; - d->baseSize = d->size = kon_fontPixelHeight(font); - kon_debugMetrics_(d); - d->overlayKey = KON_KEY_F1; - d->consoleKey = KON_KEY_GRAVE; - d->last = kon_getTime(); - - kon_debugCommand(d, "help", "list commands, or help <command>", kon_debugCmdHelp_, NULL); - kon_debugCommand(d, "clear", "clear the console", kon_debugCmdClear_, NULL); - kon_debugCommand(d, "echo", "print text", kon_debugCmdEcho_, NULL); - kon_debugCommand(d, "fps", "overlay mode 0-3, or cycle", kon_debugCmdFps_, NULL); - kon_debugCommand(d, "watch", "show a variable in the overlay", kon_debugCmdWatch_, NULL); - kon_debugCommand(d, "assets", "list loaded assets", kon_debugCmdAssets_, NULL); - kon_debugCommand(d, "fontsize", "text size in pixels, ctrl + and ctrl - too", kon_debugCmdFontSize_, NULL); - kon_debugCommand(d, "quit", "close the game", kon_debugCmdQuit_, NULL); - kon_debugCommand(d, "exit", "close the game", kon_debugCmdQuit_, NULL); - kon_debugLog(d, "type help for commands, tab completes"); - return d; -} - -void kon_debugDestroy(kon_debug_t *d) -{ - free(d); -} - -void kon_debugSetKeys(kon_debug_t *d, int overlayKey, int consoleKey) -{ - if (!d) return; - d->overlayKey = overlayKey; - d->consoleKey = consoleKey; -} - -void kon_debugSetAssets(kon_debug_t *d, kon_assets_t *assets) -{ - if (!d) return; - d->assets = assets; -} - -int kon_debugCommand(kon_debug_t *d, const char *name, const char *help, kon_debugCommandFn fn, void *user) -{ - if (!d) return 0; - kon_debugCmd_t *c; - - if (d->commandCount == KON_DEBUG_COMMANDS) return 0; - c = &d->commands[d->commandCount++]; - c->name = name; - c->help = help; - c->fn = fn; - c->user = user; - return 1; -} - -int kon_debugVar(kon_debug_t *d, const char *name, kon_debugVarType_t type, void *value) -{ - if (!d) return 0; - kon_debugVarEntry_t *v; - - if (d->varCount == KON_DEBUG_VARS) return 0; - v = &d->vars[d->varCount++]; - v->name = name; - v->type = type; - v->value = value; - v->watched = 0; - return 1; -} - -void kon_debugLog(kon_debug_t *d, const char *format, ...) -{ - if (!d) return; - char line[KON_DEBUG_LINELEN]; - va_list args; - - va_start(args, format); - vsnprintf(line, sizeof(line), format, args); - va_end(args); - kon_debugPushLine_(d, line); -} - -int kon_debugConsoleOpen(const kon_debug_t *d) -{ - if (!d) return 0; - return d->open; -} - -int kon_debugQuitRequested(const kon_debug_t *d) -{ - return d ? d->quit : 0; -} - -float kon_debugFps(const kon_debug_t *d) -{ - if (!d) return 0.0f; - return d->fps; -} - -int kon_debugFeedEvent(kon_debug_t *d, const kon_event_t *e) -{ - if (!d) return 0; - - if (e->key == KON_KEY_LEFT_CTRL || e->key == KON_KEY_RIGHT_CTRL) { - if (e->type == KON_EVENT_KEY_DOWN) d->ctrl = 1; - if (e->type == KON_EVENT_KEY_UP) d->ctrl = 0; - } - - if (e->type == KON_EVENT_KEY_DOWN) { - if (d->ctrl && (d->open || d->overlay)) { - if (e->key == KON_KEY_EQUAL) { kon_debugResize_(d, d->size + 2.0f); return 1; } - if (e->key == KON_KEY_MINUS) { kon_debugResize_(d, d->size - 2.0f); return 1; } - if (e->key == KON_KEY_0) { kon_debugResize_(d, d->baseSize); return 1; } - } - if (e->key == d->consoleKey) { - d->open = !d->open; - d->swallowTick = 1; - return 1; - } - d->swallowTick = 0; - if (e->key == d->overlayKey) { - d->overlay = (d->overlay + 1) & 3; - return 1; - } - } - if (!d->open) return 0; - - if (e->type == KON_EVENT_TEXT) { - if (d->ctrl) return 1; - if (d->swallowTick && e->codepoint == '`') { - d->swallowTick = 0; - return 1; - } - if (e->codepoint >= 32 && e->codepoint <= 126) kon_debugInsert_(d, (char)e->codepoint); - return 1; - } - if (e->type == KON_EVENT_KEY_UP) return 1; - if (e->type != KON_EVENT_KEY_DOWN) return 0; - - switch (e->key) { - case KON_KEY_ENTER: kon_debugSubmit_(d); break; - case KON_KEY_ESCAPE: d->open = 0; break; - case KON_KEY_TAB: kon_debugComplete_(d); break; - case KON_KEY_LEFT: if (d->cursor > 0) d->cursor--; break; - case KON_KEY_RIGHT: if (d->cursor < d->length) d->cursor++; break; - case KON_KEY_BACKSPACE: - if (d->cursor > 0) { - memmove(d->input + d->cursor - 1, d->input + d->cursor, (size_t)(d->length - d->cursor) + 1); - d->cursor--; - d->length--; - } - break; - case KON_KEY_DELETE: - if (d->cursor < d->length) { - memmove(d->input + d->cursor, d->input + d->cursor + 1, (size_t)(d->length - d->cursor)); - d->length--; - } - break; - case KON_KEY_UP: - if (d->historyPos > 0) kon_debugSetInput_(d, d->history[--d->historyPos]); - break; - case KON_KEY_DOWN: - if (d->historyPos < d->historyCount - 1) kon_debugSetInput_(d, d->history[++d->historyPos]); - else { - d->historyPos = d->historyCount; - kon_debugSetInput_(d, ""); - } - break; - case KON_KEY_PAGE_UP: d->scroll += 8; break; - case KON_KEY_PAGE_DOWN: d->scroll -= 8; break; - default: break; - } - if (d->scroll < 0) d->scroll = 0; - if (d->scroll > d->logCount - 1) d->scroll = d->logCount > 0 ? d->logCount - 1 : 0; - return 1; -} - -void kon_debugFrame(kon_debug_t *d) -{ - if (!d) return; - kon_time now = kon_getTime(); - double dt = now - d->last; - - d->last = now; - d->frameDt = dt; - d->accumTime += dt; - d->accumFrames++; - if (d->accumTime >= 0.5) { - d->fps = (float)((double)d->accumFrames / d->accumTime); - d->accumTime = 0.0; - d->accumFrames = 0; - } - d->graph[d->graphHead] = (float)(dt * 1000.0); - d->graphHead = (d->graphHead + 1) % KON_DEBUG_GRAPH; -} - -static void kon_debugBox_(const kon_debug_t *d, kon_framebuffer_t *fb, int x, int y, const char *s, uint32_t color) -{ - int w = 0; - - kon_measureText(d->font, s, &w, NULL); - kon_fillRectangle(fb, x - 2, y - 1, w + 4, d->lineH, 0xB0000000); - kon_debugText_(d, fb, x, y, s, color); -} - -static void kon_debugDrawOverlay_(kon_debug_t *d, kon_framebuffer_t *fb) -{ - char line[128]; - int x = 4, y = 4, i, rows; - uint32_t white = 0xFFFFFFFF, dim = 0xFFB0B0C0; - float worst = 0.0f; - - snprintf(line, sizeof(line), "%.0f fps %.2f ms", (double)d->fps, d->frameDt * 1000.0); - kon_debugBox_(d, fb, x, y, line, white); - y += d->lineH + 3; - - if (d->overlay >= 1) { - kon_fillRectangle(fb, x - 2, y - 2, KON_DEBUG_GRAPH + 4, 34, 0xB0000000); - for (i = 0; i < KON_DEBUG_GRAPH; i++) { - float ms = d->graph[(d->graphHead + i) % KON_DEBUG_GRAPH]; - int h = (int)(ms * 30.0f / 33.3f); - uint32_t color = ms < 17.5f ? 0xFF50D060 : (ms < 34.0f ? 0xFFE0C040 : 0xFFE05050); - - if (ms > worst) worst = ms; - if (h > 30) h = 30; - if (h < 1) h = 1; - kon_fillRectangle(fb, x + i, y + 30 - h, 1, h, color); - } - y += 36; - } - - for (i = 0; i < d->varCount; i++) { - char value[64]; - - if (!d->vars[i].watched) continue; - snprintf(line, sizeof(line), "%s = %s", d->vars[i].name, kon_debugVarText_(&d->vars[i], value, sizeof(value))); - kon_debugBox_(d, fb, x, y, line, dim); - y += d->lineH; - } - - if (d->overlay >= 2 && d->assets) { - const char *names[KON_DEBUG_TYPES]; - int counts[KON_DEBUG_TYPES], types = 0, total = kon_assetsCount(d->assets); - size_t bytes[KON_DEBUG_TYPES]; - kon_assetInfo_t info; - - for (i = 0; i < total; i++) { - int t; - - if (!kon_assetsInfo(d->assets, i, &info)) continue; - for (t = 0; t < types; t++) if (names[t] == info.typeName) break; - if (t == types) { - if (types == KON_DEBUG_TYPES) continue; - names[types] = info.typeName; - counts[types] = 0; - bytes[types] = 0; - types++; - } - counts[t]++; - bytes[t] += info.bytes; - } - - y += 2; - snprintf(line, sizeof(line), "assets %d, %.1f KB", total, (double)kon_assetsBytes(d->assets) / 1024.0); - kon_debugBox_(d, fb, x, y, line, white); - y += d->lineH; - for (i = 0; i < types; i++) { - snprintf(line, sizeof(line), " %-7s %3d %8.1f KB", names[i], counts[i], (double)bytes[i] / 1024.0); - kon_debugBox_(d, fb, x, y, line, dim); - y += d->lineH; - } - - if (d->overlay >= 3) { - rows = (fb->height - y) / d->lineH - 1; - for (i = 0; i < total && i < rows; i++) { - if (!kon_assetsInfo(d->assets, i, &info)) continue; - snprintf(line, sizeof(line), "%-6s x%d %7.1fK %s%s", info.typeName, info.refs, (double)info.bytes / 1024.0, info.name, info.loose ? " *" : ""); - kon_debugBox_(d, fb, x, y, line, dim); - y += d->lineH; - } - } - } else if (d->overlay >= 2) { - kon_debugBox_(d, fb, x, y + 2, "no asset manager attached", dim); - } -} - -static void kon_debugDrawConsole_(kon_debug_t *d, kon_framebuffer_t *fb) -{ - int full = fb->height / 2, height = (int)((float)full * d->slide); - int inputY, rows, i, line, cx, w = 0; - char shown[KON_DEBUG_INPUT + 4]; - - if (height < d->lineH + 6) return; - - kon_fillRectangle(fb, 0, 0, fb->width, height, 0xE0101018); - kon_fillRectangle(fb, 0, height - 1, fb->width, 1, 0xFF7088E0); - - inputY = height - d->lineH - 4; - kon_fillRectangle(fb, 0, inputY - 2, fb->width, d->lineH + 4, 0xFF181824); - - rows = (inputY - 4) / d->lineH; - for (i = 0; i < rows; i++) { - int index = d->logCount - 1 - d->scroll - i; - - if (index < 0) break; - line = (d->logHead + index) % KON_DEBUG_LOGLINES; - kon_debugText_(d, fb, 6, inputY - 4 - (i + 1) * d->lineH, d->log[line], d->log[line][0] == '>' ? 0xFF9098C0 : 0xFFE0E0E8); - } - - snprintf(shown, sizeof(shown), "> %s", d->input); - kon_debugText_(d, fb, 6, inputY, shown, 0xFFFFFFFF); - - snprintf(shown, sizeof(shown), "> %.*s", d->cursor, d->input); - kon_measureText(d->font, shown, &w, NULL); - cx = 6 + w; - kon_fillRectangle(fb, cx, inputY, 2, d->lineH - 2, 0xFFFFFFFF); -} - -void kon_debugDraw(kon_debug_t *d, kon_framebuffer_t *fb) -{ - if (!d) return; - float target = d->open ? 1.0f : 0.0f; - float step = (float)d->frameDt * 8.0f; - - if (d->slide < target) d->slide = d->slide + step > target ? target : d->slide + step; - else if (d->slide > target) d->slide = d->slide - step < target ? target : d->slide - step; - - if (d->overlay) kon_debugDrawOverlay_(d, fb); - if (d->slide > 0.0f) kon_debugDrawConsole_(d, fb); -} - -#endif diff --git a/kon/konpack.c b/kon/konpack.c new file mode 100644 index 0000000..0dfe4a8 --- /dev/null +++ b/kon/konpack.c @@ -0,0 +1,598 @@ +/* SPDX-License-Identifier: MIT */ +/* + * konpack.c + * + * Copyright (c) 2026 KonAki + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + + +#include "konpack.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#define KON_PACK_MAGIC_ "KPAK" +#define KON_PACK_VERSION_ 1u +#define KON_PACK_HEADER_SIZE_ 36u +#define KON_PACK_FLAG_ENCRYPTED_ 1u +#define KON_PACK_TABLE_INDEX_ 0xFFFFFFFFu + +static const char *kon_packErrorText_ = "no error"; + +const char *kon_packError(void) { + return kon_packErrorText_; +} + +/*** private helper ***/ + +static uint32_t kon_load32_(const uint8_t *p) { + return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24); +} + +static void kon_store32_(uint8_t *p, uint32_t v) { + p[0] = (uint8_t)(v & 0xFF); + p[1] = (uint8_t)((v >> 8) & 0xFF); + p[2] = (uint8_t)((v >> 16) & 0xFF); + p[3] = (uint8_t)((v >> 24) & 0xFF); +} + +/*** crc32 ***/ + +uint32_t kon_crc32(const uint8_t *data, size_t length) { + static uint32_t table[256]; + static int ready; + + if (!ready) { + for (uint32_t i = 0; i < 256; i++) { + uint32_t c = i; + for (int k = 0; k < 8; k++) c = (c & 1u) ? (0xEDB88320u ^ (c >> 1)) : (c >> 1); + table[i] = c; + } + ready = 1; + } + + uint32_t crc = 0xFFFFFFFFu; + for (size_t i = 0; i < length; i++) crc = table[(crc ^ data[i]) & 0xFF] ^ (crc >> 8); + return crc ^ 0xFFFFFFFFu; +} + +/*** chacha20 ***/ + +#define KON_ROTL32_(v, n) (((v) << (n)) | ((v) >> (32 - (n)))) + +#define KON_QUARTER_(a, b, c, d) \ + a += b; d ^= a; d = KON_ROTL32_(d, 16); \ + c += d; b ^= c; b = KON_ROTL32_(b, 12); \ + a += b; d ^= a; d = KON_ROTL32_(d, 8); \ + c += d; b ^= c; b = KON_ROTL32_(b, 7); + +static void kon_chachaBlock_(const uint32_t state[16], uint8_t out[64]) { + uint32_t x[16]; + + for (int i = 0; i < 16; i++) x[i] = state[i]; + + for (int round = 0; round < 10; round++) { + KON_QUARTER_(x[0], x[4], x[8], x[12]) + KON_QUARTER_(x[1], x[5], x[9], x[13]) + KON_QUARTER_(x[2], x[6], x[10], x[14]) + KON_QUARTER_(x[3], x[7], x[11], x[15]) + KON_QUARTER_(x[0], x[5], x[10], x[15]) + KON_QUARTER_(x[1], x[6], x[11], x[12]) + KON_QUARTER_(x[2], x[7], x[8], x[13]) + KON_QUARTER_(x[3], x[4], x[9], x[14]) + } + + for (int i = 0; i < 16; i++) kon_store32_(&out[i * 4], x[i] + state[i]); +} + +void kon_chacha20Xor(const uint8_t key[KON_PACK_KEY_SIZE], const uint8_t nonce[12], uint32_t counter, const uint8_t *in, uint8_t *out, size_t length) { + uint32_t state[16]; + uint8_t block[64]; + + state[0] = 0x61707865u; + state[1] = 0x3320646Eu; + state[2] = 0x79622D32u; + state[3] = 0x6B206574u; + for (int i = 0; i < 8; i++) state[4 + i] = kon_load32_(&key[i * 4]); + state[12] = counter; + for (int i = 0; i < 3; i++) state[13 + i] = kon_load32_(&nonce[i * 4]); + + while (length > 0) { + size_t n = length < 64 ? length : 64; + + kon_chachaBlock_(state, block); + for (size_t i = 0; i < n; i++) out[i] = (uint8_t)(in[i] ^ block[i]); + + in += n; + out += n; + length -= n; + state[12]++; + } +} + +static void kon_fileNonce_(const uint8_t packNonce[8], uint32_t index, uint8_t out[12]) { + memcpy(out, packNonce, 8); + kon_store32_(&out[8], index); +} + +/*** reading ***/ + +typedef struct kon_packEntry { + const char *name; + uint32_t offset, size, crc; +} kon_packEntry_t; + +struct kon_pack { + FILE *file; + uint8_t key[KON_PACK_KEY_SIZE]; + uint8_t nonce[8]; + int encrypted; + int count; + kon_packEntry_t *entries; + char *names; +}; + +void kon_packClose(kon_pack_t *pack) { + if (!pack) return; + + if (pack->file) fclose(pack->file); + free(pack->entries); + free(pack->names); + free(pack); +} + +kon_pack_t *kon_packOpen(const char *path, const uint8_t *key) { + uint8_t header[KON_PACK_HEADER_SIZE_]; + uint8_t *table = NULL; + kon_pack_t *pack = NULL; + uint32_t flags, count, tableOffset, tableSize, tableCrc; + uint64_t fileSize; + long end; + + if (!path) { + kon_packErrorText_ = "no path"; + return NULL; + } + + pack = calloc(1, sizeof(kon_pack_t)); + if (!pack) { + kon_packErrorText_ = "out of memory"; + return NULL; + } + + pack->file = fopen(path, "rb"); + if (!pack->file) { + kon_packErrorText_ = "could not open the pack file"; + goto fail; + } + + if (fread(header, 1, sizeof(header), pack->file) != sizeof(header)) { + kon_packErrorText_ = "pack is too small"; + goto fail; + } + if (memcmp(header, KON_PACK_MAGIC_, 4) != 0) { + kon_packErrorText_ = "not a pack file"; + goto fail; + } + if (kon_load32_(&header[4]) != KON_PACK_VERSION_) { + kon_packErrorText_ = "unsupported pack version"; + goto fail; + } + + flags = kon_load32_(&header[8]); + count = kon_load32_(&header[12]); + tableOffset = kon_load32_(&header[16]); + tableSize = kon_load32_(&header[20]); + tableCrc = kon_load32_(&header[24]); + memcpy(pack->nonce, &header[28], 8); + + pack->encrypted = (flags & KON_PACK_FLAG_ENCRYPTED_) != 0; + if (pack->encrypted) { + if (!key) { + kon_packErrorText_ = "pack is encrypted and no key was given"; + goto fail; + } + memcpy(pack->key, key, KON_PACK_KEY_SIZE); + } + + if (fseek(pack->file, 0, SEEK_END) != 0 || (end = ftell(pack->file)) < 0) { + kon_packErrorText_ = "could not get the pack size"; + goto fail; + } + fileSize = (uint64_t)end; + + if (tableOffset < KON_PACK_HEADER_SIZE_ || (uint64_t)tableOffset + tableSize > fileSize || + (uint64_t)count * 15u > tableSize || count > 0x7FFFFFFFu) { + kon_packErrorText_ = "pack table is damaged"; + goto fail; + } + + table = malloc(tableSize ? tableSize : 1); + pack->entries = calloc(count ? count : 1, sizeof(kon_packEntry_t)); + pack->names = malloc((size_t)tableSize + count + 1); + if (!table || !pack->entries || !pack->names) { + kon_packErrorText_ = "out of memory"; + goto fail; + } + + if (fseek(pack->file, (long)tableOffset, SEEK_SET) != 0 || fread(table, 1, tableSize, pack->file) != tableSize) { + kon_packErrorText_ = "could not read the pack table"; + goto fail; + } + + if (pack->encrypted) { + uint8_t nonce[12]; + kon_fileNonce_(pack->nonce, KON_PACK_TABLE_INDEX_, nonce); + kon_chacha20Xor(pack->key, nonce, 0, table, table, tableSize); + } + + if (kon_crc32(table, tableSize) != tableCrc) { + kon_packErrorText_ = pack->encrypted ? "wrong key or damaged pack" : "pack table is damaged"; + goto fail; + } + + { + size_t at = 0, nameAt = 0; + + for (uint32_t i = 0; i < count; i++) { + size_t nameLength; + + if (at + 2 > tableSize) goto damaged; + nameLength = (size_t)table[at] | ((size_t)table[at + 1] << 8); + at += 2; + if (nameLength == 0 || at + nameLength + 12 > tableSize) goto damaged; + + memcpy(&pack->names[nameAt], &table[at], nameLength); + pack->names[nameAt + nameLength] = '\0'; + if (strlen(&pack->names[nameAt]) != nameLength) goto damaged; + pack->entries[i].name = &pack->names[nameAt]; + nameAt += nameLength + 1; + at += nameLength; + + pack->entries[i].offset = kon_load32_(&table[at]); + pack->entries[i].size = kon_load32_(&table[at + 4]); + pack->entries[i].crc = kon_load32_(&table[at + 8]); + at += 12; + + if (pack->entries[i].offset < KON_PACK_HEADER_SIZE_ || + (uint64_t)pack->entries[i].offset + pack->entries[i].size > tableOffset) goto damaged; + if (i > 0 && strcmp(pack->entries[i - 1].name, pack->entries[i].name) >= 0) goto damaged; + } + if (at != tableSize) goto damaged; + } + + pack->count = (int)count; + free(table); + return pack; + +damaged: + kon_packErrorText_ = "pack table is damaged"; +fail: + free(table); + kon_packClose(pack); + return NULL; +} + +int kon_packCount(const kon_pack_t *pack) { + return pack ? pack->count : 0; +} + +const char *kon_packName(const kon_pack_t *pack, int index) { + if (!pack || index < 0 || index >= pack->count) return NULL; + return pack->entries[index].name; +} + +uint32_t kon_packSize(const kon_pack_t *pack, int index) { + if (!pack || index < 0 || index >= pack->count) return 0; + return pack->entries[index].size; +} + +int kon_packFind(const kon_pack_t *pack, const char *name) { + int low = 0, high; + + if (!pack || !name) return -1; + high = pack->count - 1; + + while (low <= high) { + int mid = low + (high - low) / 2; + int cmp = strcmp(name, pack->entries[mid].name); + + if (cmp == 0) return mid; + if (cmp < 0) high = mid - 1; + else low = mid + 1; + } + return -1; +} + +uint8_t *kon_packRead(kon_pack_t *pack, int index, size_t *size) { + kon_packEntry_t *entry; + uint8_t *data; + + if (size) *size = 0; + if (!pack || index < 0 || index >= pack->count) { + kon_packErrorText_ = "no such file in the pack"; + return NULL; + } + entry = &pack->entries[index]; + + data = malloc((size_t)entry->size + 1); + if (!data) { + kon_packErrorText_ = "out of memory"; + return NULL; + } + + if (fseek(pack->file, (long)entry->offset, SEEK_SET) != 0 || fread(data, 1, entry->size, pack->file) != entry->size) { + kon_packErrorText_ = "could not read from the pack"; + free(data); + return NULL; + } + + if (pack->encrypted) { + uint8_t nonce[12]; + kon_fileNonce_(pack->nonce, (uint32_t)index, nonce); + kon_chacha20Xor(pack->key, nonce, 0, data, data, entry->size); + } + + if (kon_crc32(data, entry->size) != entry->crc) { + kon_packErrorText_ = "file is damaged"; + free(data); + return NULL; + } + + data[entry->size] = 0; + if (size) *size = entry->size; + return data; +} + +/*** writing ***/ + +#ifdef KONPACK_WRITER + +typedef struct kon_packSource_ { + const char *path; + char *name; +} kon_packSource_t; + +static int kon_packSourceCompare_(const void *a, const void *b) { + return strcmp(((const kon_packSource_t *)a)->name, ((const kon_packSource_t *)b)->name); +} + +static void kon_packMakeNonce_(uint8_t nonce[8]) { + FILE *f = fopen("/dev/urandom", "rb"); + + if (f) { + size_t got = fread(nonce, 1, 8, f); + fclose(f); + if (got == 8) return; + } + + /* nothing better around, mix what changes between runs. only has to never repeat, it is not secret */ + { + uint64_t h = 1469598103934665603ull; + uint64_t parts[3]; + int local; + + parts[0] = (uint64_t)time(NULL); + parts[1] = (uint64_t)clock(); + parts[2] = (uint64_t)(size_t)&local; + + for (int i = 0; i < 3; i++) { + for (int b = 0; b < 8; b++) { + h ^= (parts[i] >> (b * 8)) & 0xFF; + h *= 1099511628211ull; + } + } + for (int i = 0; i < 8; i++) nonce[i] = (uint8_t)(h >> (i * 8)); + } +} + +static char *kon_packMakeName_(const char *path, const char *baseDir) { + size_t baseLength = baseDir ? strlen(baseDir) : 0; + const char *rest = path; + char *name; + + if (baseLength > 0 && strncmp(path, baseDir, baseLength) == 0) { + rest = path + baseLength; + while (*rest == '/' || *rest == '\\') rest++; + } + while (rest[0] == '.' && (rest[1] == '/' || rest[1] == '\\')) rest += 2; + + name = malloc(strlen(rest) + 1); + if (!name) return NULL; + + strcpy(name, rest); + for (char *c = name; *c; c++) { + if (*c == '\\') *c = '/'; + } + return name; +} + +int kon_packWrite(const char *outPath, const uint8_t *key, const char *baseDir, const char *const *files, int count) { + kon_packSource_t *sources = NULL; + FILE *out = NULL; + uint8_t *table = NULL; + uint32_t *offsets = NULL, *sizes = NULL, *crcs = NULL; + uint8_t header[KON_PACK_HEADER_SIZE_]; + uint8_t packNonce[8]; + uint64_t position = KON_PACK_HEADER_SIZE_; + size_t tableSize = 0, at = 0; + int result = -1, made = 0; + + if (!outPath || !files || count < 0) { + kon_packErrorText_ = "bad arguments"; + return -1; + } + + sources = calloc(count ? (size_t)count : 1, sizeof(kon_packSource_t)); + offsets = calloc(count ? (size_t)count : 1, sizeof(uint32_t)); + sizes = calloc(count ? (size_t)count : 1, sizeof(uint32_t)); + crcs = calloc(count ? (size_t)count : 1, sizeof(uint32_t)); + if (!sources || !offsets || !sizes || !crcs) { + kon_packErrorText_ = "out of memory"; + goto done; + } + + for (int i = 0; i < count; i++) { + sources[i].path = files[i]; + sources[i].name = kon_packMakeName_(files[i], baseDir); + made++; + if (!sources[i].name || sources[i].name[0] == '\0' || strlen(sources[i].name) > 65535) { + kon_packErrorText_ = "bad file name"; + goto done; + } + } + + qsort(sources, (size_t)count, sizeof(kon_packSource_t), kon_packSourceCompare_); + for (int i = 1; i < count; i++) { + if (strcmp(sources[i - 1].name, sources[i].name) == 0) { + kon_packErrorText_ = "two files have the same name in the pack"; + goto done; + } + } + + kon_packMakeNonce_(packNonce); + + out = fopen(outPath, "wb"); + if (!out) { + kon_packErrorText_ = "could not create the pack file"; + goto done; + } + + memset(header, 0, sizeof(header)); + if (fwrite(header, 1, sizeof(header), out) != sizeof(header)) { + kon_packErrorText_ = "could not write the pack"; + goto done; + } + + for (int i = 0; i < count; i++) { + FILE *in = fopen(sources[i].path, "rb"); + uint8_t *data = NULL; + long length; + + if (!in) { + kon_packErrorText_ = "could not open an input file"; + goto done; + } + if (fseek(in, 0, SEEK_END) != 0 || (length = ftell(in)) < 0 || fseek(in, 0, SEEK_SET) != 0) { + kon_packErrorText_ = "could not get an input file size"; + fclose(in); + goto done; + } + if ((uint64_t)length > 0xFFFFFFFFu || position + (uint64_t)length > 0xFFFFFFFFu) { + kon_packErrorText_ = "pack would be over 4 GB"; + fclose(in); + goto done; + } + + data = malloc((size_t)length ? (size_t)length : 1); + if (!data || fread(data, 1, (size_t)length, in) != (size_t)length) { + kon_packErrorText_ = "could not read an input file"; + free(data); + fclose(in); + goto done; + } + fclose(in); + + offsets[i] = (uint32_t)position; + sizes[i] = (uint32_t)length; + crcs[i] = kon_crc32(data, (size_t)length); + + if (key) { + uint8_t nonce[12]; + kon_fileNonce_(packNonce, (uint32_t)i, nonce); + kon_chacha20Xor(key, nonce, 0, data, data, (size_t)length); + } + + if (fwrite(data, 1, (size_t)length, out) != (size_t)length) { + kon_packErrorText_ = "could not write the pack"; + free(data); + goto done; + } + free(data); + position += (uint64_t)length; + } + + for (int i = 0; i < count; i++) tableSize += 2 + strlen(sources[i].name) + 12; + if (position + tableSize > 0xFFFFFFFFu) { + kon_packErrorText_ = "pack would be over 4 GB"; + goto done; + } + + table = malloc(tableSize ? tableSize : 1); + if (!table) { + kon_packErrorText_ = "out of memory"; + goto done; + } + for (int i = 0; i < count; i++) { + size_t nameLength = strlen(sources[i].name); + + table[at] = (uint8_t)(nameLength & 0xFF); + table[at + 1] = (uint8_t)((nameLength >> 8) & 0xFF); + memcpy(&table[at + 2], sources[i].name, nameLength); + at += 2 + nameLength; + kon_store32_(&table[at], offsets[i]); + kon_store32_(&table[at + 4], sizes[i]); + kon_store32_(&table[at + 8], crcs[i]); + at += 12; + } + + memcpy(header, KON_PACK_MAGIC_, 4); + kon_store32_(&header[4], KON_PACK_VERSION_); + kon_store32_(&header[8], key ? KON_PACK_FLAG_ENCRYPTED_ : 0u); + kon_store32_(&header[12], (uint32_t)count); + kon_store32_(&header[16], (uint32_t)position); + kon_store32_(&header[20], (uint32_t)tableSize); + kon_store32_(&header[24], kon_crc32(table, tableSize)); + memcpy(&header[28], packNonce, 8); + + if (key) { + uint8_t nonce[12]; + kon_fileNonce_(packNonce, KON_PACK_TABLE_INDEX_, nonce); + kon_chacha20Xor(key, nonce, 0, table, table, tableSize); + } + + if (fwrite(table, 1, tableSize, out) != tableSize || fseek(out, 0, SEEK_SET) != 0 || + fwrite(header, 1, sizeof(header), out) != sizeof(header)) { + kon_packErrorText_ = "could not write the pack"; + goto done; + } + + result = 0; + +done: + if (out && fclose(out) != 0 && result == 0) { + kon_packErrorText_ = "could not write the pack"; + result = -1; + } + if (sources) { + for (int i = 0; i < made; i++) free(sources[i].name); + } + free(sources); + free(offsets); + free(sizes); + free(crcs); + free(table); + return result; +} + +#endif /* KONPACK_WRITER */ diff --git a/kon/konpack.h b/kon/konpack.h index da7464c..3700900 100644 --- a/kon/konpack.h +++ b/kon/konpack.h @@ -63,581 +63,4 @@ void kon_chacha20Xor(const uint8_t key[KON_PACK_KEY_SIZE], const uint8_t nonce[1 int kon_packWrite(const char *outPath, const uint8_t *key, const char *baseDir, const char *const *files, int count); #endif -/*** implementation ***/ - -#ifdef KONPACK_IMPLEMENTATION - -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <time.h> - -#define KON_PACK_MAGIC_ "KPAK" -#define KON_PACK_VERSION_ 1u -#define KON_PACK_HEADER_SIZE_ 36u -#define KON_PACK_FLAG_ENCRYPTED_ 1u -#define KON_PACK_TABLE_INDEX_ 0xFFFFFFFFu - -static const char *kon_packErrorText_ = "no error"; - -const char *kon_packError(void) { - return kon_packErrorText_; -} - -/*** private helper ***/ - -static uint32_t kon_load32_(const uint8_t *p) { - return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24); -} - -static void kon_store32_(uint8_t *p, uint32_t v) { - p[0] = (uint8_t)(v & 0xFF); - p[1] = (uint8_t)((v >> 8) & 0xFF); - p[2] = (uint8_t)((v >> 16) & 0xFF); - p[3] = (uint8_t)((v >> 24) & 0xFF); -} - -/*** crc32 ***/ - -uint32_t kon_crc32(const uint8_t *data, size_t length) { - static uint32_t table[256]; - static int ready; - - if (!ready) { - for (uint32_t i = 0; i < 256; i++) { - uint32_t c = i; - for (int k = 0; k < 8; k++) c = (c & 1u) ? (0xEDB88320u ^ (c >> 1)) : (c >> 1); - table[i] = c; - } - ready = 1; - } - - uint32_t crc = 0xFFFFFFFFu; - for (size_t i = 0; i < length; i++) crc = table[(crc ^ data[i]) & 0xFF] ^ (crc >> 8); - return crc ^ 0xFFFFFFFFu; -} - -/*** chacha20 ***/ - -#define KON_ROTL32_(v, n) (((v) << (n)) | ((v) >> (32 - (n)))) - -#define KON_QUARTER_(a, b, c, d) \ - a += b; d ^= a; d = KON_ROTL32_(d, 16); \ - c += d; b ^= c; b = KON_ROTL32_(b, 12); \ - a += b; d ^= a; d = KON_ROTL32_(d, 8); \ - c += d; b ^= c; b = KON_ROTL32_(b, 7); - -static void kon_chachaBlock_(const uint32_t state[16], uint8_t out[64]) { - uint32_t x[16]; - - for (int i = 0; i < 16; i++) x[i] = state[i]; - - for (int round = 0; round < 10; round++) { - KON_QUARTER_(x[0], x[4], x[8], x[12]) - KON_QUARTER_(x[1], x[5], x[9], x[13]) - KON_QUARTER_(x[2], x[6], x[10], x[14]) - KON_QUARTER_(x[3], x[7], x[11], x[15]) - KON_QUARTER_(x[0], x[5], x[10], x[15]) - KON_QUARTER_(x[1], x[6], x[11], x[12]) - KON_QUARTER_(x[2], x[7], x[8], x[13]) - KON_QUARTER_(x[3], x[4], x[9], x[14]) - } - - for (int i = 0; i < 16; i++) kon_store32_(&out[i * 4], x[i] + state[i]); -} - -void kon_chacha20Xor(const uint8_t key[KON_PACK_KEY_SIZE], const uint8_t nonce[12], uint32_t counter, const uint8_t *in, uint8_t *out, size_t length) { - uint32_t state[16]; - uint8_t block[64]; - - state[0] = 0x61707865u; - state[1] = 0x3320646Eu; - state[2] = 0x79622D32u; - state[3] = 0x6B206574u; - for (int i = 0; i < 8; i++) state[4 + i] = kon_load32_(&key[i * 4]); - state[12] = counter; - for (int i = 0; i < 3; i++) state[13 + i] = kon_load32_(&nonce[i * 4]); - - while (length > 0) { - size_t n = length < 64 ? length : 64; - - kon_chachaBlock_(state, block); - for (size_t i = 0; i < n; i++) out[i] = (uint8_t)(in[i] ^ block[i]); - - in += n; - out += n; - length -= n; - state[12]++; - } -} - -static void kon_fileNonce_(const uint8_t packNonce[8], uint32_t index, uint8_t out[12]) { - memcpy(out, packNonce, 8); - kon_store32_(&out[8], index); -} - -/*** reading ***/ - -typedef struct kon_packEntry { - const char *name; - uint32_t offset, size, crc; -} kon_packEntry_t; - -struct kon_pack { - FILE *file; - uint8_t key[KON_PACK_KEY_SIZE]; - uint8_t nonce[8]; - int encrypted; - int count; - kon_packEntry_t *entries; - char *names; -}; - -void kon_packClose(kon_pack_t *pack) { - if (!pack) return; - - if (pack->file) fclose(pack->file); - free(pack->entries); - free(pack->names); - free(pack); -} - -kon_pack_t *kon_packOpen(const char *path, const uint8_t *key) { - uint8_t header[KON_PACK_HEADER_SIZE_]; - uint8_t *table = NULL; - kon_pack_t *pack = NULL; - uint32_t flags, count, tableOffset, tableSize, tableCrc; - uint64_t fileSize; - long end; - - if (!path) { - kon_packErrorText_ = "no path"; - return NULL; - } - - pack = calloc(1, sizeof(kon_pack_t)); - if (!pack) { - kon_packErrorText_ = "out of memory"; - return NULL; - } - - pack->file = fopen(path, "rb"); - if (!pack->file) { - kon_packErrorText_ = "could not open the pack file"; - goto fail; - } - - if (fread(header, 1, sizeof(header), pack->file) != sizeof(header)) { - kon_packErrorText_ = "pack is too small"; - goto fail; - } - if (memcmp(header, KON_PACK_MAGIC_, 4) != 0) { - kon_packErrorText_ = "not a pack file"; - goto fail; - } - if (kon_load32_(&header[4]) != KON_PACK_VERSION_) { - kon_packErrorText_ = "unsupported pack version"; - goto fail; - } - - flags = kon_load32_(&header[8]); - count = kon_load32_(&header[12]); - tableOffset = kon_load32_(&header[16]); - tableSize = kon_load32_(&header[20]); - tableCrc = kon_load32_(&header[24]); - memcpy(pack->nonce, &header[28], 8); - - pack->encrypted = (flags & KON_PACK_FLAG_ENCRYPTED_) != 0; - if (pack->encrypted) { - if (!key) { - kon_packErrorText_ = "pack is encrypted and no key was given"; - goto fail; - } - memcpy(pack->key, key, KON_PACK_KEY_SIZE); - } - - if (fseek(pack->file, 0, SEEK_END) != 0 || (end = ftell(pack->file)) < 0) { - kon_packErrorText_ = "could not get the pack size"; - goto fail; - } - fileSize = (uint64_t)end; - - if (tableOffset < KON_PACK_HEADER_SIZE_ || (uint64_t)tableOffset + tableSize > fileSize || - (uint64_t)count * 15u > tableSize || count > 0x7FFFFFFFu) { - kon_packErrorText_ = "pack table is damaged"; - goto fail; - } - - table = malloc(tableSize ? tableSize : 1); - pack->entries = calloc(count ? count : 1, sizeof(kon_packEntry_t)); - pack->names = malloc((size_t)tableSize + count + 1); - if (!table || !pack->entries || !pack->names) { - kon_packErrorText_ = "out of memory"; - goto fail; - } - - if (fseek(pack->file, (long)tableOffset, SEEK_SET) != 0 || fread(table, 1, tableSize, pack->file) != tableSize) { - kon_packErrorText_ = "could not read the pack table"; - goto fail; - } - - if (pack->encrypted) { - uint8_t nonce[12]; - kon_fileNonce_(pack->nonce, KON_PACK_TABLE_INDEX_, nonce); - kon_chacha20Xor(pack->key, nonce, 0, table, table, tableSize); - } - - if (kon_crc32(table, tableSize) != tableCrc) { - kon_packErrorText_ = pack->encrypted ? "wrong key or damaged pack" : "pack table is damaged"; - goto fail; - } - - { - size_t at = 0, nameAt = 0; - - for (uint32_t i = 0; i < count; i++) { - size_t nameLength; - - if (at + 2 > tableSize) goto damaged; - nameLength = (size_t)table[at] | ((size_t)table[at + 1] << 8); - at += 2; - if (nameLength == 0 || at + nameLength + 12 > tableSize) goto damaged; - - memcpy(&pack->names[nameAt], &table[at], nameLength); - pack->names[nameAt + nameLength] = '\0'; - if (strlen(&pack->names[nameAt]) != nameLength) goto damaged; - pack->entries[i].name = &pack->names[nameAt]; - nameAt += nameLength + 1; - at += nameLength; - - pack->entries[i].offset = kon_load32_(&table[at]); - pack->entries[i].size = kon_load32_(&table[at + 4]); - pack->entries[i].crc = kon_load32_(&table[at + 8]); - at += 12; - - if (pack->entries[i].offset < KON_PACK_HEADER_SIZE_ || - (uint64_t)pack->entries[i].offset + pack->entries[i].size > tableOffset) goto damaged; - if (i > 0 && strcmp(pack->entries[i - 1].name, pack->entries[i].name) >= 0) goto damaged; - } - if (at != tableSize) goto damaged; - } - - pack->count = (int)count; - free(table); - return pack; - -damaged: - kon_packErrorText_ = "pack table is damaged"; -fail: - free(table); - kon_packClose(pack); - return NULL; -} - -int kon_packCount(const kon_pack_t *pack) { - return pack ? pack->count : 0; -} - -const char *kon_packName(const kon_pack_t *pack, int index) { - if (!pack || index < 0 || index >= pack->count) return NULL; - return pack->entries[index].name; -} - -uint32_t kon_packSize(const kon_pack_t *pack, int index) { - if (!pack || index < 0 || index >= pack->count) return 0; - return pack->entries[index].size; -} - -int kon_packFind(const kon_pack_t *pack, const char *name) { - int low = 0, high; - - if (!pack || !name) return -1; - high = pack->count - 1; - - while (low <= high) { - int mid = low + (high - low) / 2; - int cmp = strcmp(name, pack->entries[mid].name); - - if (cmp == 0) return mid; - if (cmp < 0) high = mid - 1; - else low = mid + 1; - } - return -1; -} - -uint8_t *kon_packRead(kon_pack_t *pack, int index, size_t *size) { - kon_packEntry_t *entry; - uint8_t *data; - - if (size) *size = 0; - if (!pack || index < 0 || index >= pack->count) { - kon_packErrorText_ = "no such file in the pack"; - return NULL; - } - entry = &pack->entries[index]; - - data = malloc((size_t)entry->size + 1); - if (!data) { - kon_packErrorText_ = "out of memory"; - return NULL; - } - - if (fseek(pack->file, (long)entry->offset, SEEK_SET) != 0 || fread(data, 1, entry->size, pack->file) != entry->size) { - kon_packErrorText_ = "could not read from the pack"; - free(data); - return NULL; - } - - if (pack->encrypted) { - uint8_t nonce[12]; - kon_fileNonce_(pack->nonce, (uint32_t)index, nonce); - kon_chacha20Xor(pack->key, nonce, 0, data, data, entry->size); - } - - if (kon_crc32(data, entry->size) != entry->crc) { - kon_packErrorText_ = "file is damaged"; - free(data); - return NULL; - } - - data[entry->size] = 0; - if (size) *size = entry->size; - return data; -} - -/*** writing ***/ - -#ifdef KONPACK_WRITER - -typedef struct kon_packSource_ { - const char *path; - char *name; -} kon_packSource_t; - -static int kon_packSourceCompare_(const void *a, const void *b) { - return strcmp(((const kon_packSource_t *)a)->name, ((const kon_packSource_t *)b)->name); -} - -static void kon_packMakeNonce_(uint8_t nonce[8]) { - FILE *f = fopen("/dev/urandom", "rb"); - - if (f) { - size_t got = fread(nonce, 1, 8, f); - fclose(f); - if (got == 8) return; - } - - /* nothing better around, mix what changes between runs. only has to never repeat, it is not secret */ - { - uint64_t h = 1469598103934665603ull; - uint64_t parts[3]; - int local; - - parts[0] = (uint64_t)time(NULL); - parts[1] = (uint64_t)clock(); - parts[2] = (uint64_t)(size_t)&local; - - for (int i = 0; i < 3; i++) { - for (int b = 0; b < 8; b++) { - h ^= (parts[i] >> (b * 8)) & 0xFF; - h *= 1099511628211ull; - } - } - for (int i = 0; i < 8; i++) nonce[i] = (uint8_t)(h >> (i * 8)); - } -} - -static char *kon_packMakeName_(const char *path, const char *baseDir) { - size_t baseLength = baseDir ? strlen(baseDir) : 0; - const char *rest = path; - char *name; - - if (baseLength > 0 && strncmp(path, baseDir, baseLength) == 0) { - rest = path + baseLength; - while (*rest == '/' || *rest == '\\') rest++; - } - while (rest[0] == '.' && (rest[1] == '/' || rest[1] == '\\')) rest += 2; - - name = malloc(strlen(rest) + 1); - if (!name) return NULL; - - strcpy(name, rest); - for (char *c = name; *c; c++) { - if (*c == '\\') *c = '/'; - } - return name; -} - -int kon_packWrite(const char *outPath, const uint8_t *key, const char *baseDir, const char *const *files, int count) { - kon_packSource_t *sources = NULL; - FILE *out = NULL; - uint8_t *table = NULL; - uint32_t *offsets = NULL, *sizes = NULL, *crcs = NULL; - uint8_t header[KON_PACK_HEADER_SIZE_]; - uint8_t packNonce[8]; - uint64_t position = KON_PACK_HEADER_SIZE_; - size_t tableSize = 0, at = 0; - int result = -1, made = 0; - - if (!outPath || !files || count < 0) { - kon_packErrorText_ = "bad arguments"; - return -1; - } - - sources = calloc(count ? (size_t)count : 1, sizeof(kon_packSource_t)); - offsets = calloc(count ? (size_t)count : 1, sizeof(uint32_t)); - sizes = calloc(count ? (size_t)count : 1, sizeof(uint32_t)); - crcs = calloc(count ? (size_t)count : 1, sizeof(uint32_t)); - if (!sources || !offsets || !sizes || !crcs) { - kon_packErrorText_ = "out of memory"; - goto done; - } - - for (int i = 0; i < count; i++) { - sources[i].path = files[i]; - sources[i].name = kon_packMakeName_(files[i], baseDir); - made++; - if (!sources[i].name || sources[i].name[0] == '\0' || strlen(sources[i].name) > 65535) { - kon_packErrorText_ = "bad file name"; - goto done; - } - } - - qsort(sources, (size_t)count, sizeof(kon_packSource_t), kon_packSourceCompare_); - for (int i = 1; i < count; i++) { - if (strcmp(sources[i - 1].name, sources[i].name) == 0) { - kon_packErrorText_ = "two files have the same name in the pack"; - goto done; - } - } - - kon_packMakeNonce_(packNonce); - - out = fopen(outPath, "wb"); - if (!out) { - kon_packErrorText_ = "could not create the pack file"; - goto done; - } - - memset(header, 0, sizeof(header)); - if (fwrite(header, 1, sizeof(header), out) != sizeof(header)) { - kon_packErrorText_ = "could not write the pack"; - goto done; - } - - for (int i = 0; i < count; i++) { - FILE *in = fopen(sources[i].path, "rb"); - uint8_t *data = NULL; - long length; - - if (!in) { - kon_packErrorText_ = "could not open an input file"; - goto done; - } - if (fseek(in, 0, SEEK_END) != 0 || (length = ftell(in)) < 0 || fseek(in, 0, SEEK_SET) != 0) { - kon_packErrorText_ = "could not get an input file size"; - fclose(in); - goto done; - } - if ((uint64_t)length > 0xFFFFFFFFu || position + (uint64_t)length > 0xFFFFFFFFu) { - kon_packErrorText_ = "pack would be over 4 GB"; - fclose(in); - goto done; - } - - data = malloc((size_t)length ? (size_t)length : 1); - if (!data || fread(data, 1, (size_t)length, in) != (size_t)length) { - kon_packErrorText_ = "could not read an input file"; - free(data); - fclose(in); - goto done; - } - fclose(in); - - offsets[i] = (uint32_t)position; - sizes[i] = (uint32_t)length; - crcs[i] = kon_crc32(data, (size_t)length); - - if (key) { - uint8_t nonce[12]; - kon_fileNonce_(packNonce, (uint32_t)i, nonce); - kon_chacha20Xor(key, nonce, 0, data, data, (size_t)length); - } - - if (fwrite(data, 1, (size_t)length, out) != (size_t)length) { - kon_packErrorText_ = "could not write the pack"; - free(data); - goto done; - } - free(data); - position += (uint64_t)length; - } - - for (int i = 0; i < count; i++) tableSize += 2 + strlen(sources[i].name) + 12; - if (position + tableSize > 0xFFFFFFFFu) { - kon_packErrorText_ = "pack would be over 4 GB"; - goto done; - } - - table = malloc(tableSize ? tableSize : 1); - if (!table) { - kon_packErrorText_ = "out of memory"; - goto done; - } - for (int i = 0; i < count; i++) { - size_t nameLength = strlen(sources[i].name); - - table[at] = (uint8_t)(nameLength & 0xFF); - table[at + 1] = (uint8_t)((nameLength >> 8) & 0xFF); - memcpy(&table[at + 2], sources[i].name, nameLength); - at += 2 + nameLength; - kon_store32_(&table[at], offsets[i]); - kon_store32_(&table[at + 4], sizes[i]); - kon_store32_(&table[at + 8], crcs[i]); - at += 12; - } - - memcpy(header, KON_PACK_MAGIC_, 4); - kon_store32_(&header[4], KON_PACK_VERSION_); - kon_store32_(&header[8], key ? KON_PACK_FLAG_ENCRYPTED_ : 0u); - kon_store32_(&header[12], (uint32_t)count); - kon_store32_(&header[16], (uint32_t)position); - kon_store32_(&header[20], (uint32_t)tableSize); - kon_store32_(&header[24], kon_crc32(table, tableSize)); - memcpy(&header[28], packNonce, 8); - - if (key) { - uint8_t nonce[12]; - kon_fileNonce_(packNonce, KON_PACK_TABLE_INDEX_, nonce); - kon_chacha20Xor(key, nonce, 0, table, table, tableSize); - } - - if (fwrite(table, 1, tableSize, out) != tableSize || fseek(out, 0, SEEK_SET) != 0 || - fwrite(header, 1, sizeof(header), out) != sizeof(header)) { - kon_packErrorText_ = "could not write the pack"; - goto done; - } - - result = 0; - -done: - if (out && fclose(out) != 0 && result == 0) { - kon_packErrorText_ = "could not write the pack"; - result = -1; - } - if (sources) { - for (int i = 0; i < made; i++) free(sources[i].name); - } - free(sources); - free(offsets); - free(sizes); - free(crcs); - free(table); - return result; -} - -#endif /* KONPACK_WRITER */ - -#endif /* KONPACK_IMPLEMENTATION */ - #endif diff --git a/kon/kontile.c b/kon/kontile.c new file mode 100644 index 0000000..b133656 --- /dev/null +++ b/kon/kontile.c @@ -0,0 +1,101 @@ +/* SPDX-License-Identifier: MIT */ +/* + * kontile.c + * + * Copyright (c) 2026 KonAki + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + + +#include "kontile.h" + +static int kon_floorDiv_(int a, int b) { + int q = a / b; + return (a % b != 0 && (a < 0) != (b < 0)) ? q - 1 : q; +} + +uint16_t kon_tilemapAt(const kon_tilemap_t *map, int column, int row) { + if (!map || !map->tiles) return 0; + if (column < 0 || row < 0 || column >= map->columns || row >= map->rows) return 0; + return map->tiles[(size_t)row * (size_t)map->columns + (size_t)column]; +} + +void kon_tilemapSet(kon_tilemap_t *map, int column, int row, uint16_t tile) { + if (!map || !map->tiles) return; + if (column < 0 || row < 0 || column >= map->columns || row >= map->rows) return; + map->tiles[(size_t)row * (size_t)map->columns + (size_t)column] = tile; +} + +int kon_tilemapPixelToTile(const kon_tilemap_t *map, int x, int y, int *column, int *row) { + if (!map || map->tileWidth <= 0 || map->tileHeight <= 0) return 0; + + int c = kon_floorDiv_(x, map->tileWidth); + int r = kon_floorDiv_(y, map->tileHeight); + if (column) *column = c; + if (row) *row = r; + + return c >= 0 && r >= 0 && c < map->columns && r < map->rows; +} + +int kon_tilemapBoxHits(const kon_tilemap_t *map, int x, int y, int width, int height, const uint8_t *solid, int solidCount) { + if (!map || !solid || solidCount <= 0 || map->tileWidth <= 0 || map->tileHeight <= 0 || width <= 0 || height <= 0) return 0; + + int firstColumn = kon_floorDiv_(x, map->tileWidth); + int firstRow = kon_floorDiv_(y, map->tileHeight); + int lastColumn = kon_floorDiv_(x + width - 1, map->tileWidth); + int lastRow = kon_floorDiv_(y + height - 1, map->tileHeight); + + for (int row = firstRow; row <= lastRow; row++) { + for (int column = firstColumn; column <= lastColumn; column++) { + int tile = kon_tilemapAt(map, column, row); + if (tile < solidCount && solid[tile]) return 1; + } + } + return 0; +} + +void kon_drawTilemap(kon_framebuffer_t *fb, const kon_tilemap_t *map, int cameraX, int cameraY) { + if (!fb || !map || !map->sheet || !map->tiles) return; + if (map->tileWidth <= 0 || map->tileHeight <= 0) return; + + int sheetColumns = map->sheetColumns > 0 ? map->sheetColumns : map->sheet->width / map->tileWidth; + if (sheetColumns <= 0) return; + + int firstColumn = kon_floorDiv_(cameraX, map->tileWidth); + int firstRow = kon_floorDiv_(cameraY, map->tileHeight); + int lastColumn = kon_floorDiv_(cameraX + fb->width - 1, map->tileWidth); + int lastRow = kon_floorDiv_(cameraY + fb->height - 1, map->tileHeight); + + if (firstColumn < 0) firstColumn = 0; + if (firstRow < 0) firstRow = 0; + if (lastColumn > map->columns - 1) lastColumn = map->columns - 1; + if (lastRow > map->rows - 1) lastRow = map->rows - 1; + + for (int row = firstRow; row <= lastRow; row++) { + for (int column = firstColumn; column <= lastColumn; column++) { + int tile = map->tiles[(size_t)row * (size_t)map->columns + (size_t)column]; + if (tile == 0) continue; + tile--; + + kon_rect_t src = KON_RECT((tile % sheetColumns) * map->tileWidth, (tile / sheetColumns) * map->tileHeight, map->tileWidth, map->tileHeight); + kon_drawImageRegion(fb, column * map->tileWidth - cameraX, row * map->tileHeight - cameraY, map->sheet, src); + } + } +} diff --git a/kon/kontile.h b/kon/kontile.h index d416d34..f671da9 100644 --- a/kon/kontile.h +++ b/kon/kontile.h @@ -55,84 +55,4 @@ int kon_tilemapBoxHits(const kon_tilemap_t *map, int x, int y, int width, int he /* cameraX, cameraY is the map pixel shown at the top left of fb */ void kon_drawTilemap(kon_framebuffer_t *fb, const kon_tilemap_t *map, int cameraX, int cameraY); -/*** implementation ***/ - -#ifdef KONTILE_IMPLEMENTATION - -static int kon_floorDiv_(int a, int b) { - int q = a / b; - return (a % b != 0 && (a < 0) != (b < 0)) ? q - 1 : q; -} - -uint16_t kon_tilemapAt(const kon_tilemap_t *map, int column, int row) { - if (!map || !map->tiles) return 0; - if (column < 0 || row < 0 || column >= map->columns || row >= map->rows) return 0; - return map->tiles[(size_t)row * (size_t)map->columns + (size_t)column]; -} - -void kon_tilemapSet(kon_tilemap_t *map, int column, int row, uint16_t tile) { - if (!map || !map->tiles) return; - if (column < 0 || row < 0 || column >= map->columns || row >= map->rows) return; - map->tiles[(size_t)row * (size_t)map->columns + (size_t)column] = tile; -} - -int kon_tilemapPixelToTile(const kon_tilemap_t *map, int x, int y, int *column, int *row) { - if (!map || map->tileWidth <= 0 || map->tileHeight <= 0) return 0; - - int c = kon_floorDiv_(x, map->tileWidth); - int r = kon_floorDiv_(y, map->tileHeight); - if (column) *column = c; - if (row) *row = r; - - return c >= 0 && r >= 0 && c < map->columns && r < map->rows; -} - -int kon_tilemapBoxHits(const kon_tilemap_t *map, int x, int y, int width, int height, const uint8_t *solid, int solidCount) { - if (!map || !solid || solidCount <= 0 || map->tileWidth <= 0 || map->tileHeight <= 0 || width <= 0 || height <= 0) return 0; - - int firstColumn = kon_floorDiv_(x, map->tileWidth); - int firstRow = kon_floorDiv_(y, map->tileHeight); - int lastColumn = kon_floorDiv_(x + width - 1, map->tileWidth); - int lastRow = kon_floorDiv_(y + height - 1, map->tileHeight); - - for (int row = firstRow; row <= lastRow; row++) { - for (int column = firstColumn; column <= lastColumn; column++) { - int tile = kon_tilemapAt(map, column, row); - if (tile < solidCount && solid[tile]) return 1; - } - } - return 0; -} - -void kon_drawTilemap(kon_framebuffer_t *fb, const kon_tilemap_t *map, int cameraX, int cameraY) { - if (!fb || !map || !map->sheet || !map->tiles) return; - if (map->tileWidth <= 0 || map->tileHeight <= 0) return; - - int sheetColumns = map->sheetColumns > 0 ? map->sheetColumns : map->sheet->width / map->tileWidth; - if (sheetColumns <= 0) return; - - int firstColumn = kon_floorDiv_(cameraX, map->tileWidth); - int firstRow = kon_floorDiv_(cameraY, map->tileHeight); - int lastColumn = kon_floorDiv_(cameraX + fb->width - 1, map->tileWidth); - int lastRow = kon_floorDiv_(cameraY + fb->height - 1, map->tileHeight); - - if (firstColumn < 0) firstColumn = 0; - if (firstRow < 0) firstRow = 0; - if (lastColumn > map->columns - 1) lastColumn = map->columns - 1; - if (lastRow > map->rows - 1) lastRow = map->rows - 1; - - for (int row = firstRow; row <= lastRow; row++) { - for (int column = firstColumn; column <= lastColumn; column++) { - int tile = map->tiles[(size_t)row * (size_t)map->columns + (size_t)column]; - if (tile == 0) continue; - tile--; - - kon_rect_t src = KON_RECT((tile % sheetColumns) * map->tileWidth, (tile / sheetColumns) * map->tileHeight, map->tileWidth, map->tileHeight); - kon_drawImageRegion(fb, column * map->tileWidth - cameraX, row * map->tileHeight - cameraY, map->sheet, src); - } - } -} - -#endif /* KONTILE_IMPLEMENTATION */ - #endif diff --git a/kon/konui.c b/kon/konui.c new file mode 100644 index 0000000..777f24d --- /dev/null +++ b/kon/konui.c @@ -0,0 +1,556 @@ +/* SPDX-License-Identifier: MIT */ +/* + * konui.c + * + * Copyright (c) 2026 KonAki + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + + +#include "konui.h" + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <stdarg.h> + +#define KON_UI_IDS 16 +#define KON_UI_TEXTQUEUE 32 + +struct kon_ui { + kon_font_t *font; + kon_framebuffer_t *fb; + kon_uiTheme_t theme; + int lineH, ascent; + + int mouseX, mouseY; + int mouseDown, mousePressed, mouseReleased; + int keyPressed[KON_KEY_COUNT]; + unsigned int text[KON_UI_TEXTQUEUE]; + int textCount; + + uint32_t active, focus; + uint32_t topPanel, topPanelNext; + int wantsMouse; + int focusKept; + int dragX, dragY; + + uint32_t ids[KON_UI_IDS]; + int idCount; + + kon_uiPanel_t *panel; + uint32_t panelId; + int cx, cy, availW; + int sameLine; + kon_rect_t last; + int cursor; +}; + +/*** private helpers ***/ + +static uint32_t kon_uiHash_(const kon_ui_t *ui, const char *s) +{ + uint32_t h = ui->idCount ? ui->ids[ui->idCount - 1] : 2166136261u; + + while (*s) { + h ^= (unsigned char)*s++; + h *= 16777619u; + } + return h ? h : 1; +} + +static size_t kon_uiVisibleLength_(const char *s) +{ + size_t n = 0; + + while (s[n]) { + if (s[n] == '#' && s[n + 1] == '#') break; + n++; + } + return n; +} + +static int kon_uiInside_(const kon_ui_t *ui, kon_rect_t r) +{ + return ui->mouseX >= r.x && ui->mouseX < r.x + r.w && ui->mouseY >= r.y && ui->mouseY < r.y + r.h; +} + +/* the mouse can only reach this widget if its panel is the topmost one under it */ +static int kon_uiReachable_(const kon_ui_t *ui) +{ + return ui->topPanel == ui->panelId; +} + +static int kon_uiTextWidth_(const kon_ui_t *ui, const char *s, size_t n) +{ + char buffer[256]; + int w = 0; + + if (n > sizeof(buffer) - 1) n = sizeof(buffer) - 1; + memcpy(buffer, s, n); + buffer[n] = 0; + kon_measureText(ui->font, buffer, &w, NULL); + return w; +} + +static void kon_uiDrawText_(const kon_ui_t *ui, int x, int y, const char *s, size_t n, uint32_t color) +{ + char buffer[256]; + + if (n > sizeof(buffer) - 1) n = sizeof(buffer) - 1; + memcpy(buffer, s, n); + buffer[n] = 0; + kon_drawText(ui->fb, ui->font, x, y + ui->ascent, buffer, color); +} + +static kon_rect_t kon_uiNextRect_(kon_ui_t *ui, int w, int h) +{ + kon_rect_t r; + + if (ui->sameLine) { + r.x = ui->last.x + ui->last.w + ui->theme.spacing; + r.y = ui->last.y; + } else { + r.x = ui->cx; + r.y = ui->cy; + } + r.w = w > 0 ? w : ui->availW; + r.h = h; + + if (!ui->sameLine) ui->cy += h + ui->theme.spacing; + ui->sameLine = 0; + ui->last = r; + return r; +} + +static void kon_uiFill_(const kon_ui_t *ui, kon_rect_t r, uint32_t color) +{ + kon_fillRectangle(ui->fb, r.x, r.y, r.w, r.h, color); +} + +static void kon_uiOutline_(const kon_ui_t *ui, kon_rect_t r, uint32_t color) +{ + kon_drawRectangle(ui->fb, r.x, r.y, r.w - 1, r.h - 1, color); +} + +/* hover and press handling shared by everything clickable. returns 1 when it was clicked */ +static int kon_uiBehave_(kon_ui_t *ui, uint32_t id, kon_rect_t r, int *hovered) +{ + int over = kon_uiReachable_(ui) && kon_uiInside_(ui, r) && (ui->active == 0 || ui->active == id); + int clicked = 0; + + if (over && ui->mousePressed) ui->active = id; + if (ui->active == id && ui->mouseReleased) { + clicked = over; + ui->active = 0; + } + if (hovered) *hovered = over; + return clicked; +} + +static uint32_t kon_uiWidgetColor_(const kon_ui_t *ui, uint32_t id, int hovered) +{ + if (ui->active == id) return ui->theme.active; + return hovered ? ui->theme.hover : ui->theme.widget; +} + +/*** ui ***/ + +kon_ui_t *kon_uiCreate(kon_font_t *font) +{ + kon_ui_t *ui; + int h = 0; + + if (!font) return NULL; + ui = calloc(1, sizeof(kon_ui_t)); + if (!ui) return NULL; + + ui->font = font; + kon_measureText(font, "A", NULL, &h); + ui->ascent = h * 4 / 5; + ui->lineH = h + 4; + + ui->theme.panel = 0xE0202028; + ui->theme.title = 0xFF383848; + ui->theme.text = 0xFFE8E8F0; + ui->theme.textDim = 0xFF8888A0; + ui->theme.widget = 0xFF404058; + ui->theme.hover = 0xFF505070; + ui->theme.active = 0xFF6060A0; + ui->theme.accent = 0xFF7088E0; + ui->theme.border = 0xFF101018; + ui->theme.padding = 4; + ui->theme.spacing = 3; + return ui; +} + +void kon_uiDestroy(kon_ui_t *ui) +{ + free(ui); +} + +kon_uiTheme_t *kon_uiTheme(kon_ui_t *ui) +{ + return &ui->theme; +} + +void kon_uiFeedEvent(kon_ui_t *ui, const kon_event_t *event) +{ + switch (event->type) { + case KON_EVENT_MOUSE_MOVE: + ui->mouseX = event->mouseX; + ui->mouseY = event->mouseY; + break; + case KON_EVENT_MOUSE_DOWN: + ui->mouseX = event->mouseX; + ui->mouseY = event->mouseY; + if (event->mouseButton == KON_MOUSE_LEFT) { + ui->mouseDown = 1; + ui->mousePressed = 1; + } + break; + case KON_EVENT_MOUSE_UP: + ui->mouseX = event->mouseX; + ui->mouseY = event->mouseY; + if (event->mouseButton == KON_MOUSE_LEFT) { + ui->mouseDown = 0; + ui->mouseReleased = 1; + } + break; + case KON_EVENT_KEY_DOWN: + if (event->key >= 0 && event->key < KON_KEY_COUNT) ui->keyPressed[event->key] = 1; + break; + case KON_EVENT_TEXT: + if (ui->textCount < KON_UI_TEXTQUEUE) ui->text[ui->textCount++] = event->codepoint; + break; + default: + break; + } +} + +void kon_uiBegin(kon_ui_t *ui, kon_framebuffer_t *fb) +{ + ui->fb = fb; + ui->topPanel = ui->topPanelNext; + ui->topPanelNext = 0; + ui->focusKept = 0; + ui->panel = NULL; + ui->panelId = 0; + ui->idCount = 0; + ui->sameLine = 0; + ui->cx = ui->theme.padding; + ui->cy = ui->theme.padding; + ui->availW = fb->width - ui->theme.padding * 2; +} + +void kon_uiEnd(kon_ui_t *ui) +{ + if (ui->mousePressed && !ui->focusKept) ui->focus = 0; + if (!ui->mouseDown && ui->active) ui->active = 0; + + ui->wantsMouse = ui->topPanelNext != 0 || ui->active != 0; + + ui->mousePressed = 0; + ui->mouseReleased = 0; + memset(ui->keyPressed, 0, sizeof(ui->keyPressed)); + ui->textCount = 0; + ui->fb = NULL; +} + +int kon_uiWantsMouse(const kon_ui_t *ui) +{ + return ui->wantsMouse; +} + +int kon_uiWantsKeyboard(const kon_ui_t *ui) +{ + return ui->focus != 0; +} + +void kon_uiPushId(kon_ui_t *ui, int id) +{ + uint32_t h = ui->idCount ? ui->ids[ui->idCount - 1] : 2166136261u; + + if (ui->idCount == KON_UI_IDS) return; + h ^= (uint32_t)id; + h *= 16777619u; + ui->ids[ui->idCount++] = h; +} + +void kon_uiPopId(kon_ui_t *ui) +{ + if (ui->idCount) ui->idCount--; +} + +kon_rect_t kon_uiLastRect(const kon_ui_t *ui) +{ + return ui->last; +} + +/*** panels ***/ + +int kon_uiBeginPanel(kon_ui_t *ui, const char *title, kon_uiPanel_t *panel) +{ + uint32_t id = kon_uiHash_(ui, title); + uint32_t dragId = id ^ 0x9E3779B9u; + int pad = ui->theme.padding, titleH = ui->lineH + pad; + kon_rect_t whole, bar, arrow; + int h, hovered; + + if (ui->panel) return 0; + if (panel->w <= 0) panel->w = 220; + + h = panel->collapsed ? titleH : (panel->h > 0 ? panel->h : panel->contentH); + if (h < titleH) h = titleH; + + whole = KON_RECT(panel->x, panel->y, panel->w, h); + bar = KON_RECT(panel->x, panel->y, panel->w, titleH); + arrow = KON_RECT(panel->x, panel->y, titleH, titleH); + + if (kon_uiInside_(ui, whole)) ui->topPanelNext = id; + + ui->panelId = id; + ui->panel = panel; + + if (kon_uiBehave_(ui, id ^ 1u, arrow, &hovered)) panel->collapsed = !panel->collapsed; + + if (ui->active == 0 && ui->mousePressed && kon_uiReachable_(ui) && kon_uiInside_(ui, bar) && !kon_uiInside_(ui, arrow)) { + ui->active = dragId; + ui->dragX = ui->mouseX - panel->x; + ui->dragY = ui->mouseY - panel->y; + } + if (ui->active == dragId) { + panel->x = ui->mouseX - ui->dragX; + panel->y = ui->mouseY - ui->dragY; + if (panel->x < 40 - panel->w) panel->x = 40 - panel->w; + if (panel->x > ui->fb->width - 40) panel->x = ui->fb->width - 40; + if (panel->y < 0) panel->y = 0; + if (panel->y > ui->fb->height - titleH) panel->y = ui->fb->height - titleH; + if (ui->mouseReleased) ui->active = 0; + whole.x = bar.x = arrow.x = panel->x; + whole.y = bar.y = arrow.y = panel->y; + } + + kon_uiFill_(ui, whole, ui->theme.panel); + kon_uiFill_(ui, bar, ui->theme.title); + kon_uiOutline_(ui, whole, ui->theme.border); + kon_uiDrawText_(ui, arrow.x + pad, arrow.y + pad / 2, panel->collapsed ? "+" : "-", 1, ui->theme.textDim); + kon_uiDrawText_(ui, panel->x + titleH, panel->y + pad / 2, title, kon_uiVisibleLength_(title), ui->theme.text); + + ui->cx = panel->x + pad; + ui->cy = panel->y + titleH + pad; + ui->availW = panel->w - pad * 2; + ui->sameLine = 0; + return !panel->collapsed; +} + +void kon_uiEndPanel(kon_ui_t *ui) +{ + if (!ui->panel) return; + ui->panel->contentH = ui->cy - ui->panel->y + ui->theme.padding - ui->theme.spacing; + ui->panel = NULL; + ui->panelId = 0; + ui->cx = ui->theme.padding; + ui->cy = ui->theme.padding; + ui->availW = ui->fb->width - ui->theme.padding * 2; +} + +/*** widgets ***/ + +void kon_uiLabel(kon_ui_t *ui, const char *text) +{ + size_t n = kon_uiVisibleLength_(text); + kon_rect_t r = kon_uiNextRect_(ui, kon_uiTextWidth_(ui, text, n) + 1, ui->lineH); + + kon_uiDrawText_(ui, r.x, r.y, text, n, ui->theme.text); +} + +void kon_uiLabelf(kon_ui_t *ui, const char *format, ...) +{ + char buffer[256]; + va_list args; + + va_start(args, format); + vsnprintf(buffer, sizeof(buffer), format, args); + va_end(args); + kon_uiLabel(ui, buffer); +} + +int kon_uiButton(kon_ui_t *ui, const char *label) +{ + uint32_t id = kon_uiHash_(ui, label); + size_t n = kon_uiVisibleLength_(label); + int pad = ui->theme.padding, textW = kon_uiTextWidth_(ui, label, n); + kon_rect_t r = kon_uiNextRect_(ui, ui->sameLine ? textW + pad * 2 : 0, ui->lineH + pad); + int hovered, clicked; + + clicked = kon_uiBehave_(ui, id, r, &hovered); + kon_uiFill_(ui, r, kon_uiWidgetColor_(ui, id, hovered)); + kon_uiOutline_(ui, r, ui->theme.border); + kon_uiDrawText_(ui, r.x + (r.w - textW) / 2, r.y + pad / 2 + 1, label, n, ui->theme.text); + return clicked; +} + +int kon_uiCheckbox(kon_ui_t *ui, const char *label, int *value) +{ + uint32_t id = kon_uiHash_(ui, label); + size_t n = kon_uiVisibleLength_(label); + int box = ui->lineH, pad = ui->theme.padding; + kon_rect_t r = kon_uiNextRect_(ui, box + pad + kon_uiTextWidth_(ui, label, n) + 1, box); + kon_rect_t b = KON_RECT(r.x, r.y, box, box); + int hovered, clicked = kon_uiBehave_(ui, id, r, &hovered); + + if (clicked) *value = !*value; + kon_uiFill_(ui, b, kon_uiWidgetColor_(ui, id, hovered)); + kon_uiOutline_(ui, b, ui->theme.border); + if (*value) kon_uiFill_(ui, KON_RECT(b.x + 3, b.y + 3, b.w - 6, b.h - 6), ui->theme.accent); + kon_uiDrawText_(ui, r.x + box + pad, r.y, label, n, ui->theme.text); + return clicked; +} + +int kon_uiSliderFloat(kon_ui_t *ui, const char *label, float *value, float min, float max) +{ + uint32_t id = kon_uiHash_(ui, label); + size_t n = kon_uiVisibleLength_(label); + int pad = ui->theme.padding; + kon_rect_t r = kon_uiNextRect_(ui, 0, ui->lineH + pad); + char text[64]; + int hovered, changed = 0, fill; + float t; + + kon_uiBehave_(ui, id, r, &hovered); + + if (ui->active == id && r.w > 1 && max > min) { + float v; + + t = (float)(ui->mouseX - r.x) / (float)(r.w - 1); + if (t < 0.0f) t = 0.0f; + if (t > 1.0f) t = 1.0f; + v = min + t * (max - min); + if (v != *value) { + *value = v; + changed = 1; + } + } + + t = max > min ? (*value - min) / (max - min) : 0.0f; + if (t < 0.0f) t = 0.0f; + if (t > 1.0f) t = 1.0f; + fill = (int)(t * (float)(r.w - 2)); + + kon_uiFill_(ui, r, kon_uiWidgetColor_(ui, id, hovered)); + kon_uiFill_(ui, KON_RECT(r.x + 1, r.y + 1, fill, r.h - 2), ui->theme.accent); + kon_uiOutline_(ui, r, ui->theme.border); + + snprintf(text, sizeof(text), "%.*s: %.2f", (int)(n > 40 ? 40 : n), label, (double)*value); + kon_uiDrawText_(ui, r.x + pad, r.y + pad / 2 + 1, text, strlen(text), ui->theme.text); + return changed; +} + +int kon_uiSliderInt(kon_ui_t *ui, const char *label, int *value, int min, int max) +{ + float v = (float)*value; + int changed = kon_uiSliderFloat(ui, label, &v, (float)min, (float)max); + + if (changed) *value = (int)(v + (v < 0 ? -0.5f : 0.5f)); + return changed; +} + +int kon_uiSelectable(kon_ui_t *ui, const char *label, int selected) +{ + uint32_t id = kon_uiHash_(ui, label); + size_t n = kon_uiVisibleLength_(label); + int pad = ui->theme.padding; + kon_rect_t r = kon_uiNextRect_(ui, 0, ui->lineH); + int hovered, clicked = kon_uiBehave_(ui, id, r, &hovered); + + if (selected) kon_uiFill_(ui, r, ui->theme.active); + else if (hovered) kon_uiFill_(ui, r, ui->theme.hover); + kon_uiDrawText_(ui, r.x + pad, r.y, label, n, ui->theme.text); + return clicked; +} + +int kon_uiTextInput(kon_ui_t *ui, const char *label, char *buffer, size_t size) +{ + uint32_t id = kon_uiHash_(ui, label); + size_t n = kon_uiVisibleLength_(label); + int pad = ui->theme.padding, hovered, entered = 0, i; + size_t length = strlen(buffer); + kon_rect_t r = kon_uiNextRect_(ui, 0, ui->lineH + pad); + + kon_uiBehave_(ui, id, r, &hovered); + if (hovered && ui->mousePressed) { + ui->focus = id; + ui->cursor = (int)length; + } + if (ui->focus == id) { + ui->focusKept = ui->focusKept || (hovered && ui->mousePressed); + if (ui->cursor > (int)length) ui->cursor = (int)length; + + for (i = 0; i < ui->textCount; i++) { + unsigned int c = ui->text[i]; + + if (c < 32 || c > 126 || length + 1 >= size) continue; + memmove(buffer + ui->cursor + 1, buffer + ui->cursor, length - (size_t)ui->cursor + 1); + buffer[ui->cursor++] = (char)c; + length++; + } + if (ui->keyPressed[KON_KEY_BACKSPACE] && ui->cursor > 0) { + memmove(buffer + ui->cursor - 1, buffer + ui->cursor, length - (size_t)ui->cursor + 1); + ui->cursor--; + length--; + } + if (ui->keyPressed[KON_KEY_DELETE] && ui->cursor < (int)length) { + memmove(buffer + ui->cursor, buffer + ui->cursor + 1, length - (size_t)ui->cursor); + length--; + } + if (ui->keyPressed[KON_KEY_LEFT] && ui->cursor > 0) ui->cursor--; + if (ui->keyPressed[KON_KEY_RIGHT] && ui->cursor < (int)length) ui->cursor++; + if (ui->keyPressed[KON_KEY_ENTER]) entered = 1; + if (ui->keyPressed[KON_KEY_ESCAPE]) ui->focus = 0; + } + + kon_uiFill_(ui, r, ui->focus == id ? ui->theme.hover : ui->theme.widget); + kon_uiOutline_(ui, r, ui->focus == id ? ui->theme.accent : ui->theme.border); + + if (length == 0 && ui->focus != id) { + kon_uiDrawText_(ui, r.x + pad, r.y + pad / 2 + 1, label, n, ui->theme.textDim); + } else { + kon_uiDrawText_(ui, r.x + pad, r.y + pad / 2 + 1, buffer, length, ui->theme.text); + if (ui->focus == id) { + int cx = r.x + pad + kon_uiTextWidth_(ui, buffer, (size_t)ui->cursor); + + kon_uiFill_(ui, KON_RECT(cx, r.y + 2, 1, r.h - 4), ui->theme.text); + } + } + return entered; +} + +void kon_uiSeparator(kon_ui_t *ui) +{ + kon_rect_t r = kon_uiNextRect_(ui, 0, 1); + + kon_uiFill_(ui, r, ui->theme.border); +} + +void kon_uiSameLine(kon_ui_t *ui) +{ + ui->sameLine = 1; +} diff --git a/kon/konui.h b/kon/konui.h index e03b7fe..1a5e581 100644 --- a/kon/konui.h +++ b/kon/konui.h @@ -87,538 +87,3 @@ void kon_uiPopId(kon_ui_t *ui); kon_rect_t kon_uiLastRect(const kon_ui_t *ui); #endif - -/*** implementation ***/ - -#ifdef KONUI_IMPLEMENTATION - -#include <stdlib.h> -#include <string.h> -#include <stdio.h> -#include <stdarg.h> - -#define KON_UI_IDS 16 -#define KON_UI_TEXTQUEUE 32 - -struct kon_ui { - kon_font_t *font; - kon_framebuffer_t *fb; - kon_uiTheme_t theme; - int lineH, ascent; - - int mouseX, mouseY; - int mouseDown, mousePressed, mouseReleased; - int keyPressed[KON_KEY_COUNT]; - unsigned int text[KON_UI_TEXTQUEUE]; - int textCount; - - uint32_t active, focus; - uint32_t topPanel, topPanelNext; - int wantsMouse; - int focusKept; - int dragX, dragY; - - uint32_t ids[KON_UI_IDS]; - int idCount; - - kon_uiPanel_t *panel; - uint32_t panelId; - int cx, cy, availW; - int sameLine; - kon_rect_t last; - int cursor; -}; - -/*** private helpers ***/ - -static uint32_t kon_uiHash_(const kon_ui_t *ui, const char *s) -{ - uint32_t h = ui->idCount ? ui->ids[ui->idCount - 1] : 2166136261u; - - while (*s) { - h ^= (unsigned char)*s++; - h *= 16777619u; - } - return h ? h : 1; -} - -static size_t kon_uiVisibleLength_(const char *s) -{ - size_t n = 0; - - while (s[n]) { - if (s[n] == '#' && s[n + 1] == '#') break; - n++; - } - return n; -} - -static int kon_uiInside_(const kon_ui_t *ui, kon_rect_t r) -{ - return ui->mouseX >= r.x && ui->mouseX < r.x + r.w && ui->mouseY >= r.y && ui->mouseY < r.y + r.h; -} - -/* the mouse can only reach this widget if its panel is the topmost one under it */ -static int kon_uiReachable_(const kon_ui_t *ui) -{ - return ui->topPanel == ui->panelId; -} - -static int kon_uiTextWidth_(const kon_ui_t *ui, const char *s, size_t n) -{ - char buffer[256]; - int w = 0; - - if (n > sizeof(buffer) - 1) n = sizeof(buffer) - 1; - memcpy(buffer, s, n); - buffer[n] = 0; - kon_measureText(ui->font, buffer, &w, NULL); - return w; -} - -static void kon_uiDrawText_(const kon_ui_t *ui, int x, int y, const char *s, size_t n, uint32_t color) -{ - char buffer[256]; - - if (n > sizeof(buffer) - 1) n = sizeof(buffer) - 1; - memcpy(buffer, s, n); - buffer[n] = 0; - kon_drawText(ui->fb, ui->font, x, y + ui->ascent, buffer, color); -} - -static kon_rect_t kon_uiNextRect_(kon_ui_t *ui, int w, int h) -{ - kon_rect_t r; - - if (ui->sameLine) { - r.x = ui->last.x + ui->last.w + ui->theme.spacing; - r.y = ui->last.y; - } else { - r.x = ui->cx; - r.y = ui->cy; - } - r.w = w > 0 ? w : ui->availW; - r.h = h; - - if (!ui->sameLine) ui->cy += h + ui->theme.spacing; - ui->sameLine = 0; - ui->last = r; - return r; -} - -static void kon_uiFill_(const kon_ui_t *ui, kon_rect_t r, uint32_t color) -{ - kon_fillRectangle(ui->fb, r.x, r.y, r.w, r.h, color); -} - -static void kon_uiOutline_(const kon_ui_t *ui, kon_rect_t r, uint32_t color) -{ - kon_drawRectangle(ui->fb, r.x, r.y, r.w - 1, r.h - 1, color); -} - -/* hover and press handling shared by everything clickable. returns 1 when it was clicked */ -static int kon_uiBehave_(kon_ui_t *ui, uint32_t id, kon_rect_t r, int *hovered) -{ - int over = kon_uiReachable_(ui) && kon_uiInside_(ui, r) && (ui->active == 0 || ui->active == id); - int clicked = 0; - - if (over && ui->mousePressed) ui->active = id; - if (ui->active == id && ui->mouseReleased) { - clicked = over; - ui->active = 0; - } - if (hovered) *hovered = over; - return clicked; -} - -static uint32_t kon_uiWidgetColor_(const kon_ui_t *ui, uint32_t id, int hovered) -{ - if (ui->active == id) return ui->theme.active; - return hovered ? ui->theme.hover : ui->theme.widget; -} - -/*** ui ***/ - -kon_ui_t *kon_uiCreate(kon_font_t *font) -{ - kon_ui_t *ui; - int h = 0; - - if (!font) return NULL; - ui = calloc(1, sizeof(kon_ui_t)); - if (!ui) return NULL; - - ui->font = font; - kon_measureText(font, "A", NULL, &h); - ui->ascent = h * 4 / 5; - ui->lineH = h + 4; - - ui->theme.panel = 0xE0202028; - ui->theme.title = 0xFF383848; - ui->theme.text = 0xFFE8E8F0; - ui->theme.textDim = 0xFF8888A0; - ui->theme.widget = 0xFF404058; - ui->theme.hover = 0xFF505070; - ui->theme.active = 0xFF6060A0; - ui->theme.accent = 0xFF7088E0; - ui->theme.border = 0xFF101018; - ui->theme.padding = 4; - ui->theme.spacing = 3; - return ui; -} - -void kon_uiDestroy(kon_ui_t *ui) -{ - free(ui); -} - -kon_uiTheme_t *kon_uiTheme(kon_ui_t *ui) -{ - return &ui->theme; -} - -void kon_uiFeedEvent(kon_ui_t *ui, const kon_event_t *event) -{ - switch (event->type) { - case KON_EVENT_MOUSE_MOVE: - ui->mouseX = event->mouseX; - ui->mouseY = event->mouseY; - break; - case KON_EVENT_MOUSE_DOWN: - ui->mouseX = event->mouseX; - ui->mouseY = event->mouseY; - if (event->mouseButton == KON_MOUSE_LEFT) { - ui->mouseDown = 1; - ui->mousePressed = 1; - } - break; - case KON_EVENT_MOUSE_UP: - ui->mouseX = event->mouseX; - ui->mouseY = event->mouseY; - if (event->mouseButton == KON_MOUSE_LEFT) { - ui->mouseDown = 0; - ui->mouseReleased = 1; - } - break; - case KON_EVENT_KEY_DOWN: - if (event->key >= 0 && event->key < KON_KEY_COUNT) ui->keyPressed[event->key] = 1; - break; - case KON_EVENT_TEXT: - if (ui->textCount < KON_UI_TEXTQUEUE) ui->text[ui->textCount++] = event->codepoint; - break; - default: - break; - } -} - -void kon_uiBegin(kon_ui_t *ui, kon_framebuffer_t *fb) -{ - ui->fb = fb; - ui->topPanel = ui->topPanelNext; - ui->topPanelNext = 0; - ui->focusKept = 0; - ui->panel = NULL; - ui->panelId = 0; - ui->idCount = 0; - ui->sameLine = 0; - ui->cx = ui->theme.padding; - ui->cy = ui->theme.padding; - ui->availW = fb->width - ui->theme.padding * 2; -} - -void kon_uiEnd(kon_ui_t *ui) -{ - if (ui->mousePressed && !ui->focusKept) ui->focus = 0; - if (!ui->mouseDown && ui->active) ui->active = 0; - - ui->wantsMouse = ui->topPanelNext != 0 || ui->active != 0; - - ui->mousePressed = 0; - ui->mouseReleased = 0; - memset(ui->keyPressed, 0, sizeof(ui->keyPressed)); - ui->textCount = 0; - ui->fb = NULL; -} - -int kon_uiWantsMouse(const kon_ui_t *ui) -{ - return ui->wantsMouse; -} - -int kon_uiWantsKeyboard(const kon_ui_t *ui) -{ - return ui->focus != 0; -} - -void kon_uiPushId(kon_ui_t *ui, int id) -{ - uint32_t h = ui->idCount ? ui->ids[ui->idCount - 1] : 2166136261u; - - if (ui->idCount == KON_UI_IDS) return; - h ^= (uint32_t)id; - h *= 16777619u; - ui->ids[ui->idCount++] = h; -} - -void kon_uiPopId(kon_ui_t *ui) -{ - if (ui->idCount) ui->idCount--; -} - -kon_rect_t kon_uiLastRect(const kon_ui_t *ui) -{ - return ui->last; -} - -/*** panels ***/ - -int kon_uiBeginPanel(kon_ui_t *ui, const char *title, kon_uiPanel_t *panel) -{ - uint32_t id = kon_uiHash_(ui, title); - uint32_t dragId = id ^ 0x9E3779B9u; - int pad = ui->theme.padding, titleH = ui->lineH + pad; - kon_rect_t whole, bar, arrow; - int h, hovered; - - if (ui->panel) return 0; - if (panel->w <= 0) panel->w = 220; - - h = panel->collapsed ? titleH : (panel->h > 0 ? panel->h : panel->contentH); - if (h < titleH) h = titleH; - - whole = KON_RECT(panel->x, panel->y, panel->w, h); - bar = KON_RECT(panel->x, panel->y, panel->w, titleH); - arrow = KON_RECT(panel->x, panel->y, titleH, titleH); - - if (kon_uiInside_(ui, whole)) ui->topPanelNext = id; - - ui->panelId = id; - ui->panel = panel; - - if (kon_uiBehave_(ui, id ^ 1u, arrow, &hovered)) panel->collapsed = !panel->collapsed; - - if (ui->active == 0 && ui->mousePressed && kon_uiReachable_(ui) && kon_uiInside_(ui, bar) && !kon_uiInside_(ui, arrow)) { - ui->active = dragId; - ui->dragX = ui->mouseX - panel->x; - ui->dragY = ui->mouseY - panel->y; - } - if (ui->active == dragId) { - panel->x = ui->mouseX - ui->dragX; - panel->y = ui->mouseY - ui->dragY; - if (panel->x < 40 - panel->w) panel->x = 40 - panel->w; - if (panel->x > ui->fb->width - 40) panel->x = ui->fb->width - 40; - if (panel->y < 0) panel->y = 0; - if (panel->y > ui->fb->height - titleH) panel->y = ui->fb->height - titleH; - if (ui->mouseReleased) ui->active = 0; - whole.x = bar.x = arrow.x = panel->x; - whole.y = bar.y = arrow.y = panel->y; - } - - kon_uiFill_(ui, whole, ui->theme.panel); - kon_uiFill_(ui, bar, ui->theme.title); - kon_uiOutline_(ui, whole, ui->theme.border); - kon_uiDrawText_(ui, arrow.x + pad, arrow.y + pad / 2, panel->collapsed ? "+" : "-", 1, ui->theme.textDim); - kon_uiDrawText_(ui, panel->x + titleH, panel->y + pad / 2, title, kon_uiVisibleLength_(title), ui->theme.text); - - ui->cx = panel->x + pad; - ui->cy = panel->y + titleH + pad; - ui->availW = panel->w - pad * 2; - ui->sameLine = 0; - return !panel->collapsed; -} - -void kon_uiEndPanel(kon_ui_t *ui) -{ - if (!ui->panel) return; - ui->panel->contentH = ui->cy - ui->panel->y + ui->theme.padding - ui->theme.spacing; - ui->panel = NULL; - ui->panelId = 0; - ui->cx = ui->theme.padding; - ui->cy = ui->theme.padding; - ui->availW = ui->fb->width - ui->theme.padding * 2; -} - -/*** widgets ***/ - -void kon_uiLabel(kon_ui_t *ui, const char *text) -{ - size_t n = kon_uiVisibleLength_(text); - kon_rect_t r = kon_uiNextRect_(ui, kon_uiTextWidth_(ui, text, n) + 1, ui->lineH); - - kon_uiDrawText_(ui, r.x, r.y, text, n, ui->theme.text); -} - -void kon_uiLabelf(kon_ui_t *ui, const char *format, ...) -{ - char buffer[256]; - va_list args; - - va_start(args, format); - vsnprintf(buffer, sizeof(buffer), format, args); - va_end(args); - kon_uiLabel(ui, buffer); -} - -int kon_uiButton(kon_ui_t *ui, const char *label) -{ - uint32_t id = kon_uiHash_(ui, label); - size_t n = kon_uiVisibleLength_(label); - int pad = ui->theme.padding, textW = kon_uiTextWidth_(ui, label, n); - kon_rect_t r = kon_uiNextRect_(ui, ui->sameLine ? textW + pad * 2 : 0, ui->lineH + pad); - int hovered, clicked; - - clicked = kon_uiBehave_(ui, id, r, &hovered); - kon_uiFill_(ui, r, kon_uiWidgetColor_(ui, id, hovered)); - kon_uiOutline_(ui, r, ui->theme.border); - kon_uiDrawText_(ui, r.x + (r.w - textW) / 2, r.y + pad / 2 + 1, label, n, ui->theme.text); - return clicked; -} - -int kon_uiCheckbox(kon_ui_t *ui, const char *label, int *value) -{ - uint32_t id = kon_uiHash_(ui, label); - size_t n = kon_uiVisibleLength_(label); - int box = ui->lineH, pad = ui->theme.padding; - kon_rect_t r = kon_uiNextRect_(ui, box + pad + kon_uiTextWidth_(ui, label, n) + 1, box); - kon_rect_t b = KON_RECT(r.x, r.y, box, box); - int hovered, clicked = kon_uiBehave_(ui, id, r, &hovered); - - if (clicked) *value = !*value; - kon_uiFill_(ui, b, kon_uiWidgetColor_(ui, id, hovered)); - kon_uiOutline_(ui, b, ui->theme.border); - if (*value) kon_uiFill_(ui, KON_RECT(b.x + 3, b.y + 3, b.w - 6, b.h - 6), ui->theme.accent); - kon_uiDrawText_(ui, r.x + box + pad, r.y, label, n, ui->theme.text); - return clicked; -} - -int kon_uiSliderFloat(kon_ui_t *ui, const char *label, float *value, float min, float max) -{ - uint32_t id = kon_uiHash_(ui, label); - size_t n = kon_uiVisibleLength_(label); - int pad = ui->theme.padding; - kon_rect_t r = kon_uiNextRect_(ui, 0, ui->lineH + pad); - char text[64]; - int hovered, changed = 0, fill; - float t; - - kon_uiBehave_(ui, id, r, &hovered); - - if (ui->active == id && r.w > 1 && max > min) { - float v; - - t = (float)(ui->mouseX - r.x) / (float)(r.w - 1); - if (t < 0.0f) t = 0.0f; - if (t > 1.0f) t = 1.0f; - v = min + t * (max - min); - if (v != *value) { - *value = v; - changed = 1; - } - } - - t = max > min ? (*value - min) / (max - min) : 0.0f; - if (t < 0.0f) t = 0.0f; - if (t > 1.0f) t = 1.0f; - fill = (int)(t * (float)(r.w - 2)); - - kon_uiFill_(ui, r, kon_uiWidgetColor_(ui, id, hovered)); - kon_uiFill_(ui, KON_RECT(r.x + 1, r.y + 1, fill, r.h - 2), ui->theme.accent); - kon_uiOutline_(ui, r, ui->theme.border); - - snprintf(text, sizeof(text), "%.*s: %.2f", (int)(n > 40 ? 40 : n), label, (double)*value); - kon_uiDrawText_(ui, r.x + pad, r.y + pad / 2 + 1, text, strlen(text), ui->theme.text); - return changed; -} - -int kon_uiSliderInt(kon_ui_t *ui, const char *label, int *value, int min, int max) -{ - float v = (float)*value; - int changed = kon_uiSliderFloat(ui, label, &v, (float)min, (float)max); - - if (changed) *value = (int)(v + (v < 0 ? -0.5f : 0.5f)); - return changed; -} - -int kon_uiSelectable(kon_ui_t *ui, const char *label, int selected) -{ - uint32_t id = kon_uiHash_(ui, label); - size_t n = kon_uiVisibleLength_(label); - int pad = ui->theme.padding; - kon_rect_t r = kon_uiNextRect_(ui, 0, ui->lineH); - int hovered, clicked = kon_uiBehave_(ui, id, r, &hovered); - - if (selected) kon_uiFill_(ui, r, ui->theme.active); - else if (hovered) kon_uiFill_(ui, r, ui->theme.hover); - kon_uiDrawText_(ui, r.x + pad, r.y, label, n, ui->theme.text); - return clicked; -} - -int kon_uiTextInput(kon_ui_t *ui, const char *label, char *buffer, size_t size) -{ - uint32_t id = kon_uiHash_(ui, label); - size_t n = kon_uiVisibleLength_(label); - int pad = ui->theme.padding, hovered, entered = 0, i; - size_t length = strlen(buffer); - kon_rect_t r = kon_uiNextRect_(ui, 0, ui->lineH + pad); - - kon_uiBehave_(ui, id, r, &hovered); - if (hovered && ui->mousePressed) { - ui->focus = id; - ui->cursor = (int)length; - } - if (ui->focus == id) { - ui->focusKept = ui->focusKept || (hovered && ui->mousePressed); - if (ui->cursor > (int)length) ui->cursor = (int)length; - - for (i = 0; i < ui->textCount; i++) { - unsigned int c = ui->text[i]; - - if (c < 32 || c > 126 || length + 1 >= size) continue; - memmove(buffer + ui->cursor + 1, buffer + ui->cursor, length - (size_t)ui->cursor + 1); - buffer[ui->cursor++] = (char)c; - length++; - } - if (ui->keyPressed[KON_KEY_BACKSPACE] && ui->cursor > 0) { - memmove(buffer + ui->cursor - 1, buffer + ui->cursor, length - (size_t)ui->cursor + 1); - ui->cursor--; - length--; - } - if (ui->keyPressed[KON_KEY_DELETE] && ui->cursor < (int)length) { - memmove(buffer + ui->cursor, buffer + ui->cursor + 1, length - (size_t)ui->cursor); - length--; - } - if (ui->keyPressed[KON_KEY_LEFT] && ui->cursor > 0) ui->cursor--; - if (ui->keyPressed[KON_KEY_RIGHT] && ui->cursor < (int)length) ui->cursor++; - if (ui->keyPressed[KON_KEY_ENTER]) entered = 1; - if (ui->keyPressed[KON_KEY_ESCAPE]) ui->focus = 0; - } - - kon_uiFill_(ui, r, ui->focus == id ? ui->theme.hover : ui->theme.widget); - kon_uiOutline_(ui, r, ui->focus == id ? ui->theme.accent : ui->theme.border); - - if (length == 0 && ui->focus != id) { - kon_uiDrawText_(ui, r.x + pad, r.y + pad / 2 + 1, label, n, ui->theme.textDim); - } else { - kon_uiDrawText_(ui, r.x + pad, r.y + pad / 2 + 1, buffer, length, ui->theme.text); - if (ui->focus == id) { - int cx = r.x + pad + kon_uiTextWidth_(ui, buffer, (size_t)ui->cursor); - - kon_uiFill_(ui, KON_RECT(cx, r.y + 2, 1, r.h - 4), ui->theme.text); - } - } - return entered; -} - -void kon_uiSeparator(kon_ui_t *ui) -{ - kon_rect_t r = kon_uiNextRect_(ui, 0, 1); - - kon_uiFill_(ui, r, ui->theme.border); -} - -void kon_uiSameLine(kon_ui_t *ui) -{ - ui->sameLine = 1; -} - -#endif /* KONUI_IMPLEMENTATION */ diff --git a/src/engine.c b/src/engine.c index 3dc442c..9e7b602 100644 --- a/src/engine.c +++ b/src/engine.c @@ -13,7 +13,7 @@ #define GAME_WIDTH 320 #define GAME_HEIGHT 180 -typedef struct app { +typedef struct program { kon_window_t *window; kon_display_t *display; kon_camera_t camera; @@ -28,39 +28,39 @@ typedef struct app { float shakeLeft; int baseX, baseY; #endif -} app_t; +} program_t; #ifdef KON_MODULE_DEBUG static void cmdOpacity(kon_debug_t *debug, void *user, int argc, char **argv) { - app_t *app = user; + program_t *program = user; (void)debug; - if (argc > 1) kon_setWindowOpacity(app->window, (float)atof(argv[1])); + if (argc > 1) kon_setWindowOpacity(program->window, (float)atof(argv[1])); } static void cmdTopmost(kon_debug_t *debug, void *user, int argc, char **argv) { - app_t *app = user; + program_t *program = user; (void)debug; - if (argc > 1) kon_setWindowTopmost(app->window, atoi(argv[1]) != 0); + if (argc > 1) kon_setWindowTopmost(program->window, atoi(argv[1]) != 0); } static void cmdBorderless(kon_debug_t *debug, void *user, int argc, char **argv) { - app_t *app = user; + program_t *program = user; (void)debug; - if (argc > 1) kon_setWindowDecorated(app->window, atoi(argv[1]) == 0); + if (argc > 1) kon_setWindowDecorated(program->window, atoi(argv[1]) == 0); } static void cmdClickThrough(kon_debug_t *debug, void *user, int argc, char **argv) { - app_t *app = user; + program_t *program = user; (void)debug; - if (argc > 1) kon_setWindowClickThrough(app->window, atoi(argv[1]) != 0); + if (argc > 1) kon_setWindowClickThrough(program->window, atoi(argv[1]) != 0); } static void cmdMessage(kon_debug_t *debug, void *user, int argc, char **argv) @@ -71,116 +71,116 @@ static void cmdMessage(kon_debug_t *debug, void *user, int argc, char **argv) static void cmdShake(kon_debug_t *debug, void *user, int argc, char **argv) { - app_t *app = user; + program_t *program = user; (void)debug; (void)argc; (void)argv; - kon_getWindowPos(app->window, &app->baseX, &app->baseY); - app->shakeLeft = 0.5f; + kon_getWindowPos(program->window, &program->baseX, &program->baseY); + program->shakeLeft = 0.5f; } #endif static void frame(void *data) { - app_t *app = (app_t *)data; - kon_display_t *display = app->display; + program_t *program = (program_t *)data; + kon_display_t *display = program->display; kon_event_t ev; kon_time now, dt; kon_mat4_t model, viewProjection; - while (kon_pollEvent(app->window, &ev)) { + while (kon_pollEvent(program->window, &ev)) { #ifdef KON_MODULE_DEBUG - if (kon_debugFeedEvent(app->debug, &ev)) continue; + if (kon_debugFeedEvent(program->debug, &ev)) continue; #endif if (ev.type == KON_EVENT_RESIZE) { kon_resizeDisplay(display, ev.width, ev.height); } if (ev.type == KON_EVENT_KEY_DOWN && ev.key == KON_KEY_O) { - app->camera.projection = (app->camera.projection == KON_PROJECTION_PERSPECTIVE) + program->camera.projection = (program->camera.projection == KON_PROJECTION_PERSPECTIVE) ? KON_PROJECTION_ORTHOGRAPHIC : KON_PROJECTION_PERSPECTIVE; } } now = kon_getTime(); - dt = now - app->last; - app->angle += (float)dt; - app->last = now; + dt = now - program->last; + program->angle += (float)dt; + program->last = now; #ifdef KON_MODULE_DEBUG - if (app->shakeLeft > 0.0f) { - app->shakeLeft -= (float)dt; - if (app->shakeLeft > 0.0f) kon_setWindowPos(app->window, app->baseX + rand() % 21 - 10, app->baseY + rand() % 21 - 10); - else kon_setWindowPos(app->window, app->baseX, app->baseY); + if (program->shakeLeft > 0.0f) { + program->shakeLeft -= (float)dt; + if (program->shakeLeft > 0.0f) kon_setWindowPos(program->window, program->baseX + rand() % 21 - 10, program->baseY + rand() % 21 - 10); + else kon_setWindowPos(program->window, program->baseX, program->baseY); } #endif model = kon_mat4Identity(); - model = kon_mat4Rotate(model, KON_VEC3(app->angle * 0.5f, app->angle, 0)); - viewProjection = kon_cameraViewProjection(&app->camera, display->game->width, display->game->height); + model = kon_mat4Rotate(model, KON_VEC3(program->angle * 0.5f, program->angle, 0)); + viewProjection = kon_cameraViewProjection(&program->camera, display->game->width, display->game->height); kon_clearFramebuffer(display->game, KON_BACKGROUND_COLOR); - kon_drawMesh(display->game, viewProjection, model, &kon_cubeMesh, &app->material, &app->light, KON_RENDER_DEFAULT); + kon_drawMesh(display->game, viewProjection, model, &kon_cubeMesh, &program->material, &program->light, KON_RENDER_DEFAULT); kon_presentDisplay(display); #ifdef KON_MODULE_DEBUG - kon_debugFrame(app->debug); - if (kon_debugQuitRequested(app->debug)) kon_setShouldClose(app->window, true); - kon_debugDraw(app->debug, display->screen); + kon_debugFrame(program->debug); + if (kon_debugQuitRequested(program->debug)) kon_setShouldClose(program->window, true); + kon_debugDraw(program->debug, display->screen); #endif - kon_blitPixels(app->window, display->screen->data, display->screen->width, display->screen->height); + kon_blitPixels(program->window, display->screen->data, display->screen->width, display->screen->height); kon_sleep(0.008); } int main(void) { - app_t app = {0}; + program_t program = {0}; if (!kon_init()) return 1; - app.window = kon_createWindow("engine", 0, 0, 800, 600, KON_WINDOW_CENTER | KON_WINDOW_RESIZABLE); - if (!app.window) return 1; + program.window = kon_createWindow("engine", 0, 0, 800, 600, KON_WINDOW_CENTER | KON_WINDOW_RESIZABLE); + if (!program.window) return 1; #ifdef KON_DEVELOPER - kon_setExitKey(app.window, KON_KEY_F10); + kon_setExitKey(program.window, KON_KEY_F10); #else - kon_setExitKey(app.window, KON_KEY_ESCAPE); + kon_setExitKey(program.window, KON_KEY_ESCAPE); #endif - app.display = kon_createDisplay(GAME_WIDTH, GAME_HEIGHT, 800, 600); - if (!app.display) return 1; + program.display = kon_createDisplay(GAME_WIDTH, GAME_HEIGHT, 800, 600); + if (!program.display) return 1; - app.camera = kon_cameraDefault(); - app.camera.position = KON_VEC3(3, 3, 6); - app.material = kon_materialColor(0xFFC8C8FF); - app.light = kon_lightDefault(); - app.last = kon_getTime(); + program.camera = kon_cameraDefault(); + program.camera.position = KON_VEC3(3, 3, 6); + program.material = kon_materialColor(0xFFC8C8FF); + program.light = kon_lightDefault(); + program.last = kon_getTime(); #ifdef KON_MODULE_DEBUG - app.assets = kon_assetsCreate(NULL, "assets"); + program.assets = kon_assetsCreate(NULL, "assets"); { - kon_blob_t *ttf = kon_assetBlob(app.assets, "font.ttf"); - - if (ttf) app.font = kon_loadFont(ttf->data, ttf->size, 14.0f); - app.debug = kon_debugCreate(app.font); - kon_debugSetAssets(app.debug, app.assets); - kon_debugCommand(app.debug, "opacity", "window opacity 0 to 1", cmdOpacity, &app); - kon_debugCommand(app.debug, "topmost", "topmost 0 or 1", cmdTopmost, &app); - kon_debugCommand(app.debug, "borderless", "borderless 0 or 1", cmdBorderless, &app); - kon_debugCommand(app.debug, "clickthrough", "clickthrough 0 or 1", cmdClickThrough, &app); - kon_debugCommand(app.debug, "message", "message \"text\"", cmdMessage, &app); - kon_debugCommand(app.debug, "shake", "shake the window", cmdShake, &app); - kon_debugLog(app.debug, ttf ? "loaded font.ttf" : "put a font.ttf in assets/ for the console"); + kon_blob_t *ttf = kon_assetBlob(program.assets, "font.ttf"); + + if (ttf) program.font = kon_loadFont(ttf->data, ttf->size, 14.0f); + program.debug = kon_debugCreate(program.font); + kon_debugSetAssets(program.debug, program.assets); + kon_debugCommand(program.debug, "opacity", "window opacity 0 to 1", cmdOpacity, &program); + kon_debugCommand(program.debug, "topmost", "topmost 0 or 1", cmdTopmost, &program); + kon_debugCommand(program.debug, "borderless", "borderless 0 or 1", cmdBorderless, &program); + kon_debugCommand(program.debug, "clickthrough", "clickthrough 0 or 1", cmdClickThrough, &program); + kon_debugCommand(program.debug, "message", "message \"text\"", cmdMessage, &program); + kon_debugCommand(program.debug, "shake", "shake the window", cmdShake, &program); + kon_debugLog(program.debug, ttf ? "loaded font.ttf" : "put a font.ttf in assets/ for the console"); } #endif - kon_runMainLoop(app.window, frame, &app); + kon_runMainLoop(program.window, frame, &program); #ifdef KON_MODULE_DEBUG - kon_debugDestroy(app.debug); - kon_freeFont(app.font); - kon_assetsDestroy(app.assets); + kon_debugDestroy(program.debug); + kon_freeFont(program.font); + kon_assetsDestroy(program.assets); #endif - kon_freeDisplay(app.display); - kon_destroyWindow(app.window); + kon_freeDisplay(program.display); + kon_destroyWindow(program.window); kon_deinit(); return 0; } diff --git a/src/impl/konassets.c b/src/impl/konassets.c deleted file mode 100644 index af67fb1..0000000 --- a/src/impl/konassets.c +++ /dev/null @@ -1,2 +0,0 @@ -#define KONASSETS_IMPLEMENTATION -#include "konassets.h" diff --git a/src/impl/konaudio.c b/src/impl/konaudio.c deleted file mode 100644 index 0e5a651..0000000 --- a/src/impl/konaudio.c +++ /dev/null @@ -1,2 +0,0 @@ -#define KONAUDIO_IMPLEMENTATION -#include "konaudio.h" diff --git a/src/impl/kondebug.c b/src/impl/kondebug.c deleted file mode 100644 index 3a9abb1..0000000 --- a/src/impl/kondebug.c +++ /dev/null @@ -1,2 +0,0 @@ -#define KONDEBUG_IMPLEMENTATION -#include "kondebug.h" diff --git a/src/impl/konpack.c b/src/impl/konpack.c deleted file mode 100644 index d48b9b7..0000000 --- a/src/impl/konpack.c +++ /dev/null @@ -1,2 +0,0 @@ -#define KONPACK_IMPLEMENTATION -#include "konpack.h" diff --git a/src/impl/kontile.c b/src/impl/kontile.c deleted file mode 100644 index 8f57579..0000000 --- a/src/impl/kontile.c +++ /dev/null @@ -1,2 +0,0 @@ -#define KONTILE_IMPLEMENTATION -#include "kontile.h" diff --git a/src/impl/konui.c b/src/impl/konui.c deleted file mode 100644 index d7290c3..0000000 --- a/src/impl/konui.c +++ /dev/null @@ -1,2 +0,0 @@ -#define KONUI_IMPLEMENTATION -#include "konui.h" |
