资源管理更新
This commit is contained in:
parent
f5aa77b71e
commit
c8b8e79dd7
@ -33,8 +33,35 @@ endif()
|
||||
|
||||
list(PREPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
|
||||
|
||||
if(EXISTS "${CMAKE_SOURCE_DIR}/vcpkg_installed/x64-windows/share/imgui/imgui-config.cmake")
|
||||
list(PREPEND CMAKE_PREFIX_PATH "${CMAKE_SOURCE_DIR}/vcpkg_installed/x64-windows")
|
||||
set(METACORE_VCPKG_TRIPLET "")
|
||||
set(METACORE_VCPKG_PREFIX "")
|
||||
if(WIN32)
|
||||
set(METACORE_VCPKG_TRIPLET "x64-windows")
|
||||
elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|amd64|AMD64)$")
|
||||
set(METACORE_VCPKG_TRIPLET "x64-linux")
|
||||
endif()
|
||||
|
||||
if(METACORE_VCPKG_TRIPLET)
|
||||
foreach(METACORE_VCPKG_PREFIX_CANDIDATE IN ITEMS
|
||||
"${CMAKE_SOURCE_DIR}/vcpkg_installed/${METACORE_VCPKG_TRIPLET}"
|
||||
"$ENV{VCPKG_ROOT}/installed/${METACORE_VCPKG_TRIPLET}"
|
||||
"$ENV{HOME}/vcpkg/installed/${METACORE_VCPKG_TRIPLET}"
|
||||
)
|
||||
if(EXISTS "${METACORE_VCPKG_PREFIX_CANDIDATE}/share/imgui/imgui-config.cmake")
|
||||
set(METACORE_VCPKG_PREFIX "${METACORE_VCPKG_PREFIX_CANDIDATE}")
|
||||
list(PREPEND CMAKE_PREFIX_PATH "${METACORE_VCPKG_PREFIX_CANDIDATE}")
|
||||
break()
|
||||
endif()
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
if(METACORE_VCPKG_PREFIX)
|
||||
foreach(METACORE_CACHED_PACKAGE_DIR IN ITEMS imgui_DIR glfw3_DIR)
|
||||
if(DEFINED ${METACORE_CACHED_PACKAGE_DIR} AND
|
||||
NOT "${${METACORE_CACHED_PACKAGE_DIR}}" MATCHES "^${METACORE_VCPKG_PREFIX}(/|$)")
|
||||
unset(${METACORE_CACHED_PACKAGE_DIR} CACHE)
|
||||
endif()
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
|
||||
@ -177,6 +204,8 @@ endfunction()
|
||||
set(METACORE_FOUNDATION_HEADERS
|
||||
Source/MetaCoreFoundation/Public/MetaCoreFoundation/MetaCoreArchive.h
|
||||
Source/MetaCoreFoundation/Public/MetaCoreFoundation/MetaCoreAssetGuid.h
|
||||
Source/MetaCoreFoundation/Public/MetaCoreFoundation/MetaCoreAssetChange.h
|
||||
Source/MetaCoreFoundation/Public/MetaCoreFoundation/MetaCoreAssetDependencyGraph.h
|
||||
Source/MetaCoreFoundation/Public/MetaCoreFoundation/MetaCoreAssetRegistry.h
|
||||
Source/MetaCoreFoundation/Public/MetaCoreFoundation/MetaCoreGeneratedReflection.h
|
||||
Source/MetaCoreFoundation/Public/MetaCoreFoundation/MetaCoreHash.h
|
||||
@ -185,11 +214,13 @@ set(METACORE_FOUNDATION_HEADERS
|
||||
Source/MetaCoreFoundation/Public/MetaCoreFoundation/MetaCorePackage.h
|
||||
Source/MetaCoreFoundation/Public/MetaCoreFoundation/MetaCoreProject.h
|
||||
Source/MetaCoreFoundation/Public/MetaCoreFoundation/MetaCoreReflection.h
|
||||
Source/MetaCoreFoundation/Public/MetaCoreFoundation/MetaCoreRuntimeAssetRegistry.h
|
||||
)
|
||||
|
||||
set(METACORE_FOUNDATION_SOURCES
|
||||
Source/MetaCoreFoundation/Private/MetaCoreArchive.cpp
|
||||
Source/MetaCoreFoundation/Private/MetaCoreAssetGuid.cpp
|
||||
Source/MetaCoreFoundation/Private/MetaCoreAssetDependencyGraph.cpp
|
||||
Source/MetaCoreFoundation/Private/MetaCoreAssetRegistry.cpp
|
||||
Source/MetaCoreFoundation/Private/MetaCoreHash.cpp
|
||||
Source/MetaCoreFoundation/Private/MetaCoreId.cpp
|
||||
@ -197,6 +228,7 @@ set(METACORE_FOUNDATION_SOURCES
|
||||
Source/MetaCoreFoundation/Private/MetaCorePackage.cpp
|
||||
Source/MetaCoreFoundation/Private/MetaCoreProject.cpp
|
||||
Source/MetaCoreFoundation/Private/MetaCoreReflection.cpp
|
||||
Source/MetaCoreFoundation/Private/MetaCoreRuntimeAssetRegistry.cpp
|
||||
)
|
||||
|
||||
metacore_generate_reflection(
|
||||
|
||||
Binary file not shown.
@ -10,12 +10,14 @@
|
||||
"guid": "9cd50d41-df30-26ff-da7e-e01bb57f7135",
|
||||
"index": 0,
|
||||
"name": "Cube",
|
||||
"stable_import_key": "",
|
||||
"type": "mesh"
|
||||
},
|
||||
{
|
||||
"guid": "20a8eca7-ae5b-8ed9-c7a8-b64b59567283",
|
||||
"index": 0,
|
||||
"name": "Cube_Material",
|
||||
"stable_import_key": "",
|
||||
"type": "material"
|
||||
}
|
||||
]
|
||||
|
||||
@ -10,12 +10,14 @@
|
||||
"guid": "804bca6d-b9ea-f7af-3f0a-f078d5de0d45",
|
||||
"index": 0,
|
||||
"name": "Plane",
|
||||
"stable_import_key": "",
|
||||
"type": "mesh"
|
||||
},
|
||||
{
|
||||
"guid": "6c337003-dc9a-da7c-d685-6d27c673046a",
|
||||
"index": 0,
|
||||
"name": "Plane_Material",
|
||||
"stable_import_key": "",
|
||||
"type": "material"
|
||||
}
|
||||
]
|
||||
|
||||
@ -10,12 +10,14 @@
|
||||
"guid": "34a6ed73-d4a5-83a3-5863-742c204c4869",
|
||||
"index": 0,
|
||||
"name": "box1",
|
||||
"stable_import_key": "",
|
||||
"type": "mesh"
|
||||
},
|
||||
{
|
||||
"guid": "9833a707-d635-426e-5db2-4cb58fbb5d03",
|
||||
"index": 0,
|
||||
"name": "box1_Material",
|
||||
"stable_import_key": "",
|
||||
"type": "material"
|
||||
}
|
||||
]
|
||||
|
||||
@ -10,12 +10,14 @@
|
||||
"guid": "50f3a009-11f7-c225-e32c-79f879faf21e",
|
||||
"index": 0,
|
||||
"name": "jyc",
|
||||
"stable_import_key": "",
|
||||
"type": "mesh"
|
||||
},
|
||||
{
|
||||
"guid": "64cad172-c4ea-929e-1c10-8a6af41df213",
|
||||
"index": 0,
|
||||
"name": "jyc_Material",
|
||||
"stable_import_key": "",
|
||||
"type": "material"
|
||||
}
|
||||
]
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@ -11,6 +11,8 @@
|
||||
#include "MetaCoreFoundation/MetaCoreProject.h"
|
||||
#include "MetaCorePlatform/MetaCoreInput.h"
|
||||
#include "MetaCoreFoundation/MetaCoreAssetRegistry.h"
|
||||
#include "MetaCoreFoundation/MetaCoreAssetDependencyGraph.h"
|
||||
#include "MetaCoreFoundation/MetaCoreRuntimeAssetRegistry.h"
|
||||
#include "MetaCoreScene/MetaCoreScenePackage.h"
|
||||
#include "MetaCoreScene/MetaCoreSceneSerializer.h"
|
||||
#include <nlohmann/json.hpp>
|
||||
@ -28,13 +30,18 @@
|
||||
#include "stb_dxt.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <optional>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <regex>
|
||||
#include <sstream>
|
||||
#include <type_traits>
|
||||
@ -48,6 +55,39 @@ namespace MetaCore {
|
||||
|
||||
namespace {
|
||||
|
||||
void MetaCoreWriteGuidArray(nlohmann::json& json, const char* key, const std::vector<MetaCoreAssetGuid>& guids) {
|
||||
if (guids.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto values = nlohmann::json::array();
|
||||
for (const MetaCoreAssetGuid& guid : guids) {
|
||||
if (guid.IsValid()) {
|
||||
values.push_back(guid.ToString());
|
||||
}
|
||||
}
|
||||
if (!values.empty()) {
|
||||
json[key] = std::move(values);
|
||||
}
|
||||
}
|
||||
|
||||
void MetaCoreReadGuidArray(const nlohmann::json& json, const char* key, std::vector<MetaCoreAssetGuid>& outGuids) {
|
||||
outGuids.clear();
|
||||
if (!json.contains(key) || !json[key].is_array()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& value : json[key]) {
|
||||
if (!value.is_string()) {
|
||||
continue;
|
||||
}
|
||||
const auto guid = MetaCoreAssetGuid::Parse(value.get<std::string>());
|
||||
if (guid.has_value() && guid->IsValid()) {
|
||||
outGuids.push_back(*guid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool MetaCoreWriteMetaJson(
|
||||
const std::filesystem::path& absoluteMetaPath,
|
||||
const MetaCoreAssetMetadataDocument& metadata
|
||||
@ -70,10 +110,13 @@ bool MetaCoreWriteMetaJson(
|
||||
subAssetJson["type"] = subAsset.Type;
|
||||
subAssetJson["name"] = subAsset.Name;
|
||||
subAssetJson["index"] = subAsset.Index;
|
||||
subAssetJson["stable_import_key"] = subAsset.StableImportKey;
|
||||
MetaCoreWriteGuidArray(subAssetJson, "dependencies", subAsset.Dependencies);
|
||||
subAssetsJson.push_back(subAssetJson);
|
||||
}
|
||||
json["sub_assets"] = subAssetsJson;
|
||||
}
|
||||
MetaCoreWriteGuidArray(json, "dependencies", metadata.Dependencies);
|
||||
|
||||
// 将 JSON 漂亮格式化并写入文件
|
||||
std::ofstream output(absoluteMetaPath);
|
||||
@ -139,11 +182,16 @@ bool MetaCoreReadMetaJson(
|
||||
if (subAssetJson.contains("index") && subAssetJson["index"].is_number_integer()) {
|
||||
subAsset.Index = subAssetJson["index"].get<std::int32_t>();
|
||||
}
|
||||
if (subAssetJson.contains("stable_import_key") && subAssetJson["stable_import_key"].is_string()) {
|
||||
subAsset.StableImportKey = subAssetJson["stable_import_key"].get<std::string>();
|
||||
}
|
||||
MetaCoreReadGuidArray(subAssetJson, "dependencies", subAsset.Dependencies);
|
||||
if (subAsset.Guid.IsValid()) {
|
||||
outMetadata.SubAssets.push_back(subAsset);
|
||||
}
|
||||
}
|
||||
}
|
||||
MetaCoreReadGuidArray(json, "dependencies", outMetadata.Dependencies);
|
||||
return true;
|
||||
} catch (...) {
|
||||
return false;
|
||||
@ -3069,6 +3117,340 @@ void MetaCoreWriteEmbeddedTextureRuntimePackages(
|
||||
return "asset";
|
||||
}
|
||||
|
||||
void MetaCoreAppendUniqueGuid(std::vector<MetaCoreAssetGuid>& guids, const MetaCoreAssetGuid& guid) {
|
||||
if (guid.IsValid() && std::find(guids.begin(), guids.end(), guid) == guids.end()) {
|
||||
guids.push_back(guid);
|
||||
}
|
||||
}
|
||||
|
||||
void MetaCoreCollectJsonGuidReferences(const nlohmann::json& json, std::vector<MetaCoreAssetGuid>& outGuids) {
|
||||
if (json.is_string()) {
|
||||
const auto guid = MetaCoreAssetGuid::Parse(json.get<std::string>());
|
||||
if (guid.has_value()) {
|
||||
MetaCoreAppendUniqueGuid(outGuids, *guid);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (json.is_array()) {
|
||||
for (const auto& value : json) {
|
||||
MetaCoreCollectJsonGuidReferences(value, outGuids);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (json.is_object()) {
|
||||
for (const auto& [key, value] : json.items()) {
|
||||
(void)key;
|
||||
MetaCoreCollectJsonGuidReferences(value, outGuids);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] std::vector<MetaCoreAssetGuid> MetaCoreReadJsonGuidDependencies(
|
||||
const std::filesystem::path& absolutePath,
|
||||
const MetaCoreAssetGuid& selfGuid = {}
|
||||
) {
|
||||
std::vector<MetaCoreAssetGuid> dependencies;
|
||||
std::ifstream input(absolutePath);
|
||||
if (!input.is_open()) {
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
try {
|
||||
nlohmann::json json;
|
||||
input >> json;
|
||||
MetaCoreCollectJsonGuidReferences(json, dependencies);
|
||||
} catch (...) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::erase(dependencies, selfGuid);
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::filesystem::path MetaCoreRemapRelativePath(
|
||||
const std::filesystem::path& value,
|
||||
const std::filesystem::path& oldPath,
|
||||
const std::filesystem::path& newPath
|
||||
) {
|
||||
const std::filesystem::path normalizedValue = value.lexically_normal();
|
||||
const std::filesystem::path normalizedOldPath = oldPath.lexically_normal();
|
||||
if (normalizedValue == normalizedOldPath) {
|
||||
return newPath.lexically_normal();
|
||||
}
|
||||
|
||||
const std::string valueString = normalizedValue.generic_string();
|
||||
const std::string oldPrefix = normalizedOldPath.generic_string() + "/";
|
||||
if (valueString.rfind(oldPrefix, 0) == 0) {
|
||||
return (newPath / normalizedValue.lexically_relative(normalizedOldPath)).lexically_normal();
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
void MetaCoreUpdateMovedMetadata(
|
||||
const std::filesystem::path& projectRoot,
|
||||
const std::filesystem::path& oldRelativePath,
|
||||
const std::filesystem::path& newRelativePath
|
||||
) {
|
||||
const std::filesystem::path absoluteNewPath = projectRoot / newRelativePath;
|
||||
std::vector<std::filesystem::path> metaPaths;
|
||||
const std::filesystem::path sidecarPath = MetaCoreBuildMetaPath(absoluteNewPath);
|
||||
if (std::filesystem::exists(sidecarPath)) {
|
||||
metaPaths.push_back(sidecarPath);
|
||||
}
|
||||
if (std::filesystem::is_directory(absoluteNewPath)) {
|
||||
for (const auto& entry : std::filesystem::recursive_directory_iterator(absoluteNewPath)) {
|
||||
if (entry.is_regular_file() && MetaCoreIsMetaPath(entry.path())) {
|
||||
metaPaths.push_back(entry.path());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const std::filesystem::path& metaPath : metaPaths) {
|
||||
MetaCoreAssetMetadataDocument metadata;
|
||||
if (!MetaCoreReadMetaJson(metaPath, metadata)) {
|
||||
continue;
|
||||
}
|
||||
metadata.SourcePath = MetaCoreRemapRelativePath(metadata.SourcePath, oldRelativePath, newRelativePath);
|
||||
metadata.PackagePath = MetaCoreRemapRelativePath(metadata.PackagePath, oldRelativePath, newRelativePath);
|
||||
(void)MetaCoreWriteMetaJson(metaPath, metadata);
|
||||
}
|
||||
}
|
||||
|
||||
void MetaCoreMoveSidecarIfPresent(
|
||||
const std::filesystem::path& projectRoot,
|
||||
const std::filesystem::path& oldRelativePath,
|
||||
const std::filesystem::path& newRelativePath
|
||||
) {
|
||||
std::error_code errorCode;
|
||||
const std::filesystem::path oldMetaPath = projectRoot / MetaCoreBuildMetaPath(oldRelativePath);
|
||||
const std::filesystem::path newMetaPath = projectRoot / MetaCoreBuildMetaPath(newRelativePath);
|
||||
if (std::filesystem::exists(oldMetaPath) && !std::filesystem::exists(newMetaPath)) {
|
||||
std::filesystem::rename(oldMetaPath, newMetaPath, errorCode);
|
||||
}
|
||||
|
||||
const std::filesystem::path oldPackagePath = projectRoot / MetaCoreBuildPackagePathFromSource(oldRelativePath);
|
||||
const std::filesystem::path newPackagePath = projectRoot / MetaCoreBuildPackagePathFromSource(newRelativePath);
|
||||
if (std::filesystem::exists(oldPackagePath) && !std::filesystem::exists(newPackagePath)) {
|
||||
errorCode.clear();
|
||||
std::filesystem::rename(oldPackagePath, newPackagePath, errorCode);
|
||||
}
|
||||
}
|
||||
|
||||
void MetaCoreApplyStableSubAssetGuids(
|
||||
const std::vector<MetaCoreSubAssetMetadata>& previousSubAssets,
|
||||
MetaCoreModelAssetDocument& document
|
||||
) {
|
||||
std::unordered_map<std::string, MetaCoreAssetGuid> previousGuids;
|
||||
for (const MetaCoreSubAssetMetadata& subAsset : previousSubAssets) {
|
||||
if (subAsset.Guid.IsValid() && !subAsset.StableImportKey.empty()) {
|
||||
previousGuids[subAsset.Type + "|" + subAsset.StableImportKey] = subAsset.Guid;
|
||||
}
|
||||
}
|
||||
|
||||
std::unordered_map<MetaCoreAssetGuid, MetaCoreAssetGuid, MetaCoreAssetGuidHasher> remappedGuids;
|
||||
const auto reuseGuids = [&](auto& generatedAssets, std::string_view type) {
|
||||
for (auto& generatedAsset : generatedAssets) {
|
||||
const auto previous = previousGuids.find(std::string(type) + "|" + generatedAsset.StableImportKey);
|
||||
if (previous == previousGuids.end() || previous->second == generatedAsset.AssetGuid) {
|
||||
continue;
|
||||
}
|
||||
remappedGuids[generatedAsset.AssetGuid] = previous->second;
|
||||
generatedAsset.AssetGuid = previous->second;
|
||||
}
|
||||
};
|
||||
|
||||
reuseGuids(document.GeneratedTextureAssets, "texture");
|
||||
reuseGuids(document.GeneratedMaterialAssets, "material");
|
||||
reuseGuids(document.GeneratedMeshAssets, "mesh");
|
||||
|
||||
const auto remapGuid = [&](MetaCoreAssetGuid& guid) {
|
||||
if (const auto iterator = remappedGuids.find(guid); iterator != remappedGuids.end()) {
|
||||
guid = iterator->second;
|
||||
}
|
||||
};
|
||||
for (MetaCoreMaterialAssetDocument& material : document.GeneratedMaterialAssets) {
|
||||
remapGuid(material.BaseColorTexture);
|
||||
remapGuid(material.NormalTexture);
|
||||
remapGuid(material.MetallicRoughnessTexture);
|
||||
remapGuid(material.AoTexture);
|
||||
remapGuid(material.EmissiveTexture);
|
||||
}
|
||||
}
|
||||
|
||||
void MetaCoreUpdateModelSubAssetMetadata(
|
||||
const MetaCoreModelAssetDocument& document,
|
||||
MetaCoreAssetMetadataDocument& metadata
|
||||
) {
|
||||
metadata.SubAssets.clear();
|
||||
metadata.Dependencies.clear();
|
||||
const auto addSubAsset = [&](const auto& generatedAsset, std::string type, std::int32_t index) {
|
||||
if (!generatedAsset.AssetGuid.IsValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
MetaCoreSubAssetMetadata subAsset;
|
||||
subAsset.Guid = generatedAsset.AssetGuid;
|
||||
subAsset.Type = std::move(type);
|
||||
subAsset.Name = generatedAsset.Name;
|
||||
subAsset.Index = index;
|
||||
subAsset.StableImportKey = generatedAsset.StableImportKey;
|
||||
metadata.SubAssets.push_back(std::move(subAsset));
|
||||
MetaCoreAppendUniqueGuid(metadata.Dependencies, generatedAsset.AssetGuid);
|
||||
};
|
||||
|
||||
for (std::size_t index = 0; index < document.GeneratedMeshAssets.size(); ++index) {
|
||||
addSubAsset(document.GeneratedMeshAssets[index], "mesh", static_cast<std::int32_t>(index));
|
||||
}
|
||||
for (std::size_t index = 0; index < document.GeneratedMaterialAssets.size(); ++index) {
|
||||
if (!document.GeneratedMaterialAssets[index].AssetGuid.IsValid()) {
|
||||
continue;
|
||||
}
|
||||
addSubAsset(document.GeneratedMaterialAssets[index], "material", static_cast<std::int32_t>(index));
|
||||
auto& dependencies = metadata.SubAssets.back().Dependencies;
|
||||
const auto& material = document.GeneratedMaterialAssets[index];
|
||||
MetaCoreAppendUniqueGuid(dependencies, material.BaseColorTexture);
|
||||
MetaCoreAppendUniqueGuid(dependencies, material.NormalTexture);
|
||||
MetaCoreAppendUniqueGuid(dependencies, material.MetallicRoughnessTexture);
|
||||
MetaCoreAppendUniqueGuid(dependencies, material.AoTexture);
|
||||
MetaCoreAppendUniqueGuid(dependencies, material.EmissiveTexture);
|
||||
}
|
||||
for (std::size_t index = 0; index < document.GeneratedTextureAssets.size(); ++index) {
|
||||
addSubAsset(document.GeneratedTextureAssets[index], "texture", static_cast<std::int32_t>(index));
|
||||
}
|
||||
}
|
||||
|
||||
void MetaCoreRebuildAssetDependencyGraph(const MetaCoreProjectDescriptor& project) {
|
||||
auto& graph = MetaCoreAssetDependencyGraph::Get();
|
||||
graph.Clear();
|
||||
|
||||
const std::array roots{project.AssetsPath, project.ScenesPath};
|
||||
for (const std::filesystem::path& root : roots) {
|
||||
if (!std::filesystem::exists(root)) {
|
||||
continue;
|
||||
}
|
||||
for (const auto& entry : std::filesystem::recursive_directory_iterator(root)) {
|
||||
if (!entry.is_regular_file() || !MetaCoreIsMetaPath(entry.path())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
MetaCoreAssetMetadataDocument metadata;
|
||||
if (!MetaCoreReadMetaJson(entry.path(), metadata) || !metadata.AssetGuid.IsValid()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::vector<MetaCoreAssetGuid> dependencies = metadata.Dependencies;
|
||||
const std::filesystem::path absoluteSourcePath = project.RootPath / metadata.SourcePath;
|
||||
if (MetaCoreIsJsonSourceAssetPath(absoluteSourcePath) && std::filesystem::exists(absoluteSourcePath)) {
|
||||
for (const MetaCoreAssetGuid& dependency : MetaCoreReadJsonGuidDependencies(absoluteSourcePath, metadata.AssetGuid)) {
|
||||
MetaCoreAppendUniqueGuid(dependencies, dependency);
|
||||
}
|
||||
}
|
||||
|
||||
graph.SetDependencies(
|
||||
metadata.AssetGuid,
|
||||
MetaCoreAssetDependencyGraph::AssetGuidSet(dependencies.begin(), dependencies.end())
|
||||
);
|
||||
for (const MetaCoreSubAssetMetadata& subAsset : metadata.SubAssets) {
|
||||
graph.SetDependencies(
|
||||
subAsset.Guid,
|
||||
MetaCoreAssetDependencyGraph::AssetGuidSet(
|
||||
subAsset.Dependencies.begin(),
|
||||
subAsset.Dependencies.end()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MetaCoreQueueAssetChangeWithDependents(const MetaCoreAssetChangeEvent& rootEvent, bool propagate) {
|
||||
auto& runtimeRegistry = MetaCoreRuntimeAssetRegistry::Get();
|
||||
runtimeRegistry.QueueEvent(rootEvent);
|
||||
if (!propagate || !rootEvent.AssetGuid.IsValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<MetaCoreAssetGuid> pending{rootEvent.AssetGuid};
|
||||
std::unordered_set<MetaCoreAssetGuid, MetaCoreAssetGuidHasher> visited{rootEvent.AssetGuid};
|
||||
while (!pending.empty()) {
|
||||
const MetaCoreAssetGuid dependencyGuid = pending.back();
|
||||
pending.pop_back();
|
||||
for (const MetaCoreAssetGuid& dependentGuid : MetaCoreAssetDependencyGraph::Get().GetDependents(dependencyGuid)) {
|
||||
if (!visited.insert(dependentGuid).second) {
|
||||
continue;
|
||||
}
|
||||
MetaCoreAssetChangeEvent dependentEvent = rootEvent;
|
||||
dependentEvent.AssetGuid = dependentGuid;
|
||||
dependentEvent.SourceAssetGuid = rootEvent.AssetGuid;
|
||||
dependentEvent.OldRelativePath.clear();
|
||||
dependentEvent.NewRelativePath.clear();
|
||||
runtimeRegistry.QueueEvent(std::move(dependentEvent));
|
||||
pending.push_back(dependentGuid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MetaCorePublishAssetRecordChanges(
|
||||
const std::vector<MetaCoreAssetRecord>& previousRecords,
|
||||
const std::vector<MetaCoreAssetRecord>& currentRecords
|
||||
) {
|
||||
std::unordered_map<MetaCoreAssetGuid, MetaCoreAssetRecord, MetaCoreAssetGuidHasher> previousByGuid;
|
||||
std::unordered_map<MetaCoreAssetGuid, MetaCoreAssetRecord, MetaCoreAssetGuidHasher> currentByGuid;
|
||||
for (const MetaCoreAssetRecord& record : previousRecords) {
|
||||
if (record.Guid.IsValid()) {
|
||||
previousByGuid[record.Guid] = record;
|
||||
}
|
||||
}
|
||||
for (const MetaCoreAssetRecord& record : currentRecords) {
|
||||
if (record.Guid.IsValid()) {
|
||||
currentByGuid[record.Guid] = record;
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& [guid, previous] : previousByGuid) {
|
||||
const auto current = currentByGuid.find(guid);
|
||||
if (current == currentByGuid.end()) {
|
||||
MetaCoreQueueAssetChangeWithDependents(
|
||||
MetaCoreAssetChangeEvent{MetaCoreAssetChangeType::Deleted, guid, guid, previous.RelativePath, {}},
|
||||
true
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if (previous.RelativePath != current->second.RelativePath) {
|
||||
MetaCoreQueueAssetChangeWithDependents(
|
||||
MetaCoreAssetChangeEvent{
|
||||
MetaCoreAssetChangeType::Moved,
|
||||
guid,
|
||||
guid,
|
||||
previous.RelativePath,
|
||||
current->second.RelativePath
|
||||
},
|
||||
false
|
||||
);
|
||||
} else if (previous.SourceHash != current->second.SourceHash) {
|
||||
MetaCoreQueueAssetChangeWithDependents(
|
||||
MetaCoreAssetChangeEvent{
|
||||
MetaCoreAssetChangeType::Modified,
|
||||
guid,
|
||||
guid,
|
||||
previous.RelativePath,
|
||||
current->second.RelativePath
|
||||
},
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& [guid, current] : currentByGuid) {
|
||||
if (!previousByGuid.contains(guid)) {
|
||||
MetaCoreQueueAssetChangeWithDependents(
|
||||
MetaCoreAssetChangeEvent{MetaCoreAssetChangeType::Created, guid, guid, {}, current.RelativePath},
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] MetaCoreTextureAssetDocument MetaCoreBuildImportedTextureDocument(
|
||||
const std::filesystem::path& absoluteSourcePath,
|
||||
const std::filesystem::path& relativeSourcePath,
|
||||
@ -3405,6 +3787,7 @@ public:
|
||||
[[nodiscard]] bool RenamePath(const std::filesystem::path& relativePath, const std::filesystem::path& newName) override;
|
||||
[[nodiscard]] bool MovePath(const std::filesystem::path& relativePath, const std::filesystem::path& targetDirectory) override;
|
||||
[[nodiscard]] bool DeletePath(const std::filesystem::path& relativePath) override;
|
||||
bool ApplyExternalFileChanges(const std::vector<MetaCoreAssetChangeEvent>& changes) override;
|
||||
[[nodiscard]] const std::vector<MetaCoreAssetRecord>& GetAssetRegistry() const override { return AssetRecords_; }
|
||||
|
||||
[[nodiscard]] std::optional<MetaCoreAssetRecord> FindAssetByGuid(const MetaCoreAssetGuid& assetGuid) const override {
|
||||
@ -3466,6 +3849,7 @@ private:
|
||||
void SaveProjectDescriptor() const;
|
||||
void RefreshScenePathsFromDisk();
|
||||
void RefreshAssetRecordsFromDisk();
|
||||
void RescanHotReloadBaseline() const;
|
||||
|
||||
MetaCoreEditorModuleRegistry* ModuleRegistry_ = nullptr;
|
||||
MetaCoreProjectDescriptor Project_{};
|
||||
@ -3594,6 +3978,12 @@ bool MetaCoreBuiltinAssetDatabaseService::OpenProject(const std::filesystem::pat
|
||||
#endif
|
||||
LoadProjectDescriptor();
|
||||
const bool refreshed = Refresh();
|
||||
if (ModuleRegistry_ != nullptr) {
|
||||
if (const auto hotReloadService = ModuleRegistry_->ResolveService<MetaCoreIAssetHotReloadService>();
|
||||
hotReloadService != nullptr) {
|
||||
hotReloadService->SetProjectRoot(Project_.RootPath);
|
||||
}
|
||||
}
|
||||
if (ModuleRegistry_ != nullptr && refreshed && HasProject()) {
|
||||
ModuleRegistry_->AccessEventBus().Publish(MetaCoreEditorEvent{
|
||||
MetaCoreEditorEventType::ProjectLoaded,
|
||||
@ -3632,14 +4022,8 @@ bool MetaCoreBuiltinAssetDatabaseService::RenamePath(
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::filesystem::path legacyMetaSource = Project_.RootPath / MetaCoreBuildMetaPath(normalizedPath);
|
||||
const std::filesystem::path legacyMetaTarget = Project_.RootPath / MetaCoreBuildMetaPath(targetPath);
|
||||
if (std::filesystem::exists(legacyMetaSource) && !std::filesystem::exists(legacyMetaTarget)) {
|
||||
std::filesystem::rename(legacyMetaSource, legacyMetaTarget, errorCode);
|
||||
if (errorCode) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
MetaCoreMoveSidecarIfPresent(Project_.RootPath, normalizedPath, targetPath);
|
||||
MetaCoreUpdateMovedMetadata(Project_.RootPath, normalizedPath, targetPath);
|
||||
|
||||
const auto remapPath = [&](std::filesystem::path& pathValue) {
|
||||
if (pathValue == normalizedPath) {
|
||||
@ -3662,7 +4046,9 @@ bool MetaCoreBuiltinAssetDatabaseService::RenamePath(
|
||||
Project_.ScenePaths.erase(std::unique(Project_.ScenePaths.begin(), Project_.ScenePaths.end()), Project_.ScenePaths.end());
|
||||
SaveProjectDescriptor();
|
||||
|
||||
return Refresh();
|
||||
const bool refreshed = Refresh();
|
||||
RescanHotReloadBaseline();
|
||||
return refreshed;
|
||||
}
|
||||
|
||||
bool MetaCoreBuiltinAssetDatabaseService::MovePath(
|
||||
@ -3713,14 +4099,8 @@ bool MetaCoreBuiltinAssetDatabaseService::MovePath(
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::filesystem::path legacyMetaSource = Project_.RootPath / MetaCoreBuildMetaPath(normalizedSourcePath);
|
||||
const std::filesystem::path legacyMetaTarget = Project_.RootPath / MetaCoreBuildMetaPath(targetPath);
|
||||
if (std::filesystem::exists(legacyMetaSource) && !std::filesystem::exists(legacyMetaTarget)) {
|
||||
std::filesystem::rename(legacyMetaSource, legacyMetaTarget, errorCode);
|
||||
if (errorCode) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
MetaCoreMoveSidecarIfPresent(Project_.RootPath, normalizedSourcePath, targetPath);
|
||||
MetaCoreUpdateMovedMetadata(Project_.RootPath, normalizedSourcePath, targetPath);
|
||||
|
||||
remapPath(Project_.StartupScenePath);
|
||||
for (auto& scenePath : Project_.ScenePaths) {
|
||||
@ -3729,7 +4109,9 @@ bool MetaCoreBuiltinAssetDatabaseService::MovePath(
|
||||
std::sort(Project_.ScenePaths.begin(), Project_.ScenePaths.end());
|
||||
Project_.ScenePaths.erase(std::unique(Project_.ScenePaths.begin(), Project_.ScenePaths.end()), Project_.ScenePaths.end());
|
||||
SaveProjectDescriptor();
|
||||
return Refresh();
|
||||
const bool refreshed = Refresh();
|
||||
RescanHotReloadBaseline();
|
||||
return refreshed;
|
||||
}
|
||||
|
||||
bool MetaCoreBuiltinAssetDatabaseService::DeletePath(const std::filesystem::path& relativePath) {
|
||||
@ -3778,9 +4160,44 @@ bool MetaCoreBuiltinAssetDatabaseService::DeletePath(const std::filesystem::path
|
||||
);
|
||||
|
||||
SaveProjectDescriptor();
|
||||
const bool refreshed = Refresh();
|
||||
RescanHotReloadBaseline();
|
||||
return refreshed;
|
||||
}
|
||||
|
||||
bool MetaCoreBuiltinAssetDatabaseService::ApplyExternalFileChanges(
|
||||
const std::vector<MetaCoreAssetChangeEvent>& changes
|
||||
) {
|
||||
if (!HasProject() || changes.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const MetaCoreAssetChangeEvent& change : changes) {
|
||||
if (change.Type == MetaCoreAssetChangeType::Moved &&
|
||||
!change.OldRelativePath.empty() &&
|
||||
!change.NewRelativePath.empty()) {
|
||||
MetaCoreMoveSidecarIfPresent(Project_.RootPath, change.OldRelativePath, change.NewRelativePath);
|
||||
MetaCoreUpdateMovedMetadata(Project_.RootPath, change.OldRelativePath, change.NewRelativePath);
|
||||
} else if (change.Type == MetaCoreAssetChangeType::Deleted && !change.OldRelativePath.empty()) {
|
||||
std::error_code errorCode;
|
||||
std::filesystem::remove(Project_.RootPath / MetaCoreBuildMetaPath(change.OldRelativePath), errorCode);
|
||||
errorCode.clear();
|
||||
std::filesystem::remove(Project_.RootPath / MetaCoreBuildPackagePathFromSource(change.OldRelativePath), errorCode);
|
||||
}
|
||||
}
|
||||
return Refresh();
|
||||
}
|
||||
|
||||
void MetaCoreBuiltinAssetDatabaseService::RescanHotReloadBaseline() const {
|
||||
if (ModuleRegistry_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (const auto hotReloadService = ModuleRegistry_->ResolveService<MetaCoreIAssetHotReloadService>();
|
||||
hotReloadService != nullptr) {
|
||||
hotReloadService->RescanBaseline();
|
||||
}
|
||||
}
|
||||
|
||||
void MetaCoreBuiltinAssetDatabaseService::SaveProjectDescriptor() const {
|
||||
if (!HasProject()) {
|
||||
return;
|
||||
@ -3991,6 +4408,13 @@ bool MetaCoreBuiltinAssetDatabaseService::Refresh() {
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::vector<MetaCoreAssetRecord> previousRecords = AssetRecords_;
|
||||
|
||||
// Pass 1: make all existing GUID/path mappings available before importers
|
||||
// scan references. A second scan below picks up newly-created metadata.
|
||||
MetaCoreAssetRegistry::Get().Clear();
|
||||
MetaCoreAssetRegistry::Get().ScanDirectory(Project_.AssetsPath);
|
||||
|
||||
if (ModuleRegistry_ != nullptr) {
|
||||
if (const auto importPipeline = ModuleRegistry_->ResolveService<MetaCoreIImportPipelineService>();
|
||||
importPipeline != nullptr) {
|
||||
@ -4004,6 +4428,8 @@ bool MetaCoreBuiltinAssetDatabaseService::Refresh() {
|
||||
// 扫描并重置全局资产 GUID 注册表 (对标 Unity 升级)
|
||||
MetaCoreAssetRegistry::Get().Clear();
|
||||
MetaCoreAssetRegistry::Get().ScanDirectory(Project_.AssetsPath);
|
||||
MetaCorePublishAssetRecordChanges(previousRecords, AssetRecords_);
|
||||
MetaCoreRebuildAssetDependencyGraph(Project_);
|
||||
|
||||
if (ModuleRegistry_ != nullptr) {
|
||||
if (const auto cookService = ModuleRegistry_->ResolveService<MetaCoreICookService>(); cookService != nullptr) {
|
||||
@ -4690,9 +5116,13 @@ bool MetaCoreBuiltinImportPipelineService::ImportSourceFile(const std::filesyste
|
||||
const std::filesystem::path absoluteMetaPath = MetaCoreBuildMetaPath(absoluteSourcePath);
|
||||
|
||||
MetaCoreAssetMetadataDocument metadata;
|
||||
bool hasMetadata = false;
|
||||
if (std::filesystem::exists(absoluteMetaPath)) {
|
||||
(void)MetaCoreReadMetaJson(absoluteMetaPath, metadata);
|
||||
hasMetadata = MetaCoreReadMetaJson(absoluteMetaPath, metadata);
|
||||
}
|
||||
const std::uint64_t previousSourceHash = metadata.SourceHash;
|
||||
const std::string previousImporterId = metadata.ImporterId;
|
||||
const std::vector<MetaCoreSubAssetMetadata> previousSubAssets = metadata.SubAssets;
|
||||
|
||||
if (!metadata.AssetGuid.IsValid()) {
|
||||
metadata.AssetGuid = MetaCoreAssetGuid::Generate();
|
||||
@ -4703,17 +5133,20 @@ bool MetaCoreBuiltinImportPipelineService::ImportSourceFile(const std::filesyste
|
||||
metadata.SourcePath = relativeSourcePath;
|
||||
metadata.PackagePath = relativePackagePath;
|
||||
metadata.SourceHash = *sourceHash;
|
||||
if (jsonSourceAsset) {
|
||||
metadata.Dependencies = MetaCoreReadJsonGuidDependencies(absoluteSourcePath, metadata.AssetGuid);
|
||||
}
|
||||
|
||||
const MetaCoreModelImportSettingsDocument existingModelImportSettings =
|
||||
(metadata.AssetType == "model")
|
||||
? MetaCoreReadExistingModelImportSettings(*PackageService_, registry, absolutePackagePath)
|
||||
: MetaCoreBuildDefaultModelImportSettings();
|
||||
|
||||
bool needsPackageWrite = forcePackageWrite || !std::filesystem::exists(absolutePackagePath);
|
||||
if (!needsPackageWrite) {
|
||||
const auto package = PackageService_->ReadPackage(absolutePackagePath);
|
||||
needsPackageWrite = !package.has_value() || package->Header.SourceHash != *sourceHash;
|
||||
}
|
||||
const bool needsPackageWrite =
|
||||
forcePackageWrite ||
|
||||
!hasMetadata ||
|
||||
previousSourceHash != *sourceHash ||
|
||||
previousImporterId != metadata.ImporterId;
|
||||
|
||||
if (needsPackageWrite) {
|
||||
std::optional<std::vector<std::byte>> importedAssetBytes;
|
||||
@ -4728,42 +5161,8 @@ bool MetaCoreBuiltinImportPipelineService::ImportSourceFile(const std::filesyste
|
||||
meshPayloads,
|
||||
existingModelImportSettings
|
||||
);
|
||||
|
||||
// 收集生成的子资产信息到元数据中,以便落盘和注册表在下次启动时扫描解析
|
||||
metadata.SubAssets.clear();
|
||||
for (std::size_t index = 0; index < importedAsset.GeneratedMeshAssets.size(); ++index) {
|
||||
const auto& mesh = importedAsset.GeneratedMeshAssets[index];
|
||||
if (mesh.AssetGuid.IsValid()) {
|
||||
MetaCoreSubAssetMetadata sub;
|
||||
sub.Guid = mesh.AssetGuid;
|
||||
sub.Type = "mesh";
|
||||
sub.Name = mesh.Name;
|
||||
sub.Index = static_cast<std::int32_t>(index);
|
||||
metadata.SubAssets.push_back(sub);
|
||||
}
|
||||
}
|
||||
for (std::size_t index = 0; index < importedAsset.GeneratedMaterialAssets.size(); ++index) {
|
||||
const auto& mat = importedAsset.GeneratedMaterialAssets[index];
|
||||
if (mat.AssetGuid.IsValid()) {
|
||||
MetaCoreSubAssetMetadata sub;
|
||||
sub.Guid = mat.AssetGuid;
|
||||
sub.Type = "material";
|
||||
sub.Name = mat.Name;
|
||||
sub.Index = static_cast<std::int32_t>(index);
|
||||
metadata.SubAssets.push_back(sub);
|
||||
}
|
||||
}
|
||||
for (std::size_t index = 0; index < importedAsset.GeneratedTextureAssets.size(); ++index) {
|
||||
const auto& tex = importedAsset.GeneratedTextureAssets[index];
|
||||
if (tex.AssetGuid.IsValid()) {
|
||||
MetaCoreSubAssetMetadata sub;
|
||||
sub.Guid = tex.AssetGuid;
|
||||
sub.Type = "texture";
|
||||
sub.Name = tex.Name;
|
||||
sub.Index = static_cast<std::int32_t>(index);
|
||||
metadata.SubAssets.push_back(sub);
|
||||
}
|
||||
}
|
||||
MetaCoreApplyStableSubAssetGuids(previousSubAssets, importedAsset);
|
||||
MetaCoreUpdateModelSubAssetMetadata(importedAsset, metadata);
|
||||
|
||||
importedAssetBytes = MetaCoreSerializeToBytes(importedAsset, registry);
|
||||
importedAssetTypeName = "MetaCoreModelAssetDocument";
|
||||
@ -4861,6 +5260,8 @@ bool MetaCoreBuiltinImportPipelineService::ImportSourceFile(const std::filesyste
|
||||
metadata.SourceHash,
|
||||
existingModelImportSettings
|
||||
);
|
||||
MetaCoreApplyStableSubAssetGuids(previousSubAssets, importedAsset);
|
||||
MetaCoreUpdateModelSubAssetMetadata(importedAsset, metadata);
|
||||
importedAssetBytes = MetaCoreSerializeToBytes(importedAsset, registry);
|
||||
importedAssetTypeName = "MetaCoreModelAssetDocument";
|
||||
} else {
|
||||
@ -4903,6 +5304,10 @@ bool MetaCoreBuiltinImportPipelineService::ImportSourceFile(const std::filesyste
|
||||
}
|
||||
}
|
||||
|
||||
if (!MetaCoreWriteMetaJson(absoluteMetaPath, metadata)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (CookService_ != nullptr) {
|
||||
(void)CookService_->CookAsset(metadata.AssetGuid);
|
||||
}
|
||||
@ -6235,6 +6640,312 @@ bool MetaCoreBuiltinSceneEditingService::ReparentSelection(MetaCoreEditorContext
|
||||
return reparented;
|
||||
}
|
||||
|
||||
class MetaCoreBuiltinAssetHotReloadService final : public MetaCoreIAssetHotReloadService {
|
||||
public:
|
||||
~MetaCoreBuiltinAssetHotReloadService() override { StopWorker(); }
|
||||
|
||||
[[nodiscard]] std::string GetServiceId() const override { return "MetaCore.AssetHotReload"; }
|
||||
|
||||
void Startup(MetaCoreEditorModuleRegistry& moduleRegistry) override {
|
||||
ModuleRegistry_ = &moduleRegistry;
|
||||
AssetDatabaseService_ = moduleRegistry.ResolveService<MetaCoreIAssetDatabaseService>();
|
||||
if (AssetDatabaseService_ != nullptr && AssetDatabaseService_->HasProject()) {
|
||||
SetProjectRoot(AssetDatabaseService_->GetProjectDescriptor().RootPath);
|
||||
}
|
||||
}
|
||||
|
||||
void Shutdown(MetaCoreEditorModuleRegistry& moduleRegistry) override {
|
||||
(void)moduleRegistry;
|
||||
StopWorker();
|
||||
AssetDatabaseService_.reset();
|
||||
ModuleRegistry_ = nullptr;
|
||||
}
|
||||
|
||||
void Tick() override {
|
||||
std::vector<QueuedChange> queuedChanges;
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
{
|
||||
std::scoped_lock lock(Mutex_);
|
||||
for (auto iterator = QueuedChanges_.begin(); iterator != QueuedChanges_.end();) {
|
||||
if (iterator->NextAttemptAt <= now) {
|
||||
queuedChanges.push_back(std::move(*iterator));
|
||||
iterator = QueuedChanges_.erase(iterator);
|
||||
} else {
|
||||
++iterator;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!queuedChanges.empty() && AssetDatabaseService_ != nullptr) {
|
||||
std::vector<MetaCoreAssetChangeEvent> changes;
|
||||
changes.reserve(queuedChanges.size());
|
||||
for (const QueuedChange& queuedChange : queuedChanges) {
|
||||
changes.push_back(queuedChange.Event);
|
||||
const std::filesystem::path scenePath =
|
||||
queuedChange.Event.NewRelativePath.empty()
|
||||
? queuedChange.Event.OldRelativePath
|
||||
: queuedChange.Event.NewRelativePath;
|
||||
if (ModuleRegistry_ != nullptr && MetaCoreIsScenePath(scenePath)) {
|
||||
ModuleRegistry_->AccessEventBus().Publish(MetaCoreEditorEvent{
|
||||
MetaCoreEditorEventType::ExternalSceneChanged,
|
||||
"场景文件已在外部修改,索引已刷新;当前打开场景未自动覆盖",
|
||||
scenePath,
|
||||
0,
|
||||
true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!AssetDatabaseService_->ApplyExternalFileChanges(changes)) {
|
||||
std::scoped_lock lock(Mutex_);
|
||||
for (QueuedChange& queuedChange : queuedChanges) {
|
||||
if (queuedChange.RetryCount < 3) {
|
||||
++queuedChange.RetryCount;
|
||||
queuedChange.NextAttemptAt = now + std::chrono::milliseconds(250);
|
||||
QueuedChanges_.push_back(std::move(queuedChange));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MetaCoreRuntimeAssetRegistry::Get().DispatchQueuedEvents();
|
||||
}
|
||||
|
||||
void SetProjectRoot(const std::filesystem::path& projectRoot) override {
|
||||
{
|
||||
std::scoped_lock lock(Mutex_);
|
||||
ProjectRoot_ = projectRoot.lexically_normal();
|
||||
}
|
||||
RescanBaseline();
|
||||
StartWorker();
|
||||
}
|
||||
|
||||
void RescanBaseline() override {
|
||||
const SnapshotMap snapshot = BuildSnapshot();
|
||||
std::scoped_lock lock(Mutex_);
|
||||
Baseline_ = snapshot;
|
||||
PendingCandidates_.clear();
|
||||
QueuedChanges_.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
struct FileSnapshot {
|
||||
std::uintmax_t Size = 0;
|
||||
std::uint64_t Hash = 0;
|
||||
|
||||
bool operator==(const FileSnapshot&) const = default;
|
||||
};
|
||||
|
||||
struct PendingCandidate {
|
||||
MetaCoreAssetChangeEvent Event{};
|
||||
FileSnapshot Snapshot{};
|
||||
std::chrono::steady_clock::time_point FirstSeen{};
|
||||
};
|
||||
|
||||
struct QueuedChange {
|
||||
MetaCoreAssetChangeEvent Event{};
|
||||
int RetryCount = 0;
|
||||
std::chrono::steady_clock::time_point NextAttemptAt{};
|
||||
};
|
||||
|
||||
using SnapshotMap = std::unordered_map<std::string, FileSnapshot>;
|
||||
|
||||
[[nodiscard]] static bool ShouldIgnoreFile(const std::filesystem::path& path) {
|
||||
const std::string extension = path.extension().string();
|
||||
const std::string filename = path.filename().string();
|
||||
return extension == ".mcmeta" ||
|
||||
extension == ".meta" ||
|
||||
extension == ".mcasset" ||
|
||||
extension == ".tmp" ||
|
||||
(!filename.empty() && (filename.back() == '~' || filename.front() == '.'));
|
||||
}
|
||||
|
||||
[[nodiscard]] SnapshotMap BuildSnapshot() const {
|
||||
std::filesystem::path projectRoot;
|
||||
{
|
||||
std::scoped_lock lock(Mutex_);
|
||||
projectRoot = ProjectRoot_;
|
||||
}
|
||||
|
||||
SnapshotMap snapshot;
|
||||
for (const std::filesystem::path& root : std::array{
|
||||
projectRoot / "Assets",
|
||||
projectRoot / "Scenes"
|
||||
}) {
|
||||
if (!std::filesystem::exists(root)) {
|
||||
continue;
|
||||
}
|
||||
for (const auto& entry : std::filesystem::recursive_directory_iterator(root)) {
|
||||
if (!entry.is_regular_file() || ShouldIgnoreFile(entry.path())) {
|
||||
continue;
|
||||
}
|
||||
const auto hash = MetaCoreHashFile(entry.path());
|
||||
if (!hash.has_value()) {
|
||||
continue;
|
||||
}
|
||||
snapshot[entry.path().lexically_relative(projectRoot).generic_string()] = FileSnapshot{
|
||||
entry.file_size(),
|
||||
*hash
|
||||
};
|
||||
}
|
||||
}
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
[[nodiscard]] static std::string BuildCandidateKey(const MetaCoreAssetChangeEvent& event) {
|
||||
return std::to_string(static_cast<int>(event.Type)) + "|" +
|
||||
event.OldRelativePath.generic_string() + "|" +
|
||||
event.NewRelativePath.generic_string();
|
||||
}
|
||||
|
||||
[[nodiscard]] static bool IsCandidateCurrent(
|
||||
const PendingCandidate& candidate,
|
||||
const SnapshotMap& snapshot
|
||||
) {
|
||||
const std::string oldPath = candidate.Event.OldRelativePath.generic_string();
|
||||
const std::string newPath = candidate.Event.NewRelativePath.generic_string();
|
||||
if (candidate.Event.Type == MetaCoreAssetChangeType::Deleted) {
|
||||
return !snapshot.contains(oldPath);
|
||||
}
|
||||
if (candidate.Event.Type == MetaCoreAssetChangeType::Moved) {
|
||||
const auto iterator = snapshot.find(newPath);
|
||||
return !snapshot.contains(oldPath) && iterator != snapshot.end() && iterator->second == candidate.Snapshot;
|
||||
}
|
||||
const auto iterator = snapshot.find(newPath);
|
||||
return iterator != snapshot.end() && iterator->second == candidate.Snapshot;
|
||||
}
|
||||
|
||||
void PollOnce() {
|
||||
const SnapshotMap current = BuildSnapshot();
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
|
||||
std::scoped_lock lock(Mutex_);
|
||||
std::vector<std::pair<std::string, FileSnapshot>> created;
|
||||
std::vector<std::pair<std::string, FileSnapshot>> deleted;
|
||||
for (const auto& [path, file] : current) {
|
||||
const auto previous = Baseline_.find(path);
|
||||
if (previous == Baseline_.end()) {
|
||||
created.push_back({path, file});
|
||||
} else if (!(previous->second == file)) {
|
||||
AddCandidate(
|
||||
MetaCoreAssetChangeEvent{MetaCoreAssetChangeType::Modified, {}, {}, path, path},
|
||||
file,
|
||||
now
|
||||
);
|
||||
}
|
||||
}
|
||||
for (const auto& [path, file] : Baseline_) {
|
||||
if (!current.contains(path)) {
|
||||
deleted.push_back({path, file});
|
||||
}
|
||||
}
|
||||
|
||||
std::unordered_set<std::size_t> pairedCreates;
|
||||
for (const auto& [oldPath, oldFile] : deleted) {
|
||||
bool paired = false;
|
||||
for (std::size_t index = 0; index < created.size(); ++index) {
|
||||
if (!pairedCreates.contains(index) && created[index].second == oldFile) {
|
||||
pairedCreates.insert(index);
|
||||
AddCandidate(
|
||||
MetaCoreAssetChangeEvent{
|
||||
MetaCoreAssetChangeType::Moved,
|
||||
{},
|
||||
{},
|
||||
oldPath,
|
||||
created[index].first
|
||||
},
|
||||
created[index].second,
|
||||
now
|
||||
);
|
||||
paired = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!paired) {
|
||||
AddCandidate(
|
||||
MetaCoreAssetChangeEvent{MetaCoreAssetChangeType::Deleted, {}, {}, oldPath, {}},
|
||||
oldFile,
|
||||
now
|
||||
);
|
||||
}
|
||||
}
|
||||
for (std::size_t index = 0; index < created.size(); ++index) {
|
||||
if (!pairedCreates.contains(index)) {
|
||||
AddCandidate(
|
||||
MetaCoreAssetChangeEvent{MetaCoreAssetChangeType::Created, {}, {}, {}, created[index].first},
|
||||
created[index].second,
|
||||
now
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Baseline_ = current;
|
||||
for (auto iterator = PendingCandidates_.begin(); iterator != PendingCandidates_.end();) {
|
||||
if (!IsCandidateCurrent(iterator->second, current)) {
|
||||
iterator = PendingCandidates_.erase(iterator);
|
||||
continue;
|
||||
}
|
||||
if (now - iterator->second.FirstSeen >= std::chrono::milliseconds(250)) {
|
||||
QueuedChanges_.push_back(QueuedChange{iterator->second.Event, 0, now});
|
||||
iterator = PendingCandidates_.erase(iterator);
|
||||
continue;
|
||||
}
|
||||
++iterator;
|
||||
}
|
||||
}
|
||||
|
||||
void AddCandidate(
|
||||
MetaCoreAssetChangeEvent event,
|
||||
const FileSnapshot& snapshot,
|
||||
std::chrono::steady_clock::time_point now
|
||||
) {
|
||||
const std::string key = BuildCandidateKey(event);
|
||||
const auto iterator = PendingCandidates_.find(key);
|
||||
if (iterator == PendingCandidates_.end() || !(iterator->second.Snapshot == snapshot)) {
|
||||
PendingCandidates_[key] = PendingCandidate{std::move(event), snapshot, now};
|
||||
}
|
||||
}
|
||||
|
||||
void StartWorker() {
|
||||
if (Worker_.joinable()) {
|
||||
return;
|
||||
}
|
||||
StopRequested_ = false;
|
||||
Worker_ = std::thread([this]() {
|
||||
while (!StopRequested_) {
|
||||
std::unique_lock lock(WorkerMutex_);
|
||||
if (WorkerCondition_.wait_for(lock, std::chrono::milliseconds(500), [this]() {
|
||||
return StopRequested_.load();
|
||||
})) {
|
||||
break;
|
||||
}
|
||||
lock.unlock();
|
||||
PollOnce();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void StopWorker() {
|
||||
StopRequested_ = true;
|
||||
WorkerCondition_.notify_all();
|
||||
if (Worker_.joinable()) {
|
||||
Worker_.join();
|
||||
}
|
||||
}
|
||||
|
||||
MetaCoreEditorModuleRegistry* ModuleRegistry_ = nullptr;
|
||||
std::shared_ptr<MetaCoreIAssetDatabaseService> AssetDatabaseService_{};
|
||||
mutable std::mutex Mutex_{};
|
||||
std::filesystem::path ProjectRoot_{};
|
||||
SnapshotMap Baseline_{};
|
||||
std::unordered_map<std::string, PendingCandidate> PendingCandidates_{};
|
||||
std::vector<QueuedChange> QueuedChanges_{};
|
||||
std::atomic_bool StopRequested_ = false;
|
||||
std::mutex WorkerMutex_{};
|
||||
std::condition_variable WorkerCondition_{};
|
||||
std::thread Worker_{};
|
||||
};
|
||||
|
||||
class MetaCoreBuiltinCoreServicesModule final : public MetaCoreIModule {
|
||||
public:
|
||||
[[nodiscard]] std::string GetModuleName() const override { return "MetaCoreBuiltinCoreServicesModule"; }
|
||||
@ -6245,6 +6956,7 @@ public:
|
||||
moduleRegistry.RegisterService<MetaCoreIAssetDatabaseService>(std::make_shared<MetaCoreBuiltinAssetDatabaseService>());
|
||||
moduleRegistry.RegisterService<MetaCoreICookService>(std::make_shared<MetaCoreBuiltinCookService>());
|
||||
moduleRegistry.RegisterService<MetaCoreIImportPipelineService>(std::make_shared<MetaCoreBuiltinImportPipelineService>());
|
||||
moduleRegistry.RegisterService<MetaCoreIAssetHotReloadService>(std::make_shared<MetaCoreBuiltinAssetHotReloadService>());
|
||||
moduleRegistry.RegisterService<MetaCoreIAssetEditingService>(std::make_shared<MetaCoreBuiltinAssetEditingService>());
|
||||
moduleRegistry.RegisterService<MetaCoreIScenePersistenceService>(std::make_shared<MetaCoreBuiltinScenePersistenceService>());
|
||||
moduleRegistry.RegisterService<MetaCoreISelectionService>(std::make_shared<MetaCoreBuiltinSelectionService>());
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
#include "MetaCoreRuntimeData/MetaCoreRuntimeDataTypes.h"
|
||||
#include "MetaCorePlatform/MetaCoreWindow.h"
|
||||
#include "MetaCoreFoundation/MetaCoreAssetRegistry.h"
|
||||
#include "MetaCoreRender/MetaCoreEditorViewportRenderer.h"
|
||||
|
||||
#include <imgui.h>
|
||||
#include <imgui_internal.h>
|
||||
@ -640,6 +641,9 @@ void MetaCoreOpenProject(MetaCoreEditorContext& editorContext, const std::filesy
|
||||
}
|
||||
|
||||
if (assetDatabaseService->OpenProject(projectRoot)) {
|
||||
editorContext.GetViewportRenderer().SetProjectRootPath(assetDatabaseService->GetProjectDescriptor().RootPath);
|
||||
editorContext.SetSelectedProjectDirectory("Assets");
|
||||
editorContext.ClearSelectedAsset();
|
||||
MetaCoreEnsureProjectStartupScene(editorContext);
|
||||
editorContext.AddConsoleMessage(
|
||||
MetaCoreLogLevel::Info,
|
||||
@ -658,6 +662,9 @@ void MetaCoreCreateProject(MetaCoreEditorContext& editorContext, const std::file
|
||||
}
|
||||
|
||||
if (assetDatabaseService->CreateProject(projectRoot, projectName)) {
|
||||
editorContext.GetViewportRenderer().SetProjectRootPath(assetDatabaseService->GetProjectDescriptor().RootPath);
|
||||
editorContext.SetSelectedProjectDirectory("Assets");
|
||||
editorContext.ClearSelectedAsset();
|
||||
MetaCoreEnsureProjectStartupScene(editorContext);
|
||||
editorContext.AddConsoleMessage(
|
||||
MetaCoreLogLevel::Info,
|
||||
@ -669,6 +676,100 @@ void MetaCoreCreateProject(MetaCoreEditorContext& editorContext, const std::file
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] bool MetaCoreIsAssetsRelativePath(const std::filesystem::path& relativePath) {
|
||||
const std::filesystem::path normalizedPath = relativePath.lexically_normal();
|
||||
const auto begin = normalizedPath.begin();
|
||||
return begin != normalizedPath.end() && *begin == "Assets";
|
||||
}
|
||||
|
||||
void MetaCoreImportExternalAssetPath(
|
||||
MetaCoreEditorContext& editorContext,
|
||||
const std::filesystem::path& sourcePath,
|
||||
const std::filesystem::path& targetRelativeDirectory
|
||||
) {
|
||||
const auto assetDatabaseService = editorContext.GetModuleRegistry().ResolveService<MetaCoreIAssetDatabaseService>();
|
||||
if (assetDatabaseService == nullptr || !assetDatabaseService->HasProject()) {
|
||||
editorContext.AddConsoleMessage(MetaCoreLogLevel::Error, "Asset", "导入失败:当前没有打开项目");
|
||||
return;
|
||||
}
|
||||
|
||||
if (sourcePath.empty()) {
|
||||
editorContext.AddConsoleMessage(MetaCoreLogLevel::Error, "Asset", "导入失败:源路径为空");
|
||||
return;
|
||||
}
|
||||
|
||||
const std::filesystem::path absoluteSourcePath = sourcePath.is_absolute()
|
||||
? sourcePath.lexically_normal()
|
||||
: (std::filesystem::current_path() / sourcePath).lexically_normal();
|
||||
std::error_code errorCode;
|
||||
if (!std::filesystem::exists(absoluteSourcePath, errorCode)) {
|
||||
editorContext.AddConsoleMessage(
|
||||
MetaCoreLogLevel::Error,
|
||||
"Asset",
|
||||
"导入失败:源路径不存在: " + absoluteSourcePath.string()
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
std::filesystem::path normalizedTargetDirectory = targetRelativeDirectory.empty()
|
||||
? std::filesystem::path("Assets")
|
||||
: targetRelativeDirectory.lexically_normal();
|
||||
if (!MetaCoreIsAssetsRelativePath(normalizedTargetDirectory)) {
|
||||
editorContext.AddConsoleMessage(MetaCoreLogLevel::Error, "Asset", "导入失败:目标目录必须位于 Assets 下");
|
||||
return;
|
||||
}
|
||||
|
||||
const std::filesystem::path absoluteTargetDirectory =
|
||||
assetDatabaseService->GetProjectDescriptor().RootPath / normalizedTargetDirectory;
|
||||
std::filesystem::create_directories(absoluteTargetDirectory, errorCode);
|
||||
if (errorCode) {
|
||||
editorContext.AddConsoleMessage(
|
||||
MetaCoreLogLevel::Error,
|
||||
"Asset",
|
||||
"导入失败:无法创建目标目录: " + absoluteTargetDirectory.string()
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const std::filesystem::path absoluteTargetPath = absoluteTargetDirectory / absoluteSourcePath.filename();
|
||||
errorCode.clear();
|
||||
if (std::filesystem::is_directory(absoluteSourcePath, errorCode)) {
|
||||
std::filesystem::copy(
|
||||
absoluteSourcePath,
|
||||
absoluteTargetPath,
|
||||
std::filesystem::copy_options::recursive | std::filesystem::copy_options::overwrite_existing,
|
||||
errorCode
|
||||
);
|
||||
} else {
|
||||
std::filesystem::copy_file(
|
||||
absoluteSourcePath,
|
||||
absoluteTargetPath,
|
||||
std::filesystem::copy_options::overwrite_existing,
|
||||
errorCode
|
||||
);
|
||||
}
|
||||
|
||||
if (errorCode) {
|
||||
editorContext.AddConsoleMessage(
|
||||
MetaCoreLogLevel::Error,
|
||||
"Asset",
|
||||
"导入失败:" + errorCode.message()
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
(void)assetDatabaseService->Refresh();
|
||||
if (const auto hotReloadService = editorContext.GetModuleRegistry().ResolveService<MetaCoreIAssetHotReloadService>();
|
||||
hotReloadService != nullptr) {
|
||||
hotReloadService->RescanBaseline();
|
||||
}
|
||||
editorContext.AddConsoleMessage(
|
||||
MetaCoreLogLevel::Info,
|
||||
"Asset",
|
||||
"已导入资源: " + absoluteTargetPath.lexically_relative(assetDatabaseService->GetProjectDescriptor().RootPath).generic_string()
|
||||
);
|
||||
}
|
||||
|
||||
void MetaCoreHandleGlobalEditorShortcuts(MetaCoreEditorContext& editorContext) {
|
||||
const ImGuiIO& io = ImGui::GetIO();
|
||||
if (io.WantTextInput) {
|
||||
@ -1819,7 +1920,7 @@ public:
|
||||
std::snprintf(CreateProjectPathBuffer_, sizeof(CreateProjectPathBuffer_), "%s", std::filesystem::current_path().string().c_str());
|
||||
}
|
||||
std::snprintf(CreateProjectNameBuffer_, sizeof(CreateProjectNameBuffer_), "%s", "NewMetaCoreProject");
|
||||
ImGui::OpenPopup("MetaCoreCreateProjectPopup");
|
||||
OpenCreateProjectPopup_ = true;
|
||||
}
|
||||
if (ImGui::MenuItem("打开项目...")) {
|
||||
if (assetDatabaseService != nullptr && assetDatabaseService->HasProject()) {
|
||||
@ -1827,7 +1928,7 @@ public:
|
||||
} else {
|
||||
std::snprintf(OpenProjectPathBuffer_, sizeof(OpenProjectPathBuffer_), "%s", std::filesystem::current_path().string().c_str());
|
||||
}
|
||||
ImGui::OpenPopup("MetaCoreOpenProjectPopup");
|
||||
OpenProjectPopup_ = true;
|
||||
}
|
||||
ImGui::Separator();
|
||||
if (ImGui::MenuItem("保存场景", "Ctrl+S", false, scenePersistenceService != nullptr)) {
|
||||
@ -1849,6 +1950,19 @@ public:
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
if (OpenCreateProjectPopup_) {
|
||||
ImGui::OpenPopup("MetaCoreCreateProjectPopup");
|
||||
OpenCreateProjectPopup_ = false;
|
||||
}
|
||||
if (OpenProjectPopup_) {
|
||||
ImGui::OpenPopup("MetaCoreOpenProjectPopup");
|
||||
OpenProjectPopup_ = false;
|
||||
}
|
||||
if (OpenImportResourcePopup_) {
|
||||
ImGui::OpenPopup("MetaCoreImportResourcePopup");
|
||||
OpenImportResourcePopup_ = false;
|
||||
}
|
||||
|
||||
if (ImGui::BeginPopupModal("MetaCoreCreateProjectPopup", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||
ImGui::InputText("项目目录", CreateProjectPathBuffer_, sizeof(CreateProjectPathBuffer_));
|
||||
ImGui::InputText("项目名称", CreateProjectNameBuffer_, sizeof(CreateProjectNameBuffer_));
|
||||
@ -1877,6 +1991,25 @@ public:
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
if (ImGui::BeginPopupModal("MetaCoreImportResourcePopup", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||
ImGui::TextUnformatted("输入外部文件或目录路径,导入到项目 Assets 目录。");
|
||||
ImGui::InputText("源路径", ImportSourcePathBuffer_, sizeof(ImportSourcePathBuffer_));
|
||||
ImGui::InputText("目标目录", ImportTargetDirectoryBuffer_, sizeof(ImportTargetDirectoryBuffer_));
|
||||
if (ImGui::Button("导入")) {
|
||||
MetaCoreImportExternalAssetPath(
|
||||
editorContext,
|
||||
std::filesystem::path(ImportSourcePathBuffer_),
|
||||
std::filesystem::path(ImportTargetDirectoryBuffer_)
|
||||
);
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("取消")) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
if (ImGui::BeginMenu("编辑")) {
|
||||
if (ImGui::MenuItem("撤销", "Ctrl+Z", false, editorContext.GetCommandService().CanUndo())) {
|
||||
MetaCoreHandleUndo(editorContext);
|
||||
@ -1898,7 +2031,22 @@ public:
|
||||
}
|
||||
|
||||
if (ImGui::BeginMenu("资源")) {
|
||||
ImGui::MenuItem("导入资源", nullptr, false, false);
|
||||
const bool canImportResource =
|
||||
assetDatabaseService != nullptr && assetDatabaseService->HasProject();
|
||||
if (ImGui::MenuItem("导入资源...", nullptr, false, canImportResource)) {
|
||||
ImportSourcePathBuffer_[0] = '\0';
|
||||
std::filesystem::path targetDirectory = editorContext.GetSelectedProjectDirectory();
|
||||
if (targetDirectory.empty() || !MetaCoreIsAssetsRelativePath(targetDirectory)) {
|
||||
targetDirectory = "Assets";
|
||||
}
|
||||
std::snprintf(
|
||||
ImportTargetDirectoryBuffer_,
|
||||
sizeof(ImportTargetDirectoryBuffer_),
|
||||
"%s",
|
||||
targetDirectory.generic_string().c_str()
|
||||
);
|
||||
OpenImportResourcePopup_ = true;
|
||||
}
|
||||
if (ImGui::MenuItem("从选中对象创建 Prefab", nullptr, false, editorContext.GetActiveObjectId() != 0)) {
|
||||
(void)MetaCoreCreatePrefabFromSelection(editorContext);
|
||||
}
|
||||
@ -1953,9 +2101,14 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
bool OpenCreateProjectPopup_ = false;
|
||||
bool OpenProjectPopup_ = false;
|
||||
bool OpenImportResourcePopup_ = false;
|
||||
char CreateProjectPathBuffer_[512]{};
|
||||
char CreateProjectNameBuffer_[256]{};
|
||||
char OpenProjectPathBuffer_[512]{};
|
||||
char ImportSourcePathBuffer_[1024]{};
|
||||
char ImportTargetDirectoryBuffer_[512]{};
|
||||
};
|
||||
|
||||
class MetaCoreHierarchyPanelProvider final : public MetaCoreIEditorPanelProvider {
|
||||
|
||||
@ -738,6 +738,10 @@ bool MetaCoreEditorApp::Initialize(int argc, char* argv[]) {
|
||||
int MetaCoreEditorApp::Run() {
|
||||
while (!Window_.ShouldClose()) {
|
||||
Window_.BeginFrame();
|
||||
if (const auto hotReloadService = ModuleRegistry_.ResolveService<MetaCoreIAssetHotReloadService>();
|
||||
hotReloadService != nullptr) {
|
||||
hotReloadService->Tick();
|
||||
}
|
||||
|
||||
#if defined(_WIN32)
|
||||
ImGui_ImplWin32_NewFrame();
|
||||
|
||||
@ -29,6 +29,12 @@ struct MetaCoreSubAssetMetadata {
|
||||
|
||||
MC_PROPERTY()
|
||||
std::int32_t Index = 0;
|
||||
|
||||
MC_PROPERTY()
|
||||
std::string StableImportKey{};
|
||||
|
||||
MC_PROPERTY()
|
||||
std::vector<MetaCoreAssetGuid> Dependencies{};
|
||||
};
|
||||
|
||||
MC_STRUCT()
|
||||
@ -55,6 +61,9 @@ struct MetaCoreAssetMetadataDocument {
|
||||
|
||||
MC_PROPERTY()
|
||||
std::vector<MetaCoreSubAssetMetadata> SubAssets{};
|
||||
|
||||
MC_PROPERTY()
|
||||
std::vector<MetaCoreAssetGuid> Dependencies{};
|
||||
};
|
||||
|
||||
MC_STRUCT()
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "MetaCoreFoundation/MetaCoreAssetGuid.h"
|
||||
#include "MetaCoreFoundation/MetaCoreAssetChange.h"
|
||||
#include "MetaCoreFoundation/MetaCoreAssetTypes.h"
|
||||
#include "MetaCoreFoundation/MetaCoreId.h"
|
||||
#include "MetaCoreFoundation/MetaCorePackage.h"
|
||||
@ -28,7 +29,8 @@ enum class MetaCoreEditorEventType {
|
||||
SceneLoaded,
|
||||
SceneSaved,
|
||||
SceneDirtyStateChanged,
|
||||
SelectionChanged
|
||||
SelectionChanged,
|
||||
ExternalSceneChanged
|
||||
};
|
||||
|
||||
struct MetaCoreEditorEvent {
|
||||
@ -144,12 +146,20 @@ public:
|
||||
[[nodiscard]] virtual std::vector<std::filesystem::path> GetDirectoriesUnder(const std::filesystem::path& relativeDirectory) const = 0;
|
||||
[[nodiscard]] virtual std::vector<MetaCoreAssetRecord> GetAssetsUnder(const std::filesystem::path& relativeDirectory) const = 0;
|
||||
[[nodiscard]] virtual std::optional<MetaCoreAssetRecord> FindAssetByRelativePath(const std::filesystem::path& relativePath) const = 0;
|
||||
virtual bool ApplyExternalFileChanges(const std::vector<MetaCoreAssetChangeEvent>& changes) = 0;
|
||||
virtual bool Refresh() = 0;
|
||||
virtual bool CreateFolder(const std::filesystem::path& relativeDirectory) = 0;
|
||||
virtual bool RegisterScenePath(const std::filesystem::path& relativeScenePath, bool makeStartupScene) = 0;
|
||||
virtual bool SetStartupScenePath(const std::filesystem::path& relativeScenePath) = 0;
|
||||
};
|
||||
|
||||
class MetaCoreIAssetHotReloadService : public MetaCoreIEditorService {
|
||||
public:
|
||||
virtual void Tick() = 0;
|
||||
virtual void SetProjectRoot(const std::filesystem::path& projectRoot) = 0;
|
||||
virtual void RescanBaseline() = 0;
|
||||
};
|
||||
|
||||
class MetaCoreIImportPipelineService : public MetaCoreIEditorService {
|
||||
public:
|
||||
[[nodiscard]] virtual bool RefreshImports() = 0;
|
||||
|
||||
@ -0,0 +1,99 @@
|
||||
#include "MetaCoreFoundation/MetaCoreAssetDependencyGraph.h"
|
||||
|
||||
namespace MetaCore {
|
||||
|
||||
MetaCoreAssetDependencyGraph& MetaCoreAssetDependencyGraph::Get() {
|
||||
static MetaCoreAssetDependencyGraph graph;
|
||||
return graph;
|
||||
}
|
||||
|
||||
void MetaCoreAssetDependencyGraph::SetDependencies(
|
||||
const MetaCoreAssetGuid& assetGuid,
|
||||
const AssetGuidSet& dependencies
|
||||
) {
|
||||
if (!assetGuid.IsValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::scoped_lock lock(Mutex_);
|
||||
if (const auto previous = Dependencies_.find(assetGuid); previous != Dependencies_.end()) {
|
||||
for (const MetaCoreAssetGuid& dependency : previous->second) {
|
||||
if (auto dependents = Dependents_.find(dependency); dependents != Dependents_.end()) {
|
||||
dependents->second.erase(assetGuid);
|
||||
if (dependents->second.empty()) {
|
||||
Dependents_.erase(dependents);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AssetGuidSet validDependencies;
|
||||
for (const MetaCoreAssetGuid& dependency : dependencies) {
|
||||
if (!dependency.IsValid() || dependency == assetGuid) {
|
||||
continue;
|
||||
}
|
||||
validDependencies.insert(dependency);
|
||||
Dependents_[dependency].insert(assetGuid);
|
||||
}
|
||||
|
||||
if (validDependencies.empty()) {
|
||||
Dependencies_.erase(assetGuid);
|
||||
} else {
|
||||
Dependencies_[assetGuid] = std::move(validDependencies);
|
||||
}
|
||||
}
|
||||
|
||||
MetaCoreAssetDependencyGraph::AssetGuidSet MetaCoreAssetDependencyGraph::GetDependencies(
|
||||
const MetaCoreAssetGuid& assetGuid
|
||||
) const {
|
||||
std::scoped_lock lock(Mutex_);
|
||||
const auto iterator = Dependencies_.find(assetGuid);
|
||||
return iterator == Dependencies_.end() ? AssetGuidSet{} : iterator->second;
|
||||
}
|
||||
|
||||
MetaCoreAssetDependencyGraph::AssetGuidSet MetaCoreAssetDependencyGraph::GetDependents(
|
||||
const MetaCoreAssetGuid& assetGuid
|
||||
) const {
|
||||
std::scoped_lock lock(Mutex_);
|
||||
const auto iterator = Dependents_.find(assetGuid);
|
||||
return iterator == Dependents_.end() ? AssetGuidSet{} : iterator->second;
|
||||
}
|
||||
|
||||
void MetaCoreAssetDependencyGraph::RemoveAsset(const MetaCoreAssetGuid& assetGuid) {
|
||||
if (!assetGuid.IsValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::scoped_lock lock(Mutex_);
|
||||
if (const auto dependencies = Dependencies_.find(assetGuid); dependencies != Dependencies_.end()) {
|
||||
for (const MetaCoreAssetGuid& dependency : dependencies->second) {
|
||||
if (auto dependents = Dependents_.find(dependency); dependents != Dependents_.end()) {
|
||||
dependents->second.erase(assetGuid);
|
||||
if (dependents->second.empty()) {
|
||||
Dependents_.erase(dependents);
|
||||
}
|
||||
}
|
||||
}
|
||||
Dependencies_.erase(dependencies);
|
||||
}
|
||||
|
||||
if (const auto dependents = Dependents_.find(assetGuid); dependents != Dependents_.end()) {
|
||||
for (const MetaCoreAssetGuid& dependent : dependents->second) {
|
||||
if (auto dependencies = Dependencies_.find(dependent); dependencies != Dependencies_.end()) {
|
||||
dependencies->second.erase(assetGuid);
|
||||
if (dependencies->second.empty()) {
|
||||
Dependencies_.erase(dependencies);
|
||||
}
|
||||
}
|
||||
}
|
||||
Dependents_.erase(dependents);
|
||||
}
|
||||
}
|
||||
|
||||
void MetaCoreAssetDependencyGraph::Clear() {
|
||||
std::scoped_lock lock(Mutex_);
|
||||
Dependencies_.clear();
|
||||
Dependents_.clear();
|
||||
}
|
||||
|
||||
} // namespace MetaCore
|
||||
@ -50,7 +50,7 @@ void MetaCoreAssetRegistry::ScanDirectory(const std::filesystem::path& rootPath)
|
||||
const std::string subGuidStr = subAssetJson["guid"].get<std::string>();
|
||||
auto parsedSubGuid = MetaCoreAssetGuid::Parse(subGuidStr);
|
||||
if (parsedSubGuid.has_value()) {
|
||||
RegisterAsset(*parsedSubGuid, portablePath);
|
||||
RegisterSubAsset(*parsedSubGuid, portablePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -93,18 +93,50 @@ bool MetaCoreAssetRegistry::RegisterAsset(const MetaCoreAssetGuid& guid, const s
|
||||
return false;
|
||||
}
|
||||
|
||||
// 将路径规范化为通用字符串(统一斜杠)
|
||||
std::string keyPath = relativePath.generic_string();
|
||||
std::transform(keyPath.begin(), keyPath.end(), keyPath.begin(), ::tolower);
|
||||
|
||||
UnregisterAsset(guid);
|
||||
const std::string keyPath = NormalizePathKey(relativePath);
|
||||
GuidToPath_[guid] = relativePath;
|
||||
PathToGuid_[keyPath] = guid;
|
||||
PrimaryAssetGuids_.insert(guid);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MetaCoreAssetRegistry::RegisterSubAsset(const MetaCoreAssetGuid& guid, const std::filesystem::path& relativePath) {
|
||||
if (!guid.IsValid()) {
|
||||
return false;
|
||||
}
|
||||
UnregisterAsset(guid);
|
||||
GuidToPath_[guid] = relativePath;
|
||||
return true;
|
||||
}
|
||||
|
||||
void MetaCoreAssetRegistry::UnregisterAsset(const MetaCoreAssetGuid& guid) {
|
||||
const auto iterator = GuidToPath_.find(guid);
|
||||
if (iterator == GuidToPath_.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string keyPath = NormalizePathKey(iterator->second);
|
||||
if (const auto pathIterator = PathToGuid_.find(keyPath);
|
||||
pathIterator != PathToGuid_.end() && pathIterator->second == guid) {
|
||||
PathToGuid_.erase(pathIterator);
|
||||
}
|
||||
GuidToPath_.erase(iterator);
|
||||
PrimaryAssetGuids_.erase(guid);
|
||||
}
|
||||
|
||||
bool MetaCoreAssetRegistry::MoveAsset(const MetaCoreAssetGuid& guid, const std::filesystem::path& newRelativePath) {
|
||||
if (!guid.IsValid() || !GuidToPath_.contains(guid)) {
|
||||
return false;
|
||||
}
|
||||
const bool isPrimaryAsset = PrimaryAssetGuids_.contains(guid);
|
||||
return isPrimaryAsset ? RegisterAsset(guid, newRelativePath) : RegisterSubAsset(guid, newRelativePath);
|
||||
}
|
||||
|
||||
void MetaCoreAssetRegistry::Clear() {
|
||||
GuidToPath_.clear();
|
||||
PathToGuid_.clear();
|
||||
PrimaryAssetGuids_.clear();
|
||||
}
|
||||
|
||||
std::filesystem::path MetaCoreAssetRegistry::ResolveGuidToPath(const MetaCoreAssetGuid& guid) const {
|
||||
@ -116,9 +148,7 @@ std::filesystem::path MetaCoreAssetRegistry::ResolveGuidToPath(const MetaCoreAss
|
||||
}
|
||||
|
||||
MetaCoreAssetGuid MetaCoreAssetRegistry::ResolvePathToGuid(const std::filesystem::path& relativePath) const {
|
||||
std::string keyPath = relativePath.generic_string();
|
||||
std::transform(keyPath.begin(), keyPath.end(), keyPath.begin(), ::tolower);
|
||||
|
||||
const std::string keyPath = NormalizePathKey(relativePath);
|
||||
const auto it = PathToGuid_.find(keyPath);
|
||||
if (it != PathToGuid_.end()) {
|
||||
return it->second;
|
||||
@ -126,6 +156,16 @@ MetaCoreAssetGuid MetaCoreAssetRegistry::ResolvePathToGuid(const std::filesystem
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string MetaCoreAssetRegistry::NormalizePathKey(const std::filesystem::path& path) {
|
||||
std::string keyPath = path.lexically_normal().generic_string();
|
||||
#if defined(_WIN32)
|
||||
std::transform(keyPath.begin(), keyPath.end(), keyPath.begin(), [](unsigned char value) {
|
||||
return value >= 'A' && value <= 'Z' ? static_cast<char>(value - 'A' + 'a') : static_cast<char>(value);
|
||||
});
|
||||
#endif
|
||||
return keyPath;
|
||||
}
|
||||
|
||||
bool MetaCoreAssetRegistry::HasAsset(const MetaCoreAssetGuid& guid) const {
|
||||
return GuidToPath_.count(guid) > 0;
|
||||
}
|
||||
|
||||
@ -0,0 +1,57 @@
|
||||
#include "MetaCoreFoundation/MetaCoreRuntimeAssetRegistry.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace MetaCore {
|
||||
|
||||
MetaCoreRuntimeAssetRegistry& MetaCoreRuntimeAssetRegistry::Get() {
|
||||
static MetaCoreRuntimeAssetRegistry registry;
|
||||
return registry;
|
||||
}
|
||||
|
||||
MetaCoreRuntimeAssetRegistry::SubscriptionId MetaCoreRuntimeAssetRegistry::Subscribe(AssetChangeHandler handler) {
|
||||
if (!handler) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::scoped_lock lock(Mutex_);
|
||||
const SubscriptionId subscriptionId = NextSubscriptionId_++;
|
||||
Subscriptions_.emplace(subscriptionId, std::move(handler));
|
||||
return subscriptionId;
|
||||
}
|
||||
|
||||
void MetaCoreRuntimeAssetRegistry::Unsubscribe(SubscriptionId subscriptionId) {
|
||||
std::scoped_lock lock(Mutex_);
|
||||
Subscriptions_.erase(subscriptionId);
|
||||
}
|
||||
|
||||
void MetaCoreRuntimeAssetRegistry::QueueEvent(MetaCoreAssetChangeEvent event) {
|
||||
std::scoped_lock lock(Mutex_);
|
||||
QueuedEvents_.push_back(std::move(event));
|
||||
}
|
||||
|
||||
void MetaCoreRuntimeAssetRegistry::DispatchQueuedEvents() {
|
||||
std::vector<MetaCoreAssetChangeEvent> events;
|
||||
std::unordered_map<SubscriptionId, AssetChangeHandler> subscriptions;
|
||||
{
|
||||
std::scoped_lock lock(Mutex_);
|
||||
events.swap(QueuedEvents_);
|
||||
subscriptions = Subscriptions_;
|
||||
}
|
||||
|
||||
for (const MetaCoreAssetChangeEvent& event : events) {
|
||||
for (const auto& [subscriptionId, handler] : subscriptions) {
|
||||
(void)subscriptionId;
|
||||
if (handler) {
|
||||
handler(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MetaCoreRuntimeAssetRegistry::Clear() {
|
||||
std::scoped_lock lock(Mutex_);
|
||||
QueuedEvents_.clear();
|
||||
}
|
||||
|
||||
} // namespace MetaCore
|
||||
@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "MetaCoreFoundation/MetaCoreAssetGuid.h"
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
namespace MetaCore {
|
||||
|
||||
enum class MetaCoreAssetChangeType {
|
||||
Created = 0,
|
||||
Modified,
|
||||
Moved,
|
||||
Deleted
|
||||
};
|
||||
|
||||
struct MetaCoreAssetChangeEvent {
|
||||
MetaCoreAssetChangeType Type = MetaCoreAssetChangeType::Modified;
|
||||
MetaCoreAssetGuid AssetGuid{};
|
||||
MetaCoreAssetGuid SourceAssetGuid{};
|
||||
std::filesystem::path OldRelativePath{};
|
||||
std::filesystem::path NewRelativePath{};
|
||||
};
|
||||
|
||||
} // namespace MetaCore
|
||||
@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include "MetaCoreFoundation/MetaCoreAssetGuid.h"
|
||||
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace MetaCore {
|
||||
|
||||
class MetaCoreAssetDependencyGraph {
|
||||
public:
|
||||
using AssetGuidSet = std::unordered_set<MetaCoreAssetGuid, MetaCoreAssetGuidHasher>;
|
||||
|
||||
[[nodiscard]] static MetaCoreAssetDependencyGraph& Get();
|
||||
|
||||
void SetDependencies(const MetaCoreAssetGuid& assetGuid, const AssetGuidSet& dependencies);
|
||||
[[nodiscard]] AssetGuidSet GetDependencies(const MetaCoreAssetGuid& assetGuid) const;
|
||||
[[nodiscard]] AssetGuidSet GetDependents(const MetaCoreAssetGuid& assetGuid) const;
|
||||
void RemoveAsset(const MetaCoreAssetGuid& assetGuid);
|
||||
void Clear();
|
||||
|
||||
private:
|
||||
MetaCoreAssetDependencyGraph() = default;
|
||||
|
||||
mutable std::mutex Mutex_{};
|
||||
std::unordered_map<MetaCoreAssetGuid, AssetGuidSet, MetaCoreAssetGuidHasher> Dependencies_{};
|
||||
std::unordered_map<MetaCoreAssetGuid, AssetGuidSet, MetaCoreAssetGuidHasher> Dependents_{};
|
||||
};
|
||||
|
||||
} // namespace MetaCore
|
||||
@ -4,6 +4,7 @@
|
||||
|
||||
#include <filesystem>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <string>
|
||||
#include <optional>
|
||||
|
||||
@ -20,6 +21,12 @@ public:
|
||||
// 手动注册单个资产的双向映射关系
|
||||
bool RegisterAsset(const MetaCoreAssetGuid& guid, const std::filesystem::path& relativePath);
|
||||
|
||||
// 子资产共享源文件路径,但不参与路径到 GUID 的反查
|
||||
bool RegisterSubAsset(const MetaCoreAssetGuid& guid, const std::filesystem::path& relativePath);
|
||||
|
||||
void UnregisterAsset(const MetaCoreAssetGuid& guid);
|
||||
bool MoveAsset(const MetaCoreAssetGuid& guid, const std::filesystem::path& newRelativePath);
|
||||
|
||||
// 清空注册表缓存
|
||||
void Clear();
|
||||
|
||||
@ -33,6 +40,8 @@ public:
|
||||
[[nodiscard]] bool HasAsset(const MetaCoreAssetGuid& guid) const;
|
||||
|
||||
private:
|
||||
[[nodiscard]] static std::string NormalizePathKey(const std::filesystem::path& path);
|
||||
|
||||
MetaCoreAssetRegistry() = default;
|
||||
~MetaCoreAssetRegistry() = default;
|
||||
|
||||
@ -41,6 +50,7 @@ private:
|
||||
|
||||
std::unordered_map<MetaCoreAssetGuid, std::filesystem::path, MetaCoreAssetGuidHasher> GuidToPath_;
|
||||
std::unordered_map<std::string, MetaCoreAssetGuid> PathToGuid_;
|
||||
std::unordered_set<MetaCoreAssetGuid, MetaCoreAssetGuidHasher> PrimaryAssetGuids_;
|
||||
};
|
||||
|
||||
} // namespace MetaCore
|
||||
|
||||
@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include "MetaCoreFoundation/MetaCoreAssetChange.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace MetaCore {
|
||||
|
||||
class MetaCoreRuntimeAssetRegistry {
|
||||
public:
|
||||
using SubscriptionId = std::size_t;
|
||||
using AssetChangeHandler = std::function<void(const MetaCoreAssetChangeEvent&)>;
|
||||
|
||||
[[nodiscard]] static MetaCoreRuntimeAssetRegistry& Get();
|
||||
|
||||
[[nodiscard]] SubscriptionId Subscribe(AssetChangeHandler handler);
|
||||
void Unsubscribe(SubscriptionId subscriptionId);
|
||||
void QueueEvent(MetaCoreAssetChangeEvent event);
|
||||
void DispatchQueuedEvents();
|
||||
void Clear();
|
||||
|
||||
private:
|
||||
MetaCoreRuntimeAssetRegistry() = default;
|
||||
|
||||
std::mutex Mutex_{};
|
||||
std::unordered_map<SubscriptionId, AssetChangeHandler> Subscriptions_{};
|
||||
std::vector<MetaCoreAssetChangeEvent> QueuedEvents_{};
|
||||
SubscriptionId NextSubscriptionId_ = 1;
|
||||
};
|
||||
|
||||
} // namespace MetaCore
|
||||
@ -7,6 +7,7 @@
|
||||
#include "MetaCoreRender/MetaCoreSceneRenderSync.h"
|
||||
#include <imgui.h>
|
||||
#include "MetaCoreFoundation/MetaCoreAssetRegistry.h"
|
||||
#include "MetaCoreFoundation/MetaCoreRuntimeAssetRegistry.h"
|
||||
|
||||
#include <filament/Engine.h>
|
||||
#include <filament/Scene.h>
|
||||
@ -204,12 +205,26 @@ public:
|
||||
MaterialProvider_ = filament::gltfio::createJitShaderProvider(Engine_);
|
||||
NameManager_ = new utils::NameComponentManager(utils::EntityManager::get());
|
||||
AssetLoader_ = filament::gltfio::AssetLoader::create({ Engine_, MaterialProvider_, NameManager_ });
|
||||
RuntimeAssetSubscriptionId_ = MetaCoreRuntimeAssetRegistry::Get().Subscribe(
|
||||
[this](const MetaCoreAssetChangeEvent& event) {
|
||||
if (event.AssetGuid.IsValid()) {
|
||||
InvalidatedModelGuids_.insert(event.AssetGuid);
|
||||
}
|
||||
if (event.SourceAssetGuid.IsValid()) {
|
||||
InvalidatedModelGuids_.insert(event.SourceAssetGuid);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
std::cout << "FilamentSceneBridge: Initialized with Offscreen=" << (offscreen ? "true" : "false") << std::endl;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Shutdown() {
|
||||
if (RuntimeAssetSubscriptionId_ != 0) {
|
||||
MetaCoreRuntimeAssetRegistry::Get().Unsubscribe(RuntimeAssetSubscriptionId_);
|
||||
RuntimeAssetSubscriptionId_ = 0;
|
||||
}
|
||||
if (Engine_) {
|
||||
std::cout << "FilamentSceneBridge: Shutting down..." << std::endl;
|
||||
|
||||
@ -223,6 +238,8 @@ public:
|
||||
std::cout << "[DEBUG] Destroyed asset for ID: " << id << std::endl;
|
||||
}
|
||||
LoadedAssets_.clear();
|
||||
HostRootSourceModelGuids_.clear();
|
||||
InvalidatedModelGuids_.clear();
|
||||
|
||||
// 2. 销毁所有 pivotEntity 中间辅助坐标变换实体
|
||||
for (auto& [objectId, assetEntity] : ObjectToFilamentEntity_) {
|
||||
@ -506,8 +523,12 @@ public:
|
||||
for (auto it = LoadedAssets_.begin(); it != LoadedAssets_.end(); ) {
|
||||
MetaCoreId hostRootId = it->first;
|
||||
filament::gltfio::FilamentAsset* asset = it->second;
|
||||
const auto sourceGuid = HostRootSourceModelGuids_.find(hostRootId);
|
||||
const bool invalidated =
|
||||
sourceGuid != HostRootSourceModelGuids_.end() &&
|
||||
InvalidatedModelGuids_.contains(sourceGuid->second);
|
||||
|
||||
if (!activeHostRootIds.contains(hostRootId)) {
|
||||
if (!activeHostRootIds.contains(hostRootId) || invalidated) {
|
||||
std::cout << "FilamentSceneBridge: Unloading deleted asset: " << hostRootId << std::endl;
|
||||
|
||||
// 从场景中移除该资产关联的所有实体
|
||||
@ -565,11 +586,13 @@ public:
|
||||
}
|
||||
|
||||
// 从 LoadedAssets_ 中移除
|
||||
HostRootSourceModelGuids_.erase(hostRootId);
|
||||
it = LoadedAssets_.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
InvalidatedModelGuids_.clear();
|
||||
|
||||
for (const auto& renderable : snapshot.Renderables) {
|
||||
if (renderable.MeshSource != MetaCoreMeshSourceKind::Asset &&
|
||||
@ -641,6 +664,9 @@ public:
|
||||
}
|
||||
|
||||
LoadedAssets_[hostRootId] = asset;
|
||||
if (renderable.SourceModelAssetGuid.IsValid()) {
|
||||
HostRootSourceModelGuids_[hostRootId] = renderable.SourceModelAssetGuid;
|
||||
}
|
||||
|
||||
// 复制材质实例防止共享干扰,放在加载好资源后
|
||||
{
|
||||
@ -1328,6 +1354,8 @@ private:
|
||||
std::unordered_map<MetaCoreId, glm::mat4> ObjectWorldMatrices_{};
|
||||
std::unordered_map<MetaCoreId, std::pair<filament::gltfio::FilamentAsset*, utils::Entity>> ObjectToFilamentEntity_;
|
||||
std::unordered_map<MetaCoreId, filament::gltfio::FilamentAsset*> LoadedAssets_;
|
||||
std::unordered_map<MetaCoreId, MetaCoreAssetGuid> HostRootSourceModelGuids_;
|
||||
std::unordered_set<MetaCoreAssetGuid, MetaCoreAssetGuidHasher> InvalidatedModelGuids_;
|
||||
std::unordered_map<MetaCoreId, std::vector<filament::MaterialInstance*>> AssetDuplicatedMaterials_;
|
||||
std::unordered_map<MetaCoreId, utils::Entity> SceneLightEntities_;
|
||||
std::unordered_set<utils::Entity, utils::Entity::Hasher> EntitiesInScene_;
|
||||
@ -1344,6 +1372,7 @@ private:
|
||||
|
||||
std::filesystem::path ProjectRootPath_{};
|
||||
std::string EmptyString_{};
|
||||
MetaCoreRuntimeAssetRegistry::SubscriptionId RuntimeAssetSubscriptionId_ = 0;
|
||||
};
|
||||
|
||||
MetaCoreFilamentSceneBridge::MetaCoreFilamentSceneBridge()
|
||||
|
||||
@ -7,6 +7,8 @@
|
||||
#include "MetaCoreFoundation/MetaCoreLogService.h"
|
||||
#include "MetaCoreFoundation/MetaCoreProject.h"
|
||||
#include "MetaCoreFoundation/MetaCoreAssetRegistry.h"
|
||||
#include "MetaCoreFoundation/MetaCoreAssetDependencyGraph.h"
|
||||
#include "MetaCoreFoundation/MetaCoreRuntimeAssetRegistry.h"
|
||||
#include "MetaCorePlatform/MetaCoreWindow.h"
|
||||
#include "MetaCoreRender/MetaCoreEditorViewportRenderer.h"
|
||||
#include "MetaCoreRender/MetaCoreRenderDevice.h"
|
||||
@ -31,6 +33,7 @@
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
@ -197,6 +200,7 @@ void MetaCoreTestBuiltinModuleComposition() {
|
||||
MetaCoreExpect(moduleRegistry.ResolveService<MetaCore::MetaCoreIPackageService>() != nullptr, "应注册 PackageService");
|
||||
MetaCoreExpect(moduleRegistry.ResolveService<MetaCore::MetaCoreIAssetDatabaseService>() != nullptr, "应注册 AssetDatabaseService");
|
||||
MetaCoreExpect(moduleRegistry.ResolveService<MetaCore::MetaCoreIImportPipelineService>() != nullptr, "应注册 ImportPipelineService");
|
||||
MetaCoreExpect(moduleRegistry.ResolveService<MetaCore::MetaCoreIAssetHotReloadService>() != nullptr, "应注册 AssetHotReloadService");
|
||||
MetaCoreExpect(moduleRegistry.ResolveService<MetaCore::MetaCoreICookService>() != nullptr, "应注册 CookService");
|
||||
MetaCoreExpect(moduleRegistry.ResolveService<MetaCore::MetaCoreIScenePersistenceService>() != nullptr, "应注册 ScenePersistenceService");
|
||||
MetaCoreExpect(moduleRegistry.ResolveService<MetaCore::MetaCoreISelectionService>() != nullptr, "应注册 SelectionService");
|
||||
@ -3094,6 +3098,167 @@ void MetaCoreTestP3ProductivityStage() {
|
||||
std::filesystem::remove_all(tempProjectRoot);
|
||||
}
|
||||
|
||||
void MetaCoreTestAssetDependencyGraphAndRuntimeRegistry() {
|
||||
auto& graph = MetaCore::MetaCoreAssetDependencyGraph::Get();
|
||||
graph.Clear();
|
||||
|
||||
const MetaCore::MetaCoreAssetGuid modelGuid = MetaCore::MetaCoreAssetGuid::Generate();
|
||||
const MetaCore::MetaCoreAssetGuid materialGuid = MetaCore::MetaCoreAssetGuid::Generate();
|
||||
const MetaCore::MetaCoreAssetGuid textureGuid = MetaCore::MetaCoreAssetGuid::Generate();
|
||||
graph.SetDependencies(modelGuid, {materialGuid});
|
||||
graph.SetDependencies(materialGuid, {textureGuid});
|
||||
MetaCoreExpect(graph.GetDependencies(modelGuid).contains(materialGuid), "依赖图应记录正向依赖");
|
||||
MetaCoreExpect(graph.GetDependents(textureGuid).contains(materialGuid), "依赖图应记录反向依赖");
|
||||
|
||||
graph.SetDependencies(modelGuid, {textureGuid});
|
||||
MetaCoreExpect(!graph.GetDependents(materialGuid).contains(modelGuid), "替换依赖后旧反向边应移除");
|
||||
MetaCoreExpect(graph.GetDependents(textureGuid).contains(modelGuid), "替换依赖后新反向边应建立");
|
||||
graph.RemoveAsset(textureGuid);
|
||||
MetaCoreExpect(graph.GetDependencies(modelGuid).empty(), "删除资源后引用它的边应移除");
|
||||
|
||||
auto& runtimeRegistry = MetaCore::MetaCoreRuntimeAssetRegistry::Get();
|
||||
runtimeRegistry.Clear();
|
||||
int callbackCount = 0;
|
||||
const auto subscription = runtimeRegistry.Subscribe([&](const MetaCore::MetaCoreAssetChangeEvent& event) {
|
||||
if (event.AssetGuid == modelGuid) {
|
||||
++callbackCount;
|
||||
}
|
||||
});
|
||||
runtimeRegistry.QueueEvent(MetaCore::MetaCoreAssetChangeEvent{
|
||||
MetaCore::MetaCoreAssetChangeType::Modified,
|
||||
modelGuid,
|
||||
modelGuid,
|
||||
"Assets/Model.glb",
|
||||
"Assets/Model.glb"
|
||||
});
|
||||
MetaCoreExpect(callbackCount == 0, "运行时失效事件应先进入队列");
|
||||
runtimeRegistry.DispatchQueuedEvents();
|
||||
MetaCoreExpect(callbackCount == 1, "主线程分发后运行时失效订阅者应收到事件");
|
||||
runtimeRegistry.Unsubscribe(subscription);
|
||||
graph.Clear();
|
||||
}
|
||||
|
||||
void MetaCoreTestAssetRegistryPathIdentity() {
|
||||
auto& registry = MetaCore::MetaCoreAssetRegistry::Get();
|
||||
registry.Clear();
|
||||
|
||||
const MetaCore::MetaCoreAssetGuid modelGuid = MetaCore::MetaCoreAssetGuid::Generate();
|
||||
const MetaCore::MetaCoreAssetGuid meshGuid = MetaCore::MetaCoreAssetGuid::Generate();
|
||||
MetaCoreExpect(registry.RegisterAsset(modelGuid, "Assets/Models/CaseModel.glb"), "主资源应能注册");
|
||||
MetaCoreExpect(registry.RegisterSubAsset(meshGuid, "Assets/Models/CaseModel.glb"), "子资源应能注册");
|
||||
MetaCoreExpect(registry.ResolvePathToGuid("Assets/Models/CaseModel.glb") == modelGuid, "子资源不应覆盖主资源路径反查");
|
||||
MetaCoreExpect(registry.ResolveGuidToPath(meshGuid) == std::filesystem::path("Assets/Models/CaseModel.glb"), "子资源 GUID 应解析到源文件");
|
||||
#if defined(_WIN32)
|
||||
MetaCoreExpect(registry.ResolvePathToGuid("assets/models/casemodel.glb") == modelGuid, "Windows 路径反查应忽略 ASCII 大小写");
|
||||
#else
|
||||
MetaCoreExpect(!registry.ResolvePathToGuid("assets/models/casemodel.glb").IsValid(), "Linux 路径反查应保留大小写");
|
||||
#endif
|
||||
MetaCoreExpect(registry.MoveAsset(modelGuid, "Assets/Models/Moved.glb"), "主资源路径应能移动");
|
||||
MetaCoreExpect(!registry.ResolvePathToGuid("Assets/Models/CaseModel.glb").IsValid(), "移动后旧路径应失效");
|
||||
MetaCoreExpect(registry.ResolvePathToGuid("Assets/Models/Moved.glb") == modelGuid, "移动后新路径应生效");
|
||||
registry.Clear();
|
||||
}
|
||||
|
||||
void MetaCoreTestModelMetadataKeepsSubAssetGuidsAfterMove() {
|
||||
const std::filesystem::path tempProjectRoot =
|
||||
std::filesystem::temp_directory_path() / "MetaCoreStableSubAssetMoveProject";
|
||||
std::filesystem::remove_all(tempProjectRoot);
|
||||
std::filesystem::create_directories(tempProjectRoot / "Assets" / "Moved");
|
||||
std::filesystem::create_directories(tempProjectRoot / "Scenes");
|
||||
{
|
||||
std::ofstream projectFile(tempProjectRoot / "MetaCore.project.json", std::ios::trunc);
|
||||
projectFile << "{\"name\":\"StableMove\",\"version\":\"0.1.0\",\"scenes\":[],\"startup_scene\":\"\"}";
|
||||
}
|
||||
{
|
||||
std::ofstream modelFile(tempProjectRoot / "Assets" / "Pump.gltf", std::ios::trunc);
|
||||
modelFile << "{\"meshes\":[{\"name\":\"PumpMesh\"}],\"materials\":[{\"name\":\"PumpMaterial\"}],\"images\":[{\"uri\":\"Textures/PumpBaseColor.png\"}]}";
|
||||
}
|
||||
|
||||
_putenv_s("METACORE_PROJECT_PATH", tempProjectRoot.string().c_str());
|
||||
MetaCore::MetaCoreEditorModuleRegistry moduleRegistry;
|
||||
auto coreServicesModule = MetaCore::MetaCoreCreateBuiltinCoreServicesModule();
|
||||
coreServicesModule->Startup(moduleRegistry);
|
||||
const auto assetDatabase = moduleRegistry.ResolveService<MetaCore::MetaCoreIAssetDatabaseService>();
|
||||
const auto importPipeline = moduleRegistry.ResolveService<MetaCore::MetaCoreIImportPipelineService>();
|
||||
MetaCoreExpect(assetDatabase != nullptr && importPipeline != nullptr, "应解析到资源服务");
|
||||
|
||||
const auto sourceRecord = assetDatabase->FindAssetByRelativePath(std::filesystem::path("Assets") / "Pump.gltf");
|
||||
MetaCoreExpect(sourceRecord.has_value(), "应导入 Pump 模型");
|
||||
nlohmann::json beforeJson;
|
||||
{
|
||||
std::ifstream input(tempProjectRoot / "Assets" / "Pump.gltf.mcmeta");
|
||||
input >> beforeJson;
|
||||
}
|
||||
MetaCoreExpect(beforeJson.contains("sub_assets") && !beforeJson["sub_assets"].empty(), "模型元数据应包含子资源");
|
||||
std::unordered_map<std::string, std::string> guidByStableKey;
|
||||
for (const auto& subAsset : beforeJson["sub_assets"]) {
|
||||
MetaCoreExpect(subAsset.contains("stable_import_key"), "子资源元数据应写入 stable_import_key");
|
||||
guidByStableKey[subAsset["stable_import_key"].get<std::string>()] = subAsset["guid"].get<std::string>();
|
||||
}
|
||||
|
||||
MetaCoreExpect(assetDatabase->MovePath(std::filesystem::path("Assets") / "Pump.gltf", std::filesystem::path("Assets") / "Moved"), "应移动模型资源");
|
||||
MetaCoreExpect(importPipeline->ReimportAsset(sourceRecord->Guid), "移动后应能强制重导模型");
|
||||
MetaCoreExpect(assetDatabase->Refresh(), "重导后应能刷新数据库");
|
||||
|
||||
nlohmann::json afterJson;
|
||||
{
|
||||
std::ifstream input(tempProjectRoot / "Assets" / "Moved" / "Pump.gltf.mcmeta");
|
||||
input >> afterJson;
|
||||
}
|
||||
MetaCoreExpect(afterJson["source_path"] == "Assets/Moved/Pump.gltf", "移动后 mcmeta source_path 应更新");
|
||||
for (const auto& subAsset : afterJson["sub_assets"]) {
|
||||
const std::string stableKey = subAsset["stable_import_key"].get<std::string>();
|
||||
MetaCoreExpect(guidByStableKey.at(stableKey) == subAsset["guid"].get<std::string>(), "移动并重导后子资源 GUID 应保持");
|
||||
}
|
||||
|
||||
coreServicesModule->Shutdown(moduleRegistry);
|
||||
moduleRegistry.ShutdownServices();
|
||||
_putenv_s("METACORE_PROJECT_PATH", "");
|
||||
std::filesystem::remove_all(tempProjectRoot);
|
||||
}
|
||||
|
||||
void MetaCoreTestHotReloadQueuesExternalMoveUntilTick() {
|
||||
const std::filesystem::path tempProjectRoot =
|
||||
std::filesystem::temp_directory_path() / "MetaCoreHotReloadMoveProject";
|
||||
std::filesystem::remove_all(tempProjectRoot);
|
||||
std::filesystem::create_directories(tempProjectRoot / "Assets");
|
||||
std::filesystem::create_directories(tempProjectRoot / "Scenes");
|
||||
{
|
||||
std::ofstream projectFile(tempProjectRoot / "MetaCore.project.json", std::ios::trunc);
|
||||
projectFile << "{\"name\":\"HotReloadMove\",\"version\":\"0.1.0\",\"scenes\":[],\"startup_scene\":\"\"}";
|
||||
}
|
||||
{
|
||||
std::ofstream sourceFile(tempProjectRoot / "Assets" / "Original.dat", std::ios::trunc);
|
||||
sourceFile << "stable move payload";
|
||||
}
|
||||
|
||||
_putenv_s("METACORE_PROJECT_PATH", tempProjectRoot.string().c_str());
|
||||
MetaCore::MetaCoreEditorModuleRegistry moduleRegistry;
|
||||
auto coreServicesModule = MetaCore::MetaCoreCreateBuiltinCoreServicesModule();
|
||||
coreServicesModule->Startup(moduleRegistry);
|
||||
const auto assetDatabase = moduleRegistry.ResolveService<MetaCore::MetaCoreIAssetDatabaseService>();
|
||||
const auto hotReload = moduleRegistry.ResolveService<MetaCore::MetaCoreIAssetHotReloadService>();
|
||||
MetaCoreExpect(assetDatabase != nullptr && hotReload != nullptr, "应解析到热更新服务");
|
||||
const auto originalRecord = assetDatabase->FindAssetByRelativePath(std::filesystem::path("Assets") / "Original.dat");
|
||||
MetaCoreExpect(originalRecord.has_value(), "应导入原始文件");
|
||||
|
||||
std::filesystem::rename(tempProjectRoot / "Assets" / "Original.dat", tempProjectRoot / "Assets" / "Renamed.dat");
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1300));
|
||||
MetaCoreExpect(!std::filesystem::exists(tempProjectRoot / "Assets" / "Renamed.dat.mcmeta"), "后台监听线程不应直接移动 mcmeta");
|
||||
hotReload->Tick();
|
||||
|
||||
const auto renamedRecord = assetDatabase->FindAssetByRelativePath(std::filesystem::path("Assets") / "Renamed.dat");
|
||||
MetaCoreExpect(renamedRecord.has_value(), "主线程 Tick 后应注册外部移动后的文件");
|
||||
MetaCoreExpect(renamedRecord->Guid == originalRecord->Guid, "外部移动后应保留资源 GUID");
|
||||
MetaCoreExpect(std::filesystem::exists(tempProjectRoot / "Assets" / "Renamed.dat.mcmeta"), "主线程 Tick 后应移动 mcmeta");
|
||||
MetaCoreExpect(!std::filesystem::exists(tempProjectRoot / "Assets" / "Original.dat.mcmeta"), "外部移动后旧 mcmeta 应消失");
|
||||
|
||||
coreServicesModule->Shutdown(moduleRegistry);
|
||||
moduleRegistry.ShutdownServices();
|
||||
_putenv_s("METACORE_PROJECT_PATH", "");
|
||||
std::filesystem::remove_all(tempProjectRoot);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
@ -3125,6 +3290,15 @@ int main() {
|
||||
logService.AddEntry(MetaCore::MetaCoreLogLevel::Info, "Test", "Smoke");
|
||||
MetaCoreExpect(logService.GetEntries().size() == 1, "日志服务应记录一条日志");
|
||||
|
||||
std::cout << "[RUN] MetaCoreTestAssetDependencyGraphAndRuntimeRegistry..." << std::endl;
|
||||
MetaCoreTestAssetDependencyGraphAndRuntimeRegistry();
|
||||
std::cout << "[RUN] MetaCoreTestAssetRegistryPathIdentity..." << std::endl;
|
||||
MetaCoreTestAssetRegistryPathIdentity();
|
||||
std::cout << "[RUN] MetaCoreTestModelMetadataKeepsSubAssetGuidsAfterMove..." << std::endl;
|
||||
MetaCoreTestModelMetadataKeepsSubAssetGuidsAfterMove();
|
||||
std::cout << "[RUN] MetaCoreTestHotReloadQueuesExternalMoveUntilTick..." << std::endl;
|
||||
MetaCoreTestHotReloadQueuesExternalMoveUntilTick();
|
||||
|
||||
std::cout << "[RUN] MetaCoreTestSceneEditApi..." << std::endl;
|
||||
MetaCoreTestSceneEditApi();
|
||||
std::cout << "[RUN] MetaCoreTestSelectionStateMachine..." << std::endl;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user