add args for entry Application; using efsw to replace custom filewatcher; remove some useless code; move FileOpenSelector from Application to FileSystem;some tweaks

This commit is contained in:
2026-03-15 00:46:47 +08:00
parent 5cb9b04ab0
commit a6fb4bdcea
24 changed files with 396 additions and 353 deletions

3
.gitmodules vendored
View File

@ -32,3 +32,6 @@
[submodule "Prism/vendor/ImViewGuizmo"]
path = Prism/vendor/ImViewGuizmo
url = https://github.com/Ka1serM/ImViewGuizmo
[submodule "Prism/vendor/efsw"]
path = Prism/vendor/efsw
url = https://github.com/SpartanJ/efsw

View File

@ -2,7 +2,7 @@ project(PrismEditor)
set(CMAKE_BINARY_DIR ${CMAKE_BINARY_DIR}/bin)
file(GLOB ASSETS assets)
file(GLOB ASSETS "assets")
file(COPY ${ASSETS} DESTINATION ${CMAKE_BINARY_DIR})
# imgui.ini file

View File

@ -23,7 +23,7 @@ public:
};
Prism::Application* Prism::CreateApplication()
Prism::Application* Prism::CreateApplication(const CommandArgs args)
{
return new Editor({"hello wrold", 1920, 1080});
return new Editor({"hello world", 1920, 1080, args});
}

View File

