Compare commits

...

12 Commits

Author SHA1 Message Date
veclavtalica
f3a2dc9063 stuffs to wiki 2025-02-04 01:13:38 +03:00
veclavtalica
53c43a8f34 various SDL hints to try out ! 2025-02-04 01:04:34 +03:00
veclavtalica
507bff6ed8 /docs/wiki: add clause of low latency 2025-02-04 00:31:15 +03:00
veclavtalica
00636d65a9 fix resizing events, clean up the code 2025-02-04 00:24:31 +03:00
veclavtalica
42253fc58a fix artefacts 2025-02-04 00:24:01 +03:00
veclavtalica
c6cbf941a2 introduce audio_model = "push" to twn.toml 2025-02-04 00:21:30 +03:00
veclavtalica
277d1b2e10 /bin/twn: add wiki command 2025-02-03 22:31:53 +03:00
veclavtalica
46955a19c1 oopsie 2025-02-03 22:28:29 +03:00
veclavtalica
9ab3e942cd /bin/twnbuild: shortening 2025-02-03 22:26:51 +03:00
veclavtalica
c7bb317ead /docs/wiki: fix links, add 1.2 Wiki 2025-02-03 22:15:46 +03:00
veclavtalica
241e72be1a /docs/wiki/style.css: add bottom padding to the page 2025-02-03 22:11:10 +03:00
veclavtalica
f4fccc08c4 start of the /docs/wiki 2025-02-03 21:55:26 +03:00
11 changed files with 162 additions and 78 deletions

View File

@ -27,7 +27,6 @@ set(TWN_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR} CACHE INTERNAL "")
# feature configuration, set them with -DFEATURE=ON/OFF in cli
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)
# todo: figure out how to compile for dynamic linking instead
@ -136,8 +135,6 @@ set_target_properties(${TWN_TARGET} PROPERTIES
C_STANDARD_REQUIRED ON
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
target_precompile_headers(${TWN_TARGET} PRIVATE
$<$<NOT:$<BOOL:${EMSCRIPTEN}>>:third-party/glad/include/glad/glad.h>

View File

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

View File

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

View File

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

View File

@ -0,0 +1,44 @@
<!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>

24
docs/wiki/index.html Normal file
View File

@ -0,0 +1,24 @@
<!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>

19
docs/wiki/style.css Normal file
View File

@ -0,0 +1,19 @@
/* 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%);
}

15
docs/wiki/template.html Normal file
View File

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

View File

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

View File

@ -24,46 +24,7 @@
static SDL_Thread *opengl_load_thread;
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;
}
/* note: it drives most of IO implicitly, such as audio callbacks */
static void poll_events(void) {
SDL_Event e;
@ -81,9 +42,22 @@ static void poll_events(void) {
switch (e.window.event) {
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;
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;
}
@ -226,17 +200,18 @@ static void main_loop(void) {
input_state_update_postframe(&ctx.input);
/* TODO: make it works when ctx.ticks_per_second != 60 */
#ifdef TWN_FEATURE_PUSH_AUDIO
uint64_t const queued_frames = SDL_GetQueuedAudioSize(ctx.audio_device) / sizeof (float) / 2;
if (queued_frames >= 4096 * 2)
SDL_ClearQueuedAudio(ctx.audio_device);
static uint8_t audio_buffer[(AUDIO_FREQUENCY / 60) * sizeof (float) * 2];
if (ctx.audio_initialized) {
audio_callback(NULL, audio_buffer, sizeof audio_buffer);
if (SDL_QueueAudio(ctx.audio_device, audio_buffer, sizeof audio_buffer))
CRY_SDL("Error queueing audio: ");
if (ctx.push_audio_model) {
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)
SDL_ClearQueuedAudio(ctx.audio_device);
static uint8_t audio_buffer[(AUDIO_FREQUENCY / 60) * sizeof (float) * 2];
if (ctx.audio_initialized) {
audio_callback(NULL, audio_buffer, sizeof audio_buffer);
if (SDL_QueueAudio(ctx.audio_device, audio_buffer, sizeof audio_buffer))
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() */
preserve_persistent_ctx_fields();
@ -546,9 +521,6 @@ static bool initialize(void) {
ctx.game.resolution.x = (float)ctx.base_render_width;
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 */
/* SDL_GetPerformanceCounter returns some platform-dependent number. */
/* it should vary between game instances. i checked! random enough for me. */
@ -583,8 +555,7 @@ static bool initialize(void) {
ctx.time_averager[i] = ctx.desired_frametime;
}
/* rendering */
/* configuration */
/* engine configuration */
{
toml_datum_t datum_texture_atlas_size = toml_int_in(engine, "texture_atlas_size");
if (!datum_texture_atlas_size.ok) {
@ -639,23 +610,24 @@ static bool initialize(void) {
} else {
ctx.cull_faces = datum_cull_faces.u.b;
}
}
/* these are dynamic arrays and will be allocated lazily by stb_ds */
ctx.render_queue_2d = NULL;
ctx.uncolored_mesh_batches = NULL;
toml_datum_t datum_audio_model = toml_string_in(engine, "audio_model");
ctx.push_audio_model = datum_audio_model.ok ? SDL_strncmp(datum_audio_model.u.s, "push", sizeof "push" - 1) == 0 : false;
SDL_free(datum_audio_model.u.s);
/* input */
toml_datum_t datum_keybind_slots = toml_int_in(engine, "keybind_slots");
if (!datum_keybind_slots.ok) {
ctx.keybind_slots = KEYBIND_SLOTS_DEFAULT;
} else {
if (datum_keybind_slots.u.i < 1) {
/* input */
toml_datum_t datum_keybind_slots = toml_int_in(engine, "keybind_slots");
if (!datum_keybind_slots.ok) {
ctx.keybind_slots = KEYBIND_SLOTS_DEFAULT;
} else {
ctx.keybind_slots = datum_keybind_slots.u.i;
if (datum_keybind_slots.u.i < 1) {
ctx.keybind_slots = KEYBIND_SLOTS_DEFAULT;
} else {
ctx.keybind_slots = datum_keybind_slots.u.i;
}
}
}
input_state_init(&ctx.input);
ctx.render_double_buffered = true;
@ -675,6 +647,11 @@ static bool initialize(void) {
profile_end("opengl loading");
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 */
profile_start("window creation");