raii and some memory savings

This commit is contained in:
maksw authored and Maksymilian Wilczynski committed 2025-12-17 21:34:59 +01:00
1 parent 333ebca300
commit 0c24781c81
2 files changed
+204 -122

No files matched your search

+202 -122
View File
@@ -1,20 +1,58 @@
#include <cstdio>
// this file is an absolute clusterfuck to demo the internals.
// if you know any better please make a pr. please.
// i hate lines++; but i don't know any better, do i look like i know frontend?
// branching? use arbitrary lines value, do i look like i know any better?
#include <cstdio>
#include <cstdint>
#include <cstring>
#include <chrono>
#include <vector>
#include <memory>
#include <window.h>
#include <text.h>
#include <compositor.h>
#include <image.h>
// --- RAII wrappers for engine objects ---
struct WindowHandle {
window_t* ptr;
WindowHandle(window_t* w) : ptr(w) {}
~WindowHandle() { if (ptr) window_destroy(ptr); }
window_t* get() { return ptr; }
operator window_t* () { return ptr; }
};
struct CompositorHandle {
compositor_t* ptr;
CompositorHandle(compositor_t* c) : ptr(c) {}
~CompositorHandle() { if (ptr) compositor_destroy(ptr); }
compositor_t* get() { return ptr; }
operator compositor_t* () { return ptr; }
};
struct TextHandle {
text_t* ptr;
TextHandle(text_t* t) : ptr(t) {}
~TextHandle() { if (ptr) text_destroy(ptr); }
text_t* get() { return ptr; }
operator text_t* () { return ptr; }
};
struct ImageHandle {
uint32_t* ptr;
ImageHandle(uint32_t* p) : ptr(p) {}
~ImageHandle() { if (ptr) image_free(ptr); }
uint32_t* get() { return ptr; }
operator uint32_t* () { return ptr; }
};
int main() {
const int WIDTH = 1280;
const int HEIGHT = 720;
const int CHAR_WIDTH = 400;
const int CHAR_HEIGHT = 600;
// --- New Character Positioning Constants ---
// --- Character Positioning Constants ---
// The vertical baseline for character sprites, making positioning independent of sprite height.
const int CHAR_BASELINE_Y = HEIGHT - 140;
// The horizontal center for the character on the left.
@@ -24,86 +62,82 @@ int main() {
// An off-screen position to the left for transitions, based on the final position.
const int OFFSCREEN_LEFT_X = LEFT_CHAR_CENTER_X - 660;
window_t* window = window_create(WIDTH, HEIGHT, u8"The Obwarzanek Narrative Engine");
if (!window) {
WindowHandle window(window_create(WIDTH, HEIGHT, u8"The Obwarzanek Narrative Engine"));
if (!window.get()) {
printf("Failed to create window\n");
return 1;
}
compositor_t* compositor = compositor_init(WIDTH, HEIGHT);
if (!compositor) {
CompositorHandle compositor(compositor_init(WIDTH, HEIGHT));
if (!compositor.get()) {
printf("Failed to initialize compositor\n");
window_destroy(window);
return 1;
}
uint32_t* bg = image_load_resized("assets/background.png", WIDTH, HEIGHT);
if (bg) {
uint32_t bg_id = compositor_insert(compositor, bg, WIDTH, HEIGHT, 0, 0, 0, 1.0f, ANCHOR_TOP_LEFT);
compositor_set_alpha(compositor, bg_id, 1.0f);
ImageHandle bg(image_load_resized("assets/background.png", WIDTH, HEIGHT));
if (bg.get()) {
uint32_t bg_id = compositor_insert(compositor.get(), bg.get(), WIDTH, HEIGHT, 0, 0, 0, 1.0f, ANCHOR_TOP_LEFT);
compositor_set_alpha(compositor.get(), bg_id, 1.0f);
}
uint32_t* textbox = image_load_resized("assets/textbox.png", WIDTH, 180);
if (textbox) {
ImageHandle textbox(image_load_resized("assets/textbox.png", WIDTH, 180));
if (textbox.get()) {
// Position via the bottom-left corner for easier alignment with screen bottom
compositor_insert(compositor, textbox, WIDTH, 180, 0, HEIGHT, 3, 1.0f, ANCHOR_BOTTOM_LEFT);
compositor_insert(compositor.get(), textbox.get(), WIDTH, 180, 0, HEIGHT, 3, 1.0f, ANCHOR_BOTTOM_LEFT);
}
uint32_t* maksw_happy = image_load_resized("assets/maksw.png", CHAR_WIDTH, CHAR_HEIGHT);
ImageHandle maksw_happy(image_load_resized("assets/maksw.png", CHAR_WIDTH, CHAR_HEIGHT));
// Insert off-screen left, anchored at bottom-center for natural scaling
uint32_t maksw_happy_id = compositor_insert(compositor, maksw_happy,
uint32_t maksw_happy_id = compositor_insert(compositor.get(), maksw_happy.get(),
CHAR_WIDTH, CHAR_HEIGHT, OFFSCREEN_LEFT_X, CHAR_BASELINE_Y, 1, 1.0f, ANCHOR_BOTTOM_CENTER);
uint32_t* maksw_neutral = image_load_resized("assets/maksw2.png", CHAR_WIDTH, CHAR_HEIGHT);
ImageHandle maksw_neutral(image_load_resized("assets/maksw2.png", CHAR_WIDTH, CHAR_HEIGHT));
// Insert at the final left position, anchored at bottom-center
uint32_t maksw_neutral_id = compositor_insert(compositor, maksw_neutral,
uint32_t maksw_neutral_id = compositor_insert(compositor.get(), maksw_neutral.get(),
CHAR_WIDTH, CHAR_HEIGHT, LEFT_CHAR_CENTER_X, CHAR_BASELINE_Y, 1, 1.0f, ANCHOR_BOTTOM_CENTER);
compositor_set_visible(compositor, maksw_neutral_id, false);
compositor_set_visible(compositor.get(), maksw_neutral_id, false);
uint32_t* wikor = image_load_resized("assets/wikor.png", CHAR_WIDTH, CHAR_HEIGHT);
ImageHandle wikor(image_load_resized("assets/wikor.png", CHAR_WIDTH, CHAR_HEIGHT));
// Insert at the final right position, anchored at bottom-center
uint32_t wikor_id = compositor_insert(compositor, wikor,
uint32_t wikor_id = compositor_insert(compositor.get(), wikor.get(),
CHAR_WIDTH, CHAR_HEIGHT, RIGHT_CHAR_CENTER_X, CHAR_BASELINE_Y, 2, 1.0f, ANCHOR_BOTTOM_CENTER);
text_t* name_text = text_init(300, 100);
text_set_font(name_text, "assets/Aller_Rg.ttf", 40.0f);
TextHandle name_text(text_init(300, 100));
text_set_font(name_text.get(), "assets/Aller_Rg.ttf", 40.0f);
// Text is anchored at top-left, positioned relative to textbox
uint32_t name_id = compositor_insert(compositor, text_getfb(name_text),
uint32_t name_id = compositor_insert(compositor.get(), text_getfb(name_text.get()),
300, 100, 60, HEIGHT - 180, 4, 1.0f, ANCHOR_TOP_LEFT);
text_t* dialogue_text = text_init(WIDTH - 140, 160);
text_set_font(dialogue_text, "assets/Vera.ttf", 30.0f);
TextHandle dialogue_text(text_init(WIDTH - 140, 160));
text_set_font(dialogue_text.get(), "assets/Vera.ttf", 30.0f);
// Text is anchored at top-left, positioned relative to textbox
uint32_t dialogue_id = compositor_insert(compositor, text_getfb(dialogue_text),
uint32_t dialogue_id = compositor_insert(compositor.get(), text_getfb(dialogue_text.get()),
WIDTH - 140, 160, 60, HEIGHT - 130, 4, 1.0f, ANCHOR_TOP_LEFT);
uint32_t* mask = image_load_resized("assets/mask.png", 1280, 1080);
ImageHandle mask(image_load_resized("assets/mask.png", 1280, 1080));
// Use top-left anchor for full-screen wipes
uint32_t mask_id = compositor_insert(compositor, mask, 1280, 1080, 0, -1080, 5, 1.0f, ANCHOR_TOP_LEFT);
compositor_set_visible(compositor, mask_id, false);
uint32_t mask_id = compositor_insert(compositor.get(), mask.get(), 1280, 1080, 0, -1080, 5, 1.0f, ANCHOR_TOP_LEFT);
compositor_set_visible(compositor.get(), mask_id, false);
uint32_t* mask2 = image_load_resized("assets/mask2.png", 1280, 1080);
ImageHandle mask2(image_load_resized("assets/mask2.png", 1280, 1080));
// Use top-left anchor for full-screen wipes
uint32_t mask2_id = compositor_insert(compositor, mask2, 1280, 1080, 0, 0, 5, 1.0f, ANCHOR_TOP_LEFT);
compositor_set_visible(compositor, mask2_id, false);
uint32_t mask2_id = compositor_insert(compositor.get(), mask2.get(), 1280, 1080, 0, 0, 5, 1.0f, ANCHOR_TOP_LEFT);
compositor_set_visible(compositor.get(), mask2_id, false);
uint32_t* black = (uint32_t*)malloc(WIDTH * HEIGHT * sizeof(uint32_t));
for (int i = 0; i < WIDTH * HEIGHT; i++) {
black[i] = 0xFF000000;
}
// Use top-left anchor for full-screen fade
uint32_t black_id = compositor_insert(compositor, black, WIDTH, HEIGHT, 0, 0, 6, 1.0f, ANCHOR_TOP_LEFT);
compositor_set_visible(compositor, black_id, false);
compositor_set_alpha(compositor, black_id, 0.0f);
// yeah! memory savings!
//std::unique_ptr<uint32_t[]> black(new uint32_t[WIDTH * HEIGHT]);
//for (int i = 0; i < WIDTH * HEIGHT; i++) black[i] = 0xFF000000;
//// Use top-left anchor for full-screen fade
//uint32_t black_id = compositor_insert(compositor.get(), black.get(), WIDTH, HEIGHT, 0, 0, 6, 1.0f, ANCHOR_TOP_LEFT);
//compositor_set_visible(compositor.get(), black_id, false);
//compositor_set_alpha(compositor.get(), black_id, 0.0f);
// Animate character into position using the new centered coordinates
compositor_animate_move(compositor, maksw_happy_id, LEFT_CHAR_CENTER_X, CHAR_BASELINE_Y, 1.0f, EASE_OUT_CUBIC);
compositor_animate_scale(compositor, maksw_happy_id, 1.05f, 0.3f, EASE_IN_OUT_CUBIC, true);
text_push(name_text, "maksw", 0.0f, true);
text_push(dialogue_text,
"Hey there! Welcome to the Obwarzanek Narrative Engine demo. "
"This is a simple visual novel engine showcasing text rendering, "
"image compositing, and basic animation features.",
compositor_animate_move(compositor.get(), maksw_happy_id, LEFT_CHAR_CENTER_X, CHAR_BASELINE_Y, 1.0f, EASE_OUT_CUBIC);
compositor_animate_scale(compositor.get(), maksw_happy_id, 1.05f, 0.3f, EASE_IN_OUT_CUBIC, true);
text_push(name_text.get(), "maksw", 0.0f, true);
text_push(dialogue_text.get(),
"Hello and welcome to the Obwarzanek Narrative Engine demo!",
30.0f, true);
bool running = true;
@@ -116,7 +150,7 @@ int main() {
const std::chrono::duration<float> REPORT_INTERVAL = std::chrono::seconds(5);
while (running) {
if (window_pollevent(window)) running = false;
if (window_pollevent(window.get())) running = false;
auto current_time = std::chrono::high_resolution_clock::now();
std::chrono::duration<float> delta_duration = current_time - last_frame_time;
@@ -129,74 +163,135 @@ int main() {
std::chrono::duration<float> time_since_last_report = current_time - last_report_time;
if (time_since_last_report >= REPORT_INTERVAL) {
// Only report if at least one frame has passed
if (frame_count > 0) {
float avg_delta_time = frame_time_sum / (float)frame_count;
float fps = 1.0f / avg_delta_time;
// Calculate the actual duration the FPS was measured over (should be approx 5.0f)
float actual_duration = time_since_last_report.count();
printf("FPS: %.2f (over %d frames in %.2f seconds)\n",
fps, frame_count, actual_duration);
}
// Reset counters and update last_report_time
frame_count = 0;
frame_time_sum = 0.0f;
last_report_time = current_time; // Mark the current time as the new report start
last_report_time = current_time;
}
static bool mouse_was_down = false;
bool mouse_is_down = window_is_mouse_button_down(window, MOUSE_BUTTON_LEFT);
bool mouse_is_down = window_is_mouse_button_down(window.get(), MOUSE_BUTTON_LEFT);
bool mouse_clicked = mouse_is_down && !mouse_was_down;
mouse_was_down = mouse_is_down;
static bool key_was_down = false;
bool key_is_down = window_is_key_down(window, KEY_SPACE);
bool key_is_down = window_is_key_down(window.get(), KEY_SPACE);
bool key_pressed = key_is_down && !key_was_down;
key_was_down = key_is_down;
// don't judge me, it will work for now
static unsigned int lines = 0;
static unsigned int lines = 2;
static bool dialog_finished = false;
static bool transitioning = false;
if (mouse_clicked || key_pressed) {
if (text_is_animating(dialogue_text)) {
text_show_all_current(dialogue_text);
if (text_is_animating(dialogue_text.get())) {
text_show_all_current(dialogue_text.get());
}
else {
// this will bite me
switch (lines) {
case 0:
// line 2
compositor_animation_complete_all(compositor);
compositor_animate_scale(compositor, maksw_happy_id, 1.0f, 0.3f, EASE_IN_OUT_CUBIC);
compositor_animate_scale(compositor, wikor_id, 1.1f, 0.3f, EASE_IN_OUT_CUBIC);
text_clear(dialogue_text);
text_clear(name_text);
text_push(name_text, "wikor", 0.0f, true);
text_push(dialogue_text, "Hello to you too, maksw..", 30.0f, true);
case 2:
compositor_animation_complete_all(compositor.get());
text_clear(dialogue_text.get());
text_push(dialogue_text.get(), "Today I'll present to you-", 30.0f, true);
lines++;
break;
case 1:
// line 3
compositor_animation_complete_all(compositor);
compositor_set_visible(compositor, mask_id, true);
compositor_animate_move(compositor, mask_id, 0, 0, 0.5f, EASE_OUT_CUBIC, false);
case 3:
compositor_animation_complete_all(compositor.get());
compositor_animate_scale(compositor.get(), maksw_happy_id, 1.0f, 0.3f, EASE_IN_OUT_CUBIC);
compositor_animate_scale(compositor.get(), wikor_id, 1.05f, 0.3f, EASE_IN_OUT_CUBIC);
text_clear(dialogue_text.get());
text_clear(name_text.get());
text_push(name_text.get(), "wikor", 0.0f, true);
text_push(dialogue_text.get(), "Yeah, hello to you too.", 30.0f, true);
lines++;
break;
case 4:
compositor_animation_complete_all(compositor.get());
compositor_animate_scale(compositor.get(), wikor_id, 1.0f, 0.3f, EASE_IN_OUT_CUBIC);
compositor_animate_scale(compositor.get(), maksw_happy_id, 1.05f, 0.3f, EASE_IN_OUT_CUBIC);
text_clear(dialogue_text.get());
text_clear(name_text.get());
text_push(name_text.get(), "maksw", 0.0f, true);
text_push(dialogue_text.get(), "Oh Wiktor, what a nice suprise!", 30.0f, true);
lines++;
break;
case 5:
compositor_animation_complete_all(compositor.get());
text_clear(dialogue_text.get());
text_push(dialogue_text.get(), "Do you need something?", 30.0f, true);
lines++;
break;
case 6:
compositor_animation_complete_all(compositor.get());
compositor_animate_scale(compositor.get(), maksw_happy_id, 1.0f, 0.3f, EASE_IN_OUT_CUBIC);
compositor_animate_scale(compositor.get(), wikor_id, 1.05f, 0.3f, EASE_IN_OUT_CUBIC);
text_clear(dialogue_text.get());
text_clear(name_text.get());
text_push(name_text.get(), "wikor", 0.0f, true);
text_push(dialogue_text.get(), "No, i was just walking by.", 30.0f, true);
lines++;
break;
case 7:
compositor_animation_complete_all(compositor.get());
text_clear(dialogue_text.get());
text_push(dialogue_text.get(), "Cool engine though.", 30.0f, true);
lines++;
break;
case 8:
// fade transition
compositor_animation_complete_all(compositor.get());
compositor_set_visible(compositor.get(), mask_id, true);
compositor_animate_move(compositor.get(), mask_id, 0, 0, 0.5f, EASE_OUT_CUBIC, false);
transitioning = true;
lines++;
break;
case 2:
// (line 4), fade out
compositor_animation_complete_all(compositor);
compositor_animate_scale(compositor, maksw_neutral_id, 1.0f, 0.3f, EASE_IN_OUT_CUBIC, false);
compositor_set_visible(compositor, black_id, true);
compositor_animate_fade(compositor, black_id, 1.0f, 1.0f, EASE_IN_CUBIC, false);
case 9:
// some fucking how?
break;
case 10:
compositor_animation_complete_all(compositor.get());
compositor_animate_scale(compositor.get(), wikor_id, 1.0f, 0.3f, EASE_IN_OUT_CUBIC);
compositor_animate_scale(compositor.get(), maksw_neutral_id, 1.05f, 0.3f, EASE_IN_OUT_CUBIC);
text_clear(dialogue_text.get());
text_clear(name_text.get());
text_push(name_text.get(), "maksw", 0.0f, true);
text_push(dialogue_text.get(), "Can't complain, the winter break is just a week away. Plus the compositor is finally cooperating.", 30.0f, true);
lines++;
break;
case 11:
compositor_animation_complete_all(compositor.get());
compositor_animate_scale(compositor.get(), maksw_neutral_id, 1.0f, 0.3f, EASE_IN_OUT_CUBIC);
compositor_animate_scale(compositor.get(), wikor_id, 1.05f, 0.3f, EASE_IN_OUT_CUBIC);
text_clear(dialogue_text.get());
text_clear(name_text.get());
text_push(name_text.get(), "wikor", 0.0f, true);
text_push(dialogue_text.get(), "Sure thing..", 30.0f, true);
lines++;
break;
case 12:
compositor_animation_complete_all(compositor.get());
text_clear(dialogue_text.get());
text_push(dialogue_text.get(), "You're talking to me like i know anything about computers.", 30.0f, true);
lines++;
break;
case 13:
// fade to black and end
compositor_animation_complete_all(compositor.get());
compositor_animate_scale(compositor.get(), maksw_neutral_id, 1.0f, 0.3f, EASE_IN_OUT_CUBIC, false);
//compositor_set_visible(compositor.get(), black_id, true);
//compositor_animate_fade(compositor.get(), black_id, 1.0f, 0.7f, EASE_IN_CUBIC, false);
// yeah! memory savings!
compositor_set_visible(compositor.get(), mask_id, true);
compositor_set_alpha(compositor.get(), mask_id, 0.0f);
compositor_animate_fade(compositor.get(), mask_id, 1.0f, 0.8f, EASE_IN_OUT_CUBIC, false);
dialog_finished = true;
lines++;
break;
@@ -207,58 +302,43 @@ int main() {
}
}
compositor_tick(compositor, delta_time_sec);
compositor_tick(compositor.get(), delta_time_sec);
if (transitioning) {
if (compositor_is_element_animating(compositor, mask_id) == false) {
compositor_set_visible(compositor, maksw_happy_id, false);
compositor_set_visible(compositor, maksw_neutral_id, true);
compositor_set_scale(compositor, maksw_neutral_id, 1.05f);
text_clear(dialogue_text);
text_clear(name_text);
text_push(name_text, "maksw", 0.0f, true);
text_push(dialogue_text, "why so.. serious?", 30.0f, true);
if (!compositor_is_element_animating(compositor.get(), mask_id)) {
compositor_set_visible(compositor.get(), maksw_happy_id, false);
compositor_set_visible(compositor.get(), maksw_neutral_id, true);
compositor_set_scale(compositor.get(), maksw_neutral_id, 1.0f);
compositor_set_scale(compositor.get(), wikor_id, 1.05f);
text_clear(dialogue_text.get());
text_clear(name_text.get());
text_push(name_text.get(), "wikor", 0.0f, true);
text_push(dialogue_text.get(), "Anyways, how's your day going?", 30.0f, true);
compositor_set_visible(compositor, mask_id, false);
compositor_set_visible(compositor, mask2_id, true);
compositor_animate_move(compositor, mask2_id, 0, HEIGHT, 0.5f, EASE_OUT_CUBIC, false);
compositor_set_visible(compositor.get(), mask_id, false);
compositor_set_visible(compositor.get(), mask2_id, true);
compositor_animate_move(compositor.get(), mask2_id, 0, HEIGHT, 0.5f, EASE_OUT_CUBIC, false);
transitioning = false;
lines++;
}
}
if (dialog_finished) {
compositor_mark_content_dirty(compositor, black_id);
if (compositor_is_element_animating(compositor, black_id) == false) {
compositor_mark_content_dirty(compositor.get(), mask_id);
if (!compositor_is_element_animating(compositor.get(), mask_id)) {
running = false;
}
}
if (text_nextframe(name_text, delta_time_sec))
compositor_mark_content_dirty(compositor, name_id);
if (text_nextframe(dialogue_text, delta_time_sec))
compositor_mark_content_dirty(compositor, dialogue_id);
if (text_nextframe(name_text.get(), delta_time_sec))
compositor_mark_content_dirty(compositor.get(), name_id);
if (text_nextframe(dialogue_text.get(), delta_time_sec))
compositor_mark_content_dirty(compositor.get(), dialogue_id);
const uint32_t* fb = compositor_compose(compositor);
window_blit(window, fb);
const uint32_t* fb = compositor_compose(compositor.get());
window_blit(window.get(), fb);
}
// no raii because i'm tired already
if (maksw_happy) image_free(maksw_happy);
if (maksw_neutral) image_free(maksw_neutral);
if (wikor) image_free(wikor);
if (black) free(black);
if (mask) image_free(mask);
if (mask2) image_free(mask2);
if (bg) image_free(bg);
if (textbox) image_free(textbox);
text_destroy(name_text);
text_destroy(dialogue_text);
compositor_destroy(compositor);
window_destroy(window);
return 0;
}
}
+2
View File
@@ -102,6 +102,7 @@
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
<EnableEnhancedInstructionSet>StreamingSIMDExtensions2</EnableEnhancedInstructionSet>
<AdditionalOptions>/Zc:char8_t- %(AdditionalOptions)</AdditionalOptions>
<SupportJustMyCode>false</SupportJustMyCode>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
@@ -144,6 +145,7 @@
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<EnableEnhancedInstructionSet>StreamingSIMDExtensions2</EnableEnhancedInstructionSet>
<AdditionalOptions>/Zc:char8_t- %(AdditionalOptions)</AdditionalOptions>
<SupportJustMyCode>false</SupportJustMyCode>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>