资源管理更新

This commit is contained in:
Rowland 2026-06-04 09:33:04 +08:00
parent f5aa77b71e
commit c8b8e79dd7
22 changed files with 1502 additions and 75 deletions

View File

@ -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(

View File

@ -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"
}
]

View File

@ -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"
}
]

View File

@ -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"
}
]

View File

@ -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"
}
]

View File

@ -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>());

View File

@ -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 {

View File

@ -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();

View File

@ -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()

View File

@ -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;

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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;