1104 lines
46 KiB
C++
1104 lines
46 KiB
C++
#include "MetaCorePlatform/MetaCoreWindow.h"
|
|
#include "MetaCoreFoundation/MetaCoreGeneratedReflection.h"
|
|
#include "MetaCoreFoundation/MetaCorePackage.h"
|
|
#include "MetaCoreFoundation/MetaCoreProject.h"
|
|
#include "MetaCoreRender/MetaCorePlayerRenderer.h"
|
|
#include "MetaCoreRender/MetaCoreRenderDevice.h"
|
|
#include "MetaCoreRender/MetaCoreRuntimeUiRenderer.h"
|
|
#include "MetaCoreRender/MetaCoreRenderTypes.h"
|
|
#include "MetaCoreRuntimeData/MetaCoreRuntimeDataDispatcher.h"
|
|
#include "MetaCoreRuntimeData/MetaCoreRuntimeDataProject.h"
|
|
#include "MetaCoreRuntimeData/MetaCoreRuntimeDataSource.h"
|
|
#include "MetaCoreScene/MetaCoreRuntimeLifecycle.h"
|
|
#include "MetaCoreScene/MetaCoreScenePackage.h"
|
|
#include "MetaCoreScene/MetaCoreSceneSerializer.h"
|
|
#include "MetaCoreScene/MetaCoreScene.h"
|
|
#include "MetaCoreScene/MetaCoreUiRmlCompiler.h"
|
|
|
|
#include <nlohmann/json.hpp>
|
|
|
|
#include <algorithm>
|
|
#include <cctype>
|
|
#include <cstring>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <memory>
|
|
#include <optional>
|
|
#include <span>
|
|
#include <sstream>
|
|
#include <string_view>
|
|
#include <unordered_map>
|
|
#include <vector>
|
|
|
|
namespace {
|
|
|
|
MetaCore::MetaCoreSceneView MetaCoreBuildPlayerSceneView() {
|
|
MetaCore::MetaCoreSceneView sceneView;
|
|
sceneView.CameraPosition = {0.0F, 2.5F, 6.5F};
|
|
sceneView.CameraTarget = {0.0F, 0.7F, 0.0F};
|
|
sceneView.CameraUp = {0.0F, 1.0F, 0.0F};
|
|
sceneView.VerticalFieldOfViewDegrees = 60.0F;
|
|
return sceneView;
|
|
}
|
|
|
|
struct MetaCoreRuntimeDataSourceInstance {
|
|
MetaCore::MetaCoreDataSourceDefinition Definition{};
|
|
std::unique_ptr<MetaCore::MetaCoreIRuntimeDataSourceAdapter> Adapter{};
|
|
MetaCore::MetaCoreRuntimeDataSourceState LastReportedState =
|
|
MetaCore::MetaCoreRuntimeDataSourceState::Disconnected;
|
|
};
|
|
|
|
[[nodiscard]] std::optional<std::string> MetaCoreResolveRuntimeDataReplayFilePaths(
|
|
MetaCore::MetaCoreRuntimeDataSourcesDocument& sourcesDocument,
|
|
const std::filesystem::path& projectRoot
|
|
) {
|
|
for (MetaCore::MetaCoreDataSourceDefinition& sourceDefinition : sourcesDocument.Sources) {
|
|
if (sourceDefinition.AdapterType != "file_replay") {
|
|
continue;
|
|
}
|
|
|
|
for (MetaCore::MetaCoreDataSourceSetting& setting : sourceDefinition.ConnectionSettings) {
|
|
if (setting.Key != "file_path" || setting.Value.empty()) {
|
|
continue;
|
|
}
|
|
|
|
const std::filesystem::path replayPath(setting.Value);
|
|
if (replayPath.is_absolute()) {
|
|
continue;
|
|
}
|
|
if (MetaCore::MetaCoreIsUnsafeRuntimeProjectPath(replayPath)) {
|
|
return "RuntimeData source " + sourceDefinition.Id +
|
|
" file_path must be project-relative without parent traversal: " +
|
|
replayPath.generic_string();
|
|
}
|
|
|
|
setting.Value = (projectRoot / replayPath.lexically_normal()).string();
|
|
}
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
|
|
[[nodiscard]] std::optional<MetaCore::MetaCoreCookManifestDocument> MetaCoreReadCookManifestDocument(
|
|
const std::filesystem::path& manifestPath,
|
|
const MetaCore::MetaCoreTypeRegistry& registry
|
|
) {
|
|
std::ifstream input(manifestPath, 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> buffer(size);
|
|
if (size > 0 && !input.read(reinterpret_cast<char*>(buffer.data()), static_cast<std::streamsize>(size))) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
MetaCore::MetaCoreCookManifestDocument document;
|
|
if (!MetaCore::MetaCoreDeserializeFromBytes(
|
|
std::span<const std::byte>(buffer.data(), buffer.size()),
|
|
document,
|
|
registry)) {
|
|
return std::nullopt;
|
|
}
|
|
return document;
|
|
}
|
|
|
|
[[nodiscard]] std::optional<MetaCore::MetaCoreSceneDocument> MetaCoreReadSceneDocumentFromPath(
|
|
const std::filesystem::path& path,
|
|
const MetaCore::MetaCoreTypeRegistry& registry
|
|
) {
|
|
if (path.extension() == ".json") {
|
|
return MetaCore::MetaCoreSceneSerializer::LoadSceneFromJson(path, registry);
|
|
}
|
|
return MetaCore::MetaCoreReadScenePackage(path);
|
|
}
|
|
|
|
[[nodiscard]] std::string MetaCoreBuildPortablePathKey(const std::filesystem::path& path) {
|
|
return path.lexically_normal().generic_string();
|
|
}
|
|
|
|
[[nodiscard]] const MetaCore::MetaCoreCookManifestEntry* MetaCoreFindCookedManifestEntry(
|
|
const MetaCore::MetaCoreCookManifestDocument& manifest,
|
|
const std::filesystem::path& relativeSourcePath
|
|
) {
|
|
const std::string requestedPath = MetaCoreBuildPortablePathKey(relativeSourcePath);
|
|
const auto iterator = std::find_if(
|
|
manifest.Entries.begin(),
|
|
manifest.Entries.end(),
|
|
[&](const MetaCore::MetaCoreCookManifestEntry& entry) {
|
|
return MetaCoreBuildPortablePathKey(entry.SourcePackagePath) == requestedPath;
|
|
}
|
|
);
|
|
return iterator == manifest.Entries.end() ? nullptr : &*iterator;
|
|
}
|
|
|
|
[[nodiscard]] const MetaCore::MetaCoreCookManifestEntry* MetaCoreFindCookedManifestEntry(
|
|
const MetaCore::MetaCoreCookManifestDocument& manifest,
|
|
const MetaCore::MetaCoreAssetGuid& assetGuid
|
|
) {
|
|
if (!assetGuid.IsValid()) {
|
|
return nullptr;
|
|
}
|
|
const auto iterator = std::find_if(
|
|
manifest.Entries.begin(),
|
|
manifest.Entries.end(),
|
|
[&](const MetaCore::MetaCoreCookManifestEntry& entry) {
|
|
return entry.AssetGuid == assetGuid;
|
|
}
|
|
);
|
|
return iterator == manifest.Entries.end() ? nullptr : &*iterator;
|
|
}
|
|
|
|
[[nodiscard]] bool MetaCoreIsCookedRuntimeGltfModel(const std::filesystem::path& relativeSourcePath) {
|
|
std::string extension = relativeSourcePath.extension().generic_string();
|
|
std::transform(extension.begin(), extension.end(), extension.begin(), [](unsigned char c) {
|
|
return static_cast<char>(std::tolower(c));
|
|
});
|
|
return extension == ".glb" || extension == ".gltf";
|
|
}
|
|
|
|
void MetaCoreRewriteSceneGltfModelPathsToCooked(
|
|
MetaCore::MetaCoreSceneDocument& sceneDocument,
|
|
const MetaCore::MetaCoreCookManifestDocument& manifest
|
|
) {
|
|
for (MetaCore::MetaCoreGameObjectData& gameObject : sceneDocument.GameObjects) {
|
|
if (!gameObject.MeshRenderer.has_value()) {
|
|
continue;
|
|
}
|
|
if (gameObject.MeshRenderer->SourceModelPath.empty() &&
|
|
!gameObject.MeshRenderer->SourceModelAssetGuid.IsValid()) {
|
|
continue;
|
|
}
|
|
|
|
const std::filesystem::path sourceModelPath(gameObject.MeshRenderer->SourceModelPath);
|
|
const MetaCore::MetaCoreCookManifestEntry* entry = nullptr;
|
|
if (!sourceModelPath.empty() && MetaCoreIsCookedRuntimeGltfModel(sourceModelPath)) {
|
|
entry = MetaCoreFindCookedManifestEntry(manifest, sourceModelPath);
|
|
}
|
|
if (entry == nullptr && gameObject.MeshRenderer->SourceModelAssetGuid.IsValid()) {
|
|
entry = MetaCoreFindCookedManifestEntry(manifest, gameObject.MeshRenderer->SourceModelAssetGuid);
|
|
}
|
|
if (entry == nullptr || entry->CookedPath.empty()) {
|
|
continue;
|
|
}
|
|
if (!MetaCoreIsCookedRuntimeGltfModel(entry->SourcePackagePath)) {
|
|
continue;
|
|
}
|
|
|
|
const std::string cookedModelPath = entry->CookedPath.generic_string();
|
|
gameObject.MeshRenderer->SourceModelPath = cookedModelPath;
|
|
gameObject.MeshRenderer->SourceModelAssetGuid = {};
|
|
if (gameObject.ModelRootTag.has_value()) {
|
|
gameObject.ModelRootTag->SourceModelPath = cookedModelPath;
|
|
}
|
|
}
|
|
}
|
|
|
|
template <typename T>
|
|
[[nodiscard]] std::optional<T> MetaCoreReadTypedPackagePayload(
|
|
const MetaCore::MetaCorePackageDocument& package,
|
|
const MetaCore::MetaCoreTypeRegistry& registry,
|
|
std::string_view expectedTypeName
|
|
) {
|
|
const MetaCore::MetaCoreTypeId expectedTypeId = MetaCore::MetaCoreMakeTypeId(expectedTypeName);
|
|
for (const MetaCore::MetaCoreExportEntry& exportEntry : package.Exports) {
|
|
if (exportEntry.TypeId != expectedTypeId || exportEntry.PayloadIndex >= package.PayloadSections.size()) {
|
|
continue;
|
|
}
|
|
|
|
T payload{};
|
|
if (MetaCore::MetaCoreDeserializeFromBytes(
|
|
package.PayloadSections[exportEntry.PayloadIndex],
|
|
payload,
|
|
registry)) {
|
|
return payload;
|
|
}
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
|
|
template <typename T>
|
|
[[nodiscard]] std::optional<T> MetaCoreReadCookedDocument(
|
|
const std::filesystem::path& projectRoot,
|
|
const MetaCore::MetaCoreCookManifestDocument& manifest,
|
|
const std::filesystem::path& relativeSourcePath,
|
|
const MetaCore::MetaCoreTypeRegistry& registry,
|
|
std::string_view expectedTypeName
|
|
) {
|
|
const MetaCore::MetaCoreCookManifestEntry* entry =
|
|
MetaCoreFindCookedManifestEntry(manifest, relativeSourcePath);
|
|
if (entry == nullptr || entry->CookedPath.empty()) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
const auto cookedPackage = MetaCore::MetaCoreReadPackageFile(projectRoot / entry->CookedPath, registry);
|
|
if (!cookedPackage.has_value()) {
|
|
return std::nullopt;
|
|
}
|
|
return MetaCoreReadTypedPackagePayload<T>(*cookedPackage, registry, expectedTypeName);
|
|
}
|
|
|
|
[[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;
|
|
}
|
|
|
|
[[nodiscard]] std::string MetaCoreBuildRuntimeDataStatusText(
|
|
const MetaCore::MetaCoreRuntimeDiagnosticsSnapshot& diagnostics
|
|
) {
|
|
const auto connectedSources = std::count_if(
|
|
diagnostics.SourceStatuses.begin(),
|
|
diagnostics.SourceStatuses.end(),
|
|
[](const MetaCore::MetaCoreRuntimeDataSourceStatus& status) {
|
|
return status.State == MetaCore::MetaCoreRuntimeDataSourceState::Connected;
|
|
}
|
|
);
|
|
const auto sourceIssues = std::count_if(
|
|
diagnostics.SourceStatuses.begin(),
|
|
diagnostics.SourceStatuses.end(),
|
|
[](const MetaCore::MetaCoreRuntimeDataSourceStatus& status) {
|
|
return status.State == MetaCore::MetaCoreRuntimeDataSourceState::Degraded ||
|
|
status.State == MetaCore::MetaCoreRuntimeDataSourceState::Faulted;
|
|
}
|
|
);
|
|
const auto bindingIssues = std::count_if(
|
|
diagnostics.BindingStatuses.begin(),
|
|
diagnostics.BindingStatuses.end(),
|
|
[](const MetaCore::MetaCoreRuntimeBindingStatus& status) {
|
|
return !status.Healthy || status.Stale;
|
|
}
|
|
);
|
|
|
|
return "RuntimeData sources=" + std::to_string(diagnostics.SourceStatuses.size())
|
|
+ " connected=" + std::to_string(static_cast<std::size_t>(connectedSources))
|
|
+ " source_issues=" + std::to_string(static_cast<std::size_t>(sourceIssues))
|
|
+ " bindings=" + std::to_string(diagnostics.BindingStatuses.size())
|
|
+ " binding_issues=" + std::to_string(static_cast<std::size_t>(bindingIssues));
|
|
}
|
|
|
|
[[nodiscard]] std::string MetaCoreFormatRuntimeDataValue(
|
|
const MetaCore::MetaCoreRuntimeDataValue& value
|
|
) {
|
|
std::ostringstream stream;
|
|
switch (value.Type) {
|
|
case MetaCore::MetaCoreRuntimeValueType::Bool:
|
|
return value.BoolValue ? "true" : "false";
|
|
case MetaCore::MetaCoreRuntimeValueType::Int64:
|
|
return std::to_string(value.Int64Value);
|
|
case MetaCore::MetaCoreRuntimeValueType::Double:
|
|
stream << value.DoubleValue;
|
|
return stream.str();
|
|
case MetaCore::MetaCoreRuntimeValueType::String:
|
|
return value.StringValue;
|
|
case MetaCore::MetaCoreRuntimeValueType::Vec3:
|
|
stream << value.Vec3Value.x << ", " << value.Vec3Value.y << ", " << value.Vec3Value.z;
|
|
return stream.str();
|
|
}
|
|
return {};
|
|
}
|
|
|
|
[[nodiscard]] const MetaCore::MetaCoreDataPointDefinition* MetaCoreFindRuntimeDataPoint(
|
|
const std::vector<MetaCore::MetaCoreDataPointDefinition>& dataPoints,
|
|
const std::string& dataPointId
|
|
) {
|
|
const auto iterator = std::find_if(
|
|
dataPoints.begin(),
|
|
dataPoints.end(),
|
|
[&](const MetaCore::MetaCoreDataPointDefinition& dataPoint) {
|
|
return dataPoint.Id == dataPointId;
|
|
}
|
|
);
|
|
return iterator == dataPoints.end() ? nullptr : &*iterator;
|
|
}
|
|
|
|
void MetaCoreMarkRuntimeBindingFault(
|
|
std::vector<MetaCore::MetaCoreRuntimeBindingStatus>& statuses,
|
|
const std::string& bindingId,
|
|
const std::string& error
|
|
) {
|
|
const auto iterator = std::find_if(
|
|
statuses.begin(),
|
|
statuses.end(),
|
|
[&](const MetaCore::MetaCoreRuntimeBindingStatus& status) {
|
|
return status.BindingId == bindingId;
|
|
}
|
|
);
|
|
if (iterator == statuses.end()) {
|
|
return;
|
|
}
|
|
|
|
iterator->Healthy = false;
|
|
iterator->Stale = false;
|
|
iterator->LastError = error;
|
|
}
|
|
|
|
void MetaCoreMarkRuntimeBindingHealthy(
|
|
std::vector<MetaCore::MetaCoreRuntimeBindingStatus>& statuses,
|
|
const std::string& bindingId,
|
|
std::uint64_t appliedAt
|
|
) {
|
|
const auto iterator = std::find_if(
|
|
statuses.begin(),
|
|
statuses.end(),
|
|
[&](const MetaCore::MetaCoreRuntimeBindingStatus& status) {
|
|
return status.BindingId == bindingId;
|
|
}
|
|
);
|
|
if (iterator == statuses.end()) {
|
|
return;
|
|
}
|
|
|
|
iterator->Healthy = true;
|
|
iterator->Stale = false;
|
|
iterator->LastAppliedAt = appliedAt;
|
|
iterator->LastError.clear();
|
|
}
|
|
|
|
[[nodiscard]] std::vector<MetaCore::MetaCoreRuntimeBindingStatus> MetaCoreBuildRuntimeUiBindingStatuses(
|
|
const std::vector<MetaCore::MetaCoreUiBindingDefinition>& uiBindings
|
|
) {
|
|
std::vector<MetaCore::MetaCoreRuntimeBindingStatus> statuses;
|
|
statuses.reserve(uiBindings.size());
|
|
for (const MetaCore::MetaCoreUiBindingDefinition& binding : uiBindings) {
|
|
statuses.push_back(MetaCore::MetaCoreRuntimeBindingStatus{
|
|
binding.BindingId,
|
|
true,
|
|
false,
|
|
0,
|
|
1000,
|
|
{}
|
|
});
|
|
}
|
|
return statuses;
|
|
}
|
|
|
|
[[nodiscard]] bool MetaCoreHasRuntimeUiTextBindingForNode(
|
|
const std::vector<MetaCore::MetaCoreUiBindingDefinition>& uiBindings,
|
|
std::string_view nodeId
|
|
) {
|
|
return std::any_of(
|
|
uiBindings.begin(),
|
|
uiBindings.end(),
|
|
[nodeId](const MetaCore::MetaCoreUiBindingDefinition& binding) {
|
|
return binding.Target == MetaCore::MetaCoreRuntimeUiBindingTarget::Text &&
|
|
binding.TargetNodeId == nodeId;
|
|
}
|
|
);
|
|
}
|
|
|
|
void MetaCoreValidateRuntimeUiBindingTargets(
|
|
const std::vector<MetaCore::MetaCoreUiBindingDefinition>& uiBindings,
|
|
const MetaCore::MetaCoreRuntimeUiRenderer& runtimeUiRenderer,
|
|
std::vector<MetaCore::MetaCoreRuntimeBindingStatus>& uiBindingStatuses
|
|
) {
|
|
for (const MetaCore::MetaCoreUiBindingDefinition& binding : uiBindings) {
|
|
if (binding.Target != MetaCore::MetaCoreRuntimeUiBindingTarget::Text) {
|
|
continue;
|
|
}
|
|
|
|
if (!runtimeUiRenderer.GetStats().Loaded) {
|
|
MetaCoreMarkRuntimeBindingFault(
|
|
uiBindingStatuses,
|
|
binding.BindingId,
|
|
"Runtime UI document is not loaded"
|
|
);
|
|
continue;
|
|
}
|
|
|
|
if (!runtimeUiRenderer.HasNode(binding.TargetNodeId)) {
|
|
MetaCoreMarkRuntimeBindingFault(
|
|
uiBindingStatuses,
|
|
binding.BindingId,
|
|
"Runtime UI node was not found: " + binding.TargetNodeId
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
void MetaCoreApplyRuntimeUiBindings(
|
|
const std::vector<MetaCore::MetaCoreRuntimeDataUpdate>& updates,
|
|
const std::vector<MetaCore::MetaCoreDataPointDefinition>& dataPoints,
|
|
const std::vector<MetaCore::MetaCoreUiBindingDefinition>& uiBindings,
|
|
MetaCore::MetaCoreRuntimeUiRenderer& runtimeUiRenderer,
|
|
std::vector<MetaCore::MetaCoreRuntimeBindingStatus>& uiBindingStatuses
|
|
) {
|
|
for (const MetaCore::MetaCoreRuntimeDataUpdate& update : updates) {
|
|
const MetaCore::MetaCoreDataPointDefinition* dataPoint =
|
|
MetaCoreFindRuntimeDataPoint(dataPoints, update.DataPointId);
|
|
if (dataPoint == nullptr) {
|
|
continue;
|
|
}
|
|
|
|
for (const MetaCore::MetaCoreUiBindingDefinition& binding : uiBindings) {
|
|
if (binding.DataPointId != update.DataPointId) {
|
|
continue;
|
|
}
|
|
if (update.Value.Type != dataPoint->ValueType) {
|
|
MetaCoreMarkRuntimeBindingFault(
|
|
uiBindingStatuses,
|
|
binding.BindingId,
|
|
"Update type does not match data point definition"
|
|
);
|
|
continue;
|
|
}
|
|
if (update.Value.Quality == MetaCore::MetaCoreRuntimeDataQuality::Bad) {
|
|
MetaCoreMarkRuntimeBindingFault(uiBindingStatuses, binding.BindingId, "Update quality is bad");
|
|
continue;
|
|
}
|
|
if (binding.Target == MetaCore::MetaCoreRuntimeUiBindingTarget::Text) {
|
|
if (!runtimeUiRenderer.SetNodeText(binding.TargetNodeId, MetaCoreFormatRuntimeDataValue(update.Value))) {
|
|
MetaCoreMarkRuntimeBindingFault(
|
|
uiBindingStatuses,
|
|
binding.BindingId,
|
|
runtimeUiRenderer.GetStats().LastError
|
|
);
|
|
continue;
|
|
}
|
|
}
|
|
MetaCoreMarkRuntimeBindingHealthy(uiBindingStatuses, binding.BindingId, update.Value.SourceTimestamp);
|
|
}
|
|
}
|
|
}
|
|
|
|
void MetaCoreTickRuntimeBindingStaleness(
|
|
std::vector<MetaCore::MetaCoreRuntimeBindingStatus>& statuses,
|
|
std::uint64_t currentTimestamp
|
|
) {
|
|
for (MetaCore::MetaCoreRuntimeBindingStatus& status : statuses) {
|
|
if (status.LastAppliedAt == 0) {
|
|
status.Stale = true;
|
|
continue;
|
|
}
|
|
status.Stale =
|
|
currentTimestamp > status.LastAppliedAt &&
|
|
(currentTimestamp - status.LastAppliedAt) > status.StaleAfterMs;
|
|
}
|
|
}
|
|
|
|
void MetaCoreAppendRuntimeUiBindingDiagnostics(
|
|
MetaCore::MetaCoreRuntimeDiagnosticsSnapshot& diagnostics,
|
|
const std::vector<MetaCore::MetaCoreRuntimeBindingStatus>& uiBindingStatuses
|
|
) {
|
|
diagnostics.BindingStatuses.insert(
|
|
diagnostics.BindingStatuses.end(),
|
|
uiBindingStatuses.begin(),
|
|
uiBindingStatuses.end()
|
|
);
|
|
diagnostics.HasFaults = diagnostics.HasFaults || std::any_of(
|
|
uiBindingStatuses.begin(),
|
|
uiBindingStatuses.end(),
|
|
[](const MetaCore::MetaCoreRuntimeBindingStatus& status) {
|
|
return !status.Healthy || status.Stale;
|
|
}
|
|
);
|
|
}
|
|
|
|
[[nodiscard]] std::vector<MetaCore::MetaCoreRuntimeDataSourceStatus> MetaCoreCollectRuntimeSourceStatuses(
|
|
const std::vector<MetaCoreRuntimeDataSourceInstance>& runtimeSources
|
|
) {
|
|
std::vector<MetaCore::MetaCoreRuntimeDataSourceStatus> sourceStatuses;
|
|
sourceStatuses.reserve(runtimeSources.size());
|
|
for (const MetaCoreRuntimeDataSourceInstance& sourceInstance : runtimeSources) {
|
|
sourceStatuses.push_back(sourceInstance.Adapter->GetStatus());
|
|
}
|
|
return sourceStatuses;
|
|
}
|
|
|
|
void MetaCoreWriteRuntimeDiagnosticsSnapshot(
|
|
const std::filesystem::path& diagnosticsPath,
|
|
const MetaCore::MetaCoreTypeRegistry& typeRegistry,
|
|
const MetaCore::MetaCoreRuntimeDataDispatcher& runtimeDataDispatcher,
|
|
const std::vector<MetaCore::MetaCoreRuntimeDataSourceStatus>& sourceStatuses,
|
|
const std::vector<MetaCore::MetaCoreRuntimeBindingStatus>& uiBindingStatuses
|
|
) {
|
|
MetaCore::MetaCoreRuntimeDiagnosticsSnapshot diagnostics =
|
|
runtimeDataDispatcher.BuildDiagnosticsSnapshot(sourceStatuses);
|
|
MetaCoreAppendRuntimeUiBindingDiagnostics(diagnostics, uiBindingStatuses);
|
|
(void)MetaCore::MetaCoreWriteRuntimeDiagnosticsSnapshot(diagnosticsPath, diagnostics, typeRegistry);
|
|
}
|
|
|
|
[[nodiscard]] std::string MetaCoreRuntimeBindingIssueSignature(
|
|
const MetaCore::MetaCoreRuntimeBindingStatus& bindingStatus
|
|
) {
|
|
std::ostringstream stream;
|
|
stream << (bindingStatus.Healthy ? "healthy" : "fault")
|
|
<< "|stale=" << (bindingStatus.Stale ? "true" : "false")
|
|
<< "|error=" << bindingStatus.LastError;
|
|
return stream.str();
|
|
}
|
|
|
|
void MetaCoreLogRuntimeBindingIssues(
|
|
const MetaCore::MetaCoreRuntimeDiagnosticsSnapshot& diagnostics,
|
|
std::unordered_map<std::string, std::string>& reportedBindingIssues
|
|
) {
|
|
for (const MetaCore::MetaCoreRuntimeBindingStatus& bindingStatus : diagnostics.BindingStatuses) {
|
|
if (bindingStatus.Healthy && !bindingStatus.Stale) {
|
|
reportedBindingIssues.erase(bindingStatus.BindingId);
|
|
continue;
|
|
}
|
|
|
|
const std::string issueSignature = MetaCoreRuntimeBindingIssueSignature(bindingStatus);
|
|
const auto reportedIterator = reportedBindingIssues.find(bindingStatus.BindingId);
|
|
if (reportedIterator != reportedBindingIssues.end() && reportedIterator->second == issueSignature) {
|
|
continue;
|
|
}
|
|
|
|
std::cout << "MetaCorePlayer: binding issue id=" << bindingStatus.BindingId
|
|
<< " stale=" << (bindingStatus.Stale ? "true" : "false")
|
|
<< " error=" << bindingStatus.LastError << '\n';
|
|
reportedBindingIssues[bindingStatus.BindingId] = issueSignature;
|
|
}
|
|
}
|
|
|
|
struct MetaCorePlayerLaunchConfig {
|
|
std::string Platform{"Windows"};
|
|
std::string GraphicsBackend{"OpenGL"};
|
|
std::string Architecture{"x64"};
|
|
std::string BuildProfile{"Development"};
|
|
};
|
|
|
|
[[nodiscard]] MetaCorePlayerLaunchConfig MetaCoreReadPlayerLaunchConfig(
|
|
const std::filesystem::path& configPath
|
|
) {
|
|
MetaCorePlayerLaunchConfig config;
|
|
std::ifstream input(configPath, std::ios::binary);
|
|
if (!input.is_open()) {
|
|
return config;
|
|
}
|
|
|
|
nlohmann::json document;
|
|
try {
|
|
input >> document;
|
|
} catch (const nlohmann::json::exception&) {
|
|
return config;
|
|
}
|
|
|
|
const auto targetIterator = document.find("target");
|
|
if (targetIterator != document.end() && targetIterator->is_object()) {
|
|
config.Platform = targetIterator->value("platform", config.Platform);
|
|
config.GraphicsBackend = targetIterator->value("graphics_backend", config.GraphicsBackend);
|
|
config.Architecture = targetIterator->value("architecture", config.Architecture);
|
|
}
|
|
const auto runtimeIterator = document.find("runtime");
|
|
if (runtimeIterator != document.end() && runtimeIterator->is_object()) {
|
|
config.BuildProfile = runtimeIterator->value("build_profile", config.BuildProfile);
|
|
}
|
|
return config;
|
|
}
|
|
|
|
[[nodiscard]] MetaCore::MetaCoreRenderBackend MetaCoreSelectRenderBackend(
|
|
const MetaCorePlayerLaunchConfig& launchConfig
|
|
) {
|
|
if (launchConfig.GraphicsBackend == "Vulkan") {
|
|
return MetaCore::MetaCoreRenderBackend::Vulkan;
|
|
}
|
|
if (launchConfig.GraphicsBackend == "WebGPU") {
|
|
return MetaCore::MetaCoreRenderBackend::WebGPU;
|
|
}
|
|
if (launchConfig.GraphicsBackend == "WebGL") {
|
|
std::cout << "MetaCorePlayer: WebGL is not available in native Player; using OpenGL backend\n";
|
|
}
|
|
return MetaCore::MetaCoreRenderBackend::OpenGL;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
int main(int argc, char* argv[]) {
|
|
std::filesystem::path customProjectRoot{};
|
|
std::filesystem::path customScenePath{};
|
|
for (int i = 1; i < argc; ++i) {
|
|
if (std::strcmp(argv[i], "--project") == 0 && i + 1 < argc) {
|
|
customProjectRoot = argv[i + 1];
|
|
++i;
|
|
} else if (std::strcmp(argv[i], "--scene") == 0 && i + 1 < argc) {
|
|
customScenePath = argv[i + 1];
|
|
++i;
|
|
}
|
|
}
|
|
|
|
if (argc > 0) {
|
|
std::filesystem::current_path(std::filesystem::path(argv[0]).parent_path());
|
|
}
|
|
const MetaCorePlayerLaunchConfig launchConfig =
|
|
MetaCoreReadPlayerLaunchConfig(std::filesystem::current_path() / "MetaCorePlayer.config.json");
|
|
std::cout << "MetaCorePlayer: target platform=" << launchConfig.Platform
|
|
<< " graphics=" << launchConfig.GraphicsBackend
|
|
<< " arch=" << launchConfig.Architecture
|
|
<< " config_profile=" << launchConfig.BuildProfile
|
|
<< '\n';
|
|
|
|
MetaCore::MetaCoreWindow window;
|
|
if (!window.Initialize(1280, 720, "MetaCore Player")) {
|
|
std::cerr << "MetaCorePlayer: window initialize failed\n";
|
|
return 1;
|
|
}
|
|
|
|
MetaCore::MetaCoreRenderDevice renderDevice;
|
|
MetaCore::MetaCoreRenderDeviceConfig renderConfig{};
|
|
renderConfig.Backend = MetaCoreSelectRenderBackend(launchConfig);
|
|
if (!renderDevice.Initialize(window, renderConfig)) {
|
|
std::cerr << "MetaCorePlayer: render device initialize failed\n";
|
|
return 1;
|
|
}
|
|
|
|
MetaCore::MetaCorePlayerRenderer playerRenderer;
|
|
if (!playerRenderer.Initialize(renderDevice, window)) {
|
|
std::cerr << "MetaCorePlayer: renderer initialize failed\n";
|
|
return 1;
|
|
}
|
|
|
|
MetaCore::MetaCoreRuntimeUiRenderer runtimeUiRenderer;
|
|
if (!runtimeUiRenderer.Initialize()) {
|
|
std::cerr << "MetaCorePlayer: runtime UI renderer initialize failed\n";
|
|
return 1;
|
|
}
|
|
|
|
std::filesystem::path projectRoot;
|
|
if (!customProjectRoot.empty()) {
|
|
projectRoot = std::filesystem::absolute(customProjectRoot);
|
|
} else {
|
|
const auto discovered = MetaCore::MetaCoreDiscoverProjectRoot();
|
|
if (!discovered.has_value()) {
|
|
std::cerr << "MetaCorePlayer: failed to discover project root\n";
|
|
return 1;
|
|
}
|
|
projectRoot = *discovered;
|
|
}
|
|
const std::filesystem::path projectPath = MetaCore::MetaCoreGetProjectFilePath(projectRoot);
|
|
playerRenderer.SetProjectRootPath(projectRoot);
|
|
MetaCore::MetaCoreTypeRegistry typeRegistry;
|
|
MetaCore::MetaCoreRegisterFoundationGeneratedTypes(typeRegistry);
|
|
MetaCore::MetaCoreRegisterSceneGeneratedTypes(typeRegistry);
|
|
MetaCore::MetaCoreRegisterRuntimeDataGeneratedTypes(typeRegistry);
|
|
const auto projectDocument = MetaCore::MetaCoreReadProjectFile(projectPath);
|
|
std::filesystem::path runtimeDirectoryRelative = projectDocument.has_value()
|
|
? projectDocument->RuntimeDirectory
|
|
: std::filesystem::path("Runtime");
|
|
if (runtimeDirectoryRelative.empty()) {
|
|
runtimeDirectoryRelative = "Runtime";
|
|
}
|
|
if (MetaCore::MetaCoreIsUnsafeRuntimeProjectPath(runtimeDirectoryRelative)) {
|
|
std::cerr << "MetaCorePlayer: project runtime_directory must be project-relative without parent traversal: "
|
|
<< runtimeDirectoryRelative.generic_string() << '\n';
|
|
return 1;
|
|
}
|
|
const std::filesystem::path runtimeDirectory =
|
|
projectRoot / runtimeDirectoryRelative.lexically_normal();
|
|
const auto loadedRuntimeProjectDocument = MetaCore::MetaCoreReadRuntimeProjectDocument(
|
|
runtimeDirectory / "ProjectRuntime.mcruntimecfg",
|
|
typeRegistry
|
|
);
|
|
auto runtimeProjectDocument =
|
|
loadedRuntimeProjectDocument.value_or(MetaCore::MetaCoreBuildDefaultRuntimeProjectDocument(runtimeDirectoryRelative));
|
|
MetaCore::MetaCoreApplyRuntimeProjectDefaults(runtimeProjectDocument, runtimeDirectoryRelative);
|
|
const std::vector<MetaCore::MetaCoreRuntimeConfigIssue> runtimeProjectPathIssues =
|
|
MetaCore::MetaCoreValidateRuntimeProjectPaths(runtimeProjectDocument);
|
|
if (!runtimeProjectPathIssues.empty()) {
|
|
std::cerr << "MetaCorePlayer: Runtime project path validation failed: "
|
|
<< runtimeProjectPathIssues.front().Message << '\n';
|
|
return 1;
|
|
}
|
|
std::cout << "MetaCorePlayer: build profile=" << runtimeProjectDocument.BuildProfileName
|
|
<< " platform=" << runtimeProjectDocument.TargetPlatform
|
|
<< " output=" << runtimeProjectDocument.OutputDirectory.generic_string()
|
|
<< " cooked_assets=" << (runtimeProjectDocument.UseCookedAssets ? "true" : "false")
|
|
<< '\n';
|
|
|
|
std::optional<MetaCore::MetaCoreCookManifestDocument> cookedManifest;
|
|
if (runtimeProjectDocument.UseCookedAssets) {
|
|
const std::filesystem::path manifestPath =
|
|
projectRoot / runtimeProjectDocument.CookedAssetsDirectory / "CookManifest.bin";
|
|
cookedManifest = MetaCoreReadCookManifestDocument(manifestPath, typeRegistry);
|
|
if (cookedManifest.has_value()) {
|
|
std::cout << "MetaCorePlayer: loaded cook manifest "
|
|
<< manifestPath.string()
|
|
<< " entries=" << cookedManifest->Entries.size()
|
|
<< '\n';
|
|
} else {
|
|
std::cerr << "MetaCorePlayer: cooked assets enabled but cook manifest is missing or unreadable: "
|
|
<< manifestPath.string() << '\n';
|
|
}
|
|
}
|
|
|
|
std::optional<MetaCore::MetaCoreSceneDocument> startupSceneDocument;
|
|
if (!customScenePath.empty()) {
|
|
const auto absoluteScene = customScenePath.is_absolute() ? customScenePath : (projectRoot / customScenePath);
|
|
startupSceneDocument = MetaCoreReadSceneDocumentFromPath(absoluteScene, typeRegistry);
|
|
}
|
|
if (!startupSceneDocument.has_value() &&
|
|
cookedManifest.has_value() &&
|
|
!runtimeProjectDocument.StartupScenePath.empty()) {
|
|
startupSceneDocument = MetaCoreReadCookedDocument<MetaCore::MetaCoreSceneDocument>(
|
|
projectRoot,
|
|
*cookedManifest,
|
|
runtimeProjectDocument.StartupScenePath,
|
|
typeRegistry,
|
|
"MetaCoreSceneDocument"
|
|
);
|
|
if (startupSceneDocument.has_value()) {
|
|
std::cout << "MetaCorePlayer: loaded cooked startup scene "
|
|
<< runtimeProjectDocument.StartupScenePath.generic_string()
|
|
<< '\n';
|
|
}
|
|
}
|
|
if (!startupSceneDocument.has_value() && !runtimeProjectDocument.StartupScenePath.empty()) {
|
|
startupSceneDocument =
|
|
MetaCoreReadSceneDocumentFromPath(projectRoot / runtimeProjectDocument.StartupScenePath, typeRegistry);
|
|
}
|
|
if (!startupSceneDocument.has_value()) {
|
|
startupSceneDocument = MetaCore::MetaCoreLoadStartupSceneDocument(projectPath);
|
|
}
|
|
MetaCore::MetaCoreScene scene;
|
|
if (startupSceneDocument.has_value()) {
|
|
if (cookedManifest.has_value()) {
|
|
MetaCoreRewriteSceneGltfModelPathsToCooked(*startupSceneDocument, *cookedManifest);
|
|
}
|
|
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::MetaCoreRuntimeLifecycleExecutor runtimeLifecycleExecutor;
|
|
const std::size_t runtimeComponentCount = runtimeLifecycleExecutor.RegisterDescriptors(
|
|
MetaCore::MetaCoreGetRuntimeLifecycleRegistry().GetDescriptors()
|
|
);
|
|
if (runtimeComponentCount > 0) {
|
|
std::cout << "MetaCorePlayer: registered " << runtimeComponentCount
|
|
<< " runtime lifecycle component(s)\n";
|
|
}
|
|
const auto runtimeLifecycleErrorSink = [](const MetaCore::MetaCoreRuntimeLifecycleError& error) {
|
|
std::cerr << "MetaCorePlayer: lifecycle " << error.Phase
|
|
<< " failed component=" << error.ComponentTypeId
|
|
<< " object=" << error.ObjectId
|
|
<< " error=" << error.Message << '\n';
|
|
};
|
|
runtimeLifecycleExecutor.EnterScene(scene, runtimeLifecycleErrorSink);
|
|
|
|
std::optional<MetaCore::MetaCoreUiDocument> startupUiDocument;
|
|
if (!runtimeProjectDocument.StartupUiPath.empty()) {
|
|
bool startupUiLoadedFromCooked = false;
|
|
if (cookedManifest.has_value()) {
|
|
startupUiDocument = MetaCoreReadCookedDocument<MetaCore::MetaCoreUiDocument>(
|
|
projectRoot,
|
|
*cookedManifest,
|
|
runtimeProjectDocument.StartupUiPath,
|
|
typeRegistry,
|
|
"MetaCoreUiDocument"
|
|
);
|
|
startupUiLoadedFromCooked = startupUiDocument.has_value();
|
|
}
|
|
if (!startupUiDocument.has_value()) {
|
|
const std::filesystem::path absoluteUiPath =
|
|
runtimeProjectDocument.StartupUiPath.is_absolute()
|
|
? runtimeProjectDocument.StartupUiPath
|
|
: (projectRoot / runtimeProjectDocument.StartupUiPath);
|
|
if (std::filesystem::exists(absoluteUiPath)) {
|
|
startupUiDocument = MetaCore::MetaCoreSceneSerializer::LoadUiFromJson(absoluteUiPath, typeRegistry);
|
|
if (!startupUiDocument.has_value()) {
|
|
std::cerr << "MetaCorePlayer: startup UI exists but is unreadable: "
|
|
<< runtimeProjectDocument.StartupUiPath.generic_string()
|
|
<< '\n';
|
|
}
|
|
} else {
|
|
std::cout << "MetaCorePlayer: startup UI not found: "
|
|
<< runtimeProjectDocument.StartupUiPath.generic_string()
|
|
<< '\n';
|
|
}
|
|
}
|
|
if (startupUiDocument.has_value()) {
|
|
const auto compiledUi = MetaCore::MetaCoreCompileUiDocumentToRml(
|
|
*startupUiDocument,
|
|
runtimeProjectDocument.StartupUiPath.filename().string() + ".rcss"
|
|
);
|
|
if (!runtimeUiRenderer.LoadCompiledDocument(compiledUi)) {
|
|
std::cerr << "MetaCorePlayer: startup UI failed to load into runtime renderer error="
|
|
<< runtimeUiRenderer.GetStats().LastError << '\n';
|
|
}
|
|
std::cout << "MetaCorePlayer: loaded "
|
|
<< (startupUiLoadedFromCooked ? "cooked " : "")
|
|
<< "startup UI "
|
|
<< runtimeProjectDocument.StartupUiPath.generic_string()
|
|
<< " nodes=" << startupUiDocument->Nodes.size()
|
|
<< " roots=" << startupUiDocument->RootNodeIds.size()
|
|
<< " rml_bytes=" << compiledUi.Rml.size()
|
|
<< " rcss_bytes=" << compiledUi.Rcss.size()
|
|
<< '\n';
|
|
}
|
|
}
|
|
MetaCore::MetaCoreRuntimeDataDispatcher runtimeDataDispatcher(scene);
|
|
|
|
const auto sourcesPath = (projectRoot / runtimeProjectDocument.DataSourcesPath).lexically_normal();
|
|
const auto bindingsPath = (projectRoot / runtimeProjectDocument.BindingsPath).lexically_normal();
|
|
const auto diagnosticsPath = (projectRoot / runtimeProjectDocument.DiagnosticsPath).lexically_normal();
|
|
const auto loadedSourcesDocument = MetaCore::MetaCoreReadRuntimeDataSourcesDocument(sourcesPath, typeRegistry);
|
|
const auto loadedBindingsDocument = MetaCore::MetaCoreReadRuntimeBindingsDocument(bindingsPath, typeRegistry);
|
|
auto sourcesDocument = loadedSourcesDocument.value_or(MetaCore::MetaCoreRuntimeDataSourcesDocument{});
|
|
const auto bindingsDocument = loadedBindingsDocument.value_or(MetaCore::MetaCoreRuntimeBindingsDocument{});
|
|
|
|
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; continuing without runtime data bindings\n";
|
|
} else {
|
|
std::cout << "MetaCorePlayer: runtime config missing; continuing without runtime data bindings\n";
|
|
}
|
|
}
|
|
if (const auto replayPathError = MetaCoreResolveRuntimeDataReplayFilePaths(sourcesDocument, projectRoot);
|
|
replayPathError.has_value()) {
|
|
std::cerr << "MetaCorePlayer: " << *replayPathError << '\n';
|
|
return 1;
|
|
}
|
|
|
|
runtimeDataDispatcher.SetDataPointDefinitions(sourcesDocument.DataPoints);
|
|
runtimeDataDispatcher.SetBindingDefinitions(bindingsDocument.Bindings);
|
|
std::vector<MetaCore::MetaCoreRuntimeBindingStatus> runtimeUiBindingStatuses =
|
|
MetaCoreBuildRuntimeUiBindingStatuses(bindingsDocument.UiBindings);
|
|
MetaCoreValidateRuntimeUiBindingTargets(bindingsDocument.UiBindings, runtimeUiRenderer, runtimeUiBindingStatuses);
|
|
|
|
std::vector<MetaCoreRuntimeDataSourceInstance> runtimeSources;
|
|
runtimeSources.reserve(sourcesDocument.Sources.size());
|
|
for (const MetaCore::MetaCoreDataSourceDefinition& sourceDefinition : sourcesDocument.Sources) {
|
|
auto runtimeAdapter = MetaCore::MetaCoreCreateRuntimeDataSourceAdapter(sourceDefinition.AdapterType);
|
|
if (runtimeAdapter == nullptr) {
|
|
std::cerr << "MetaCorePlayer: unsupported adapter type "
|
|
<< sourceDefinition.AdapterType
|
|
<< " source=" << sourceDefinition.Id << '\n';
|
|
auto sourceStatuses = MetaCoreCollectRuntimeSourceStatuses(runtimeSources);
|
|
MetaCore::MetaCoreRuntimeDataSourceStatus unsupportedStatus;
|
|
unsupportedStatus.SourceId = sourceDefinition.Id;
|
|
unsupportedStatus.State = MetaCore::MetaCoreRuntimeDataSourceState::Faulted;
|
|
unsupportedStatus.LastError = "Unsupported adapter type: " + sourceDefinition.AdapterType;
|
|
sourceStatuses.push_back(std::move(unsupportedStatus));
|
|
MetaCoreWriteRuntimeDiagnosticsSnapshot(
|
|
diagnosticsPath,
|
|
typeRegistry,
|
|
runtimeDataDispatcher,
|
|
sourceStatuses,
|
|
runtimeUiBindingStatuses
|
|
);
|
|
return 1;
|
|
}
|
|
if (!runtimeAdapter->Configure(sourceDefinition)) {
|
|
std::cerr << "MetaCorePlayer: failed to configure runtime adapter error="
|
|
<< runtimeAdapter->GetStatus().LastError << '\n';
|
|
auto sourceStatuses = MetaCoreCollectRuntimeSourceStatuses(runtimeSources);
|
|
sourceStatuses.push_back(runtimeAdapter->GetStatus());
|
|
MetaCoreWriteRuntimeDiagnosticsSnapshot(
|
|
diagnosticsPath,
|
|
typeRegistry,
|
|
runtimeDataDispatcher,
|
|
sourceStatuses,
|
|
runtimeUiBindingStatuses
|
|
);
|
|
return 1;
|
|
}
|
|
MetaCoreRuntimeDataSourceInstance sourceInstance;
|
|
sourceInstance.Definition = sourceDefinition;
|
|
sourceInstance.LastReportedState = runtimeAdapter->GetStatus().State;
|
|
sourceInstance.Adapter = std::move(runtimeAdapter);
|
|
if (sourceDefinition.AutoConnect && !sourceInstance.Adapter->Connect()) {
|
|
std::cerr << "MetaCorePlayer: failed to connect runtime adapter error="
|
|
<< sourceInstance.Adapter->GetStatus().LastError
|
|
<< " source=" << sourceDefinition.Id << '\n';
|
|
auto sourceStatuses = MetaCoreCollectRuntimeSourceStatuses(runtimeSources);
|
|
sourceStatuses.push_back(sourceInstance.Adapter->GetStatus());
|
|
MetaCoreWriteRuntimeDiagnosticsSnapshot(
|
|
diagnosticsPath,
|
|
typeRegistry,
|
|
runtimeDataDispatcher,
|
|
sourceStatuses,
|
|
runtimeUiBindingStatuses
|
|
);
|
|
return 1;
|
|
}
|
|
sourceInstance.LastReportedState = sourceInstance.Adapter->GetStatus().State;
|
|
runtimeSources.push_back(std::move(sourceInstance));
|
|
}
|
|
|
|
std::uint64_t diagnosticsWriteFrame = 0;
|
|
std::unordered_map<std::string, std::string> reportedBindingIssues;
|
|
bool runtimeStatusUiAvailable =
|
|
!MetaCoreHasRuntimeUiTextBindingForNode(bindingsDocument.UiBindings, "runtime.status");
|
|
if (!runtimeStatusUiAvailable) {
|
|
std::cout << "MetaCorePlayer: runtime.status is controlled by a RuntimeData UI binding\n";
|
|
}
|
|
std::string lastRuntimeStatusText{};
|
|
while (!window.ShouldClose()) {
|
|
window.BeginFrame();
|
|
const auto [windowWidth, windowHeight] = window.GetWindowSize();
|
|
playerRenderer.SetViewportRect(MetaCore::MetaCoreViewportRect{
|
|
0.0F,
|
|
0.0F,
|
|
static_cast<float>(windowWidth),
|
|
static_cast<float>(windowHeight)
|
|
});
|
|
runtimeUiRenderer.Resize(windowWidth, windowHeight);
|
|
runtimeUiRenderer.BeginFrame(1.0F / 60.0F);
|
|
runtimeLifecycleExecutor.TickScene(scene, 1.0F / 60.0F, runtimeLifecycleErrorSink);
|
|
if (!runtimeSources.empty()) {
|
|
std::uint64_t latestUpdateAt = 0;
|
|
for (MetaCoreRuntimeDataSourceInstance& sourceInstance : runtimeSources) {
|
|
sourceInstance.Adapter->Tick(1.0 / 60.0);
|
|
const std::vector<MetaCore::MetaCoreRuntimeDataUpdate> updates =
|
|
sourceInstance.Adapter->PollUpdates();
|
|
runtimeDataDispatcher.ApplyUpdates(updates);
|
|
MetaCoreApplyRuntimeUiBindings(
|
|
updates,
|
|
sourcesDocument.DataPoints,
|
|
bindingsDocument.UiBindings,
|
|
runtimeUiRenderer,
|
|
runtimeUiBindingStatuses
|
|
);
|
|
latestUpdateAt = std::max(latestUpdateAt, sourceInstance.Adapter->GetStatus().LastUpdateAt);
|
|
}
|
|
runtimeDataDispatcher.TickStaleness(latestUpdateAt);
|
|
MetaCoreTickRuntimeBindingStaleness(runtimeUiBindingStatuses, latestUpdateAt);
|
|
}
|
|
std::vector<MetaCore::MetaCoreRuntimeDataSourceStatus> sourceStatuses =
|
|
MetaCoreCollectRuntimeSourceStatuses(runtimeSources);
|
|
auto diagnostics = runtimeDataDispatcher.BuildDiagnosticsSnapshot(sourceStatuses);
|
|
MetaCoreAppendRuntimeUiBindingDiagnostics(diagnostics, runtimeUiBindingStatuses);
|
|
if (runtimeStatusUiAvailable && runtimeUiRenderer.GetStats().Loaded) {
|
|
const std::string runtimeStatusText = MetaCoreBuildRuntimeDataStatusText(diagnostics);
|
|
if (runtimeStatusText != lastRuntimeStatusText) {
|
|
if (runtimeUiRenderer.SetNodeText("runtime.status", runtimeStatusText)) {
|
|
lastRuntimeStatusText = runtimeStatusText;
|
|
} else {
|
|
runtimeStatusUiAvailable = false;
|
|
}
|
|
}
|
|
}
|
|
if ((diagnosticsWriteFrame % 15ULL) == 0ULL) {
|
|
(void)MetaCore::MetaCoreWriteRuntimeDiagnosticsSnapshot(diagnosticsPath, diagnostics, typeRegistry);
|
|
}
|
|
++diagnosticsWriteFrame;
|
|
for (MetaCoreRuntimeDataSourceInstance& sourceInstance : runtimeSources) {
|
|
if (sourceInstance.Adapter->GetStatus().State != sourceInstance.LastReportedState) {
|
|
std::cout << "MetaCorePlayer: data source state changed source="
|
|
<< sourceInstance.Definition.Id
|
|
<< " adapter=" << sourceInstance.Definition.AdapterType
|
|
<< " state=" << static_cast<int>(sourceInstance.Adapter->GetStatus().State)
|
|
<< " error=" << sourceInstance.Adapter->GetStatus().LastError << '\n';
|
|
sourceInstance.LastReportedState = sourceInstance.Adapter->GetStatus().State;
|
|
}
|
|
}
|
|
MetaCoreLogRuntimeBindingIssues(diagnostics, reportedBindingIssues);
|
|
playerRenderer.RenderScene(scene, MetaCoreBuildPlayerSceneView());
|
|
runtimeUiRenderer.Render();
|
|
playerRenderer.SetRuntimeUiOverlayFrame(runtimeUiRenderer.GetLastFrame(), windowWidth, windowHeight);
|
|
playerRenderer.RenderAll();
|
|
renderDevice.RenderFrame();
|
|
renderDevice.PresentFrame();
|
|
window.EndFrame();
|
|
}
|
|
|
|
runtimeLifecycleExecutor.ExitScene(scene, runtimeLifecycleErrorSink);
|
|
runtimeUiRenderer.Shutdown();
|
|
playerRenderer.Shutdown();
|
|
renderDevice.Shutdown();
|
|
window.Shutdown();
|
|
return 0;
|
|
}
|