Compare commits

..

No commits in common. "f3a2dc9063ae95f65c3d269835c90071a3530092" and "2286cdefeb53a7cc4d751983a19ba0dd61d00465" have entirely different histories.

11 changed files with 82 additions and 166 deletions

View File

@ -27,6 +27,7 @@ set(TWN_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR} CACHE INTERNAL "")
# feature configuration, set them with -DFEATURE=ON/OFF in cli # feature configuration, set them with -DFEATURE=ON/OFF in cli
option(TWN_FEATURE_DYNLIB_GAME "Enable dynamic library loading support" ON) option(TWN_FEATURE_DYNLIB_GAME "Enable dynamic library loading support" ON)
option(TWN_FEATURE_PUSH_AUDIO "Enable frame based audio push for easy realtime audio" ON)
option(TWN_USE_AMALGAM "Enable use of twn_amalgam.c as a single compilation unit" ON) option(TWN_USE_AMALGAM "Enable use of twn_amalgam.c as a single compilation unit" ON)
# todo: figure out how to compile for dynamic linking instead # todo: figure out how to compile for dynamic linking instead
@ -135,6 +136,8 @@ set_target_properties(${TWN_TARGET} PROPERTIES
C_STANDARD_REQUIRED ON C_STANDARD_REQUIRED ON
C_EXTENSIONS ON) # extensions are required by stb_ds.h C_EXTENSIONS ON) # extensions are required by stb_ds.h
target_compile_definitions(${TWN_TARGET} PRIVATE $<$<BOOL:${TWN_FEATURE_PUSH_AUDIO}>:TWN_FEATURE_PUSH_AUDIO>)
# precompile commonly used not-so-small headers # precompile commonly used not-so-small headers
target_precompile_headers(${TWN_TARGET} PRIVATE target_precompile_headers(${TWN_TARGET} PRIVATE
$<$<NOT:$<BOOL:${EMSCRIPTEN}>>:third-party/glad/include/glad/glad.h> $<$<NOT:$<BOOL:${EMSCRIPTEN}>>:third-party/glad/include/glad/glad.h>

View File

@ -2,8 +2,6 @@ cmake_minimum_required(VERSION 3.21)
cmake_policy(SET CMP0171 NEW) cmake_policy(SET CMP0171 NEW)
project(twnlua LANGUAGES C) project(twnlua LANGUAGES C)
option(TWN_OUT_DIR "Artifact destination" ${CMAKE_SOURCE_DIR})
if(NOT CMAKE_BUILD_TYPE) if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Debug) set(CMAKE_BUILD_TYPE Debug)
endif() endif()
@ -41,5 +39,5 @@ set(SOURCE_FILES
${CMAKE_CURRENT_SOURCE_DIR}/luabind.c ${CMAKE_CURRENT_SOURCE_DIR}/luabind.c
) )
cmake_path(GET TWN_OUT_DIR STEM LAST_ONLY GAME_PROJECT_NAME) cmake_path(GET CMAKE_SOURCE_DIR STEM LAST_ONLY GAME_PROJECT_NAME)
use_townengine(${GAME_PROJECT_NAME} "${SOURCE_FILES}" ${TWN_OUT_DIR}) use_townengine(${GAME_PROJECT_NAME} "${SOURCE_FILES}" ${CMAKE_SOURCE_DIR})

View File

@ -37,9 +37,6 @@ case "$1" in
api-gen ) "$toolpath"/gen_api_header.sh api-gen ) "$toolpath"/gen_api_header.sh
;; ;;
wiki ) xdg-open "file://$TWNROOT/docs/wiki/index.html"
;;
* ) echo "Unknown command." * ) echo "Unknown command."
;; ;;
esac esac

View File

@ -24,11 +24,12 @@ if "--release" in argv:
cmake += ["-DCMAKE_BUILD_TYPE=Release"] cmake += ["-DCMAKE_BUILD_TYPE=Release"]
# pass arbitrary arguments over # pass arbitrary arguments over
if "--" in argv: if "--" in argv:
cmake += argv[argv.index("--")+1:] cmake += argv[argv.find("--"):]
# if no root cmake file is present, infer it from `twn.toml:game.interpreter` # if no root cmake file is present, infer it from `twn.toml:game.interpreter`
if not Path("CMakeLists.txt").is_file(): if not Path("CMakeLists.txt").is_file():
config = tomllib.loads(Path("data/twn.toml").read_text()) with open("data/twn.toml", "rb") as f:
config = tomllib.load(f)
cmake += ["-S", expandvars(config["game"]["interpreter"])] cmake += ["-S", expandvars(config["game"]["interpreter"])]
run(cmake, check=True) run(cmake, check=True)

