Build runtime data workflow and localize design docs

This commit is contained in:
ayuan9957 2026-03-28 09:44:06 +08:00
parent 1f7c4b5cda
commit c8ba59f07a
82 changed files with 12256 additions and 468 deletions

View File

@ -1,11 +1,17 @@
#include "MetaCorePlatform/MetaCoreWindow.h" #include "MetaCorePlatform/MetaCoreWindow.h"
#include "MetaCoreFoundation/MetaCoreGeneratedReflection.h"
#include "MetaCoreRender/MetaCoreEditorViewportRenderer.h" #include "MetaCoreRender/MetaCoreEditorViewportRenderer.h"
#include "MetaCoreRender/MetaCoreRenderDevice.h" #include "MetaCoreRender/MetaCoreRenderDevice.h"
#include "MetaCoreRender/MetaCoreRenderTypes.h" #include "MetaCoreRender/MetaCoreRenderTypes.h"
#include "MetaCoreRuntimeData/MetaCoreRuntimeDataDispatcher.h"
#include "MetaCoreRuntimeData/MetaCoreRuntimeDataProject.h"
#include "MetaCoreRuntimeData/MetaCoreRuntimeDataSource.h"
#include "MetaCoreScene/MetaCoreScenePackage.h"
#include "MetaCoreScene/MetaCoreScene.h" #include "MetaCoreScene/MetaCoreScene.h"
#include <filesystem> #include <filesystem>
#include <iostream> #include <iostream>
#include <memory>
namespace { namespace {
@ -18,6 +24,142 @@ MetaCore::MetaCoreSceneView MetaCoreBuildPlayerSceneView() {
return sceneView; return sceneView;
} }
[[nodiscard]] std::filesystem::path MetaCoreResolveRuntimePath(const std::filesystem::path& relativePath) {
const std::vector<std::filesystem::path> candidates{
std::filesystem::current_path() / relativePath,
std::filesystem::current_path() / ".." / ".." / ".." / relativePath,
std::filesystem::current_path() / ".." / ".." / ".." / "TestProject" / "Runtime" / relativePath.filename()
};
for (const std::filesystem::path& candidate : candidates) {
std::error_code errorCode;
if (std::filesystem::exists(candidate, errorCode)) {
return std::filesystem::weakly_canonical(candidate, errorCode);
}
}
return candidates.front();
}
[[nodiscard]] std::filesystem::path MetaCoreResolveProjectPath(const std::filesystem::path& relativePath) {
const std::vector<std::filesystem::path> candidates{
std::filesystem::current_path() / relativePath,
std::filesystem::current_path() / ".." / ".." / ".." / relativePath
};
for (const std::filesystem::path& candidate : candidates) {
std::error_code errorCode;
if (std::filesystem::exists(candidate, errorCode)) {
return std::filesystem::weakly_canonical(candidate, errorCode);
}
}
return candidates.front();
}
[[nodiscard]] MetaCore::MetaCoreRuntimeProjectDocument MetaCoreBuildDefaultRuntimeProjectDocument() {
MetaCore::MetaCoreRuntimeProjectDocument document;
document.StartupScenePath = std::filesystem::path("Scenes") / "Main.mcscene";
document.DataSourcesPath = std::filesystem::path("Runtime") / "DataSources.mcruntime";
document.BindingsPath = std::filesystem::path("Runtime") / "Bindings.mcruntime";
document.DiagnosticsPath = std::filesystem::path("Runtime") / "Diagnostics.mcruntimestate";
return document;
}
[[nodiscard]] MetaCore::MetaCoreRuntimeDataSourcesDocument MetaCoreBuildDefaultRuntimeDataSourcesDocument() {
MetaCore::MetaCoreRuntimeDataSourcesDocument document;
document.Sources.push_back(MetaCore::MetaCoreDataSourceDefinition{
"replay-source",
"file_replay",
"Replay Source",
{MetaCore::MetaCoreDataSourceSetting{"file_path", "TestProject/Runtime/RuntimeReplay.mcstream"}},
true,
1000
});
document.DataPoints.push_back(MetaCore::MetaCoreDataPointDefinition{
"cube.position",
"replay-source",
"cube.position",
MetaCore::MetaCoreRuntimeValueType::Vec3
});
document.DataPoints.push_back(MetaCore::MetaCoreDataPointDefinition{
"cube.visible",
"replay-source",
"cube.visible",
MetaCore::MetaCoreRuntimeValueType::Bool
});
document.DataPoints.push_back(MetaCore::MetaCoreDataPointDefinition{
"cube.base_color",
"replay-source",
"cube.base_color",
MetaCore::MetaCoreRuntimeValueType::Vec3
});
document.DataPoints.push_back(MetaCore::MetaCoreDataPointDefinition{
"valve.visible",
"replay-source",
"valve.visible",
MetaCore::MetaCoreRuntimeValueType::Bool
});
document.DataPoints.push_back(MetaCore::MetaCoreDataPointDefinition{
"tank.base_color",
"replay-source",
"tank.base_color",
MetaCore::MetaCoreRuntimeValueType::Vec3
});
document.DataPoints.push_back(MetaCore::MetaCoreDataPointDefinition{
"alarm.intensity",
"replay-source",
"alarm.intensity",
MetaCore::MetaCoreRuntimeValueType::Double
});
return document;
}
[[nodiscard]] MetaCore::MetaCoreRuntimeBindingsDocument MetaCoreBuildDefaultRuntimeBindingsDocument() {
MetaCore::MetaCoreRuntimeBindingsDocument document;
document.Bindings.push_back(MetaCore::MetaCoreSceneBindingDefinition{
"binding.cube.position",
"cube.position",
3,
MetaCore::MetaCoreRuntimeBindingTarget::TransformPosition,
MetaCore::MetaCoreRuntimeMissingDataPolicy::KeepLastValue
});
document.Bindings.push_back(MetaCore::MetaCoreSceneBindingDefinition{
"binding.cube.visible",
"cube.visible",
3,
MetaCore::MetaCoreRuntimeBindingTarget::MeshRendererVisible,
MetaCore::MetaCoreRuntimeMissingDataPolicy::KeepLastValue
});
document.Bindings.push_back(MetaCore::MetaCoreSceneBindingDefinition{
"binding.cube.base_color",
"cube.base_color",
3,
MetaCore::MetaCoreRuntimeBindingTarget::MeshRendererBaseColor,
MetaCore::MetaCoreRuntimeMissingDataPolicy::KeepLastValue
});
document.Bindings.push_back(MetaCore::MetaCoreSceneBindingDefinition{
"binding.valve.visible",
"valve.visible",
4,
MetaCore::MetaCoreRuntimeBindingTarget::MeshRendererVisible,
MetaCore::MetaCoreRuntimeMissingDataPolicy::KeepLastValue
});
document.Bindings.push_back(MetaCore::MetaCoreSceneBindingDefinition{
"binding.tank.base_color",
"tank.base_color",
5,
MetaCore::MetaCoreRuntimeBindingTarget::MeshRendererBaseColor,
MetaCore::MetaCoreRuntimeMissingDataPolicy::KeepLastValue
});
document.Bindings.push_back(MetaCore::MetaCoreSceneBindingDefinition{
"binding.alarm.intensity",
"alarm.intensity",
6,
MetaCore::MetaCoreRuntimeBindingTarget::LightIntensity,
MetaCore::MetaCoreRuntimeMissingDataPolicy::KeepLastValue
});
return document;
}
} // namespace } // namespace
int main(int argc, char* argv[]) { int main(int argc, char* argv[]) {
@ -43,8 +185,86 @@ int main(int argc, char* argv[]) {
return 1; return 1;
} }
MetaCore::MetaCoreScene scene = MetaCore::MetaCoreCreateDefaultScene(); const auto projectPath = MetaCoreResolveProjectPath("TestProject/MetaCore.project.json");
const std::filesystem::path projectRoot = projectPath.parent_path();
MetaCore::MetaCoreTypeRegistry typeRegistry;
MetaCore::MetaCoreRegisterRuntimeDataGeneratedTypes(typeRegistry);
const auto loadedRuntimeProjectDocument = MetaCore::MetaCoreReadRuntimeProjectDocument(
projectRoot / "Runtime" / "ProjectRuntime.mcruntimecfg",
typeRegistry
);
const auto runtimeProjectDocument = loadedRuntimeProjectDocument.value_or(MetaCoreBuildDefaultRuntimeProjectDocument());
std::optional<MetaCore::MetaCoreSceneDocument> startupSceneDocument;
if (!runtimeProjectDocument.StartupScenePath.empty()) {
startupSceneDocument = MetaCore::MetaCoreReadScenePackage(projectRoot / runtimeProjectDocument.StartupScenePath);
}
if (!startupSceneDocument.has_value()) {
startupSceneDocument = MetaCore::MetaCoreLoadStartupSceneDocument(projectPath);
}
MetaCore::MetaCoreScene scene;
if (startupSceneDocument.has_value()) {
MetaCore::MetaCoreSceneSnapshot snapshot;
snapshot.GameObjects = startupSceneDocument->GameObjects;
scene.RestoreSnapshot(snapshot);
std::cout << "MetaCorePlayer: loaded startup scene from project "
<< projectPath.string() << '\n';
} else {
scene = MetaCore::MetaCoreCreateDefaultScene();
std::cout << "MetaCorePlayer: startup scene unavailable, using built-in default scene\n";
}
MetaCore::MetaCoreRuntimeDataDispatcher runtimeDataDispatcher(scene);
const auto sourcesPath = MetaCoreResolveRuntimePath(projectRoot / runtimeProjectDocument.DataSourcesPath);
const auto bindingsPath = MetaCoreResolveRuntimePath(projectRoot / runtimeProjectDocument.BindingsPath);
const auto diagnosticsPath = MetaCoreResolveRuntimePath(projectRoot / runtimeProjectDocument.DiagnosticsPath);
const auto loadedSourcesDocument = MetaCore::MetaCoreReadRuntimeDataSourcesDocument(sourcesPath, typeRegistry);
const auto loadedBindingsDocument = MetaCore::MetaCoreReadRuntimeBindingsDocument(bindingsPath, typeRegistry);
const auto sourcesDocument = loadedSourcesDocument.value_or(MetaCoreBuildDefaultRuntimeDataSourcesDocument());
const auto bindingsDocument = loadedBindingsDocument.value_or(MetaCoreBuildDefaultRuntimeBindingsDocument());
if (loadedSourcesDocument.has_value() && loadedBindingsDocument.has_value()) {
std::cout << "MetaCorePlayer: loaded runtime config from binary .mcruntime documents\n";
} else {
const bool sourcesExists = std::filesystem::exists(sourcesPath);
const bool bindingsExists = std::filesystem::exists(bindingsPath);
if ((sourcesExists && !loadedSourcesDocument.has_value()) ||
(bindingsExists && !loadedBindingsDocument.has_value())) {
std::cerr << "MetaCorePlayer: runtime config exists but is unreadable or corrupted, using built-in fallback config\n";
} else {
std::cout << "MetaCorePlayer: runtime config missing, using built-in fallback config\n";
}
}
runtimeDataDispatcher.SetDataPointDefinitions(sourcesDocument.DataPoints);
runtimeDataDispatcher.SetBindingDefinitions(bindingsDocument.Bindings);
std::unique_ptr<MetaCore::MetaCoreIRuntimeDataSourceAdapter> runtimeAdapter;
if (!sourcesDocument.Sources.empty()) {
runtimeAdapter = MetaCore::MetaCoreCreateRuntimeDataSourceAdapter(sourcesDocument.Sources.front().AdapterType);
if (runtimeAdapter == nullptr) {
std::cerr << "MetaCorePlayer: unsupported adapter type "
<< sourcesDocument.Sources.front().AdapterType << '\n';
return 1;
}
if (!runtimeAdapter->Configure(sourcesDocument.Sources.front())) {
std::cerr << "MetaCorePlayer: failed to configure runtime adapter error="
<< runtimeAdapter->GetStatus().LastError << '\n';
return 1;
}
if (!runtimeAdapter->Connect()) {
std::cerr << "MetaCorePlayer: failed to connect runtime adapter error="
<< runtimeAdapter->GetStatus().LastError << '\n';
return 1;
}
}
MetaCore::MetaCoreRuntimeDataSourceState lastReportedSourceState =
runtimeAdapter != nullptr
? runtimeAdapter->GetStatus().State
: MetaCore::MetaCoreRuntimeDataSourceState::Disconnected;
std::uint64_t diagnosticsWriteFrame = 0;
while (!window.ShouldClose()) { while (!window.ShouldClose()) {
window.BeginFrame(); window.BeginFrame();
const auto [windowWidth, windowHeight] = window.GetWindowSize(); const auto [windowWidth, windowHeight] = window.GetWindowSize();
@ -54,6 +274,35 @@ int main(int argc, char* argv[]) {
static_cast<float>(windowWidth), static_cast<float>(windowWidth),
static_cast<float>(windowHeight) static_cast<float>(windowHeight)
}); });
if (runtimeAdapter != nullptr) {
runtimeAdapter->Tick(1.0 / 60.0);
runtimeDataDispatcher.ApplyUpdates(runtimeAdapter->PollUpdates());
runtimeDataDispatcher.TickStaleness(runtimeAdapter->GetStatus().LastUpdateAt);
}
const auto diagnostics = runtimeDataDispatcher.BuildDiagnosticsSnapshot(
runtimeAdapter != nullptr
? std::vector<MetaCore::MetaCoreRuntimeDataSourceStatus>{runtimeAdapter->GetStatus()}
: std::vector<MetaCore::MetaCoreRuntimeDataSourceStatus>{}
);
if ((diagnosticsWriteFrame % 15ULL) == 0ULL) {
(void)MetaCore::MetaCoreWriteRuntimeDiagnosticsSnapshot(diagnosticsPath, diagnostics, typeRegistry);
}
++diagnosticsWriteFrame;
if (runtimeAdapter != nullptr && runtimeAdapter->GetStatus().State != lastReportedSourceState) {
std::cout << "MetaCorePlayer: data source state changed to "
<< static_cast<int>(runtimeAdapter->GetStatus().State)
<< " error=" << runtimeAdapter->GetStatus().LastError << '\n';
lastReportedSourceState = runtimeAdapter->GetStatus().State;
}
if (diagnostics.HasFaults) {
for (const MetaCore::MetaCoreRuntimeBindingStatus& bindingStatus : diagnostics.BindingStatuses) {
if (!bindingStatus.Healthy || bindingStatus.Stale) {
std::cout << "MetaCorePlayer: binding issue id=" << bindingStatus.BindingId
<< " stale=" << (bindingStatus.Stale ? "true" : "false")
<< " error=" << bindingStatus.LastError << '\n';
}
}
}
viewportRenderer.RenderSceneToViewport(scene, MetaCoreBuildPlayerSceneView()); viewportRenderer.RenderSceneToViewport(scene, MetaCoreBuildPlayerSceneView());
renderDevice.RenderFrame(); renderDevice.RenderFrame();
renderDevice.PresentFrame(); renderDevice.PresentFrame();

View File

