Nuke the old input system, use the new UI system

This commit is contained in:
2026-05-01 15:21:46 -05:00
parent a9948142ad
commit 36db89c36e
17 changed files with 134 additions and 641 deletions
-1
View File
@@ -62,7 +62,6 @@ add_subdirectory(log)
add_subdirectory(engine)
add_subdirectory(entity)
add_subdirectory(error)
add_subdirectory(event)
add_subdirectory(input)
add_subdirectory(locale)
add_subdirectory(physics)
-10
View File
@@ -1,10 +0,0 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Sources
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
event.c
)
-239
View File
@@ -1,239 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "event.h"
#include "assert/assert.h"
#include "util/memory.h"
#include "console/console.h"
void eventInit(
event_t *event,
eventlistener_t *array,
const uint16_t maxListeners
) {
assertNotNull(event, "Event cannot be NULL");
assertNotNull(array, "Listener array cannot be NULL");
assertTrue(maxListeners > 0, "Max listeners must be greater than 0");
memoryZero(event, sizeof(event_t));
event->listenerArray = array;
event->maxListeners = maxListeners;
}
eventsub_t eventSubscribeUser(
event_t *event,
const eventtype_t type,
const eventuserdata_t user
) {
assertNotNull(event, "Event cannot be NULL");
assertTrue(
event->listenerCount < event->maxListeners,
"Maximum number of listeners reached"
);
if(type == EVENT_TYPE_C) {
assertNotNull(
user.c.callback,
"C event listener callback cannot be NULL"
);
} else if(type == EVENT_TYPE_SCRIPT) {
assertNotNull(
user.script.context,
"Script event listener context cannot be NULL"
);
assertTrue(
user.script.funcValue != 0,
"Script event listener function reference is invalid"
);
} else {
assertUnreachable("Unknown event listener type");
}
eventsub_t id = event->nextId++;
assertTrue(event->nextId != 0, "Event subscription ID overflow");
eventlistener_t *listener = &event->listenerArray[event->listenerCount++];
memoryZero(listener, sizeof(eventlistener_t));
listener->user = user;
listener->id = id;
listener->type = type;
return id;
}
eventsub_t eventSubscribe(
event_t *event,
const eventcallback_t callback,
const void *user
) {
return eventSubscribeUser(
event,
EVENT_TYPE_C,
(eventuserdata_t){ .c = { .callback = callback, .user = (void *)user } }
);
}
eventsub_t eventSubscribeScriptContext(
event_t *event,
scriptmanager_t *context,
jerry_value_t funcValue
) {
assertNotNull(context, "Script context cannot be NULL");
assertTrue(funcValue != 0, "Script function value is invalid");
bool_t alreadySubbed = false;
uint8_t i = 0;
do {
if(context->subscribedEvents[i] == event) {
alreadySubbed = true;
break;
}
i++;
} while(i < SCRIPT_MANAGER_MAX_EVENT_SUBSCRIPTIONS);
if(!alreadySubbed) {
i = 0;
do {
if(context->subscribedEvents[i] == NULL) {
context->subscribedEvents[i] = event;
break;
}
i++;
} while(i < SCRIPT_MANAGER_MAX_EVENT_SUBSCRIPTIONS);
assertTrue(
i < SCRIPT_MANAGER_MAX_EVENT_SUBSCRIPTIONS,
"Script context has reached maximum event subscriptions"
);
}
return eventSubscribeUser(
event,
EVENT_TYPE_SCRIPT,
(eventuserdata_t){
.script = {
.context = context,
.funcValue = funcValue
}
}
);
}
void eventUnsubscribe(event_t *event, const eventsub_t id) {
assertNotNull(event, "Event cannot be NULL");
assertFalse(event->isInvoking, "Cannot unsubscribe while invoking event");
if(event->listenerCount == 0) return;
uint16_t index = 0;
do {
if(event->listenerArray[index].id != id) {
index++;
continue;
}
if(event->listenerArray[index].type == EVENT_TYPE_SCRIPT) {
jerry_value_t funcVal = event->listenerArray[index].user.script.funcValue;
if(funcVal != 0) {
jerry_value_free(funcVal);
}
}
event->listenerArray[index] = event->listenerArray[--event->listenerCount];
return;
} while(index < event->listenerCount);
}
void eventUnsubscribeScriptContext(
event_t *event,
const scriptmanager_t *ctx
) {
assertNotNull(event, "Event cannot be NULL");
assertNotNull(ctx, "Script context cannot be NULL");
if(event->listenerCount == 0) return;
uint16_t i = 0;
do {
eventlistener_t *listener = &event->listenerArray[i];
if(
listener->type != EVENT_TYPE_SCRIPT ||
listener->user.script.context != ctx
) {
i++;
continue;
}
eventUnsubscribe(event, listener->id);
} while(i < event->listenerCount);
}
void eventInvoke(
event_t *event,
const void *eventParams,
const char_t *metatableName
) {
assertNotNull(event, "Event cannot be NULL");
if(event->listenerCount == 0) return;
event->isInvoking = true;
eventdata_t data = {
.event = event,
.eventParams = eventParams,
};
uint16_t i = 0;
do {
eventlistener_t *listener = &event->listenerArray[i];
if(listener->type == EVENT_TYPE_C) {
listener->user.c.callback(&data, listener->user.c);
} else if(listener->type == EVENT_TYPE_SCRIPT) {
jerry_value_t funcVal = listener->user.script.funcValue;
assertNotNull((void *)(uintptr_t)funcVal, "Script function value is NULL");
jerry_value_t callArgs[1];
jerry_length_t argCount = 0;
if(eventParams != NULL) {
callArgs[0] = jerry_object();
jerry_object_set_native_ptr(
callArgs[0], &JS_PTR_NATIVE_INFO, (void *)eventParams
);
argCount = 1;
}
jerry_value_t result = jerry_call(
funcVal, jerry_undefined(), callArgs, argCount
);
if(argCount > 0) jerry_value_free(callArgs[0]);
if(jerry_value_is_exception(result)) {
jerry_value_t errStr = jerry_value_to_string(
jerry_exception_value(result, false)
);
char_t buf[256];
jerry_size_t len = jerry_string_to_buffer(
errStr, JERRY_ENCODING_UTF8, (jerry_char_t *)buf, sizeof(buf) - 1
);
buf[len] = '\0';
jerry_value_free(errStr);
consolePrint("Error invoking script event listener:\n%s\n", buf);
}
jerry_value_free(result);
} else {
assertUnreachable("Unknown event listener type");
}
i++;
} while(i < event->listenerCount);
event->isInvoking = false;
}
-124
View File
@@ -1,124 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "eventuser.h"
typedef struct event_s event_t;
typedef struct eventdata_s {
const void *eventParams;
const event_t *event;
} eventdata_t;
typedef struct eventlistener_s {
eventuserdata_t user;
eventtype_t type;
uint16_t id;
} eventlistener_t;
typedef uint16_t eventsub_t;
typedef struct event_s {
eventlistener_t *listenerArray;
uint16_t listenerCount;
uint16_t maxListeners;
eventsub_t nextId;
bool_t isInvoking;
} event_t;
/**
* Initialize an event structure.
*
* @param event The event to initialize.
* @param array The array to hold event listeners.
* @param maxListeners The maximum number of listeners.
*/
void eventInit(
event_t *event,
eventlistener_t *array,
const uint16_t maxListeners
);
/**
* Subscribe to an event.
*
* @param event The event to subscribe to.
* @param type The type of the event (C or Lua).
* @param user User data to pass to the callback.
* @return The subscription ID, used to unsubscribe later.
*/
eventsub_t eventSubscribeUser(
event_t *event,
const eventtype_t type,
const eventuserdata_t user
);
/**
* Subscribe to an event with a C function callback.
*
* @param event The event to subscribe to.
* @param callback The C function callback.
* @param user User data pointer to pass to the callback.
* @return The subscription ID, used to unsubscribe later.
*/
eventsub_t eventSubscribe(
event_t *event,
const eventcallback_t callback,
const void *user
);
/**
* Subscribe to an event with a script function callback.
*
* @param event The event to subscribe to.
* @param context The script context.
* @param functionIndex The index of the Lua function on the stack.
* @return The subscription ID, used to unsubscribe later.
*/
eventsub_t eventSubscribeScriptContext(
event_t *event,
scriptmanager_t *context,
jerry_value_t funcValue
);
/**
* Unsubscribe from an event.
*
* @param event The event to unsubscribe from.
* @param subscription The subscription ID to remove.
*/
void eventUnsubscribe(event_t *event, const eventsub_t subscription);
/**
* Unsubscribe all event listeners associated with a script context.
*
* @param event The event to unsubscribe from.
* @param context The script context whose listeners should be removed.
*/
void eventUnsubscribeScriptContext(
event_t *event,
const scriptmanager_t *ctx
);
/**
* Invoke an event, calling all subscribed listeners. Optionally provide event
* parameters, and if desired pass a metatable name. Event listeners will be
* able to read event params, and if metatable name is provided the script
* listeners will lookup metatable information matching that name to access the
* params as a structure within the script.
*
* @param event The event to invoke.
* @param eventParams Parameters to pass to the event listeners.
* @param metatableName Metatable name. See details.
*/
void eventInvoke(
event_t *event,
const void *eventParams,
const char_t *metatableName
);
-14
View File
@@ -1,14 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
typedef struct eventdata_s eventdata_t;
typedef struct eventc_s eventc_t;
typedef void (*eventcallback_t)(eventdata_t *data, eventc_t user);
-30
View File
@@ -1,30 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "eventcallback.h"
#include "script/scriptmanager.h"
typedef enum {
EVENT_TYPE_C = 0,
EVENT_TYPE_SCRIPT = 1
} eventtype_t;
typedef struct {
scriptmanager_t *context;
jerry_value_t funcValue;
} eventscript_t;
typedef struct eventc_s {
void *user;
eventcallback_t callback;
} eventc_t;
typedef union eventuserdata_u {
eventscript_t script;
eventc_t c;
} eventuserdata_t;
-29
View File
@@ -27,13 +27,6 @@ errorret_t inputInit(void) {
errorChain(inputInitPlatform());
#endif
eventInit(
&INPUT.eventPressed, INPUT.pressedListeners, INPUT_LISTENER_PRESSED_MAX
);
eventInit(
&INPUT.eventReleased, INPUT.releasedListeners, INPUT_LISTENER_RELEASED_MAX
);
errorOk();
}
@@ -94,28 +87,6 @@ void inputUpdate(void) {
#ifdef DUSK_TIME_DYNAMIC
if(TIME.dynamicUpdate) return;
#endif
// if(INPUT.eventPressed.listenerCount > 0) {
// action = &INPUT.actions[0];
// do {
// if(inputPressed(action->action)) {
// inputevent_t inputEvent = { .action = action->action };
// eventInvoke(&INPUT.eventPressed, &inputEvent, "input_mt");
// }
// action++;
// } while(action < &INPUT.actions[INPUT_ACTION_COUNT]);
// }
// if(INPUT.eventReleased.listenerCount > 0) {
// action = &INPUT.actions[0];
// do {
// if(inputReleased(action->action)) {
// inputevent_t inputEvent = { .action = action->action };
// eventInvoke(&INPUT.eventReleased, &inputEvent, "input_mt");
// }
// action++;
// } while(action < &INPUT.actions[INPUT_ACTION_COUNT]);
// }
}
float_t inputGetCurrentValue(const inputaction_t action) {
-10
View File
@@ -9,23 +9,13 @@
#include "error/error.h"
#include "inputbutton.h"
#include "inputaction.h"
#include "event/event.h"
#define INPUT_LISTENER_PRESSED_MAX 16
#define INPUT_LISTENER_RELEASED_MAX INPUT_LISTENER_PRESSED_MAX
typedef struct {
const inputaction_t action;
} inputevent_t;
typedef struct {
inputactiondata_t actions[INPUT_ACTION_COUNT];
eventlistener_t pressedListeners[INPUT_LISTENER_PRESSED_MAX];
event_t eventPressed;
eventlistener_t releasedListeners[INPUT_LISTENER_RELEASED_MAX];
event_t eventReleased;
inputplatform_t platform;
} input_t;
+40 -164
View File
@@ -11,14 +11,12 @@
#include "display/screen/screen.h"
#include "entity/entitymanager.h"
#include "display/shader/shaderunlit.h"
#include "display/mesh/cube.h"
#include "display/spritebatch/spritebatch.h"
#include "display/text/text.h"
#include "display/screen/screen.h"
#include "console/console.h"
#include "util/string.h"
#include "script/scriptmanager.h"
#include "script/module/scene/modulescene.h"
#include "ui/ui.h"
scene_t SCENE;
@@ -55,6 +53,7 @@ errorret_t sceneRender(void) {
COMPONENT_TYPE_CAMERA, camEnts, camComps
);
shader_t *shaderCurrent = NULL;
mat4 view, proj, model;
// For each camera
@@ -70,7 +69,8 @@ errorret_t sceneRender(void) {
entityCameraGetProjection(camEnt, camComp, proj);
entityPositionGetTransform(camEnt, camPos, view);
// For each entity
// For each entity (I could iterate only over entities with mesh/material)
// but in future I may have different renderable types.
for(entityid_t entityId = 0; entityId < ENTITY_COUNT_MAX; entityId++) {
// Does this entity have a material?
componentid_t matComp = entityGetComponent(
@@ -81,9 +81,13 @@ errorret_t sceneRender(void) {
componentid_t meshComp = entityGetComponent(
entityId, COMPONENT_TYPE_MESH
);
if(meshComp == 0xFF) {
logError("Entity with material component without mesh component found\n");
logError("Entity with material but no mesh found\n");
continue;
}
mesh_t *mesh = entityMeshGetMesh(entityId, meshComp);
if(mesh == NULL) {
logError("Entity with material but no mesh found\n");
continue;
}
@@ -93,14 +97,7 @@ errorret_t sceneRender(void) {
);
shader_t *shader = entityMaterialGetShader(entityId, matComp);
if(shader == NULL) {
logError("Entity with material component without shader found\n");
continue;
}
// Get the mesh
mesh_t *mesh = entityMeshGetMesh(entityId, meshComp);
if(mesh == NULL) {
logError("Entity with material component without mesh found\n");
logError("Entity with material but no shader found\n");
continue;
}
@@ -115,9 +112,13 @@ errorret_t sceneRender(void) {
}
// Render the mesh.
errorChain(shaderBind(shader));
errorChain(shaderSetMatrix(shader, SHADER_UNLIT_PROJECTION, proj));
errorChain(shaderSetMatrix(shader, SHADER_UNLIT_VIEW, view));
if(shaderCurrent != shader) {
shaderCurrent = shader;
errorChain(shaderBind(shaderCurrent));
errorChain(shaderSetMatrix(shader, SHADER_UNLIT_PROJECTION, proj));
errorChain(shaderSetMatrix(shader, SHADER_UNLIT_VIEW, view));
}
errorChain(shaderSetMatrix(shader, SHADER_UNLIT_MODEL, model));
errorChain(shaderSetMaterial(shader, material));
errorChain(meshDraw(mesh, 0, -1));
@@ -128,154 +129,29 @@ errorret_t sceneRender(void) {
}
}
// UI Rendering
glm_ortho(
0.0f, SCREEN.width,
SCREEN.height, 0.0f,
0.1f, 100.0f,
proj
);
glm_lookat(
(vec3){ 0.0f, 0.0f, 1.0f },
(vec3){ 0.0f, 0.0f, 0.0f },
(vec3){ 0.0f, 1.0f, 0.0f },
view
);
glm_mat4_identity(model);
errorChain(shaderBind(&SHADER_UNLIT));
errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_PROJECTION, proj));
errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_VIEW, view));
errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_MODEL, model));
errorChain(uiRender());
errorOk();
// if(camCount > 0) {
// // For each entity
// for(entityid_t entityId = 0; entityId < ENTITY_COUNT_MAX; entityId++) {
// }
// entityid_t meshEnts[ENTITY_COUNT_MAX];
// componentid_t meshComps[ENTITY_COUNT_MAX];
// entityid_t meshCount = componentGetEntitiesWithComponent(
// COMPONENT_TYPE_MESH, meshEnts, meshComps
// );
// if(meshCount > 0) {
// errorChain(shaderBind(&SHADER_UNLIT));
// for(entityid_t camIndex = 0; camIndex < camCount; camIndex++) {
// entityid_t camEnt = camEnts[camIndex];
// componentid_t camComp = camComps[camIndex];
// componentid_t camPos = entityGetComponent(camEnt, COMPONENT_TYPE_POSITION);
// if(camPos == 0xFF) {
// logError("Camera entity without entity position found\n");
// continue;
// }
// entityCameraGetProjection(camEnt, camComp, proj);
// entityPositionGetTransform(camEnt, camPos, view);
// for(entityid_t meshIndex = 0; meshIndex < meshCount; meshIndex++) {
// entityid_t meshEnt = meshEnts[meshIndex];
// componentid_t meshComp = meshComps[meshIndex];
// mesh_t *mesh = entityMeshGetMesh(meshEnt, meshComp);
// if(mesh == NULL) continue;
// componentid_t meshPos = entityGetComponent(
// meshEnt, COMPONENT_TYPE_POSITION
// );
// if(meshPos == 0xFF) {
// logError("Mesh entity without entity position found\n");
// continue;
// }
// componentid_t meshMat = entityGetComponent(
// meshEnt, COMPONENT_TYPE_MATERIAL
// );
// if(meshMat == 0xFF) {
// logError("Mesh entity without material component found\n");
// continue;
// }
// shadermaterial_t *material = entityMaterialGetShaderMaterial(
// meshEnt, meshMat
// );
// shader_t *shader = entityMaterialGetShader(meshEnt, meshMat);
// if(shader == NULL) {
// logError("Mesh entity with material component without shader found\n");
// continue;
// }
// entityPositionGetTransform(meshEnt, meshPos, model);
// errorChain(shaderBind(shader));
// errorChain(shaderSetMatrix(shader, SHADER_UNLIT_PROJECTION, proj));
// errorChain(shaderSetMatrix(shader, SHADER_UNLIT_VIEW, view));
// errorChain(shaderSetMatrix(shader, SHADER_UNLIT_MODEL, model));
// errorChain(shaderSetMaterial(shader, material));
// errorChain(meshDraw(mesh, 0, -1));
// }
// }
// }
// }
// glm_ortho(
// 0.0f, SCREEN.width,
// SCREEN.height, 0.0f,
// 0.1f, 100.0f,
// proj
// );
// glm_lookat(
// (vec3){ 0.0f, 0.0f, 1.0f },
// (vec3){ 0.0f, 0.0f, 0.0f },
// (vec3){ 0.0f, 1.0f, 0.0f },
// view
// );
// glm_mat4_identity(model);
// errorChain(shaderBind(&SHADER_UNLIT));
// errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_PROJECTION, proj));
// errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_VIEW, view));
// errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_MODEL, model));
// {
// entityid_t sprEnts[ENTITY_COUNT_MAX];
// componentid_t sprComps[ENTITY_COUNT_MAX];
// entityid_t sprCount = componentGetEntitiesWithComponent(
// COMPONENT_TYPE_SPRITE, sprEnts, sprComps
// );
// for(entityid_t si = 0; si < sprCount; si++) {
// entitysprite_t *spr = entitySpriteGet(sprEnts[si], sprComps[si]);
// vec3 pos = { 0.0f, 0.0f, 0.0f };
// componentid_t posComp = entityGetComponent(
// sprEnts[si], COMPONENT_TYPE_POSITION
// );
// if(posComp != 0xFF) {
// entityPositionGetPosition(sprEnts[si], posComp, pos);
// }
// errorChain(shaderSetTexture(
// &SHADER_UNLIT, SHADER_UNLIT_TEXTURE, spr->texture
// ));
// #if !MESH_ENABLE_COLOR
// errorChain(shaderSetColor(
// &SHADER_UNLIT, SHADER_UNLIT_COLOR, spr->color
// ));
// #endif
// errorChain(spriteBatchPush(
// pos[0], pos[1],
// pos[0] + spr->width, pos[1] + spr->height,
// #if MESH_ENABLE_COLOR
// spr->color,
// #endif
// spr->uv[0], spr->uv[1],
// spr->uv[2], spr->uv[3]
// ));
// errorChain(spriteBatchFlush());
// }
// }
// errorChain(consoleDraw());
// // FPS
// char_t fpsText[32];
// dusktimeepoch_t now = timeGetEpoch();
// double_t delta = now.time - LAST.time;
// LAST = now;
// double_t fps = delta > 0 ? 1.0 / delta : 0.0;
// snprintf(fpsText, sizeof(fpsText), "FPS: %.2f", fps);
// errorChain(spriteBatchFlush());
// errorChain(textDraw(
// 0, 0,
// fpsText, COLOR_WHITE,
// &FONT_TILESET_DEFAULT, &FONT_TEXTURE_DEFAULT
// ));
// errorChain(spriteBatchFlush());
// errorOk();
}
errorret_t sceneSetImmediate(const char_t *scene) {
-1
View File
@@ -8,7 +8,6 @@
#pragma once
#include "script/scriptmanager.h"
#include "asset/assetfile.h"
#include "event/event.h"
#define SCENE_EVENT_UPDATE_MAX 16
-7
View File
@@ -9,7 +9,6 @@
#include "assert/assert.h"
#include "asset/asset.h"
#include "util/memory.h"
#include "event/event.h"
#include "asset/loader/script/assetscriptloader.h"
#include "script/module/module.h"
@@ -79,12 +78,6 @@ errorret_t scriptManagerExecFile(
}
errorret_t scriptManagerDispose(void) {
for(uint8_t i = 0; i < SCRIPT_MANAGER_MAX_EVENT_SUBSCRIPTIONS; i++) {
event_t *event = SCRIPT_MANAGER.subscribedEvents[i];
if(event == NULL) continue;
eventUnsubscribeScriptContext(event, &SCRIPT_MANAGER);
}
jerry_cleanup();
errorOk();
}
+1 -3
View File
@@ -9,12 +9,10 @@
#include "error/error.h"
#include "scriptvalue.h"
typedef struct event_s event_t;
#define SCRIPT_MANAGER_MAX_EVENT_SUBSCRIPTIONS 64
typedef struct {
event_t *subscribedEvents[SCRIPT_MANAGER_MAX_EVENT_SUBSCRIPTIONS];
void* nothing;
} scriptmanager_t;
extern scriptmanager_t SCRIPT_MANAGER;
+1
View File
@@ -7,4 +7,5 @@
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
ui.c
uifps.c
)
+7 -8
View File
@@ -10,6 +10,8 @@
#include "assert/assert.h"
#include "display/spritebatch/spritebatch.h"
#include "display/screen/screen.h"
#include "ui/uifps.h"
#include "console/console.h"
ui_t UI;
@@ -26,15 +28,12 @@ errorret_t uiInit(void) {
void uiUpdate(void) {
}
void uiRender(void) {
// UI.camera.orthographic.right = SCREEN.width;
// UI.camera.orthographic.top = SCREEN.height;
errorret_t uiRender(void) {
errorChain(uiFPSDraw());
errorChain(consoleDraw());
errorChain(spriteBatchFlush());
// // cameraPushMatrix(&UI.camera);
// spriteBatchClear();
// spriteBatchFlush();
// // cameraPopMatrix();
errorOk();
}
void uiDispose(void) {
+3 -1
View File
@@ -26,8 +26,10 @@ void uiUpdate(void);
/**
* Renders the UI system.
*
* @return Any error that occurs.
*/
void uiRender(void);
errorret_t uiRender(void);
/**
* Disposes of the UI system.
+58
View File
@@ -0,0 +1,58 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "uifps.h"
#include "time/time.h"
#include "display/spritebatch/spritebatch.h"
#include "display/text/text.h"
uifps_t UIFPS;
errorret_t uiFPSDraw() {
char_t fpsText[32];
// Get now.
dusktimeepoch_t now = timeGetEpoch();
double_t delta = now.time - UIFPS.lastTick.time;
UIFPS.lastTick = now;
// Raw current FPS
float_t fps = delta > 0 ? 1.0 / delta : 0.0;
// Average FPS using exponential moving average
const float_t alpha = 0.1f; // Smoothing factor
if(UIFPS.fpsAverage == 0.0f) {
UIFPS.fpsAverage = fps; // Initialize average on first run
} else {
UIFPS.fpsAverage = alpha * fps + (1.0f - alpha) * UIFPS.fpsAverage;
}
snprintf(
fpsText,
sizeof(fpsText),
"%.1f/%.1fms",
UIFPS.fpsAverage,
delta * 1000.0f
);
color_t textColor;
if(fps >= 60.0f) {
textColor = COLOR_GREEN;
} else if(fps >= 45.0f) {
textColor = COLOR_YELLOW;
} else {
textColor = COLOR_RED;
}
errorChain(textDraw(
0, 0,
fpsText, textColor,
&FONT_TILESET_DEFAULT, &FONT_TEXTURE_DEFAULT
));
errorOk();
}
+24
View File
@@ -0,0 +1,24 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "error/error.h"
#include "time/timeepoch.h"
typedef struct {
dusktimeepoch_t lastTick;
float_t fpsAverage;
} uifps_t;
extern uifps_t UIFPS;
/**
* Draws the FPS counter on the screen, and also does the update (for now).
*
* @return Any error that occurs.
*/
errorret_t uiFPSDraw();