#include #include #include #include #include #include #include #include "DefaultHardware.h" #include "Display.h" #include "DrawContext.h" #include "TimeSource.h" #include "Timer.h" #include "app/TomGameApp.h" #include "recognition/KeywordRecognizer.h" #ifdef _WIN32 #include "recognition/PythonKeywordRecognizer.h" #else #include "recognition/TinyKwsRecognizer.h" #endif #ifdef USE_FRAMEBUFFER #include "FBDisplay.h" #else #include "SDLDisplay.h" #endif namespace { const int32_t ScreenWidth = 1024; const int32_t ScreenHeight = 600; struct ProgramOptions { uint32_t target_fps; std::string kws_model_path; float kws_threshold; bool show_help; ProgramOptions() : target_fps(Core::Timer::DefaultFps), kws_model_path("src/Apps/Game/model/mlcommons-tiny-kws/models/kws_ref_model_float32.tflite"), kws_threshold(0.75f), show_help(false) { } }; static Platform::IDisplay* CreateDisplay() { #ifdef USE_FRAMEBUFFER return new Platform::FBDisplay(); #else return new Platform::SDLDisplay(); #endif } static void PrintUsage(const char* program_name) { std::cout << "Usage: " << program_name << " [--fps 30|45|60] [--kws-model path] [--kws-threshold 0.75]\n" << " " << program_name << " [--fps=30|45|60] [--kws-model=path] [--kws-threshold=0.75]\n"; } static ProgramOptions ParseProgramOptions(int argc, char* argv[]) { ProgramOptions options; for (int i = 1; i < argc; ++i) { const char* arg = argv[i]; const char* fps_value = nullptr; const char* kws_model_value = nullptr; const char* kws_threshold_value = nullptr; if (std::strcmp(arg, "--help") == 0 || std::strcmp(arg, "-h") == 0) { options.show_help = true; return options; } else if (std::strcmp(arg, "--fps") == 0) { if (i + 1 < argc) { fps_value = argv[++i]; } else { std::cerr << "Missing value for --fps, falling back to 30. Supported: 30, 45, 60.\n"; } } else if (std::strncmp(arg, "--fps=", 6) == 0) { fps_value = arg + 6; } else if (std::strcmp(arg, "--kws-model") == 0) { if (i + 1 < argc) { kws_model_value = argv[++i]; } else { std::cerr << "Missing value for --kws-model, using default model path.\n"; } } else if (std::strncmp(arg, "--kws-model=", 12) == 0) { kws_model_value = arg + 12; } else if (std::strcmp(arg, "--kws-threshold") == 0) { if (i + 1 < argc) { kws_threshold_value = argv[++i]; } else { std::cerr << "Missing value for --kws-threshold, using default 0.75.\n"; } } else if (std::strncmp(arg, "--kws-threshold=", 16) == 0) { kws_threshold_value = arg + 16; } if (fps_value != nullptr) { const uint32_t parsed_fps = static_cast(std::strtoul(fps_value, nullptr, 10)); if (Core::Timer::is_supported_fps(parsed_fps)) { options.target_fps = parsed_fps; } else { std::cerr << "Unsupported FPS '" << fps_value << "', falling back to 30. Supported: 30, 45, 60.\n"; options.target_fps = Core::Timer::DefaultFps; } } if (kws_model_value != nullptr && kws_model_value[0] != '\0') { options.kws_model_path = kws_model_value; } if (kws_threshold_value != nullptr) { char* threshold_end = nullptr; const float parsed_threshold = std::strtof(kws_threshold_value, &threshold_end); if (threshold_end != kws_threshold_value && *threshold_end == '\0' && parsed_threshold >= 0.0f) { options.kws_threshold = parsed_threshold; } else { std::cerr << "Unsupported KWS threshold '" << kws_threshold_value << "', using default 0.75.\n"; options.kws_threshold = 0.75f; } } } return options; } static void SleepRemainingFrameTime(const Core::Timer& timer, const Platform::ITimeSource& time_source) { const uint32_t sleep_ms = timer.remaining_frame_ms(time_source.get_time_ms()); if (sleep_ms > 0u) { std::this_thread::sleep_for(std::chrono::milliseconds(sleep_ms)); } } } int main(int argc, char* argv[]) { const ProgramOptions options = ParseProgramOptions(argc, argv); if (options.show_help) { PrintUsage(argv[0]); return 0; } Platform::IDisplay* display = CreateDisplay(); if (!display->init(ScreenWidth, ScreenHeight)) { delete display; return -1; } Platform::DefaultAudioInput audioInput; Platform::DefaultAudioOutput audioOutput; Platform::DefaultButtonInput buttonInput; Platform::DefaultPointerInput pointerInput; #ifdef _WIN32 Game::PythonKeywordRecognizer keywordRecognizer( "python", "src/Apps/Game/tools/kws_python_test.py", options.kws_model_path, options.kws_threshold); #else Game::TinyKwsRecognizer keywordRecognizer(options.kws_model_path); keywordRecognizer.set_confidence_threshold(options.kws_threshold); #endif if (!buttonInput.init()) { std::cerr << "[WARN] Button input init failed; physical key trigger is disabled." << std::endl; } if (!pointerInput.init("", ScreenWidth, ScreenHeight)) { std::cerr << "[WARN] Pointer input init failed; on-screen button trigger is disabled." << std::endl; } Core::DrawContext ctx(ScreenWidth, ScreenHeight); Core::Timer timer(options.target_fps); Platform::SteadyTimeSource time_source; Game::TomGameApp app( ScreenWidth, ScreenHeight, &audioInput, &audioOutput, &buttonInput, &pointerInput, &keywordRecognizer); std::cout << "[INFO] Tom game started. Target FPS: " << timer.target_fps() << std::endl; bool is_running = true; while (is_running) { timer.begin_frame(time_source.get_time_ms()); bool should_quit = false; display->poll_events(should_quit); if (should_quit) { is_running = false; } app.update(timer.fixed_delta_ms()); app.draw(ctx); ctx.present(display); SleepRemainingFrameTime(timer, time_source); } pointerInput.shutdown(); buttonInput.shutdown(); audioInput.shutdown(); audioOutput.shutdown(); display->shutdown(); delete display; return 0; }