@ -30,19 +30,90 @@ metacore_prepare_panda3d()
find_package(glm CONFIG REQUIRED) find_package(glm CONFIG REQUIRED)
find_package(imgui CONFIG REQUIRED) find_package(imgui CONFIG REQUIRED)
set(METACORE_COMMON_WARNINGS)
if(MSVC)
set(METACORE_COMMON_WARNINGS /W4 /permissive- /EHsc)
else()
set(METACORE_COMMON_WARNINGS -Wall -Wextra -Wpedantic)
endif()
add_executable(MetaCoreHeaderTool
Tools/MetaCoreHeaderTool/main.cpp
)
target_compile_options(MetaCoreHeaderTool PRIVATE ${METACORE_COMMON_WARNINGS})
add_executable(MetaCoreRuntimeConfigTool
Tools/MetaCoreRuntimeConfigTool/main.cpp
)
target_compile_options(MetaCoreRuntimeConfigTool PRIVATE ${METACORE_COMMON_WARNINGS})
add_executable(MetaCoreTcpSenderTool
Tools/MetaCoreTcpSenderTool/main.cpp
)
target_compile_options(MetaCoreTcpSenderTool PRIVATE ${METACORE_COMMON_WARNINGS})
target_link_libraries(MetaCoreTcpSenderTool PRIVATE ws2_32)
function(metacore_generate_reflection module_name function_name output_var)
set(generated_headers ${ARGN})
set(absolute_headers)
foreach(header IN LISTS generated_headers)
list(APPEND absolute_headers "${CMAKE_SOURCE_DIR}/${header}")
endforeach()
set(generated_cpp "${CMAKE_BINARY_DIR}/Generated/${module_name}/${module_name}GeneratedReflection.cpp")
add_custom_command(
OUTPUT "${generated_cpp}"
COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/Generated/${module_name}"
COMMAND $<TARGET_FILE:MetaCoreHeaderTool>
--output "${generated_cpp}"
--function "${function_name}"
--module "${module_name}"
${absolute_headers}
DEPENDS
MetaCoreHeaderTool
${absolute_headers}
VERBATIM
)
set(${output_var} "${generated_cpp}" PARENT_SCOPE)
endfunction()
set(METACORE_FOUNDATION_HEADERS set(METACORE_FOUNDATION_HEADERS
Source/MetaCoreFoundation/Public/MetaCoreFoundation/MetaCoreArchive.h
Source/MetaCoreFoundation/Public/MetaCoreFoundation/MetaCoreAssetGuid.h
Source/MetaCoreFoundation/Public/MetaCoreFoundation/MetaCoreGeneratedReflection.h
Source/MetaCoreFoundation/Public/MetaCoreFoundation/MetaCoreHash.h
Source/MetaCoreFoundation/Public/MetaCoreFoundation/MetaCoreId.h Source/MetaCoreFoundation/Public/MetaCoreFoundation/MetaCoreId.h
Source/MetaCoreFoundation/Public/MetaCoreFoundation/MetaCoreLogService.h Source/MetaCoreFoundation/Public/MetaCoreFoundation/MetaCoreLogService.h
Source/MetaCoreFoundation/Public/MetaCoreFoundation/MetaCorePackage.h
Source/MetaCoreFoundation/Public/MetaCoreFoundation/MetaCoreReflection.h
) )
set(METACORE_FOUNDATION_SOURCES set(METACORE_FOUNDATION_SOURCES
Source/MetaCoreFoundation/Private/MetaCoreArchive.cpp
Source/MetaCoreFoundation/Private/MetaCoreAssetGuid.cpp
Source/MetaCoreFoundation/Private/MetaCoreHash.cpp
Source/MetaCoreFoundation/Private/MetaCoreId.cpp Source/MetaCoreFoundation/Private/MetaCoreId.cpp
Source/MetaCoreFoundation/Private/MetaCoreLogService.cpp Source/MetaCoreFoundation/Private/MetaCoreLogService.cpp
Source/MetaCoreFoundation/Private/MetaCorePackage.cpp
Source/MetaCoreFoundation/Private/MetaCoreReflection.cpp
)
metacore_generate_reflection(
Foundation
MetaCoreRegisterFoundationGeneratedTypes
METACORE_FOUNDATION_GENERATED_SOURCE
Source/MetaCoreFoundation/Public/MetaCoreFoundation/MetaCoreAssetGuid.h
Source/MetaCoreFoundation/Public/MetaCoreFoundation/MetaCorePackage.h
) )
add_library(MetaCoreFoundation STATIC add_library(MetaCoreFoundation STATIC
${METACORE_FOUNDATION_HEADERS} ${METACORE_FOUNDATION_HEADERS}
${METACORE_FOUNDATION_SOURCES} ${METACORE_FOUNDATION_SOURCES}
${METACORE_FOUNDATION_GENERATED_SOURCE}
) )
target_include_directories(MetaCoreFoundation target_include_directories(MetaCoreFoundation
@ -50,12 +121,10 @@ target_include_directories(MetaCoreFoundation
Source/MetaCoreFoundation/Public Source/MetaCoreFoundation/Public
) )
set(METACORE_COMMON_WARNINGS) target_link_libraries(MetaCoreFoundation
if(MSVC) PUBLIC
set(METACORE_COMMON_WARNINGS /W4 /permissive- /EHsc) glm::glm
else() )
set(METACORE_COMMON_WARNINGS -Wall -Wextra -Wpedantic)
endif()
target_compile_options(MetaCoreFoundation PRIVATE ${METACORE_COMMON_WARNINGS}) target_compile_options(MetaCoreFoundation PRIVATE ${METACORE_COMMON_WARNINGS})
@ -92,16 +161,30 @@ target_compile_definitions(MetaCorePlatform PRIVATE WIN32_LEAN_AND_MEAN NOMINMAX
set(METACORE_SCENE_HEADERS set(METACORE_SCENE_HEADERS
Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreComponents.h Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreComponents.h
Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreGameObject.h Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreGameObject.h
Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreSceneDocument.h
Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreScenePackage.h
Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreScene.h Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreScene.h
) )
set(METACORE_SCENE_SOURCES set(METACORE_SCENE_SOURCES
Source/MetaCoreScene/Private/MetaCoreScenePackage.cpp
Source/MetaCoreScene/Private/MetaCoreScene.cpp Source/MetaCoreScene/Private/MetaCoreScene.cpp
) )
metacore_generate_reflection(
Scene
MetaCoreRegisterSceneGeneratedTypes
METACORE_SCENE_GENERATED_SOURCE
Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreComponents.h
Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreGameObject.h
Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreSceneDocument.h
Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreScene.h
)
add_library(MetaCoreScene STATIC add_library(MetaCoreScene STATIC
${METACORE_SCENE_HEADERS} ${METACORE_SCENE_HEADERS}
${METACORE_SCENE_SOURCES} ${METACORE_SCENE_SOURCES}
${METACORE_SCENE_GENERATED_SOURCE}
) )
target_include_directories(MetaCoreScene target_include_directories(MetaCoreScene
@ -151,11 +234,64 @@ target_link_libraries(MetaCoreRender
target_compile_options(MetaCoreRender PRIVATE ${METACORE_COMMON_WARNINGS}) target_compile_options(MetaCoreRender PRIVATE ${METACORE_COMMON_WARNINGS})
set(METACORE_RUNTIME_DATA_HEADERS
Source/MetaCoreRuntimeData/Public/MetaCoreRuntimeData/MetaCoreRuntimeDataDispatcher.h
Source/MetaCoreRuntimeData/Public/MetaCoreRuntimeData/MetaCoreRuntimeDataProject.h
Source/MetaCoreRuntimeData/Public/MetaCoreRuntimeData/MetaCoreRuntimeDataSource.h
Source/MetaCoreRuntimeData/Public/MetaCoreRuntimeData/MetaCoreRuntimeDataTypes.h
)
set(METACORE_RUNTIME_DATA_SOURCES
Source/MetaCoreRuntimeData/Private/MetaCoreRuntimeDataDispatcher.cpp
Source/MetaCoreRuntimeData/Private/MetaCoreRuntimeDataProject.cpp
Source/MetaCoreRuntimeData/Private/MetaCoreRuntimeDataSource.cpp
)
metacore_generate_reflection(
RuntimeData
MetaCoreRegisterRuntimeDataGeneratedTypes
METACORE_RUNTIME_DATA_GENERATED_SOURCE
Source/MetaCoreRuntimeData/Public/MetaCoreRuntimeData/MetaCoreRuntimeDataProject.h
Source/MetaCoreRuntimeData/Public/MetaCoreRuntimeData/MetaCoreRuntimeDataTypes.h
)
add_library(MetaCoreRuntimeData STATIC
${METACORE_RUNTIME_DATA_HEADERS}
${METACORE_RUNTIME_DATA_SOURCES}
${METACORE_RUNTIME_DATA_GENERATED_SOURCE}
)
target_include_directories(MetaCoreRuntimeData
PUBLIC
Source/MetaCoreRuntimeData/Public
)
target_link_libraries(MetaCoreRuntimeData
PUBLIC
MetaCoreFoundation
MetaCoreScene
glm::glm
PRIVATE
ws2_32
)
target_compile_options(MetaCoreRuntimeData PRIVATE ${METACORE_COMMON_WARNINGS})
target_link_libraries(MetaCoreRuntimeConfigTool
PRIVATE
MetaCoreFoundation
MetaCoreRuntimeData
MetaCoreScene
)
set(METACORE_EDITOR_HEADERS set(METACORE_EDITOR_HEADERS
Source/MetaCoreEditor/Public/MetaCoreEditor/MetaCoreBuiltinModules.h
Source/MetaCoreEditor/Public/MetaCoreEditor/MetaCoreEditorApp.h Source/MetaCoreEditor/Public/MetaCoreEditor/MetaCoreEditorApp.h
Source/MetaCoreEditor/Public/MetaCoreEditor/MetaCoreEditorAssetTypes.h
Source/MetaCoreEditor/Public/MetaCoreEditor/MetaCoreEditorCommandService.h Source/MetaCoreEditor/Public/MetaCoreEditor/MetaCoreEditorCommandService.h
Source/MetaCoreEditor/Public/MetaCoreEditor/MetaCoreEditorContext.h Source/MetaCoreEditor/Public/MetaCoreEditor/MetaCoreEditorContext.h
Source/MetaCoreEditor/Public/MetaCoreEditor/MetaCoreEditorModule.h Source/MetaCoreEditor/Public/MetaCoreEditor/MetaCoreEditorModule.h
Source/MetaCoreEditor/Public/MetaCoreEditor/MetaCoreEditorServices.h
Source/MetaCoreEditor/Public/MetaCoreEditor/MetaCoreSceneInteractionService.h Source/MetaCoreEditor/Public/MetaCoreEditor/MetaCoreSceneInteractionService.h
) )
@ -166,20 +302,31 @@ set(METACORE_EDITOR_PRIVATE_HEADERS
) )
set(METACORE_EDITOR_SOURCES set(METACORE_EDITOR_SOURCES
Source/MetaCoreEditor/Private/MetaCoreBuiltinCoreServicesModule.cpp
Source/MetaCoreEditor/Private/MetaCoreBuiltinEditorModule.cpp Source/MetaCoreEditor/Private/MetaCoreBuiltinEditorModule.cpp
Source/MetaCoreEditor/Private/MetaCoreEditorApp.cpp Source/MetaCoreEditor/Private/MetaCoreEditorApp.cpp
Source/MetaCoreEditor/Private/MetaCoreEditorCameraController.cpp Source/MetaCoreEditor/Private/MetaCoreEditorCameraController.cpp
Source/MetaCoreEditor/Private/MetaCoreEditorCommandService.cpp Source/MetaCoreEditor/Private/MetaCoreEditorCommandService.cpp
Source/MetaCoreEditor/Private/MetaCoreEditorContext.cpp Source/MetaCoreEditor/Private/MetaCoreEditorContext.cpp
Source/MetaCoreEditor/Private/MetaCoreEditorServices.cpp
Source/MetaCoreEditor/Private/MetaCoreEditorModule.cpp Source/MetaCoreEditor/Private/MetaCoreEditorModule.cpp
Source/MetaCoreEditor/Private/MetaCoreSceneInteractionService.cpp Source/MetaCoreEditor/Private/MetaCoreSceneInteractionService.cpp
third_party/ImGuizmo/ImGuizmo.cpp third_party/ImGuizmo/ImGuizmo.cpp
) )
metacore_generate_reflection(
Editor
MetaCoreRegisterEditorGeneratedTypes
METACORE_EDITOR_GENERATED_SOURCE
Source/MetaCoreEditor/Public/MetaCoreEditor/MetaCoreEditorAssetTypes.h
Source/MetaCoreEditor/Public/MetaCoreEditor/MetaCoreEditorContext.h
)
add_library(MetaCoreEditor STATIC add_library(MetaCoreEditor STATIC
${METACORE_EDITOR_HEADERS} ${METACORE_EDITOR_HEADERS}
${METACORE_EDITOR_PRIVATE_HEADERS} ${METACORE_EDITOR_PRIVATE_HEADERS}
${METACORE_EDITOR_SOURCES} ${METACORE_EDITOR_SOURCES}
${METACORE_EDITOR_GENERATED_SOURCE}
) )
target_include_directories(MetaCoreEditor target_include_directories(MetaCoreEditor
@ -187,6 +334,7 @@ target_include_directories(MetaCoreEditor
Source/MetaCoreEditor/Public Source/MetaCoreEditor/Public
PRIVATE PRIVATE
Source/MetaCoreEditor/Private Source/MetaCoreEditor/Private
third_party
third_party/ImGuizmo third_party/ImGuizmo
) )
@ -195,6 +343,7 @@ target_link_libraries(MetaCoreEditor
MetaCoreFoundation MetaCoreFoundation
MetaCorePlatform MetaCorePlatform
MetaCoreRender MetaCoreRender
MetaCoreRuntimeData
MetaCoreScene MetaCoreScene
glm::glm glm::glm
imgui::imgui imgui::imgui
@ -223,6 +372,7 @@ target_link_libraries(MetaCorePlayer
MetaCoreFoundation MetaCoreFoundation
MetaCorePlatform MetaCorePlatform
MetaCoreRender MetaCoreRender
MetaCoreRuntimeData
MetaCoreScene MetaCoreScene
MetaCorePanda3D::SDK MetaCorePanda3D::SDK
) )
@ -239,6 +389,7 @@ if(METACORE_BUILD_TESTS)
target_link_libraries(MetaCoreSmokeTests target_link_libraries(MetaCoreSmokeTests
PRIVATE PRIVATE
MetaCoreEditor MetaCoreEditor
MetaCoreRuntimeData
) )
metacore_stage_panda3d_runtime(MetaCoreSmokeTests) metacore_stage_panda3d_runtime(MetaCoreSmokeTests)

View File

@ -1,5 +0,0 @@
{
"id": "00005031af6c28bc73f3772350037bcf00000005",
"relative_path": "Assets/Materials/default_pbr.material.json",
"type": "material"
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,5 +0,0 @@
{
"id": "00005031af6c1e30843b82e4691e8cba00000004",
"relative_path": "Assets/UI/main_menu.rcss",
"type": "ui_stylesheet"
}

Binary file not shown.

Binary file not shown.

View File

@ -1,5 +0,0 @@
{
"id": "00005031af6bf9783f1fb78dcaa5ed8900000003",
"relative_path": "Assets/UI/main_menu.rml",
"type": "ui_document"
}

View File

@ -1,19 +0,0 @@
{
"records": [
{
"id": "00005031af6bf9783f1fb78dcaa5ed8900000003",
"relative_path": "Assets/UI/main_menu.rml",
"type": "ui_document"
},
{
"id": "00005031af6c1e30843b82e4691e8cba00000004",
"relative_path": "Assets/UI/main_menu.rcss",
"type": "ui_stylesheet"
},
{
"id": "00005031af6c28bc73f3772350037bcf00000005",
"relative_path": "Assets/Materials/default_pbr.material.json",
"type": "material"
}
]
}

Binary file not shown.

View File

@ -1,8 +1,8 @@
{ {
"name": "MetaCoreSample", "name": "MetaCoreSample",
"version": "0.1.0",
"startup_scene": "Scenes/Main.mcscene",
"scenes": [ "scenes": [
"Scenes/Main.mcscene.json" "Scenes/Main.mcscene"
], ]
"startup_scene": "Scenes/Main.mcscene.json",
"version": "0.1.0"
} }

Binary file not shown.

View File

@ -1,140 +0,0 @@
{
"name": "Main",
"objects": [
{
"camera": {
"far_clip": 1000,
"fov_degrees": 60,
"near_clip": 0.100000001490116,
"primary": true
},
"editor_metadata": {
"editor_only": false,
"locked": false,
"selected": false
},
"id": "00005031af666d280adc4216764a07c100000001",
"name": "Main Camera",
"parent_id": "",
"transform": {
"position": [
0,
-10,
3
],
"rotation": [
0,
0,
0
],
"scale": [
1,
1,
1
]
}
},
{
"editor_metadata": {
"editor_only": false,
"locked": false,
"selected": false
},
"id": "00005031af669a14494b5bcf7b0288f500000002",
"light": {
"color": [
1,
1,
1
],
"intensity": 1,
"range": 10,
"spot_angle_degrees": 45,
"type": "directional"
},
"name": "Directional Light",
"parent_id": "",
"transform": {
"position": [
0,
0,
0
],
"rotation": [
0,
0,
0
],
"scale": [
1,
1,
1
]
}
},
{
"editor_metadata": {
"editor_only": false,
"locked": false,
"selected": false
},
"id": "00005031af7e8fac310b0d408494fcfb00000008",
"mesh_renderer": {
"material_asset_id": "builtin://editor-default",
"mesh_asset_id": "builtin://box",
"visible": true
},
"name": "Demo Cube",
"parent_id": "",
"transform": {
"position": [
0,
0,
0
],
"rotation": [
0,
0,
0
],
"scale": [
1,
1,
1
]
}
},
{
"editor_metadata": {
"editor_only": false,
"locked": false,
"selected": false
},
"id": "0000399eef6307dc90859f938da98af700000003",
"name": "UI Root",
"parent_id": "",
"transform": {
"position": [
0,
0,
0
],
"rotation": [
0,
0,
0
],
"scale": [
1,
1,
1
]
},
"ui_document": {
"document_path": "Assets/UI/main_menu.rml",
"stylesheet_path": "Assets/UI/main_menu.rcss",
"visible": true
}
}
]
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,12 +1,3 @@
#pragma once #pragma once
#include "MetaCoreEditor/MetaCoreEditorModule.h" #include "MetaCoreEditor/MetaCoreBuiltinModules.h"
#include <memory>
namespace MetaCore {
// 创建内置 Unity-like 编辑器模块。
std::unique_ptr<MetaCoreIModule> MetaCoreCreateBuiltinEditorModule();
} // namespace MetaCore

View File

@ -1,6 +1,7 @@
#include "MetaCoreEditor/MetaCoreEditorApp.h" #include "MetaCoreEditor/MetaCoreEditorApp.h"
#include "MetaCoreBuiltinEditorModule.h" #include "MetaCoreEditor/MetaCoreBuiltinModules.h"
#include "MetaCoreEditor/MetaCoreEditorServices.h"
#include "MetaCoreEditorCameraController.h" #include "MetaCoreEditorCameraController.h"
#include <imgui.h> #include <imgui.h>
@ -188,10 +189,11 @@ bool MetaCoreEditorApp::Initialize() {
return false; return false;
} }
MetaCoreTraceStartup("init: create default scene"); MetaCoreTraceStartup("init: create bootstrap scene");
Scene_ = MetaCoreCreateDefaultScene(); Scene_ = MetaCoreCreateDefaultScene();
MetaCoreTraceStartup("init: create modules"); MetaCoreTraceStartup("init: create modules");
Modules_.push_back(MetaCoreCreateBuiltinEditorModule()); Modules_.push_back(MetaCoreCreateBuiltinCoreServicesModule());
Modules_.push_back(MetaCoreCreateBuiltinEditorViewsModule());
MetaCoreTraceStartup("init: startup modules"); MetaCoreTraceStartup("init: startup modules");
for (const auto& module : Modules_) { for (const auto& module : Modules_) {
module->Startup(ModuleRegistry_); module->Startup(ModuleRegistry_);
@ -207,18 +209,48 @@ bool MetaCoreEditorApp::Initialize() {
ModuleRegistry_ ModuleRegistry_
); );
MetaCoreTraceStartup("init: select default cube"); MetaCoreTraceStartup("init: load startup scene");
bool loadedStartupScene = false;
if (const auto scenePersistenceService = ModuleRegistry_.ResolveService<MetaCoreIScenePersistenceService>();
scenePersistenceService != nullptr) {
loadedStartupScene = scenePersistenceService->LoadStartupScene(*EditorContext_);
if (!loadedStartupScene) {
if (const auto assetDatabaseService = ModuleRegistry_.ResolveService<MetaCoreIAssetDatabaseService>();
assetDatabaseService != nullptr &&
assetDatabaseService->HasProject() &&
assetDatabaseService->GetProjectDescriptor().ScenePaths.empty()) {
const std::filesystem::path bootstrapScenePath = std::filesystem::path("Scenes") / "Main.mcscene";
if (scenePersistenceService->SaveSceneAs(*EditorContext_, bootstrapScenePath)) {
(void)assetDatabaseService->SetStartupScenePath(bootstrapScenePath);
loadedStartupScene = true;
EditorContext_->AddConsoleMessage(
MetaCoreLogLevel::Info,
"Project",
"已创建默认启动场景: " + bootstrapScenePath.generic_string()
);
}
}
}
}
MetaCoreTraceStartup("init: select default focus object");
if (EditorContext_->GetSelectedObjectId() == 0) {
for (const MetaCoreGameObject& object : Scene_.GetGameObjects()) { for (const MetaCoreGameObject& object : Scene_.GetGameObjects()) {
if (object.Name == "Cube") { if (object.Name == "Cube" || object.Name == "Demo Cube") {
EditorContext_->SetSelectedObjectId(object.Id); EditorContext_->SetSelectedObjectId(object.Id);
EditorContext_->GetCameraController().FocusGameObject(object); EditorContext_->GetCameraController().FocusGameObject(object);
break; break;
} }
} }
}
MetaCoreTraceStartup("init: add console messages"); MetaCoreTraceStartup("init: add console messages");
EditorContext_->AddConsoleMessage(MetaCoreLogLevel::Info, "System", "MetaCore 编辑器已初始化"); EditorContext_->AddConsoleMessage(MetaCoreLogLevel::Info, "System", "MetaCore 编辑器已初始化");
EditorContext_->AddConsoleMessage(MetaCoreLogLevel::Info, "Render", "Panda3D 场景视口已准备完成"); EditorContext_->AddConsoleMessage(MetaCoreLogLevel::Info, "Render", "Panda3D 场景视口已准备完成");
if (loadedStartupScene) {
EditorContext_->AddConsoleMessage(MetaCoreLogLevel::Info, "Project", "已加载启动场景");
}
Initialized_ = true; Initialized_ = true;
MetaCoreTraceStartup("init: done"); MetaCoreTraceStartup("init: done");
@ -256,6 +288,7 @@ void MetaCoreEditorApp::Shutdown() {
for (const auto& module : Modules_) { for (const auto& module : Modules_) {
module->Shutdown(ModuleRegistry_); module->Shutdown(ModuleRegistry_);
} }
ModuleRegistry_.ShutdownServices();
Modules_.clear(); Modules_.clear();
EditorContext_.reset(); EditorContext_.reset();
ViewportRenderer_.Shutdown(); ViewportRenderer_.Shutdown();

View File

@ -1,12 +1,17 @@
#include "MetaCoreEditor/MetaCoreEditorContext.h" #include "MetaCoreEditor/MetaCoreEditorContext.h"
#include "MetaCoreEditor/MetaCoreEditorModule.h"
#include "MetaCoreEditor/MetaCoreEditorServices.h"
#include "MetaCoreEditorCameraController.h" #include "MetaCoreEditorCameraController.h"
#include "MetaCoreFoundation/MetaCoreGeneratedReflection.h"
#include "MetaCoreRuntimeData/MetaCoreRuntimeDataProject.h"
#include "MetaCorePlatform/MetaCoreWindow.h" #include "MetaCorePlatform/MetaCoreWindow.h"
#include "MetaCoreRender/MetaCoreEditorViewportRenderer.h" #include "MetaCoreRender/MetaCoreEditorViewportRenderer.h"
#include "MetaCoreRender/MetaCoreRenderDevice.h" #include "MetaCoreRender/MetaCoreRenderDevice.h"
#include <algorithm> #include <algorithm>
#include <cmath> #include <cmath>
#include <filesystem>
#include <unordered_set> #include <unordered_set>
namespace MetaCore { namespace MetaCore {
@ -133,6 +138,33 @@ private:
lhs.SelectionSnapshot.SelectionAnchorId == rhs.SelectionSnapshot.SelectionAnchorId; lhs.SelectionSnapshot.SelectionAnchorId == rhs.SelectionSnapshot.SelectionAnchorId;
} }
[[nodiscard]] std::optional<std::vector<std::byte>> MetaCoreTrySerializeStateSnapshot(
const MetaCoreEditorModuleRegistry& moduleRegistry,
const MetaCoreEditorStateSnapshot& snapshot
) {
const auto reflectionRegistry = moduleRegistry.ResolveService<MetaCoreIReflectionRegistry>();
if (reflectionRegistry == nullptr) {
return std::nullopt;
}
return MetaCoreSerializeToBytes(snapshot, reflectionRegistry->GetTypeRegistry());
}
[[nodiscard]] MetaCoreTypeRegistry MetaCoreBuildRuntimeDataTypeRegistry() {
MetaCoreTypeRegistry registry;
MetaCoreRegisterFoundationGeneratedTypes(registry);
MetaCoreRegisterRuntimeDataGeneratedTypes(registry);
return registry;
}
[[nodiscard]] MetaCoreRuntimeProjectDocument MetaCoreBuildDefaultRuntimeProjectDocument() {
MetaCoreRuntimeProjectDocument document;
document.StartupScenePath = std::filesystem::path("Scenes") / "Main.mcscene";
document.DataSourcesPath = std::filesystem::path("Runtime") / "DataSources.mcruntime";
document.BindingsPath = std::filesystem::path("Runtime") / "Bindings.mcruntime";
document.DiagnosticsPath = std::filesystem::path("Runtime") / "Diagnostics.mcruntimestate";
return document;
}
} // namespace } // namespace
MetaCoreEditorContext::MetaCoreEditorContext( MetaCoreEditorContext::MetaCoreEditorContext(
@ -181,6 +213,7 @@ void MetaCoreEditorContext::ClearSelection() {
SelectedObjectIds_.clear(); SelectedObjectIds_.clear();
ActiveObjectId_ = 0; ActiveObjectId_ = 0;
SelectionAnchorId_ = 0; SelectionAnchorId_ = 0;
NotifySelectionChanged();
} }
void MetaCoreEditorContext::SelectOnly(MetaCoreId objectId) { void MetaCoreEditorContext::SelectOnly(MetaCoreId objectId) {
@ -205,6 +238,7 @@ void MetaCoreEditorContext::ToggleSelection(MetaCoreId objectId) {
SelectionAnchorId_ = ActiveObjectId_ != 0 ? ActiveObjectId_ : SelectionAnchorId_; SelectionAnchorId_ = ActiveObjectId_ != 0 ? ActiveObjectId_ : SelectionAnchorId_;
NormalizeSelection(); NormalizeSelection();
NotifySelectionChanged();
} }
void MetaCoreEditorContext::AddToSelection(MetaCoreId objectId, bool makeActive) { void MetaCoreEditorContext::AddToSelection(MetaCoreId objectId, bool makeActive) {
@ -220,6 +254,7 @@ void MetaCoreEditorContext::AddToSelection(MetaCoreId objectId, bool makeActive)
SelectionAnchorId_ = objectId; SelectionAnchorId_ = objectId;
} }
NormalizeSelection(); NormalizeSelection();
NotifySelectionChanged();
} }
void MetaCoreEditorContext::SetSelection(const std::vector<MetaCoreId>& objectIds, MetaCoreId activeObjectId) { void MetaCoreEditorContext::SetSelection(const std::vector<MetaCoreId>& objectIds, MetaCoreId activeObjectId) {
@ -243,6 +278,7 @@ void MetaCoreEditorContext::SetSelection(const std::vector<MetaCoreId>& objectId
SelectionAnchorId_ = ActiveObjectId_; SelectionAnchorId_ = ActiveObjectId_;
NormalizeSelection(); NormalizeSelection();
NotifySelectionChanged();
} }
MetaCoreId MetaCoreEditorContext::GetSelectionAnchorId() const { return SelectionAnchorId_; } MetaCoreId MetaCoreEditorContext::GetSelectionAnchorId() const { return SelectionAnchorId_; }
@ -312,6 +348,7 @@ bool MetaCoreEditorContext::ExecuteCommand(std::unique_ptr<MetaCoreIEditorComman
} }
NormalizeSelection(); NormalizeSelection();
RefreshSceneDirtyState();
return true; return true;
} }
@ -322,6 +359,7 @@ bool MetaCoreEditorContext::UndoCommand() {
PendingRenameObjectId_ = 0; PendingRenameObjectId_ = 0;
NormalizeSelection(); NormalizeSelection();
RefreshSceneDirtyState();
return true; return true;
} }
@ -332,6 +370,7 @@ bool MetaCoreEditorContext::RedoCommand() {
PendingRenameObjectId_ = 0; PendingRenameObjectId_ = 0;
NormalizeSelection(); NormalizeSelection();
RefreshSceneDirtyState();
return true; return true;
} }
@ -352,6 +391,7 @@ void MetaCoreEditorContext::RestoreStateSnapshot(const MetaCoreEditorStateSnapsh
SelectionAnchorId_ = snapshot.SelectionSnapshot.SelectionAnchorId; SelectionAnchorId_ = snapshot.SelectionSnapshot.SelectionAnchorId;
} }
NormalizeSelection(); NormalizeSelection();
RefreshSceneDirtyState();
} }
bool MetaCoreEditorContext::CommitStateTransition( bool MetaCoreEditorContext::CommitStateTransition(
@ -360,7 +400,13 @@ bool MetaCoreEditorContext::CommitStateTransition(
const MetaCoreEditorStateSnapshot& afterSnapshot, const MetaCoreEditorStateSnapshot& afterSnapshot,
bool allowMerge bool allowMerge
) { ) {
if (MetaCoreStateSnapshotEqual(beforeSnapshot, afterSnapshot)) { const auto beforeBytes = MetaCoreTrySerializeStateSnapshot(ModuleRegistry_, beforeSnapshot);
const auto afterBytes = MetaCoreTrySerializeStateSnapshot(ModuleRegistry_, afterSnapshot);
if (beforeBytes.has_value() && afterBytes.has_value()) {
if (beforeBytes == afterBytes) {
return false;
}
} else if (MetaCoreStateSnapshotEqual(beforeSnapshot, afterSnapshot)) {
return false; return false;
} }
@ -368,6 +414,7 @@ bool MetaCoreEditorContext::CommitStateTransition(
return false; return false;
} }
NormalizeSelection(); NormalizeSelection();
RefreshSceneDirtyState();
return true; return true;
} }
@ -395,6 +442,122 @@ void MetaCoreEditorContext::AddConsoleMessage(MetaCoreLogLevel level, const std:
LogService_.AddEntry(level, category, message); LogService_.AddEntry(level, category, message);
} }
bool MetaCoreEditorContext::EnsureRuntimeDataConfigLoaded() {
if (RuntimeDataConfigLoaded_) {
return true;
}
const auto assetDatabaseService = ModuleRegistry_.ResolveService<MetaCoreIAssetDatabaseService>();
if (assetDatabaseService == nullptr || !assetDatabaseService->HasProject()) {
AddConsoleMessage(MetaCoreLogLevel::Warning, "RuntimeData", "项目尚未就绪,无法加载 Runtime 配置");
return false;
}
const std::filesystem::path runtimeDirectory = ResolveRuntimeDirectory();
std::error_code errorCode;
std::filesystem::create_directories(runtimeDirectory, errorCode);
const MetaCoreTypeRegistry runtimeRegistry = MetaCoreBuildRuntimeDataTypeRegistry();
const auto loadedProjectRuntime = MetaCoreReadRuntimeProjectDocument(
runtimeDirectory / "ProjectRuntime.mcruntimecfg",
runtimeRegistry
);
const auto loadedSources = MetaCoreReadRuntimeDataSourcesDocument(
runtimeDirectory / "DataSources.mcruntime",
runtimeRegistry
);
const auto loadedBindings = MetaCoreReadRuntimeBindingsDocument(
runtimeDirectory / "Bindings.mcruntime",
runtimeRegistry
);
RuntimeDataSourcesDocument_ = loadedSources.value_or(MetaCoreRuntimeDataSourcesDocument{});
RuntimeBindingsDocument_ = loadedBindings.value_or(MetaCoreRuntimeBindingsDocument{});
RuntimeDataConfigLoaded_ = true;
if (!loadedSources.has_value() || !loadedBindings.has_value()) {
AddConsoleMessage(MetaCoreLogLevel::Info, "RuntimeData", "未找到完整 Runtime 配置,已使用空配置初始化");
}
if (!loadedProjectRuntime.has_value()) {
AddConsoleMessage(MetaCoreLogLevel::Info, "RuntimeData", "未找到 ProjectRuntime 配置,将按默认项目运行路径保存");
}
return true;
}
bool MetaCoreEditorContext::SaveRuntimeDataConfig() {
if (!EnsureRuntimeDataConfigLoaded()) {
return false;
}
const auto assetDatabaseService = ModuleRegistry_.ResolveService<MetaCoreIAssetDatabaseService>();
if (assetDatabaseService == nullptr || !assetDatabaseService->HasProject()) {
AddConsoleMessage(MetaCoreLogLevel::Error, "RuntimeData", "项目尚未就绪,无法保存 Runtime 配置");
return false;
}
const std::filesystem::path runtimeDirectory = ResolveRuntimeDirectory();
std::error_code errorCode;
std::filesystem::create_directories(runtimeDirectory, errorCode);
const auto validationIssues = MetaCoreValidateRuntimeDataDocuments(RuntimeDataSourcesDocument_, RuntimeBindingsDocument_);
const bool hasValidationErrors = std::any_of(validationIssues.begin(), validationIssues.end(), [](const MetaCoreRuntimeConfigIssue& issue) {
return issue.Severity == MetaCoreRuntimeConfigIssueSeverity::Error;
});
for (const auto& issue : validationIssues) {
AddConsoleMessage(
issue.Severity == MetaCoreRuntimeConfigIssueSeverity::Error ? MetaCoreLogLevel::Error : MetaCoreLogLevel::Warning,
"RuntimeData",
issue.Scope + ": " + issue.Message
);
}
if (hasValidationErrors) {
AddConsoleMessage(MetaCoreLogLevel::Error, "RuntimeData", "Runtime 配置校验失败,已拒绝保存");
return false;
}
const MetaCoreTypeRegistry runtimeRegistry = MetaCoreBuildRuntimeDataTypeRegistry();
const MetaCoreRuntimeProjectDocument runtimeProjectDocument = MetaCoreBuildDefaultRuntimeProjectDocument();
const bool wroteProjectRuntime = MetaCoreWriteRuntimeProjectDocument(
runtimeDirectory / "ProjectRuntime.mcruntimecfg",
runtimeProjectDocument,
runtimeRegistry
);
const bool wroteSources = MetaCoreWriteRuntimeDataSourcesDocument(
runtimeDirectory / "DataSources.mcruntime",
RuntimeDataSourcesDocument_,
runtimeRegistry
);
const bool wroteBindings = MetaCoreWriteRuntimeBindingsDocument(
runtimeDirectory / "Bindings.mcruntime",
RuntimeBindingsDocument_,
runtimeRegistry
);
if (!wroteProjectRuntime || !wroteSources || !wroteBindings) {
AddConsoleMessage(MetaCoreLogLevel::Error, "RuntimeData", "保存 Runtime 配置失败");
return false;
}
AddConsoleMessage(MetaCoreLogLevel::Info, "RuntimeData", "已保存 Runtime 配置");
return true;
}
MetaCoreRuntimeDataSourcesDocument& MetaCoreEditorContext::AccessRuntimeDataSourcesDocument() {
return RuntimeDataSourcesDocument_;
}
MetaCoreRuntimeBindingsDocument& MetaCoreEditorContext::AccessRuntimeBindingsDocument() {
return RuntimeBindingsDocument_;
}
const MetaCoreRuntimeDataSourcesDocument& MetaCoreEditorContext::GetRuntimeDataSourcesDocument() const {
return RuntimeDataSourcesDocument_;
}
const MetaCoreRuntimeBindingsDocument& MetaCoreEditorContext::GetRuntimeBindingsDocument() const {
return RuntimeBindingsDocument_;
}
void MetaCoreEditorContext::SetDockLayoutBuilt(bool built) { DockLayoutBuilt_ = built; } void MetaCoreEditorContext::SetDockLayoutBuilt(bool built) { DockLayoutBuilt_ = built; }
bool MetaCoreEditorContext::HasDockLayoutBuilt() const { return DockLayoutBuilt_; } bool MetaCoreEditorContext::HasDockLayoutBuilt() const { return DockLayoutBuilt_; }
@ -420,4 +583,29 @@ void MetaCoreEditorContext::NormalizeSelection() {
} }
} }
void MetaCoreEditorContext::NotifySelectionChanged() {
ModuleRegistry_.AccessEventBus().Publish(MetaCoreEditorEvent{
MetaCoreEditorEventType::SelectionChanged,
"Selection changed",
{},
ActiveObjectId_,
!SelectedObjectIds_.empty()
});
}
void MetaCoreEditorContext::RefreshSceneDirtyState() {
if (const auto scenePersistenceService = ModuleRegistry_.ResolveService<MetaCoreIScenePersistenceService>();
scenePersistenceService != nullptr) {
scenePersistenceService->UpdateDirtyState(Scene_.CaptureSnapshot());
}
}
std::filesystem::path MetaCoreEditorContext::ResolveRuntimeDirectory() const {
if (const auto assetDatabaseService = ModuleRegistry_.ResolveService<MetaCoreIAssetDatabaseService>();
assetDatabaseService != nullptr && assetDatabaseService->HasProject()) {
return assetDatabaseService->GetProjectDescriptor().RootPath / "Runtime";
}
return std::filesystem::current_path() / "Runtime";
}
} // namespace MetaCore } // namespace MetaCore

View File

@ -29,6 +29,18 @@ const std::vector<std::unique_ptr<MetaCoreIInspectorDrawer>>& MetaCoreEditorModu
return InspectorDrawers_; return InspectorDrawers_;
} }
const std::vector<std::shared_ptr<MetaCoreIEditorService>>& MetaCoreEditorModuleRegistry::GetServices() const {
return Services_;
}
std::shared_ptr<MetaCoreIEditorService> MetaCoreEditorModuleRegistry::ResolveServiceById(const std::string& serviceId) const {
const auto iterator = ServicesById_.find(serviceId);
if (iterator == ServicesById_.end()) {
return nullptr;
}
return iterator->second;
}
bool& MetaCoreEditorModuleRegistry::AccessPanelOpenState(const std::string& panelId) { bool& MetaCoreEditorModuleRegistry::AccessPanelOpenState(const std::string& panelId) {
return PanelOpenStates_[panelId]; return PanelOpenStates_[panelId];
} }
@ -38,4 +50,22 @@ bool MetaCoreEditorModuleRegistry::IsPanelOpen(const std::string& panelId) const
return iterator != PanelOpenStates_.end() ? iterator->second : false; return iterator != PanelOpenStates_.end() ? iterator->second : false;
} }
MetaCoreEditorEventBus& MetaCoreEditorModuleRegistry::AccessEventBus() {
return EventBus_;
}
const MetaCoreEditorEventBus& MetaCoreEditorModuleRegistry::GetEventBus() const {
return EventBus_;
}
void MetaCoreEditorModuleRegistry::ShutdownServices() {
for (auto iterator = Services_.rbegin(); iterator != Services_.rend(); ++iterator) {
if (*iterator != nullptr) {
(*iterator)->Shutdown(*this);
}
}
Services_.clear();
ServicesById_.clear();
}
} // namespace MetaCore } // namespace MetaCore

View File

@ -0,0 +1,52 @@
#include "MetaCoreEditor/MetaCoreEditorServices.h"
#include <algorithm>
#include <utility>
namespace MetaCore {
MetaCoreEditorEventBus::MetaCoreEditorEventSubscriptionId MetaCoreEditorEventBus::Subscribe(
MetaCoreEditorEventType eventType,
MetaCoreEditorEventHandler handler
) {
if (!handler) {
return 0;
}
const MetaCoreEditorEventSubscriptionId subscriptionId = NextSubscriptionId_++;
Subscriptions_.push_back(std::pair{
eventType,
MetaCoreEditorEventSubscription{
subscriptionId,
std::move(handler)
}
});
return subscriptionId;
}
void MetaCoreEditorEventBus::Unsubscribe(
MetaCoreEditorEventType eventType,
MetaCoreEditorEventSubscriptionId subscriptionId
) {
std::erase_if(Subscriptions_, [&](const auto& entry) {
return entry.first == eventType && entry.second.Id == subscriptionId;
});
}
void MetaCoreEditorEventBus::Publish(const MetaCoreEditorEvent& event) const {
for (const auto& entry : Subscriptions_) {
if (entry.first == event.Type && entry.second.Handler) {
entry.second.Handler(event);
}
}
}
void MetaCoreIEditorService::Startup(MetaCoreEditorModuleRegistry& moduleRegistry) {
(void)moduleRegistry;
}
void MetaCoreIEditorService::Shutdown(MetaCoreEditorModuleRegistry& moduleRegistry) {
(void)moduleRegistry;
}
} // namespace MetaCore

View File