View File

@ -1,44 +0,0 @@
<!doctype html>
<html>
<head>
<title>Townengine Wiki : About Townengine</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1 style="margin-bottom:0in;">1. About Townengine</h1>
<a href="index.html">Go back
<p><a name="introduction"></a><strong>1.1 </strong><strong>Introduction</strong>
<blockquote>
<p>Townengine, {twn}, is an opinionated game development framework designed around ideas of simplicity, managed state,
care for old devices, portability, language agnosticism, use-case orientation, iterability and low latency.
<p><b>Simplicity.</b> It makes assumptions that trickle down to your game code. There's no delta between frames, nor resolution change.
Textures have constant known size, not requiring scaling.
<p><b>Managed state.</b> Designed around this we can provide hot reloading at any point,
serialization for save files and debug dumps, as well as synchronization over network, without any user code.
Every frame apparent engine state is cleared, which removes another variable for you to handle, - when to initialize.
There's no need for you to call `LoadImage()` of sorts before using it in render, you just pass filepaths around.
Input is initialized anew each frame, making rebinds trivial.
<p><b>Care for old devices.</b> It's to both to provide it for more people, who otherwise might not be able to make games they want,
as well as to have restrictions that constitute in desired aesthetic. Graphics capabilities are limited, but
what is present, - is heavily optimized. It is rather different from performance-driven approach that tries to take
advantage of the latest hardware and features, sacrificing both the reach and portability.
<p><b>Portability.</b> Written in C11 with all dependencies being in C, it's possible to compile it to most platforms.
SDL2 is at the center and you cannot get better than this. Default graphics API is OpenGL 1.5 with extension detection.
As an example, it builds and runs on Haiku OS with LLVMpipe.
<p><b>Language agnosticism.</b> API is restricted to not require much glue.
Language interpreters don't need to be part of the engine itself. Anything with C ABI support can link to it.
<p><b>Use-case orientation.</b> It doesn't try to be a yet another general purpose engine conquering all and nothing.
Instead we seek particular use cases and implement the most essential parts.
<p><b>Iterability.</b> Defaults provided for most of the API, making initial code faster to spring.
Hot reloading for both assets and code.
<p><b>Low latency.</b> Care is given to various incarnations of feared latency. Engine is fast to build, allowing for low commitment patches.
Startup time is profiled and optimized. Streaming is used as much as possible for asset load.
</blockquote>
<p><a name="wiki"></a><strong>1.2 </strong><strong>Wiki</strong>
<blockquote>
<p>Purpose of this wiki is to collect information on various usages of {twn} across the genres, FAQ style.
You're welcomed to contribute to it. It's written in HTML so basic you can edit it by hand.
Just check <a href="template.html">template.html</a>.
</blockquote>
</body>
</html>

View File

@ -1,24 +0,0 @@
<!doctype html>
<html>
<head>
<title>Townengine Wiki</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>Townengine Wiki</h1>
<table>
<tr>
<td><strong>1.</strong> <a href="#about-townengine">About Townengnine</a></td>
<td><strong>2.</strong> <a href="#making-2dot5d-shooters">Making 2.5D Shooters</a></td>
</tr>
</tbody></table>
<p><a name="about-townengine"></a><strong>1. </strong><a href="about-townengine.html"><strong>About Townengine</strong></a></p>
<blockquote>
<p style="margin:0">1.1 <a href="about-townengine.html#introduction">Introduction</a></p>
<p style="margin:0">1.2 <a href="about-townengine.html#wiki">Wiki</a></p>
</blockquote>
<p><a name="making-2dot5d-shooters"></a><strong>2. </strong><a href="making-2dot5d-shooters.html"><strong>Making 2.5D Shooters</strong></a></p>
<blockquote>
</blockquote>
</body>
</html>

View File

@ -1,19 +0,0 @@
/* https://wiki.c2.com/?WikiStyle */
body { margin: 1em 2.3em 1em 1.5em; zoom: 150%; padding-bottom: 50px;}
h1 { font-size: 2.1em;
font-weight: bold;
}
p { margin-left: 0.2em;
}
blockquote {
font-style: normal;
}
pre { margin: 0em 3em 0em 2em;
color: rgb(20%,20%,50%); background-color: rgb(100%,100%,100%);
border: 1px solid rgb(50%,50%,50%);
padding: 1em;
font-size: 0.85em;
white-space: pre;
}
hr { color: rgb(30%,30%,60%);
}

View File

