game_engine.c 4.8 KB

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