@ -1,5 +1,7 @@
#include "MetaCoreEditor/MetaCoreSceneInteractionService.h" #include "MetaCoreEditor/MetaCoreSceneInteractionService.h"
#include "MetaCoreEditor/MetaCoreEditorContext.h" #include "MetaCoreEditor/MetaCoreEditorContext.h"
#include "MetaCoreEditor/MetaCoreEditorModule.h"
#include "MetaCoreEditor/MetaCoreEditorServices.h"
#include "MetaCoreEditorCameraController.h" #include "MetaCoreEditorCameraController.h"
#include "MetaCoreRender/MetaCoreEditorViewportRenderer.h" #include "MetaCoreRender/MetaCoreEditorViewportRenderer.h"
#include "MetaCoreScene/MetaCoreScene.h" #include "MetaCoreScene/MetaCoreScene.h"
@ -36,15 +38,25 @@ glm::mat4 MetaCoreBuildTransformMatrix(const MetaCoreTransformComponent& transfo
return translationMatrix * rotationMatrix * scaleMatrix; return translationMatrix * rotationMatrix * scaleMatrix;
} }
// Helper to convert Panda3D (Z-up) matrix to MetaCore (Y-up) space glm::mat4 MetaCoreBuildWorldTransformMatrix(const MetaCoreScene& scene, MetaCoreId objectId) {
glm::mat4 MetaCoreConvertPandaSpaceMatrixToMetaCoreMatrix(const glm::mat4& pandaMatrix) { const MetaCoreGameObject* object = scene.FindGameObject(objectId);
const glm::mat4 pandaToMetaCore( if (object == nullptr) {
1.0F, 0.0F, 0.0F, 0.0F, return glm::mat4(1.0F);
0.0F, 0.0F, 1.0F, 0.0F, }
0.0F, 1.0F, 0.0F, 0.0F,
0.0F, 0.0F, 0.0F, 1.0F glm::mat4 worldMatrix = MetaCoreBuildTransformMatrix(object->Transform);
); MetaCoreId parentId = object->ParentId;
return pandaToMetaCore * pandaMatrix * pandaToMetaCore; while (parentId != 0) {
const MetaCoreGameObject* parentObject = scene.FindGameObject(parentId);
if (parentObject == nullptr) {
break;
}
worldMatrix = MetaCoreBuildTransformMatrix(parentObject->Transform) * worldMatrix;
parentId = parentObject->ParentId;
}
return worldMatrix;
} }
// Helper to apply MetaCore matrix back to Transform component // Helper to apply MetaCore matrix back to Transform component
@ -213,13 +225,20 @@ MetaCoreId MetaCoreSceneInteractionService::PickGameObjectFromViewport(
} }
void MetaCoreSceneInteractionService::ApplyViewportSelection(MetaCoreEditorContext& editorContext, MetaCoreId pickedObjectId) const { void MetaCoreSceneInteractionService::ApplyViewportSelection(MetaCoreEditorContext& editorContext, MetaCoreId pickedObjectId) const {
if (const auto selectionService = editorContext.GetModuleRegistry().ResolveService<MetaCoreISelectionService>();
selectionService != nullptr) {
selectionService->ApplyViewportSelection(editorContext, pickedObjectId);
return;
}
if (editorContext.GetInput().IsKeyDown(MetaCoreInputKey::Control)) { if (editorContext.GetInput().IsKeyDown(MetaCoreInputKey::Control)) {
if (pickedObjectId != 0) { if (pickedObjectId != 0) {
editorContext.ToggleSelection(pickedObjectId); editorContext.ToggleSelection(pickedObjectId);
} }
} else { return;
editorContext.SelectOnly(pickedObjectId);
} }
editorContext.SelectOnly(pickedObjectId);
} }
void MetaCoreSceneInteractionService::HandleGizmoBeginUse(MetaCoreEditorContext& editorContext, bool gizmoUsing) { void MetaCoreSceneInteractionService::HandleGizmoBeginUse(MetaCoreEditorContext& editorContext, bool gizmoUsing) {
@ -253,8 +272,6 @@ void MetaCoreSceneInteractionService::HandleGizmoManipulation(MetaCoreEditorCont
const MetaCoreSceneView sceneView = editorContext.GetCameraController().BuildSceneView(); const MetaCoreSceneView sceneView = editorContext.GetCameraController().BuildSceneView();
const MetaCoreSceneViewportState& viewportState = editorContext.GetSceneViewportState(); const MetaCoreSceneViewportState& viewportState = editorContext.GetSceneViewportState();
MetaCoreEditorViewportRenderer& viewportRenderer = editorContext.GetViewportRenderer();
const float gizmoAspect = viewportState.Width / viewportState.Height; const float gizmoAspect = viewportState.Width / viewportState.Height;
const glm::mat4 gizmoViewMatrix = glm::lookAt(sceneView.CameraPosition, sceneView.CameraTarget, sceneView.CameraUp); const glm::mat4 gizmoViewMatrix = glm::lookAt(sceneView.CameraPosition, sceneView.CameraTarget, sceneView.CameraUp);
const glm::mat4 gizmoProjectionMatrix = glm::perspective( const glm::mat4 gizmoProjectionMatrix = glm::perspective(
@ -262,14 +279,9 @@ void MetaCoreSceneInteractionService::HandleGizmoManipulation(MetaCoreEditorCont
gizmoAspect, 0.05F, 500.0F gizmoAspect, 0.05F, 500.0F
); );
// 1. Fetch World Matrix from ViewportRenderer (in Panda3D Z-up space). glm::mat4 worldMatrixMetaCore = MetaCoreBuildWorldTransformMatrix(editorContext.GetScene(), selectedObject->Id);
glm::mat4 worldMatrixPanda(1.0F);
if (viewportRenderer.TryGetObjectWorldMatrix(selectedObject->Id, worldMatrixPanda)) {
// 2. Convert to MetaCore Y-up World Space.
glm::mat4 worldMatrixMetaCore = MetaCoreConvertPandaSpaceMatrixToMetaCoreMatrix(worldMatrixPanda);
const glm::mat4 originalWorldMatrixMetaCore = worldMatrixMetaCore; const glm::mat4 originalWorldMatrixMetaCore = worldMatrixMetaCore;
// 3. Setup ImGuizmo state.
ImGuizmo::SetOrthographic(false); ImGuizmo::SetOrthographic(false);
ImGuizmo::Enable(true); ImGuizmo::Enable(true);
ImGuizmo::AllowAxisFlip(false); ImGuizmo::AllowAxisFlip(false);
@ -284,24 +296,13 @@ void MetaCoreSceneInteractionService::HandleGizmoManipulation(MetaCoreEditorCont
viewportState.Height viewportState.Height
); );
// 4. Determine Gizmo Mode (force Local for Scale). ImGuizmo::MODE effectiveMode = (editorContext.GetGizmoMode() == MetaCoreGizmoMode::Global) ? ImGuizmo::WORLD : ImGuizmo::LOCAL;
const MetaCoreGizmoMode mcMode = editorContext.GetGizmoMode();
ImGuizmo::MODE effectiveMode = (mcMode == MetaCoreGizmoMode::Global) ? ImGuizmo::WORLD : ImGuizmo::LOCAL;
if (currentOp == MetaCoreGizmoOperation::Scale) { if (currentOp == MetaCoreGizmoOperation::Scale) {
effectiveMode = ImGuizmo::LOCAL; effectiveMode = ImGuizmo::LOCAL;
} }
// 5. Manipulate.
ImGuizmo::PushID(reinterpret_cast<void*>(selectedObject)); ImGuizmo::PushID(reinterpret_cast<void*>(selectedObject));
// WORLD mode handle alignment fix.
glm::mat4 gizmoMatrix = worldMatrixMetaCore; glm::mat4 gizmoMatrix = worldMatrixMetaCore;
if (effectiveMode == ImGuizmo::WORLD) {
gizmoMatrix = glm::translate(glm::mat4(1.0F), glm::vec3(worldMatrixMetaCore[3]));
}
ImGuizmo::Manipulate( ImGuizmo::Manipulate(
glm::value_ptr(gizmoViewMatrix), glm::value_ptr(gizmoViewMatrix),
glm::value_ptr(gizmoProjectionMatrix), glm::value_ptr(gizmoProjectionMatrix),
@ -310,35 +311,21 @@ void MetaCoreSceneInteractionService::HandleGizmoManipulation(MetaCoreEditorCont
glm::value_ptr(gizmoMatrix) glm::value_ptr(gizmoMatrix)
); );
bool gizmoUsing = ImGuizmo::IsUsing(); const bool gizmoUsing = ImGuizmo::IsUsing();
ImGuizmo::PopID(); ImGuizmo::PopID();
if (gizmoUsing && (gizmoMatrix != worldMatrixMetaCore || gizmoMatrix != originalWorldMatrixMetaCore)) { if (gizmoUsing && gizmoMatrix != originalWorldMatrixMetaCore) {
if (effectiveMode == ImGuizmo::WORLD) { glm::mat4 newLocalMatrix = gizmoMatrix;
if (currentOp == MetaCoreGizmoOperation::Translate) { if (selectedObject->ParentId != 0) {
worldMatrixMetaCore[3] = gizmoMatrix[3]; const glm::mat4 parentWorldMatrixMetaCore = MetaCoreBuildWorldTransformMatrix(editorContext.GetScene(), selectedObject->ParentId);
} else if (currentOp == MetaCoreGizmoOperation::Rotate) { newLocalMatrix = glm::inverse(parentWorldMatrixMetaCore) * gizmoMatrix;
worldMatrixMetaCore = gizmoMatrix;
}
} else {
worldMatrixMetaCore = gizmoMatrix;
} }
// 6. Decompose back to Local Space.
glm::mat4 newLocalMatrix = worldMatrixMetaCore;
if (selectedObject->ParentId != 0) {
glm::mat4 parentWorldMatrixPanda(1.0F);
if (viewportRenderer.TryGetObjectWorldMatrix(selectedObject->ParentId, parentWorldMatrixPanda)) {
const glm::mat4 parentWorldMatrixMetaCore = MetaCoreConvertPandaSpaceMatrixToMetaCoreMatrix(parentWorldMatrixPanda);
newLocalMatrix = glm::inverse(parentWorldMatrixMetaCore) * worldMatrixMetaCore;
}
}
MetaCoreApplyMatrixToTransform(newLocalMatrix, selectedObject->Transform); MetaCoreApplyMatrixToTransform(newLocalMatrix, selectedObject->Transform);
} }
HandleGizmoBeginUse(editorContext, gizmoUsing); HandleGizmoBeginUse(editorContext, gizmoUsing);
HandleGizmoEndUse(editorContext, gizmoUsing); HandleGizmoEndUse(editorContext, gizmoUsing);
}
} }
void MetaCoreSceneInteractionService::DrawViewportToolbar(MetaCoreEditorContext& editorContext) { void MetaCoreSceneInteractionService::DrawViewportToolbar(MetaCoreEditorContext& editorContext) {

View File

@ -0,0 +1,13 @@
#pragma once
#include "MetaCoreEditor/MetaCoreEditorModule.h"
#include <memory>
namespace MetaCore {
std::unique_ptr<MetaCoreIModule> MetaCoreCreateBuiltinCoreServicesModule();
std::unique_ptr<MetaCoreIModule> MetaCoreCreateBuiltinEditorViewsModule();
std::unique_ptr<MetaCoreIModule> MetaCoreCreateBuiltinEditorModule();
} // namespace MetaCore

View File

@ -0,0 +1,93 @@
#pragma once
#include "MetaCoreFoundation/MetaCoreAssetGuid.h"
#include "MetaCoreFoundation/MetaCoreReflection.h"
#include "MetaCoreScene/MetaCoreSceneDocument.h"
#include "MetaCoreScene/MetaCoreGameObject.h"
#include <filesystem>
#include <string>
#include <vector>
namespace MetaCore {
MC_STRUCT()
struct MetaCoreAssetMetadataDocument {
MC_GENERATED_BODY()
MC_PROPERTY()
MetaCoreAssetGuid AssetGuid{};
MC_PROPERTY()
std::string AssetType{};
MC_PROPERTY()
std::string ImporterId{};
MC_PROPERTY()
std::filesystem::path SourcePath{};
MC_PROPERTY()
std::filesystem::path PackagePath{};
MC_PROPERTY()
std::uint64_t SourceHash = 0;
};
MC_STRUCT()
struct MetaCoreImportedAssetDocument {
MC_GENERATED_BODY()
MC_PROPERTY()
std::string AssetType{};
MC_PROPERTY()
std::string ImporterId{};
MC_PROPERTY()
std::filesystem::path SourcePath{};
MC_PROPERTY()
std::uint64_t SourceHash = 0;
};
MC_STRUCT()
struct MetaCorePrefabDocument {
MC_GENERATED_BODY()
MC_PROPERTY()
std::string Name{};
MC_PROPERTY()
std::vector<MetaCoreGameObject> GameObjects{};
};
MC_STRUCT()
struct MetaCoreCookManifestEntry {
MC_GENERATED_BODY()
MC_PROPERTY()
MetaCoreAssetGuid AssetGuid{};
MC_PROPERTY()
std::filesystem::path SourcePackagePath{};
MC_PROPERTY()
std::filesystem::path CookedPath{};
MC_PROPERTY()
std::uint64_t SourceHash = 0;
MC_PROPERTY()
std::uint64_t CookedKey = 0;
};
MC_STRUCT()
struct MetaCoreCookManifestDocument {
MC_GENERATED_BODY()
MC_PROPERTY()
std::vector<MetaCoreCookManifestEntry> Entries{};
};
} // namespace MetaCore

View File

@ -2,7 +2,10 @@
#include "MetaCoreFoundation/MetaCoreId.h" #include "MetaCoreFoundation/MetaCoreId.h"
#include "MetaCoreFoundation/MetaCoreLogService.h" #include "MetaCoreFoundation/MetaCoreLogService.h"
#include "MetaCoreFoundation/MetaCoreReflection.h"
#include "MetaCoreEditor/MetaCoreEditorCommandService.h" #include "MetaCoreEditor/MetaCoreEditorCommandService.h"
#include "MetaCoreRuntimeData/MetaCoreRuntimeDataProject.h"
#include "MetaCoreScene/MetaCoreSceneDocument.h"
#include "MetaCoreScene/MetaCoreScene.h" #include "MetaCoreScene/MetaCoreScene.h"
#include <functional> #include <functional>
@ -37,17 +40,17 @@ enum class MetaCoreReparentTransformRule {
KeepLocal KeepLocal
}; };
struct MetaCoreEditorSelectionSnapshot { MC_STRUCT()
std::vector<MetaCoreId> SelectedObjectIds;
MetaCoreId ActiveObjectId = 0;
MetaCoreId SelectionAnchorId = 0;
};
struct MetaCoreEditorStateSnapshot { struct MetaCoreEditorStateSnapshot {
MC_GENERATED_BODY()
MC_PROPERTY()
MetaCoreSceneSnapshot SceneSnapshot{}; MetaCoreSceneSnapshot SceneSnapshot{};
MC_PROPERTY()
MetaCoreEditorSelectionSnapshot SelectionSnapshot{}; MetaCoreEditorSelectionSnapshot SelectionSnapshot{};
}; };
struct MetaCoreSceneViewportState { struct MetaCoreSceneViewportState {
float Left = 0.0F; float Left = 0.0F;
float Top = 0.0F; float Top = 0.0F;
@ -122,11 +125,20 @@ public:
void RequestRenameActiveObject(); void RequestRenameActiveObject();
[[nodiscard]] MetaCoreId ConsumeRenameRequestObjectId(); [[nodiscard]] MetaCoreId ConsumeRenameRequestObjectId();
void AddConsoleMessage(MetaCoreLogLevel level, const std::string& category, const std::string& message); void AddConsoleMessage(MetaCoreLogLevel level, const std::string& category, const std::string& message);
[[nodiscard]] bool EnsureRuntimeDataConfigLoaded();
[[nodiscard]] bool SaveRuntimeDataConfig();
[[nodiscard]] MetaCoreRuntimeDataSourcesDocument& AccessRuntimeDataSourcesDocument();
[[nodiscard]] MetaCoreRuntimeBindingsDocument& AccessRuntimeBindingsDocument();
[[nodiscard]] const MetaCoreRuntimeDataSourcesDocument& GetRuntimeDataSourcesDocument() const;
[[nodiscard]] const MetaCoreRuntimeBindingsDocument& GetRuntimeBindingsDocument() const;
void SetDockLayoutBuilt(bool built); void SetDockLayoutBuilt(bool built);
[[nodiscard]] bool HasDockLayoutBuilt() const; [[nodiscard]] bool HasDockLayoutBuilt() const;
private: private:
void NormalizeSelection(); void NormalizeSelection();
void NotifySelectionChanged();
void RefreshSceneDirtyState();
[[nodiscard]] std::filesystem::path ResolveRuntimeDirectory() const;
MetaCoreWindow& Window_; MetaCoreWindow& Window_;
MetaCoreRenderDevice& RenderDevice_; MetaCoreRenderDevice& RenderDevice_;
@ -145,6 +157,9 @@ private:
MetaCoreEditorCommandService CommandService_{}; MetaCoreEditorCommandService CommandService_{};
MetaCoreId PendingRenameObjectId_ = 0; MetaCoreId PendingRenameObjectId_ = 0;
bool DockLayoutBuilt_ = false; bool DockLayoutBuilt_ = false;
bool RuntimeDataConfigLoaded_ = false;
MetaCoreRuntimeDataSourcesDocument RuntimeDataSourcesDocument_{};
MetaCoreRuntimeBindingsDocument RuntimeBindingsDocument_{};
}; };
} // namespace MetaCore } // namespace MetaCore

View File

@ -1,7 +1,10 @@
#pragma once #pragma once
#include "MetaCoreEditor/MetaCoreEditorServices.h"
#include <memory> #include <memory>
#include <string> #include <string>
#include <type_traits>
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
@ -42,19 +45,51 @@ public:
void RegisterMenuProvider(std::unique_ptr<MetaCoreIMenuProvider> provider); void RegisterMenuProvider(std::unique_ptr<MetaCoreIMenuProvider> provider);
void RegisterPanelProvider(std::unique_ptr<MetaCoreIEditorPanelProvider> provider); void RegisterPanelProvider(std::unique_ptr<MetaCoreIEditorPanelProvider> provider);
void RegisterInspectorDrawer(std::unique_ptr<MetaCoreIInspectorDrawer> drawer); void RegisterInspectorDrawer(std::unique_ptr<MetaCoreIInspectorDrawer> drawer);
template <typename TService>
void RegisterService(std::shared_ptr<TService> service) {
static_assert(std::is_base_of_v<MetaCoreIEditorService, TService>);
if (service == nullptr) {
return;
}
const std::string serviceId = service->GetServiceId();
if (!serviceId.empty()) {
ServicesById_[serviceId] = service;
}
Services_.push_back(service);
service->Startup(*this);
}
[[nodiscard]] const std::vector<std::unique_ptr<MetaCoreIMenuProvider>>& GetMenuProviders() const; [[nodiscard]] const std::vector<std::unique_ptr<MetaCoreIMenuProvider>>& GetMenuProviders() const;
[[nodiscard]] const std::vector<std::unique_ptr<MetaCoreIEditorPanelProvider>>& GetPanelProviders() const; [[nodiscard]] const std::vector<std::unique_ptr<MetaCoreIEditorPanelProvider>>& GetPanelProviders() const;
[[nodiscard]] const std::vector<std::unique_ptr<MetaCoreIInspectorDrawer>>& GetInspectorDrawers() const; [[nodiscard]] const std::vector<std::unique_ptr<MetaCoreIInspectorDrawer>>& GetInspectorDrawers() const;
[[nodiscard]] const std::vector<std::shared_ptr<MetaCoreIEditorService>>& GetServices() const;
template <typename TService>
[[nodiscard]] std::shared_ptr<TService> ResolveService() const {
static_assert(std::is_base_of_v<MetaCoreIEditorService, TService>);
for (const auto& service : Services_) {
if (const auto typedService = std::dynamic_pointer_cast<TService>(service); typedService != nullptr) {
return typedService;
}
}
return nullptr;
}
[[nodiscard]] std::shared_ptr<MetaCoreIEditorService> ResolveServiceById(const std::string& serviceId) const;
bool& AccessPanelOpenState(const std::string& panelId); bool& AccessPanelOpenState(const std::string& panelId);
[[nodiscard]] bool IsPanelOpen(const std::string& panelId) const; [[nodiscard]] bool IsPanelOpen(const std::string& panelId) const;
[[nodiscard]] MetaCoreEditorEventBus& AccessEventBus();
[[nodiscard]] const MetaCoreEditorEventBus& GetEventBus() const;
void ShutdownServices();
private: private:
std::vector<std::unique_ptr<MetaCoreIMenuProvider>> MenuProviders_{}; std::vector<std::unique_ptr<MetaCoreIMenuProvider>> MenuProviders_{};
std::vector<std::unique_ptr<MetaCoreIEditorPanelProvider>> PanelProviders_{}; std::vector<std::unique_ptr<MetaCoreIEditorPanelProvider>> PanelProviders_{};
std::vector<std::unique_ptr<MetaCoreIInspectorDrawer>> InspectorDrawers_{}; std::vector<std::unique_ptr<MetaCoreIInspectorDrawer>> InspectorDrawers_{};
std::vector<std::shared_ptr<MetaCoreIEditorService>> Services_{};
std::unordered_map<std::string, bool> PanelOpenStates_{}; std::unordered_map<std::string, bool> PanelOpenStates_{};
std::unordered_map<std::string, std::shared_ptr<MetaCoreIEditorService>> ServicesById_{};
MetaCoreEditorEventBus EventBus_{};
}; };
class MetaCoreIModule { class MetaCoreIModule {

View File

@ -0,0 +1,257 @@
#pragma once
#include "MetaCoreFoundation/MetaCoreAssetGuid.h"
#include "MetaCoreFoundation/MetaCoreId.h"
#include "MetaCoreFoundation/MetaCorePackage.h"
#include "MetaCoreFoundation/MetaCoreReflection.h"
#include "MetaCoreScene/MetaCoreScene.h"
#include <cstddef>
#include <filesystem>
#include <functional>
#include <memory>
#include <optional>
#include <span>
#include <string>
#include <vector>
namespace MetaCore {
class MetaCoreEditorContext;
class MetaCoreEditorModuleRegistry;
struct MetaCoreGameObject;
enum class MetaCoreEditorEventType {
ProjectLoaded = 0,
AssetDatabaseChanged,
SceneLoaded,
SceneSaved,
SceneDirtyStateChanged,
SelectionChanged
};
struct MetaCoreEditorEvent {
MetaCoreEditorEventType Type = MetaCoreEditorEventType::ProjectLoaded;
std::string Message{};
std::filesystem::path Path{};
MetaCoreId ObjectId = 0;
bool BoolValue = false;
};
class MetaCoreEditorEventBus {
public:
using MetaCoreEditorEventHandler = std::function<void(const MetaCoreEditorEvent&)>;
using MetaCoreEditorEventSubscriptionId = std::size_t;
MetaCoreEditorEventSubscriptionId Subscribe(
MetaCoreEditorEventType eventType,
MetaCoreEditorEventHandler handler
);
void Unsubscribe(
MetaCoreEditorEventType eventType,
MetaCoreEditorEventSubscriptionId subscriptionId
);
void Publish(const MetaCoreEditorEvent& event) const;
private:
struct MetaCoreEditorEventSubscription {
MetaCoreEditorEventSubscriptionId Id = 0;
MetaCoreEditorEventHandler Handler{};
};
std::vector<std::pair<MetaCoreEditorEventType, MetaCoreEditorEventSubscription>> Subscriptions_{};
MetaCoreEditorEventSubscriptionId NextSubscriptionId_ = 1;
};
class MetaCoreIEditorService {
public:
virtual ~MetaCoreIEditorService() = default;
[[nodiscard]] virtual std::string GetServiceId() const = 0;
virtual void Startup(MetaCoreEditorModuleRegistry& moduleRegistry);
virtual void Shutdown(MetaCoreEditorModuleRegistry& moduleRegistry);
};
struct MetaCoreProjectDescriptor {
std::string Name{};
std::string Version{};
std::filesystem::path RootPath{};
std::filesystem::path AssetsPath{};
std::filesystem::path ScenesPath{};
std::filesystem::path LibraryPath{};
std::filesystem::path StartupScenePath{};
std::vector<std::filesystem::path> ScenePaths{};
};
enum class MetaCoreAssetStorageKind {
SourcePackage = 0,
SourceFile,
Metadata,
Cooked
};
struct MetaCoreAssetRecord {
MetaCoreAssetGuid Guid{};
std::filesystem::path RelativePath{};
std::string Type{};
MetaCoreAssetStorageKind StorageKind = MetaCoreAssetStorageKind::SourcePackage;
std::filesystem::path SourcePath{};
std::filesystem::path PackagePath{};
std::filesystem::path MetaPath{};
std::string ImporterId{};
std::uint64_t SourceHash = 0;
};
class MetaCoreIReflectionRegistry : public MetaCoreIEditorService {
public:
[[nodiscard]] virtual const MetaCoreTypeRegistry& GetTypeRegistry() const = 0;
[[nodiscard]] virtual MetaCoreTypeRegistry& AccessTypeRegistry() = 0;
};
class MetaCoreIPackageService : public MetaCoreIEditorService {
public:
[[nodiscard]] virtual bool WritePackage(
const std::filesystem::path& absolutePath,
MetaCorePackageDocument document
) = 0;
[[nodiscard]] virtual std::optional<MetaCorePackageDocument> ReadPackage(
const std::filesystem::path& absolutePath
) const = 0;
};
class MetaCoreIAssetRegistryService : public MetaCoreIEditorService {
public:
[[nodiscard]] virtual const std::vector<MetaCoreAssetRecord>& GetAssetRegistry() const = 0;
[[nodiscard]] virtual std::optional<MetaCoreAssetRecord> FindAssetByGuid(const MetaCoreAssetGuid& assetGuid) const = 0;
};
class MetaCoreIAssetDatabaseService : public MetaCoreIAssetRegistryService {
public:
[[nodiscard]] virtual bool HasProject() const = 0;
[[nodiscard]] virtual const MetaCoreProjectDescriptor& GetProjectDescriptor() const = 0;
[[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 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 MetaCoreIImportPipelineService : public MetaCoreIEditorService {
public:
[[nodiscard]] virtual bool RefreshImports() = 0;
[[nodiscard]] virtual bool ReimportAsset(const MetaCoreAssetGuid& assetGuid) = 0;
};
class MetaCoreICookService : public MetaCoreIEditorService {
public:
[[nodiscard]] virtual bool CookAsset(const MetaCoreAssetGuid& assetGuid) = 0;
[[nodiscard]] virtual bool CookScene(const std::filesystem::path& relativeScenePath) = 0;
[[nodiscard]] virtual std::filesystem::path GetCookedPathForAsset(const MetaCoreAssetGuid& assetGuid) const = 0;
};
class MetaCoreIScenePersistenceService : public MetaCoreIEditorService {
public:
[[nodiscard]] virtual bool HasOpenScene() const = 0;
[[nodiscard]] virtual bool IsSceneDirty() const = 0;
[[nodiscard]] virtual std::filesystem::path GetCurrentScenePath() const = 0;
[[nodiscard]] virtual std::string GetCurrentSceneDisplayName() const = 0;
[[nodiscard]] virtual std::vector<std::filesystem::path> GetKnownScenePaths() const = 0;
virtual bool LoadStartupScene(MetaCoreEditorContext& editorContext) = 0;
virtual bool LoadScene(MetaCoreEditorContext& editorContext, const std::filesystem::path& relativeScenePath) = 0;
virtual bool SaveCurrentScene(MetaCoreEditorContext& editorContext) = 0;
virtual bool SaveSceneAs(MetaCoreEditorContext& editorContext, const std::filesystem::path& relativeScenePath) = 0;
virtual void UpdateDirtyState(const MetaCoreSceneSnapshot& snapshot) = 0;
};
class MetaCoreISelectionService : public MetaCoreIEditorService {
public:
virtual void ClearSelection(MetaCoreEditorContext& editorContext) = 0;
virtual void SelectOnly(MetaCoreEditorContext& editorContext, MetaCoreId objectId) = 0;
virtual void ToggleSelection(MetaCoreEditorContext& editorContext, MetaCoreId objectId) = 0;
virtual void ApplyHierarchySelection(MetaCoreEditorContext& editorContext, MetaCoreId objectId, bool allowToggle) = 0;
virtual void ApplyViewportSelection(MetaCoreEditorContext& editorContext, MetaCoreId pickedObjectId) = 0;
};
class MetaCoreIClipboardService : public MetaCoreIEditorService {
public:
[[nodiscard]] virtual bool DuplicateSelection(MetaCoreEditorContext& editorContext) = 0;
[[nodiscard]] virtual bool DeleteSelection(MetaCoreEditorContext& editorContext) = 0;
};
class MetaCoreISceneEditingService : public MetaCoreIEditorService {
public:
[[nodiscard]] virtual std::optional<MetaCoreId> CreateGameObject(
MetaCoreEditorContext& editorContext,
const std::string& objectName,
bool withMeshRenderer,
std::optional<MetaCoreId> forcedParentId
) = 0;
[[nodiscard]] virtual bool RenameGameObject(
MetaCoreEditorContext& editorContext,
MetaCoreId objectId,
const std::string& name
) = 0;
[[nodiscard]] virtual bool ReparentSelection(
MetaCoreEditorContext& editorContext,
MetaCoreId newParentId
) = 0;
};
class MetaCoreIPrefabService : public MetaCoreIEditorService {
public:
[[nodiscard]] virtual bool SupportsPrefabWorkflows() const = 0;
[[nodiscard]] virtual std::optional<std::filesystem::path> CreatePrefabFromSelection(
MetaCoreEditorContext& editorContext,
const std::filesystem::path& relativePrefabPath
) = 0;
[[nodiscard]] virtual std::optional<MetaCoreId> InstantiatePrefab(
MetaCoreEditorContext& editorContext,
const MetaCoreAssetGuid& prefabAssetGuid,
std::optional<MetaCoreId> forcedParentId
) = 0;
[[nodiscard]] virtual bool ApplySelectedPrefabInstance(MetaCoreEditorContext& editorContext) = 0;
[[nodiscard]] virtual bool RevertSelectedPrefabInstance(MetaCoreEditorContext& editorContext) = 0;
};
class MetaCoreIComponentTypeRegistry : public MetaCoreIEditorService {
public:
struct MetaCoreComponentDescriptor {
std::string TypeId{};
std::string DisplayName{};
std::function<bool(const MetaCoreGameObject&)> HasComponent{};
std::function<bool(MetaCoreGameObject&)> AddComponent{};
std::function<bool(MetaCoreGameObject&)> RemoveComponent{};
std::function<bool(MetaCoreGameObject&)> ResetComponent{};
std::function<std::optional<std::vector<std::byte>>(const MetaCoreGameObject&, const MetaCoreTypeRegistry&)> CopyComponentPayload{};
std::function<bool(MetaCoreGameObject&, std::span<const std::byte>, const MetaCoreTypeRegistry&)> PasteComponentPayload{};
std::function<void(MetaCoreEditorContext&, MetaCoreGameObject&)> DrawInspector{};
};
[[nodiscard]] virtual std::vector<std::string> GetRegisteredComponentTypeIds() const = 0;
[[nodiscard]] virtual const std::vector<MetaCoreComponentDescriptor>& GetComponentDescriptors() const = 0;
[[nodiscard]] virtual const MetaCoreComponentDescriptor* FindDescriptor(std::string_view typeId) const = 0;
[[nodiscard]] virtual bool CopyComponent(std::string_view typeId, const MetaCoreGameObject& gameObject) = 0;
[[nodiscard]] virtual bool CanPasteComponent(std::string_view typeId) const = 0;
[[nodiscard]] virtual bool PasteComponent(std::string_view typeId, MetaCoreGameObject& gameObject) const = 0;
};
enum class MetaCorePlayModeState {
Edit = 0,
Playing,
Paused
};
class MetaCoreIPlayModeService : public MetaCoreIEditorService {
public:
[[nodiscard]] virtual MetaCorePlayModeState GetState() const = 0;
[[nodiscard]] virtual bool CanEnterPlayMode() const = 0;
};
} // namespace MetaCore

View File

@ -0,0 +1,87 @@
#include "MetaCoreFoundation/MetaCoreArchive.h"
#include <cstring>
namespace MetaCore {
void MetaCoreArchiveWriter::WriteBytes(const void* data, std::size_t size) {
if (data == nullptr || size == 0) {
return;
}
const auto* bytes = static_cast<const std::byte*>(data);
Buffer_.insert(Buffer_.end(), bytes, bytes + size);
}
void MetaCoreArchiveWriter::WriteSpan(std::span<const std::byte> data) {
Buffer_.insert(Buffer_.end(), data.begin(), data.end());
}
const std::vector<std::byte>& MetaCoreArchiveWriter::GetBuffer() const {
return Buffer_;
}
std::size_t MetaCoreArchiveWriter::GetSize() const {
return Buffer_.size();
}
MetaCoreArchiveReader::MetaCoreArchiveReader(std::span<const std::byte> buffer)
: Buffer_(buffer) {
}
bool MetaCoreArchiveReader::ReadBytes(void* destination, std::size_t size) {
if (size == 0) {
return true;
}
if (destination == nullptr || Offset_ + size > Buffer_.size()) {
return false;
}
std::memcpy(destination, Buffer_.data() + Offset_, size);
Offset_ += size;
return true;
}
std::optional<std::span<const std::byte>> MetaCoreArchiveReader::ReadSpan(std::size_t size) {
if (Offset_ + size > Buffer_.size()) {
return std::nullopt;
}
const std::span<const std::byte> result = Buffer_.subspan(Offset_, size);
Offset_ += size;
return result;
}
std::optional<std::span<const std::byte>> MetaCoreArchiveReader::PeekSpan(std::size_t size) const {
if (Offset_ + size > Buffer_.size()) {
return std::nullopt;
}
return Buffer_.subspan(Offset_, size);
}
bool MetaCoreArchiveReader::Skip(std::size_t size) {
if (Offset_ + size > Buffer_.size()) {
return false;
}
Offset_ += size;
return true;
}
bool MetaCoreArchiveReader::IsEof() const {
return Offset_ >= Buffer_.size();
}
std::size_t MetaCoreArchiveReader::GetOffset() const {
return Offset_;
}
std::size_t MetaCoreArchiveReader::GetRemainingSize() const {
return Buffer_.size() - Offset_;
}
std::span<const std::byte> MetaCoreArchiveReader::GetBuffer() const {
return Buffer_;
}
} // namespace MetaCore

View File

@ -0,0 +1,106 @@
#include "MetaCoreFoundation/MetaCoreAssetGuid.h"
#include <iomanip>
#include <optional>
#include <random>
#include <sstream>
namespace MetaCore {
namespace {
[[nodiscard]] std::optional<std::uint8_t> MetaCoreParseHexNibble(char value) {
if (value >= '0' && value <= '9') {
return static_cast<std::uint8_t>(value - '0');
}
if (value >= 'a' && value <= 'f') {
return static_cast<std::uint8_t>(10 + value - 'a');
}
if (value >= 'A' && value <= 'F') {
return static_cast<std::uint8_t>(10 + value - 'A');
}
return std::nullopt;
}
} // namespace
bool MetaCoreAssetGuid::IsValid() const {
for (std::uint8_t byteValue : Bytes) {
if (byteValue != 0) {
return true;
}
}
return false;
}
std::string MetaCoreAssetGuid::ToString() const {
std::ostringstream stream;
stream << std::hex << std::setfill('0');
for (std::size_t index = 0; index < Bytes.size(); ++index) {
if (index == 4 || index == 6 || index == 8 || index == 10) {
stream << '-';
}
stream << std::setw(2) << static_cast<unsigned int>(Bytes[index]);
}
return stream.str();
}
MetaCoreAssetGuid MetaCoreAssetGuid::Generate() {
static thread_local std::mt19937_64 generator(std::random_device{}());
MetaCoreAssetGuid guid;
for (std::size_t index = 0; index < guid.Bytes.size(); index += sizeof(std::uint64_t)) {
const std::uint64_t randomValue = generator();
for (std::size_t byteIndex = 0; byteIndex < sizeof(std::uint64_t) && (index + byteIndex) < guid.Bytes.size(); ++byteIndex) {
guid.Bytes[index + byteIndex] = static_cast<std::uint8_t>((randomValue >> (byteIndex * 8U)) & 0xFFU);
}
}
guid.Bytes[6] = static_cast<std::uint8_t>((guid.Bytes[6] & 0x0FU) | 0x40U);
guid.Bytes[8] = static_cast<std::uint8_t>((guid.Bytes[8] & 0x3FU) | 0x80U);
return guid;
}
std::optional<MetaCoreAssetGuid> MetaCoreAssetGuid::Parse(std::string_view value) {
std::array<char, 32> hexDigits{};
std::size_t digitCount = 0;
for (char character : value) {
if (character == '-') {
continue;
}
if (digitCount >= hexDigits.size()) {
return std::nullopt;
}
hexDigits[digitCount++] = character;
}
if (digitCount != hexDigits.size()) {
return std::nullopt;
}
MetaCoreAssetGuid guid;
for (std::size_t index = 0; index < guid.Bytes.size(); ++index) {
const auto highNibble = MetaCoreParseHexNibble(hexDigits[index * 2]);
const auto lowNibble = MetaCoreParseHexNibble(hexDigits[index * 2 + 1]);
if (!highNibble.has_value() || !lowNibble.has_value()) {
return std::nullopt;
}
guid.Bytes[index] = static_cast<std::uint8_t>((highNibble.value() << 4U) | lowNibble.value());
}
return guid;
}
std::size_t MetaCoreAssetGuidHasher::operator()(const MetaCoreAssetGuid& guid) const noexcept {
std::size_t hashValue = 1469598103934665603ULL;
for (std::uint8_t byteValue : guid.Bytes) {
hashValue ^= static_cast<std::size_t>(byteValue);
hashValue *= 1099511628211ULL;
}
return hashValue;
}
} // namespace MetaCore

View File

@ -0,0 +1,59 @@
#include "MetaCoreFoundation/MetaCoreHash.h"
#include <array>
#include <fstream>
namespace MetaCore {
namespace {
constexpr std::uint64_t GMetaCoreFnvOffsetBasis = 1469598103934665603ULL;
constexpr std::uint64_t GMetaCoreFnvPrime = 1099511628211ULL;
} // namespace
std::uint64_t MetaCoreHashBytes(std::span<const std::byte> data) {
std::uint64_t hashValue = GMetaCoreFnvOffsetBasis;
for (std::byte byteValue : data) {
hashValue ^= static_cast<std::uint64_t>(std::to_integer<unsigned char>(byteValue));
hashValue *= GMetaCoreFnvPrime;
}
return hashValue;
}
std::uint64_t MetaCoreHashString(std::string_view value) {
const auto* bytes = reinterpret_cast<const std::byte*>(value.data());
return MetaCoreHashBytes(std::span(bytes, value.size()));
}
std::optional<std::uint64_t> MetaCoreHashFile(const std::filesystem::path& path) {
std::ifstream input(path, std::ios::binary);
if (!input.is_open()) {
return std::nullopt;
}
std::array<char, 4096> buffer{};
std::uint64_t hashValue = GMetaCoreFnvOffsetBasis;
while (input.good()) {
input.read(buffer.data(), static_cast<std::streamsize>(buffer.size()));
const std::streamsize readCount = input.gcount();
if (readCount <= 0) {
break;
}
const auto* bytes = reinterpret_cast<const std::byte*>(buffer.data());
for (std::streamsize index = 0; index < readCount; ++index) {
hashValue ^= static_cast<std::uint64_t>(std::to_integer<unsigned char>(bytes[index]));
hashValue *= GMetaCoreFnvPrime;
}
}
return hashValue;
}
std::uint64_t MetaCoreHashCombine(std::uint64_t seed, std::uint64_t value) {
seed ^= value + 0x9E3779B97F4A7C15ULL + (seed << 6U) + (seed >> 2U);
return seed;
}
} // namespace MetaCore

View File

@ -0,0 +1,150 @@
#include "MetaCoreFoundation/MetaCorePackage.h"
#include <fstream>
namespace MetaCore {
namespace {
[[nodiscard]] std::optional<std::vector<std::byte>> MetaCoreReadAllBytes(const std::filesystem::path& path) {
std::ifstream input(path, std::ios::binary);
if (!input.is_open()) {
return std::nullopt;
}
input.seekg(0, std::ios::end);
const auto size = static_cast<std::size_t>(input.tellg());
input.seekg(0, std::ios::beg);
std::vector<std::byte> bytes(size);
if (size > 0 && !input.read(reinterpret_cast<char*>(bytes.data()), static_cast<std::streamsize>(size))) {
return std::nullopt;
}
return bytes;
}
template <typename T>
[[nodiscard]] std::optional<std::vector<std::byte>> MetaCoreSerializeSection(
const T& value,
const MetaCoreTypeRegistry& registry
) {
return MetaCoreSerializeToBytes(value, registry);
}
template <typename T>
[[nodiscard]] bool MetaCoreDeserializeSection(
const MetaCorePackageSection& section,
std::span<const std::byte> buffer,
T& value,
const MetaCoreTypeRegistry& registry
) {
if (section.Offset + section.Size > buffer.size()) {
return false;
}
return MetaCoreDeserializeFromBytes(
buffer.subspan(static_cast<std::size_t>(section.Offset), static_cast<std::size_t>(section.Size)),
value,
registry
);
}
} // namespace
bool MetaCoreWritePackageFile(
const std::filesystem::path& path,
MetaCorePackageDocument document,
const MetaCoreTypeRegistry& registry
) {
const auto serializedNames = MetaCoreSerializeSection(document.NameTable, registry);
const auto serializedImports = MetaCoreSerializeSection(document.Imports, registry);
const auto serializedExports = MetaCoreSerializeSection(document.Exports, registry);
const auto serializedDependencies = MetaCoreSerializeSection(document.Dependencies, registry);
const auto serializedCustomVersions = MetaCoreSerializeSection(document.CustomVersions, registry);
const auto serializedPayloads = MetaCoreSerializeSection(document.PayloadSections, registry);
if (!serializedNames.has_value() ||
!serializedImports.has_value() ||
!serializedExports.has_value() ||
!serializedDependencies.has_value() ||
!serializedCustomVersions.has_value() ||
!serializedPayloads.has_value()) {
return false;
}
const auto provisionalHeaderBytes = MetaCoreSerializeToBytes(document.Header, registry);
if (!provisionalHeaderBytes.has_value()) {
return false;
}
std::uint64_t cursor = static_cast<std::uint64_t>(provisionalHeaderBytes->size());
document.Header.NameTable = MetaCorePackageSection{cursor, static_cast<std::uint64_t>(serializedNames->size())};
cursor += document.Header.NameTable.Size;
document.Header.ImportTable = MetaCorePackageSection{cursor, static_cast<std::uint64_t>(serializedImports->size())};
cursor += document.Header.ImportTable.Size;
document.Header.ExportTable = MetaCorePackageSection{cursor, static_cast<std::uint64_t>(serializedExports->size())};
cursor += document.Header.ExportTable.Size;
document.Header.DependencyTable = MetaCorePackageSection{cursor, static_cast<std::uint64_t>(serializedDependencies->size())};
cursor += document.Header.DependencyTable.Size;
document.Header.CustomVersionTable = MetaCorePackageSection{cursor, static_cast<std::uint64_t>(serializedCustomVersions->size())};
cursor += document.Header.CustomVersionTable.Size;
document.Header.PayloadSections = MetaCorePackageSection{cursor, static_cast<std::uint64_t>(serializedPayloads->size())};
cursor += document.Header.PayloadSections.Size;
document.Header.FileSize = cursor;
const auto finalHeaderBytes = MetaCoreSerializeToBytes(document.Header, registry);
if (!finalHeaderBytes.has_value()) {
return false;
}
std::filesystem::create_directories(path.parent_path());
std::ofstream output(path, std::ios::binary | std::ios::trunc);
if (!output.is_open()) {
return false;
}
output.write(reinterpret_cast<const char*>(finalHeaderBytes->data()), static_cast<std::streamsize>(finalHeaderBytes->size()));
output.write(reinterpret_cast<const char*>(serializedNames->data()), static_cast<std::streamsize>(serializedNames->size()));
output.write(reinterpret_cast<const char*>(serializedImports->data()), static_cast<std::streamsize>(serializedImports->size()));
output.write(reinterpret_cast<const char*>(serializedExports->data()), static_cast<std::streamsize>(serializedExports->size()));
output.write(reinterpret_cast<const char*>(serializedDependencies->data()), static_cast<std::streamsize>(serializedDependencies->size()));
output.write(reinterpret_cast<const char*>(serializedCustomVersions->data()), static_cast<std::streamsize>(serializedCustomVersions->size()));
output.write(reinterpret_cast<const char*>(serializedPayloads->data()), static_cast<std::streamsize>(serializedPayloads->size()));
return output.good();
}
std::optional<MetaCorePackageDocument> MetaCoreReadPackageFile(
const std::filesystem::path& path,
const MetaCoreTypeRegistry& registry
) {
const auto bytes = MetaCoreReadAllBytes(path);
if (!bytes.has_value()) {
return std::nullopt;
}
MetaCorePackageDocument document;
MetaCoreArchiveReader headerReader(std::span<const std::byte>(bytes->data(), bytes->size()));
if (!MetaCoreDeserializeValue(headerReader, document.Header, registry)) {
return std::nullopt;
}
if (document.Header.Magic != GMetaCorePackageMagic ||
document.Header.FormatVersion != GMetaCorePackageFormatVersion ||
document.Header.Endianness != MetaCorePackageEndianness::Little ||
document.Header.FileSize != bytes->size()) {
return std::nullopt;
}
const std::span<const std::byte> byteSpan(bytes->data(), bytes->size());
if (!MetaCoreDeserializeSection(document.Header.NameTable, byteSpan, document.NameTable, registry) ||
!MetaCoreDeserializeSection(document.Header.ImportTable, byteSpan, document.Imports, registry) ||
!MetaCoreDeserializeSection(document.Header.ExportTable, byteSpan, document.Exports, registry) ||
!MetaCoreDeserializeSection(document.Header.DependencyTable, byteSpan, document.Dependencies, registry) ||
!MetaCoreDeserializeSection(document.Header.CustomVersionTable, byteSpan, document.CustomVersions, registry) ||
!MetaCoreDeserializeSection(document.Header.PayloadSections, byteSpan, document.PayloadSections, registry)) {
return std::nullopt;
}
return document;
}
} // namespace MetaCore

View File

@ -0,0 +1,27 @@
#include "MetaCoreFoundation/MetaCoreReflection.h"
#include "MetaCoreFoundation/MetaCoreHash.h"
namespace MetaCore {
const MetaCoreStructDescriptor* MetaCoreTypeRegistry::FindStructByName(std::string_view name) const {
const auto nameIterator = StructNames_.find(std::string(name));
if (nameIterator == StructNames_.end()) {
return nullptr;
}
const auto descriptorIterator = StructsByRuntimeType_.find(nameIterator->second);
return descriptorIterator == StructsByRuntimeType_.end() ? nullptr : &descriptorIterator->second;
}
void MetaCoreTypeRegistry::Clear() {
StructsByRuntimeType_.clear();
StructNames_.clear();
EnumsByRuntimeType_.clear();
}
MetaCoreTypeId MetaCoreMakeTypeId(std::string_view value) {
return MetaCoreHashString(value);
}
} // namespace MetaCore

View File

@ -0,0 +1,40 @@
#pragma once
#include <cstddef>
#include <optional>
#include <span>
#include <vector>
namespace MetaCore {
class MetaCoreArchiveWriter {
public:
void WriteBytes(const void* data, std::size_t size);
void WriteSpan(std::span<const std::byte> data);
[[nodiscard]] const std::vector<std::byte>& GetBuffer() const;
[[nodiscard]] std::size_t GetSize() const;
private:
std::vector<std::byte> Buffer_{};
};
class MetaCoreArchiveReader {
public:
explicit MetaCoreArchiveReader(std::span<const std::byte> buffer);
[[nodiscard]] bool ReadBytes(void* destination, std::size_t size);
[[nodiscard]] std::optional<std::span<const std::byte>> ReadSpan(std::size_t size);
[[nodiscard]] std::optional<std::span<const std::byte>> PeekSpan(std::size_t size) const;
[[nodiscard]] bool Skip(std::size_t size);
[[nodiscard]] bool IsEof() const;
[[nodiscard]] std::size_t GetOffset() const;
[[nodiscard]] std::size_t GetRemainingSize() const;
[[nodiscard]] std::span<const std::byte> GetBuffer() const;
private:
std::span<const std::byte> Buffer_{};
std::size_t Offset_ = 0;
};
} // namespace MetaCore

View File

@ -0,0 +1,35 @@
#pragma once
#include "MetaCoreFoundation/MetaCoreReflection.h"
#include <array>
#include <cstddef>
#include <compare>
#include <cstdint>
#include <optional>
#include <string>
#include <string_view>
namespace MetaCore {
MC_STRUCT()
struct MetaCoreAssetGuid {
MC_GENERATED_BODY()
MC_PROPERTY()
std::array<std::uint8_t, 16> Bytes{};
[[nodiscard]] bool IsValid() const;
[[nodiscard]] std::string ToString() const;
[[nodiscard]] static MetaCoreAssetGuid Generate();
[[nodiscard]] static std::optional<MetaCoreAssetGuid> Parse(std::string_view value);
auto operator<=>(const MetaCoreAssetGuid&) const = default;
};
struct MetaCoreAssetGuidHasher {
[[nodiscard]] std::size_t operator()(const MetaCoreAssetGuid& guid) const noexcept;
};
} // namespace MetaCore

View File

@ -0,0 +1,12 @@
#pragma once
namespace MetaCore {
class MetaCoreTypeRegistry;
void MetaCoreRegisterFoundationGeneratedTypes(MetaCoreTypeRegistry& registry);
void MetaCoreRegisterSceneGeneratedTypes(MetaCoreTypeRegistry& registry);
void MetaCoreRegisterEditorGeneratedTypes(MetaCoreTypeRegistry& registry);
void MetaCoreRegisterRuntimeDataGeneratedTypes(MetaCoreTypeRegistry& registry);
} // namespace MetaCore

View File

@ -0,0 +1,18 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include <filesystem>
#include <optional>
#include <span>
#include <string_view>
namespace MetaCore {
[[nodiscard]] std::uint64_t MetaCoreHashBytes(std::span<const std::byte> data);
[[nodiscard]] std::uint64_t MetaCoreHashString(std::string_view value);
[[nodiscard]] std::optional<std::uint64_t> MetaCoreHashFile(const std::filesystem::path& path);
[[nodiscard]] std::uint64_t MetaCoreHashCombine(std::uint64_t seed, std::uint64_t value);
} // namespace MetaCore

View File

@ -0,0 +1,167 @@
#pragma once
#include "MetaCoreFoundation/MetaCoreAssetGuid.h"
#include <cstddef>
#include <cstdint>
#include <filesystem>
#include <optional>
#include <string>
#include <vector>
namespace MetaCore {
constexpr std::uint32_t GMetaCorePackageMagic = 0x4D434F52U;
constexpr std::uint32_t GMetaCorePackageFormatVersion = 1U;
constexpr std::uint64_t GMetaCoreGeneratorVersion = 1U;
MC_ENUM()
enum class MetaCorePackageType : std::uint32_t {
Scene = 1,
Prefab = 2,
Asset = 3,
Meta = 4
};
MC_ENUM()
enum class MetaCorePackageEndianness : std::uint32_t {
Little = 1
};
MC_STRUCT()
struct MetaCorePackageSection {
MC_GENERATED_BODY()
MC_PROPERTY()
std::uint64_t Offset = 0;
MC_PROPERTY()
std::uint64_t Size = 0;
};
MC_STRUCT()
struct MetaCorePackageHeader {
MC_GENERATED_BODY()
MC_PROPERTY()
std::uint32_t Magic = GMetaCorePackageMagic;
MC_PROPERTY()
std::uint32_t FormatVersion = GMetaCorePackageFormatVersion;
MC_PROPERTY()
MetaCorePackageType PackageType = MetaCorePackageType::Asset;
MC_PROPERTY()
MetaCorePackageEndianness Endianness = MetaCorePackageEndianness::Little;
MC_PROPERTY()
MetaCoreAssetGuid PackageGuid{};
MC_PROPERTY()
std::uint64_t SourceHash = 0;
MC_PROPERTY()
std::uint64_t GeneratorVersion = GMetaCoreGeneratorVersion;
MC_PROPERTY()
MetaCorePackageSection NameTable{};
MC_PROPERTY()
MetaCorePackageSection ImportTable{};
MC_PROPERTY()
MetaCorePackageSection ExportTable{};
MC_PROPERTY()
MetaCorePackageSection DependencyTable{};
MC_PROPERTY()
MetaCorePackageSection CustomVersionTable{};
MC_PROPERTY()
MetaCorePackageSection PayloadSections{};
MC_PROPERTY()
std::uint64_t FileSize = 0;
};
MC_STRUCT()
struct MetaCoreImportEntry {
MC_GENERATED_BODY()
MC_PROPERTY()
MetaCoreAssetGuid AssetGuid{};
MC_PROPERTY()
std::string ObjectPath{};
MC_PROPERTY()
std::string Type{};
};
MC_STRUCT()
struct MetaCoreExportEntry {
MC_GENERATED_BODY()
MC_PROPERTY()
MetaCoreAssetGuid AssetGuid{};
MC_PROPERTY()
std::string ObjectName{};
MC_PROPERTY()
MetaCoreTypeId TypeId = 0;
MC_PROPERTY()
std::uint64_t PayloadIndex = 0;
MC_PROPERTY()
std::uint64_t PayloadSize = 0;
};
MC_STRUCT()
struct MetaCoreDependencyEntry {
MC_GENERATED_BODY()
MC_PROPERTY()
MetaCoreAssetGuid AssetGuid{};
MC_PROPERTY()
std::string Reason{};
};
MC_STRUCT()
struct MetaCoreCustomVersion {
MC_GENERATED_BODY()
MC_PROPERTY()
std::string Key{};
MC_PROPERTY()
std::uint32_t Version = 0;
};
struct MetaCorePackageDocument {
MetaCorePackageHeader Header{};
std::vector<std::string> NameTable{};
std::vector<MetaCoreImportEntry> Imports{};
std::vector<MetaCoreExportEntry> Exports{};
std::vector<MetaCoreDependencyEntry> Dependencies{};
std::vector<MetaCoreCustomVersion> CustomVersions{};
std::vector<std::vector<std::byte>> PayloadSections{};
};
[[nodiscard]] bool MetaCoreWritePackageFile(
const std::filesystem::path& path,
MetaCorePackageDocument document,
const MetaCoreTypeRegistry& registry
);
[[nodiscard]] std::optional<MetaCorePackageDocument> MetaCoreReadPackageFile(
const std::filesystem::path& path,
const MetaCoreTypeRegistry& registry
);
} // namespace MetaCore

View File

@ -0,0 +1,469 @@
#pragma once
#include "MetaCoreFoundation/MetaCoreArchive.h"
#include <glm/vec3.hpp>
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <filesystem>
#include <functional>
#include <optional>
#include <span>
#include <string>
#include <string_view>
#include <type_traits>
#include <typeindex>
#include <unordered_map>
#include <utility>
#include <vector>
namespace MetaCore {
using MetaCoreTypeId = std::uint64_t;
struct MetaCoreFieldDescriptor {
std::string Name{};
std::function<bool(const void*, MetaCoreArchiveWriter&, const class MetaCoreTypeRegistry&)> Serialize{};
std::function<bool(void*, MetaCoreArchiveReader&, const class MetaCoreTypeRegistry&)> Deserialize{};
};
struct MetaCoreStructDescriptor {
MetaCoreTypeId TypeId = 0;
std::string Name{};
std::uint32_t Version = 1;
std::type_index RuntimeType = typeid(void);
std::vector<MetaCoreFieldDescriptor> Fields{};
};
struct MetaCoreEnumDescriptor {
MetaCoreTypeId TypeId = 0;
std::string Name{};
std::type_index RuntimeType = typeid(void);
};
class MetaCoreTypeRegistry {
public:
template <typename T>
MetaCoreStructDescriptor& RegisterStruct(std::string_view name, std::uint32_t version = 1);
template <typename T>
MetaCoreEnumDescriptor& RegisterEnum(std::string_view name);
template <typename T>
[[nodiscard]] const MetaCoreStructDescriptor* FindStruct() const;
template <typename T>
[[nodiscard]] const MetaCoreEnumDescriptor* FindEnum() const;
[[nodiscard]] const MetaCoreStructDescriptor* FindStructByName(std::string_view name) const;
void Clear();
private:
std::unordered_map<std::type_index, MetaCoreStructDescriptor> StructsByRuntimeType_{};
std::unordered_map<std::string, std::type_index> StructNames_{};
std::unordered_map<std::type_index, MetaCoreEnumDescriptor> EnumsByRuntimeType_{};
};
template <typename T>
class MetaCoreStructRegistrationBuilder {
public:
explicit MetaCoreStructRegistrationBuilder(MetaCoreStructDescriptor& descriptor)
: Descriptor_(descriptor) {
}
template <auto MemberPointer>
MetaCoreStructRegistrationBuilder& Field(std::string_view name);
private:
MetaCoreStructDescriptor& Descriptor_;
};
[[nodiscard]] MetaCoreTypeId MetaCoreMakeTypeId(std::string_view value);
template <typename T>
[[nodiscard]] bool MetaCoreSerializeValue(
MetaCoreArchiveWriter& writer,
const T& value,
const MetaCoreTypeRegistry& registry
);
template <typename T>
[[nodiscard]] bool MetaCoreDeserializeValue(
MetaCoreArchiveReader& reader,
T& value,
const MetaCoreTypeRegistry& registry
);
template <typename T>
[[nodiscard]] std::optional<std::vector<std::byte>> MetaCoreSerializeToBytes(
const T& value,
const MetaCoreTypeRegistry& registry
);
template <typename T>
[[nodiscard]] bool MetaCoreDeserializeFromBytes(
std::span<const std::byte> bytes,
T& value,
const MetaCoreTypeRegistry& registry
);
template <typename T>
[[nodiscard]] MetaCoreStructRegistrationBuilder<T> MetaCoreRegisterGeneratedStruct(
MetaCoreTypeRegistry& registry,
std::string_view name,
std::uint32_t version = 1
);
template <typename TEnum>
void MetaCoreRegisterGeneratedEnum(
MetaCoreTypeRegistry& registry,
std::string_view name
);
#define MC_STRUCT(...)
#define MC_CLASS(...)
#define MC_ENUM(...)
#define MC_PROPERTY(...)
#define MC_GENERATED_BODY()
namespace Detail {
template <typename T>
struct MetaCoreIsVector : std::false_type {};
template <typename TValue, typename TAllocator>
struct MetaCoreIsVector<std::vector<TValue, TAllocator>> : std::true_type {
using ValueType = TValue;
};
template <typename T>
struct MetaCoreIsOptional : std::false_type {};
template <typename TValue>
struct MetaCoreIsOptional<std::optional<TValue>> : std::true_type {
using ValueType = TValue;
};
template <typename T>
struct MetaCoreIsStdArray : std::false_type {};
template <typename TValue, std::size_t TSize>
struct MetaCoreIsStdArray<std::array<TValue, TSize>> : std::true_type {
using ValueType = TValue;
static constexpr std::size_t Size = TSize;
};
template <typename T>
inline constexpr bool GMetaCoreAlwaysFalse = false;
} // namespace Detail
template <typename T>
MetaCoreStructDescriptor& MetaCoreTypeRegistry::RegisterStruct(std::string_view name, std::uint32_t version) {
auto& descriptor = StructsByRuntimeType_[std::type_index(typeid(T))];
descriptor.TypeId = MetaCoreMakeTypeId(name);
descriptor.Name = std::string(name);
descriptor.Version = version;
descriptor.RuntimeType = std::type_index(typeid(T));
descriptor.Fields.clear();
StructNames_.insert_or_assign(descriptor.Name, descriptor.RuntimeType);
return descriptor;
}
template <typename T>
MetaCoreEnumDescriptor& MetaCoreTypeRegistry::RegisterEnum(std::string_view name) {
auto& descriptor = EnumsByRuntimeType_[std::type_index(typeid(T))];
descriptor.TypeId = MetaCoreMakeTypeId(name);
descriptor.Name = std::string(name);
descriptor.RuntimeType = std::type_index(typeid(T));
return descriptor;
}
template <typename T>
const MetaCoreStructDescriptor* MetaCoreTypeRegistry::FindStruct() const {
const auto iterator = StructsByRuntimeType_.find(std::type_index(typeid(T)));
return iterator == StructsByRuntimeType_.end() ? nullptr : &iterator->second;
}
template <typename T>
const MetaCoreEnumDescriptor* MetaCoreTypeRegistry::FindEnum() const {
const auto iterator = EnumsByRuntimeType_.find(std::type_index(typeid(T)));
return iterator == EnumsByRuntimeType_.end() ? nullptr : &iterator->second;
}
template <typename T>
template <auto MemberPointer>
MetaCoreStructRegistrationBuilder<T>& MetaCoreStructRegistrationBuilder<T>::Field(std::string_view name) {
Descriptor_.Fields.push_back(MetaCoreFieldDescriptor{
std::string(name),
[](const void* instance, MetaCoreArchiveWriter& writer, const MetaCoreTypeRegistry& registry) {
const auto& typedInstance = *static_cast<const T*>(instance);
return MetaCoreSerializeValue(writer, typedInstance.*MemberPointer, registry);
},
[](void* instance, MetaCoreArchiveReader& reader, const MetaCoreTypeRegistry& registry) {
auto& typedInstance = *static_cast<T*>(instance);
return MetaCoreDeserializeValue(reader, typedInstance.*MemberPointer, registry);
}
});
return *this;
}
template <typename T>
MetaCoreStructRegistrationBuilder<T> MetaCoreRegisterGeneratedStruct(
MetaCoreTypeRegistry& registry,
std::string_view name,
std::uint32_t version
) {
return MetaCoreStructRegistrationBuilder<T>(registry.RegisterStruct<T>(name, version));
}
template <typename TEnum>
void MetaCoreRegisterGeneratedEnum(MetaCoreTypeRegistry& registry, std::string_view name) {
(void)registry.RegisterEnum<TEnum>(name);
}
template <typename T>
bool MetaCoreSerializeValue(
MetaCoreArchiveWriter& writer,
const T& value,
const MetaCoreTypeRegistry& registry
) {
using TValue = std::remove_cvref_t<T>;
if constexpr (std::is_same_v<TValue, bool>) {
const std::uint8_t byteValue = value ? 1U : 0U;
writer.WriteBytes(&byteValue, sizeof(byteValue));
return true;
} else if constexpr (std::is_integral_v<TValue> || std::is_floating_point_v<TValue>) {
writer.WriteBytes(&value, sizeof(TValue));
return true;
} else if constexpr (std::is_enum_v<TValue>) {
const auto enumValue = static_cast<std::underlying_type_t<TValue>>(value);
return MetaCoreSerializeValue(writer, enumValue, registry);
} else if constexpr (std::is_same_v<TValue, std::string>) {
const std::uint64_t size = static_cast<std::uint64_t>(value.size());
writer.WriteBytes(&size, sizeof(size));
if (!value.empty()) {
writer.WriteBytes(value.data(), value.size());
}
return true;
} else if constexpr (std::is_same_v<TValue, std::filesystem::path>) {
return MetaCoreSerializeValue(writer, value.generic_string(), registry);
} else if constexpr (std::is_same_v<TValue, glm::vec3>) {
writer.WriteBytes(&value.x, sizeof(value.x));
writer.WriteBytes(&value.y, sizeof(value.y));
writer.WriteBytes(&value.z, sizeof(value.z));
return true;
} else if constexpr (Detail::MetaCoreIsStdArray<TValue>::value) {
for (const auto& item : value) {
if (!MetaCoreSerializeValue(writer, item, registry)) {
return false;
}
}
return true;
} else if constexpr (Detail::MetaCoreIsVector<TValue>::value) {
const std::uint64_t size = static_cast<std::uint64_t>(value.size());
writer.WriteBytes(&size, sizeof(size));
for (const auto& item : value) {
if (!MetaCoreSerializeValue(writer, item, registry)) {
return false;
}
}
return true;
} else if constexpr (Detail::MetaCoreIsOptional<TValue>::value) {
const bool hasValue = value.has_value();
if (!MetaCoreSerializeValue(writer, hasValue, registry)) {
return false;
}
if (hasValue) {
return MetaCoreSerializeValue(writer, value.value(), registry);
}
return true;
} else {
const MetaCoreStructDescriptor* descriptor = registry.FindStruct<TValue>();
if (descriptor == nullptr) {
return false;
}
writer.WriteBytes(&descriptor->Version, sizeof(descriptor->Version));
const std::uint32_t fieldCount = static_cast<std::uint32_t>(descriptor->Fields.size());
writer.WriteBytes(&fieldCount, sizeof(fieldCount));
for (const MetaCoreFieldDescriptor& field : descriptor->Fields) {
if (!MetaCoreSerializeValue(writer, field.Name, registry)) {
return false;
}
MetaCoreArchiveWriter fieldWriter;
if (!field.Serialize(&value, fieldWriter, registry)) {
return false;
}
const std::uint64_t fieldSize = static_cast<std::uint64_t>(fieldWriter.GetSize());
writer.WriteBytes(&fieldSize, sizeof(fieldSize));
writer.WriteSpan(fieldWriter.GetBuffer());
}
return true;
}
}
template <typename T>
bool MetaCoreDeserializeValue(
MetaCoreArchiveReader& reader,
T& value,
const MetaCoreTypeRegistry& registry
) {
using TValue = std::remove_cvref_t<T>;
if constexpr (std::is_same_v<TValue, bool>) {
std::uint8_t byteValue = 0;
if (!reader.ReadBytes(&byteValue, sizeof(byteValue))) {
return false;
}
value = byteValue != 0;
return true;
} else if constexpr (std::is_integral_v<TValue> || std::is_floating_point_v<TValue>) {
return reader.ReadBytes(&value, sizeof(TValue));
} else if constexpr (std::is_enum_v<TValue>) {
std::underlying_type_t<TValue> enumValue{};
if (!MetaCoreDeserializeValue(reader, enumValue, registry)) {
return false;
}
value = static_cast<TValue>(enumValue);
return true;
} else if constexpr (std::is_same_v<TValue, std::string>) {
std::uint64_t size = 0;
if (!reader.ReadBytes(&size, sizeof(size))) {
return false;
}
const auto bytes = reader.ReadSpan(static_cast<std::size_t>(size));
if (!bytes.has_value()) {
return false;
}
value.assign(reinterpret_cast<const char*>(bytes->data()), bytes->size());
return true;
} else if constexpr (std::is_same_v<TValue, std::filesystem::path>) {
std::string serializedPath;
if (!MetaCoreDeserializeValue(reader, serializedPath, registry)) {
return false;
}
value = std::filesystem::path(serializedPath);
return true;
} else if constexpr (std::is_same_v<TValue, glm::vec3>) {
return reader.ReadBytes(&value.x, sizeof(value.x)) &&
reader.ReadBytes(&value.y, sizeof(value.y)) &&
reader.ReadBytes(&value.z, sizeof(value.z));
} else if constexpr (Detail::MetaCoreIsStdArray<TValue>::value) {
for (auto& item : value) {
if (!MetaCoreDeserializeValue(reader, item, registry)) {
return false;
}
}
return true;
} else if constexpr (Detail::MetaCoreIsVector<TValue>::value) {
std::uint64_t size = 0;
if (!reader.ReadBytes(&size, sizeof(size))) {
return false;
}
value.clear();
value.reserve(static_cast<std::size_t>(size));
for (std::uint64_t index = 0; index < size; ++index) {
typename Detail::MetaCoreIsVector<TValue>::ValueType item{};
if (!MetaCoreDeserializeValue(reader, item, registry)) {
return false;
}
value.push_back(std::move(item));
}
return true;
} else if constexpr (Detail::MetaCoreIsOptional<TValue>::value) {
bool hasValue = false;
if (!MetaCoreDeserializeValue(reader, hasValue, registry)) {
return false;
}
if (!hasValue) {
value.reset();
return true;
}
typename Detail::MetaCoreIsOptional<TValue>::ValueType innerValue{};
if (!MetaCoreDeserializeValue(reader, innerValue, registry)) {
return false;
}
value = std::move(innerValue);
return true;
} else {
const MetaCoreStructDescriptor* descriptor = registry.FindStruct<TValue>();
if (descriptor == nullptr) {
return false;
}
std::uint32_t serializedVersion = 0;
std::uint32_t fieldCount = 0;
if (!reader.ReadBytes(&serializedVersion, sizeof(serializedVersion)) ||
!reader.ReadBytes(&fieldCount, sizeof(fieldCount))) {
return false;
}
(void)serializedVersion;
for (std::uint32_t index = 0; index < fieldCount; ++index) {
std::string fieldName;
std::uint64_t fieldSize = 0;
if (!MetaCoreDeserializeValue(reader, fieldName, registry) ||
!reader.ReadBytes(&fieldSize, sizeof(fieldSize))) {
return false;
}
const auto fieldBytes = reader.ReadSpan(static_cast<std::size_t>(fieldSize));
if (!fieldBytes.has_value()) {
return false;
}
const auto fieldIterator = std::find_if(
descriptor->Fields.begin(),
descriptor->Fields.end(),
[&](const MetaCoreFieldDescriptor& field) {
return field.Name == fieldName;
}
);
if (fieldIterator == descriptor->Fields.end()) {
continue;
}
MetaCoreArchiveReader fieldReader(*fieldBytes);
if (!fieldIterator->Deserialize(&value, fieldReader, registry)) {
return false;
}
}
return true;
}
}
template <typename T>
std::optional<std::vector<std::byte>> MetaCoreSerializeToBytes(
const T& value,
const MetaCoreTypeRegistry& registry
) {
MetaCoreArchiveWriter writer;
if (!MetaCoreSerializeValue(writer, value, registry)) {
return std::nullopt;
}
return writer.GetBuffer();
}
template <typename T>
bool MetaCoreDeserializeFromBytes(
std::span<const std::byte> bytes,
T& value,
const MetaCoreTypeRegistry& registry
) {
MetaCoreArchiveReader reader(bytes);
return MetaCoreDeserializeValue(reader, value, registry) && reader.IsEof();
}
} // namespace MetaCore

View File

@ -0,0 +1,183 @@
#include "MetaCoreRuntimeData/MetaCoreRuntimeDataDispatcher.h"
#include "MetaCoreScene/MetaCoreComponents.h"
#include <algorithm>
namespace MetaCore {
MetaCoreRuntimeDataDispatcher::MetaCoreRuntimeDataDispatcher(MetaCoreScene& scene)
: Scene_(scene) {
}
void MetaCoreRuntimeDataDispatcher::SetDataPointDefinitions(std::vector<MetaCoreDataPointDefinition> dataPointDefinitions) {
DataPointDefinitions_ = std::move(dataPointDefinitions);
}
void MetaCoreRuntimeDataDispatcher::SetBindingDefinitions(std::vector<MetaCoreSceneBindingDefinition> bindingDefinitions) {
BindingDefinitions_ = std::move(bindingDefinitions);
RebuildBindingStatuses();
}
void MetaCoreRuntimeDataDispatcher::ApplyUpdates(const std::vector<MetaCoreRuntimeDataUpdate>& updates) {
for (const MetaCoreRuntimeDataUpdate& update : updates) {
const MetaCoreDataPointDefinition* dataPointDefinition = FindDataPointDefinition(update.DataPointId);
if (dataPointDefinition == nullptr) {
continue;
}
for (const MetaCoreSceneBindingDefinition& bindingDefinition : BindingDefinitions_) {
if (bindingDefinition.DataPointId != update.DataPointId) {
continue;
}
MetaCoreGameObject* gameObject = Scene_.FindGameObject(bindingDefinition.TargetObjectId);
if (gameObject == nullptr) {
MarkBindingFault(bindingDefinition.BindingId, "Target object missing");
continue;
}
bool applied = false;
switch (bindingDefinition.Target) {
case MetaCoreRuntimeBindingTarget::TransformPosition:
if (update.Value.Type == MetaCoreRuntimeValueType::Vec3) {
gameObject->Transform.Position = update.Value.Vec3Value;
applied = true;
}
break;
case MetaCoreRuntimeBindingTarget::MeshRendererVisible:
if (update.Value.Type == MetaCoreRuntimeValueType::Bool && gameObject->MeshRenderer.has_value()) {
gameObject->MeshRenderer->Visible = update.Value.BoolValue;
applied = true;
}
break;
case MetaCoreRuntimeBindingTarget::MeshRendererBaseColor:
if (update.Value.Type == MetaCoreRuntimeValueType::Vec3 && gameObject->MeshRenderer.has_value()) {
gameObject->MeshRenderer->BaseColor = update.Value.Vec3Value;
applied = true;
}
break;
case MetaCoreRuntimeBindingTarget::LightIntensity:
if (update.Value.Type == MetaCoreRuntimeValueType::Double && gameObject->Light.has_value()) {
gameObject->Light->Intensity = static_cast<float>(update.Value.DoubleValue);
applied = true;
}
break;
case MetaCoreRuntimeBindingTarget::LightColor:
if (update.Value.Type == MetaCoreRuntimeValueType::Vec3 && gameObject->Light.has_value()) {
gameObject->Light->Color = update.Value.Vec3Value;
applied = true;
}
break;
}
if (!applied) {
MarkBindingFault(bindingDefinition.BindingId, "Update type incompatible with binding target");
continue;
}
MarkBindingHealthy(bindingDefinition.BindingId, update.Value.SourceTimestamp);
}
}
}
void MetaCoreRuntimeDataDispatcher::TickStaleness(std::uint64_t currentTimestamp) {
for (MetaCoreRuntimeBindingStatus& bindingStatus : BindingStatuses_) {
if (bindingStatus.LastAppliedAt == 0) {
bindingStatus.Stale = true;
continue;
}
bindingStatus.Stale =
currentTimestamp > bindingStatus.LastAppliedAt &&
(currentTimestamp - bindingStatus.LastAppliedAt) > bindingStatus.StaleAfterMs;
}
}
const std::vector<MetaCoreRuntimeBindingStatus>& MetaCoreRuntimeDataDispatcher::GetBindingStatuses() const {
return BindingStatuses_;
}
bool MetaCoreRuntimeDataDispatcher::HasBindingFaults() const {
return std::any_of(
BindingStatuses_.begin(),
BindingStatuses_.end(),
[](const MetaCoreRuntimeBindingStatus& bindingStatus) {
return !bindingStatus.Healthy || bindingStatus.Stale;
}
);
}
MetaCoreRuntimeDiagnosticsSnapshot MetaCoreRuntimeDataDispatcher::BuildDiagnosticsSnapshot(
const std::vector<MetaCoreRuntimeDataSourceStatus>& sourceStatuses
) const {
MetaCoreRuntimeDiagnosticsSnapshot snapshot;
snapshot.SourceStatuses = sourceStatuses;
snapshot.BindingStatuses = BindingStatuses_;
snapshot.HasFaults = HasBindingFaults();
snapshot.HasFaults = snapshot.HasFaults || std::any_of(
snapshot.SourceStatuses.begin(),
snapshot.SourceStatuses.end(),
[](const MetaCoreRuntimeDataSourceStatus& sourceStatus) {
return sourceStatus.State == MetaCoreRuntimeDataSourceState::Degraded ||
sourceStatus.State == MetaCoreRuntimeDataSourceState::Faulted;
}
);
return snapshot;
}
void MetaCoreRuntimeDataDispatcher::RebuildBindingStatuses() {
BindingStatuses_.clear();
BindingStatusIndexById_.clear();
BindingStatuses_.reserve(BindingDefinitions_.size());
for (const MetaCoreSceneBindingDefinition& bindingDefinition : BindingDefinitions_) {
BindingStatusIndexById_[bindingDefinition.BindingId] = BindingStatuses_.size();
BindingStatuses_.push_back(MetaCoreRuntimeBindingStatus{
bindingDefinition.BindingId,
true,
false,
0,
1000,
{}
});
}
}
void MetaCoreRuntimeDataDispatcher::MarkBindingFault(const std::string& bindingId, const std::string& error) {
const auto iterator = BindingStatusIndexById_.find(bindingId);
if (iterator == BindingStatusIndexById_.end()) {
return;
}
MetaCoreRuntimeBindingStatus& status = BindingStatuses_[iterator->second];
status.Healthy = false;
status.Stale = false;
status.LastError = error;
}
void MetaCoreRuntimeDataDispatcher::MarkBindingHealthy(const std::string& bindingId, std::uint64_t appliedAt) {
const auto iterator = BindingStatusIndexById_.find(bindingId);
if (iterator == BindingStatusIndexById_.end()) {
return;
}
MetaCoreRuntimeBindingStatus& status = BindingStatuses_[iterator->second];
status.Healthy = true;
status.Stale = false;
status.LastAppliedAt = appliedAt;
status.LastError.clear();
}
const MetaCoreDataPointDefinition* MetaCoreRuntimeDataDispatcher::FindDataPointDefinition(const std::string& dataPointId) const {
const auto iterator = std::find_if(
DataPointDefinitions_.begin(),
DataPointDefinitions_.end(),
[&](const MetaCoreDataPointDefinition& definition) {
return definition.Id == dataPointId;
}
);
return iterator == DataPointDefinitions_.end() ? nullptr : &(*iterator);
}
} // namespace MetaCore

View File

@ -0,0 +1,207 @@
#include "MetaCoreRuntimeData/MetaCoreRuntimeDataProject.h"
#include <algorithm>
#include <fstream>
#include <unordered_set>
namespace MetaCore {
namespace {
template <typename T>
[[nodiscard]] bool MetaCoreWriteBinaryDocument(
const std::filesystem::path& path,
const T& document,
const MetaCoreTypeRegistry& registry
) {
const auto bytes = MetaCoreSerializeToBytes(document, registry);
if (!bytes.has_value()) {
return false;
}
std::filesystem::create_directories(path.parent_path());
std::ofstream output(path, std::ios::binary | std::ios::trunc);
if (!output.is_open()) {
return false;
}
if (!bytes->empty()) {
output.write(reinterpret_cast<const char*>(bytes->data()), static_cast<std::streamsize>(bytes->size()));
}
return output.good();
}
template <typename T>
[[nodiscard]] std::optional<T> MetaCoreReadBinaryDocument(
const std::filesystem::path& path,
const MetaCoreTypeRegistry& registry
) {
std::ifstream input(path, std::ios::binary);
if (!input.is_open()) {
return std::nullopt;
}
input.seekg(0, std::ios::end);
const auto size = static_cast<std::size_t>(input.tellg());
input.seekg(0, std::ios::beg);
std::vector<std::byte> bytes(size);
if (size > 0 && !input.read(reinterpret_cast<char*>(bytes.data()), static_cast<std::streamsize>(size))) {
return std::nullopt;
}
T document{};
if (!MetaCoreDeserializeFromBytes(std::span<const std::byte>(bytes.data(), bytes.size()), document, registry)) {
return std::nullopt;
}
return document;
}
} // namespace
bool MetaCoreWriteRuntimeDataSourcesDocument(
const std::filesystem::path& path,
const MetaCoreRuntimeDataSourcesDocument& document,
const MetaCoreTypeRegistry& registry
) {
return MetaCoreWriteBinaryDocument(path, document, registry);
}
bool MetaCoreWriteRuntimeBindingsDocument(
const std::filesystem::path& path,
const MetaCoreRuntimeBindingsDocument& document,
const MetaCoreTypeRegistry& registry
) {
return MetaCoreWriteBinaryDocument(path, document, registry);
}
std::optional<MetaCoreRuntimeDataSourcesDocument> MetaCoreReadRuntimeDataSourcesDocument(
const std::filesystem::path& path,
const MetaCoreTypeRegistry& registry
) {
return MetaCoreReadBinaryDocument<MetaCoreRuntimeDataSourcesDocument>(path, registry);
}
std::optional<MetaCoreRuntimeBindingsDocument> MetaCoreReadRuntimeBindingsDocument(
const std::filesystem::path& path,
const MetaCoreTypeRegistry& registry
) {
return MetaCoreReadBinaryDocument<MetaCoreRuntimeBindingsDocument>(path, registry);
}
bool MetaCoreWriteRuntimeProjectDocument(
const std::filesystem::path& path,
const MetaCoreRuntimeProjectDocument& document,
const MetaCoreTypeRegistry& registry
) {
return MetaCoreWriteBinaryDocument(path, document, registry);
}
std::optional<MetaCoreRuntimeProjectDocument> MetaCoreReadRuntimeProjectDocument(
const std::filesystem::path& path,
const MetaCoreTypeRegistry& registry
) {
return MetaCoreReadBinaryDocument<MetaCoreRuntimeProjectDocument>(path, registry);
}
bool MetaCoreWriteRuntimeDiagnosticsSnapshot(
const std::filesystem::path& path,
const MetaCoreRuntimeDiagnosticsSnapshot& snapshot,
const MetaCoreTypeRegistry& registry
) {
return MetaCoreWriteBinaryDocument(path, snapshot, registry);
}
std::optional<MetaCoreRuntimeDiagnosticsSnapshot> MetaCoreReadRuntimeDiagnosticsSnapshot(
const std::filesystem::path& path,
const MetaCoreTypeRegistry& registry
) {
return MetaCoreReadBinaryDocument<MetaCoreRuntimeDiagnosticsSnapshot>(path, registry);
}
std::vector<MetaCoreRuntimeConfigIssue> MetaCoreValidateRuntimeDataDocuments(
const MetaCoreRuntimeDataSourcesDocument& sourcesDocument,
const MetaCoreRuntimeBindingsDocument& bindingsDocument
) {
std::vector<MetaCoreRuntimeConfigIssue> issues;
std::unordered_set<std::string> sourceIds;
for (const auto& source : sourcesDocument.Sources) {
if (source.Id.empty()) {
issues.push_back({MetaCoreRuntimeConfigIssueSeverity::Error, "Source", "存在空的 SourceId"});
continue;
}
if (!sourceIds.insert(source.Id).second) {
issues.push_back({MetaCoreRuntimeConfigIssueSeverity::Error, source.Id, "SourceId 重复"});
}
if (source.AdapterType.empty()) {
issues.push_back({MetaCoreRuntimeConfigIssueSeverity::Error, source.Id, "AdapterType 不能为空"});
}
if (source.AdapterType == "file_replay") {
const auto filePathSetting = std::find_if(
source.ConnectionSettings.begin(),
source.ConnectionSettings.end(),
[](const MetaCoreDataSourceSetting& setting) {
return setting.Key == "file_path";
}
);
if (filePathSetting == source.ConnectionSettings.end() || filePathSetting->Value.empty()) {
issues.push_back({MetaCoreRuntimeConfigIssueSeverity::Error, source.Id, "file_replay 缺少 file_path"});
}
}
if (source.AdapterType == "tcp") {
const auto portSetting = std::find_if(
source.ConnectionSettings.begin(),
source.ConnectionSettings.end(),
[](const MetaCoreDataSourceSetting& setting) {
return setting.Key == "port";
}
);
if (portSetting == source.ConnectionSettings.end() || portSetting->Value.empty()) {
issues.push_back({MetaCoreRuntimeConfigIssueSeverity::Error, source.Id, "tcp 缺少 port"});
}
}
}
std::unordered_set<std::string> dataPointIds;
for (const auto& dataPoint : sourcesDocument.DataPoints) {
if (dataPoint.Id.empty()) {
issues.push_back({MetaCoreRuntimeConfigIssueSeverity::Error, "DataPoint", "存在空的 DataPointId"});
continue;
}
if (!dataPointIds.insert(dataPoint.Id).second) {
issues.push_back({MetaCoreRuntimeConfigIssueSeverity::Error, dataPoint.Id, "DataPointId 重复"});
}
if (dataPoint.SourceId.empty() || !sourceIds.contains(dataPoint.SourceId)) {
issues.push_back({MetaCoreRuntimeConfigIssueSeverity::Error, dataPoint.Id, "DataPoint 引用了不存在的 SourceId"});
}
if (dataPoint.ExternalAddress.empty()) {
issues.push_back({MetaCoreRuntimeConfigIssueSeverity::Warning, dataPoint.Id, "ExternalAddress 为空"});
}
}
std::unordered_set<std::string> bindingIds;
for (const auto& binding : bindingsDocument.Bindings) {
if (binding.BindingId.empty()) {
issues.push_back({MetaCoreRuntimeConfigIssueSeverity::Error, "Binding", "存在空的 BindingId"});
continue;
}
if (!bindingIds.insert(binding.BindingId).second) {
issues.push_back({MetaCoreRuntimeConfigIssueSeverity::Error, binding.BindingId, "BindingId 重复"});
}
if (binding.DataPointId.empty() || !dataPointIds.contains(binding.DataPointId)) {
issues.push_back({MetaCoreRuntimeConfigIssueSeverity::Error, binding.BindingId, "Binding 引用了不存在的 DataPointId"});
}
if (binding.TargetObjectId == 0) {
issues.push_back({MetaCoreRuntimeConfigIssueSeverity::Error, binding.BindingId, "Binding 缺少 TargetObjectId"});
}
}
if (sourcesDocument.Sources.empty()) {
issues.push_back({MetaCoreRuntimeConfigIssueSeverity::Warning, "Runtime", "当前没有任何 Source"});
}
return issues;
}
} // namespace MetaCore

View File

@ -0,0 +1,618 @@
#include "MetaCoreRuntimeData/MetaCoreRuntimeDataSource.h"
#include <algorithm>
#include <array>
#include <cctype>
#include <cmath>
#include <filesystem>
#include <fstream>
#include <memory>
#include <sstream>
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <winsock2.h>
#include <ws2tcpip.h>
namespace MetaCore {
namespace {
constexpr SOCKET MetaCoreInvalidSocket = INVALID_SOCKET;
[[nodiscard]] std::string MetaCoreTrim(std::string value) {
const auto isSpace = [](unsigned char ch) {
return std::isspace(ch) != 0;
};
value.erase(value.begin(), std::find_if(value.begin(), value.end(), [&](unsigned char ch) {
return !isSpace(ch);
}));
value.erase(std::find_if(value.rbegin(), value.rend(), [&](unsigned char ch) {
return !isSpace(ch);
}).base(), value.end());
return value;
}
[[nodiscard]] const std::string* MetaCoreFindSetting(
const MetaCoreDataSourceDefinition& sourceDefinition,
const std::string& key
) {
const auto iterator = std::find_if(
sourceDefinition.ConnectionSettings.begin(),
sourceDefinition.ConnectionSettings.end(),
[&](const MetaCoreDataSourceSetting& setting) {
return setting.Key == key;
}
);
if (iterator == sourceDefinition.ConnectionSettings.end()) {
return nullptr;
}
return &iterator->Value;
}
[[nodiscard]] bool MetaCoreParseValuePayload(
std::istringstream& parser,
const std::string& valueType,
MetaCoreRuntimeDataValue& value,
std::string& errorMessage
) {
value.Quality = MetaCoreRuntimeDataQuality::Good;
if (valueType == "bool") {
std::string token;
if (!(parser >> token)) {
errorMessage = "bool value missing";
return false;
}
value.Type = MetaCoreRuntimeValueType::Bool;
value.BoolValue = (token == "true" || token == "1");
return true;
}
if (valueType == "int64") {
std::int64_t parsedValue = 0;
if (!(parser >> parsedValue)) {
errorMessage = "int64 value missing";
return false;
}
value.Type = MetaCoreRuntimeValueType::Int64;
value.Int64Value = parsedValue;
return true;
}
if (valueType == "double") {
double parsedValue = 0.0;
if (!(parser >> parsedValue)) {
errorMessage = "double value missing";
return false;
}
value.Type = MetaCoreRuntimeValueType::Double;
value.DoubleValue = parsedValue;
return true;
}
if (valueType == "string") {
std::string parsedValue;
std::getline(parser >> std::ws, parsedValue);
value.Type = MetaCoreRuntimeValueType::String;
value.StringValue = MetaCoreTrim(parsedValue);
return true;
}
if (valueType == "vec3") {
float x = 0.0F;
float y = 0.0F;
float z = 0.0F;
if (!(parser >> x >> y >> z)) {
errorMessage = "vec3 value missing";
return false;
}
value.Type = MetaCoreRuntimeValueType::Vec3;
value.Vec3Value = glm::vec3(x, y, z);
return true;
}
errorMessage = "value type not supported";
return false;
}
[[nodiscard]] std::string MetaCoreResolveReplayFilePath(const std::string& replayFilePath) {
const std::filesystem::path inputPath(replayFilePath);
const std::vector<std::filesystem::path> candidates{
std::filesystem::current_path() / inputPath,
std::filesystem::current_path() / ".." / ".." / ".." / inputPath,
std::filesystem::current_path() / ".." / ".." / ".." / "TestProject" / "Runtime" / inputPath.filename()
};
for (const auto& candidate : candidates) {
std::error_code errorCode;
if (std::filesystem::exists(candidate, errorCode)) {
return std::filesystem::weakly_canonical(candidate, errorCode).string();
}
}
return (std::filesystem::current_path() / inputPath).string();
}
} // namespace
std::string MetaCoreMockRuntimeDataSourceAdapter::GetAdapterType() const {
return "mock";
}
bool MetaCoreMockRuntimeDataSourceAdapter::Configure(const MetaCoreDataSourceDefinition& sourceDefinition) {
SourceDefinition_ = sourceDefinition;
Status_.SourceId = sourceDefinition.Id;
Status_.State = MetaCoreRuntimeDataSourceState::Disconnected;
Status_.LastConnectedAt = 0;
Status_.LastUpdateAt = 0;
Status_.LastError.clear();
ElapsedSeconds_ = 0.0;
Sequence_ = 0;
return true;
}
bool MetaCoreMockRuntimeDataSourceAdapter::Connect() {
Status_.State = MetaCoreRuntimeDataSourceState::Connected;
Status_.LastConnectedAt = 1;
Status_.LastError.clear();
return true;
}
void MetaCoreMockRuntimeDataSourceAdapter::Disconnect() {
Status_.State = MetaCoreRuntimeDataSourceState::Disconnected;
Status_.LastError.clear();
}
void MetaCoreMockRuntimeDataSourceAdapter::Tick(double deltaSeconds) {
if (Status_.State != MetaCoreRuntimeDataSourceState::Connected) {
return;
}
ElapsedSeconds_ += std::max(0.0, deltaSeconds);
if (!EmitUpdates_) {
Status_.State = MetaCoreRuntimeDataSourceState::Degraded;
Status_.LastError = "Mock adapter update emission disabled";
} else {
Status_.State = MetaCoreRuntimeDataSourceState::Connected;
Status_.LastError.clear();
}
}
std::vector<MetaCoreRuntimeDataUpdate> MetaCoreMockRuntimeDataSourceAdapter::PollUpdates() {
if (Status_.State == MetaCoreRuntimeDataSourceState::Disconnected || !EmitUpdates_) {
return {};
}
const std::uint64_t timestamp = static_cast<std::uint64_t>(ElapsedSeconds_ * 1000.0);
Status_.LastUpdateAt = timestamp;
std::vector<MetaCoreRuntimeDataUpdate> updates;
updates.push_back(MetaCoreRuntimeDataUpdate{
"cube.position",
MetaCoreRuntimeDataValue{
MetaCoreRuntimeValueType::Vec3,
false,
0,
0.0,
{},
glm::vec3(static_cast<float>(std::sin(ElapsedSeconds_)) * 2.0F, 0.5F, 0.0F),
timestamp,
MetaCoreRuntimeDataQuality::Good
},
++Sequence_
});
updates.push_back(MetaCoreRuntimeDataUpdate{
"cube.visible",
MetaCoreRuntimeDataValue{
MetaCoreRuntimeValueType::Bool,
std::fmod(ElapsedSeconds_, 2.0) < 1.0,
0,
0.0,
{},
glm::vec3(0.0F, 0.0F, 0.0F),
timestamp,
MetaCoreRuntimeDataQuality::Good
},
++Sequence_
});
updates.push_back(MetaCoreRuntimeDataUpdate{
"cube.base_color",
MetaCoreRuntimeDataValue{
MetaCoreRuntimeValueType::Vec3,
false,
0,
0.0,
{},
glm::vec3(
0.5F + static_cast<float>(std::sin(ElapsedSeconds_)) * 0.5F,
0.6F,
0.9F - static_cast<float>(std::sin(ElapsedSeconds_)) * 0.3F
),
timestamp,
MetaCoreRuntimeDataQuality::Good
},
++Sequence_
});
return updates;
}
const MetaCoreRuntimeDataSourceStatus& MetaCoreMockRuntimeDataSourceAdapter::GetStatus() const {
return Status_;
}
void MetaCoreMockRuntimeDataSourceAdapter::SetEmitUpdates(bool emitUpdates) {
EmitUpdates_ = emitUpdates;
}
std::string MetaCoreFileReplayRuntimeDataSourceAdapter::GetAdapterType() const {
return "file_replay";
}
bool MetaCoreFileReplayRuntimeDataSourceAdapter::Configure(const MetaCoreDataSourceDefinition& sourceDefinition) {
SourceDefinition_ = sourceDefinition;
Status_.SourceId = sourceDefinition.Id;
Status_.State = MetaCoreRuntimeDataSourceState::Disconnected;
Status_.LastConnectedAt = 0;
Status_.LastUpdateAt = 0;
Status_.LastError.clear();
Frames_.clear();
ReplayFilePath_.clear();
NextFrameIndex_ = 0;
ElapsedSeconds_ = 0.0;
Sequence_ = 0;
const std::string* replayFilePath = MetaCoreFindSetting(sourceDefinition, "file_path");
if (replayFilePath == nullptr || replayFilePath->empty()) {
Status_.State = MetaCoreRuntimeDataSourceState::Faulted;
Status_.LastError = "Missing file_path connection setting";
return false;
}
ReplayFilePath_ = MetaCoreResolveReplayFilePath(*replayFilePath);
return true;
}
bool MetaCoreFileReplayRuntimeDataSourceAdapter::Connect() {
Status_.State = MetaCoreRuntimeDataSourceState::Connecting;
if (!LoadReplayFile()) {
Status_.State = MetaCoreRuntimeDataSourceState::Faulted;
if (Status_.LastError.empty()) {
Status_.LastError = "Failed to load replay file";
}
return false;
}
Status_.State = MetaCoreRuntimeDataSourceState::Connected;
Status_.LastConnectedAt = 1;
Status_.LastError.clear();
return true;
}
void MetaCoreFileReplayRuntimeDataSourceAdapter::Disconnect() {
Status_.State = MetaCoreRuntimeDataSourceState::Disconnected;
Status_.LastError.clear();
NextFrameIndex_ = 0;
ElapsedSeconds_ = 0.0;
}
void MetaCoreFileReplayRuntimeDataSourceAdapter::Tick(double deltaSeconds) {
if (Status_.State != MetaCoreRuntimeDataSourceState::Connected &&
Status_.State != MetaCoreRuntimeDataSourceState::Degraded) {
return;
}
ElapsedSeconds_ += std::max(0.0, deltaSeconds);
if (Frames_.empty()) {
Status_.State = MetaCoreRuntimeDataSourceState::Degraded;
Status_.LastError = "Replay file has no frames";
return;
}
if (NextFrameIndex_ >= Frames_.size()) {
Status_.State = MetaCoreRuntimeDataSourceState::Degraded;
Status_.LastError = "Replay stream exhausted";
} else {
Status_.State = MetaCoreRuntimeDataSourceState::Connected;
Status_.LastError.clear();
}
}
std::vector<MetaCoreRuntimeDataUpdate> MetaCoreFileReplayRuntimeDataSourceAdapter::PollUpdates() {
if (Status_.State == MetaCoreRuntimeDataSourceState::Disconnected ||
Status_.State == MetaCoreRuntimeDataSourceState::Faulted ||
Frames_.empty()) {
return {};
}
std::vector<MetaCoreRuntimeDataUpdate> updates;
while (NextFrameIndex_ < Frames_.size() &&
Frames_[NextFrameIndex_].EmitAfterSeconds <= ElapsedSeconds_) {
MetaCoreRuntimeDataUpdate update = Frames_[NextFrameIndex_].Update;
update.Sequence = ++Sequence_;
update.Value.SourceTimestamp = static_cast<std::uint64_t>(Frames_[NextFrameIndex_].EmitAfterSeconds * 1000.0);
Status_.LastUpdateAt = update.Value.SourceTimestamp;
updates.push_back(update);
++NextFrameIndex_;
}
if (NextFrameIndex_ >= Frames_.size() && updates.empty()) {
Status_.State = MetaCoreRuntimeDataSourceState::Degraded;
Status_.LastError = "Replay stream exhausted";
}
return updates;
}
const MetaCoreRuntimeDataSourceStatus& MetaCoreFileReplayRuntimeDataSourceAdapter::GetStatus() const {
return Status_;
}
bool MetaCoreFileReplayRuntimeDataSourceAdapter::LoadReplayFile() {
Frames_.clear();
std::ifstream input(ReplayFilePath_);
if (!input) {
Status_.LastError = "Unable to open replay file: " + ReplayFilePath_;
return false;
}
std::string line;
std::size_t lineNumber = 0;
while (std::getline(input, line)) {
++lineNumber;
line = MetaCoreTrim(line);
if (line.empty() || line.front() == '#') {
continue;
}
std::istringstream parser(line);
double emitAfterSeconds = 0.0;
std::string dataPointId;
std::string valueType;
if (!(parser >> emitAfterSeconds >> dataPointId >> valueType)) {
Status_.LastError = "Replay parse error at line " + std::to_string(lineNumber);
return false;
}
MetaCoreRuntimeDataUpdate update;
update.DataPointId = dataPointId;
update.Sequence = 0;
std::string parseError;
if (!MetaCoreParseValuePayload(parser, valueType, update.Value, parseError)) {
Status_.LastError = "Replay " + parseError + " at line " + std::to_string(lineNumber);
return false;
}
Frames_.push_back(ReplayFrame{
emitAfterSeconds,
update
});
}
std::sort(Frames_.begin(), Frames_.end(), [](const ReplayFrame& lhs, const ReplayFrame& rhs) {
return lhs.EmitAfterSeconds < rhs.EmitAfterSeconds;
});
return true;
}
std::string MetaCoreTcpRuntimeDataSourceAdapter::GetAdapterType() const {
return "tcp";
}
bool MetaCoreTcpRuntimeDataSourceAdapter::Configure(const MetaCoreDataSourceDefinition& sourceDefinition) {
SourceDefinition_ = sourceDefinition;
Status_.SourceId = sourceDefinition.Id;
Status_.State = MetaCoreRuntimeDataSourceState::Disconnected;
Status_.LastConnectedAt = 0;
Status_.LastUpdateAt = 0;
Status_.LastError.clear();
Host_ = "127.0.0.1";
Port_ = 0;
Sequence_ = 0;
ElapsedSeconds_ = 0.0;
ReceiveBuffer_.clear();
PendingUpdates_.clear();
if (const std::string* host = MetaCoreFindSetting(sourceDefinition, "host"); host != nullptr && !host->empty()) {
Host_ = *host;
}
const std::string* portValue = MetaCoreFindSetting(sourceDefinition, "port");
if (portValue == nullptr || portValue->empty()) {
Status_.State = MetaCoreRuntimeDataSourceState::Faulted;
Status_.LastError = "Missing port connection setting";
return false;
}
try {
Port_ = static_cast<std::uint16_t>(std::stoul(*portValue));
} catch (...) {
Status_.State = MetaCoreRuntimeDataSourceState::Faulted;
Status_.LastError = "Invalid port connection setting";
return false;
}
return true;
}
bool MetaCoreTcpRuntimeDataSourceAdapter::EnsureWinsockInitialized() {
static bool initialized = false;
if (initialized) {
return true;
}
WSADATA wsaData{};
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
Status_.LastError = "WSAStartup failed";
return false;
}
initialized = true;
return true;
}
bool MetaCoreTcpRuntimeDataSourceAdapter::Connect() {
Status_.State = MetaCoreRuntimeDataSourceState::Connecting;
if (!EnsureWinsockInitialized()) {
Status_.State = MetaCoreRuntimeDataSourceState::Faulted;
return false;
}
addrinfo hints{};
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
addrinfo* result = nullptr;
const std::string portString = std::to_string(Port_);
if (getaddrinfo(Host_.c_str(), portString.c_str(), &hints, &result) != 0 || result == nullptr) {
Status_.State = MetaCoreRuntimeDataSourceState::Faulted;
Status_.LastError = "getaddrinfo failed";
return false;
}
SOCKET socketHandle = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
if (socketHandle == MetaCoreInvalidSocket) {
freeaddrinfo(result);
Status_.State = MetaCoreRuntimeDataSourceState::Faulted;
Status_.LastError = "socket creation failed";
return false;
}
if (connect(socketHandle, result->ai_addr, static_cast<int>(result->ai_addrlen)) == SOCKET_ERROR) {
closesocket(socketHandle);
freeaddrinfo(result);
Status_.State = MetaCoreRuntimeDataSourceState::Faulted;
Status_.LastError = "tcp connect failed";
return false;
}
freeaddrinfo(result);
u_long nonBlocking = 1;
ioctlsocket(socketHandle, FIONBIO, &nonBlocking);
SocketHandle_ = reinterpret_cast<void*>(socketHandle);
Status_.State = MetaCoreRuntimeDataSourceState::Connected;
Status_.LastConnectedAt = 1;
Status_.LastError.clear();
return true;
}
void MetaCoreTcpRuntimeDataSourceAdapter::Disconnect() {
if (SocketHandle_ != nullptr) {
closesocket(reinterpret_cast<SOCKET>(SocketHandle_));
SocketHandle_ = nullptr;
}
Status_.State = MetaCoreRuntimeDataSourceState::Disconnected;
Status_.LastError.clear();
ReceiveBuffer_.clear();
PendingUpdates_.clear();
}
void MetaCoreTcpRuntimeDataSourceAdapter::Tick(double deltaSeconds) {
ElapsedSeconds_ += std::max(0.0, deltaSeconds);
if (SocketHandle_ == nullptr || Status_.State == MetaCoreRuntimeDataSourceState::Faulted) {
return;
}
SOCKET socketHandle = reinterpret_cast<SOCKET>(SocketHandle_);
std::array<char, 1024> buffer{};
for (;;) {
const int received = recv(socketHandle, buffer.data(), static_cast<int>(buffer.size()), 0);
if (received > 0) {
ReceiveBuffer_.append(buffer.data(), static_cast<std::size_t>(received));
Status_.State = MetaCoreRuntimeDataSourceState::Connected;
Status_.LastError.clear();
continue;
}
if (received == 0) {
Status_.State = MetaCoreRuntimeDataSourceState::Degraded;
Status_.LastError = "tcp stream closed by peer";
break;
}
const int socketError = WSAGetLastError();
if (socketError == WSAEWOULDBLOCK) {
break;
}
Status_.State = MetaCoreRuntimeDataSourceState::Faulted;
Status_.LastError = "tcp recv failed";
break;
}
for (;;) {
const std::size_t newlineIndex = ReceiveBuffer_.find('\n');
if (newlineIndex == std::string::npos) {
break;
}
std::string line = MetaCoreTrim(ReceiveBuffer_.substr(0, newlineIndex));
ReceiveBuffer_.erase(0, newlineIndex + 1);
if (line.empty() || line.front() == '#') {
continue;
}
MetaCoreRuntimeDataUpdate update;
if (!ParseSocketLine(line, update)) {
Status_.State = MetaCoreRuntimeDataSourceState::Degraded;
if (Status_.LastError.empty()) {
Status_.LastError = "tcp line parse failed";
}
continue;
}
PendingUpdates_.push_back(std::move(update));
}
}
std::vector<MetaCoreRuntimeDataUpdate> MetaCoreTcpRuntimeDataSourceAdapter::PollUpdates() {
if (PendingUpdates_.empty()) {
return {};
}
const std::uint64_t timestamp = static_cast<std::uint64_t>(ElapsedSeconds_ * 1000.0);
for (auto& update : PendingUpdates_) {
update.Sequence = ++Sequence_;
update.Value.SourceTimestamp = timestamp;
}
Status_.LastUpdateAt = timestamp;
std::vector<MetaCoreRuntimeDataUpdate> updates = std::move(PendingUpdates_);
PendingUpdates_.clear();
return updates;
}
const MetaCoreRuntimeDataSourceStatus& MetaCoreTcpRuntimeDataSourceAdapter::GetStatus() const {
return Status_;
}
bool MetaCoreTcpRuntimeDataSourceAdapter::ParseSocketLine(const std::string& line, MetaCoreRuntimeDataUpdate& update) {
std::istringstream parser(line);
std::string dataPointId;
std::string valueType;
if (!(parser >> dataPointId >> valueType)) {
Status_.LastError = "tcp line missing header fields";
return false;
}
update = MetaCoreRuntimeDataUpdate{};
update.DataPointId = dataPointId;
std::string parseError;
if (!MetaCoreParseValuePayload(parser, valueType, update.Value, parseError)) {
Status_.LastError = "tcp " + parseError;
return false;
}
return true;
}
std::unique_ptr<MetaCoreIRuntimeDataSourceAdapter> MetaCoreCreateRuntimeDataSourceAdapter(
const std::string& adapterType
) {
if (adapterType == "mock") {
return std::make_unique<MetaCoreMockRuntimeDataSourceAdapter>();
}
if (adapterType == "file_replay") {
return std::make_unique<MetaCoreFileReplayRuntimeDataSourceAdapter>();
}
if (adapterType == "tcp") {
return std::make_unique<MetaCoreTcpRuntimeDataSourceAdapter>();
}
return nullptr;
}
} // namespace MetaCore

View File

@ -0,0 +1,42 @@
#pragma once
#include "MetaCoreRuntimeData/MetaCoreRuntimeDataProject.h"
#include "MetaCoreRuntimeData/MetaCoreRuntimeDataTypes.h"
#include "MetaCoreScene/MetaCoreScene.h"
#include <string>
#include <unordered_map>
#include <vector>
namespace MetaCore {
class MetaCoreRuntimeDataDispatcher {
public:
explicit MetaCoreRuntimeDataDispatcher(MetaCoreScene& scene);
void SetDataPointDefinitions(std::vector<MetaCoreDataPointDefinition> dataPointDefinitions);
void SetBindingDefinitions(std::vector<MetaCoreSceneBindingDefinition> bindingDefinitions);
void ApplyUpdates(const std::vector<MetaCoreRuntimeDataUpdate>& updates);
void TickStaleness(std::uint64_t currentTimestamp);
[[nodiscard]] const std::vector<MetaCoreRuntimeBindingStatus>& GetBindingStatuses() const;
[[nodiscard]] bool HasBindingFaults() const;
[[nodiscard]] MetaCoreRuntimeDiagnosticsSnapshot BuildDiagnosticsSnapshot(
const std::vector<MetaCoreRuntimeDataSourceStatus>& sourceStatuses
) const;
private:
void RebuildBindingStatuses();
void MarkBindingFault(const std::string& bindingId, const std::string& error);
void MarkBindingHealthy(const std::string& bindingId, std::uint64_t appliedAt);
const MetaCoreDataPointDefinition* FindDataPointDefinition(const std::string& dataPointId) const;
MetaCoreScene& Scene_;
std::vector<MetaCoreDataPointDefinition> DataPointDefinitions_{};
std::vector<MetaCoreSceneBindingDefinition> BindingDefinitions_{};
std::vector<MetaCoreRuntimeBindingStatus> BindingStatuses_{};
std::unordered_map<std::string, std::size_t> BindingStatusIndexById_{};
};
} // namespace MetaCore

View File

@ -0,0 +1,170 @@
#pragma once
#include "MetaCoreFoundation/MetaCoreId.h"
#include "MetaCoreFoundation/MetaCoreReflection.h"
#include "MetaCoreRuntimeData/MetaCoreRuntimeDataTypes.h"
#include <filesystem>
#include <cstdint>
#include <string>
#include <vector>
namespace MetaCore {
MC_STRUCT()
struct MetaCoreDataSourceSetting {
MC_GENERATED_BODY()
MC_PROPERTY()
std::string Key{};
MC_PROPERTY()
std::string Value{};
};
MC_STRUCT()
struct MetaCoreDataSourceDefinition {
MC_GENERATED_BODY()
MC_PROPERTY()
std::string Id{};
MC_PROPERTY()
std::string AdapterType{};
MC_PROPERTY()
std::string DisplayName{};
MC_PROPERTY()
std::vector<MetaCoreDataSourceSetting> ConnectionSettings{};
MC_PROPERTY()
bool AutoConnect = true;
MC_PROPERTY()
std::uint64_t ReconnectDelayMs = 1000;
};
MC_STRUCT()
struct MetaCoreDataPointDefinition {
MC_GENERATED_BODY()
MC_PROPERTY()
std::string Id{};
MC_PROPERTY()
std::string SourceId{};
MC_PROPERTY()
std::string ExternalAddress{};
MC_PROPERTY()
MetaCoreRuntimeValueType ValueType = MetaCoreRuntimeValueType::Bool;
};
MC_STRUCT()
struct MetaCoreSceneBindingDefinition {
MC_GENERATED_BODY()
MC_PROPERTY()
std::string BindingId{};
MC_PROPERTY()
std::string DataPointId{};
MC_PROPERTY()
MetaCoreId TargetObjectId = 0;
MC_PROPERTY()
MetaCoreRuntimeBindingTarget Target = MetaCoreRuntimeBindingTarget::TransformPosition;
MC_PROPERTY()
MetaCoreRuntimeMissingDataPolicy MissingDataPolicy = MetaCoreRuntimeMissingDataPolicy::KeepLastValue;
};
MC_STRUCT()
struct MetaCoreRuntimeDataSourcesDocument {
MC_GENERATED_BODY()
MC_PROPERTY()
std::vector<MetaCoreDataSourceDefinition> Sources{};
MC_PROPERTY()
std::vector<MetaCoreDataPointDefinition> DataPoints{};
};
MC_STRUCT()
struct MetaCoreRuntimeBindingsDocument {
MC_GENERATED_BODY()
MC_PROPERTY()
std::vector<MetaCoreSceneBindingDefinition> Bindings{};
};
MC_STRUCT()
struct MetaCoreRuntimeProjectDocument {
MC_GENERATED_BODY()
MC_PROPERTY()
std::filesystem::path StartupScenePath{};
MC_PROPERTY()
std::filesystem::path DataSourcesPath{};
MC_PROPERTY()
std::filesystem::path BindingsPath{};
MC_PROPERTY()
std::filesystem::path DiagnosticsPath{};
};
[[nodiscard]] bool MetaCoreWriteRuntimeDataSourcesDocument(
const std::filesystem::path& path,
const MetaCoreRuntimeDataSourcesDocument& document,
const MetaCoreTypeRegistry& registry
);
[[nodiscard]] bool MetaCoreWriteRuntimeBindingsDocument(
const std::filesystem::path& path,
const MetaCoreRuntimeBindingsDocument& document,
const MetaCoreTypeRegistry& registry
);
[[nodiscard]] std::optional<MetaCoreRuntimeDataSourcesDocument> MetaCoreReadRuntimeDataSourcesDocument(
const std::filesystem::path& path,
const MetaCoreTypeRegistry& registry
);
[[nodiscard]] std::optional<MetaCoreRuntimeBindingsDocument> MetaCoreReadRuntimeBindingsDocument(
const std::filesystem::path& path,
const MetaCoreTypeRegistry& registry
);
[[nodiscard]] bool MetaCoreWriteRuntimeProjectDocument(
const std::filesystem::path& path,
const MetaCoreRuntimeProjectDocument& document,
const MetaCoreTypeRegistry& registry
);
[[nodiscard]] std::optional<MetaCoreRuntimeProjectDocument> MetaCoreReadRuntimeProjectDocument(
const std::filesystem::path& path,
const MetaCoreTypeRegistry& registry
);
[[nodiscard]] bool MetaCoreWriteRuntimeDiagnosticsSnapshot(
const std::filesystem::path& path,
const MetaCoreRuntimeDiagnosticsSnapshot& snapshot,
const MetaCoreTypeRegistry& registry
);
[[nodiscard]] std::optional<MetaCoreRuntimeDiagnosticsSnapshot> MetaCoreReadRuntimeDiagnosticsSnapshot(
const std::filesystem::path& path,
const MetaCoreTypeRegistry& registry
);
[[nodiscard]] std::vector<MetaCoreRuntimeConfigIssue> MetaCoreValidateRuntimeDataDocuments(
const MetaCoreRuntimeDataSourcesDocument& sourcesDocument,
const MetaCoreRuntimeBindingsDocument& bindingsDocument
);
} // namespace MetaCore

View File

@ -0,0 +1,101 @@
#pragma once
#include "MetaCoreRuntimeData/MetaCoreRuntimeDataProject.h"
#include "MetaCoreRuntimeData/MetaCoreRuntimeDataTypes.h"
#include <memory>
#include <string>
#include <vector>
namespace MetaCore {
class MetaCoreIRuntimeDataSourceAdapter {
public:
virtual ~MetaCoreIRuntimeDataSourceAdapter() = default;
[[nodiscard]] virtual std::string GetAdapterType() const = 0;
virtual bool Configure(const MetaCoreDataSourceDefinition& sourceDefinition) = 0;
virtual bool Connect() = 0;
virtual void Disconnect() = 0;
virtual void Tick(double deltaSeconds) = 0;
[[nodiscard]] virtual std::vector<MetaCoreRuntimeDataUpdate> PollUpdates() = 0;
[[nodiscard]] virtual const MetaCoreRuntimeDataSourceStatus& GetStatus() const = 0;
};
class MetaCoreMockRuntimeDataSourceAdapter final : public MetaCoreIRuntimeDataSourceAdapter {
public:
[[nodiscard]] std::string GetAdapterType() const override;
bool Configure(const MetaCoreDataSourceDefinition& sourceDefinition) override;
bool Connect() override;
void Disconnect() override;
void Tick(double deltaSeconds) override;
[[nodiscard]] std::vector<MetaCoreRuntimeDataUpdate> PollUpdates() override;
[[nodiscard]] const MetaCoreRuntimeDataSourceStatus& GetStatus() const override;
void SetEmitUpdates(bool emitUpdates);
private:
MetaCoreDataSourceDefinition SourceDefinition_{};
MetaCoreRuntimeDataSourceStatus Status_{};
double ElapsedSeconds_ = 0.0;
std::uint64_t Sequence_ = 0;
bool EmitUpdates_ = true;
};
class MetaCoreFileReplayRuntimeDataSourceAdapter final : public MetaCoreIRuntimeDataSourceAdapter {
public:
[[nodiscard]] std::string GetAdapterType() const override;
bool Configure(const MetaCoreDataSourceDefinition& sourceDefinition) override;
bool Connect() override;
void Disconnect() override;
void Tick(double deltaSeconds) override;
[[nodiscard]] std::vector<MetaCoreRuntimeDataUpdate> PollUpdates() override;
[[nodiscard]] const MetaCoreRuntimeDataSourceStatus& GetStatus() const override;
private:
struct ReplayFrame {
double EmitAfterSeconds = 0.0;
MetaCoreRuntimeDataUpdate Update{};
};
[[nodiscard]] bool LoadReplayFile();
MetaCoreDataSourceDefinition SourceDefinition_{};
MetaCoreRuntimeDataSourceStatus Status_{};
std::string ReplayFilePath_{};
std::vector<ReplayFrame> Frames_{};
std::size_t NextFrameIndex_ = 0;
double ElapsedSeconds_ = 0.0;
std::uint64_t Sequence_ = 0;
};
class MetaCoreTcpRuntimeDataSourceAdapter final : public MetaCoreIRuntimeDataSourceAdapter {
public:
[[nodiscard]] std::string GetAdapterType() const override;
bool Configure(const MetaCoreDataSourceDefinition& sourceDefinition) override;
bool Connect() override;
void Disconnect() override;
void Tick(double deltaSeconds) override;
[[nodiscard]] std::vector<MetaCoreRuntimeDataUpdate> PollUpdates() override;
[[nodiscard]] const MetaCoreRuntimeDataSourceStatus& GetStatus() const override;
private:
[[nodiscard]] bool EnsureWinsockInitialized();
[[nodiscard]] bool ParseSocketLine(const std::string& line, MetaCoreRuntimeDataUpdate& update);
MetaCoreDataSourceDefinition SourceDefinition_{};
MetaCoreRuntimeDataSourceStatus Status_{};
std::string Host_{"127.0.0.1"};
std::uint16_t Port_ = 0;
void* SocketHandle_ = nullptr;
std::string ReceiveBuffer_{};
std::vector<MetaCoreRuntimeDataUpdate> PendingUpdates_{};
std::uint64_t Sequence_ = 0;
double ElapsedSeconds_ = 0.0;
};
[[nodiscard]] std::unique_ptr<MetaCoreIRuntimeDataSourceAdapter> MetaCoreCreateRuntimeDataSourceAdapter(
const std::string& adapterType
);
} // namespace MetaCore

View File

@ -0,0 +1,174 @@
#pragma once
#include "MetaCoreFoundation/MetaCoreId.h"
#include "MetaCoreFoundation/MetaCoreReflection.h"
#include <cstdint>
#include <string>
#include <glm/vec3.hpp>
namespace MetaCore {
MC_ENUM()
enum class MetaCoreRuntimeValueType : std::uint32_t {
Bool = 0,
Int64,
Double,
String,
Vec3
};
MC_ENUM()
enum class MetaCoreRuntimeDataQuality : std::uint32_t {
Good = 0,
Uncertain,
Bad
};
MC_ENUM()
enum class MetaCoreRuntimeDataSourceState : std::uint32_t {
Disconnected = 0,
Connecting,
Connected,
Degraded,
Faulted
};
MC_ENUM()
enum class MetaCoreRuntimeBindingTarget : std::uint32_t {
TransformPosition = 0,
MeshRendererVisible,
MeshRendererBaseColor,
LightIntensity,
LightColor
};
MC_ENUM()
enum class MetaCoreRuntimeMissingDataPolicy : std::uint32_t {
KeepLastValue = 0,
ResetToDefault,
MarkFaultOnly
};
MC_STRUCT()
struct MetaCoreRuntimeDataValue {
MC_GENERATED_BODY()
MC_PROPERTY()
MetaCoreRuntimeValueType Type = MetaCoreRuntimeValueType::Bool;
MC_PROPERTY()
bool BoolValue = false;
MC_PROPERTY()
std::int64_t Int64Value = 0;
MC_PROPERTY()
double DoubleValue = 0.0;
MC_PROPERTY()
std::string StringValue{};
MC_PROPERTY()
glm::vec3 Vec3Value{0.0F, 0.0F, 0.0F};
MC_PROPERTY()
std::uint64_t SourceTimestamp = 0;
MC_PROPERTY()
MetaCoreRuntimeDataQuality Quality = MetaCoreRuntimeDataQuality::Good;
};
MC_STRUCT()
struct MetaCoreRuntimeDataUpdate {
MC_GENERATED_BODY()
MC_PROPERTY()
std::string DataPointId{};
MC_PROPERTY()
MetaCoreRuntimeDataValue Value{};
MC_PROPERTY()
std::uint64_t Sequence = 0;
};
MC_STRUCT()
struct MetaCoreRuntimeDataSourceStatus {
MC_GENERATED_BODY()
MC_PROPERTY()
std::string SourceId{};
MC_PROPERTY()
MetaCoreRuntimeDataSourceState State = MetaCoreRuntimeDataSourceState::Disconnected;
MC_PROPERTY()
std::uint64_t LastConnectedAt = 0;
MC_PROPERTY()
std::uint64_t LastUpdateAt = 0;
MC_PROPERTY()
std::string LastError{};
};
MC_STRUCT()
struct MetaCoreRuntimeBindingStatus {
MC_GENERATED_BODY()
MC_PROPERTY()
std::string BindingId{};
MC_PROPERTY()
bool Healthy = true;
MC_PROPERTY()
bool Stale = false;
MC_PROPERTY()
std::uint64_t LastAppliedAt = 0;
MC_PROPERTY()
std::uint64_t StaleAfterMs = 1000;
MC_PROPERTY()
std::string LastError{};
};
MC_STRUCT()
struct MetaCoreRuntimeDiagnosticsSnapshot {
MC_GENERATED_BODY()
MC_PROPERTY()
std::vector<MetaCoreRuntimeDataSourceStatus> SourceStatuses{};
MC_PROPERTY()
std::vector<MetaCoreRuntimeBindingStatus> BindingStatuses{};
MC_PROPERTY()
bool HasFaults = false;
};
MC_ENUM()
enum class MetaCoreRuntimeConfigIssueSeverity : std::uint32_t {
Warning = 0,
Error
};
MC_STRUCT()
struct MetaCoreRuntimeConfigIssue {
MC_GENERATED_BODY()
MC_PROPERTY()
MetaCoreRuntimeConfigIssueSeverity Severity = MetaCoreRuntimeConfigIssueSeverity::Warning;
MC_PROPERTY()
std::string Scope{};
MC_PROPERTY()
std::string Message{};
};
} // namespace MetaCore

View File

@ -407,6 +407,23 @@ MetaCoreScene MetaCoreCreateDefaultScene() {
cube.MeshRenderer = MetaCoreMeshRendererComponent{}; cube.MeshRenderer = MetaCoreMeshRendererComponent{};
cube.Transform.Position = glm::vec3(0.0F, 0.5F, 0.0F); cube.Transform.Position = glm::vec3(0.0F, 0.5F, 0.0F);
MetaCoreGameObject& valve = scene.CreateGameObject("Valve");
valve.MeshRenderer = MetaCoreMeshRendererComponent{};
valve.Transform.Position = glm::vec3(-2.2F, 0.5F, 0.0F);
valve.MeshRenderer->BaseColor = glm::vec3(0.85F, 0.45F, 0.20F);
MetaCoreGameObject& tank = scene.CreateGameObject("Tank");
tank.MeshRenderer = MetaCoreMeshRendererComponent{};
tank.Transform.Position = glm::vec3(2.2F, 0.5F, 0.0F);
tank.Transform.Scale = glm::vec3(1.4F, 1.4F, 1.4F);
tank.MeshRenderer->BaseColor = glm::vec3(0.35F, 0.65F, 0.90F);
MetaCoreGameObject& alarmBeacon = scene.CreateGameObject("Alarm Beacon");
alarmBeacon.Light = MetaCoreLightComponent{};
alarmBeacon.Light->Intensity = 0.0F;
alarmBeacon.Light->Color = glm::vec3(1.0F, 0.15F, 0.1F);
alarmBeacon.Transform.Position = glm::vec3(0.0F, 2.5F, 0.0F);
return scene; return scene;
} }

View File

@ -0,0 +1,167 @@
#include "MetaCoreScene/MetaCoreScenePackage.h"
#include "MetaCoreFoundation/MetaCoreGeneratedReflection.h"
#include "MetaCoreFoundation/MetaCoreHash.h"
#include "MetaCoreFoundation/MetaCorePackage.h"
#include <fstream>
#include <sstream>
namespace MetaCore {
namespace {
[[nodiscard]] MetaCorePackageDocument MetaCoreBuildTypedScenePackage(
const MetaCoreAssetGuid& assetGuid,
const std::string& objectName,
std::vector<std::byte> payload
) {
MetaCorePackageDocument document;
document.Header.PackageType = MetaCorePackageType::Scene;
document.Header.PackageGuid = assetGuid;
document.Header.SourceHash = MetaCoreHashBytes(std::span<const std::byte>(payload.data(), payload.size()));
document.NameTable = {objectName, "MetaCoreSceneDocument"};
document.Exports.push_back(MetaCoreExportEntry{
assetGuid,
objectName,
MetaCoreMakeTypeId("MetaCoreSceneDocument"),
0,
static_cast<std::uint64_t>(payload.size())
});
document.PayloadSections.push_back(std::move(payload));
return document;
}
template <typename T>
[[nodiscard]] std::optional<T> MetaCoreReadTypedPayload(
const MetaCorePackageDocument& document,
const MetaCoreTypeRegistry& registry,
std::string_view expectedTypeName
) {
if (document.Exports.empty() || document.PayloadSections.empty()) {
return std::nullopt;
}
const MetaCoreExportEntry& exportEntry = document.Exports.front();
if (exportEntry.TypeId != MetaCoreMakeTypeId(expectedTypeName) ||
exportEntry.PayloadIndex >= document.PayloadSections.size()) {
return std::nullopt;
}
T payload{};
if (!MetaCoreDeserializeFromBytes(document.PayloadSections[exportEntry.PayloadIndex], payload, registry)) {
return std::nullopt;
}
return payload;
}
[[nodiscard]] std::optional<std::string> MetaCoreFindJsonStringValue(std::string_view json, std::string_view key) {
const std::string needle = "\"" + std::string(key) + "\"";
std::size_t keyPosition = json.find(needle);
if (keyPosition == std::string_view::npos) {
return std::nullopt;
}
std::size_t colonPosition = json.find(':', keyPosition + needle.size());
if (colonPosition == std::string_view::npos) {
return std::nullopt;
}
std::size_t firstQuote = json.find('"', colonPosition + 1);
if (firstQuote == std::string_view::npos) {
return std::nullopt;
}
std::size_t secondQuote = firstQuote + 1;
while (secondQuote < json.size()) {
secondQuote = json.find('"', secondQuote);
if (secondQuote == std::string_view::npos) {
return std::nullopt;
}
if (secondQuote == firstQuote + 1 || json[secondQuote - 1] != '\\') {
break;
}
++secondQuote;
}
return std::string(json.substr(firstQuote + 1, secondQuote - firstQuote - 1));
}
} // namespace
MetaCoreTypeRegistry MetaCoreBuildScenePackageTypeRegistry() {
MetaCoreTypeRegistry registry;
MetaCoreRegisterFoundationGeneratedTypes(registry);
MetaCoreRegisterSceneGeneratedTypes(registry);
return registry;
}
bool MetaCoreWriteScenePackage(
const std::filesystem::path& path,
const MetaCoreSceneDocument& document
) {
const MetaCoreTypeRegistry registry = MetaCoreBuildScenePackageTypeRegistry();
const auto payload = MetaCoreSerializeToBytes(document, registry);
if (!payload.has_value()) {
return false;
}
const std::string objectName = path.stem().string().empty() ? "Scene" : path.stem().string();
MetaCorePackageDocument package = MetaCoreBuildTypedScenePackage(
MetaCoreAssetGuid::Generate(),
objectName,
*payload
);
return MetaCoreWritePackageFile(path, std::move(package), registry);
}
std::optional<MetaCoreSceneDocument> MetaCoreReadScenePackage(const std::filesystem::path& path) {
const MetaCoreTypeRegistry registry = MetaCoreBuildScenePackageTypeRegistry();
const auto package = MetaCoreReadPackageFile(path, registry);
if (!package.has_value() || package->Header.PackageType != MetaCorePackageType::Scene) {
return std::nullopt;
}
return MetaCoreReadTypedPayload<MetaCoreSceneDocument>(*package, registry, "MetaCoreSceneDocument");
}
std::optional<std::filesystem::path> MetaCoreResolveStartupScenePackagePath(const std::filesystem::path& projectFilePath) {
std::ifstream input(projectFilePath, std::ios::binary);
if (!input.is_open()) {
return std::nullopt;
}
std::ostringstream buffer;
buffer << input.rdbuf();
const auto startupSceneValue = MetaCoreFindJsonStringValue(buffer.str(), "startup_scene");
if (!startupSceneValue.has_value() || startupSceneValue->empty()) {
return std::nullopt;
}
const std::filesystem::path relativeStartupScenePath = std::filesystem::path(*startupSceneValue).lexically_normal();
const std::filesystem::path projectRoot = projectFilePath.parent_path();
std::vector<std::filesystem::path> candidates;
if (relativeStartupScenePath.extension() == ".json") {
candidates.push_back((projectRoot / relativeStartupScenePath).replace_extension(""));
}
candidates.push_back(projectRoot / relativeStartupScenePath);
for (const std::filesystem::path& candidate : candidates) {
std::error_code errorCode;
if (std::filesystem::exists(candidate, errorCode)) {
return std::filesystem::weakly_canonical(candidate, errorCode);
}
}
return (projectRoot / relativeStartupScenePath).lexically_normal();
}
std::optional<MetaCoreSceneDocument> MetaCoreLoadStartupSceneDocument(const std::filesystem::path& projectFilePath) {
const auto startupScenePath = MetaCoreResolveStartupScenePackagePath(projectFilePath);
if (!startupScenePath.has_value()) {
return std::nullopt;
}
return MetaCoreReadScenePackage(*startupScenePath);
}
} // namespace MetaCore

View File

@ -1,5 +1,7 @@
#pragma once #pragma once
#include "MetaCoreFoundation/MetaCoreReflection.h"
#include <glm/vec3.hpp> #include <glm/vec3.hpp>
#include <optional> #include <optional>
@ -7,36 +9,61 @@
namespace MetaCore { namespace MetaCore {
MC_ENUM()
// 定义第一阶段内置的网格类型。 // 定义第一阶段内置的网格类型。
enum class MetaCoreBuiltinMeshType { enum class MetaCoreBuiltinMeshType {
Cube Cube
}; };
MC_STRUCT()
// 表示 Unity 风格对象的变换组件。 // 表示 Unity 风格对象的变换组件。
struct MetaCoreTransformComponent { struct MetaCoreTransformComponent {
MC_GENERATED_BODY()
MC_PROPERTY()
glm::vec3 Position{0.0F, 0.0F, 0.0F}; glm::vec3 Position{0.0F, 0.0F, 0.0F};
MC_PROPERTY()
glm::vec3 RotationEulerDegrees{0.0F, 0.0F, 0.0F}; glm::vec3 RotationEulerDegrees{0.0F, 0.0F, 0.0F};
MC_PROPERTY()
glm::vec3 Scale{1.0F, 1.0F, 1.0F}; glm::vec3 Scale{1.0F, 1.0F, 1.0F};
}; };
MC_STRUCT()
// 表示场景中用于观察的摄像机组件。 // 表示场景中用于观察的摄像机组件。
struct MetaCoreCameraComponent { struct MetaCoreCameraComponent {
MC_GENERATED_BODY()
MC_PROPERTY()
float FieldOfViewDegrees = 60.0F; float FieldOfViewDegrees = 60.0F;
MC_PROPERTY()
float NearClip = 0.1F; float NearClip = 0.1F;
MC_PROPERTY()
float FarClip = 100.0F; float FarClip = 100.0F;
MC_PROPERTY()
bool IsPrimary = false; bool IsPrimary = false;
}; };
MC_STRUCT()
// 表示一个最小可用的网格渲染组件。 // 表示一个最小可用的网格渲染组件。
struct MetaCoreMeshRendererComponent { struct MetaCoreMeshRendererComponent {
MC_GENERATED_BODY()
MC_PROPERTY()
MetaCoreBuiltinMeshType BuiltinMesh = MetaCoreBuiltinMeshType::Cube; MetaCoreBuiltinMeshType BuiltinMesh = MetaCoreBuiltinMeshType::Cube;
MC_PROPERTY()
glm::vec3 BaseColor{0.75F, 0.78F, 0.84F}; glm::vec3 BaseColor{0.75F, 0.78F, 0.84F};
MC_PROPERTY()
bool Visible = true; bool Visible = true;
}; };
MC_STRUCT()
// 表示第一阶段场景中的方向光组件。 // 表示第一阶段场景中的方向光组件。
struct MetaCoreLightComponent { struct MetaCoreLightComponent {
MC_GENERATED_BODY()
MC_PROPERTY()
glm::vec3 Color{1.0F, 1.0F, 1.0F}; glm::vec3 Color{1.0F, 1.0F, 1.0F};
MC_PROPERTY()
float Intensity = 1.5F; float Intensity = 1.5F;
}; };

View File

@ -1,6 +1,8 @@
#pragma once #pragma once
#include "MetaCoreFoundation/MetaCoreId.h" #include "MetaCoreFoundation/MetaCoreId.h"
#include "MetaCoreFoundation/MetaCoreAssetGuid.h"
#include "MetaCoreFoundation/MetaCoreReflection.h"
#include "MetaCoreScene/MetaCoreComponents.h" #include "MetaCoreScene/MetaCoreComponents.h"
#include <optional> #include <optional>
@ -8,15 +10,41 @@
namespace MetaCore { namespace MetaCore {
MC_STRUCT()
struct MetaCorePrefabInstanceMetadata {
MC_GENERATED_BODY()
MC_PROPERTY()
MetaCoreAssetGuid PrefabAssetGuid{};
MC_PROPERTY()
MetaCoreId PrefabObjectId = 0;
MC_PROPERTY()
MetaCoreId PrefabInstanceRootId = 0;
};
MC_STRUCT()
// 表示一个 Unity 风格的场景对象。 // 表示一个 Unity 风格的场景对象。
struct MetaCoreGameObject { struct MetaCoreGameObject {
MC_GENERATED_BODY()
MC_PROPERTY()
MetaCoreId Id = 0; MetaCoreId Id = 0;
MC_PROPERTY()
MetaCoreId ParentId = 0; MetaCoreId ParentId = 0;
MC_PROPERTY()
std::string Name; std::string Name;
MC_PROPERTY()
MetaCoreTransformComponent Transform{}; MetaCoreTransformComponent Transform{};
MC_PROPERTY()
std::optional<MetaCoreCameraComponent> Camera; std::optional<MetaCoreCameraComponent> Camera;
MC_PROPERTY()
std::optional<MetaCoreMeshRendererComponent> MeshRenderer; std::optional<MetaCoreMeshRendererComponent> MeshRenderer;
MC_PROPERTY()
std::optional<MetaCoreLightComponent> Light; std::optional<MetaCoreLightComponent> Light;
MC_PROPERTY()
std::optional<MetaCorePrefabInstanceMetadata> PrefabInstance;
}; };
} // namespace MetaCore } // namespace MetaCore

View File

@ -1,12 +1,18 @@
#pragma once #pragma once
#include "MetaCoreFoundation/MetaCoreReflection.h"
#include "MetaCoreScene/MetaCoreSceneDocument.h"
#include "MetaCoreScene/MetaCoreGameObject.h" #include "MetaCoreScene/MetaCoreGameObject.h"
#include <vector> #include <vector>
namespace MetaCore { namespace MetaCore {
MC_STRUCT()
struct MetaCoreSceneSnapshot { struct MetaCoreSceneSnapshot {
MC_GENERATED_BODY()
MC_PROPERTY()
std::vector<MetaCoreGameObject> GameObjects; std::vector<MetaCoreGameObject> GameObjects;
}; };

View File

@ -0,0 +1,40 @@
#pragma once
#include "MetaCoreFoundation/MetaCoreId.h"
#include "MetaCoreFoundation/MetaCoreReflection.h"
#include "MetaCoreScene/MetaCoreGameObject.h"
#include <string>
#include <vector>
namespace MetaCore {
MC_STRUCT()
struct MetaCoreEditorSelectionSnapshot {
MC_GENERATED_BODY()
MC_PROPERTY()
std::vector<MetaCoreId> SelectedObjectIds{};
MC_PROPERTY()
MetaCoreId ActiveObjectId = 0;
MC_PROPERTY()
MetaCoreId SelectionAnchorId = 0;
};
MC_STRUCT()
struct MetaCoreSceneDocument {
MC_GENERATED_BODY()
MC_PROPERTY()
std::string Name{};
MC_PROPERTY()
std::vector<MetaCoreGameObject> GameObjects{};
MC_PROPERTY()
MetaCoreEditorSelectionSnapshot Selection{};
};
} // namespace MetaCore

View File

@ -0,0 +1,29 @@
#pragma once
#include "MetaCoreScene/MetaCoreSceneDocument.h"
#include <filesystem>
#include <optional>
namespace MetaCore {
[[nodiscard]] MetaCoreTypeRegistry MetaCoreBuildScenePackageTypeRegistry();
[[nodiscard]] bool MetaCoreWriteScenePackage(
const std::filesystem::path& path,
const MetaCoreSceneDocument& document
);
[[nodiscard]] std::optional<MetaCoreSceneDocument> MetaCoreReadScenePackage(
const std::filesystem::path& path
);
[[nodiscard]] std::optional<std::filesystem::path> MetaCoreResolveStartupScenePackagePath(
const std::filesystem::path& projectFilePath
);
[[nodiscard]] std::optional<MetaCoreSceneDocument> MetaCoreLoadStartupSceneDocument(
const std::filesystem::path& projectFilePath
);
} // namespace MetaCore

View File

@ -1,5 +1,5 @@
{ {
"id": "00005052332aa8d4625dd7775e94e6d800000005", "id": "0000573e4df2db1c0fff4788060abca900000005",
"relative_path": "Assets/Materials/default_pbr.material.json", "relative_path": "Assets/Materials/default_pbr.material.json",
"type": "material" "type": "material"
} }

View File

@ -1,5 +1,5 @@
{ {
"id": "00005052332a9f74171e39271225dd0800000004", "id": "0000573e4df29210cf02d084bb56a04a00000004",
"relative_path": "Assets/UI/main_menu.rcss", "relative_path": "Assets/UI/main_menu.rcss",
"type": "ui_stylesheet" "type": "ui_stylesheet"
} }

View File

@ -1,5 +1,5 @@
{ {
"id": "00005052332a822841724324849eccb900000003", "id": "0000573e4df220c85f0baaf5dd5242f900000003",
"relative_path": "Assets/UI/main_menu.rml", "relative_path": "Assets/UI/main_menu.rml",
"type": "ui_document" "type": "ui_document"
} }

View File

@ -1,17 +1,17 @@
{ {
"records": [ "records": [
{ {
"id": "00005052332a822841724324849eccb900000003", "id": "0000573e4df220c85f0baaf5dd5242f900000003",
"relative_path": "Assets/UI/main_menu.rml", "relative_path": "Assets/UI/main_menu.rml",
"type": "ui_document" "type": "ui_document"
}, },
{ {
"id": "00005052332a9f74171e39271225dd0800000004", "id": "0000573e4df29210cf02d084bb56a04a00000004",
"relative_path": "Assets/UI/main_menu.rcss", "relative_path": "Assets/UI/main_menu.rcss",
"type": "ui_stylesheet" "type": "ui_stylesheet"
}, },
{ {
"id": "00005052332aa8d4625dd7775e94e6d800000005", "id": "0000573e4df2db1c0fff4788060abca900000005",
"relative_path": "Assets/Materials/default_pbr.material.json", "relative_path": "Assets/Materials/default_pbr.material.json",
"type": "material" "type": "material"
} }

View File

@ -1,8 +1,8 @@
{ {
"name": "MetaCoreTest", "name": "MetaCoreTest",
"scenes": [ "scenes": [
"Scenes/Main.mcscene.json" "Scenes/Main.mcscene"
], ],
"startup_scene": "Scenes/Main.mcscene.json", "startup_scene": "Scenes/Main.mcscene",
"version": "0.1.0" "version": "0.1.0"
} }

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,17 @@
# emit_after_seconds data_point_id value_type payload
0.00 cube.position vec3 0.0 0.5 0.0
0.00 cube.visible bool true
0.00 cube.base_color vec3 0.4 0.6 0.9
0.00 valve.visible bool true
0.00 tank.base_color vec3 0.35 0.65 0.90
0.00 alarm.intensity double 0.0
0.50 cube.position vec3 1.5 0.5 0.0
0.50 cube.base_color vec3 0.8 0.6 0.5
0.50 tank.base_color vec3 0.20 0.80 0.25
0.50 alarm.intensity double 2.0
1.00 cube.visible bool false
1.00 valve.visible bool false
1.50 cube.position vec3 -1.5 0.5 0.0
1.50 cube.visible bool true
1.50 valve.visible bool true
1.50 alarm.intensity double 0.0

Binary file not shown.

View File

@ -13,7 +13,7 @@
"locked": false, "locked": false,
"selected": false "selected": false
}, },
"id": "0000505233216b341f8e0361195336fe00000001", "id": "0000573e4dab34ec66e90e17c04d1c5d00000001",
"name": "Main Camera", "name": "Main Camera",
"parent_id": "", "parent_id": "",
"transform": { "transform": {
@ -40,7 +40,7 @@
"locked": false, "locked": false,
"selected": false "selected": false
}, },
"id": "00005052332197bcd649b574240bc6be00000002", "id": "0000573e4dc9f2d82e684c2197ceab9a00000002",
"light": { "light": {
"color": [ "color": [
1, 1,

View File

@ -13,7 +13,7 @@
"locked": false, "locked": false,
"selected": false "selected": false
}, },
"id": "00005052333cdf54bd42defb639e412c00000006", "id": "0000573e4e1b3d64d8ce209463b7208500000006",
"name": "Main Camera", "name": "Main Camera",
"parent_id": "", "parent_id": "",
"transform": { "transform": {
@ -40,7 +40,7 @@
"locked": false, "locked": false,
"selected": false "selected": false
}, },
"id": "00005052333ceef49f76de9c4d0fd70200000007", "id": "0000573e4e1b84e073179b24ce5ac48c00000007",
"light": { "light": {
"color": [ "color": [
1, 1,
@ -78,7 +78,7 @@
"locked": false, "locked": false,
"selected": false "selected": false
}, },
"id": "00005052333cf6c426b72f1a10f785ee00000008", "id": "0000573e4e1bbde8c5e3ba6a189478bd00000008",
"mesh_renderer": { "mesh_renderer": {
"material_asset_id": "", "material_asset_id": "",
"mesh_asset_id": "mesh.cube", "mesh_asset_id": "mesh.cube",

View File

@ -0,0 +1,363 @@
# MetaCore 引擎基础能力优先级
生成时间2026-03-28
状态:草案
读者:产品、架构、引擎、编辑器、交付
## 目的
这份文档用于纠正当前第一阶段的开发顺序。
核心结论很简单:
**MetaCore 第一阶段应该先成为一个可用于构建工业三维应用的可用引擎和编辑器,然后再叠加数字孪生特有的运行时数据能力。**
运行时数据很重要,但它不是引擎主干。
## 执行摘要
当前项目在 RuntimeData 方向已经有了不少有效进展:
- 独立的运行时数据模块
- 二进制运行时配置
- 多种适配器
- 编辑器侧 binding 配置
- Player 侧数据分发
这些工作都应该保留。
但它不应该继续定义第一阶段的关键路径。
如果 MetaCore 的目标是“像 Unity / Unreal 一样,用于开发工业数字孪生项目的引擎”,那第一阶段的正确顺序应该是:
1. 项目与内容工作流
2. 资源与模型导入
3. 场景编辑与层级
4. 材质与光照
5. 场景持久化
6. UI 工作流
7. 打包与运行时
8. RuntimeData 集成
## 为什么顺序这么重要
数字孪生团队不会先从把一个点位绑定到对象开始。
他们会先做这些事:
- 创建项目
- 导入模型
- 组织场景层级
- 调整变换
- 指派材质
- 设置灯光与相机
- 搭界面
- 保存场景
- 打包一个可运行客户端
只有这些步骤成立之后,运行时数据才真正有价值。
如果引擎不能顺畅支撑这些基础步骤,那 RuntimeData 只能驱动一个薄 demo而不是可交付流程。
## 专业判断
从产品和工程两个视角看,第一阶段的正确定义应该是:
**MetaCore 第一阶段是一个具备项目、资源、场景、材质、光照、UI、持久化和打包工作流的工业级引擎/编辑器底座,并在其上提供第一版 RuntimeData 子系统用于数字孪生集成。**
它不是:
- 一个数据驱动 demo 壳
- 一个完整数字孪生平台
- 一个完整 VR 训练平台
## 第一阶段的正确开发顺序
### Phase 1A引擎生产力工作流
这是新的主干。
必须包含:
- 项目创建与打开
- 启动场景行为
- 资源导入与资源数据库
- 模型导入与层级保留
- 场景层级编辑
- Transform 编辑与 Gizmo
- 材质创建、复用与指派
- 基础光照
- 场景保存/打开
- UI 基础编辑
- 打包与独立运行时
### Phase 1B数字孪生使能
这部分建立在引擎工作流之上。
必须包含:
- 运行时数据源抽象
- 数据点模型
- 场景 binding 模型
- 运行时诊断
- 一个或多个 live adapter
## 优先级模型
### P0现在必须立刻抬高优先级
这些才是真正的引擎阻塞项。
- 项目系统
- 模型导入
- 场景层级编辑
- Transform 工具与 Gizmo
- 材质系统
- 光照基础
- 场景保存/打开
- 打包与独立运行时
### P1紧随 P0
- Project 面板与资源管理
- UI 基础编辑
- prefab 强化
- 相机与视口工作流
- 资源刷新与重新导入稳定性
- 运行时诊断可见性
### P2重要但应晚于引擎底座
- RuntimeData 集成
- 数字孪生样板模板
- 更多 live adapter
- 行业化运行时组件
### P3后续扩展
- VR/XR 工作流
- 浏览器嵌入产品化
- AI 辅助工具
- 仿真编排
## 首批推荐能力集合
### 1. 项目系统
必须支持:
- 创建/打开项目
- 稳定的项目目录布局
- 启动场景
- 项目描述文件持久化
为什么重要:
- 这是所有后续工作流的入口
### 2. 模型导入
必须支持:
- 一等支持 `glTF/.glb`
- 第二阶段支持 `FBX`
- 可选支持 `OBJ`
建议:
- 把 `glTF/.glb` 做成第一优先的生产级导入器
原因:
- 现代、清晰、适合可控工具链
- 比 FBX-first 路线更可预测
- 足够覆盖早期很多工业可视化场景
### 3. 场景编辑与层级
必须支持:
- 创建/删除/复制
- 重命名
- 父子级
- 拖拽重挂接
- 多选
- 稳定的选择行为
### 4. Transform 工具
必须支持:
- 移动
- 旋转
- 缩放
- 本地/世界坐标模式
- 基础吸附
没有这套东西,编辑器就谈不上生产可用。
### 5. 材质系统
必须支持:
- 材质资源
- 基础色
- 纹理槽
- 基础 PBR 参数
- 材质复用
### 6. 光照
必须支持:
- Directional Light
- Point Light
- Spot Light
- 稳定的场景照明
- 条件允许时补基础阴影
### 7. 场景持久化
必须支持:
- 新建场景
- 保存
- 另存
- 重新打开
- 启动场景加载
对这个仓库来说,二进制 package 持久化是正确方向。
### 8. UI 工作流
必须支持:
- 静态面板
- 文本
- 图片
- 按钮
- 基础布局
- UI 资源引用
这是数字孪生项目的硬需求。
### 9. 打包
必须支持:
- 独立 Player
- 启动场景接管
- 项目内容打包带出
- clean machine 运行验证
## 当前思维模型里的遗漏项
下面这些点很容易被低估,但它们都属于核心引擎工作。
### 资源管理
导入远远不够。
还需要:
- Project 面板
- 元数据可见
- 重命名/移动行为
- 重新导入行为
- package 引用稳定性
### 视口工作流
一个 3D 编辑器必须具备:
- 稳定的 orbit/fly 导航
- 聚焦选中对象
- 明确的相机行为
- 编辑器视角和运行时视角分离
### Prefab/模板工作流
工业场景会重复大量对象:
- 阀门
- 罐体
- 指示器
- 告警灯
- 设备
没有 prefab/template 复用,内容生产效率会很差。
### 导入归一化
一个真正可用的导入器最终必须处理:
- 坐标系归一化
- 缩放归一化
- 网格/材质映射
- 层级保留
- 重新导入一致性
### 诊断与错误可见性
交付团队需要清晰的失败面:
- 导入失败
- 资源缺失
- 场景无法读取
- runtime config 不匹配
- binding 失败
### 扩展点
即使 VR 或脚本系统还没完成,引擎也应该先预留:
- 应用模块挂点
- 运行时组件扩展点
- 导入器扩展点
- adapter 扩展点
## RuntimeData 现有工作哪些仍然有效
当前已经做的 RuntimeData 工作不是错误。
应该保留,但重新归类:
- 它不是第一阶段主干
- 它是 Phase 1B 子系统
- 它在一些方向上已经超前于部分基础层
这其实是有价值的:
- 它证明了引擎已经能承载一个行业化子系统
- 它提前打通了未来的数据路径
- 一旦内容工作流更稳,它就能直接复用
## 修正后的里程碑形态
旧的里程碑顺序让 RuntimeData 过早主导节奏。
修正后的顺序应该是:
```text
M1 引擎壳与项目循环
-> M2 资源与模型导入循环
-> M3 场景编辑、材质与光照工作流
-> M4 UI、持久化与打包循环
-> M5 面向数字孪生的 RuntimeData 集成
-> M6 硬化与未来预留
```
## 最终建议
MetaCore 应该先被做成:
**一个可用的工业三维引擎/编辑器**
然后再做成:
**一个具备数字孪生能力的引擎**
顺序不能反。
如果不强制执行这个顺序,项目会继续滑向“行业功能优先”,而不是“引擎基础能力优先”。

View File

@ -0,0 +1,307 @@
# MetaCore 第一阶段实施计划
生成时间2026-03-27
状态:草案
范围:第一阶段执行
## 目的
这份文档把第一阶段范围翻译成实施路线图。
它要回答:
- 先做什么
- 谁依赖谁
- 每个里程碑在证明什么
- 当前真正应该推进哪一段
## 里程碑总览
第一阶段应拆成 6 个里程碑,并把引擎生产力工作流排在 RuntimeData 前面。
```text
M1 引擎壳与项目循环
-> M2 资源与模型导入循环
-> M3 场景编辑、材质与光照工作流
-> M4 UI、持久化与打包循环
-> M5 面向数字孪生的 RuntimeData 集成
-> M6 引擎硬化与未来预留
```
## M1 引擎壳与项目循环
### 目标
让 MetaCore 成为一个具备稳定项目行为的引擎壳。
### 交付物
- 稳定的 Windows 应用主循环
- 稳定的平台层
- 项目打开/创建行为
- 启动场景行为
- 独立 Player 执行能力
### 关键工作流
- 应用生命周期
- 模块与服务启动
- 项目描述文件处理
- 视口渲染稳定性
- 编辑器基线工作流与项目引导
### 完成标准
- 项目能稳定打开
- 启动场景能被解析
- 运行时能加载并渲染启动场景
## M2 资源与模型导入循环
### 目标
让 MetaCore 从“只有场景原型”变成“能围绕资源工作的工具”。
### 交付物
- 资源元数据生成
- 资源数据库稳定
- 导入流程稳定
- 一等支持的模型导入工作流
- package 流程稳定
- 重新导入与资源浏览基础
### 关键工作流
- 资源元数据与 package 格式
- 导入流水线
- 模型格式支持
- Project 面板工作流
- prefab 基础
### 完成标准
- 导入的资源稳定出现
- 导入的模型能在场景中使用
- 场景和资源在重载后仍然有效
## M3 场景编辑、材质与光照工作流
### 目标
让 MetaCore 真正可用于内容搭建。
### 交付物
- 稳定的层级编辑
- Transform Gizmo
- 材质工作流
- 光照工作流
- 可用的视口编辑体验
### 关键工作流
- 层级行为
- 多选和操作
- Transform 工具
- 材质资源与材质指派
- 灯光编辑
### 完成标准
- 可以基于导入资源搭一个场景
- 材质和光照可以稳定编辑
- 编辑器内容生产不再依赖硬编码 demo 对象
## M4 UI、持久化与打包循环
### 目标
让 MetaCore 作为项目工具具备交付能力。
### 交付物
- UI 基础编辑
- 场景保存/打开成熟化
- Windows 打包验证
- 项目级 pilot 打包流程
### 关键工作流
- UI 资源与 UI 运行时
- 二进制场景持久化
- 启动场景打包
- clean machine 验证
### 完成标准
- 开发者能构建并打包一个小型工业三维可视化客户端
- 打包后的运行时在编辑器外表现稳定
## M5 面向数字孪生的 RuntimeData 集成
### 目标
在引擎基础工作流之上叠加数字孪生所需的 RuntimeData 能力。
### 交付物
- 独立的 RuntimeData 模块
- source / point / binding 模型
- runtime config 持久化
- live adapter 支持
- binding 配置工作流
- runtime diagnostics
### 关键工作流
- RuntimeData 架构
- adapter 与 binding 模型
- 运行时分发与故障处理
- 编辑器侧配置
- 数字孪生样板工程
### 完成标准
- 打包后的运行时能把外部数据应用到场景目标
- 运行时故障可见且不会导致崩溃
- 引擎能支撑一个小型数字孪生项目
## M6 引擎硬化与未来预留
### 目标
把 MetaCore 强化成一个可长期演进的引擎底座。
### 交付物
- 更清晰的 Runtime / Editor 分离
- 更强的诊断能力
- 更干净的子系统边界
- XR 与 B/S 的未来预留在代码结构中落地
### 关键工作流
- 模块边界清理
- 诊断与健康可见性
- 未来扩展接口
- 样板与测试扩充
### 完成标准
- 第二阶段工作不需要推翻第一阶段架构
## 任务树
### A. 引擎基础
- A1 应用生命周期
- A2 平台服务
- A3 日志
- A4 模块启动
### B. 场景与对象系统
- B1 场景结构
- B2 GameObject 层级
- B3 组件状态
- B4 持久化
- B5 prefab 基础
### C. 渲染与运行时
- C1 渲染桥
- C2 场景视口
- C3 运行时视口
- C4 相机控制
- C5 光照基础
### D. 项目与资源循环
- D1 项目描述文件
- D2 资源元数据
- D3 资源数据库
- D4 导入流程
- D5 package 流程
- D6 cook 流程
### E. 编辑器
- E1 Hierarchy 面板
- E2 Scene 视图
- E3 Inspector
- E4 Project 面板
- E5 Console
- E6 Undo / Redo
### F. UI 与交付
- F1 UI 资源与 UI 运行时
- F2 场景持久化
- F3 打包
- F4 clean-machine 验证
### G. RuntimeData
- G1 RuntimeData 模块
- G2 RuntimeData 类型系统
- G3 source 与 binding 文档
- G4 adapter 接口
- G5 dispatcher
- G6 live adapter
- G7 binding 编辑
- G8 诊断与 stale 处理
### H. 未来预留
- H1 XR 扩展边界
- H2 B/S 扩展边界
- H3 子系统清理
## 修正后的实施重点
旧建议过早把 RuntimeData 放在主线中央。
修正后的顺序应该是:
1. 先做 M1 和 M2
2. 再做 M3
3. 再做 M4
4. 最后再让 M5 成为主导
RuntimeData 仍然重要,但它现在应该被视为 Phase 1B 子系统,而不是第一阶段最先主导实现节奏的模块。
## 当前关键路径
当前关键路径应该是:
```text
项目循环
-> 资源与模型导入
-> 层级与 Transform 工作流
-> 材质与光照工作流
-> 场景保存/打开
-> UI 基础
-> 打包
-> RuntimeData 集成
```
如果这条路径被过早的行业子系统实现打断MetaCore 会继续表现得像一个专用 demo而不是一个可用于开发项目的引擎工具。
## 当前执行焦点的完成标准
当前执行焦点完成时,应满足:
- 项目可以稳定打开并引导
- 导入的模型资源可以进入场景
- 层级编辑和 Transform 操作稳定
- 材质和光照可以重复性编辑
- 场景可以保存、重开并打包
只有在这些点成立之后RuntimeData 才应该重新成为第一阶段主导任务。
## 立即下一步
从 M1 和 M2 开始推进。
在引擎内容工作流足够强之前,不要继续优先扩 adapter、工业协议或更多 RuntimeData 专属工具。

View File

@ -0,0 +1,261 @@
# MetaCore 第一阶段范围定义
生成时间2026-03-27
状态:草案
范围:第一阶段
## 目的
这份文档定义 MetaCore 第一阶段到底要交付什么、要给未来预留什么,以及明确不准备在这一阶段完成什么。
目标是防止范围漂移,同时保留真实的产品野心:
**MetaCore 第一阶段要成为一个足够完整的引擎版本,能够支撑工业数字孪生应用开发,并为后续扩展到 VR 虚拟维修训练留下清晰路径。**
## 第一阶段产品定义
MetaCore 第一阶段是:
- 一个以 Windows 为先的 C/S 引擎与编辑器栈
- 能用于构建工业三维可视化和数字孪生类应用
- 具备实时数据接入接口
- 在架构上为未来 XR 和 B/S 扩展保留空间
MetaCore 第一阶段不是:
- 一个完整行业平台
- 一个完整 VR 训练平台
- 一个浏览器优先的引擎
- 一个要在广度上正面对标 Unity 的通用引擎
## 第一阶段完成陈述
当 MetaCore 能够被用于构建并交付一个 Windows C/S 工业三维可视化应用,并且具备以下能力时,第一阶段才算完成:
- 项目与资源管理
- 场景制作
- 运行时打包
- 实时数据源集成
- 场景对象状态绑定
- 稳定的运行时执行
并且同一套架构仍然保留了通往以下方向的清晰路径:
- VR 应用开发
- B/S 嵌入式产品化
## 第一阶段功能分层
### 1. 引擎核心
必须包含:
- 应用生命周期
- 平台抽象
- 日志
- 模块与服务基础设施
- 反射与序列化
- package 格式与资产标识
### 2. 场景与对象系统
必须包含:
- 场景模型
- GameObject 层级
- 组件模型
- Transform 编辑
- 场景保存与加载
- prefab 基础工作流
### 3. 渲染与运行时
必须包含:
- 场景渲染
- 相机系统
- 基础光照
- 视口渲染
- 运行时 Player
### 4. 项目与资源系统
必须包含:
- 项目结构
- 资源导入
- 元数据生成
- 资源数据库
- package 与 cook 流程
- 启动场景与 runtime config
### 5. 编辑器系统
必须包含:
- Hierarchy 面板
- Scene 视图
- Inspector
- Project 面板
- Console
- Undo / Redo
- 对象与组件编辑
### 6. 支撑应用开发的 RuntimeData 层
必须包含:
- 实时数据 adapter 接口
- 数据源定义
- 数据点定义
- 场景 binding 定义
- 运行时更新分发
- 诊断与降级行为
## 第一阶段必须具备的功能
这些功能构成第一阶段闭环。
### 引擎与编辑器
- 打开项目
- 加载启动场景
- 编辑场景
- 保存并重新加载场景
- 查看并修改对象属性
- 导入并管理项目资源
- 打包并运行独立 Windows 客户端
### RuntimeData
- 定义数据源
- 定义逻辑数据点
- 把数据点绑定到场景目标
- 用外部更新驱动运行时场景状态
- 可见地处理 source fault 和 stale data
### 交付
- 在编辑器外运行一个已打包的样板项目
- 把样板项目交付为 Windows C/S 应用
## 第一阶段支持的 binding 目标
第一阶段只应支持受控白名单。
### 初始必须支持
- `Transform.Position`
- `MeshRenderer.Visible`
- `MeshRenderer.BaseColor`
- `Light.Intensity`
- `Light.Color`
### 后置目标
- 任意组件字段绑定
- UI 文本绑定
- 动画参数绑定
- 骨骼状态绑定
## 第一阶段必须预留的区域
这些不需要在第一阶段完全产品化,但架构必须给它们留口子。
### XR 与 VR
- 输入系统不能从设计上只服务桌面
- 运行时逻辑不能默认只有键鼠交互
- 对象模型和运行时系统要能复用于训练场景
### B/S 与嵌入
- 场景和资源格式不能锁死桌面形态
- RuntimeData 模型要能脱离编辑器被服务化
- Player 运行时不能依赖编辑器专属系统
### 行业解决方案分层
- 第一阶段的应用逻辑不能把某一种客户业务流程硬编码进引擎核心
## 第一阶段明确不做的内容
这些内容不进入第一阶段完成定义。
- 完整 B/S 运行时产品
- 嵌入式 SDK 产品化
- 完整 VR 虚拟维修训练工作流
- XR Interaction Toolkit 等价能力
- 训练评分与回放系统
- AI 辅助工作流
- 仿真编排平台
- 大规模工业协议矩阵
- 多人协作编辑
- Unity 级别的编辑器广度
- 通用可视化脚本平台
## MVP / V1 / V1.5 分层
### MVP
目的:
- 证明引擎闭环是真的
包括:
- 引擎主循环与运行时壳
- 项目与场景基础
- 编辑器基础
- Windows 独立运行
- RuntimeData 子系统骨架
- mock adapter
- 基础 scene binding
- 一个样板项目
### V1
目的:
- 证明 MetaCore 能支撑第一类真实交付场景
包括:
- 稳定的资源与 package 流程
- 稳定的 prefab 基础
- 一个简单 live adapter
- runtime config 持久化
- binding config 持久化
- 最小 binding 编辑 UI
- runtime diagnostics
- 更强的回归覆盖
### V1.5
目的:
- 让 MetaCore 更像引擎,而不是 pilot 壳
包括:
- 更成熟的编辑器工作流
- 更强的诊断能力
- 更清晰的 Runtime / Editor 分离
- 更稳的资源工作流
- 更明确的 XR 与 B/S 架构预留
## 验收标准
MetaCore 第一阶段应该按以下标准判断。
### 产品验收
- 开发者可以使用 MetaCore 构建一个小型工业三维可视化应用
- 运行时应用可以响应外部数据更新
- 打包后的应用可以独立在 Windows 上运行
### 工程验收
- Runtime 与 Editor 边界清晰
- 数据接入以独立子系统实现,而不是散落在各处逻辑里
- 核心工作流具备回归测试

View File

@ -0,0 +1,263 @@
# MetaCore 产品计划
生成时间2026-03-27
状态:草案
读者:创始人、产品、设计、工程、交付
## 一句话定位
MetaCore 是一套面向国产化部署项目的自主可控三维引擎底座,第一阶段聚焦工业数字孪生项目交付。
## 为什么要做这件事
上一代方案已经证明底层路线能够通过自主可控评测,但基于 Python 的架构在持续功能演进、第三方软件适配和长期稳定交付上成本太高。
C++ 重构不是表面上的技术升级,而是产品动作。
它要让引擎:
- 更容易适配国产平台
- 更容易在交付中审计和裁剪
- 更容易接入复杂企业环境
- 更适合长生命周期行业项目
## 产品假设
第一商业切口不是“做一个通用引擎”,而是:
**在国产化环境下更可靠地运行,并更快交付工业数字孪生项目。**
这才是采购故事。
客户不是因为它像 Unity 才买,而是因为:
- 它能跑在目标国产环境
- 它能降低交付成本和排期风险
- 它足够可控,适合长期掌握
## 核心客户
### 直接客户
- 在国产化约束下做工业数字孪生项目交付的团队
- 面向国企、重工、军工相关客户的系统集成商
- 需要掌握三维底座而不愿长期依赖国外商业引擎的企业技术团队
### 最终运行环境
- 国产 CPU、国产 OS、国产 GPU、中间件及网络受限环境
- 带有自主可控、可信、可追溯、供应链审查要求的项目环境
## 第一商业切口
### Stage 1
通过 C/S 架构完成工业数字孪生项目交付。
### Stage 2
在同一引擎底座上产品化 B/S 嵌入能力和 VR 虚拟维修训练能力。
### Stage 3
扩展到仿真编排、AI 辅助工作流和更高层行业解决方案。
## 核心价值主张
MetaCore 必须同时赢在两件事上:
1. 国产化平台适配能力
2. 快速项目交付能力
如果只有其中一项成立,产品就是弱的:
- 只适配、不快交付:技术上有意思,商业上偏弱
- 只快交付、不适配国产化:容易被替代,护城河不足
## 产品分层
MetaCore 应被定义为一个分层产品,而不是一个单体引擎。
### 1. MetaCore Core
引擎底座。
职责:
- 平台抽象
- 运行时主循环
- 渲染桥
- 场景模型
- 资源流水线
- package 与序列化
- 面向交付的模块化能力
### 2. MetaCore Studio
内部团队与交付团队使用的编辑器。
职责:
- 场景搭建
- 资源导入与整理
- 项目配置
- package 和部署准备
- 面向交付的编辑工作流
非目标:
- 在广度上复制 Unity 编辑器
### 3. MetaCore Industry Twin Kit
面向数字孪生项目交付的解决方案层。
职责:
- 行业项目模板
- 标准场景骨架
- 常用运行时组件
- 数据绑定 adapter
- 部署预设
### 4. MetaCore XR Training Kit
第二阶段的解决方案层。
职责:
- VR 运行时支持
- 维修训练流程编辑
- 交互脚本支持
- 训练评分与回放挂点
## 产品原则
### 原则 1可控优先于便利
凡是会让审计、裁剪、替换、国产适配变难的依赖,默认都应被视为高风险依赖。
### 原则 2交付优先于平台演示
每一个重大功能都应该帮助真实客户项目更快、更稳或更低适配成本地交付。
### 原则 3模块替换点本身就是产品能力
渲染桥、导入流水线、数据连接器、打包方式、XR 层,都应被视为可替换模块,而不是写死的实现。
### 原则 4编辑器围绕交付工作流服务
编辑器不是拿来做“像不像 Unity”的面子工程而是项目团队的生产工具。
### 原则 5可审计性属于架构的一部分
资源流、package 流、场景持久化和部署配置,都应该是显式且可检查的。
## Stage 1 必须交付什么
只有当下面这条最小 C/S 交付链真实成立时Stage 1 才算完成:
1. 在目标国产化环境中运行
2. 导入并组织项目资源
3. 搭建和编辑一个可用的工业场景
4. 配置基础交互与展示行为
5. 打包并运行一个可交付应用
6. 接入基础状态或数据驱动可视化
7. 把一个可部署项目包移交给交付团队,而不需要额外手工拼接
如果这条链没闭环,就不该过早加 AI 或高级仿真能力。
## Stage 1 范围
### 范围内
- 以 Windows 为先的 C++ 引擎重构,同时把国产适配作为架构要求
- 第一阶段只承诺 C/S 交付
- 场景、对象、组件、资源、package 和编辑器基础
- 工业数字孪生场景搭建工作流
- 面向交付的项目结构
- 部署打包与项目移交准备
- 基础数据驱动可视化挂点
- 为后续平台与模块适配保留稳定扩展点
- 为未来 B/S 嵌入保留架构边界
### 明确不在范围内
- 与 Unity 全工作流完全对齐
- 在游戏引擎功能广度上竞争
- 第一阶段完整 B/S 产品化
- 第一阶段浏览器优先运行时
- 高级可视化脚本系统
- 复杂多人协作
- AI-first 工作流
- 完整仿真平台
- 第一阶段完整 VR 训练产品化
## Stage 1 成功标准
### 产品结果
- 一个交付团队能在 MetaCore 上完成一个小型工业数字孪生项目
- 该项目能在目标国产化环境中稳定运行到可接受程度
- 核心项目工作流不再被 Python 时代的架构限制卡住
### 商业结果
- MetaCore 可以被讲成“国产化交付底座”,而不只是内部原型
- 至少有一个项目机会可以用新的 C++ 架构作为主技术故事去推进
- 产品叙事优先支持项目交付收入,再延伸到买断或融资故事
### 工程结果
- 引擎核心足够模块化,便于平台适配和长期演进
- 资源与场景流水线是确定且可审计的
- 运行时与编辑器的依赖边界清晰可控
## 为什么是纯 C++
这次重构应该从产品角度来解释:
- 降低国产环境适配复杂度
- 增强长生命周期企业部署的稳定性
- 让性能和打包结果更可预测
- 更容易接入现有原生软件体系
- 对依赖和执行边界有更强控制
不应该把话术讲成“Python 不好”,正确的说法应该是:
**Python 版本足以支撑上一代评测,但不足以支撑下一代交付。**
## 交付架构策略
MetaCore 最终应该同时支持 C/S 和 B/S。
但第一阶段不应同时完整交付两者。
### 第一阶段承诺
- C/S 是唯一承诺的生产交付架构
- 第一条付费项目闭环必须通过桌面式国产化部署完成
### 第一阶段对 B/S 的预留
- 场景与资源格式不能写死桌面假设
- 运行时边界不能把业务逻辑锁死在单一桌面壳里
- package 与服务边界要保留未来嵌入第三方系统的路径
### 第二阶段承诺
- 产品化 B/S 输出和嵌入模式,用于第三方业务系统集成
规则很简单:
**为两者设计,只在第一阶段承诺 C/S。**
## 技术方向约束
### 架构约束
- 保持严格模块边界
- 优先显式服务契约,避免隐式耦合
- 反射与序列化体系必须掌握在自己手里
- package 格式必须稳定且可版本化
- 平台适配必须作为一等架构问题

View File

@ -0,0 +1,348 @@
# MetaCore RuntimeData 接入设计
生成时间2026-03-27
状态:草案
范围:第一阶段 A
## 目的
这份文档定义 MetaCore RuntimeData 子系统的第一版正式设计。
目标不是一次性解决所有未来接入问题,而是在 Windows C/S 交付闭环里,为实时数据接入提供一条清晰、显式且不会污染场景逻辑或编辑器代码的路径。
## 问题陈述
第一阶段要求从一开始就支持实时数据接入。
这意味着 MetaCore 必须支持:
- 连接外部数据源
- 把输入值归一化
- 把值绑定到场景对象和组件
- 安全处理断连和非法 payload
- 在不改源码的情况下打包和交付项目
当前代码库已经具备:
- 场景与对象持久化
- package 与资源格式
- 编辑器服务注册
- 运行时场景基础
当前代码库尚未完整具备:
- 独立的运行时数据源抽象
- 项目级 binding 模型
- 归一化的运行时更新格式
- 明确的故障与重连策略
## 设计原则
### 1. Runtime 优先
RuntimeData 首先是运行时子系统。Studio 只是它的配置编辑端。
### 2. 协议相关逻辑必须隔离
场景对象不应该直接解析协议 payload。
### 3. 显式优先于聪明
binding 必须是可检查、可追踪、数据驱动的,不应该依赖对象名推断或隐式约定。
### 4. 安全降级
坏数据、目标缺失或断连必须以可见且可预测的方式失败。
### 5. 第一版表面要小
第一阶段只支持最小集合的数据类型和 adapter再根据真实交付压力扩展。
## 推荐模块位置
新增一个模块:
- `Source/MetaCoreRuntimeData`
该模块应依赖:
- `MetaCoreFoundation`
- `MetaCoreScene`
该模块不应依赖:
- `MetaCoreEditor`
下游消费者可以是:
- `MetaCorePlayer`
- 未来用于预览和配置的 `MetaCoreEditor`
## 高层架构
```text
外部数据源
|
v
数据源 Adapter
|
v
归一化 Runtime Update
|
v
Binding Dispatcher
|
+--> 场景对象 Binding Applier
|
+--> Runtime Diagnostics / Fault State
```
## 核心概念
### 1. Data Source Definition
这部分由项目级配置定义。
它描述:
- source id
- adapter 类型
- 连接参数
- 启动模式
- 重连策略
建议结构:
```text
MetaCoreDataSourceDefinition
Id
AdapterType
DisplayName
ConnectionSettings
AutoConnect
ReconnectPolicy
```
### 2. Data Point Definition
这是项目面对的数据契约。
它定义:
- source id
- 外部地址或键
- 逻辑 point id
- 期望值类型
- 更新语义
建议结构:
```text
MetaCoreDataPointDefinition
Id
SourceId
ExternalAddress
ValueType
UpdateMode
FallbackBehavior
```
### 3. Scene Binding Definition
它把逻辑数据点绑定到场景目标。
它定义:
- 目标对象 id
- 目标组件路径
- 转换规则
- 可选转换器
- 缺失数据行为
建议结构:
```text
MetaCoreSceneBindingDefinition
BindingId
DataPointId
TargetObjectId
TargetPath
BindingMode
ConverterId
MissingDataPolicy
```
### 4. Runtime Update
这是 adapter 输出的归一化 payload。
它不应该暴露协议专属表示。
建议结构:
```text
MetaCoreRuntimeDataValue
Type
BoolValue
IntValue
FloatValue
Vec3Value
StringValue
Timestamp
Quality
```
```text
MetaCoreRuntimeDataUpdate
DataPointId
Value
Sequence
SourceTimestamp
```
## 第一阶段值类型
第一阶段故意保持小集合:
- `bool`
- `int64`
- `double`
- `string`
- `vec3`
除非有真实客户场景逼迫,不要在第一阶段引入任意 JSON 形态的通用绑定。
## Binding 目标
第一阶段只应支持一组很窄的 binding 目标。
建议初始目标:
- `Transform.Position`
- `MeshRenderer.Visible`
- `MeshRenderer.BaseColor`
- `Light.Intensity`
- `Light.Color`
如有必要可追加:
- 对象 active / visible 状态
- 未来 UI 组件的文本字段
不要一开始就做“任意反射字段随便绑”的通用系统。看起来优雅,实际通常不可控。
## Adapter 接口
建议运行时接口:
```text
MetaCoreIRuntimeDataSourceAdapter
GetAdapterType()
Configure()
Connect()
Disconnect()
Tick()
PollUpdates()
GetStatus()
```
要求:
- adapter 拥有传输与协议细节
- adapter 只输出归一化 update
- adapter 暴露明确状态
### Adapter 状态
建议状态:
- `Disconnected`
- `Connecting`
- `Connected`
- `Degraded`
- `Faulted`
## 第一阶段推荐 adapter
建议先做两类:
- `mock`
- `file_replay` 或一个简单 live adapter
如果要尽快验证交付闭环,最现实的顺序是:
1. `mock`
2. `file_replay`
3. `tcp`
不要一开始就冲复杂工业协议。
## Dispatcher 职责
Dispatcher 负责:
- 接收 runtime updates
- 找到对应 binding
- 找到目标场景对象
- 把值应用到目标字段
- 更新 binding 健康状态
- 记录 fault 和 stale
它不负责:
- 协议解析
- 编辑器 UI
- 行业业务逻辑
## 配置与持久化
第一阶段走二进制配置路线。
建议产物:
- `ProjectRuntime.mcruntimecfg`
- `DataSources.mcruntime`
- `Bindings.mcruntime`
- `Diagnostics.mcruntimestate`
这样可以:
- 保持运行时配置清晰
- 便于打包
- 与现有二进制 package 方向保持一致
## 失败模式
第一阶段必须显式处理:
- source 不存在
- 连接失败
- payload 类型不匹配
- binding 指向不存在对象
- 长时间无更新导致 stale
- 配置文件损坏
这些都不能静默失败。
## 测试要求
至少要覆盖:
- 配置读写
- 坏配置拒绝
- dispatcher 应用更新
- 缺失目标不崩
- stale 检测
- adapter 状态变化
- 二进制配置可回读
## 结论
RuntimeData 是 MetaCore 的重要行业子系统,但它应该是:
- 明确隔离的
- 运行时优先的
- 二进制配置驱动的
- 小范围起步的
第一阶段只要把这条线做成清晰、稳定、可诊断的子系统就够了,不应该过早抽象成一个万能绑定平台。

View File

@ -0,0 +1,248 @@
# MetaCore RuntimeData 使用说明
生成时间2026-03-27
状态:草案
范围:第一阶段 A / Sprint 4+
## 目的
这份文档说明当前 MetaCore RuntimeData 管线应该如何实际使用。
它主要服务于:
- 内部开发
- 演示准备
- 第一轮交付验证
当前支持的 adapter 类型:
- `mock`
- `file_replay`
- `tcp`
当前运行时配置产物:
- `Runtime/ProjectRuntime.mcruntimecfg`
- `Runtime/DataSources.mcruntime`
- `Runtime/Bindings.mcruntime`
- `Runtime/Diagnostics.mcruntimestate`
## 当前交付模型
当前流程如下:
1. 编写或生成运行时配置
2. 保存二进制 `.mcruntime` 文档
3. 启动 `MetaCorePlayer`
4. 通过某个 adapter 喂入运行时更新
5. 验证场景对象是否按照 binding 发生变化
## RuntimeData 文件
默认样例位置:
- [ProjectRuntime.mcruntimecfg](D:/MetaCore/TestProject/Runtime/ProjectRuntime.mcruntimecfg)
- [DataSources.mcruntime](D:/MetaCore/TestProject/Runtime/DataSources.mcruntime)
- [Bindings.mcruntime](D:/MetaCore/TestProject/Runtime/Bindings.mcruntime)
- [Diagnostics.mcruntimestate](D:/MetaCore/TestProject/Runtime/Diagnostics.mcruntimestate)
- [RuntimeReplay.mcstream](D:/MetaCore/TestProject/Runtime/RuntimeReplay.mcstream)
- [Main.mcscene](D:/MetaCore/TestProject/Scenes/Main.mcscene)
## 当前支持的 binding 目标
第一阶段当前支持:
- `Transform.Position`
- `MeshRenderer.Visible`
- `MeshRenderer.BaseColor`
- `Light.Intensity`
- `Light.Color`
## 方案一File Replay 演示
这是最简单的演示路径。
### 生成 replay 配置
```powershell
D:\MetaCore\build\vs2022-debug\Debug\MetaCoreRuntimeConfigTool.exe D:\MetaCore\TestProject\Runtime
```
这会生成:
- `ProjectRuntime.mcruntimecfg`
- replay 版本的 `DataSources.mcruntime`
- `Bindings.mcruntime`
- `Main.mcscene`
- `RuntimeReplay.mcstream`
### 启动 Player
```powershell
D:\MetaCore\build\vs2022-debug\Debug\MetaCorePlayer.exe
```
预期结果:
- `MetaCorePlayer` 会从项目 / runtime 配置里加载启动场景
- `MetaCorePlayer` 会加载二进制 `.mcruntime`
- 运行时数据来自 `file_replay`
- 样板对象会随时间改变位置、显隐、颜色和告警灯强度
## 方案二TCP 演示
这是当前最小 live adapter 路线。
### 生成 TCP 配置
```powershell
D:\MetaCore\build\vs2022-debug\Debug\MetaCoreRuntimeConfigTool.exe D:\MetaCore\TestProject\Runtime --tcp
```
这会生成:
- `ProjectRuntime.mcruntimecfg`
- TCP 版本的 `DataSources.mcruntime`
- `Bindings.mcruntime`
- `Main.mcscene`
当前默认 TCP source 使用:
- `host = 127.0.0.1`
- `port = 7001`
### 启动 Player
```powershell
D:\MetaCore\build\vs2022-debug\Debug\MetaCorePlayer.exe
```
### 启动 TCP 发送器
```powershell
D:\MetaCore\build\vs2022-debug\Debug\MetaCoreTcpSenderTool.exe
```
预期结果:
- `MetaCorePlayer` 连接 TCP source
- 发送器逐行输出 runtime updates
- 样板对象会改变位置、显隐、颜色和告警灯强度
## TCP 文本协议
当前行格式:
```text
<data_point_id> <value_type> <payload>
```
示例:
```text
cube.visible bool true
cube.position vec3 1.0 2.0 3.0
cube.base_color vec3 0.7 0.6 0.5
```
支持的值类型:
- `bool`
- `int64`
- `double`
- `string`
- `vec3`
## Studio 中的使用方式
当前 Studio 的入口有:
- Inspector 面板
- `Runtime Data Bindings`
- `运行时数据` 面板
### Inspector 当前支持
- 新增 `Replay Source`
- 新增 `TCP Source`
- 编辑 source 显示名
- 编辑 source adapter 类型
- 编辑 replay 的 `file_path`
- 编辑 TCP 的 `host`
- 编辑 TCP 的 `port`
- 新增 `DataPoint`
- 编辑 data point 的 `Point Id`
- 编辑 data point 的 `External Address`
- 编辑 data point 的 `Source`
- 编辑 data point 的 `Value Type`
- 给当前对象新增 binding
- 编辑 binding target 和绑定的数据点
- 保存 runtime 配置
### RuntimeData 面板当前支持
- source / data point / binding 数量统计
- 项目 runtime 路径摘要
- 最近一次 Player 写出的 diagnostics 快照
- source 摘要
- 校验结果摘要
- 当前所有校验问题列表
## 校验规则
当 runtime 配置中存在 `Error` 级问题时,保存会被拒绝。
当前阻断规则包括:
- 空或重复的 `SourceId`
- 空 `AdapterType`
- `file_replay` source 缺少 `file_path`
- `tcp` source 缺少 `port`
- 空或重复的 `DataPointId`
- `DataPoint` 引用了不存在的 `SourceId`
- 空或重复的 `BindingId`
- `Binding` 引用了不存在的 `DataPointId`
- `Binding` 缺少 `TargetObjectId`
当前 Warning 包括:
- 空 `ExternalAddress`
- 没有定义任何 source
## 推荐演示流程
如果只是为了最快演示:
1. 用 `MetaCoreRuntimeConfigTool` 生成配置
2. 打开 `MetaCoreEditorApp`
3. 如有需要,在 Inspector 中调整 binding
4. 保存 runtime 配置
5. 启动 `MetaCorePlayer`
6. 用 replay 或 TCP sender 喂数据
## 常用构建命令
```powershell
cmake --build --preset build-debug --target MetaCoreEditorApp MetaCorePlayer MetaCoreRuntimeConfigTool MetaCoreTcpSenderTool MetaCoreSmokeTests
```
## Smoke Test 命令
```powershell
D:\MetaCore\build\vs2022-debug\Debug\MetaCoreSmokeTests.exe
```
## 当前限制
这些限制是当前阶段有意为之:
- TCP adapter 仍然是最小文本协议,不是工业协议
- runtime diagnostics 当前是二进制快照导出,不是完整时序监控系统
- binding 目标是白名单,不支持任意反射路径
- Studio 的 source 编辑还是表单式,不是完整表格或节点图
## 下一步建议
- 给 Player 增加运行时 diagnostics overlay 或面板
- 增加多对象、多 source 的样板项目
- 在当前 TCP 边界之上接一个更接近生产的 adapter

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,216 @@
#include <cctype>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <optional>
#include <regex>
#include <sstream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <vector>
namespace {
enum class MetaCoreReflectedKind {
Struct,
Class,
Enum
};
struct MetaCoreReflectedField {
std::string Name{};
};
struct MetaCoreReflectedType {
MetaCoreReflectedKind Kind = MetaCoreReflectedKind::Struct;
std::string Name{};
std::vector<MetaCoreReflectedField> Fields{};
};
[[nodiscard]] std::string MetaCoreTrim(std::string value) {
auto isWhitespace = [](unsigned char character) {
return std::isspace(character) != 0;
};
while (!value.empty() && isWhitespace(static_cast<unsigned char>(value.front()))) {
value.erase(value.begin());
}
while (!value.empty() && isWhitespace(static_cast<unsigned char>(value.back()))) {
value.pop_back();
}
return value;
}
[[nodiscard]] std::string MetaCoreBuildIncludePath(const std::filesystem::path& path) {
const std::string full = path.generic_string();
constexpr std::string_view marker = "/Public/";
const std::size_t markerIndex = full.find(marker);
if (markerIndex == std::string::npos) {
return path.filename().generic_string();
}
return full.substr(markerIndex + marker.size());
}
[[nodiscard]] std::vector<MetaCoreReflectedType> MetaCoreParseHeader(const std::filesystem::path& path) {
std::ifstream input(path);
if (!input.is_open()) {
throw std::runtime_error("Unable to open header: " + path.string());
}
const std::regex typeRegex(R"(^(struct|class|enum\s+class)\s+([A-Za-z_]\w*))");
const std::regex fieldRegex(R"(^(.+?)\s+([A-Za-z_]\w*)\s*(?:\{.*\}|=.*)?;$)");
std::vector<MetaCoreReflectedType> reflectedTypes;
std::optional<MetaCoreReflectedKind> pendingKind;
MetaCoreReflectedType* currentType = nullptr;
bool expectingField = false;
std::string line;
while (std::getline(input, line)) {
const std::string trimmed = MetaCoreTrim(line);
if (trimmed.empty() || trimmed.starts_with("//")) {
continue;
}
if (trimmed.starts_with("MC_STRUCT")) {
pendingKind = MetaCoreReflectedKind::Struct;
continue;
}
if (trimmed.starts_with("MC_CLASS")) {
pendingKind = MetaCoreReflectedKind::Class;
continue;
}
if (trimmed.starts_with("MC_ENUM")) {
pendingKind = MetaCoreReflectedKind::Enum;
continue;
}
if (trimmed.starts_with("MC_PROPERTY")) {
expectingField = true;
continue;
}
if (trimmed.starts_with("MC_GENERATED_BODY")) {
continue;
}
if (pendingKind.has_value()) {
std::smatch match;
if (std::regex_search(trimmed, match, typeRegex)) {
reflectedTypes.push_back(MetaCoreReflectedType{
pendingKind.value(),
match[2].str(),
{}
});
currentType = &reflectedTypes.back();
pendingKind.reset();
expectingField = false;
continue;
}
}
if (currentType != nullptr && trimmed == "};") {
currentType = nullptr;
expectingField = false;
continue;
}
if (currentType == nullptr || currentType->Kind == MetaCoreReflectedKind::Enum || !expectingField) {
continue;
}
std::smatch match;
if (!std::regex_search(trimmed, match, fieldRegex)) {
throw std::runtime_error("Unable to parse reflected field in " + path.string() + ": " + trimmed);
}
currentType->Fields.push_back(MetaCoreReflectedField{
match[2].str()
});
expectingField = false;
}
return reflectedTypes;
}
[[nodiscard]] std::string MetaCoreBuildOutput(
const std::string& functionName,
const std::vector<std::filesystem::path>& headers
) {
std::vector<MetaCoreReflectedType> reflectedTypes;
for (const auto& header : headers) {
const auto parsed = MetaCoreParseHeader(header);
reflectedTypes.insert(reflectedTypes.end(), parsed.begin(), parsed.end());
}
std::ostringstream output;
output << "#include \"MetaCoreFoundation/MetaCoreGeneratedReflection.h\"\n";
output << "#include \"MetaCoreFoundation/MetaCoreReflection.h\"\n";
for (const auto& header : headers) {
output << "#include \"" << MetaCoreBuildIncludePath(header) << "\"\n";
}
output << "\nnamespace MetaCore {\n\n";
output << "void " << functionName << "(MetaCoreTypeRegistry& registry) {\n";
for (const auto& reflectedType : reflectedTypes) {
if (reflectedType.Kind == MetaCoreReflectedKind::Enum) {
output << " MetaCoreRegisterGeneratedEnum<" << reflectedType.Name << ">(registry, \"" << reflectedType.Name << "\");\n";
continue;
}
output << " auto " << reflectedType.Name << "Builder = MetaCoreRegisterGeneratedStruct<"
<< reflectedType.Name << ">(registry, \"" << reflectedType.Name << "\");\n";
for (const auto& field : reflectedType.Fields) {
output << " " << reflectedType.Name << "Builder.Field<&" << reflectedType.Name << "::"
<< field.Name << ">(\"" << field.Name << "\");\n";
}
}
output << "}\n\n} // namespace MetaCore\n";
return output.str();
}
} // namespace
int main(int argc, char** argv) {
std::filesystem::path outputPath;
std::string functionName;
std::vector<std::filesystem::path> headers;
for (int index = 1; index < argc; ++index) {
const std::string_view argument(argv[index]);
if (argument == "--output" && index + 1 < argc) {
outputPath = argv[++index];
continue;
}
if (argument == "--function" && index + 1 < argc) {
functionName = argv[++index];
continue;
}
if (argument == "--module" && index + 1 < argc) {
++index;
continue;
}
headers.emplace_back(argv[index]);
}
if (outputPath.empty() || functionName.empty() || headers.empty()) {
std::cerr << "Usage: MetaCoreHeaderTool --output <file> --function <name> [--module <name>] <headers...>\n";
return 1;
}
try {
const std::string output = MetaCoreBuildOutput(functionName, headers);
std::ofstream file(outputPath, std::ios::trunc);
if (!file.is_open()) {
std::cerr << "Unable to open output file: " << outputPath << '\n';
return 1;
}
file << output;
} catch (const std::exception& exception) {
std::cerr << exception.what() << '\n';
return 1;
}
return 0;
}

View File

@ -0,0 +1,187 @@
#include "MetaCoreFoundation/MetaCoreGeneratedReflection.h"
#include "MetaCoreRuntimeData/MetaCoreRuntimeDataProject.h"
#include "MetaCoreScene/MetaCoreScene.h"
#include "MetaCoreScene/MetaCoreScenePackage.h"
#include <filesystem>
#include <fstream>
#include <iostream>
namespace {
[[nodiscard]] MetaCore::MetaCoreSceneDocument MetaCoreBuildPilotSceneDocument() {
MetaCore::MetaCoreSceneDocument document;
document.Name = "Main";
const MetaCore::MetaCoreScene scene = MetaCore::MetaCoreCreateDefaultScene();
document.GameObjects = scene.GetGameObjects();
return document;
}
} // namespace
int main(int argc, char* argv[]) {
if (argc < 2) {
std::cerr << "Usage: MetaCoreRuntimeConfigTool <runtime-directory> [--tcp]\n";
return 1;
}
const bool generateTcpConfig = argc >= 3 && std::string_view(argv[2]) == "--tcp";
MetaCore::MetaCoreTypeRegistry registry;
MetaCore::MetaCoreRegisterRuntimeDataGeneratedTypes(registry);
MetaCore::MetaCoreRuntimeDataSourcesDocument sources;
sources.Sources.push_back(MetaCore::MetaCoreDataSourceDefinition{
generateTcpConfig ? "tcp-source" : "replay-source",
generateTcpConfig ? "tcp" : "file_replay",
generateTcpConfig ? "Tcp Source" : "Replay Source",
generateTcpConfig
? std::vector<MetaCore::MetaCoreDataSourceSetting>{
MetaCore::MetaCoreDataSourceSetting{"host", "127.0.0.1"},
MetaCore::MetaCoreDataSourceSetting{"port", "7001"}
}
: std::vector<MetaCore::MetaCoreDataSourceSetting>{
MetaCore::MetaCoreDataSourceSetting{"file_path", "TestProject/Runtime/RuntimeReplay.mcstream"}
},
true,
1000
});
sources.DataPoints.push_back(MetaCore::MetaCoreDataPointDefinition{
"cube.position",
generateTcpConfig ? "tcp-source" : "replay-source",
"cube.position",
MetaCore::MetaCoreRuntimeValueType::Vec3
});
sources.DataPoints.push_back(MetaCore::MetaCoreDataPointDefinition{
"cube.visible",
generateTcpConfig ? "tcp-source" : "replay-source",
"cube.visible",
MetaCore::MetaCoreRuntimeValueType::Bool
});
sources.DataPoints.push_back(MetaCore::MetaCoreDataPointDefinition{
"cube.base_color",
generateTcpConfig ? "tcp-source" : "replay-source",
"cube.base_color",
MetaCore::MetaCoreRuntimeValueType::Vec3
});
sources.DataPoints.push_back(MetaCore::MetaCoreDataPointDefinition{
"valve.visible",
generateTcpConfig ? "tcp-source" : "replay-source",
"valve.visible",
MetaCore::MetaCoreRuntimeValueType::Bool
});
sources.DataPoints.push_back(MetaCore::MetaCoreDataPointDefinition{
"tank.base_color",
generateTcpConfig ? "tcp-source" : "replay-source",
"tank.base_color",
MetaCore::MetaCoreRuntimeValueType::Vec3
});
sources.DataPoints.push_back(MetaCore::MetaCoreDataPointDefinition{
"alarm.intensity",
generateTcpConfig ? "tcp-source" : "replay-source",
"alarm.intensity",
MetaCore::MetaCoreRuntimeValueType::Double
});
MetaCore::MetaCoreRuntimeBindingsDocument bindings;
bindings.Bindings.push_back(MetaCore::MetaCoreSceneBindingDefinition{
"binding.cube.position",
"cube.position",
3,
MetaCore::MetaCoreRuntimeBindingTarget::TransformPosition,
MetaCore::MetaCoreRuntimeMissingDataPolicy::KeepLastValue
});
bindings.Bindings.push_back(MetaCore::MetaCoreSceneBindingDefinition{
"binding.cube.visible",
"cube.visible",
3,
MetaCore::MetaCoreRuntimeBindingTarget::MeshRendererVisible,
MetaCore::MetaCoreRuntimeMissingDataPolicy::KeepLastValue
});
bindings.Bindings.push_back(MetaCore::MetaCoreSceneBindingDefinition{
"binding.cube.base_color",
"cube.base_color",
3,
MetaCore::MetaCoreRuntimeBindingTarget::MeshRendererBaseColor,
MetaCore::MetaCoreRuntimeMissingDataPolicy::KeepLastValue
});
bindings.Bindings.push_back(MetaCore::MetaCoreSceneBindingDefinition{
"binding.valve.visible",
"valve.visible",
4,
MetaCore::MetaCoreRuntimeBindingTarget::MeshRendererVisible,
MetaCore::MetaCoreRuntimeMissingDataPolicy::KeepLastValue
});
bindings.Bindings.push_back(MetaCore::MetaCoreSceneBindingDefinition{
"binding.tank.base_color",
"tank.base_color",
5,
MetaCore::MetaCoreRuntimeBindingTarget::MeshRendererBaseColor,
MetaCore::MetaCoreRuntimeMissingDataPolicy::KeepLastValue
});
bindings.Bindings.push_back(MetaCore::MetaCoreSceneBindingDefinition{
"binding.alarm.intensity",
"alarm.intensity",
6,
MetaCore::MetaCoreRuntimeBindingTarget::LightIntensity,
MetaCore::MetaCoreRuntimeMissingDataPolicy::KeepLastValue
});
const std::filesystem::path runtimeDirectory = argv[1];
const std::filesystem::path projectRoot = runtimeDirectory.parent_path();
const std::filesystem::path scenePath = projectRoot / "Scenes" / "Main.mcscene";
std::filesystem::create_directories(runtimeDirectory);
if (!MetaCore::MetaCoreWriteRuntimeDataSourcesDocument(runtimeDirectory / "DataSources.mcruntime", sources, registry)) {
std::cerr << "Failed to write DataSources.mcruntime\n";
return 1;
}
if (!MetaCore::MetaCoreWriteRuntimeBindingsDocument(runtimeDirectory / "Bindings.mcruntime", bindings, registry)) {
std::cerr << "Failed to write Bindings.mcruntime\n";
return 1;
}
MetaCore::MetaCoreRuntimeProjectDocument runtimeProjectDocument;
runtimeProjectDocument.StartupScenePath = std::filesystem::path("Scenes") / "Main.mcscene";
runtimeProjectDocument.DataSourcesPath = std::filesystem::path("Runtime") / "DataSources.mcruntime";
runtimeProjectDocument.BindingsPath = std::filesystem::path("Runtime") / "Bindings.mcruntime";
runtimeProjectDocument.DiagnosticsPath = std::filesystem::path("Runtime") / "Diagnostics.mcruntimestate";
if (!MetaCore::MetaCoreWriteRuntimeProjectDocument(runtimeDirectory / "ProjectRuntime.mcruntimecfg", runtimeProjectDocument, registry)) {
std::cerr << "Failed to write ProjectRuntime.mcruntimecfg\n";
return 1;
}
std::filesystem::create_directories(scenePath.parent_path());
if (!MetaCore::MetaCoreWriteScenePackage(scenePath, MetaCoreBuildPilotSceneDocument())) {
std::cerr << "Failed to write Main.mcscene\n";
return 1;
}
if (!generateTcpConfig) {
std::ofstream replayFile(runtimeDirectory / "RuntimeReplay.mcstream", std::ios::trunc);
if (!replayFile) {
std::cerr << "Failed to write RuntimeReplay.mcstream\n";
return 1;
}
replayFile
<< "# emit_after_seconds data_point_id value_type payload\n"
<< "0.00 cube.position vec3 0.0 0.5 0.0\n"
<< "0.00 cube.visible bool true\n"
<< "0.00 cube.base_color vec3 0.4 0.6 0.9\n"
<< "0.00 valve.visible bool true\n"
<< "0.00 tank.base_color vec3 0.35 0.65 0.90\n"
<< "0.00 alarm.intensity double 0.0\n"
<< "0.50 cube.position vec3 1.5 0.5 0.0\n"
<< "0.50 cube.base_color vec3 0.8 0.6 0.5\n"
<< "0.50 tank.base_color vec3 0.20 0.80 0.25\n"
<< "0.50 alarm.intensity double 2.0\n"
<< "1.00 cube.visible bool false\n"
<< "1.00 valve.visible bool false\n"
<< "1.50 cube.position vec3 -1.5 0.5 0.0\n"
<< "1.50 cube.visible bool true\n"
<< "1.50 valve.visible bool true\n"
<< "1.50 alarm.intensity double 0.0\n";
}
std::cout << "Runtime config generated at " << runtimeDirectory.string()
<< " scene=" << scenePath.string()
<< " mode=" << (generateTcpConfig ? "tcp" : "file_replay") << '\n';
return 0;
}

View File

@ -0,0 +1,125 @@
#include <chrono>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <string>
#include <thread>
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <winsock2.h>
#include <ws2tcpip.h>
namespace {
bool MetaCoreInitializeWinsock() {
WSADATA wsaData{};
return WSAStartup(MAKEWORD(2, 2), &wsaData) == 0;
}
void MetaCoreShutdownWinsock() {
WSACleanup();
}
} // namespace
int main(int argc, char* argv[]) {
const std::string bindHost = argc > 1 ? argv[1] : "127.0.0.1";
const unsigned short port = argc > 2 ? static_cast<unsigned short>(std::strtoul(argv[2], nullptr, 10)) : 7001;
if (!MetaCoreInitializeWinsock()) {
std::cerr << "MetaCoreTcpSenderTool: WSAStartup failed\n";
return 1;
}
addrinfo hints{};
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = AI_PASSIVE;
addrinfo* result = nullptr;
const std::string portString = std::to_string(port);
if (getaddrinfo(bindHost.c_str(), portString.c_str(), &hints, &result) != 0 || result == nullptr) {
std::cerr << "MetaCoreTcpSenderTool: getaddrinfo failed\n";
MetaCoreShutdownWinsock();
return 1;
}
SOCKET listenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
if (listenSocket == INVALID_SOCKET) {
std::cerr << "MetaCoreTcpSenderTool: socket creation failed\n";
freeaddrinfo(result);
MetaCoreShutdownWinsock();
return 1;
}
if (bind(listenSocket, result->ai_addr, static_cast<int>(result->ai_addrlen)) == SOCKET_ERROR) {
std::cerr << "MetaCoreTcpSenderTool: bind failed\n";
closesocket(listenSocket);
freeaddrinfo(result);
MetaCoreShutdownWinsock();
return 1;
}
freeaddrinfo(result);
if (listen(listenSocket, 1) == SOCKET_ERROR) {
std::cerr << "MetaCoreTcpSenderTool: listen failed\n";
closesocket(listenSocket);
MetaCoreShutdownWinsock();
return 1;
}
std::cout << "MetaCoreTcpSenderTool: listening on " << bindHost << ":" << port << '\n';
SOCKET clientSocket = accept(listenSocket, nullptr, nullptr);
closesocket(listenSocket);
if (clientSocket == INVALID_SOCKET) {
std::cerr << "MetaCoreTcpSenderTool: accept failed\n";
MetaCoreShutdownWinsock();
return 1;
}
std::cout << "MetaCoreTcpSenderTool: client connected\n";
for (int frame = 0; frame < 600; ++frame) {
const double t = static_cast<double>(frame) * 0.05;
const float x = static_cast<float>(std::sin(t) * 2.0);
const bool visible = std::fmod(t, 2.0) < 1.0;
const bool valveVisible = std::fmod(t, 3.0) < 1.5;
const float r = 0.5F + static_cast<float>(std::sin(t) * 0.4);
const float g = 0.6F;
const float b = 0.9F - static_cast<float>(std::sin(t) * 0.2);
const float tankR = 0.25F + static_cast<float>((std::sin(t * 0.5) + 1.0) * 0.25);
const float tankG = 0.55F + static_cast<float>((std::cos(t * 0.5) + 1.0) * 0.15);
const float tankB = 0.35F;
const double alarmIntensity = valveVisible ? 2.5 : 0.0;
const std::string payload =
"cube.position vec3 " + std::to_string(x) + " 0.5 0.0\n" +
"cube.visible bool " + std::string(visible ? "true" : "false") + "\n" +
"cube.base_color vec3 " + std::to_string(r) + " " + std::to_string(g) + " " + std::to_string(b) + "\n" +
"valve.visible bool " + std::string(valveVisible ? "true" : "false") + "\n" +
"tank.base_color vec3 " + std::to_string(tankR) + " " + std::to_string(tankG) + " " + std::to_string(tankB) + "\n" +
"alarm.intensity double " + std::to_string(alarmIntensity) + "\n";
const int sent = send(clientSocket, payload.c_str(), static_cast<int>(payload.size()), 0);
if (sent == SOCKET_ERROR) {
std::cerr << "MetaCoreTcpSenderTool: send failed\n";
closesocket(clientSocket);
MetaCoreShutdownWinsock();
return 1;
}
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
shutdown(clientSocket, SD_SEND);
closesocket(clientSocket);
MetaCoreShutdownWinsock();
std::cout << "MetaCoreTcpSenderTool: stream completed\n";
return 0;
}