game_engine.c 5.1 KB

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