diff options
| -rw-r--r-- | Makefile | 2 | ||||
| -rwxr-xr-x | build.sh | 23 | ||||
| -rw-r--r-- | kon/konprogram.c | 227 | ||||
| -rw-r--r-- | kon/konprogram.h | 83 | ||||
| -rw-r--r-- | src/engine.c | 205 |
5 files changed, 367 insertions, 173 deletions
@@ -26,7 +26,7 @@ EXAMPLES = modelview EXAMPLEBINS = $(EXAMPLES:%=$(OBJDIR)/%$(EXE)) PACKFILE = $(OBJDIR)/data.kpk CORE = $(OBJDIR)/engine.o $(OBJDIR)/konwinlib.o $(OBJDIR)/konsofren.o \ - $(OBJDIR)/kontime.o $(OBJDIR)/koninput.o + $(OBJDIR)/kontime.o $(OBJDIR)/koninput.o $(OBJDIR)/konprogram.o OBJS = $(CORE) $(MODOBJS) all: $(BIN) @@ -93,15 +93,20 @@ for m in $modules; do esac done -case " $modules " in -*" assets "*|*" model "*) addobj konpack ;; -esac -case " $modules " in -*" ui "*) addobj konfont ;; -esac -case " $modules " in -*" debug "*) addobj konfont; addobj konassets; addobj konpack ;; -esac +need() +{ + case " $DEFS " in + *" -DKON_MODULE_$(echo "$1" | tr 'a-z' 'A-Z') "*) ;; + *) DEFS="$DEFS -DKON_MODULE_$(echo "$1" | tr 'a-z' 'A-Z')" ;; + esac + addobj "kon$1" +} + +# modules that use other modules +case " $modules " in *" debug "*) need font; need assets ;; esac +case " $modules " in *" ui "*) need font ;; esac +case " $modules " in *" model "*) need assets ;; esac +case " $DEFS " in *" -DKON_MODULE_ASSETS "*) need pack ;; esac case " $modules " in *" debug "*) [ $developer = 1 ] || echo "note: debug module does nothing without -d or -D" ;; diff --git a/kon/konprogram.c b/kon/konprogram.c new file mode 100644 index 0000000..33e1f15 --- /dev/null +++ b/kon/konprogram.c @@ -0,0 +1,227 @@ +/* SPDX-License-Identifier: MIT */ +/* + * konprogram.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 <stdlib.h> +#include <string.h> + +/* the debug functions only exist with the debug module, without KON_DEVELOPER the header gives empty ones instead */ +#if defined(KON_DEVELOPER) && !defined(KON_MODULE_DEBUG) +#undef KON_DEVELOPER +#endif + +#include "konprogram.h" +#include "kontime.h" + +/*** definition ***/ + +typedef struct kon_programState { + kon_program_t pub; + kon_programConfig_t config; + kon_time last; + float shakeLeft; + int shakeX, shakeY; +} kon_programState_t; + +/*** console commands ***/ + +#ifdef KON_DEVELOPER + +static void kon_cmdOpacity_(kon_debug_t *debug, void *user, int argc, char **argv) { + kon_programState_t *state = user; + + (void)debug; + if (argc > 1) kon_setWindowOpacity(state->pub.window, (float)atof(argv[1])); +} + +static void kon_cmdTopmost_(kon_debug_t *debug, void *user, int argc, char **argv) { + kon_programState_t *state = user; + + (void)debug; + if (argc > 1) kon_setWindowTopmost(state->pub.window, atoi(argv[1]) != 0); +} + +static void kon_cmdBorderless_(kon_debug_t *debug, void *user, int argc, char **argv) { + kon_programState_t *state = user; + + (void)debug; + if (argc > 1) kon_setWindowDecorated(state->pub.window, atoi(argv[1]) == 0); +} + +static void kon_cmdClickThrough_(kon_debug_t *debug, void *user, int argc, char **argv) { + kon_programState_t *state = user; + + (void)debug; + if (argc > 1) kon_setWindowClickThrough(state->pub.window, atoi(argv[1]) != 0); +} + +static void kon_cmdMessage_(kon_debug_t *debug, void *user, int argc, char **argv) { + (void)debug; (void)user; + kon_messageBox("konengine", argc > 1 ? argv[1] : "hello", KON_MESSAGE_INFO); +} + +static void kon_cmdShake_(kon_debug_t *debug, void *user, int argc, char **argv) { + kon_programState_t *state = user; + + (void)debug; (void)argc; (void)argv; + kon_getWindowPos(state->pub.window, &state->shakeX, &state->shakeY); + state->shakeLeft = 0.5f; +} + +static void kon_addWindowCommands_(kon_programState_t *state) { + kon_debug_t *debug = state->pub.debug; + + kon_debugCommand(debug, "opacity", "window opacity 0 to 1", kon_cmdOpacity_, state); + kon_debugCommand(debug, "topmost", "topmost 0 or 1", kon_cmdTopmost_, state); + kon_debugCommand(debug, "borderless", "borderless 0 or 1", kon_cmdBorderless_, state); + kon_debugCommand(debug, "clickthrough", "clickthrough 0 or 1", kon_cmdClickThrough_, state); + kon_debugCommand(debug, "message", "message \"text\"", kon_cmdMessage_, state); + kon_debugCommand(debug, "shake", "shake the window", kon_cmdShake_, state); +} + +#endif + +/*** loop ***/ + +static void kon_frame_(void *data) { + kon_programState_t *state = data; + kon_program_t *program = &state->pub; + const kon_programConfig_t *config = &state->config; + kon_display_t *display = program->display; + kon_event_t ev; + kon_time now, dt; + + while (kon_pollEvent(program->window, &ev)) { + if (kon_debugFeedEvent(program->debug, &ev)) continue; + if (ev.type == KON_EVENT_RESIZE) kon_resizeDisplay(display, ev.width, ev.height); + if (config->event) config->event(program, &ev, config->user); + } + + now = kon_getTime(); + dt = now - state->last; + state->last = now; + /* a long stall, like dragging the window, shouldn't make the game jump */ + if (dt > 0.25) dt = 0.25; + program->time += dt; + + if (state->shakeLeft > 0.0f) { + state->shakeLeft -= (float)dt; + if (state->shakeLeft > 0.0f) kon_setWindowPos(program->window, state->shakeX + rand() % 21 - 10, state->shakeY + rand() % 21 - 10); + else kon_setWindowPos(program->window, state->shakeX, state->shakeY); + } + + if (config->update) config->update(program, dt, config->user); + kon_clearFramebuffer(display->game, KON_BACKGROUND_COLOR); + if (config->draw) config->draw(program, display->game, config->user); + + kon_presentDisplay(display); + if (config->drawScreen) config->drawScreen(program, display->screen, config->user); + kon_debugFrame(program->debug); + if (kon_debugQuitRequested(program->debug)) kon_setShouldClose(program->window, true); + kon_debugDraw(program->debug, display->screen); + kon_blitPixels(program->window, display->screen->data, display->screen->width, display->screen->height); + kon_sleep(0.008); +} + +void kon_programQuit(kon_program_t *program) { + if (program) kon_setShouldClose(program->window, true); +} + +int kon_programRun(const kon_programConfig_t *config) { + kon_programState_t *state; + kon_program_t *program; + int gameWidth, gameHeight, windowWidth, windowHeight; + + if (!config) return 1; + state = calloc(1, sizeof *state); + if (!state) return 1; + program = &state->pub; + state->config = *config; + + gameWidth = config->gameWidth > 0 ? config->gameWidth : 320; + gameHeight = config->gameHeight > 0 ? config->gameHeight : 180; + windowWidth = config->windowWidth > 0 ? config->windowWidth : gameWidth * 3; + windowHeight = config->windowHeight > 0 ? config->windowHeight : gameHeight * 3; + + if (!kon_init()) goto fail; + program->window = kon_createWindow(config->title ? config->title : "konengine", 0, 0, windowWidth, windowHeight, KON_WINDOW_CENTER | KON_WINDOW_RESIZABLE); + if (!program->window) goto fail; +#ifdef KON_DEVELOPER + kon_setExitKey(program->window, KON_KEY_F10); +#else + kon_setExitKey(program->window, KON_KEY_ESCAPE); +#endif + program->display = kon_createDisplay(gameWidth, gameHeight, windowWidth, windowHeight); + if (!program->display) goto fail; + +#ifdef KON_MODULE_PACK + if (config->packPath) program->pack = kon_packOpen(config->packPath, NULL); +#endif +#ifdef KON_MODULE_ASSETS + program->assets = kon_assetsCreate(program->pack, config->assetDir ? config->assetDir : "assets"); +#endif +#ifdef KON_MODULE_FONT + { + kon_blob_t *ttf = program->assets ? kon_assetBlob(program->assets, "font.ttf") : NULL; + + if (ttf) program->font = kon_loadFont(ttf->data, ttf->size, 14.0f); + if (!program->font) program->font = kon_loadBuiltinFont(1); + } +#endif +#ifdef KON_MODULE_DEBUG + program->debug = kon_debugCreate(program->font); + kon_debugSetAssets(program->debug, program->assets); +#ifdef KON_DEVELOPER + kon_addWindowCommands_(state); +#endif +#endif + + state->last = kon_getTime(); + if (config->init && !config->init(program, config->user)) goto fail; + + kon_runMainLoop(program->window, kon_frame_, state); + + if (config->shutdown) config->shutdown(program, config->user); + kon_debugDestroy(program->debug); +#ifdef KON_MODULE_FONT + kon_freeFont(program->font); +#endif +#ifdef KON_MODULE_ASSETS + kon_assetsDestroy(program->assets); +#endif +#ifdef KON_MODULE_PACK + if (program->pack) kon_packClose(program->pack); +#endif + kon_freeDisplay(program->display); + kon_destroyWindow(program->window); + kon_deinit(); + free(state); + return 0; + +fail: + if (program->display) kon_freeDisplay(program->display); + if (program->window) kon_destroyWindow(program->window); + free(state); + return 1; +} diff --git a/kon/konprogram.h b/kon/konprogram.h new file mode 100644 index 0000000..f0a29b3 --- /dev/null +++ b/kon/konprogram.h @@ -0,0 +1,83 @@ +/* SPDX-License-Identifier: MIT */ +/* + * konprogram.h + * + * 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. + */ + +#ifndef KONPROGRAM_H +#define KONPROGRAM_H + +#include <stdint.h> + +#include "konwinlib.h" +#include "konsofren.h" +#include "konpack.h" +#include "konassets.h" +#include "konfont.h" +#include "kondebug.h" + +/*** definition ***/ + +typedef struct kon_program kon_program_t; + +/* every field can be left at 0. the callbacks are all optional too */ +typedef struct kon_programConfig { + const char *title; /* "konengine" */ + int gameWidth, gameHeight; /* the size you draw at, 320 x 180 */ + int windowWidth, windowHeight; /* 3 times the game size */ + const char *assetDir; /* folder with loose files, "assets". only used with KON_DEVELOPER */ + const char *packPath; /* pack file to read assets from, none if NULL */ + void *user; /* given back to every callback */ + + /* return 0 to quit before the loop starts */ + int (*init)(kon_program_t *program, void *user); + /* gets every event the console didn't take, resizes are already handled. return 1 if you used it */ + int (*event)(kon_program_t *program, const kon_event_t *event, void *user); + void (*update)(kon_program_t *program, double dt, void *user); + /* draw into the game framebuffer, it is scaled to the window afterwards */ + void (*draw)(kon_program_t *program, kon_framebuffer_t *game, void *user); + /* draw at window resolution on top of the scaled game, for text and UI that has to stay sharp */ + void (*drawScreen)(kon_program_t *program, kon_framebuffer_t *screen, void *user); + void (*shutdown)(kon_program_t *program, void *user); +} kon_programConfig_t; + +/* what is not built is NULL, so assets is NULL without the assets module and debug is NULL without the debug module. + font is the font.ttf from your assets if there is one, otherwise the built in one, and it is NULL without the font module */ +struct kon_program { + kon_window_t *window; + kon_display_t *display; + kon_pack_t *pack; + kon_assets_t *assets; + kon_font_t *font; + kon_debug_t *debug; + double time; /* seconds since the loop started */ +}; + +/*** declarations ***/ + +/* does everything from opening the window to cleaning up and returns when the program is closed. + returns 0, or 1 if something failed to start. a program that wants its own loop doesn't need this */ +int kon_programRun(const kon_programConfig_t *config); + +void kon_programQuit(kon_program_t *program); + +#endif diff --git a/src/engine.c b/src/engine.c index 15fae3d..1594032 100644 --- a/src/engine.c +++ b/src/engine.c @@ -1,187 +1,66 @@ -#include "konwinlib.h" -#include "konsofren.h" -#include <stdlib.h> +#include "konprogram.h" -#include "kontime.h" - -#ifdef KON_MODULE_DEBUG -#include "konassets.h" -#include "konfont.h" -#include "kondebug.h" -#endif - -#define GAME_WIDTH 320 -#define GAME_HEIGHT 180 - -typedef struct program { - kon_window_t *window; - kon_display_t *display; +typedef struct demo { kon_camera_t camera; kon_material_t material; kon_light_t light; - kon_time last; float angle; -#ifdef KON_MODULE_DEBUG - kon_assets_t *assets; - kon_font_t *font; - kon_debug_t *debug; - float shakeLeft; - int baseX, baseY; -#endif -} program_t; + int ortho; +} demo_t; -#ifdef KON_MODULE_DEBUG -static void cmdOpacity(kon_debug_t *debug, void *user, int argc, char **argv) +static int init(kon_program_t *program, void *user) { - program_t *program = user; - - (void)debug; - if (argc > 1) kon_setWindowOpacity(program->window, (float)atof(argv[1])); + demo_t *demo = user; + + (void)program; + demo->camera = kon_cameraDefault(); + demo->camera.position = KON_VEC3(3, 3, 6); + demo->material = kon_materialColor(0xFFC8C8FF); + demo->light = kon_lightDefault(); + return 1; } -static void cmdTopmost(kon_debug_t *debug, void *user, int argc, char **argv) +static int event(kon_program_t *program, const kon_event_t *ev, void *user) { - program_t *program = user; + demo_t *demo = user; - (void)debug; - if (argc > 1) kon_setWindowTopmost(program->window, atoi(argv[1]) != 0); -} - -static void cmdBorderless(kon_debug_t *debug, void *user, int argc, char **argv) -{ - program_t *program = user; - - (void)debug; - if (argc > 1) kon_setWindowDecorated(program->window, atoi(argv[1]) == 0); + (void)program; + if (ev->type == KON_EVENT_KEY_DOWN && ev->key == KON_KEY_O) { + demo->ortho = !demo->ortho; + demo->camera.projection = demo->ortho ? KON_PROJECTION_ORTHOGRAPHIC : KON_PROJECTION_PERSPECTIVE; + return 1; + } + return 0; } -static void cmdClickThrough(kon_debug_t *debug, void *user, int argc, char **argv) +static void update(kon_program_t *program, double dt, void *user) { - program_t *program = user; + demo_t *demo = user; - (void)debug; - if (argc > 1) kon_setWindowClickThrough(program->window, atoi(argv[1]) != 0); + (void)program; + demo->angle += (float)dt; } -static void cmdMessage(kon_debug_t *debug, void *user, int argc, char **argv) +static void draw(kon_program_t *program, kon_framebuffer_t *game, void *user) { - (void)debug; (void)user; - kon_messageBox("konengine", argc > 1 ? argv[1] : "hello", KON_MESSAGE_INFO); -} + demo_t *demo = user; + kon_mat4_t model = kon_mat4Rotate(kon_mat4Identity(), KON_VEC3(demo->angle * 0.5f, demo->angle, 0)); + kon_mat4_t viewProjection = kon_cameraViewProjection(&demo->camera, game->width, game->height); -static void cmdShake(kon_debug_t *debug, void *user, int argc, char **argv) -{ - program_t *program = user; - - (void)debug; (void)argc; (void)argv; - kon_getWindowPos(program->window, &program->baseX, &program->baseY); - program->shakeLeft = 0.5f; -} -#endif - -static void frame(void *data) -{ - 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(program->window, &ev)) { -#ifdef KON_MODULE_DEBUG - 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) { - program->camera.projection = (program->camera.projection == KON_PROJECTION_PERSPECTIVE) - ? KON_PROJECTION_ORTHOGRAPHIC : KON_PROJECTION_PERSPECTIVE; - } - } - - now = kon_getTime(); - dt = now - program->last; - program->angle += (float)dt; - program->last = now; - -#ifdef KON_MODULE_DEBUG - 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(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, &program->material, &program->light, KON_RENDER_DEFAULT); - - kon_presentDisplay(display); -#ifdef KON_MODULE_DEBUG - kon_debugFrame(program->debug); - if (kon_debugQuitRequested(program->debug)) kon_setShouldClose(program->window, true); - kon_debugDraw(program->debug, display->screen); -#endif - kon_blitPixels(program->window, display->screen->data, display->screen->width, display->screen->height); - kon_sleep(0.008); + (void)program; + kon_drawMesh(game, viewProjection, model, &kon_cubeMesh, &demo->material, &demo->light, KON_RENDER_DEFAULT); } int main(void) { - program_t program = {0}; - - if (!kon_init()) 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(program.window, KON_KEY_F10); -#else - kon_setExitKey(program.window, KON_KEY_ESCAPE); -#endif - - program.display = kon_createDisplay(GAME_WIDTH, GAME_HEIGHT, 800, 600); - if (!program.display) return 1; - - 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 - program.assets = kon_assetsCreate(NULL, "assets"); - { - kon_blob_t *ttf = kon_assetBlob(program.assets, "font.ttf"); - - if (ttf) program.font = kon_loadFont(ttf->data, ttf->size, 14.0f); - if (!program.font) program.font = kon_loadBuiltinFont(1); - 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" : "no assets/font.ttf, using the built in font"); - } -#endif - - kon_runMainLoop(program.window, frame, &program); - -#ifdef KON_MODULE_DEBUG - kon_debugDestroy(program.debug); - kon_freeFont(program.font); - kon_assetsDestroy(program.assets); -#endif - - kon_freeDisplay(program.display); - kon_destroyWindow(program.window); - kon_deinit(); - return 0; + demo_t demo = {0}; + kon_programConfig_t config = {0}; + + config.title = "engine"; + config.user = &demo; + config.init = init; + config.event = event; + config.update = update; + config.draw = draw; + return kon_programRun(&config); } |
