Compare commits

..

4 Commits

23 changed files with 153 additions and 142 deletions

View File

@ -17,7 +17,7 @@ void handle_input(void)
{ {
State *state = ctx.udata; State *state = ctx.udata;
if (ctx.mouse_window_position.y <= 60) if (ctx.mouse_position.y <= 60)
return; return;
if (input_is_action_pressed("add_a_bit")) if (input_is_action_pressed("add_a_bit"))
@ -27,10 +27,10 @@ void handle_input(void)
if (state->bunniesCount < MAX_BUNNIES) if (state->bunniesCount < MAX_BUNNIES)
{ {
state->bunnies[state->bunniesCount].position = input_get_action_position("add_a_bit"); state->bunnies[state->bunniesCount].position = input_get_action_position("add_a_bit");
state->bunnies[state->bunniesCount].speed.x = (float)(rand() % 500 - 250) / 60.0; state->bunnies[state->bunniesCount].speed.x = (float)(rand() % 500 - 250) / 60.0f;
state->bunnies[state->bunniesCount].speed.y = (float)(rand() % 500 - 250) / 60.0; state->bunnies[state->bunniesCount].speed.y = (float)(rand() % 500 - 250) / 60.0f;
state->bunnies[state->bunniesCount].color = state->bunnies[state->bunniesCount].color =
(Color){rand() % 190 + 50, rand() % 160 + 80, rand() % 140 + 100, 255}; (Color){(uint8_t)(rand() % 190 + 50), (uint8_t)(rand() % 160 + 80), (uint8_t)(rand() % 140 + 100), 255};
state->bunniesCount++; state->bunniesCount++;
} }
} }
@ -43,10 +43,10 @@ void handle_input(void)
if (state->bunniesCount < MAX_BUNNIES) if (state->bunniesCount < MAX_BUNNIES)
{ {
state->bunnies[state->bunniesCount].position = input_get_action_position("add_a_lot"); state->bunnies[state->bunniesCount].position = input_get_action_position("add_a_lot");
state->bunnies[state->bunniesCount].speed.x = (float)(rand() % 500 - 250) / 60.0; state->bunnies[state->bunniesCount].speed.x = (float)(rand() % 500 - 250) / 60.0f;
state->bunnies[state->bunniesCount].speed.y = (float)(rand() % 500 - 250) / 60.0; state->bunnies[state->bunniesCount].speed.y = (float)(rand() % 500 - 250) / 60.0f;
state->bunnies[state->bunniesCount].color = state->bunnies[state->bunniesCount].color =
(Color){rand() % 190 + 50, rand() % 160 + 80, rand() % 140 + 100, 255}; (Color){(uint8_t)(rand() % 190 + 50), (uint8_t)(rand() % 160 + 80), (uint8_t)(rand() % 140 + 100), 255};
state->bunniesCount++; state->bunniesCount++;
} }
} }
@ -73,32 +73,29 @@ void game_tick(void)
State *state = ctx.udata; State *state = ctx.udata;
const double delta =
(double)(ctx.delta_time) / 1000.0; // Receiving floating point delta value (diving by 1000 based on vibe)
for (int i = 0; i < state->bunniesCount; i++) for (int i = 0; i < state->bunniesCount; i++)
{ {
state->bunnies[i].position.x += state->bunnies[i].speed.x; state->bunnies[i].position.x += state->bunnies[i].speed.x;
state->bunnies[i].position.y += state->bunnies[i].speed.y; state->bunnies[i].position.y += state->bunnies[i].speed.y;
if (((state->bunnies[i].position.x + BUNNY_W / 2) > ctx.base_draw_w) || if (((state->bunnies[i].position.x + (float)BUNNY_W / 2) > (float)ctx.resolution.x) ||
((state->bunnies[i].position.x + BUNNY_W / 2) < 0)) ((state->bunnies[i].position.x + (float)BUNNY_W / 2) < 0))
state->bunnies[i].speed.x *= -1; state->bunnies[i].speed.x *= -1;
if (((state->bunnies[i].position.y + BUNNY_H / 2) > ctx.base_draw_h) || if (((state->bunnies[i].position.y + (float)BUNNY_H / 2) > (float)ctx.resolution.y) ||
((state->bunnies[i].position.y + BUNNY_H / 2 - 60) < 0)) ((state->bunnies[i].position.y + (float)BUNNY_H / 2 - 60) < 0))
state->bunnies[i].speed.y *= -1; state->bunnies[i].speed.y *= -1;
} }
handle_input(); handle_input();
// Clear window with Gray color (set the background color this way) // Clear window with Gray color (set the background color this way)
draw_rectangle((Rect){0, 0, ctx.base_draw_w, ctx.base_draw_h}, GRAY); draw_rectangle((Rect){0, 0, (float)ctx.resolution.x, (float)ctx.resolution.y}, GRAY);
for (int i = 0; i < state->bunniesCount; i++) for (int i = 0; i < state->bunniesCount; i++)
{ // Draw each bunny based on their position and color, also scale accordingly { // Draw each bunny based on their position and color, also scale accordingly
m_sprite(m_set(path, "wabbit_alpha.png"), m_sprite(m_set(path, "wabbit_alpha.png"),
m_set(rect, ((Rect){.x = (int)state->bunnies[i].position.x, m_set(rect, ((Rect){.x = state->bunnies[i].position.x,
.y = (int)state->bunnies[i].position.y, .y = state->bunnies[i].position.y,
.w = BUNNY_W * SPRITE_SCALE, .w = BUNNY_W * SPRITE_SCALE,
.h = BUNNY_H * SPRITE_SCALE})), .h = BUNNY_H * SPRITE_SCALE})),
m_opt(color, (state->bunnies[i].color)), m_opt(stretch, true), ); m_opt(color, (state->bunnies[i].color)), m_opt(stretch, true), );

View File

@ -19,15 +19,13 @@ static void title_tick(State *state) {
return; return;
} }
m_sprite("/assets/title.png", ((Rect) { m_sprite("/assets/title.png", ((Rect) {
((float)ctx.base_draw_w / 2) - ((float)320 / 2), 64, 320, 128 })); ((float)ctx.resolution.x / 2) - ((float)320 / 2), 64, 320, 128 }));
/* draw the tick count as an example of dynamic text */ /* draw the tick count as an example of dynamic text */
size_t text_str_len = snprintf(NULL, 0, "%lu", state->ctx->tick_count) + 1; size_t text_str_len = snprintf(NULL, 0, "%lu", state->ctx->frame_number) + 1;
char *text_str = cmalloc(text_str_len); char *text_str = cmalloc(text_str_len);
snprintf(text_str, text_str_len, "%lu", state->ctx->tick_count); snprintf(text_str, text_str_len, "%lu", state->ctx->frame_number);
const char *font = "fonts/kenney-pixel.ttf"; const char *font = "fonts/kenney-pixel.ttf";
int text_h = 32; int text_h = 32;