@ -1,15 +0,0 @@
<!doctype html>
<html>
<head>
<title>Townengine Wiki : {About}</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1 style="margin-bottom:0in;">1. {About}</h1>
<a href="index.html">Go back
<p><a name="{Section}"></a><strong>1.1 </strong><strong>{Section}</strong>
<blockquote>
<p>Text
</blockquote>
</body>
</html>

View File

@ -259,8 +259,9 @@ void audio_play(const char *path,
request.format = AUDIO_F32; request.format = AUDIO_F32;
request.channels = 2; request.channels = 2;
request.samples = 4096; request.samples = 4096;
if (!ctx.push_audio_model) #ifndef TWN_FEATURE_PUSH_AUDIO
request.callback = audio_callback; request.callback = audio_callback;
#endif
/* TODO: check for errors */ /* TODO: check for errors */
ctx.audio_device = SDL_OpenAudioDevice(NULL, 0, &request, &got, 0); ctx.audio_device = SDL_OpenAudioDevice(NULL, 0, &request, &got, 0);
ctx.audio_stream_format = got.format; ctx.audio_stream_format = got.format;
@ -278,8 +279,6 @@ void audio_play(const char *path,
ctx.audio_initialized = true; ctx.audio_initialized = true;
} }
SDL_LockAudioDevice(ctx.audio_device);
if (channel) { if (channel) {
AudioChannelItem *pair = shgetp_null(ctx.audio_channels, channel); AudioChannelItem *pair = shgetp_null(ctx.audio_channels, channel);
@ -332,8 +331,6 @@ void audio_play(const char *path,
arrpush(ctx.unnamed_audio_channels, new_channel); arrpush(ctx.unnamed_audio_channels, new_channel);
} }
SDL_UnlockAudioDevice(ctx.audio_device);
} }

View File

@ -83,7 +83,6 @@ typedef struct EngineContext {
/* signals mouse focus, used to disable mouse capture */ /* signals mouse focus, used to disable mouse capture */
bool window_mouse_resident; bool window_mouse_resident;
bool audio_initialized; bool audio_initialized;
bool push_audio_model;
bool cull_faces; bool cull_faces;
} EngineContext; } EngineContext;

View File

