game_engine.c 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. #include "game_engine.h"
  2. #include <furi.h>
  3. #include <gui/gui.h>
  4. #include <input/input.h>
  5. #include <notification/notification_messages.h>
  6. #include "clock_timer.h"
  7. typedef _Atomic uint32_t AtomicUint32;
  8. GameEngineSettings game_engine_settings_init() {
  9. GameEngineSettings settings;
  10. settings.target_fps = 30.0f;
  11. settings.show_fps = false;
  12. settings.always_backlight = true;
  13. settings.start_callback = NULL;
  14. settings.frame_callback = NULL;
  15. settings.stop_callback = NULL;
  16. settings.context = NULL;
  17. return settings;
  18. }
  19. struct GameEngine {
  20. Gui* gui;
  21. NotificationApp* notifications;
  22. FuriPubSub* input_pubsub;
  23. FuriThreadId thread_id;
  24. GameEngineSettings settings;
  25. float fps;
  26. };
  27. typedef enum {
  28. GameThreadFlagUpdate = 1 << 0,
  29. GameThreadFlagStop = 1 << 1,
  30. } GameThreadFlag;
  31. #define GameThreadFlagMask (GameThreadFlagUpdate | GameThreadFlagStop)
  32. GameEngine* game_engine_alloc(GameEngineSettings settings) {
  33. furi_check(settings.frame_callback != NULL);
  34. GameEngine* engine = malloc(sizeof(GameEngine));
  35. engine->gui = furi_record_open(RECORD_GUI);
  36. engine->notifications = furi_record_open(RECORD_NOTIFICATION);
  37. engine->input_pubsub = furi_record_open(RECORD_INPUT_EVENTS);
  38. engine->thread_id = furi_thread_get_current_id();
  39. engine->settings = settings;
  40. engine->fps = 1.0f;
  41. return engine;
  42. }
  43. void game_engine_free(GameEngine* engine) {
  44. furi_record_close(RECORD_GUI);
  45. furi_record_close(RECORD_NOTIFICATION);
  46. furi_record_close(RECORD_INPUT_EVENTS);
  47. free(engine);
  48. }
  49. static void canvas_printf(Canvas* canvas, uint8_t x, uint8_t y, const char* format, ...) {
  50. FuriString* string = furi_string_alloc();
  51. va_list args;
  52. va_start(args, format);
  53. furi_string_vprintf(string, format, args);
  54. va_end(args);
  55. canvas_draw_str(canvas, x, y, furi_string_get_cstr(string));
  56. furi_string_free(string);
  57. }
  58. static void clock_timer_callback(void* context) {
  59. GameEngine* engine = context;
  60. furi_thread_flags_set(engine->thread_id, GameThreadFlagUpdate);
  61. }
  62. static const GameKey keys[] = {
  63. [InputKeyUp] = GameKeyUp,
  64. [InputKeyDown] = GameKeyDown,
  65. [InputKeyRight] = GameKeyRight,
  66. [InputKeyLeft] = GameKeyLeft,
  67. [InputKeyOk] = GameKeyOk,
  68. [InputKeyBack] = GameKeyBack,
  69. };
  70. static const size_t keys_count = sizeof(keys) / sizeof(keys[0]);
  71. static void input_events_callback(const void* value, void* context) {
  72. AtomicUint32* input_state = context;
  73. const InputEvent* event = value;
  74. if(event->key < keys_count) {
  75. switch(event->type) {
  76. case InputTypePress:
  77. *input_state |= (keys[event->key]);
  78. break;
  79. case InputTypeRelease:
  80. *input_state &= ~(keys[event->key]);
  81. break;
  82. default:
  83. break;
  84. }
  85. }
  86. }
  87. void game_engine_run(GameEngine* engine) {
  88. // input state
  89. AtomicUint32 input_state = 0;
  90. uint32_t input_prev_state = 0;
  91. // set backlight if needed
  92. if(engine->settings.always_backlight) {
  93. notification_message(engine->notifications, &sequence_display_backlight_enforce_on);
  94. }
  95. // acquire gui canvas
  96. Canvas* canvas = gui_direct_draw_acquire(engine->gui);
  97. // subscribe to input events
  98. FuriPubSubSubscription* input_subscription =
  99. furi_pubsub_subscribe(engine->input_pubsub, input_events_callback, &input_state);
  100. // call start callback, if any
  101. if(engine->settings.start_callback) {
  102. engine->settings.start_callback(engine, engine->settings.context);
  103. }
  104. // start "game update" timer
  105. clock_timer_start(clock_timer_callback, engine, engine->settings.target_fps);
  106. // init fps counter
  107. uint32_t time_start = DWT->CYCCNT;
  108. while(true) {
  109. uint32_t flags =
  110. furi_thread_flags_wait(GameThreadFlagMask, FuriFlagWaitAny, FuriWaitForever);
  111. furi_check((flags & FuriFlagError) == 0);
  112. if(flags & GameThreadFlagUpdate) {
  113. // update fps counter
  114. uint32_t time_end = DWT->CYCCNT;
  115. uint32_t time_delta = time_end - time_start;
  116. time_start = time_end;
  117. // update input state
  118. uint32_t input_current_state = input_state;
  119. InputState input = {
  120. .held = input_current_state,
  121. .pressed = input_current_state & ~input_prev_state,
  122. .released = ~input_current_state & input_prev_state,
  123. };
  124. input_prev_state = input_current_state;
  125. // clear screen
  126. canvas_reset(canvas);
  127. // calculate actual fps
  128. engine->fps = (float)SystemCoreClock / time_delta;
  129. // do the work
  130. engine->settings.frame_callback(engine, canvas, input, engine->settings.context);
  131. // show fps if needed
  132. if(engine->settings.show_fps) {
  133. canvas_set_color(canvas, ColorXOR);
  134. canvas_printf(canvas, 0, 7, "%u", (uint32_t)roundf(engine->fps));
  135. }
  136. // and output screen buffer
  137. canvas_commit(canvas);
  138. // throttle a bit
  139. furi_delay_tick(2);
  140. }
  141. if(flags & GameThreadFlagStop) {
  142. break;
  143. }
  144. }
  145. // stop timer
  146. clock_timer_stop();
  147. // call stop callback, if any
  148. if(engine->settings.stop_callback) {
  149. engine->settings.stop_callback(engine, engine->settings.context);
  150. }
  151. // release gui canvas and unsubscribe from input events
  152. gui_direct_draw_release(engine->gui);
  153. furi_pubsub_unsubscribe(engine->input_pubsub, input_subscription);
  154. if(engine->settings.always_backlight) {
  155. notification_message(engine->notifications, &sequence_display_backlight_enforce_auto);
  156. }
  157. }
  158. void game_engine_stop(GameEngine* engine) {
  159. furi_thread_flags_set(engine->thread_id, GameThreadFlagStop);
  160. }
  161. float game_engine_get_delta_time(GameEngine* engine) {
  162. return 1.0f / engine->fps;
  163. }
  164. float game_engine_get_delta_frames(GameEngine* engine) {
  165. return engine->fps / engine->settings.target_fps;
  166. }
  167. void game_engine_show_fps_set(GameEngine* engine, bool show_fps) {
  168. engine->settings.show_fps = show_fps;
  169. }