2026-02-25 06:23:53 +08:00
|
|
|
|
#include <app/application.h>
|
2026-02-27 20:46:16 +08:00
|
|
|
|
#include <core/director.h>
|
2026-02-27 22:59:17 +08:00
|
|
|
|
#include <core/service.h>
|
|
|
|
|
|
#include <core/event/events.h>
|
|
|
|
|
|
#include <platform/sdl2.h>
|
|
|
|
|
|
#include <platform/window.h>
|
|
|
|
|
|
#include <platform/input.h>
|
|
|
|
|
|
#include <platform/file.h>
|
2026-02-25 06:23:53 +08:00
|
|
|
|
#include <utils/logger.h>
|
2026-02-11 19:40:26 +08:00
|
|
|
|
|
|
|
|
|
|
#include <chrono>
|
|
|
|
|
|
#include <thread>
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef __SWITCH__
|
|
|
|
|
|
#include <switch.h>
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
namespace extra2d {
|
|
|
|
|
|
|
2026-02-27 22:59:17 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 获取当前时间(秒)
|
|
|
|
|
|
*/
|
2026-02-11 19:40:26 +08:00
|
|
|
|
static double getTimeSeconds() {
|
|
|
|
|
|
#ifdef __SWITCH__
|
2026-02-27 20:46:16 +08:00
|
|
|
|
struct timespec ts;
|
|
|
|
|
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
|
|
|
|
|
return static_cast<double>(ts.tv_sec) + static_cast<double>(ts.tv_nsec) / 1000000000.0;
|
2026-02-11 19:40:26 +08:00
|
|
|
|
#else
|
2026-02-27 20:46:16 +08:00
|
|
|
|
using namespace std::chrono;
|
|
|
|
|
|
auto now = steady_clock::now();
|
|
|
|
|
|
auto duration = now.time_since_epoch();
|
|
|
|
|
|
return duration_cast<std::chrono::duration<double>>(duration).count();
|
2026-02-11 19:40:26 +08:00
|
|
|
|
#endif
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-27 20:46:16 +08:00
|
|
|
|
Application& Application::instance() {
|
|
|
|
|
|
static Application instance;
|
|
|
|
|
|
return instance;
|
2026-02-11 19:40:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-27 20:46:16 +08:00
|
|
|
|
Application::~Application() {
|
|
|
|
|
|
shutdown();
|
|
|
|
|
|
}
|
2026-02-11 19:40:26 +08:00
|
|
|
|
|
2026-02-27 20:46:16 +08:00
|
|
|
|
bool Application::init(const AppConfig& config) {
|
|
|
|
|
|
if (initialized_) {
|
|
|
|
|
|
E2D_LOG_WARN("Application already initialized");
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
2026-02-11 19:40:26 +08:00
|
|
|
|
|
2026-02-27 20:46:16 +08:00
|
|
|
|
config_ = config;
|
2026-02-11 19:40:26 +08:00
|
|
|
|
|
2026-02-27 20:46:16 +08:00
|
|
|
|
PlatformType platform = config_.platform;
|
|
|
|
|
|
if (platform == PlatformType::Auto) {
|
2026-02-11 19:40:26 +08:00
|
|
|
|
#ifdef __SWITCH__
|
2026-02-27 20:46:16 +08:00
|
|
|
|
platform = PlatformType::Switch;
|
2026-02-11 19:40:26 +08:00
|
|
|
|
#else
|
2026-02-27 20:46:16 +08:00
|
|
|
|
platform = PlatformType::PC;
|
2026-02-11 19:40:26 +08:00
|
|
|
|
#endif
|
2026-02-27 20:46:16 +08:00
|
|
|
|
}
|
2026-02-11 19:40:26 +08:00
|
|
|
|
|
2026-02-27 20:46:16 +08:00
|
|
|
|
if (platform == PlatformType::Switch) {
|
2026-02-11 19:40:26 +08:00
|
|
|
|
#ifdef __SWITCH__
|
2026-02-27 20:46:16 +08:00
|
|
|
|
Result rc;
|
|
|
|
|
|
rc = romfsInit();
|
|
|
|
|
|
if (R_SUCCEEDED(rc)) {
|
|
|
|
|
|
E2D_LOG_INFO("RomFS initialized successfully");
|
|
|
|
|
|
} else {
|
|
|
|
|
|
E2D_LOG_WARN("romfsInit failed: {:#08X}", rc);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
rc = socketInitializeDefault();
|
|
|
|
|
|
if (R_FAILED(rc)) {
|
|
|
|
|
|
E2D_LOG_WARN("socketInitializeDefault failed");
|
|
|
|
|
|
}
|
|
|
|
|
|
#endif
|
2026-02-11 19:40:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-27 22:59:17 +08:00
|
|
|
|
if (!Sdl2::initAll()) {
|
|
|
|
|
|
E2D_LOG_ERROR("Failed to initialize SDL2");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
SVC_MGR.reg(&WINDOW);
|
|
|
|
|
|
SVC_MGR.reg(&INPUT_SVC);
|
|
|
|
|
|
SVC_MGR.reg(&FILE_SVC);
|
|
|
|
|
|
|
|
|
|
|
|
if (!SVC_MGR.initAll()) {
|
|
|
|
|
|
E2D_LOG_ERROR("Failed to initialize services");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
WindowCfg winCfg;
|
|
|
|
|
|
winCfg.title = config_.title;
|
|
|
|
|
|
winCfg.width = config_.width;
|
|
|
|
|
|
winCfg.height = config_.height;
|
|
|
|
|
|
winCfg.fullscreen = config_.fullscreen;
|
|
|
|
|
|
winCfg.resizable = config_.resizable;
|
|
|
|
|
|
winCfg.vsync = config_.vsync;
|
|
|
|
|
|
winCfg.glMajor = config_.glMajor;
|
|
|
|
|
|
winCfg.glMinor = config_.glMinor;
|
|
|
|
|
|
|
|
|
|
|
|
if (!WINDOW.create(winCfg)) {
|
|
|
|
|
|
E2D_LOG_ERROR("Failed to create window");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-27 20:46:16 +08:00
|
|
|
|
if (!DIRECTOR.init()) {
|
|
|
|
|
|
E2D_LOG_ERROR("Failed to initialize Director");
|
|
|
|
|
|
return false;
|
2026-02-11 19:40:26 +08:00
|
|
|
|
}
|
2026-02-27 20:46:16 +08:00
|
|
|
|
|
2026-02-27 22:59:17 +08:00
|
|
|
|
WINDOW.setOnClose([this]() {
|
|
|
|
|
|
quit();
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-02-27 20:46:16 +08:00
|
|
|
|
initialized_ = true;
|
|
|
|
|
|
running_ = true;
|
|
|
|
|
|
|
2026-02-27 22:59:17 +08:00
|
|
|
|
events::OnInit::emit();
|
|
|
|
|
|
|
2026-02-27 20:46:16 +08:00
|
|
|
|
E2D_LOG_INFO("Application initialized successfully");
|
2026-02-27 22:59:17 +08:00
|
|
|
|
E2D_LOG_INFO("Window: {}x{}, Fullscreen: {}, VSync: {}",
|
|
|
|
|
|
config_.width, config_.height, config_.fullscreen, config_.vsync);
|
2026-02-27 20:46:16 +08:00
|
|
|
|
return true;
|
2026-02-11 19:40:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Application::shutdown() {
|
2026-02-27 20:46:16 +08:00
|
|
|
|
if (!initialized_) return;
|
2026-02-11 19:40:26 +08:00
|
|
|
|
|
2026-02-27 22:59:17 +08:00
|
|
|
|
events::OnShutdown::emit();
|
|
|
|
|
|
|
2026-02-27 20:46:16 +08:00
|
|
|
|
E2D_LOG_INFO("Shutting down application...");
|
2026-02-11 19:40:26 +08:00
|
|
|
|
|
2026-02-27 20:46:16 +08:00
|
|
|
|
DIRECTOR.shutdown();
|
2026-02-27 22:59:17 +08:00
|
|
|
|
SVC_MGR.shutdownAll();
|
|
|
|
|
|
Sdl2::shutdown();
|
2026-02-11 19:40:26 +08:00
|
|
|
|
|
2026-02-27 20:46:16 +08:00
|
|
|
|
PlatformType platform = config_.platform;
|
|
|
|
|
|
if (platform == PlatformType::Auto) {
|
2026-02-11 19:40:26 +08:00
|
|
|
|
#ifdef __SWITCH__
|
2026-02-27 20:46:16 +08:00
|
|
|
|
platform = PlatformType::Switch;
|
2026-02-11 19:40:26 +08:00
|
|
|
|
#else
|
2026-02-27 20:46:16 +08:00
|
|
|
|
platform = PlatformType::PC;
|
2026-02-11 19:40:26 +08:00
|
|
|
|
#endif
|
2026-02-27 20:46:16 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (platform == PlatformType::Switch) {
|
2026-02-11 19:40:26 +08:00
|
|
|
|
#ifdef __SWITCH__
|
2026-02-27 20:46:16 +08:00
|
|
|
|
romfsExit();
|
|
|
|
|
|
socketExit();
|
2026-02-11 19:40:26 +08:00
|
|
|
|
#endif
|
2026-02-27 20:46:16 +08:00
|
|
|
|
}
|
2026-02-11 19:40:26 +08:00
|
|
|
|
|
2026-02-27 20:46:16 +08:00
|
|
|
|
initialized_ = false;
|
|
|
|
|
|
running_ = false;
|
2026-02-11 19:40:26 +08:00
|
|
|
|
|
2026-02-27 20:46:16 +08:00
|
|
|
|
E2D_LOG_INFO("Application shutdown complete");
|
2026-02-11 19:40:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Application::run() {
|
2026-02-27 20:46:16 +08:00
|
|
|
|
if (!initialized_) {
|
|
|
|
|
|
E2D_LOG_ERROR("Application not initialized");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-02-11 19:40:26 +08:00
|
|
|
|
|
2026-02-27 20:46:16 +08:00
|
|
|
|
lastFrameTime_ = getTimeSeconds();
|
2026-02-11 19:40:26 +08:00
|
|
|
|
|
2026-02-27 22:59:17 +08:00
|
|
|
|
while (running_ && !WINDOW.shouldClose()) {
|
2026-02-27 20:46:16 +08:00
|
|
|
|
mainLoop();
|
|
|
|
|
|
}
|
2026-02-11 19:40:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Application::quit() {
|
2026-02-27 20:46:16 +08:00
|
|
|
|
shouldQuit_ = true;
|
|
|
|
|
|
running_ = false;
|
2026-02-27 22:59:17 +08:00
|
|
|
|
WINDOW.requestClose();
|
2026-02-11 19:40:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Application::pause() {
|
2026-02-27 20:46:16 +08:00
|
|
|
|
if (!paused_) {
|
|
|
|
|
|
paused_ = true;
|
2026-02-27 22:59:17 +08:00
|
|
|
|
SVC_MGR.pauseAll();
|
2026-02-27 20:46:16 +08:00
|
|
|
|
DIRECTOR.pause();
|
2026-02-27 22:59:17 +08:00
|
|
|
|
events::OnPause::emit();
|
2026-02-27 20:46:16 +08:00
|
|
|
|
E2D_LOG_INFO("Application paused");
|
|
|
|
|
|
}
|
2026-02-11 19:40:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Application::resume() {
|
2026-02-27 20:46:16 +08:00
|
|
|
|
if (paused_) {
|
|
|
|
|
|
paused_ = false;
|
2026-02-27 22:59:17 +08:00
|
|
|
|
SVC_MGR.resumeAll();
|
2026-02-27 20:46:16 +08:00
|
|
|
|
DIRECTOR.resume();
|
|
|
|
|
|
lastFrameTime_ = getTimeSeconds();
|
2026-02-27 22:59:17 +08:00
|
|
|
|
events::OnResume::emit();
|
2026-02-27 20:46:16 +08:00
|
|
|
|
E2D_LOG_INFO("Application resumed");
|
|
|
|
|
|
}
|
2026-02-11 19:40:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Application::mainLoop() {
|
2026-02-27 22:59:17 +08:00
|
|
|
|
if (!WINDOW.pollEvents()) {
|
|
|
|
|
|
running_ = false;
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-27 20:46:16 +08:00
|
|
|
|
double currentTime = getTimeSeconds();
|
|
|
|
|
|
deltaTime_ = static_cast<float>(currentTime - lastFrameTime_);
|
|
|
|
|
|
lastFrameTime_ = currentTime;
|
|
|
|
|
|
|
|
|
|
|
|
totalTime_ += deltaTime_;
|
|
|
|
|
|
|
|
|
|
|
|
frameCount_++;
|
|
|
|
|
|
fpsTimer_ += deltaTime_;
|
|
|
|
|
|
if (fpsTimer_ >= 1.0f) {
|
|
|
|
|
|
currentFps_ = frameCount_;
|
|
|
|
|
|
frameCount_ = 0;
|
|
|
|
|
|
fpsTimer_ -= 1.0f;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!paused_) {
|
|
|
|
|
|
update();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-27 22:59:17 +08:00
|
|
|
|
WINDOW.swapBuffers();
|
|
|
|
|
|
|
2026-02-27 20:46:16 +08:00
|
|
|
|
if (!config_.vsync && config_.fpsLimit > 0) {
|
|
|
|
|
|
double frameEndTime = getTimeSeconds();
|
|
|
|
|
|
double frameTime = frameEndTime - currentTime;
|
|
|
|
|
|
double target = 1.0 / static_cast<double>(config_.fpsLimit);
|
|
|
|
|
|
if (frameTime < target) {
|
|
|
|
|
|
std::this_thread::sleep_for(std::chrono::duration<double>(target - frameTime));
|
|
|
|
|
|
}
|
2026-02-11 19:40:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Application::update() {
|
2026-02-27 22:59:17 +08:00
|
|
|
|
SVC_MGR.updateAll(deltaTime_);
|
|
|
|
|
|
events::OnUpdate::emit(deltaTime_);
|
2026-02-27 20:46:16 +08:00
|
|
|
|
DIRECTOR.mainLoop(deltaTime_);
|
2026-02-11 19:40:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} // namespace extra2d
|