@ -280,6 +280,31 @@ namespace Prism
{
auto& materials = mesh->GetMaterials();
static uint32_t selectedMaterialIndex = 0;
{
ImGui::BeginChild("MaterialList", ImVec2(0, 150), ImGuiChildFlags_Borders);
static ImGuiTextFilter filter;
filter.Draw("Filter", ImGui::GetContentRegionAvail().x);
if (ImGui::BeginListBox("##MaterialsListBox", ImVec2(-FLT_MIN, -FLT_MIN)))
{
for (uint32_t i = 0; i < materials.size(); i++)
{
const auto& materialInstance = materials[i];
std::string name = materialInstance->GetName();
if (!filter.PassFilter(name.c_str()))
continue;
bool isSelected = (selectedMaterialIndex == i);
if (ImGui::Selectable(name.c_str(), isSelected))
selectedMaterialIndex = i;
}
ImGui::EndListBox();
}
ImGui::EndChild();
}
/*
for (uint32_t i = 0; i < materials.size(); i++)
{
const auto& materialInstance = materials[i];
@ -294,6 +319,7 @@ namespace Prism
ImGui::TreePop();
}
*/
ImGui::Separator();
@ -327,8 +353,7 @@ namespace Prism
}
if (ImGui::IsItemClicked())
{
std::string filename = Application::Get().OpenFile("");
if (filename != "")
if (std::string filename = FileSystem::OpenFileSelector("*.png;*.tga;*.jpg;*.jpeg"); !filename.empty())
{
albedoMap = Texture2D::Create(filename, true/*m_AlbedoInput.SRGB*/);
materialInstance->Set("u_AlbedoTexture", albedoMap);
@ -374,8 +399,7 @@ namespace Prism
}
if (ImGui::IsItemClicked())
{
std::string filename = Application::Get().OpenFile("");
if (filename != "")
if (std::string filename = FileSystem::OpenFileSelector("*.png;*.tga;*.jpg;*.jpeg"); !filename.empty())
{
normalMap = Texture2D::Create(filename);
materialInstance->Set("u_NormalTexture", normalMap);
@ -412,8 +436,7 @@ namespace Prism
}
if (ImGui::IsItemClicked())
{
std::string filename = Application::Get().OpenFile("");
if (filename != "")
if (std::string filename = FileSystem::OpenFileSelector("*.png;*.tga;*.jpg;*.jpeg"); !filename.empty())
{
metalnessMap = Texture2D::Create(filename);
materialInstance->Set("u_MetalnessTexture", metalnessMap);
@ -451,8 +474,7 @@ namespace Prism
}
if (ImGui::IsItemClicked())
{
std::string filename = Application::Get().OpenFile("");
if (filename != "")
if (std::string filename = FileSystem::OpenFileSelector("*.png;*.tga;*.jpg;*.jpeg"); !filename.empty())
{
roughnessMap = Texture2D::Create(filename);
materialInstance->Set("u_RoughnessTexture", roughnessMap);
@ -493,31 +515,13 @@ namespace Prism
UI::BeginPropertyGrid();
ImGui::AlignTextToFramePadding();
auto& light = m_EditorScene->GetLight();
UI::PropertySlider("Light Direction", light.Direction, -1.0f, 1.0f);
UI::PropertyColor("Light Radiance", light.Radiance);
UI::PropertySlider("Light Multiplier", light.Multiplier, 0.0f, 5.0f);
UI::PropertySlider("Exposure", m_EditorCamera.GetExposure(), 0.0f, 5.0f);
UI::Property("Radiance Prefiltering", m_RadiancePrefilter);
UI::PropertySlider("Env Map Rotation", m_EnvMapRotation, -360.0f, 360.0f);
if (m_SceneState == SceneState::Edit)
float physics2DGravity = m_EditorScene->GetPhysics2DGravity();
if (UI::Property("2D World Gravity", physics2DGravity, -100.0f, 100.0f))
{
float physics2DGravity = m_EditorScene->GetPhysics2DGravity();
if (UI::Property("2D World Gravity", physics2DGravity, -10000.0f, 10000.0f))
{
m_EditorScene->SetPhysics2DGravity(physics2DGravity);
}
}
else if (m_SceneState == SceneState::Play)
{
float physics2DGravity = m_RuntimeScene->GetPhysics2DGravity();
if (UI::Property("2D World Gravity", physics2DGravity, -10000.0f, 10000.0f))
{
m_RuntimeScene->SetPhysics2DGravity(physics2DGravity);
}
m_EditorScene->SetPhysics2DGravity(physics2DGravity);
}
UI::Property("Show Bounding Boxes", m_UIShowBoundingBoxes);
@ -579,6 +583,15 @@ namespace Prism
m_ViewportPanelHovered = ImGui::IsWindowHovered();
m_ViewportPanelFocused = ImGui::IsWindowFocused();
if (!m_ViewportPanelFocused && m_ViewportPanelHovered)
{
if (Input::IsMouseButtonPressed(MouseButton::Right))
{
ImGui::SetWindowFocus("Viewport");
m_ViewportPanelFocused = true;
}
}
auto viewportOffset = ImGui::GetCursorPos(); // includes tab bar
auto viewportSize = ImGui::GetContentRegionAvail();
@ -693,7 +706,7 @@ namespace Prism
}
}else
{
glm::mat4 transformBase = transform * selection.Mesh->Transform;
glm::mat4 transformBase = transform * selection.Mesh->LocalTransform;
ImGuizmo::Manipulate(glm::value_ptr(m_EditorCamera.GetViewMatrix()),
glm::value_ptr(m_EditorCamera.GetProjectionMatrix()),
(ImGuizmo::OPERATION)m_GizmoType,
@ -704,6 +717,7 @@ namespace Prism
if (ImGuizmo::IsUsing())
{
selection.Mesh->LocalTransform = glm::inverse(entityTransform.GetTransform()) * transformBase;
selection.Mesh->Transform = glm::inverse(entityTransform.GetTransform()) * transformBase;
}
}
@ -950,7 +964,7 @@ namespace Prism
case KeyCode::B:
// Toggle bounding boxes
m_UIShowBoundingBoxes = !m_UIShowBoundingBoxes;
ShowBoundingBoxes(m_UIShowBoundingBoxes,false);
ShowBoundingBoxes(m_UIShowBoundingBoxes);
break;
case KeyCode::D:
if (m_SelectionContext.size())
@ -1059,9 +1073,9 @@ namespace Prism
return false;
}
void EditorLayer::ShowBoundingBoxes(bool show, bool onTop)
void EditorLayer::ShowBoundingBoxes(const bool show)
{
SceneRenderer::GetOptions().ShowBoundingBoxes = show && !onTop;
SceneRenderer::GetOptions().ShowBoundingBoxes = show;
}
void EditorLayer::SelectEntity(Entity entity)
@ -1157,9 +1171,7 @@ namespace Prism
void EditorLayer::OpenScene()
{
const auto& app = Application::Get();
const std::string filepath = app.OpenFile("Hazel Scene (*.hsc)\0*.hsc\0");
if (!filepath.empty())
if (const std::string filepath = FileSystem::OpenFileSelector("*.scene"); !filepath.empty())
OpenScene(filepath);
}
@ -1199,8 +1211,7 @@ namespace Prism
void EditorLayer::SaveSceneAs()
{
const auto& app = Application::Get();
const std::string filepath = app.SaveFile("Prism Scene (*.hsc)\0*.hsc\0");
if (!filepath.empty())
if (const std::string filepath = FileSystem::SaveFileSelector("*.scene"); !filepath.empty())
{
PM_CLIENT_INFO("Saving scene to: {0}", m_SceneFilePath);
SceneSerializer serializer(m_EditorScene);

View File

@ -28,7 +28,7 @@ namespace Prism
bool OnKeyPressedEvent(KeyPressedEvent& e);
bool OnMouseButtonPressedEvent(MouseButtonPressedEvent& e);
void ShowBoundingBoxes(bool show, bool onTop = false);
void ShowBoundingBoxes(bool show);
void SelectEntity(Entity entity);
void UpdateWindowTitle(const std::string& sceneName);
@ -126,10 +126,6 @@ namespace Prism
// PBR params
bool m_RadiancePrefilter = false;
float m_EnvMapRotation = 0.0f;
// Editor resources
Ref<Texture2D> m_CheckerboardTex;

View File

@ -18,6 +18,7 @@ add_subdirectory(vendor/mono EXCLUDE_FROM_ALL)
add_subdirectory(vendor/FastNoise EXCLUDE_FROM_ALL)
add_subdirectory(vendor/yaml-cpp EXCLUDE_FROM_ALL)
add_subdirectory(vendor/Box2D EXCLUDE_FROM_ALL)
add_subdirectory(vendor/efsw EXCLUDE_FROM_ALL)
# ------------- imgui -------------
@ -91,6 +92,7 @@ set(LINK_LIBRARIES_PRIVATE
tinyFileDialogs
FastNoise
yaml-cpp
efsw
PhysXExtensions_static_64
PhysX_static_64

View File

@ -303,11 +303,11 @@ namespace Prism
return newFileName;
}
void AssetsManager::ImportAsset(const std::string& filepath, AssetHandle parentHandle)
Ref<Asset> AssetsManager::ImportAsset(const std::string& filepath, AssetHandle parentHandle)
{
const std::string extension = Utils::GetExtension(filepath);
if (extension == "meta")
return;
return Ref<Asset>();
const AssetType type = AssetTypes::GetAssetTypeFromExtension(extension);
Ref<Asset> asset = AssetSerializer::LoadAssetInfo(filepath, parentHandle, type);
@ -320,10 +320,12 @@ namespace Prism
}
s_LoadedAssets[asset->Handle] = asset;
return asset;
}
AssetHandle AssetsManager::ProcessDirectory(const std::string& directoryPath, AssetHandle parentHandle)
Ref<Asset> AssetsManager::ProcessDirectory(const std::string& directoryPath, AssetHandle parentHandle)
{
Ref<Directory> dirInfo = AssetSerializer::LoadAssetInfo(directoryPath, parentHandle, AssetType::Directory).As<Directory>();
s_LoadedAssets[dirInfo->Handle] = dirInfo;
@ -336,15 +338,17 @@ namespace Prism
if (entry.is_directory())
ProcessDirectory(entry.path().string(), dirInfo->Handle);
else
ImportAsset(entry.path().string(), dirInfo->Handle);
{
Ref<Asset> asset = ImportAsset(entry.path().string(), dirInfo->Handle);
}
}
return dirInfo->Handle;
return dirInfo;
}
void AssetsManager::ReloadAssets()
Ref<Asset> AssetsManager::ReloadAssets()
{
ProcessDirectory("assets", 0);
return ProcessDirectory("assets", 0);
}
void AssetsManager::OnFileSystemChanged(FileSystemChangedEvent e)
@ -363,35 +367,70 @@ namespace Prism
if (e.IsDirectory)
ProcessDirectory(e.FilePath, parentHandle);
else
ImportAsset(e.FilePath, parentHandle);
{
Ref<Asset> asset = ImportAsset(e.FilePath, parentHandle);
}
}
break;
case FileSystemAction::Modified:
{
if (!e.IsDirectory)
ImportAsset(e.FilePath, parentHandle);
{
Ref<Asset> asset = ImportAsset(e.FilePath, parentHandle);
}
}
break;
case FileSystemAction::Rename:
{
Ref<Asset> asset;
for (auto it = s_LoadedAssets.begin(); it != s_LoadedAssets.end(); it++)
{
if (it->second->FileName == e.OldName)
{
it->second->FilePath = e.FilePath;
it->second->FileName = e.NewName;
asset = it->second;
}
}
if (asset)
{
if (asset->Type != AssetTypes::GetAssetTypeFromExtension(Utils::GetExtension(e.FilePath)))
{
RemoveAsset(asset->Handle);
FileSystem::PrismDeleteFile(asset->FilePath + ".meta");
asset = ImportAsset(e.FilePath, parentHandle);
}else
{
std::string oldMetaPath = asset->FilePath + ".meta";
std::string newMetaPath = e.FilePath + ".meta";
std::error_code ec;
std::filesystem::rename(oldMetaPath, newMetaPath, ec);
if (ec)
{
PM_CORE_ERROR("Failed to rename meta file: {}", ec.message());
}
asset->FilePath = e.FilePath;
asset->FileName = e.NewName;
}
}
}
break;
case FileSystemAction::Delete:
{
for (auto it = s_LoadedAssets.begin(); it != s_LoadedAssets.end(); it++)
{
if (it->second->FilePath != e.FilePath)
std::string filePath = Utils::NormalizePath(it->second->FilePath);
std::string eFilePath = Utils::NormalizePath(e.FilePath);
if (filePath != eFilePath)
continue;
RemoveAsset(it->first);
FileSystem::PrismDeleteFile(eFilePath + ".meta");
break;
}
}

View File

@ -10,6 +10,7 @@
#include "Asset.h"
#include "Prism/Utilities/FileSystem.h"
#include "Prism/Core/Ref.h"
#include "Prism/Project/Project.h"
namespace Prism
{
@ -66,9 +67,14 @@ namespace Prism
private:
static void ImportAsset(const std::string& filepath, AssetHandle parentHandle);
static AssetHandle ProcessDirectory(const std::string& directoryPath, AssetHandle parentHandle);
static void ReloadAssets();
static Ref<Asset> ImportAsset(const std::string& filepath, AssetHandle parentHandle);
static Ref<Asset> ProcessDirectory(const std::string& directoryPath, AssetHandle parentHandle);
/**
* this will load from projectPath all assets, and return the root Asset Ref
* @param projectPath assets path
* @return the root asset Ref
*/
static Ref<Asset> ReloadAssets();
static void OnFileSystemChanged(FileSystemChangedEvent e);

View File

@ -10,7 +10,6 @@
#include "GLFW/glfw3.h"
#include "Prism/Renderer/FrameBuffer.h"
#include "tinyfiledialogs.h"
#include "Prism/Physics/Physics3D.h"
#include "Prism/Script/ScriptEngine.h"
#include "Prism/Asset/AssetsManager.h"
@ -28,6 +27,10 @@ namespace Prism
Application::Application(const ApplicationProps& props)
{
if (s_Instance != nullptr)
{
PM_CORE_ASSERT(false, "Application already exists!");
}
s_Instance = this;
m_Window = std::unique_ptr<Window>(Window::Create(WindowProps{props.Name, props.Width, props.Height}));
@ -101,6 +104,9 @@ namespace Prism
{
m_ImGuiLayer->Begin();
for (Layer* layer : m_LayerStack)
layer->OnImGuiRender();
ImGui::Begin("Renderer");
const auto& caps = RendererAPI::GetCapabilities();
ImGui::Text("Vendor: %s", caps.Vendor.c_str());
@ -109,90 +115,9 @@ namespace Prism
ImGui::Text("Frame Time: %.2fms\n", m_TimeStep.GetMilliseconds());
ImGui::End();
for (Layer* layer : m_LayerStack)
layer->OnImGuiRender();
m_ImGuiLayer->End();
}
// TODO: fix this to prase filter
std::string Application::OpenFile(const std::string& filter) const
{
// TODO: will move it to other folder
// 处理过滤器
std::vector<const char*> filterPatterns;
std::vector<std::string> patternStorage;
if (!filter.empty()) {
const char* ptr = filter.c_str();
bool isDescription = true;
while (*ptr) {
if (isDescription) {
// 跳过描述
isDescription = false;
} else {
// 添加模式
patternStorage.push_back(ptr);
isDescription = true;
}
ptr += strlen(ptr) + 1;
}
// 转换为 C 字符串数组
for (const auto& pattern : patternStorage) {
filterPatterns.push_back(pattern.c_str());
}
}
// 如果没有过滤器,添加默认值
if (filterPatterns.empty()) {
filterPatterns.push_back("*");
}
// 构建过滤器描述
std::string filterDesc;
if (!patternStorage.empty()) {
std::stringstream ss;
ss << "Files (";
for (size_t i = 0; i < patternStorage.size(); ++i) {
if (i > 0) ss << ",";
ss << patternStorage[i];
}
ss << ")";
filterDesc = ss.str();
}
const char* result = tinyfd_openFileDialog(
"Open File", // 对话框标题
nullptr, // 初始路径nullptr 表示使用默认
0, // 过滤器数量 (注意:这里需要根据解析情况调整,见下文)
nullptr, // 过滤器模式数组
nullptr, // 过滤器描述
0 // 是否允许多选0 为单选1 为多选
);
// 调用文件对话框
/*
const char* result = tinyfd_openFileDialog(
"Open File", // 标题
nullptr, // 初始目录
static_cast<int>(filterPatterns.size()), // 过滤器数量
filterPatterns.data(), // 过滤器模式
filterDesc.empty() ? nullptr : filterDesc.c_str(), // 描述
0 // 单选
);
*/
return result ? std::string(result) : std::string();
}
std::string Application::SaveFile(const std::string& filter) const
{
return OpenFile(filter);
}
Application& Application::Get()
{
return *s_Instance;

View File

@ -13,10 +13,29 @@
namespace Prism
{
class Project;
struct CommandArgs
{
int count = 0;
char** args = nullptr;
const char* operator[](const int index)
{
if (index >= count)
{
return nullptr;
}
return args[count];
}
};
struct ApplicationProps
{
std::string Name;
uint32_t Width, Height;
CommandArgs CommandArgs;
};
class PRISM_API Application
@ -35,9 +54,6 @@ namespace Prism
void RenderImGui();
std::string OpenFile(const std::string& filter = "All\0*.*\0") const;
std::string SaveFile(const std::string& filter = "All\0*.*\0") const;
inline Window& GetWindow() { return *m_Window; }
static Application& Get();
@ -70,7 +86,7 @@ namespace Prism
// this function will implemented by client
Application* CreateApplication();
Application* CreateApplication(CommandArgs args);
}
#if defined(_MSC_VER) && !defined(__clang__)

View File

@ -5,17 +5,26 @@
#ifndef ENTRYPOINT_H
#define ENTRYPOINT_H
#include <iostream>
#include "Application.h"
extern Prism::Application* Prism::CreateApplication();
extern Prism::Application* Prism::CreateApplication(CommandArgs args);
int main(int argc, char** argv)
{
//TODO: this will use other method to impl
std::cout << std::filesystem::current_path() << std::endl;
std::filesystem::current_path(std::filesystem::path(argv[0]).parent_path());
std::cout << std::filesystem::current_path() << std::endl;
#ifdef PRISM_STATIC
Prism::InitializeCore();
#endif
Prism::Application* app = Prism::CreateApplication();
const Prism::CommandArgs args{ argc, argv };
Prism::Application* app = Prism::CreateApplication(args);
app->Run();
delete app;

View File

@ -10,9 +10,11 @@
#include "spdlog/sinks/stdout_color_sinks-inl.h"
#ifdef _DEBUG
#define LOG_LEVEL spdlog::level::trace
#define LOG_LEVEL spdlog::level::trace
#define LOG_PATTERN "%^[%@:%#][%Y-%m-%d %H:%M:%S.%e] [%l] %v%$"
#else
#define LOG_LEVEL spdlog::level::trace
#define LOG_PATTERN "%^[%Y-%m-%d %H:%M:%S.%e] [%l] %v%$"
#endif
namespace Prism
@ -40,11 +42,11 @@ namespace Prism
s_CoreLogger = std::make_shared<spdlog::logger>("PRISM", prismSinks.begin(), prismSinks.end());
s_CoreLogger->set_level(LOG_LEVEL);
s_CoreLogger->set_pattern("%^[%Y-%m-%d %H:%M:%S.%e] [%l] %v%$");
s_CoreLogger->set_pattern(LOG_PATTERN);
s_ClientLogger = std::make_shared<spdlog::logger>("APP", appSinks.begin(), appSinks.end());
s_ClientLogger->set_level(LOG_LEVEL);
s_CoreLogger->set_pattern("%^[%Y-%m-%d %H:%M:%S.%e] [%l] %v%$");
s_ClientLogger->set_pattern(LOG_PATTERN);
}
void Log::Shutdown()

View File

@ -25,13 +25,22 @@ namespace Prism
};
}
#ifdef _DEBUG
#define PM_CORE_TRACE(...) SPDLOG_LOGGER_TRACE(::Prism::Log::GetCoreLogger(), __VA_ARGS__)
#define PM_CORE_DEBUG(...) SPDLOG_LOGGER_DEBUG(::Prism::Log::GetCoreLogger(), __VA_ARGS__)
#define PM_CORE_INFO(...) SPDLOG_LOGGER_INFO(::Prism::Log::GetCoreLogger(), __VA_ARGS__)
#define PM_CORE_WARN(...) SPDLOG_LOGGER_WARN(::Prism::Log::GetCoreLogger(), __VA_ARGS__)
#define PM_CORE_ERROR(...) SPDLOG_LOGGER_ERROR(::Prism::Log::GetCoreLogger(), __VA_ARGS__)
#define PM_CORE_FATAL(...) SPDLOG_LOGGER_CRITICAL(::Prism::Log::GetCoreLogger(), __VA_ARGS__)
#else
#define PM_CORE_TRACE(...) ::Prism::Log::GetCoreLogger()->trace(__VA_ARGS__)
#define PM_CORE_DEBUG(...) ::Prism::Log::GetCoreLogger()->debug( __VA_ARGS__)
#define PM_CORE_INFO(...) ::Prism::Log::GetCoreLogger()->info(__VA_ARGS__)
#define PM_CORE_WARN(...) ::Prism::Log::GetCoreLogger()->warn(__VA_ARGS__)
#define PM_CORE_ERROR(...) ::Prism::Log::GetCoreLogger()->error(__VA_ARGS__)
#define PM_CORE_FATAL(...) ::Prism::Log::GetCoreLogger()->critical(__VA_ARGS__)
#endif
#define PM_CORE_TRACE(...) ::Prism::Log::GetCoreLogger()->trace(__VA_ARGS__)
#define PM_CORE_DEBUG(...) ::Prism::Log::GetCoreLogger()->debug( __VA_ARGS__)
#define PM_CORE_INFO(...) ::Prism::Log::GetCoreLogger()->info(__VA_ARGS__)
#define PM_CORE_WARN(...) ::Prism::Log::GetCoreLogger()->warn(__VA_ARGS__)
#define PM_CORE_ERROR(...) ::Prism::Log::GetCoreLogger()->error(__VA_ARGS__)
#define PM_CORE_FATAL(...) ::Prism::Log::GetCoreLogger()->critical(__VA_ARGS__)
#define PM_CLIENT_TRACE(...) ::Prism::Log::GetClientLogger()->trace(__VA_ARGS__)
#define PM_CLIENT_DEBUG(...) ::Prism::Log::GetClientLogger()->debug(__VA_ARGS__)

View File

@ -133,7 +133,7 @@ namespace Prism
if (ImGui::MenuItem("Import"))
{
//std::string filename = Application::Get().OpenFile("");
// std::string filename = FileSystem::OpenFileSelector();
}
if (ImGui::MenuItem("Refresh"))

View File

@ -9,16 +9,158 @@
#include <Windows.h>
#include <filesystem>
#include "tinyfiledialogs.h"
#include <sstream>
#include "Prism/Core/Log.h"
#include "Prism/Asset/AssetsManager.h"
#include "Prism/Core/Application.h"
#include "efsw/efsw.hpp"
namespace Prism
{
FileSystem::FileSystemChangedCallbackFn FileSystem::s_Callback;
static bool s_Watching = true;
static bool s_IgnoreNextChange = false;
static HANDLE s_WatcherThread;
std::atomic<bool> FileSystem::s_IgnoreNextChange = false;
efsw::FileWatcher* FileSystem::s_FileWatcher = nullptr;
PrismFileWatcherListener* FileSystem::s_Listener = nullptr;
class PrismFileWatcherListener : public efsw::FileWatchListener
{
public:
void handleFileAction(efsw::WatchID watchid,
const std::string& dir,
const std::string& filename,
efsw::Action action,
std::string oldFilename = "") override
{
// 如果引擎自身操作设置了忽略标志,则跳过本次事件
if (FileSystem::s_IgnoreNextChange.load())
return;
std::filesystem::path fullPath = std::filesystem::path(dir) / filename;
FileSystemChangedEvent e;
e.FilePath = fullPath.string();
e.NewName = filename;
e.OldName = oldFilename; // efsw 在重命名时会提供旧文件名
e.IsDirectory = false; // 稍后根据实际情况判断
// 判断是否为目录(可能抛出异常,使用 error_code 版本)
std::error_code ec;
if (std::filesystem::is_directory(fullPath, ec))
e.IsDirectory = true;
// 如果 ec 有错误(文件可能已被删除),保持 false
// 映射 efsw 动作到你的枚举
switch (action)
{
case efsw::Actions::Add:
e.Action = FileSystemAction::Added;
break;
case efsw::Actions::Delete:
e.Action = FileSystemAction::Delete;
break;
case efsw::Actions::Modified:
e.Action = FileSystemAction::Modified;
break;
case efsw::Actions::Moved:
e.Action = FileSystemAction::Rename;
break;
default:
return; // 忽略未知事件
}
// 调用外部回调(需确保线程安全)
if (FileSystem::s_Callback)
FileSystem::s_Callback(e);
}
};
std::string FileSystem::OpenFileSelector(const std::string& filter)
{
std::vector<const char*> patternCstrs;
std::vector<std::string> patterns;
std::string description = "All files (*.*)";
if (!filter.empty() && filter != "*.*") {
std::istringstream iss(filter);
std::string token;
while (std::getline(iss, token, ';')) {
if (!token.empty()) {
patterns.push_back(token);
}
}
// 构建描述文字
if (!patterns.empty()) {
description = "Custom files (";
for (size_t i = 0; i < patterns.size(); ++i) {
if (i > 0) description += "; ";
description += patterns[i];
}
description += ")";
// 准备 C 字符串数组
for (const auto& p : patterns) {
patternCstrs.push_back(p.c_str());
}
}
}
const char* result = tinyfd_openFileDialog(
"Open File", // title
"", // default open path
static_cast<int>(patternCstrs.size()), // filter count
patternCstrs.empty() ? nullptr : patternCstrs.data(), // filter pattern
description.c_str(), // filter description
0 // 0 is single select, 1 is Multiple select
);
return result ? std::string(result) : std::string();
}
std::string FileSystem::SaveFileSelector(const std::string& filter)
{
std::vector<const char*> patternCstrs;
std::string description = "All files (*.*)";
std::vector<std::string> patterns;
if (!filter.empty() && filter != "*.*") {
std::istringstream iss(filter);
std::string token;
while (std::getline(iss, token, ';')) {
if (!token.empty()) {
patterns.push_back(token);
}
}
if (!patterns.empty()) {
description = "Custom files (";
for (size_t i = 0; i < patterns.size(); ++i) {
if (i > 0) description += "; ";
description += patterns[i];
}
description += ")";
for (const auto& p : patterns) {
patternCstrs.push_back(p.c_str());
}
}
}
const char* result = tinyfd_saveFileDialog(
"Save File",
"",
static_cast<int>(patternCstrs.size()),
patternCstrs.empty() ? nullptr : patternCstrs.data(),
description.c_str()
);
return result ? std::string(result) : std::string();
}
bool FileSystem::CreateFolder(const std::filesystem::path& filepath)
{
@ -54,57 +196,29 @@ namespace Prism
s_Callback = callback;
}
/*
static std::string wchar_to_string(wchar_t* input)
{
std::wstring string_input(input);
std::string converted(string_input.begin(), string_input.end());
return converted;
}
*/
static std::string wchar_to_string(const wchar_t* input)
{
if (!input) return {};
const int bufferSize = WideCharToMultiByte(CP_UTF8, 0, input, -1, nullptr, 0, nullptr, nullptr);
if (bufferSize == 0) return {};
std::string result(bufferSize, 0);
WideCharToMultiByte(CP_UTF8, 0, input, -1, &result[0], bufferSize, nullptr, nullptr);
// 移除末尾的null终止符
result.pop_back();
return result;
}
std::string FileSystem::Rename(const std::string& filepath, const std::string& newName)
{
s_IgnoreNextChange = true;
std::filesystem::path p = filepath;
const std::filesystem::path p = filepath;
std::string newFilePath = p.parent_path().string() + "/" + newName + p.extension().string();
MoveFileA(filepath.c_str(), newFilePath.c_str());
s_IgnoreNextChange = false;
return newFilePath;
}
bool FileSystem::PrismDeleteFile(const std::string& filepath)
{
s_IgnoreNextChange = true;
std::string fp = filepath;
fp.append(1, '\0');
SHFILEOPSTRUCTA file_op;
file_op.hwnd = NULL;
file_op.wFunc = FO_DELETE;
file_op.pFrom = fp.c_str();
file_op.pTo = "";
file_op.fFlags = FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_SILENT;
file_op.fAnyOperationsAborted = false;
file_op.hNameMappings = 0;
file_op.lpszProgressTitle = "";
const int result = SHFileOperationA(&file_op);
std::error_code ec;
const bool removed = std::filesystem::remove(filepath, ec);
s_IgnoreNextChange = false;
return result == 0;
if (!removed || ec)
{
PM_CORE_ERROR("Delete failed: {}", ec.message());
return false;
}
return true;
}
bool FileSystem::PrismMoveFile(const std::string& filepath, const std::string& dest)
@ -120,139 +234,41 @@ namespace Prism
void FileSystem::StartWatching()
{
DWORD threadId;
s_WatcherThread = CreateThread(NULL, 0, Watch, NULL, 0, &threadId);
PM_CORE_ASSERT(s_WatcherThread != NULL);
PM_CORE_TRACE("Starting file watching services");
if (s_FileWatcher) return;
s_FileWatcher = new efsw::FileWatcher();
s_Listener = new PrismFileWatcherListener();
efsw::WatchID watchID = s_FileWatcher->addWatch("assets", s_Listener, true);
if (watchID < 0)
{
PM_CORE_ERROR("Failed to add watch for {}", "assets");
delete s_FileWatcher;
s_FileWatcher = nullptr;
delete s_Listener;
s_Listener = nullptr;
return;
}
s_FileWatcher->watch();
PM_CORE_TRACE("Started file watching services on {}", "assets");
}
void FileSystem::StopWatching()
{
// TODO: this delay is too long
s_Watching = false;
const DWORD result = WaitForSingleObject(s_WatcherThread, 1000);
if (result == WAIT_TIMEOUT)
TerminateThread(s_WatcherThread, 0);
CloseHandle(s_WatcherThread);
PM_CORE_TRACE("closing file watching services");
}
unsigned long FileSystem::Watch(void* param)
{
const LPCSTR filepath = "assets";
BYTE* buffer = new BYTE[10 * 1024]; // 1 MB
OVERLAPPED overlapped = { 0 };
HANDLE handle = NULL;
DWORD bytesReturned = 0;
handle = CreateFile(
filepath,
FILE_LIST_DIRECTORY,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
NULL
);
ZeroMemory(&overlapped, sizeof(overlapped));
if (handle == INVALID_HANDLE_VALUE)
PM_CORE_ERROR("Unable to accquire directory handle: {0}", GetLastError());
overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (overlapped.hEvent == NULL)
if (s_FileWatcher)
{
PM_CORE_ERROR("CreateEvent failed!");
return 0;
delete s_FileWatcher;
s_FileWatcher = nullptr;
}
while (s_Watching)
if (s_Listener)
{
DWORD status = ReadDirectoryChangesW(
handle,
buffer,
10 * 1024 * sizeof(BYTE),
TRUE,
FILE_NOTIFY_CHANGE_CREATION | FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME,
&bytesReturned,
&overlapped,
NULL
);
if (!status)
PM_CORE_ERROR("{0}", GetLastError());
const DWORD waitOperation = WaitForSingleObject(overlapped.hEvent, 2000);
if (waitOperation != WAIT_OBJECT_0)
continue;
if (s_IgnoreNextChange)
continue;
std::string oldName;
char fileName[MAX_PATH * 10] = "";
FILE_NOTIFY_INFORMATION* current = (FILE_NOTIFY_INFORMATION*)buffer;
for (;;)
{
ZeroMemory(fileName, sizeof(fileName));
WideCharToMultiByte(CP_ACP, 0, current->FileName, current->FileNameLength / sizeof(WCHAR), fileName, sizeof(fileName), NULL, NULL);
std::filesystem::path filePath = std::filesystem::path("assets") / std::string(fileName);
FileSystemChangedEvent e;
e.FilePath = filePath.string();
e.NewName = filePath.filename().string();
e.OldName = filePath.filename().string();
e.IsDirectory = std::filesystem::is_directory(filePath);
switch (current->Action)
{
case FILE_ACTION_ADDED:
{
e.Action = FileSystemAction::Added;
s_Callback(e);
break;
}
case FILE_ACTION_REMOVED:
{
e.IsDirectory = AssetsManager::IsDirectory(e.FilePath);
e.Action = FileSystemAction::Delete;
s_Callback(e);
break;
}
case FILE_ACTION_MODIFIED:
{
e.Action = FileSystemAction::Modified;
s_Callback(e);
break;
}
case FILE_ACTION_RENAMED_OLD_NAME:
{
oldName = filePath.filename().string();
break;
}
case FILE_ACTION_RENAMED_NEW_NAME:
{
e.OldName = oldName;
e.Action = FileSystemAction::Rename;
s_Callback(e);
break;
}
}
if (!current->NextEntryOffset)
break;
current += current->NextEntryOffset;
}
delete s_Listener;
s_Listener = nullptr;
}
return 0;
PM_CORE_TRACE("Stopped file watching services");
}
}

View File

@ -163,7 +163,7 @@ namespace Prism
//////////////// BloomPass ////////////////
{
FramebufferSpecification bloomBlurFramebufferSpec;
bloomBlurFramebufferSpec.Attachments = { FramebufferTextureFormat::RGBA16F , FramebufferTextureFormat::RGBA8};
bloomBlurFramebufferSpec.Attachments = { FramebufferTextureFormat::RGBA16F , FramebufferTextureFormat::RGBA16F};
bloomBlurFramebufferSpec.ClearColor = { 0.1f, 0.1f, 0.1f, 1.0f };
RenderPassSpecification bloomBlurRenderPassSpec;
@ -173,7 +173,7 @@ namespace Prism
s_Data.BloomBlurPass[1] = RenderPass::Create(bloomBlurRenderPassSpec);
FramebufferSpecification bloomBlendFramebufferSpec;
bloomBlendFramebufferSpec.Attachments = { FramebufferTextureFormat::RGBA8 };
bloomBlendFramebufferSpec.Attachments = { FramebufferTextureFormat::RGBA16F };
bloomBlendFramebufferSpec.ClearColor = { 0.1f, 0.1f, 0.1f, 1.0f };
RenderPassSpecification bloomBlendRenderPassSpec;
@ -275,7 +275,6 @@ namespace Prism
s_Data.SceneData.SkyboxMaterial = scene->m_SkyboxMaterial;
s_Data.SceneData.SceneEnvironment = scene->m_Environment;
s_Data.SceneData.SceneEnvironmentIntensity = scene->m_EnvironmentIntensity;
s_Data.SceneData.ActiveLight = scene->m_Light;
s_Data.SceneData.SceneLightEnvironment = scene->m_LightEnvironment;
}

View File

@ -26,7 +26,7 @@ namespace Prism
void SceneRenderer::Init()
{
FramebufferSpecification finalFramebufferSpec;
finalFramebufferSpec.Attachments = { FramebufferTextureFormat::RGBA8, FramebufferTextureFormat::Depth};
finalFramebufferSpec.Attachments = { FramebufferTextureFormat::RGBA16F, FramebufferTextureFormat::Depth};
finalFramebufferSpec.ClearColor = { 0.1f, 0.1f, 0.1f, 1.0f };
RenderPassSpecification finalRenderPassSpec;

View File

@ -594,9 +594,6 @@ namespace Prism
void Scene::CopyTo(Ref<Scene>& target)
{
target->m_Light = m_Light;
target->m_LightMultiplier = m_LightMultiplier;
target->m_Environment = m_Environment;
target->m_SkyboxTexture = m_SkyboxTexture;
target->m_SkyboxMaterial = m_SkyboxMaterial;
@ -658,6 +655,11 @@ namespace Prism
return gravity.y;
}
void Scene::SetPhysics2DGravity(const glm::vec2& vec)
{
b2World_SetGravity(m_Registry.get<Box2DWorldComponent>(m_SceneEntity).World, {vec.x, vec.y});
}
void Scene::SetPhysics2DGravity(const float gravity)
{
b2World_SetGravity(m_Registry.get<Box2DWorldComponent>(m_SceneEntity).World, {0.0f, gravity});

View File

@ -75,9 +75,6 @@ namespace Prism
float& GetSkyboxLod() { return m_SkyboxLod; }
Light& GetLight() { return m_Light; }
const Light& GetLight() const { return m_Light; }
Entity GetMainCameraEntity();
void AddEntity(Entity* entity);
@ -107,6 +104,16 @@ namespace Prism
static Ref<Scene> GetScene(const UUID& uuid);
float GetPhysics2DGravity() const;
/**
* set 2D gravity two directions
* @param vec (x,y) two directory
*/
void SetPhysics2DGravity(const glm::vec2& vec);
/**
* only set y-axis gravity
* @param gravity y gravity
*/
void SetPhysics2DGravity(float gravity);
// Editor-specific
@ -129,8 +136,6 @@ namespace Prism
Entity* m_Physics2DBodyEntityBuffer = nullptr;
Entity* m_Physics3DBodyEntityBuffer = nullptr;
Light m_Light;
float m_LightMultiplier = 0.3f;
LightEnvironment m_LightEnvironment;
bool m_IsPlaying = false;

View File

@ -588,13 +588,6 @@ namespace Prism
out << YAML::Value;
out << YAML::BeginMap; // Environment
out << YAML::Key << "AssetHandle" << YAML::Value << scene->GetEnvironment()->Handle;
const auto& light = scene->GetLight();
out << YAML::Key << "Light" << YAML::Value;
out << YAML::BeginMap; // Light
out << YAML::Key << "Direction" << YAML::Value << light.Direction;
out << YAML::Key << "Radiance" << YAML::Value << light.Radiance;
out << YAML::Key << "Multiplier" << YAML::Value << light.Multiplier;
out << YAML::EndMap; // Light
out << YAML::EndMap; // Environment
}

View File

@ -7,6 +7,12 @@
#include <functional>
namespace efsw
{
class FileWatcher;
}
namespace Prism
{
enum class FileSystemAction
@ -29,6 +35,9 @@ namespace Prism
using FileSystemChangedCallbackFn = std::function<void(FileSystemChangedEvent)>;
public:
static std::string OpenFileSelector(const std::string& filter = "");
static std::string SaveFileSelector(const std::string& filter = "");
static bool CreateFolder(const std::filesystem::path& filepath);
static bool Exists(const std::string& filepath);
static std::string Rename(const std::string& filepath, const std::string& newName);
@ -40,10 +49,13 @@ namespace Prism
static void StopWatching();
private:
static unsigned long Watch(void* param);
static std::atomic<bool> s_IgnoreNextChange;
static efsw::FileWatcher* s_FileWatcher;
static class PrismFileWatcherListener* s_Listener;
static FileSystemChangedCallbackFn s_Callback;
private:
static FileSystemChangedCallbackFn s_Callback;
friend class PrismFileWatcherListener;
};
}

View File

@ -17,13 +17,10 @@ namespace Prism::Utils
return "";
}
std::string GetExtension(const std::string& filename)
std::string GetExtension(const std::string& filename, const bool includeDot)
{
std::vector<std::string> parts = SplitString(filename, '.');
if (parts.size() > 1)
return parts[parts.size() - 1];
if (const std::vector<std::string> parts = SplitString(filename, '.'); parts.size() > 1)
return parts[ includeDot ? parts.size() - 1 : parts.size()];
return "";
}

View File

@ -10,7 +10,7 @@
namespace Prism::Utils
{
std::string GetFilename(const std::string& filepath);
std::string GetExtension(const std::string& filename);
std::string GetExtension(const std::string& filename, bool includeDot = true);
std::string RemoveExtension(const std::string& filename);
std::string NormalizePath(std::string path);
std::string StringToLower(const std::string& str);

1
Prism/vendor/efsw vendored Submodule

Submodule Prism/vendor/efsw added at 22f17a0bcd