View File

@ -17,8 +17,8 @@ static void ingame_tick(State *state) {
if (input_is_mouse_captured()) { if (input_is_mouse_captured()) {
const float sensitivity = 0.6f; /* TODO: put this in a better place */ const float sensitivity = 0.6f; /* TODO: put this in a better place */
scn->yaw += (float)ctx.mouse_relative_position.x * sensitivity; scn->yaw += (float)ctx.mouse_movement.x * sensitivity;
scn->pitch -= (float)ctx.mouse_relative_position.y * sensitivity; scn->pitch -= (float)ctx.mouse_movement.y * sensitivity;
scn->pitch = clampf(scn->pitch, -89.0f, 89.0f); scn->pitch = clampf(scn->pitch, -89.0f, 89.0f);
const float yaw_rad = scn->yaw * (float)DEG2RAD; const float yaw_rad = scn->yaw * (float)DEG2RAD;
@ -62,8 +62,8 @@ static void ingame_tick(State *state) {
for (int ly = 64; ly--;) { for (int ly = 64; ly--;) {
for (int lx = 64; lx--;) { for (int lx = 64; lx--;) {
float x = SDL_truncf(scn->cam.pos.x + 32 - lx); float x = SDL_truncf(scn->cam.pos.x + 32 - (float)lx);
float y = SDL_truncf(scn->cam.pos.z + 32 - ly); float y = SDL_truncf(scn->cam.pos.z + 32 - (float)ly);
float d0 = stb_perlin_noise3((float)x * TERRAIN_FREQUENCY, (float)y * TERRAIN_FREQUENCY, 0, 0, 0, 0) * 3 - 6; float d0 = stb_perlin_noise3((float)x * TERRAIN_FREQUENCY, (float)y * TERRAIN_FREQUENCY, 0, 0, 0, 0) * 3 - 6;
float d1 = stb_perlin_noise3((float)(x + 1) * TERRAIN_FREQUENCY, (float)y * TERRAIN_FREQUENCY, 0, 0, 0, 0) * 3 - 6; float d1 = stb_perlin_noise3((float)(x + 1) * TERRAIN_FREQUENCY, (float)y * TERRAIN_FREQUENCY, 0, 0, 0, 0) * 3 - 6;
@ -89,7 +89,7 @@ static void ingame_tick(State *state) {
} }
draw_skybox("/assets/miramar/miramar_*.tga"); draw_skybox("/assets/miramar/miramar_*.tga");
draw_fog(0.9, 1.0, 0.05, (Color){ 140, 147, 160, 255 }); draw_fog(0.9f, 1.0f, 0.05f, (Color){ 140, 147, 160, 255 });
} }

View File

@ -16,15 +16,13 @@ static void title_tick(State *state) {
return; return;
} }
m_sprite("/assets/title.png", ((Rect) { m_sprite("/assets/title.png", ((Rect) {
((float)ctx.base_draw_w / 2) - ((float)320 / 2), 64, 320, 128 })); ((float)ctx.resolution.x / 2) - ((float)320 / 2), 64, 320, 128 }));
/* draw the tick count as an example of dynamic text */ /* draw the tick count as an example of dynamic text */
size_t text_str_len = snprintf(NULL, 0, "%lu", state->ctx->tick_count) + 1; size_t text_str_len = snprintf(NULL, 0, "%lu", state->ctx->frame_number) + 1;
char *text_str = cmalloc(text_str_len); char *text_str = cmalloc(text_str_len);
snprintf(text_str, text_str_len, "%lu", state->ctx->tick_count); snprintf(text_str, text_str_len, "%lu", state->ctx->frame_number);
const char *font = "/fonts/kenney-pixel.ttf"; const char *font = "/fonts/kenney-pixel.ttf";
int text_h = 32; int text_h = 32;
@ -39,6 +37,7 @@ static void title_tick(State *state) {
}, },
(Color) { 0, 0, 0, 255 } (Color) { 0, 0, 0, 255 }
); );
draw_text(text_str, (Vec2){ 0, 0 }, text_h, (Color) { 255, 255, 255, 255 }, font); draw_text(text_str, (Vec2){ 0, 0 }, text_h, (Color) { 255, 255, 255, 255 }, font);
free(text_str); free(text_str);

View File

@ -1,6 +1,7 @@
firstly, make sure to pull git-lfs references with: :git lfs fetch origin main: firstly, make sure to pull git-lfs references with: :git lfs fetch origin main:
you might have to run `git lfs install` before that
install cmake and sdl2-dev packages in manner suitable to your distribution install cmake and sdl2-dev packages in manner suitable to your distribution
add /tools/ directory to your $PATH, for example, with :source hooks: add /bin/ directory to your $PATH, you can do it cleanly with :source hooks: command
navigate to one of /apps/ folders for demos navigate to one of /apps/ folders for demos
run `twn build` to build run `twn build` to build
runnable apps should have the same name as app folder it is in runnable apps should have the same name as app folder it is in

View File

@ -4,6 +4,7 @@ for that certain steps are taken:
* number of public api calls is kept at the minimum * number of public api calls is kept at the minimum
* procedure signatures can only use basic types, no aggregates, with exception of Vec/Matrix types and alike, * procedure signatures can only use basic types, no aggregates, with exception of Vec/Matrix types and alike,
with no expectation on new additions (see /include/twn_types.h) with no expectation on new additions (see /include/twn_types.h)
* optionals can be expressed via pointer passage of value primitives, with NULL expressive default
* /include/twn_game_api.json file is hand-kept with a schema to aid automatic generation and other tooling * /include/twn_game_api.json file is hand-kept with a schema to aid automatic generation and other tooling
one of main inspirations for that is opengl model one of main inspirations for that is opengl model

View File

@ -1,7 +1,7 @@
#ifndef TWN_CAMERA_H #ifndef TWN_CAMERA_H
#define TWN_CAMERA_H #define TWN_CAMERA_H
#include "twn_util.h" #include "twn_types.h"
#include "twn_engine_api.h" #include "twn_engine_api.h"
/* TODO: make it cached? */ /* TODO: make it cached? */

View File

@ -1,47 +1,46 @@
#ifndef TWN_CONTEXT_H #ifndef TWN_CONTEXT_H
#define TWN_CONTEXT_H #define TWN_CONTEXT_H
#include "twn_input.h" #include "twn_types.h"
#include "twn_engine_api.h" #include "twn_engine_api.h"
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
/* context that is valid for current frame */ /* context that is only valid for current frame */
/* changes to it should not have an effect, unless specified */ /* any changes to it will be dropped, unless explicitly stated */
/* TODO: ensure the statement above */
typedef struct Context { typedef struct Context {
/* you may read from and write to these from game code */ /* you may read from and write to this one from game code */
/* it is ensured to persist between initializations */
void *udata; void *udata;
/* TODO: is it what we actually want? */ /* which frame is it, starting from 0 at startup */
int64_t delta_time; /* preserves real time frame delta with no manipilation */ uint64_t frame_number;
uint64_t tick_count;
Vec2i mouse_window_position; /* real time spent on one frame (in seconds) */
Vec2i mouse_relative_position; /* townengine is fixed step based, so you don't have */
/* TODO: actually set it */
float frame_duration;
/* set just once on startup */ /* resolution is set from config and dictates both logical and drawing space, as they're related */
/* even if scaling is done, game logic should never change over that */
Vec2i resolution;
Vec2i mouse_position;
Vec2i mouse_movement;
/* is set on startup, should be used as source of randomness */
uint64_t random_seed; uint64_t random_seed;
/* this should be a multiple of the current ticks per second */ /* whether debugging logic should be enabled in user code */
/* use it to simulate low framerate (e.g. at 60 tps, set to 2 for 30 fps) */
/* it can be changed at runtime; any resulting logic anomalies are bugs */
unsigned int update_multiplicity;
/* TODO: use Vec2i? */
int window_w;
int window_h;
int base_draw_w;
int base_draw_h;
bool debug; bool debug;
bool is_running;
bool window_size_has_changed; /* is set to true when state is invalidated and needs to be rebuilt */
/* watch for it and handle properly! */
bool initialization_needed; bool initialization_needed;
} Context; } Context;
/* when included after twn_engine_context there's an 'ctx' defined already */
#ifndef TWN_ENGINE_CONTEXT_C_H #ifndef TWN_ENGINE_CONTEXT_C_H
TWN_API extern Context ctx; TWN_API extern Context ctx;
#endif #endif

View File

@ -1,7 +1,7 @@
#ifndef TWN_DRAW_H #ifndef TWN_DRAW_H
#define TWN_DRAW_H #define TWN_DRAW_H
#include "twn_util.h" #include "twn_types.h"
#include "twn_option.h" #include "twn_option.h"
#include "twn_camera.h" #include "twn_camera.h"
#include "twn_engine_api.h" #include "twn_engine_api.h"

View File

@ -1,22 +1,23 @@
/* include this header in game code to get the usable parts of the engine */ /* include this header in game code to get the usable parts of the engine */
#ifndef GAME_API_H #ifndef TWN_GAME_API_H
#define GAME_API_H #define TWN_GAME_API_H
#include "twn_input.h"
#include "twn_context.h" #include "twn_context.h"
#include "twn_draw.h" #include "twn_draw.h"
#include "twn_audio.h" #include "twn_audio.h"
#include "twn_util.h"
#include "twn_input.h"
#include "twn_engine_api.h" #include "twn_engine_api.h"
#include "twn_util.h"
#ifndef TWN_NOT_C
/* sole game logic and display function. /* sole game logic and display function.
all state must be used from and saved to supplied state pointer. */ all state must be used from and saved to supplied state pointer. */
TWN_API extern void game_tick(void); TWN_API extern void game_tick(void);
/* called when application is closing. */
TWN_API extern void game_end(void);
/* called when application is closing. */
TWN_API extern void game_end(void);
#endif
#endif #endif

View File

@ -1,23 +1,15 @@
#ifndef UTIL_H #ifndef TWN_UTIL_H
#define UTIL_H #define TWN_UTIL_H
#include "twn_types.h" #include "twn_types.h"
#include "twn_engine_api.h" #include "twn_engine_api.h"
#include "twn_types.h"
#include <stdint.h> #include <stdint.h>
#include <stddef.h>
#include <stdbool.h> #include <stdbool.h>
#include <math.h> #include <math.h>
/* DON'T FORGET ABOUT DOUBLE EVALUATION */
/* YOU THINK YOU WON'T, AND THEN YOU DO */
/* C23's typeof could fix it, so i will */
/* leave them as macros to be replaced. */
/* use the macro in tgmath.h for floats */
#define MAX SDL_max
#define MIN SDL_min
#ifndef M_PI #ifndef M_PI
#define M_PI 3.14159265358979323846264338327950288 /**< pi */ #define M_PI 3.14159265358979323846264338327950288 /**< pi */
#endif #endif
@ -26,16 +18,13 @@
#define DEG2RAD (M_PI / 180) #define DEG2RAD (M_PI / 180)
#define RAD2DEG (180 / M_PI) #define RAD2DEG (180 / M_PI)
#ifndef TWN_NOT_C
/* */ TWN_API void *cmalloc(size_t size);
/* GENERAL UTILITIES */ TWN_API void *crealloc(void *ptr, size_t size);
/* */ TWN_API void *ccalloc(size_t num, size_t size);
TWN_API void cry_impl(const char *file, const int line, const char *title, const char *text); #endif /* TWN_NOT_C */
#define CRY(title, text) cry_impl(__FILE__, __LINE__, title, text)
#define CRY_SDL(title) cry_impl(__FILE__, __LINE__, title, SDL_GetError())
#define CRY_PHYSFS(title) \
cry_impl(__FILE__, __LINE__, title, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()))
TWN_API void log_info(const char *restrict format, ...); TWN_API void log_info(const char *restrict format, ...);
@ -43,18 +32,6 @@ TWN_API void log_critical(const char *restrict format, ...);
TWN_API void log_warn(const char *restrict format, ...); TWN_API void log_warn(const char *restrict format, ...);
/* for when there's absolutely no way to continue */
TWN_API _Noreturn void die_abruptly(void);
/* "critical" allocation functions which will log and abort() on failure. */
/* if it is reasonable to handle this gracefully, use the standard versions. */
/* the stb implementations will be configured to use these */
TWN_API void *cmalloc(size_t size);
TWN_API void *crealloc(void *ptr, size_t size);
TWN_API void *ccalloc(size_t num, size_t size);
/* TODO: this is why generics were invented. sorry, i'm tired today */ /* TODO: this is why generics were invented. sorry, i'm tired today */
TWN_API double clamp(double d, double min, double max); TWN_API double clamp(double d, double min, double max);
TWN_API float clampf(float f, float min, float max); TWN_API float clampf(float f, float min, float max);

