feat(平台): 添加Switch平台支持并实现资源管理器

- 新增Switch平台初始化与清理功能
- 实现Asset资源管理器类,提供文件读写、路径处理等功能
- 完善Window类的销毁逻辑,释放SDL资源
- 更新Switch平台编译配置,移除冗余标志
- 在主程序中集成资源管理器功能
This commit is contained in:
Lenheart 2026-02-21 03:34:48 +08:00
parent ce2cc4d210
commit 9f29192ae8
9 changed files with 905 additions and 8 deletions

View File

@ -79,7 +79,7 @@ struct WindowConfig {
class Window { class Window {
public: public:
Window() = default; Window() = default;
virtual ~Window() = default; ~Window() = default;
/** /**
* @brief * @brief

View File

@ -0,0 +1,18 @@
#pragma once
#ifdef __SWITCH__
#include <switch.h>
#include <sys/socket.h>
namespace frostbite2D {
/**
* @brief Switch平台相关函数
*/
void switchInit();
void switchShutdown();
} // namespace frostbite2D
#endif

View File

@ -0,0 +1,397 @@
#pragma once
#include <filesystem>
#include <fostbite2D/types/type_alias.h>
#include <optional>
#include <string>
#include <vector>
namespace frostbite2D {
namespace fs = std::filesystem;
/**
* @brief
*/
struct FileInfo {
std::string name; ///< 文件名(包含扩展名)
std::string extension; ///< 文件扩展名(包含点,如 ".txt"
std::string fullPath; ///< 完整路径
uint64 size = 0; ///< 文件大小(字节)
bool isDirectory = false; ///< 是否为目录
bool isRegularFile = false; ///< 是否为普通文件
bool exists = false; ///< 是否存在
};
/**
* @brief
*
*
*
* UTF-8
*
* @example
* auto& asset = Asset::get();
* asset.setWorkingDirectory("D:/游戏/资源");
* std::string content;
* asset.readTextFile("配置/设置.json", content);
*/
class Asset {
public:
/**
* @brief
* @return
*/
static Asset &get();
Asset(const Asset &) = delete;
Asset &operator=(const Asset &) = delete;
// ---------------------------------------------------------------------------
// 文件读写
// ---------------------------------------------------------------------------
/**
* @brief
* @param path
* @param outContent
* @return true
*/
bool readTextFile(const std::string &path, std::string &outContent);
/**
* @brief
* @param path
* @param content
* @param append
* @return true
*/
bool writeTextFile(const std::string &path, const std::string &content,
bool append = false);
/**
* @brief
* @param path
* @param outData
* @return true
*/
bool readBinaryFile(const std::string &path, std::vector<uint8> &outData);
/**
* @brief
* @param path
* @param data
* @param append
* @return true
*/
bool writeBinaryFile(const std::string &path, const std::vector<uint8> &data,
bool append = false);
/**
* @brief 便
* @param path
* @return std::nullopt
*/
std::optional<std::string> readFileToString(const std::string &path);
/**
* @brief 便
* @param path
* @return std::nullopt
*/
std::optional<std::vector<uint8>> readFileToBytes(const std::string &path);
// ---------------------------------------------------------------------------
// 文件/目录检查
// ---------------------------------------------------------------------------
/**
* @brief
* @param path
* @return true
*/
bool exists(const std::string &path) const;
/**
* @brief
* @param path
* @return true
*/
bool isDirectory(const std::string &path) const;
/**
* @brief
* @param path
* @return true
*/
bool isRegularFile(const std::string &path) const;
/**
* @brief
* @param path
* @return true
*/
bool isEmpty(const std::string &path) const;
// ---------------------------------------------------------------------------
// 目录操作
// ---------------------------------------------------------------------------
/**
* @brief
* @param path
* @return true
*/
bool createDirectory(const std::string &path);
/**
* @brief
* @param path
* @return true
*/
bool createDirectories(const std::string &path);
/**
* @brief
* @param path
* @return true
*/
bool removeFile(const std::string &path);
/**
* @brief
* @param path
* @return true
*/
bool removeDirectory(const std::string &path);
// ---------------------------------------------------------------------------
// 文件操作
// ---------------------------------------------------------------------------
/**
* @brief
* @param from
* @param to
* @param overwrite
* @return true
*/
bool copyFile(const std::string &from, const std::string &to,
bool overwrite = false);
/**
* @brief
* @param from
* @param to
* @return true
*/
bool moveFile(const std::string &from, const std::string &to);
/**
* @brief
* @param oldPath
* @param newPath
* @return true
*/
bool rename(const std::string &oldPath, const std::string &newPath);
// ---------------------------------------------------------------------------
// 目录遍历
// ---------------------------------------------------------------------------
/**
* @brief
* @param directoryPath
* @param recursive
* @return
*/
std::vector<std::string> listFiles(const std::string &directoryPath,
bool recursive = false);
/**
* @brief
* @param directoryPath
* @param recursive
* @return
*/
std::vector<std::string> listDirectories(const std::string &directoryPath,
bool recursive = false);
/**
* @brief
* @param directoryPath
* @param recursive
* @return
*/
std::vector<std::string> listAll(const std::string &directoryPath,
bool recursive = false);
/**
* @brief
* @param directoryPath
* @param extension ".png"
* @param recursive
* @return
*/
std::vector<std::string>
listFilesWithExtension(const std::string &directoryPath,
const std::string &extension, bool recursive = false);
// ---------------------------------------------------------------------------
// 文件信息
// ---------------------------------------------------------------------------
/**
* @brief
* @param path
* @return
*/
FileInfo getFileInfo(const std::string &path);
/**
* @brief
* @param path
* @return
*/
uint64 getFileSize(const std::string &path) const;
// ---------------------------------------------------------------------------
// 路径处理
// ---------------------------------------------------------------------------
/**
* @brief
* @param path
* @return
*/
std::string getFileName(const std::string &path) const;
/**
* @brief
* @param path
* @return
*/
std::string getFileNameWithoutExtension(const std::string &path) const;
/**
* @brief
* @param path
* @return ".txt"
*/
std::string getExtension(const std::string &path) const;
/**
* @brief
* @param path
* @return
*/
std::string getParentPath(const std::string &path) const;
/**
* @brief
* @param path
* @return
*/
std::string getAbsolutePath(const std::string &path) const;
/**
* @brief
* @param path
* @return
*/
std::string getCanonicalPath(const std::string &path) const;
/**
* @brief
* @param left
* @param right
* @return
*/
std::string combinePath(const std::string &left,
const std::string &right) const;
/**
* @brief
* @param path
* @return
*/
std::string normalizePath(const std::string &path) const;
// ---------------------------------------------------------------------------
// 工作目录
// ---------------------------------------------------------------------------
/**
* @brief
* @return
*/
std::string getCurrentPath() const;
/**
* @brief
* @param path
* @return true
*/
bool setCurrentPath(const std::string &path);
/**
* @brief
*
*
*
* @param path
*/
void setWorkingDirectory(const std::string &path);
/**
* @brief
* @return
*/
const std::string &getWorkingDirectory() const;
/**
* @brief
* @param relativePath
* @return
*/
std::string resolvePath(const std::string &relativePath) const;
// ---------------------------------------------------------------------------
// 资源根目录
// ---------------------------------------------------------------------------
/**
* @brief
* @param root
*/
void setAssetRoot(const std::string &root);
/**
* @brief
* @return
*/
const std::string &getAssetRoot() const;
/**
* @brief
*
* //
*
* @param relativePath
* @return
*/
std::string resolveAssetPath(const std::string &relativePath) const;
private:
Asset() = default;
~Asset() = default;
fs::path toPath(const std::string &path) const;
std::string fromPath(const fs::path &path) const;
std::string resolveFullPath(const std::string &path) const;
std::string workingDirectory_;
std::string assetRoot_;
};
} // namespace frostbite2D

View File

@ -1,5 +1,6 @@
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include <fostbite2D/core/application.h> #include <fostbite2D/core/application.h>
#include <fostbite2D/platform/switch.h>
namespace frostbite2D { namespace frostbite2D {
Application &Application::get() { Application &Application::get() {
@ -34,6 +35,11 @@ bool Application::init(const AppConfig &config) {
return true; return true;
} }
// 平台相关初始化
#ifdef __SWITCH__
switchInit();
#endif
this->window_ = new Window(); this->window_ = new Window();
if (!window_->create(config.windowConfig)) { if (!window_->create(config.windowConfig)) {
@ -47,12 +53,16 @@ bool Application::init(const AppConfig &config) {
void Application::shutdown() { void Application::shutdown() {
if (!initialized_) if (!initialized_)
return; return;
if (window_) {
window_->destroy();
window_ = nullptr;
}
// VRAMMgr::get().printStats(); // 平台相关清理
#ifdef __SWITCH__
// ServiceLocator::instance().clear(); switchShutdown();
#endif
// window_ = nullptr;
initialized_ = false; initialized_ = false;
running_ = false; running_ = false;

View File

@ -87,7 +87,15 @@ bool Window::create(const WindowConfig &cfg) {
return true; return true;
} }
void Window::destroy() {} void Window::destroy() {
if (sdlWindow_) {
SDL_GL_DeleteContext(glContext_);
glContext_ = nullptr;
SDL_DestroyWindow(sdlWindow_);
sdlWindow_ = nullptr;
}
SDL_Quit();
}
void Window::poll() {} void Window::poll() {}

View File

@ -0,0 +1,17 @@
#include <fostbite2D/platform/switch.h>
#ifdef __SWITCH__
namespace frostbite2D {
void switchInit() {
// 初始化Switch平台相关的资源
socketInitializeDefault();
nxlinkStdio();
}
void switchShutdown() {
// 清理Switch平台相关的资源
socketExit();
}
} // namespace frostbite2D
#endif

View File

@ -0,0 +1,435 @@
#include <fostbite2D/utils/asset.h>
#include <fstream>
#include <sstream>
#include <system_error>
namespace frostbite2D {
Asset &Asset::get() {
static Asset instance;
return instance;
}
fs::path Asset::toPath(const std::string &path) const {
#ifdef _WIN32
return fs::u8path(path);
#else
return fs::path(path);
#endif
}
std::string Asset::fromPath(const fs::path &path) const {
#ifdef _WIN32
return path.u8string();
#else
return path.string();
#endif
}
std::string Asset::resolveFullPath(const std::string &path) const {
if (path.empty()) {
return path;
}
fs::path p = toPath(path);
if (p.is_absolute()) {
return path;
}
if (!workingDirectory_.empty()) {
return fromPath(toPath(workingDirectory_) / p);
}
return path;
}
bool Asset::readTextFile(const std::string &path, std::string &outContent) {
std::string fullPath = resolveFullPath(path);
if (!exists(fullPath)) {
return false;
}
std::ifstream file(toPath(fullPath), std::ios::in | std::ios::binary);
if (!file.is_open()) {
return false;
}
std::ostringstream ss;
ss << file.rdbuf();
outContent = ss.str();
file.close();
return true;
}
bool Asset::writeTextFile(const std::string &path, const std::string &content,
bool append) {
std::string fullPath = resolveFullPath(path);
auto mode = std::ios::out;
if (append) {
mode |= std::ios::app;
}
std::ofstream file(toPath(fullPath), mode);
if (!file.is_open()) {
return false;
}
file << content;
file.close();
return true;
}
bool Asset::readBinaryFile(const std::string &path,
std::vector<uint8> &outData) {
std::string fullPath = resolveFullPath(path);
if (!exists(fullPath)) {
return false;
}
std::ifstream file(toPath(fullPath), std::ios::in | std::ios::binary);
if (!file.is_open()) {
return false;
}
file.seekg(0, std::ios::end);
auto size = file.tellg();
file.seekg(0, std::ios::beg);
outData.resize(static_cast<size_t>(size));
file.read(reinterpret_cast<char *>(outData.data()), size);
file.close();
return true;
}
bool Asset::writeBinaryFile(const std::string &path,
const std::vector<uint8> &data, bool append) {
std::string fullPath = resolveFullPath(path);
auto mode = std::ios::out | std::ios::binary;
if (append) {
mode |= std::ios::app;
}
std::ofstream file(toPath(fullPath), mode);
if (!file.is_open()) {
return false;
}
file.write(reinterpret_cast<const char *>(data.data()), data.size());
file.close();
return true;
}
bool Asset::exists(const std::string &path) const {
std::error_code ec;
return fs::exists(toPath(resolveFullPath(path)), ec);
}
bool Asset::isDirectory(const std::string &path) const {
std::error_code ec;
return fs::is_directory(toPath(resolveFullPath(path)), ec);
}
bool Asset::isRegularFile(const std::string &path) const {
std::error_code ec;
return fs::is_regular_file(toPath(resolveFullPath(path)), ec);
}
bool Asset::createDirectory(const std::string &path) {
std::error_code ec;
return fs::create_directory(toPath(resolveFullPath(path)), ec);
}
bool Asset::createDirectories(const std::string &path) {
std::error_code ec;
return fs::create_directories(toPath(resolveFullPath(path)), ec);
}
bool Asset::removeFile(const std::string &path) {
std::error_code ec;
return fs::remove(toPath(resolveFullPath(path)), ec);
}
bool Asset::removeDirectory(const std::string &path) {
std::error_code ec;
return fs::remove_all(toPath(resolveFullPath(path)), ec) > 0;
}
bool Asset::copyFile(const std::string &from, const std::string &to,
bool overwrite) {
std::error_code ec;
auto options =
overwrite ? fs::copy_options::overwrite_existing : fs::copy_options::none;
return fs::copy_file(toPath(resolveFullPath(from)),
toPath(resolveFullPath(to)), options, ec);
}
bool Asset::moveFile(const std::string &from, const std::string &to) {
std::error_code ec;
fs::rename(toPath(resolveFullPath(from)), toPath(resolveFullPath(to)), ec);
return !ec;
}
bool Asset::rename(const std::string &oldPath, const std::string &newPath) {
std::error_code ec;
fs::rename(toPath(resolveFullPath(oldPath)), toPath(resolveFullPath(newPath)),
ec);
return !ec;
}
std::vector<std::string> Asset::listFiles(const std::string &directoryPath,
bool recursive) {
std::vector<std::string> files;
fs::path fullPath = toPath(resolveFullPath(directoryPath));
std::error_code ec;
if (!fs::exists(fullPath, ec) || !fs::is_directory(fullPath, ec)) {
return files;
}
try {
if (recursive) {
for (const auto &entry : fs::recursive_directory_iterator(fullPath, ec)) {
if (entry.is_regular_file()) {
files.push_back(fromPath(entry.path()));
}
}
} else {
for (const auto &entry : fs::directory_iterator(fullPath, ec)) {
if (entry.is_regular_file()) {
files.push_back(fromPath(entry.path()));
}
}
}
} catch (...) {
}
return files;
}
std::vector<std::string>
Asset::listDirectories(const std::string &directoryPath, bool recursive) {
std::vector<std::string> directories;
fs::path fullPath = toPath(resolveFullPath(directoryPath));
std::error_code ec;
if (!fs::exists(fullPath, ec) || !fs::is_directory(fullPath, ec)) {
return directories;
}
try {
if (recursive) {
for (const auto &entry : fs::recursive_directory_iterator(fullPath, ec)) {
if (entry.is_directory()) {
directories.push_back(fromPath(entry.path()));
}
}
} else {
for (const auto &entry : fs::directory_iterator(fullPath, ec)) {
if (entry.is_directory()) {
directories.push_back(fromPath(entry.path()));
}
}
}
} catch (...) {
}
return directories;
}
std::vector<std::string> Asset::listAll(const std::string &directoryPath,
bool recursive) {
std::vector<std::string> items;
fs::path fullPath = toPath(resolveFullPath(directoryPath));
std::error_code ec;
if (!fs::exists(fullPath, ec) || !fs::is_directory(fullPath, ec)) {
return items;
}
try {
if (recursive) {
for (const auto &entry : fs::recursive_directory_iterator(fullPath, ec)) {
items.push_back(fromPath(entry.path()));
}
} else {
for (const auto &entry : fs::directory_iterator(fullPath, ec)) {
items.push_back(fromPath(entry.path()));
}
}
} catch (...) {
}
return items;
}
std::vector<std::string>
Asset::listFilesWithExtension(const std::string &directoryPath,
const std::string &extension, bool recursive) {
std::vector<std::string> files;
fs::path fullPath = toPath(resolveFullPath(directoryPath));
fs::path ext = toPath(extension);
std::error_code ec;
if (!fs::exists(fullPath, ec) || !fs::is_directory(fullPath, ec)) {
return files;
}
try {
if (recursive) {
for (const auto &entry : fs::recursive_directory_iterator(fullPath, ec)) {
if (entry.is_regular_file() && entry.path().extension() == ext) {
files.push_back(fromPath(entry.path()));
}
}
} else {
for (const auto &entry : fs::directory_iterator(fullPath, ec)) {
if (entry.is_regular_file() && entry.path().extension() == ext) {
files.push_back(fromPath(entry.path()));
}
}
}
} catch (...) {
}
return files;
}
FileInfo Asset::getFileInfo(const std::string &path) {
FileInfo info;
std::string fullPath = resolveFullPath(path);
info.fullPath = fullPath;
if (!exists(fullPath)) {
info.exists = false;
return info;
}
info.exists = true;
info.isDirectory = isDirectory(fullPath);
info.isRegularFile = isRegularFile(fullPath);
fs::path p = toPath(fullPath);
info.name = fromPath(p.filename());
info.extension = fromPath(p.extension());
if (info.isRegularFile) {
std::error_code ec;
info.size = fs::file_size(p, ec);
if (ec) {
info.size = 0;
}
}
return info;
}
std::string Asset::getFileName(const std::string &path) const {
return fromPath(toPath(path).filename());
}
std::string Asset::getFileNameWithoutExtension(const std::string &path) const {
return fromPath(toPath(path).stem());
}
std::string Asset::getExtension(const std::string &path) const {
return fromPath(toPath(path).extension());
}
std::string Asset::getParentPath(const std::string &path) const {
return fromPath(toPath(path).parent_path());
}
std::string Asset::getAbsolutePath(const std::string &path) const {
std::error_code ec;
return fromPath(fs::absolute(toPath(resolveFullPath(path)), ec));
}
std::string Asset::getCanonicalPath(const std::string &path) const {
std::error_code ec;
return fromPath(fs::canonical(toPath(resolveFullPath(path)), ec));
}
std::string Asset::getCurrentPath() const {
std::error_code ec;
return fromPath(fs::current_path(ec));
}
bool Asset::setCurrentPath(const std::string &path) {
std::error_code ec;
fs::current_path(toPath(path), ec);
return !ec;
}
std::string Asset::combinePath(const std::string &left,
const std::string &right) const {
return fromPath(toPath(left) / toPath(right));
}
std::string Asset::normalizePath(const std::string &path) const {
std::error_code ec;
auto p = fs::absolute(toPath(resolveFullPath(path)), ec);
if (ec) {
return path;
}
return fromPath(p.make_preferred());
}
uint64 Asset::getFileSize(const std::string &path) const {
std::error_code ec;
auto size = fs::file_size(toPath(resolveFullPath(path)), ec);
if (ec) {
return 0;
}
return size;
}
bool Asset::isEmpty(const std::string &path) const {
std::error_code ec;
return fs::is_empty(toPath(resolveFullPath(path)), ec);
}
std::optional<std::string> Asset::readFileToString(const std::string &path) {
std::string content;
if (readTextFile(path, content)) {
return content;
}
return std::nullopt;
}
std::optional<std::vector<uint8>>
Asset::readFileToBytes(const std::string &path) {
std::vector<uint8> data;
if (readBinaryFile(path, data)) {
return data;
}
return std::nullopt;
}
void Asset::setWorkingDirectory(const std::string &path) {
workingDirectory_ = path;
}
const std::string &Asset::getWorkingDirectory() const {
return workingDirectory_;
}
std::string Asset::resolvePath(const std::string &relativePath) const {
return resolveFullPath(relativePath);
}
void Asset::setAssetRoot(const std::string &root) { assetRoot_ = root; }
const std::string &Asset::getAssetRoot() const { return assetRoot_; }
std::string Asset::resolveAssetPath(const std::string &relativePath) const {
if (assetRoot_.empty()) {
return resolveFullPath(relativePath);
}
return resolveFullPath(combinePath(assetRoot_, relativePath));
}
} // namespace frostbite2D

View File

@ -2,6 +2,8 @@
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include <fostbite2D/core/application.h> #include <fostbite2D/core/application.h>
#include <fostbite2D/core/window.h> #include <fostbite2D/core/window.h>
#include <fostbite2D/utils/asset.h>
#include <glad/glad.h> #include <glad/glad.h>
using namespace frostbite2D; using namespace frostbite2D;
@ -22,6 +24,16 @@ int main(int argc, char **argv) {
return -1; return -1;
} }
Asset &asset = Asset::get();
// asset.setWorkingDirectory("I:/DOF/骑士团");
asset.setWorkingDirectory("/switch/testgame");
std::string content;
if (asset.readTextFile("test.txt", content)) {
SDL_Log("test.txt content: %s", content.c_str());
} else {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to read test.txt!");
}
app.shutdown(); app.shutdown();
SDL_Log("程序正常退出"); SDL_Log("程序正常退出");

View File

@ -17,7 +17,7 @@ target("Frostbite2D")
set_toolset("strip", path.join(devkitA64, "bin/aarch64-none-elf-strip.exe")) set_toolset("strip", path.join(devkitA64, "bin/aarch64-none-elf-strip.exe"))
-- 架构标志 -- 架构标志
local arch_flags = "-march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE" local arch_flags = "-march=armv8-a+crc+crypto -mtune=cortex-a57 -fPIE"
add_cxflags(arch_flags) add_cxflags(arch_flags)
-- 使用 devkitPro 提供的 switch.specs 文件 -- 使用 devkitPro 提供的 switch.specs 文件
add_ldflags("-specs=" .. path.join(devkitPro, "libnx/switch.specs"), "-g", arch_flags) add_ldflags("-specs=" .. path.join(devkitPro, "libnx/switch.specs"), "-g", arch_flags)