MetaCore/Apps/MetaCorePlayer/main.cpp
2026-07-22 16:08:48 +08:00

750 lines
40 KiB
C++

#include "MetaCorePlatform/MetaCoreWindow.h"
#include "MetaCorePhysics/MetaCorePhysics.h"
#include "MetaCoreDelivery/MetaCoreBuildPipeline.h"
#include "MetaCoreDelivery/MetaCoreCrashReporter.h"
#include "MetaCoreFoundation/MetaCoreGeneratedReflection.h"
#include "MetaCoreFoundation/MetaCoreArchive.h"
#include "MetaCoreFoundation/MetaCoreAssetTypes.h"
#include "MetaCoreFoundation/MetaCorePackage.h"
#include "MetaCoreFoundation/MetaCoreProject.h"
#include "MetaCoreRender/MetaCoreEditorViewportRenderer.h"
#include "MetaCoreRender/MetaCoreRenderDevice.h"
#include "MetaCoreRender/MetaCoreRenderTypes.h"
#include "MetaCoreRender/MetaCoreSceneRenderSync.h"
#include "MetaCoreRendering/MetaCoreRendering.h"
#include "MetaCoreRuntimeData/MetaCoreRuntimeDataDispatcher.h"
#include "MetaCoreRuntimeData/MetaCoreRuntimeDataProject.h"
#include "MetaCoreRuntimeData/MetaCoreRuntimeDataSource.h"
#include "MetaCoreRuntimeUi/MetaCoreRuntimeUiSystem.h"
#include "MetaCoreRuntimeInput/MetaCoreRuntimeInput.h"
#include "MetaCoreRuntimeInput/MetaCoreRuntimeInteraction.h"
#include "MetaCoreScene/MetaCoreScenePackage.h"
#include "MetaCoreScene/MetaCoreScene.h"
#include "MetaCoreScripting/MetaCoreScripting.h"
#include <filesystem>
#include <algorithm>
#include <cstring>
#include <fstream>
#include <iostream>
#include <memory>
#include <unordered_map>
#include <vector>
namespace {
struct MetaCorePackedPhysicsVertex { float px, py, pz, nx, ny, nz, u, v; };
std::unordered_map<MetaCore::MetaCoreAssetGuid, MetaCore::MetaCorePhysicsMeshData, MetaCore::MetaCoreAssetGuidHasher>
MetaCoreLoadPhysicsMeshes(const std::filesystem::path& root, const MetaCore::MetaCoreTypeRegistry& registry) {
std::unordered_map<MetaCore::MetaCoreAssetGuid, MetaCore::MetaCorePhysicsMeshData, MetaCore::MetaCoreAssetGuidHasher> result;
std::error_code error;
if (!std::filesystem::is_directory(root, error)) return result;
const auto collisionRoot = root / "Physics";
if (std::filesystem::is_directory(collisionRoot, error)) {
for (const auto& entry : std::filesystem::directory_iterator(collisionRoot, error)) {
if (error || !entry.is_regular_file() || entry.path().extension() != ".mccollision") continue;
const auto guid = MetaCore::MetaCoreAssetGuid::Parse(entry.path().stem().string());
if (!guid) continue;
std::ifstream input(entry.path(), std::ios::binary | std::ios::ate);
if (!input) continue;
const auto size = input.tellg();
if (size <= 0) continue;
std::vector<std::byte> bytes(static_cast<std::size_t>(size));
input.seekg(0, std::ios::beg);
input.read(reinterpret_cast<char*>(bytes.data()), size);
if (!input) continue;
if (auto collision = MetaCore::MetaCoreDecodePhysicsCollisionDerivedData(bytes))
result.insert_or_assign(*guid, std::move(*collision));
}
}
for (const auto& entry : std::filesystem::recursive_directory_iterator(root, error)) {
if (error || !entry.is_regular_file() || entry.path().extension() != ".mcasset") continue;
const auto package = MetaCore::MetaCoreReadPackageFile(entry.path(), registry);
if (!package || package->PayloadSections.empty()) continue;
MetaCore::MetaCoreModelAssetDocument model;
if (!MetaCore::MetaCoreDeserializeFromBytes(package->PayloadSections.front(), model, registry)) continue;
for (const auto& mesh : model.GeneratedMeshAssets) {
if (!mesh.AssetGuid.IsValid() || mesh.PayloadIndex >= package->PayloadSections.size()) continue;
const auto& payload = package->PayloadSections[static_cast<std::size_t>(mesh.PayloadIndex)];
if (payload.size() < sizeof(std::uint32_t) * 2U) continue;
std::uint32_t vertexCount = 0, indexCount = 0;
std::memcpy(&vertexCount, payload.data(), sizeof(vertexCount));
std::memcpy(&indexCount, payload.data() + sizeof(vertexCount), sizeof(indexCount));
const std::size_t required = sizeof(std::uint32_t) * 2U + sizeof(MetaCorePackedPhysicsVertex) * vertexCount + sizeof(std::uint32_t) * indexCount;
if (vertexCount == 0U || indexCount < 3U || required > payload.size()) continue;
MetaCore::MetaCorePhysicsMeshData data; data.Positions.resize(vertexCount); data.Indices.resize(indexCount);
const std::byte* cursor = payload.data() + sizeof(std::uint32_t) * 2U;
for (std::uint32_t index = 0; index < vertexCount; ++index) {
MetaCorePackedPhysicsVertex vertex{}; std::memcpy(&vertex, cursor, sizeof(vertex)); cursor += sizeof(vertex);
data.Positions[index] = {vertex.px, vertex.pz, -vertex.py};
}
std::memcpy(data.Indices.data(), cursor, sizeof(std::uint32_t) * indexCount);
result.try_emplace(mesh.AssetGuid, std::move(data));
}
}
return result;
}
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";
document.InputMapPath = std::filesystem::path("Runtime") / "Input.mcruntime";
document.PhysicsSettingsPath = std::filesystem::path("Runtime") / "Physics.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::string requestedRenderDebugView{};
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));
} else if (std::strcmp(argv[i], "--render-debug") == 0 && i + 1 < argc) {
requestedRenderDebugView = argv[++i];
}
}
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;
}
if(!requestedRenderDebugView.empty()){
bool developmentPlayer=!deliveryDocument.has_value();
if(deliveryDocument&&std::filesystem::is_regular_file(packageRoot/"Manifest"/"MetaCore.release.json")){
MetaCore::MetaCoreBuildIssue manifestIssue;const auto manifest=MetaCore::MetaCoreReadReleaseManifest(packageRoot/"Manifest"/"MetaCore.release.json",&manifestIssue);
developmentPlayer=manifest&&manifest->Configuration!="Release";
}
const auto debugView=MetaCore::MetaCoreParseRenderDebugView(requestedRenderDebugView);
if(developmentPlayer&&debugView)viewportRenderer.SetRenderDebugView(*debugView);
else std::cerr<<"Render debug view rejected; mode="<<requestedRenderDebugView<<"; fallback=lit; reason="<<(developmentPlayer?"unknown mode":"formal Release Player")<<'\n';
}
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::MetaCoreRegisterFoundationGeneratedTypes(typeRegistry);
MetaCore::MetaCoreRegisterRenderingGeneratedTypes(typeRegistry);
MetaCore::MetaCoreRegisterRuntimeDataGeneratedTypes(typeRegistry);
MetaCore::MetaCoreRegisterRuntimeInputGeneratedTypes(typeRegistry);
MetaCore::MetaCoreRegisterPhysicsGeneratedTypes(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;
const auto loadedInputMap = runtimeProjectDocument.InputMapPath.empty() ? std::optional<MetaCore::MetaCoreInputMapDocument>{} :
MetaCore::MetaCoreReadInputMap((projectRoot / runtimeProjectDocument.InputMapPath).lexically_normal(), typeRegistry);
MetaCore::MetaCoreRuntimeInput runtimeInput;
runtimeInput.SetInputMap(loadedInputMap.value_or(MetaCore::MetaCoreInputMapDocument{}));
const auto projectDocument = MetaCore::MetaCoreReadProjectFile(projectPath);
const std::string inputProjectName = projectDocument ? projectDocument->Name : projectRoot.filename().string();
const auto inputOverridePath = MetaCore::MetaCoreGetInputOverridePath(inputProjectName);
(void)runtimeInput.LoadOverrides(inputOverridePath);
for (const auto& warning : runtimeInput.GetWarnings()) errors << "MetaCorePlayer Input: " << warning << '\n';
const auto physicsSettingsPath = projectRoot / (runtimeProjectDocument.PhysicsSettingsPath.empty()
? std::filesystem::path("Runtime/Physics.mcruntime") : runtimeProjectDocument.PhysicsSettingsPath);
auto physicsSettings = MetaCore::MetaCoreReadPhysicsSettings(physicsSettingsPath, typeRegistry)
.value_or(MetaCore::MetaCoreBuildDefaultPhysicsSettings());
std::unordered_map<MetaCore::MetaCoreAssetGuid, MetaCore::MetaCorePhysicsMaterialDocument, MetaCore::MetaCoreAssetGuidHasher> physicsMaterials;
std::error_code physicsAssetError;
const auto physicsAssetsRoot=projectRoot/"Assets";
if(std::filesystem::is_directory(physicsAssetsRoot,physicsAssetError))for(const auto& entry:std::filesystem::recursive_directory_iterator(physicsAssetsRoot,physicsAssetError))if(entry.is_regular_file()&&entry.path().extension()==".mcphysicsmaterial")if(auto material=MetaCore::MetaCoreReadPhysicsMaterial(entry.path(),typeRegistry))physicsMaterials.insert_or_assign(material->AssetGuid,*material);
auto physicsMeshes = MetaCoreLoadPhysicsMeshes(projectRoot, typeRegistry);
MetaCore::MetaCorePhysicsWorld physicsWorld(scene, physicsSettings,
[&physicsMeshes](MetaCore::MetaCoreAssetGuid guid)->std::optional<MetaCore::MetaCorePhysicsMeshData>{const auto it=physicsMeshes.find(guid);return it==physicsMeshes.end()?std::nullopt:std::optional<MetaCore::MetaCorePhysicsMeshData>(it->second);},
[&physicsMaterials](MetaCore::MetaCoreAssetGuid guid)->std::optional<MetaCore::MetaCorePhysicsMaterialDocument>{const auto it=physicsMaterials.find(guid);return it==physicsMaterials.end()?std::nullopt:std::optional<MetaCore::MetaCorePhysicsMaterialDocument>(it->second);});
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';
}, &runtimeInput, &physicsWorld
);
scriptRuntime.Start();
physicsWorld.SetEventCallback([&scriptRuntime](const MetaCore::MetaCorePhysicsContactEvent& event) {
const auto topic = [&]() -> const char* {
using E = MetaCore::MetaCorePhysicsEventType;
switch (event.Type) {
case E::CollisionEnter: return "MetaCore.Physics.CollisionEnter";
case E::CollisionStay: return "MetaCore.Physics.CollisionStay";
case E::CollisionExit: return "MetaCore.Physics.CollisionExit";
case E::TriggerEnter: return "MetaCore.Physics.TriggerEnter";
case E::TriggerStay: return "MetaCore.Physics.TriggerStay";
case E::TriggerExit: return "MetaCore.Physics.TriggerExit";
case E::ConstraintBroken: return "MetaCore.Physics.ConstraintBroken";
}
return "MetaCore.Physics";
}();
const auto objectValue = [&scriptRuntime](MetaCore::MetaCoreId id) { MetaCoreScriptValueV1 value{}; value.Type = MetaCoreScriptAbiValue_GameObjectRef; value.ObjectValue = {scriptRuntime.GetWorldGeneration(), id}; return value; };
const auto vectorValue = [](glm::vec3 source) { MetaCoreScriptValueV1 value{}; value.Type = MetaCoreScriptAbiValue_Vec3; value.Vec3[0]=source.x; value.Vec3[1]=source.y; value.Vec3[2]=source.z; return value; };
MetaCoreScriptValueV1 impulse{}; impulse.Type = MetaCoreScriptAbiValue_Float; impulse.NumberValue = event.Impulse;
scriptRuntime.PublishToObject(event.ObjectA, topic, {objectValue(event.ObjectB), objectValue(event.ColliderA), objectValue(event.ColliderB), vectorValue(event.Point), vectorValue(event.Normal), impulse});
scriptRuntime.PublishToObject(event.ObjectB, topic, {objectValue(event.ObjectA), objectValue(event.ColliderB), objectValue(event.ColliderA), vectorValue(event.Point), vectorValue(-event.Normal), impulse});
});
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;
MetaCore::MetaCoreRuntimeInteraction runtimeInteraction;
const std::uint64_t inputSubscription = runtimeInput.Subscribe([&scriptRuntime](const MetaCore::MetaCoreInputActionEvent& event) {
MetaCoreScriptValueV1 phase{}; phase.Type = MetaCoreScriptAbiValue_Int; phase.IntValue = static_cast<std::int64_t>(event.Phase);
scriptRuntime.Publish("MetaCore.Input.Action/" + event.ActionId, {phase});
});
runtimeUi.SetEventCallback([&scriptRuntime](const MetaCore::MetaCoreRuntimeUiEvent& event) {
const auto textValue = [](const std::string& text) {
MetaCoreScriptValueV1 value{}; value.Type = MetaCoreScriptAbiValue_String;
std::snprintf(value.Text, sizeof(value.Text), "%s", text.c_str()); return value;
};
scriptRuntime.Publish("MetaCore.UI", {
textValue(event.DocumentId), textValue(event.ElementId), textValue(event.Action),
textValue(event.Type), textValue(event.Value)
});
});
runtimeInteraction.SetEventCallback([&scriptRuntime](const MetaCore::MetaCorePointerEvent& event) {
MetaCoreScriptValueV1 type{}; type.Type = MetaCoreScriptAbiValue_Int; type.IntValue = static_cast<std::int64_t>(event.Type);
MetaCoreScriptValueV1 button{}; button.Type = MetaCoreScriptAbiValue_Int; button.IntValue = event.Button;
MetaCoreScriptValueV1 screen{}; screen.Type = MetaCoreScriptAbiValue_Vec3; screen.Vec3[0] = event.ScreenPosition.x; screen.Vec3[1] = event.ScreenPosition.y;
MetaCoreScriptValueV1 delta{}; delta.Type = MetaCoreScriptAbiValue_Vec3; delta.Vec3[0] = event.ScreenDelta.x; delta.Vec3[1] = event.ScreenDelta.y;
MetaCoreScriptValueV1 world{}; world.Type = MetaCoreScriptAbiValue_Vec3; world.Vec3[0] = event.WorldPosition.x; world.Vec3[1] = event.WorldPosition.y; world.Vec3[2] = event.WorldPosition.z;
MetaCoreScriptValueV1 target{}; target.Type = MetaCoreScriptAbiValue_GameObjectRef;
target.ObjectValue = {scriptRuntime.GetWorldGeneration(), event.TargetObjectId};
scriptRuntime.PublishToObject(event.TargetObjectId, "MetaCore.Interaction.Pointer", {type, button, screen, delta, world, target});
});
std::uint64_t diagnosticsWriteFrame = 0;
std::uint64_t renderedFrames = 0U;
double fixedAccumulatorSeconds = 0.0;
MetaCore::MetaCoreSceneRenderSync interactionCameraSync;
while (!window.ShouldClose()) {
window.BeginFrame();
const double frameDeltaSeconds = window.GetDeltaSeconds();
const MetaCore::MetaCoreInputConsumption uiConsumption = runtimeUi.ProcessInput();
runtimeInput.Update(window.GetInput(), uiConsumption);
const auto [inputWidth, inputHeight] = window.GetWindowSize();
const MetaCore::MetaCoreViewportRect inputViewport{0.0F, 0.0F, static_cast<float>(inputWidth), static_cast<float>(inputHeight)};
MetaCore::MetaCoreSceneView interactionSceneView = MetaCoreBuildPlayerSceneView();
const auto interactionSnapshot = interactionCameraSync.BuildSnapshot(scene);
(void)MetaCore::MetaCoreSceneRenderSync::TryBuildSceneViewFromPrimaryCamera(interactionSnapshot, interactionSceneView);
runtimeInteraction.Update(scene, viewportRenderer, physicsWorld, interactionSceneView,
window.GetInput(), inputViewport, uiConsumption.Mouse);
fixedAccumulatorSeconds += std::clamp(frameDeltaSeconds, 0.0, physicsSettings.MaxFrameDelta);
std::uint32_t fixedSteps = 0;
while (fixedAccumulatorSeconds >= physicsSettings.FixedTimeStep && fixedSteps < physicsSettings.MaxFixedStepsPerFrame) {
scriptRuntime.FixedUpdate(physicsSettings.FixedTimeStep);
physicsWorld.Step(physicsSettings.FixedTimeStep);
fixedAccumulatorSeconds -= physicsSettings.FixedTimeStep;
++fixedSteps;
}
if (fixedSteps == physicsSettings.MaxFixedStepsPerFrame && fixedAccumulatorSeconds >= physicsSettings.FixedTimeStep)
fixedAccumulatorSeconds = 0.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;
}
physicsWorld.Stop();
scriptRuntime.Stop();
runtimeInput.Unsubscribe(inputSubscription);
(void)runtimeInput.SaveOverrides(inputOverridePath);
runtimeUi.Shutdown();
viewportRenderer.Shutdown();
renderDevice.Shutdown();
window.Shutdown();
return 0;
}