View File

@ -1,5 +1,6 @@
#include "twn_game_object_c.h" #include "twn_game_object_c.h"
#include "twn_engine_context_c.h" #include "twn_engine_context_c.h"
#include "twn_util_c.h"
#include <x-watcher.h> #include <x-watcher.h>
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
@ -57,7 +58,7 @@ static void load_game_object(void) {
handle = new_handle; handle = new_handle;
if (ctx.game.tick_count != 0) if (ctx.game.frame_number != 0)
log_info("Game object was reloaded\n"); log_info("Game object was reloaded\n");
return; return;
@ -84,7 +85,7 @@ static void watcher_callback(XWATCHER_FILE_EVENT event,
switch(event) { switch(event) {
case XWATCHER_FILE_MODIFIED: case XWATCHER_FILE_MODIFIED:
SDL_LockMutex(lock); SDL_LockMutex(lock);
last_tick_modified = ctx.game.tick_count; last_tick_modified = ctx.game.frame_number;
loaded_after_modification = false; loaded_after_modification = false;
SDL_UnlockMutex(lock); SDL_UnlockMutex(lock);
break; break;
@ -129,7 +130,7 @@ bool game_object_try_reloading(void) {
/* only load the modified library after some time, as compilers make a lot of modifications */ /* only load the modified library after some time, as compilers make a lot of modifications */
SDL_LockMutex(lock); SDL_LockMutex(lock);
if (ctx.game.tick_count - last_tick_modified > MODIFIED_TICKS_MERGED && if (ctx.game.frame_number - last_tick_modified > MODIFIED_TICKS_MERGED &&
!loaded_after_modification) { !loaded_after_modification) {
load_game_object(); load_game_object();
loaded_after_modification = true; loaded_after_modification = true;

View File

@ -1,5 +1,6 @@
#include "twn_game_object_c.h" #include "twn_game_object_c.h"
#include "twn_engine_context_c.h" #include "twn_engine_context_c.h"
#include "twn_util_c.h"
#include <errhandlingapi.h> #include <errhandlingapi.h>
#include <libloaderapi.h> #include <libloaderapi.h>
@ -48,7 +49,7 @@ static void load_game_object(void) {
handle = new_handle; handle = new_handle;
if (ctx.game.tick_count != 0) if (ctx.game.frame_number != 0)
log_info("Game object was reloaded\n"); log_info("Game object was reloaded\n");
return; return;

View File

@ -250,23 +250,23 @@ void render(void) {
textures_update_atlas(&ctx.texture_cache); textures_update_atlas(&ctx.texture_cache);
/* fit rendering context onto the resizable screen */ /* fit rendering context onto the resizable screen */
if (ctx.game.window_size_has_changed) { if (ctx.window_size_has_changed) {
if ((float)ctx.game.window_w / (float)ctx.game.window_h > (float)ctx.base_render_width / (float)ctx.base_render_height) { if ((float)ctx.window_dims.x / (float)ctx.window_dims.y > (float)ctx.base_render_width / (float)ctx.base_render_height) {
float ratio = (float)ctx.game.window_h / (float)ctx.base_render_height; float ratio = (float)ctx.window_dims.y / (float)ctx.base_render_height;
int w = (int)((float)ctx.base_render_width * ratio); int w = (int)((float)ctx.base_render_width * ratio);
setup_viewport( setup_viewport(
ctx.game.window_w / 2 - w / 2, ctx.window_dims.x / 2 - w / 2,
0, 0,
w, w,
ctx.game.window_h ctx.window_dims.y
); );
} else { } else {
float ratio = (float)ctx.game.window_w / (float)ctx.base_render_width; float ratio = (float)ctx.window_dims.x / (float)ctx.base_render_width;
int h = (int)((float)ctx.base_render_height * ratio); int h = (int)((float)ctx.base_render_height * ratio);
setup_viewport( setup_viewport(
0, 0,
ctx.game.window_h / 2 - h / 2, ctx.window_dims.y / 2 - h / 2,
ctx.game.window_w, ctx.window_dims.x,
h h
); );
} }

View File

@ -1,5 +1,5 @@
#include "twn_gpu_texture_c.h" #include "twn_gpu_texture_c.h"
#include "twn_util.h" #include "twn_util_c.h"
GPUTexture create_gpu_texture(TextureFilter filter, bool generate_mipmaps) { GPUTexture create_gpu_texture(TextureFilter filter, bool generate_mipmaps) {

View File

@ -1,6 +1,6 @@
#include "twn_draw_c.h" #include "twn_draw_c.h"
#include "twn_engine_context_c.h" #include "twn_engine_context_c.h"
#include "twn_util.h" #include "twn_util_c.h"
#ifdef EMSCRIPTEN #ifdef EMSCRIPTEN
#include <GLES2/gl2.h> #include <GLES2/gl2.h>

View File

@ -1,6 +1,7 @@
#include "twn_draw_c.h" #include "twn_draw_c.h"
#include "twn_draw.h" #include "twn_draw.h"
#include "twn_util.h" #include "twn_util.h"
#include "twn_util_c.h"
#include "twn_engine_context_c.h" #include "twn_engine_context_c.h"
#include <stb_truetype.h> #include <stb_truetype.h>

View File

@ -1,6 +1,7 @@
#include "twn_audio_c.h" #include "twn_audio_c.h"
#include "twn_engine_context_c.h" #include "twn_engine_context_c.h"
#include "twn_util.h" #include "twn_util.h"
#include "twn_util_c.h"
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include <stb_ds.h> #include <stb_ds.h>

View File

@ -17,6 +17,8 @@
typedef struct EngineContext { typedef struct EngineContext {
/* user code facing context */ /* user code facing context */
Context game_copy;
/* engine-side context, copied to `game_copy` before every frame */
Context game; Context game;
InputState input; InputState input;
@ -27,6 +29,8 @@ typedef struct EngineContext {
/* where the app was run from, used as the root for packs */ /* where the app was run from, used as the root for packs */
char *base_dir; char *base_dir;
Vec2i window_dims;
/* configuration */ /* configuration */
toml_table_t *config_table; toml_table_t *config_table;
int64_t base_render_width; int64_t base_render_width;
@ -52,6 +56,7 @@ typedef struct EngineContext {
uint8_t audio_stream_channel_count; uint8_t audio_stream_channel_count;
/* main loop machinery */ /* main loop machinery */
int64_t delta_time; /* preserves real time frame delta with no manipilation */
int64_t clocks_per_second; int64_t clocks_per_second;
int64_t prev_frame_time; int64_t prev_frame_time;
int64_t desired_frametime; /* how long one tick should be */ int64_t desired_frametime; /* how long one tick should be */
@ -59,10 +64,17 @@ typedef struct EngineContext {
int64_t delta_averager_residual; int64_t delta_averager_residual;
int64_t time_averager[4]; int64_t time_averager[4];
/* this should be a multiple of the current ticks per second */
/* use it to simulate low framerate (e.g. at 60 tps, set to 2 for 30 fps) */
/* it can be changed at runtime; any resulting logic anomalies are bugs */
uint32_t update_multiplicity;
SDL_GLContext *gl_context; SDL_GLContext *gl_context;
SDL_Window *window; SDL_Window *window;
uint32_t window_id; uint32_t window_id;
bool is_running;
bool window_size_has_changed;
bool resync_flag; bool resync_flag;
bool was_successful; bool was_successful;
} EngineContext; } EngineContext;

View File

@ -194,8 +194,8 @@ void input_state_update(InputState *input) {
SDL_GetRelativeMouseState(&input->mouse_relative_position.x, SDL_GetRelativeMouseState(&input->mouse_relative_position.x,
&input->mouse_relative_position.y); &input->mouse_relative_position.y);
ctx.game.mouse_window_position = input->mouse_window_position; ctx.game.mouse_position = input->mouse_window_position;
ctx.game.mouse_relative_position = input->mouse_relative_position; ctx.game.mouse_movement = input->mouse_relative_position;
for (size_t i = 0; i < shlenu(input->action_hash); ++i) { for (size_t i = 0; i < shlenu(input->action_hash); ++i) {
Action *action = &input->action_hash[i].value; Action *action = &input->action_hash[i].value;

View File

@ -2,6 +2,7 @@
#include "twn_engine_context_c.h" #include "twn_engine_context_c.h"
#include "twn_input_c.h" #include "twn_input_c.h"
#include "twn_util.h" #include "twn_util.h"
#include "twn_util_c.h"
#include "twn_game_object_c.h" #include "twn_game_object_c.h"
#include "twn_audio_c.h" #include "twn_audio_c.h"
#include "twn_textures_c.h" #include "twn_textures_c.h"
@ -36,8 +37,8 @@ static int event_callback(void *userdata, SDL_Event *event) {
switch (event->window.event) { switch (event->window.event) {
case SDL_WINDOWEVENT_SIZE_CHANGED: case SDL_WINDOWEVENT_SIZE_CHANGED:
ctx.game.window_w = event->window.data1; ctx.window_dims.x = event->window.data1;
ctx.game.window_h = event->window.data2; ctx.window_dims.y = event->window.data2;
ctx.resync_flag = true; ctx.resync_flag = true;
break; break;
@ -59,12 +60,12 @@ static int event_callback(void *userdata, SDL_Event *event) {
static void poll_events(void) { static void poll_events(void) {
SDL_Event e; SDL_Event e;
ctx.game.window_size_has_changed = false; ctx.window_size_has_changed = false;
while (SDL_PollEvent(&e)) { while (SDL_PollEvent(&e)) {
switch (e.type) { switch (e.type) {
case SDL_QUIT: case SDL_QUIT:
ctx.game.is_running = false; ctx.is_running = false;
break; break;
case SDL_WINDOWEVENT: case SDL_WINDOWEVENT:
@ -73,7 +74,7 @@ static void poll_events(void) {
switch (e.window.event) { switch (e.window.event) {
case SDL_WINDOWEVENT_SIZE_CHANGED: case SDL_WINDOWEVENT_SIZE_CHANGED:
ctx.game.window_size_has_changed = true; ctx.window_size_has_changed = true;
break; break;
default: default:
@ -111,6 +112,11 @@ static void APIENTRY opengl_log(GLenum source,
#endif #endif
void preserve_persistent_ctx_fields(void) {
ctx.game.udata = ctx.game_copy.udata;
}
static void main_loop(void) { static void main_loop(void) {
/* /*
if (!ctx.is_running) { if (!ctx.is_running) {
@ -124,7 +130,7 @@ static void main_loop(void) {
int64_t current_frame_time = SDL_GetPerformanceCounter(); int64_t current_frame_time = SDL_GetPerformanceCounter();
int64_t delta_time = current_frame_time - ctx.prev_frame_time; int64_t delta_time = current_frame_time - ctx.prev_frame_time;
ctx.prev_frame_time = current_frame_time; ctx.prev_frame_time = current_frame_time;
ctx.game.delta_time = delta_time; ctx.delta_time = delta_time;
/* handle unexpected timer anomalies (overflow, extra slow frames, etc) */ /* handle unexpected timer anomalies (overflow, extra slow frames, etc) */
if (delta_time > ctx.desired_frametime * 8) { /* ignore extra-slow frames */ if (delta_time > ctx.desired_frametime * 8) { /* ignore extra-slow frames */
@ -188,24 +194,23 @@ static void main_loop(void) {
ctx.resync_flag = false; ctx.resync_flag = false;
} }
ctx.game_copy = ctx.game;
/* finally, let's get to work */ /* finally, let's get to work */
int frames = 0; int frames = 0;
while (ctx.frame_accumulator >= ctx.desired_frametime * ctx.game.update_multiplicity) { while (ctx.frame_accumulator >= ctx.desired_frametime * ctx.update_multiplicity) {
frames += 1; frames += 1;
for (size_t i = 0; i < ctx.game.update_multiplicity; ++i) { for (size_t i = 0; i < ctx.update_multiplicity; ++i) {
/* TODO: disable rendering pushes on not-last ? */ /* TODO: disable rendering pushes on not-last ? */
render_queue_clear(); render_queue_clear();
poll_events(); poll_events();
input_state_update(&ctx.input); input_state_update(&ctx.input);
game_object_tick(); game_object_tick();
preserve_persistent_ctx_fields();
ctx.frame_accumulator -= ctx.desired_frametime; ctx.frame_accumulator -= ctx.desired_frametime;
ctx.game.tick_count = (ctx.game.tick_count % ULLONG_MAX) + 1; ctx.game.frame_number = (ctx.game.frame_number % ULLONG_MAX) + 1;
ctx.game.initialization_needed = false; ctx.game.initialization_needed = false;
} }
} }
@ -439,7 +444,7 @@ static bool initialize(void) {
goto fail; goto fail;
} }
ctx.base_render_width = datum_base_render_width.u.i; ctx.base_render_width = datum_base_render_width.u.i;
ctx.game.base_draw_w = (int)ctx.base_render_width; ctx.game.resolution.x = (int)ctx.base_render_width;
toml_datum_t datum_base_render_height = toml_int_in(game, "base_render_height"); toml_datum_t datum_base_render_height = toml_int_in(game, "base_render_height");
if (!datum_base_render_height.ok) { if (!datum_base_render_height.ok) {
@ -447,7 +452,7 @@ static bool initialize(void) {
goto fail; goto fail;
} }
ctx.base_render_height = datum_base_render_height.u.i; ctx.base_render_height = datum_base_render_height.u.i;
ctx.game.base_draw_h = (int)ctx.base_render_height; ctx.game.resolution.y = (int)ctx.base_render_height;
ctx.window = SDL_CreateWindow(datum_title.u.s, ctx.window = SDL_CreateWindow(datum_title.u.s,
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
@ -503,8 +508,8 @@ static bool initialize(void) {
/* TODO: */ /* TODO: */
// SDL_GetRendererOutputSize(ctx.renderer, &ctx.window_w, &ctx.window_h); // SDL_GetRendererOutputSize(ctx.renderer, &ctx.window_w, &ctx.window_h);
ctx.game.window_w = (int)ctx.base_render_width; ctx.window_dims.x = (int)ctx.base_render_width;
ctx.game.window_h = (int)ctx.base_render_height; ctx.window_dims.y = (int)ctx.base_render_height;
/* add a watcher for immediate updates on window size */ /* add a watcher for immediate updates on window size */
SDL_AddEventWatch(event_callback, NULL); SDL_AddEventWatch(event_callback, NULL);
@ -532,7 +537,7 @@ static bool initialize(void) {
} }
/* you could change this at runtime if you wanted */ /* you could change this at runtime if you wanted */
ctx.game.update_multiplicity = 1; ctx.update_multiplicity = 1;
#ifndef EMSCRIPTEN #ifndef EMSCRIPTEN
/* hook up opengl debugging callback */ /* hook up opengl debugging callback */
@ -561,13 +566,13 @@ static bool initialize(void) {
} }
} }
ctx.game.is_running = true; ctx.is_running = true;
ctx.resync_flag = true; ctx.resync_flag = true;
ctx.clocks_per_second = SDL_GetPerformanceFrequency(); ctx.clocks_per_second = SDL_GetPerformanceFrequency();
ctx.prev_frame_time = SDL_GetPerformanceCounter(); ctx.prev_frame_time = SDL_GetPerformanceCounter();
ctx.desired_frametime = ctx.clocks_per_second / ctx.ticks_per_second; ctx.desired_frametime = ctx.clocks_per_second / ctx.ticks_per_second;
ctx.frame_accumulator = 0; ctx.frame_accumulator = 0;
ctx.game.tick_count = 0; ctx.game.frame_number = 0;
/* delta time averaging */ /* delta time averaging */
ctx.delta_averager_residual = 0; ctx.delta_averager_residual = 0;
@ -772,7 +777,7 @@ int enter_loop(int argc, char **argv) {
ctx.was_successful = true; ctx.was_successful = true;
ctx.game.initialization_needed = true; ctx.game.initialization_needed = true;
while (ctx.game.is_running) { while (ctx.is_running) {
if (game_object_try_reloading()) { if (game_object_try_reloading()) {
ctx.game.initialization_needed = true; ctx.game.initialization_needed = true;
reset_state(); reset_state();

View File

@ -273,11 +273,11 @@ void tick_timer(int *value) {
} }
void tick_ftimer(float *value) { void tick_ftimer(float *value) {
*value = MAX(*value - ((float)ctx.game.delta_time / (float)ctx.clocks_per_second), 0.0f); *value = MAX(*value - ((float)ctx.delta_time / (float)ctx.clocks_per_second), 0.0f);
} }
bool repeat_ftimer(float *value, float at) { bool repeat_ftimer(float *value, float at) {
*value -= (float)ctx.game.delta_time / (float)ctx.clocks_per_second; *value -= (float)ctx.delta_time / (float)ctx.clocks_per_second;
if (*value < 0.0f) { if (*value < 0.0f) {
*value += at; *value += at;
return true; return true;

View File

@ -3,6 +3,23 @@
#include "twn_types.h" #include "twn_types.h"
#include <SDL2/SDL.h>
#include <stdbool.h>
#define MAX SDL_max
#define MIN SDL_min
void cry_impl(const char *file, const int line, const char *title, const char *text);
#define CRY(title, text) cry_impl(__FILE__, __LINE__, title, text)
#define CRY_SDL(title) cry_impl(__FILE__, __LINE__, title, SDL_GetError())
#define CRY_PHYSFS(title) \
cry_impl(__FILE__, __LINE__, title, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()))
/* for when there's absolutely no way to continue */
_Noreturn void die_abruptly(void);
/* note: you must free the returned string */ /* note: you must free the returned string */
char *expand_asterisk(const char *mask, const char *to); char *expand_asterisk(const char *mask, const char *to);
@ -22,7 +39,7 @@ static inline float fast_sqrt(float x)
static inline Vec2 fast_cossine(float a) { static inline Vec2 fast_cossine(float a) {
const float s = sinf(a); const float s = SDL_sinf(a);
return (Vec2){ return (Vec2){
.x = fast_sqrt(1.0f - s * s) * (a >= (float)M_PI_2 && a < (float)(M_PI + M_PI_2) ? -1 : 1), .x = fast_sqrt(1.0f - s * s) * (a >= (float)M_PI_2 && a < (float)(M_PI + M_PI_2) ? -1 : 1),
.y = s .y = s