| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199 |
- #include "game_engine.h"
- #include <furi.h>
- #include <gui/gui.h>
- #include <input/input.h>
- #include <notification/notification_messages.h>
- #include "clock_timer.h"
- typedef _Atomic uint32_t AtomicUint32;
- GameEngineSettings game_engine_settings_init() {
- GameEngineSettings settings;
- settings.target_fps = 30.0f;
- settings.show_fps = false;
- settings.always_backlight = true;
- settings.start_callback = NULL;
- settings.frame_callback = NULL;
- settings.stop_callback = NULL;
- settings.context = NULL;
- return settings;
- }
- struct GameEngine {
- Gui* gui;
- NotificationApp* notifications;
- FuriPubSub* input_pubsub;
- FuriThreadId thread_id;
- GameEngineSettings settings;
- float fps;
- };
- typedef enum {
- GameThreadFlagUpdate = 1 << 0,
- GameThreadFlagStop = 1 << 1,
- } GameThreadFlag;
- #define GameThreadFlagMask (GameThreadFlagUpdate | GameThreadFlagStop)
- GameEngine* game_engine_alloc(GameEngineSettings settings) {
- furi_check(settings.frame_callback != NULL);
- GameEngine* engine = malloc(sizeof(GameEngine));
- engine->gui = furi_record_open(RECORD_GUI);
- engine->notifications = furi_record_open(RECORD_NOTIFICATION);
- engine->input_pubsub = furi_record_open(RECORD_INPUT_EVENTS);
- engine->thread_id = furi_thread_get_current_id();
- engine->settings = settings;
- engine->fps = 1.0f;
- return engine;
- }
- void game_engine_free(GameEngine* engine) {
- furi_record_close(RECORD_GUI);
- furi_record_close(RECORD_NOTIFICATION);
- furi_record_close(RECORD_INPUT_EVENTS);
- free(engine);
- }
- static void clock_timer_callback(void* context) {
- GameEngine* engine = context;
- furi_thread_flags_set(engine->thread_id, GameThreadFlagUpdate);
- }
- static const GameKey keys[] = {
- [InputKeyUp] = GameKeyUp,
- [InputKeyDown] = GameKeyDown,
- [InputKeyRight] = GameKeyRight,
- [InputKeyLeft] = GameKeyLeft,
- [InputKeyOk] = GameKeyOk,
- [InputKeyBack] = GameKeyBack,
- };
- static const size_t keys_count = sizeof(keys) / sizeof(keys[0]);
- static void input_events_callback(const void* value, void* context) {
- AtomicUint32* input_state = context;
- const InputEvent* event = value;
- if(event->key < keys_count) {
- switch(event->type) {
- case InputTypePress:
- *input_state |= (keys[event->key]);
- break;
- case InputTypeRelease:
- *input_state &= ~(keys[event->key]);
- break;
- default:
- break;
- }
- }
- }
- void game_engine_run(GameEngine* engine) {
- // input state
- AtomicUint32 input_state = 0;
- uint32_t input_prev_state = 0;
- // set backlight if needed
- if(engine->settings.always_backlight) {
- notification_message(engine->notifications, &sequence_display_backlight_enforce_on);
- }
- // acquire gui canvas
- Canvas* canvas = gui_direct_draw_acquire(engine->gui);
- // subscribe to input events
- FuriPubSubSubscription* input_subscription =
- furi_pubsub_subscribe(engine->input_pubsub, input_events_callback, &input_state);
- // call start callback, if any
- if(engine->settings.start_callback) {
- engine->settings.start_callback(engine, engine->settings.context);
- }
- // start "game update" timer
- clock_timer_start(clock_timer_callback, engine, engine->settings.target_fps);
- // init fps counter
- uint32_t time_start = DWT->CYCCNT;
- while(true) {
- uint32_t flags =
- furi_thread_flags_wait(GameThreadFlagMask, FuriFlagWaitAny, FuriWaitForever);
- furi_check((flags & FuriFlagError) == 0);
- if(flags & GameThreadFlagUpdate) {
- // update fps counter
- uint32_t time_end = DWT->CYCCNT;
- uint32_t time_delta = time_end - time_start;
- time_start = time_end;
- // update input state
- uint32_t input_current_state = input_state;
- InputState input = {
- .held = input_current_state,
- .pressed = input_current_state & ~input_prev_state,
- .released = ~input_current_state & input_prev_state,
- };
- input_prev_state = input_current_state;
- // clear screen
- canvas_reset(canvas);
- // calculate actual fps
- engine->fps = (float)SystemCoreClock / time_delta;
- // do the work
- engine->settings.frame_callback(engine, canvas, input, engine->settings.context);
- // show fps if needed
- if(engine->settings.show_fps) {
- canvas_set_color(canvas, ColorXOR);
- canvas_printf(canvas, 0, 7, "%u", (uint32_t)roundf(engine->fps));
- }
- // and output screen buffer
- canvas_commit(canvas);
- // throttle a bit
- furi_delay_tick(2);
- }
- if(flags & GameThreadFlagStop) {
- break;
- }
- }
- // stop timer
- clock_timer_stop();
- // call stop callback, if any
- if(engine->settings.stop_callback) {
- engine->settings.stop_callback(engine, engine->settings.context);
- }
- // release gui canvas and unsubscribe from input events
- gui_direct_draw_release(engine->gui);
- furi_pubsub_unsubscribe(engine->input_pubsub, input_subscription);
- if(engine->settings.always_backlight) {
- notification_message(engine->notifications, &sequence_display_backlight_enforce_auto);
- }
- }
- void game_engine_stop(GameEngine* engine) {
- furi_thread_flags_set(engine->thread_id, GameThreadFlagStop);
- }
- float game_engine_get_delta_time(GameEngine* engine) {
- return 1.0f / engine->fps;
- }
- float game_engine_get_delta_frames(GameEngine* engine) {
- return engine->fps / engine->settings.target_fps;
- }
- void game_engine_show_fps_set(GameEngine* engine, bool show_fps) {
- engine->settings.show_fps = show_fps;
- }
|