577 lines
27 KiB
C++
577 lines
27 KiB
C++
#include "MetaCorePlatform/MetaCoreWindow.h"
|
|
#include "MetaCoreDelivery/MetaCoreBuildPipeline.h"
|
|
#include "MetaCoreDelivery/MetaCoreCrashReporter.h"
|
|
#include "MetaCoreFoundation/MetaCoreGeneratedReflection.h"
|
|
#include "MetaCoreFoundation/MetaCoreProject.h"
|
|
#include "MetaCoreRender/MetaCoreEditorViewportRenderer.h"
|
|
#include "MetaCoreRender/MetaCoreRenderDevice.h"
|
|
#include "MetaCoreRender/MetaCoreRenderTypes.h"
|
|
#include "MetaCoreRuntimeData/MetaCoreRuntimeDataDispatcher.h"
|
|
#include "MetaCoreRuntimeData/MetaCoreRuntimeDataProject.h"
|
|
#include "MetaCoreRuntimeData/MetaCoreRuntimeDataSource.h"
|
|
#include "MetaCoreRuntimeUi/MetaCoreRuntimeUiSystem.h"
|
|
#include "MetaCoreScene/MetaCoreScenePackage.h"
|
|
#include "MetaCoreScene/MetaCoreScene.h"
|
|
#include "MetaCoreScripting/MetaCoreScripting.h"
|
|
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <memory>
|
|
#include <unordered_map>
|
|
#include <vector>
|
|
|
|
namespace {
|
|
|
|
class MetaCoreTeeBuffer final : public std::streambuf {
|
|
public:
|
|
MetaCoreTeeBuffer(std::streambuf* first, std::streambuf* second) : First_(first), Second_(second) {}
|
|
protected:
|
|
int overflow(int character) override {
|
|
if (character == traits_type::eof()) return traits_type::not_eof(character);
|
|
const bool firstOk = First_ == nullptr || First_->sputc(static_cast<char>(character)) != traits_type::eof();
|
|
const bool secondOk = Second_ == nullptr || Second_->sputc(static_cast<char>(character)) != traits_type::eof();
|
|
return firstOk && secondOk ? character : traits_type::eof();
|
|
}
|
|
int sync() override {
|
|
return (First_ == nullptr || First_->pubsync() == 0) && (Second_ == nullptr || Second_->pubsync() == 0) ? 0 : -1;
|
|
}
|
|
private:
|
|
std::streambuf* First_ = nullptr;
|
|
std::streambuf* Second_ = nullptr;
|
|
};
|
|
|
|
[[nodiscard]] std::filesystem::path MetaCoreGetExecutableRoot(int argc, char* argv[]) {
|
|
#if defined(__linux__)
|
|
std::error_code error;
|
|
const auto executable = std::filesystem::canonical("/proc/self/exe", error);
|
|
if (!error) return executable.parent_path();
|
|
#endif
|
|
if (argc > 0) {
|
|
std::error_code error;
|
|
return std::filesystem::absolute(argv[0], error).parent_path();
|
|
}
|
|
return std::filesystem::current_path();
|
|
}
|
|
|
|
void MetaCoreRotatePlayerLog(const std::filesystem::path& path) {
|
|
std::error_code error;
|
|
if (std::filesystem::is_regular_file(path, error) && std::filesystem::file_size(path, error) >= 5U * 1024U * 1024U) {
|
|
std::filesystem::rename(path, path.string() + ".1", error);
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
[[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";
|
|
document.UiManifestPath = std::filesystem::path("Runtime") / "Ui.mcruntime";
|
|
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", "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]] std::filesystem::path MetaCoreStripFirstPathComponent(const std::filesystem::path& path) {
|
|
std::filesystem::path stripped;
|
|
auto iterator = path.begin();
|
|
if (iterator == path.end()) {
|
|
return stripped;
|
|
}
|
|
|
|
++iterator;
|
|
for (; iterator != path.end(); ++iterator) {
|
|
stripped /= *iterator;
|
|
}
|
|
return stripped;
|
|
}
|
|
|
|
void MetaCoreResolveRuntimeSourcePathsForProject(
|
|
MetaCore::MetaCoreRuntimeDataSourcesDocument& sourcesDocument,
|
|
const std::filesystem::path& projectRoot
|
|
) {
|
|
for (MetaCore::MetaCoreDataSourceDefinition& source : sourcesDocument.Sources) {
|
|
if (source.AdapterType != "file_replay") {
|
|
continue;
|
|
}
|
|
|
|
for (MetaCore::MetaCoreDataSourceSetting& setting : source.ConnectionSettings) {
|
|
if (setting.Key != "file_path" || setting.Value.empty()) {
|
|
continue;
|
|
}
|
|
|
|
const std::filesystem::path configuredPath(setting.Value);
|
|
if (configuredPath.is_absolute()) {
|
|
continue;
|
|
}
|
|
|
|
std::vector<std::filesystem::path> candidates;
|
|
candidates.push_back(projectRoot / configuredPath);
|
|
candidates.push_back(projectRoot.parent_path() / configuredPath);
|
|
candidates.push_back(projectRoot / "Runtime" / configuredPath.filename());
|
|
|
|
const std::filesystem::path strippedPath = MetaCoreStripFirstPathComponent(configuredPath);
|
|
if (!strippedPath.empty()) {
|
|
candidates.push_back(projectRoot / strippedPath);
|
|
}
|
|
|
|
for (const std::filesystem::path& candidate : candidates) {
|
|
std::error_code errorCode;
|
|
if (!std::filesystem::exists(candidate, errorCode)) {
|
|
continue;
|
|
}
|
|
|
|
const std::filesystem::path canonicalCandidate = std::filesystem::weakly_canonical(candidate, errorCode);
|
|
setting.Value = errorCode ? candidate.lexically_normal().string() : canonicalCandidate.string();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
[[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
|
|
|
|
int main(int argc, char* argv[]) {
|
|
std::filesystem::path customProjectRoot{};
|
|
std::filesystem::path customScenePath{};
|
|
std::filesystem::path runtimeProjectPath{};
|
|
bool validateOnly = false;
|
|
std::uint64_t smokeTestFrames = 0U;
|
|
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;
|
|
} else if (std::strcmp(argv[i], "--runtime-project") == 0 && i + 1 < argc) {
|
|
runtimeProjectPath = argv[++i];
|
|
} else if (std::strcmp(argv[i], "--validate-only") == 0) {
|
|
validateOnly = true;
|
|
} else if (std::strcmp(argv[i], "--smoke-test-frames") == 0 && i + 1 < argc) {
|
|
smokeTestFrames = static_cast<std::uint64_t>(std::strtoull(argv[++i], nullptr, 10));
|
|
}
|
|
}
|
|
|
|
const std::filesystem::path executableRoot = MetaCoreGetExecutableRoot(argc, argv);
|
|
if (runtimeProjectPath.empty() && customProjectRoot.empty() &&
|
|
std::filesystem::is_regular_file(executableRoot / "Project" / "MetaCore.runtimeproject")) {
|
|
runtimeProjectPath = executableRoot / "Project" / "MetaCore.runtimeproject";
|
|
}
|
|
std::optional<MetaCore::MetaCoreRuntimeDeliveryDocument> deliveryDocument;
|
|
std::filesystem::path packageRoot;
|
|
if (!runtimeProjectPath.empty()) {
|
|
runtimeProjectPath = runtimeProjectPath.is_absolute() ? runtimeProjectPath : std::filesystem::absolute(runtimeProjectPath);
|
|
packageRoot = runtimeProjectPath.parent_path().parent_path();
|
|
MetaCore::MetaCoreBuildIssue issue;
|
|
deliveryDocument = MetaCore::MetaCoreReadRuntimeDeliveryDocument(runtimeProjectPath, &issue);
|
|
if (!deliveryDocument) {
|
|
std::cerr << MetaCore::MetaCoreBuildErrorCodeName(issue.Code) << ": " << issue.Message << " path=" << issue.Path << '\n';
|
|
return 2;
|
|
}
|
|
const auto validationIssues = MetaCore::MetaCoreValidateReleasePackage(packageRoot);
|
|
if (!validationIssues.empty()) {
|
|
for (const auto& validationIssue : validationIssues) {
|
|
std::cerr << MetaCore::MetaCoreBuildErrorCodeName(validationIssue.Code) << ": " << validationIssue.Message
|
|
<< " path=" << validationIssue.Path << '\n';
|
|
}
|
|
return 2;
|
|
}
|
|
if (validateOnly) {
|
|
std::cout << "MetaCorePlayer: delivery package validation passed build_id=" << deliveryDocument->BuildId << '\n';
|
|
return 0;
|
|
}
|
|
} else if (validateOnly) {
|
|
std::cerr << "MCB1001: --validate-only requires a packaged runtime project\n";
|
|
return 2;
|
|
}
|
|
|
|
std::filesystem::current_path(deliveryDocument ? packageRoot : executableRoot);
|
|
const auto diagnosticsRoot = deliveryDocument ? packageRoot / "Diagnostics" : executableRoot / "Diagnostics";
|
|
std::error_code logError;
|
|
std::filesystem::create_directories(diagnosticsRoot / "Logs", logError);
|
|
const auto logPath = diagnosticsRoot / "Logs" / "MetaCorePlayer.log";
|
|
MetaCoreRotatePlayerLog(logPath);
|
|
std::ofstream logFile(logPath, std::ios::app);
|
|
MetaCoreTeeBuffer coutBuffer(std::cout.rdbuf(), logFile.rdbuf());
|
|
MetaCoreTeeBuffer cerrBuffer(std::cerr.rdbuf(), logFile.rdbuf());
|
|
std::ostream output(&coutBuffer);
|
|
std::ostream errors(&cerrBuffer);
|
|
output << "MetaCorePlayer: start version=1.0.0 platform=Linux-x86_64 build_id="
|
|
<< (deliveryDocument ? deliveryDocument->BuildId : "development") << '\n';
|
|
if (!MetaCore::MetaCoreInitializeCrashReporter(
|
|
executableRoot,
|
|
diagnosticsRoot,
|
|
deliveryDocument ? deliveryDocument->BuildId : "development")) {
|
|
output << "MetaCorePlayer: Crashpad handler unavailable; Minidump capture disabled\n";
|
|
}
|
|
|
|
MetaCore::MetaCoreWindow window;
|
|
if (!window.Initialize(1280, 720, "MetaCore Player")) {
|
|
errors << "MetaCorePlayer: window initialize failed\n";
|
|
return 1;
|
|
}
|
|
|
|
MetaCore::MetaCoreRenderDevice renderDevice;
|
|
if (!renderDevice.Initialize(window)) {
|
|
errors << "MetaCorePlayer: render device initialize failed\n";
|
|
return 1;
|
|
}
|
|
|
|
MetaCore::MetaCoreEditorViewportRenderer viewportRenderer;
|
|
if (!viewportRenderer.Initialize(renderDevice, window, false)) {
|
|
errors << "MetaCorePlayer: viewport renderer initialize failed\n";
|
|
return 1;
|
|
}
|
|
|
|
std::filesystem::path projectRoot;
|
|
if (deliveryDocument) {
|
|
projectRoot = packageRoot / deliveryDocument->ContentRoot;
|
|
} else if (!customProjectRoot.empty()) {
|
|
projectRoot = std::filesystem::absolute(customProjectRoot);
|
|
} else {
|
|
const auto discovered = MetaCore::MetaCoreDiscoverProjectRoot();
|
|
if (!discovered.has_value()) {
|
|
errors << "MetaCorePlayer: failed to discover project root\n";
|
|
return 1;
|
|
}
|
|
projectRoot = *discovered;
|
|
}
|
|
const std::filesystem::path projectPath = MetaCore::MetaCoreGetProjectFilePath(projectRoot);
|
|
viewportRenderer.SetProjectRootPath(projectRoot);
|
|
MetaCore::MetaCoreTypeRegistry typeRegistry;
|
|
MetaCore::MetaCoreRegisterRuntimeDataGeneratedTypes(typeRegistry);
|
|
const auto loadedRuntimeProjectDocument = MetaCore::MetaCoreReadRuntimeProjectDocument(
|
|
deliveryDocument ? packageRoot / deliveryDocument->RuntimeConfig : projectRoot / "Runtime" / "ProjectRuntime.mcruntimecfg",
|
|
typeRegistry
|
|
);
|
|
const auto runtimeProjectDocument = loadedRuntimeProjectDocument.value_or(MetaCoreBuildDefaultRuntimeProjectDocument());
|
|
|
|
std::optional<MetaCore::MetaCoreSceneDocument> startupSceneDocument;
|
|
if (!customScenePath.empty()) {
|
|
const auto absoluteScene = customScenePath.is_absolute() ? customScenePath : (projectRoot / customScenePath);
|
|
startupSceneDocument = MetaCore::MetaCoreReadScenePackage(absoluteScene);
|
|
}
|
|
if (!startupSceneDocument.has_value() && deliveryDocument) {
|
|
startupSceneDocument = MetaCore::MetaCoreReadScenePackage(packageRoot / deliveryDocument->StartupScene);
|
|
}
|
|
if (!startupSceneDocument.has_value() && !deliveryDocument && !runtimeProjectDocument.StartupScenePath.empty()) {
|
|
startupSceneDocument = MetaCore::MetaCoreReadScenePackage(projectRoot / runtimeProjectDocument.StartupScenePath);
|
|
}
|
|
if (!startupSceneDocument.has_value() && !deliveryDocument) {
|
|
startupSceneDocument = MetaCore::MetaCoreLoadStartupSceneDocument(projectPath);
|
|
}
|
|
MetaCore::MetaCoreScene scene;
|
|
if (startupSceneDocument.has_value()) {
|
|
MetaCore::MetaCoreSceneSnapshot snapshot;
|
|
snapshot.GameObjects = startupSceneDocument->GameObjects;
|
|
scene.RestoreSnapshot(snapshot);
|
|
output << "MetaCorePlayer: loaded startup scene from project "
|
|
<< projectPath.string() << '\n';
|
|
} else if (!deliveryDocument) {
|
|
scene = MetaCore::MetaCoreCreateDefaultScene();
|
|
output << "MetaCorePlayer: startup scene unavailable, using built-in default scene\n";
|
|
} else {
|
|
errors << "MCB6004: packaged startup scene is missing or unreadable path=" << deliveryDocument->StartupScene << '\n';
|
|
return 2;
|
|
}
|
|
MetaCore::MetaCoreRuntimeDataDispatcher runtimeDataDispatcher(scene);
|
|
MetaCore::MetaCoreScriptRegistry scriptRegistry;
|
|
MetaCore::MetaCoreRegisterBuiltinScripts(scriptRegistry);
|
|
MetaCore::MetaCoreScriptModuleLoader scriptModuleLoader;
|
|
std::vector<MetaCore::MetaCoreLoadedScriptModule> loadedScriptModules;
|
|
std::vector<std::filesystem::path> scriptModulePaths;
|
|
if (deliveryDocument) {
|
|
for (const auto& relative : deliveryDocument->ScriptModules) scriptModulePaths.push_back(packageRoot / relative);
|
|
} else if (const auto latest = MetaCore::MetaCoreFindLatestScriptModule(projectRoot); latest.has_value()) {
|
|
scriptModulePaths.push_back(*latest);
|
|
}
|
|
for (const auto& modulePath : scriptModulePaths) {
|
|
std::string scriptError;
|
|
auto module = scriptModuleLoader.LoadCandidate(modulePath, &scriptError);
|
|
if (!module.has_value() || !scriptRegistry.ReplaceModule(module->ModuleId, module->Definitions, &scriptError)) {
|
|
errors << "MetaCorePlayer: script module load failed path=" << modulePath << " error=" << scriptError << '\n';
|
|
if (deliveryDocument) return 2;
|
|
continue;
|
|
}
|
|
output << "MetaCorePlayer: loaded script module id=" << module->ModuleId << " build_id=" << module->BuildId << '\n';
|
|
loadedScriptModules.push_back(std::move(*module));
|
|
}
|
|
bool missingRequiredScript = false;
|
|
for (auto object : scene.GetGameObjects()) {
|
|
if (!object.HasComponent<MetaCore::MetaCoreScriptComponent>()) continue;
|
|
for (const auto& instance : object.GetComponent<MetaCore::MetaCoreScriptComponent>().Instances) {
|
|
if (!instance.ScriptTypeId.empty() && scriptRegistry.FindScript(instance.ScriptTypeId) == nullptr) {
|
|
errors << "MetaCorePlayer: missing script type=" << instance.ScriptTypeId << " object=" << object.GetId() << '\n';
|
|
missingRequiredScript = true;
|
|
}
|
|
}
|
|
}
|
|
if (deliveryDocument && missingRequiredScript) return 2;
|
|
MetaCore::MetaCoreScriptRuntime scriptRuntime(
|
|
scene, scriptRegistry,
|
|
[&output, &errors](std::uint32_t level, std::string_view category, std::string_view message) {
|
|
std::ostream& stream = level >= 2U ? errors : output;
|
|
stream << "MetaCorePlayer Script[" << category << "]: " << message << '\n';
|
|
}
|
|
);
|
|
scriptRuntime.Start();
|
|
|
|
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(MetaCoreBuildDefaultRuntimeDataSourcesDocument());
|
|
auto bindingsDocument = loadedBindingsDocument.value_or(MetaCoreBuildDefaultRuntimeBindingsDocument());
|
|
MetaCoreResolveRuntimeSourcePathsForProject(sourcesDocument, projectRoot);
|
|
|
|
const std::filesystem::path uiManifestPath = (projectRoot / runtimeProjectDocument.UiManifestPath).lexically_normal();
|
|
const auto loadedUiManifest = runtimeProjectDocument.UiManifestPath.empty()
|
|
? std::optional<MetaCore::MetaCoreRuntimeUiManifest>{}
|
|
: MetaCore::MetaCoreReadRuntimeUiManifest(uiManifestPath, typeRegistry);
|
|
MetaCore::MetaCoreRuntimeUiSystem runtimeUi;
|
|
if (loadedUiManifest.has_value()) {
|
|
if (!runtimeUi.Initialize(window, projectRoot, projectRoot / "Ui", *loadedUiManifest)) {
|
|
errors << "MetaCorePlayer: failed to initialize runtime UI\n";
|
|
} else {
|
|
runtimeUi.AttachToViewportRenderer(viewportRenderer);
|
|
}
|
|
for (const std::string& error : runtimeUi.GetErrors()) {
|
|
errors << "MetaCorePlayer UI: " << error << '\n';
|
|
}
|
|
}
|
|
|
|
if (loadedSourcesDocument.has_value() && loadedBindingsDocument.has_value()) {
|
|
output << "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())) {
|
|
errors << "MetaCorePlayer: runtime config exists but is unreadable or corrupted"
|
|
<< (deliveryDocument ? "\n" : ", using built-in fallback config\n");
|
|
if (deliveryDocument) return 2;
|
|
} else {
|
|
if (deliveryDocument) {
|
|
errors << "MCB6004: packaged runtime configuration is missing\n";
|
|
return 2;
|
|
}
|
|
output << "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) {
|
|
errors << "MetaCorePlayer: unsupported adapter type "
|
|
<< sourcesDocument.Sources.front().AdapterType << '\n';
|
|
return 1;
|
|
}
|
|
if (!runtimeAdapter->Configure(sourcesDocument.Sources.front())) {
|
|
errors << "MetaCorePlayer: failed to configure runtime adapter error="
|
|
<< runtimeAdapter->GetStatus().LastError << '\n';
|
|
return 1;
|
|
}
|
|
if (!runtimeAdapter->Connect()) {
|
|
errors << "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::unordered_map<std::string, std::string> lastReportedBindingIssueKeys;
|
|
|
|
std::uint64_t diagnosticsWriteFrame = 0;
|
|
std::uint64_t renderedFrames = 0U;
|
|
while (!window.ShouldClose()) {
|
|
window.BeginFrame();
|
|
const double frameDeltaSeconds = window.GetDeltaSeconds();
|
|
scriptRuntime.FixedUpdate(1.0 / 60.0);
|
|
scriptRuntime.Update(frameDeltaSeconds);
|
|
const auto [windowWidth, windowHeight] = window.GetWindowSize();
|
|
viewportRenderer.SetViewportRect(MetaCore::MetaCoreViewportRect{
|
|
0.0F,
|
|
0.0F,
|
|
static_cast<float>(windowWidth),
|
|
static_cast<float>(windowHeight)
|
|
});
|
|
if (runtimeAdapter != nullptr) {
|
|
runtimeAdapter->Tick(1.0 / 60.0);
|
|
const std::vector<MetaCore::MetaCoreRuntimeDataUpdate> updates = runtimeAdapter->PollUpdates();
|
|
runtimeDataDispatcher.ApplyUpdates(updates);
|
|
runtimeUi.ApplyDataUpdates(updates);
|
|
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) {
|
|
output << "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) {
|
|
const std::string issueKey =
|
|
std::string(bindingStatus.Healthy ? "healthy" : "fault") +
|
|
"|" +
|
|
(bindingStatus.Stale ? "stale" : "fresh") +
|
|
"|" +
|
|
bindingStatus.LastError;
|
|
const auto previousIssue = lastReportedBindingIssueKeys.find(bindingStatus.BindingId);
|
|
if (previousIssue == lastReportedBindingIssueKeys.end() ||
|
|
previousIssue->second != issueKey) {
|
|
output << "MetaCorePlayer: binding issue id=" << bindingStatus.BindingId
|
|
<< " stale=" << (bindingStatus.Stale ? "true" : "false")
|
|
<< " error=" << bindingStatus.LastError << '\n';
|
|
lastReportedBindingIssueKeys[bindingStatus.BindingId] = issueKey;
|
|
}
|
|
} else {
|
|
const auto previousIssue = lastReportedBindingIssueKeys.find(bindingStatus.BindingId);
|
|
if (previousIssue != lastReportedBindingIssueKeys.end()) {
|
|
output << "MetaCorePlayer: binding recovered id=" << bindingStatus.BindingId << '\n';
|
|
lastReportedBindingIssueKeys.erase(previousIssue);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
runtimeUi.Update(window.GetDeltaSeconds());
|
|
scriptRuntime.LateUpdate(frameDeltaSeconds);
|
|
viewportRenderer.RenderSceneToViewport(scene, MetaCoreBuildPlayerSceneView(), true);
|
|
viewportRenderer.RenderAll();
|
|
renderDevice.RenderFrame();
|
|
renderDevice.PresentFrame();
|
|
window.EndFrame();
|
|
++renderedFrames;
|
|
if (smokeTestFrames > 0U && renderedFrames >= smokeTestFrames) break;
|
|
}
|
|
|
|
scriptRuntime.Stop();
|
|
runtimeUi.Shutdown();
|
|
viewportRenderer.Shutdown();
|
|
renderDevice.Shutdown();
|
|
window.Shutdown();
|
|
return 0;
|
|
}
|