@ -24,7 +24,46 @@
static SDL_Thread *opengl_load_thread; static SDL_Thread *opengl_load_thread;
/* note: it drives most of IO implicitly, such as audio callbacks */ static int event_callback(void *userdata, SDL_Event *event) {
(void)userdata;
switch (event->type) {
case SDL_WINDOWEVENT:
if (event->window.windowID != ctx.window_id)
break;
switch (event->window.event) {
case SDL_WINDOWEVENT_SIZE_CHANGED:
ctx.window_dims.x = (float)event->window.data1;
ctx.window_dims.y = (float)event->window.data2;
ctx.resync_flag = true;
break;
case SDL_WINDOWEVENT_FOCUS_LOST: {
ctx.window_mouse_resident = false;
break;
}
case SDL_WINDOWEVENT_FOCUS_GAINED: {
ctx.window_mouse_resident = true;
break;
}
default:
break;
}
break;
default:
break;
}
/* ignored */
return 0;
}
static void poll_events(void) { static void poll_events(void) {
SDL_Event e; SDL_Event e;
@ -42,22 +81,9 @@ static void poll_events(void) {
switch (e.window.event) { switch (e.window.event) {
case SDL_WINDOWEVENT_SIZE_CHANGED: case SDL_WINDOWEVENT_SIZE_CHANGED:
ctx.window_dims.x = (float)e.window.data1;
ctx.window_dims.y = (float)e.window.data2;
ctx.resync_flag = true;
ctx.window_size_has_changed = true; ctx.window_size_has_changed = true;
break; break;
case SDL_WINDOWEVENT_FOCUS_LOST: {
ctx.window_mouse_resident = false;
break;
}
case SDL_WINDOWEVENT_FOCUS_GAINED: {
ctx.window_mouse_resident = true;
break;
}
default: default:
break; break;
} }
@ -200,9 +226,8 @@ static void main_loop(void) {
input_state_update_postframe(&ctx.input); input_state_update_postframe(&ctx.input);
/* TODO: make it works when ctx.ticks_per_second != 60 */ /* TODO: make it works when ctx.ticks_per_second != 60 */
if (ctx.push_audio_model) { #ifdef TWN_FEATURE_PUSH_AUDIO
uint64_t const queued_frames = SDL_GetQueuedAudioSize(ctx.audio_device) / sizeof (float) / 2; uint64_t const queued_frames = SDL_GetQueuedAudioSize(ctx.audio_device) / sizeof (float) / 2;
/* note: this is here to reduce drift which sometimes appears */
if (queued_frames >= 4096 * 2) if (queued_frames >= 4096 * 2)
SDL_ClearQueuedAudio(ctx.audio_device); SDL_ClearQueuedAudio(ctx.audio_device);
static uint8_t audio_buffer[(AUDIO_FREQUENCY / 60) * sizeof (float) * 2]; static uint8_t audio_buffer[(AUDIO_FREQUENCY / 60) * sizeof (float) * 2];
@ -211,7 +236,7 @@ static void main_loop(void) {
if (SDL_QueueAudio(ctx.audio_device, audio_buffer, sizeof audio_buffer)) if (SDL_QueueAudio(ctx.audio_device, audio_buffer, sizeof audio_buffer))
CRY_SDL("Error queueing audio: "); CRY_SDL("Error queueing audio: ");
} }
} #endif
/* TODO: ctx.game_copy = ctx.game should be placed after it, but it messes with state used in render() */ /* TODO: ctx.game_copy = ctx.game should be placed after it, but it messes with state used in render() */
preserve_persistent_ctx_fields(); preserve_persistent_ctx_fields();
@ -521,6 +546,9 @@ static bool initialize(void) {
ctx.game.resolution.x = (float)ctx.base_render_width; ctx.game.resolution.x = (float)ctx.base_render_width;
ctx.game.resolution.y = (float)ctx.base_render_height; ctx.game.resolution.y = (float)ctx.base_render_height;
/* add a watcher for immediate updates on window size */
SDL_AddEventWatch(event_callback, NULL);
/* random seeding */ /* random seeding */
/* SDL_GetPerformanceCounter returns some platform-dependent number. */ /* SDL_GetPerformanceCounter returns some platform-dependent number. */
/* it should vary between game instances. i checked! random enough for me. */ /* it should vary between game instances. i checked! random enough for me. */
@ -555,7 +583,8 @@ static bool initialize(void) {
ctx.time_averager[i] = ctx.desired_frametime; ctx.time_averager[i] = ctx.desired_frametime;
} }
/* engine configuration */ /* rendering */
/* configuration */
{ {
toml_datum_t datum_texture_atlas_size = toml_int_in(engine, "texture_atlas_size"); toml_datum_t datum_texture_atlas_size = toml_int_in(engine, "texture_atlas_size");
if (!datum_texture_atlas_size.ok) { if (!datum_texture_atlas_size.ok) {
@ -610,10 +639,11 @@ static bool initialize(void) {
} else { } else {
ctx.cull_faces = datum_cull_faces.u.b; ctx.cull_faces = datum_cull_faces.u.b;
} }
}
toml_datum_t datum_audio_model = toml_string_in(engine, "audio_model"); /* these are dynamic arrays and will be allocated lazily by stb_ds */
ctx.push_audio_model = datum_audio_model.ok ? SDL_strncmp(datum_audio_model.u.s, "push", sizeof "push" - 1) == 0 : false; ctx.render_queue_2d = NULL;
SDL_free(datum_audio_model.u.s); ctx.uncolored_mesh_batches = NULL;
/* input */ /* input */
toml_datum_t datum_keybind_slots = toml_int_in(engine, "keybind_slots"); toml_datum_t datum_keybind_slots = toml_int_in(engine, "keybind_slots");
@ -626,8 +656,6 @@ static bool initialize(void) {
ctx.keybind_slots = datum_keybind_slots.u.i; ctx.keybind_slots = datum_keybind_slots.u.i;
} }
} }
}
input_state_init(&ctx.input); input_state_init(&ctx.input);
ctx.render_double_buffered = true; ctx.render_double_buffered = true;
@ -647,11 +675,6 @@ static bool initialize(void) {
profile_end("opengl loading"); profile_end("opengl loading");
SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0"); SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0");
SDL_SetHint(SDL_HINT_WINDOWS_NO_CLOSE_ON_ALT_F4, "1");
SDL_SetHint(SDL_HINT_WAVE_FACT_CHUNK, "ignorezero");
SDL_SetHint(SDL_HINT_VIDEO_DOUBLE_BUFFER, "1");
if (ctx.game.debug)
SDL_SetHint(SDL_HINT_SHUTDOWN_DBUS_ON_QUIT, "1");
/* TODO: investigate viability of detached thread driver and window creation, as it's the worst load time offender */ /* TODO: investigate viability of detached thread driver and window creation, as it's the worst load time offender */
profile_start("window creation"); profile_start("window creation");