feat(editor): align MetaCore workflows with Infernux

This commit is contained in:
ayuan9957 2026-06-04 22:14:22 +08:00
parent 93fddb0296
commit 695fc30d34
43 changed files with 11002 additions and 579 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1,18 +1,31 @@
#include "MetaCorePlatform/MetaCoreWindow.h"
#include "MetaCoreFoundation/MetaCoreGeneratedReflection.h"
#include "MetaCoreFoundation/MetaCorePackage.h"
#include "MetaCoreFoundation/MetaCoreProject.h"
#include "MetaCoreRender/MetaCoreEditorViewportRenderer.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/MetaCoreScenePackage.h"
#include "MetaCoreScene/MetaCoreSceneSerializer.h"
#include "MetaCoreScene/MetaCoreScene.h"
#include "MetaCoreScene/MetaCoreUiRmlCompiler.h"
#include <algorithm>
#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 {
@ -25,15 +38,143 @@ MetaCore::MetaCoreSceneView MetaCoreBuildPlayerSceneView() {
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";
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;
}
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{
@ -130,6 +271,313 @@ MetaCore::MetaCoreSceneView MetaCoreBuildPlayerSceneView() {
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;
}
}
} // namespace
int main(int argc, char* argv[]) {
@ -167,6 +615,12 @@ int main(int argc, char* argv[]) {
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);
@ -181,20 +635,83 @@ int main(int argc, char* argv[]) {
const std::filesystem::path projectPath = MetaCore::MetaCoreGetProjectFilePath(projectRoot);
viewportRenderer.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(
projectRoot / "Runtime" / "ProjectRuntime.mcruntimecfg",
runtimeDirectory / "ProjectRuntime.mcruntimecfg",
typeRegistry
);
const auto runtimeProjectDocument = loadedRuntimeProjectDocument.value_or(MetaCoreBuildDefaultRuntimeProjectDocument());
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 = MetaCore::MetaCoreReadScenePackage(absoluteScene);
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 = MetaCore::MetaCoreReadScenePackage(projectRoot / runtimeProjectDocument.StartupScenePath);
startupSceneDocument =
MetaCoreReadSceneDocumentFromPath(projectRoot / runtimeProjectDocument.StartupScenePath, typeRegistry);
}
if (!startupSceneDocument.has_value()) {
startupSceneDocument = MetaCore::MetaCoreLoadStartupSceneDocument(projectPath);
@ -210,6 +727,58 @@ int main(int argc, char* argv[]) {
scene = MetaCore::MetaCoreCreateDefaultScene();
std::cout << "MetaCorePlayer: startup scene unavailable, using built-in default scene\n";
}
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();
@ -217,7 +786,7 @@ int main(int argc, char* argv[]) {
const auto diagnosticsPath = (projectRoot / runtimeProjectDocument.DiagnosticsPath).lexically_normal();
const auto loadedSourcesDocument = MetaCore::MetaCoreReadRuntimeDataSourcesDocument(sourcesPath, typeRegistry);
const auto loadedBindingsDocument = MetaCore::MetaCoreReadRuntimeBindingsDocument(bindingsPath, typeRegistry);
const auto sourcesDocument = loadedSourcesDocument.value_or(MetaCoreBuildDefaultRuntimeDataSourcesDocument());
auto sourcesDocument = loadedSourcesDocument.value_or(MetaCoreBuildDefaultRuntimeDataSourcesDocument());
const auto bindingsDocument = loadedBindingsDocument.value_or(MetaCoreBuildDefaultRuntimeBindingsDocument());
if (loadedSourcesDocument.has_value() && loadedBindingsDocument.has_value()) {
@ -232,36 +801,86 @@ int main(int argc, char* argv[]) {
std::cout << "MetaCorePlayer: runtime config missing, using built-in fallback config\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::unique_ptr<MetaCore::MetaCoreIRuntimeDataSourceAdapter> runtimeAdapter;
if (!sourcesDocument.Sources.empty()) {
runtimeAdapter = MetaCore::MetaCoreCreateRuntimeDataSourceAdapter(sourcesDocument.Sources.front().AdapterType);
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 "
<< sourcesDocument.Sources.front().AdapterType << '\n';
<< 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(sourcesDocument.Sources.front())) {
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;
}
if (!runtimeAdapter->Connect()) {
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="
<< runtimeAdapter->GetStatus().LastError << '\n';
<< 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));
}
MetaCore::MetaCoreRuntimeDataSourceState lastReportedSourceState =
runtimeAdapter != nullptr
? runtimeAdapter->GetStatus().State
: MetaCore::MetaCoreRuntimeDataSourceState::Disconnected;
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();
@ -271,42 +890,66 @@ int main(int argc, char* argv[]) {
static_cast<float>(windowWidth),
static_cast<float>(windowHeight)
});
if (runtimeAdapter != nullptr) {
runtimeAdapter->Tick(1.0 / 60.0);
runtimeDataDispatcher.ApplyUpdates(runtimeAdapter->PollUpdates());
runtimeDataDispatcher.TickStaleness(runtimeAdapter->GetStatus().LastUpdateAt);
runtimeUiRenderer.Resize(windowWidth, windowHeight);
runtimeUiRenderer.BeginFrame(1.0F / 60.0F);
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;
}
}
}
const auto diagnostics = runtimeDataDispatcher.BuildDiagnosticsSnapshot(
runtimeAdapter != nullptr
? std::vector<MetaCore::MetaCoreRuntimeDataSourceStatus>{runtimeAdapter->GetStatus()}
: std::vector<MetaCore::MetaCoreRuntimeDataSourceStatus>{}
);
if ((diagnosticsWriteFrame % 15ULL) == 0ULL) {
(void)MetaCore::MetaCoreWriteRuntimeDiagnosticsSnapshot(diagnosticsPath, diagnostics, typeRegistry);
}
++diagnosticsWriteFrame;
if (runtimeAdapter != nullptr && runtimeAdapter->GetStatus().State != lastReportedSourceState) {
std::cout << "MetaCorePlayer: data source state changed to "
<< static_cast<int>(runtimeAdapter->GetStatus().State)
<< " error=" << runtimeAdapter->GetStatus().LastError << '\n';
lastReportedSourceState = runtimeAdapter->GetStatus().State;
}
if (diagnostics.HasFaults) {
for (const MetaCore::MetaCoreRuntimeBindingStatus& bindingStatus : diagnostics.BindingStatuses) {
if (!bindingStatus.Healthy || bindingStatus.Stale) {
std::cout << "MetaCorePlayer: binding issue id=" << bindingStatus.BindingId
<< " stale=" << (bindingStatus.Stale ? "true" : "false")
<< " error=" << bindingStatus.LastError << '\n';
}
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);
viewportRenderer.RenderSceneToViewport(scene, MetaCoreBuildPlayerSceneView(), true);
runtimeUiRenderer.Render();
viewportRenderer.SetRuntimeUiOverlayFrame(runtimeUiRenderer.GetLastFrame(), windowWidth, windowHeight);
viewportRenderer.RenderAll();
renderDevice.RenderFrame();
renderDevice.PresentFrame();
window.EndFrame();
}
runtimeUiRenderer.Shutdown();
viewportRenderer.Shutdown();
renderDevice.Shutdown();
window.Shutdown();

View File

@ -23,6 +23,22 @@ list(PREPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
if(EXISTS "${CMAKE_SOURCE_DIR}/vcpkg_installed/x64-windows/share/imgui/imgui-config.cmake")
list(PREPEND CMAKE_PREFIX_PATH "${CMAKE_SOURCE_DIR}/vcpkg_installed/x64-windows")
endif()
if(EXISTS "${CMAKE_BINARY_DIR}/vcpkg_installed/x64-windows/share/rmlui/RmlUiConfig.cmake")
list(PREPEND CMAKE_PREFIX_PATH "${CMAKE_BINARY_DIR}/vcpkg_installed/x64-windows")
endif()
set(METACORE_QTBASE_PACKAGE_DIR "")
if(CMAKE_TOOLCHAIN_FILE MATCHES "[/\\\\]vcpkg\\.cmake$")
get_filename_component(METACORE_VCPKG_BUILDSYSTEM_DIR "${CMAKE_TOOLCHAIN_FILE}" DIRECTORY)
get_filename_component(METACORE_VCPKG_SCRIPTS_DIR "${METACORE_VCPKG_BUILDSYSTEM_DIR}" DIRECTORY)
get_filename_component(METACORE_VCPKG_ROOT_DIR "${METACORE_VCPKG_SCRIPTS_DIR}" DIRECTORY)
if(NOT VCPKG_TARGET_TRIPLET)
set(VCPKG_TARGET_TRIPLET "x64-windows")
endif()
set(METACORE_QTBASE_PACKAGE_DIR "${METACORE_VCPKG_ROOT_DIR}/packages/qtbase_${VCPKG_TARGET_TRIPLET}")
if(EXISTS "${METACORE_QTBASE_PACKAGE_DIR}/share/Qt6/Qt6Config.cmake")
list(PREPEND CMAKE_PREFIX_PATH "${METACORE_QTBASE_PACKAGE_DIR}")
endif()
endif()
@ -31,6 +47,8 @@ include(MetaCoreFilament)
find_package(glm CONFIG REQUIRED)
find_package(imgui CONFIG REQUIRED)
find_package(RmlUi CONFIG REQUIRED)
find_package(Qt6 CONFIG REQUIRED COMPONENTS Widgets)
set(METACORE_COMMON_WARNINGS)
if(MSVC)
@ -82,6 +100,12 @@ add_executable(MetaCoreRuntimeConfigTool
target_compile_options(MetaCoreRuntimeConfigTool PRIVATE ${METACORE_COMMON_WARNINGS})
add_executable(MetaCoreBuildPackageTool
Tools/MetaCoreBuildPackageTool/main.cpp
)
target_compile_options(MetaCoreBuildPackageTool PRIVATE ${METACORE_COMMON_WARNINGS})
add_executable(MetaCoreTcpSenderTool
Tools/MetaCoreTcpSenderTool/main.cpp
)
@ -203,12 +227,14 @@ set(METACORE_SCENE_HEADERS
Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreScenePackage.h
Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreScene.h
Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreSceneSerializer.h
Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreUiRmlCompiler.h
)
set(METACORE_SCENE_SOURCES
Source/MetaCoreScene/Private/MetaCoreScenePackage.cpp
Source/MetaCoreScene/Private/MetaCoreScene.cpp
Source/MetaCoreScene/Private/MetaCoreSceneSerializer.cpp
Source/MetaCoreScene/Private/MetaCoreUiRmlCompiler.cpp
)
metacore_generate_reflection(
@ -246,6 +272,7 @@ set(METACORE_RENDER_HEADERS
Source/MetaCoreRender/Public/MetaCoreRender/MetaCoreFilamentSceneBridge.h
Source/MetaCoreRender/Public/MetaCoreRender/MetaCoreImGuiHelper.h
Source/MetaCoreRender/Public/MetaCoreRender/MetaCoreRenderDevice.h
Source/MetaCoreRender/Public/MetaCoreRender/MetaCoreRuntimeUiRenderer.h
Source/MetaCoreRender/Public/MetaCoreRender/MetaCoreSceneRenderSync.h
Source/MetaCoreRender/Public/MetaCoreRender/MetaCoreRenderTypes.h
)
@ -255,6 +282,7 @@ set(METACORE_RENDER_SOURCES
Source/MetaCoreRender/Private/MetaCoreFilamentSceneBridge.cpp
Source/MetaCoreRender/Private/MetaCoreImGuiHelper.cpp
Source/MetaCoreRender/Private/MetaCoreRenderDevice.cpp
Source/MetaCoreRender/Private/MetaCoreRuntimeUiRenderer.cpp
Source/MetaCoreRender/Private/MetaCoreSceneRenderSync.cpp
)
@ -276,6 +304,8 @@ target_link_libraries(MetaCoreRender
MetaCoreScene
glm::glm
imgui::imgui
PRIVATE
RmlUi::RmlUi
)
metacore_use_filament(MetaCoreRender)
@ -408,6 +438,11 @@ target_link_libraries(MetaCoreEditor
target_compile_options(MetaCoreEditor PRIVATE ${METACORE_COMMON_WARNINGS})
target_compile_definitions(MetaCoreEditor PRIVATE WIN32_LEAN_AND_MEAN NOMINMAX)
target_link_libraries(MetaCoreBuildPackageTool
PRIVATE
MetaCoreEditor
)
add_executable(MetaCoreEditorApp
Apps/MetaCoreEditor/main.cpp
)
@ -418,6 +453,35 @@ target_link_libraries(MetaCoreEditorApp
)
metacore_stage_ui_blit_material(MetaCoreEditorApp)
add_executable(MetaCoreLauncher
Apps/MetaCoreLauncher/main.cpp
)
target_link_libraries(MetaCoreLauncher
PRIVATE
MetaCoreFoundation
Qt6::Widgets
)
target_compile_options(MetaCoreLauncher PRIVATE ${METACORE_COMMON_WARNINGS})
target_compile_definitions(MetaCoreLauncher PRIVATE NOMINMAX)
if(WIN32)
if(EXISTS "${METACORE_QTBASE_PACKAGE_DIR}/bin/Qt6Core.dll")
add_custom_command(TARGET MetaCoreLauncher POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${METACORE_QTBASE_PACKAGE_DIR}/bin/Qt6Core.dll"
"${METACORE_QTBASE_PACKAGE_DIR}/bin/Qt6Gui.dll"
"${METACORE_QTBASE_PACKAGE_DIR}/bin/Qt6Widgets.dll"
"$<TARGET_FILE_DIR:MetaCoreLauncher>"
COMMAND ${CMAKE_COMMAND} -E make_directory
"$<TARGET_FILE_DIR:MetaCoreLauncher>/platforms"
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${METACORE_QTBASE_PACKAGE_DIR}/Qt6/plugins/platforms/qwindows.dll"
"$<TARGET_FILE_DIR:MetaCoreLauncher>/platforms"
VERBATIM
)
endif()
endif()
add_executable(MetaCorePlayer
@ -432,8 +496,11 @@ target_link_libraries(MetaCorePlayer
MetaCoreRuntimeData
MetaCoreScene
)
target_compile_options(MetaCorePlayer PRIVATE ${METACORE_COMMON_WARNINGS})
metacore_stage_ui_blit_material(MetaCorePlayer)
add_dependencies(MetaCoreBuildPackageTool MetaCorePlayer)
if(METACORE_BUILD_TESTS)
@ -449,9 +516,17 @@ if(METACORE_BUILD_TESTS)
MetaCoreRuntimeData
)
target_compile_options(MetaCoreSmokeTests PRIVATE ${METACORE_COMMON_WARNINGS})
add_dependencies(MetaCoreSmokeTests MetaCoreRuntimeConfigTool)
add_test(NAME MetaCoreSmokeTests COMMAND MetaCoreSmokeTests)
add_test(
NAME MetaCoreRuntimeConfigToolSmoke
COMMAND ${CMAKE_COMMAND}
-DMETACORE_RUNTIME_CONFIG_TOOL=$<TARGET_FILE:MetaCoreRuntimeConfigTool>
-DMETACORE_RUNTIME_CONFIG_OUTPUT_ROOT=${CMAKE_BINARY_DIR}/RuntimeConfigToolSmoke
-P ${CMAKE_SOURCE_DIR}/tests/MetaCoreRuntimeConfigToolSmoke.cmake
)
# Filament + ImGui Demo
add_executable(FilamentImGuiDemo

View File

@ -0,0 +1,126 @@
{
"Name": "RuntimeHud",
"ReferenceWidth": 1280,
"ReferenceHeight": 720,
"RootNodeIds": [
"hud.root"
],
"Nodes": [
{
"Id": "hud.root",
"Name": "HUD Root",
"Type": 0,
"ParentId": "",
"Children": [
"hud.title",
"hud.status",
"hud.button"
],
"Visible": true,
"RectTransform": {
"AnchorMin": [0.0, 0.0, 0.0],
"AnchorMax": [0.0, 0.0, 0.0],
"Pivot": [0.0, 0.0, 0.0],
"Position": [24.0, 24.0, 0.0],
"Size": [360.0, 164.0, 0.0]
},
"Style": {
"BackgroundColor": [0.05, 0.08, 0.12],
"TextColor": [1.0, 1.0, 1.0],
"TintColor": [1.0, 1.0, 1.0],
"FontSize": 16.0,
"Padding": [12.0, 12.0, 0.0],
"HorizontalAlignment": 0,
"VerticalAlignment": 0,
"ImageAssetGuid": "",
"PreserveAspect": false
},
"Text": "",
"Interactable": false
},
{
"Id": "hud.title",
"Name": "Title",
"Type": 1,
"ParentId": "hud.root",
"Children": [],
"Visible": true,
"RectTransform": {
"AnchorMin": [0.0, 0.0, 0.0],
"AnchorMax": [0.0, 0.0, 0.0],
"Pivot": [0.0, 0.0, 0.0],
"Position": [16.0, 14.0, 0.0],
"Size": [320.0, 36.0, 0.0]
},
"Style": {
"BackgroundColor": [0.08, 0.11, 0.16],
"TextColor": [0.95, 0.98, 1.0],
"TintColor": [1.0, 1.0, 1.0],
"FontSize": 24.0,
"Padding": [8.0, 4.0, 0.0],
"HorizontalAlignment": 0,
"VerticalAlignment": 0,
"ImageAssetGuid": "",
"PreserveAspect": false
},
"Text": "MetaCore Runtime",
"Interactable": false
},
{
"Id": "hud.status",
"Name": "Status Strip",
"Type": 0,
"ParentId": "hud.root",
"Children": [],
"Visible": true,
"RectTransform": {
"AnchorMin": [0.0, 0.0, 0.0],
"AnchorMax": [0.0, 0.0, 0.0],
"Pivot": [0.0, 0.0, 0.0],
"Position": [16.0, 62.0, 0.0],
"Size": [220.0, 34.0, 0.0]
},
"Style": {
"BackgroundColor": [0.12, 0.42, 0.82],
"TextColor": [1.0, 1.0, 1.0],
"TintColor": [1.0, 1.0, 1.0],
"FontSize": 16.0,
"Padding": [8.0, 4.0, 0.0],
"HorizontalAlignment": 0,
"VerticalAlignment": 0,
"ImageAssetGuid": "",
"PreserveAspect": false
},
"Text": "",
"Interactable": false
},
{
"Id": "hud.button",
"Name": "Action Button",
"Type": 3,
"ParentId": "hud.root",
"Children": [],
"Visible": true,
"RectTransform": {
"AnchorMin": [0.0, 0.0, 0.0],
"AnchorMax": [0.0, 0.0, 0.0],
"Pivot": [0.0, 0.0, 0.0],
"Position": [16.0, 110.0, 0.0],
"Size": [144.0, 38.0, 0.0]
},
"Style": {
"BackgroundColor": [0.18, 0.72, 0.36],
"TextColor": [0.02, 0.04, 0.03],
"TintColor": [1.0, 1.0, 1.0],
"FontSize": 17.0,
"Padding": [10.0, 5.0, 0.0],
"HorizontalAlignment": 1,
"VerticalAlignment": 0,
"ImageAssetGuid": "",
"PreserveAspect": false
},
"Text": "Run",
"Interactable": true
}
]
}

View File

@ -0,0 +1,8 @@
{
"asset_type": "ui_document",
"guid": "821c1714-975f-4284-8e4d-650db0be9f3e",
"importer_id": "UiDocumentImporter",
"package_path": "Assets/UI/Hud.mcui.json",
"source_hash": 18133359267932109919,
"source_path": "Assets/UI/Hud.mcui.json"
}

File diff suppressed because it is too large Load Diff

View File

@ -19,6 +19,7 @@
#define GLM_ENABLE_EXPERIMENTAL
#include <cstdlib>
#include <cstdio>
#include <algorithm>
#include <filesystem>
#include <glm/ext/matrix_clip_space.hpp>
#include <glm/ext/matrix_transform.hpp>
@ -30,6 +31,9 @@
#include <glm/vec4.hpp>
#include <memory>
#include <iostream>
#include <string>
#include <string_view>
#include <system_error>
#include <unordered_set>
#include <vector>
@ -68,6 +72,42 @@ constexpr bool GMetaCoreEnableImGuizmo = true;
constexpr bool GMetaCoreEnableImGuizmo = true;
#endif
constexpr const char* GMetaCoreEditorLayoutRelativePath = "Library/Editor/imgui.ini";
[[nodiscard]] const char* MetaCoreGetInfernuxDockWindowId(std::string_view panelId) {
if (panelId == "Hierarchy") {
return "hierarchy";
}
if (panelId == "Inspector") {
return "inspector";
}
if (panelId == "Project") {
return "project";
}
if (panelId == "Console") {
return "console";
}
if (panelId == "RuntimeData") {
return "runtime_data";
}
if (panelId == "BuildSettings") {
return "build_settings";
}
return "";
}
[[nodiscard]] std::string MetaCoreBuildDockWindowName(const MetaCoreIEditorPanelProvider& panelProvider) {
const char* stableId = MetaCoreGetInfernuxDockWindowId(panelProvider.GetPanelId());
if (stableId == nullptr || stableId[0] == '\0') {
return panelProvider.GetPanelTitle();
}
std::string windowName = panelProvider.GetPanelTitle();
windowName += "###";
windowName += stableId;
return windowName;
}
[[nodiscard]] bool MetaCoreInstantiateProjectAssetToScene(
MetaCoreEditorContext& editorContext,
const MetaCoreProjectAssetDragDropPayload& payload
@ -703,6 +743,7 @@ bool MetaCoreEditorApp::Initialize(int argc, char* argv[]) {
int MetaCoreEditorApp::Run() {
while (!Window_.ShouldClose()) {
Window_.BeginFrame();
SyncImGuiLayoutIniPath();
// Filament 接管,不再使用 OpenGL3 后端的 NewFrame
ImGui_ImplWin32_NewFrame();
@ -711,6 +752,11 @@ int MetaCoreEditorApp::Run() {
ImGuizmo::BeginFrame();
}
if (const auto playModeService = ModuleRegistry_.ResolveService<MetaCoreIPlayModeService>();
playModeService != nullptr) {
playModeService->TickPlayMode(*EditorContext_, Window_.GetDeltaSeconds());
}
DrawEditorFrame();
ImGui::Render();
@ -755,11 +801,8 @@ bool MetaCoreEditorApp::InitializeImGui() {
ImGuiIO& io = ImGui::GetIO();
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
#if defined(_DEBUG)
// Avoid loading persisted docking/layout ini in Debug; malformed/legacy ini may trigger large allocations.
io.IniFilename = nullptr;
io.LogFilename = nullptr;
#endif
MetaCoreTraceStartup("metacore.ui: font setup begin");
MetaCoreConfigureChineseFont();
MetaCoreTraceStartup("metacore.ui: font setup done");
@ -780,295 +823,622 @@ void MetaCoreEditorApp::ShutdownImGui() {
ImGui::DestroyContext();
}
void MetaCoreEditorApp::SyncImGuiLayoutIniPath() {
if (EditorContext_ == nullptr) {
return;
}
std::filesystem::path projectRoot{};
if (const auto assetDatabaseService = ModuleRegistry_.ResolveService<MetaCoreIAssetDatabaseService>();
assetDatabaseService != nullptr && assetDatabaseService->HasProject()) {
projectRoot = assetDatabaseService->GetProjectDescriptor().RootPath.lexically_normal();
}
if (projectRoot == ImGuiLayoutProjectRoot_) {
return;
}
ImGuiIO& io = ImGui::GetIO();
if (!ImGuiIniPath_.empty()) {
ImGui::SaveIniSettingsToDisk(ImGuiIniPath_.c_str());
}
ImGuiLayoutProjectRoot_ = projectRoot;
ImGuiIniPath_.clear();
io.IniFilename = nullptr;
if (projectRoot.empty()) {
if (EditorContext_ != nullptr) {
EditorContext_->SetDockLayoutBuilt(false);
}
return;
}
const std::filesystem::path layoutIniPath = projectRoot / GMetaCoreEditorLayoutRelativePath;
const bool hasSavedLayout = std::filesystem::exists(layoutIniPath);
std::error_code errorCode;
std::filesystem::create_directories(layoutIniPath.parent_path(), errorCode);
ImGui::ClearIniSettings();
ImGuiIniPath_ = layoutIniPath.string();
io.IniFilename = ImGuiIniPath_.c_str();
if (hasSavedLayout) {
ImGui::LoadIniSettingsFromDisk(ImGuiIniPath_.c_str());
}
EditorContext_->SetDockLayoutBuilt(hasSavedLayout);
}
void MetaCoreEditorApp::DrawEditorFrame() {
SceneInteractionService_.HandleShortcuts(*EditorContext_);
const ImGuiViewport* mainViewport = ImGui::GetMainViewport();
ImGui::SetNextWindowPos(mainViewport->WorkPos);
ImGui::SetNextWindowSize(mainViewport->WorkSize);
ImGui::SetNextWindowViewport(mainViewport->ID);
{
const ImGuiViewport* mainViewport = ImGui::GetMainViewport();
constexpr float statusBarHeight = 24.0F;
ImGui::SetNextWindowPos(mainViewport->WorkPos);
ImGui::SetNextWindowSize(ImVec2(mainViewport->WorkSize.x, mainViewport->WorkSize.y - statusBarHeight));
ImGui::SetNextWindowViewport(mainViewport->ID);
constexpr ImGuiWindowFlags hostWindowFlags =
ImGuiWindowFlags_NoTitleBar |
ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoBackground |
ImGuiWindowFlags_NoBringToFrontOnFocus |
ImGuiWindowFlags_NoNavFocus |
ImGuiWindowFlags_MenuBar;
constexpr ImGuiWindowFlags hostWindowFlags =
ImGuiWindowFlags_NoDocking |
ImGuiWindowFlags_NoTitleBar |
ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoBackground |
ImGuiWindowFlags_NoBringToFrontOnFocus |
ImGuiWindowFlags_NoNavFocus |
ImGuiWindowFlags_MenuBar;
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0F);
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0F);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0F, 0.0F));
ImGui::Begin("MetaCoreMainDockSpace", nullptr, hostWindowFlags);
ImGui::PopStyleVar(3);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0F);
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0F);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0F, 0.0F));
ImGui::Begin("DockSpaceWindow", nullptr, hostWindowFlags);
ImGui::PopStyleVar(3);
if (ImGui::BeginMenuBar()) {
for (const auto& menuProvider : ModuleRegistry_.GetMenuProviders()) {
menuProvider->DrawMenuBar(*EditorContext_);
if (ImGui::BeginMenuBar()) {
for (const auto& menuProvider : ModuleRegistry_.GetMenuProviders()) {
menuProvider->DrawMenuBar(*EditorContext_);
}
ImGui::EndMenuBar();
}
// Center Play/Pause/Stop buttons
const float buttonWidth = 32.0F;
const float totalWidth = buttonWidth * 3 + ImGui::GetStyle().ItemSpacing.x * 2;
ImGui::SetCursorPosX((ImGui::GetWindowWidth() - totalWidth) * 0.5F);
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.2F, 0.2F, 0.2F, 0.0F));
if (ImGui::Button(" > ", ImVec2(buttonWidth, 0))) { /* Play */ }
ImGui::SameLine();
if (ImGui::Button(" ||", ImVec2(buttonWidth, 0))) { /* Pause */ }
ImGui::SameLine();
if (ImGui::Button(" >>", ImVec2(buttonWidth, 0))) { /* Step */ }
ImGui::PopStyleColor();
ImGui::EndMenuBar();
}
const ImGuiID dockSpaceId = ImGui::GetID("MetaCoreDockSpaceId");
ImGui::DockSpace(dockSpaceId, ImVec2(0.0F, 0.0F), ImGuiDockNodeFlags_PassthruCentralNode);
EnsureDefaultDockLayout(dockSpaceId);
ImGui::End();
for (const auto& panelProvider : ModuleRegistry_.GetPanelProviders()) {
if (panelProvider->GetPanelId() == "Scene") {
continue;
const ImGuiID dockSpaceId = ImGui::GetID("MainDockSpace");
ImGui::DockSpace(dockSpaceId, ImVec2(0.0F, 0.0F), ImGuiDockNodeFlags_None);
if (!EditorContext_->HasDockLayoutBuilt()) {
EnsureDefaultDockLayout(dockSpaceId);
}
bool& panelOpen = ModuleRegistry_.AccessPanelOpenState(panelProvider->GetPanelId());
if (!panelOpen) {
continue;
}
bool pushedTransparentColors = false;
if (panelProvider->WantsTransparentBackground()) {
ImGui::SetNextWindowBgAlpha(0.0F);
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.0F, 0.0F, 0.0F, 0.0F));
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.0F, 0.0F, 0.0F, 0.0F));
ImGui::PushStyleColor(ImGuiCol_PopupBg, ImVec4(0.0F, 0.0F, 0.0F, 0.0F));
pushedTransparentColors = true;
}
bool pushedPadding = false;
if (panelProvider->WantsZeroPadding()) {
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0F, 0.0F));
pushedPadding = true;
}
const ImGuiWindowFlags panelWindowFlags = static_cast<ImGuiWindowFlags>(panelProvider->GetWindowFlags());
ImGui::Begin(panelProvider->GetPanelTitle().c_str(), &panelOpen, panelWindowFlags);
panelProvider->DrawPanel(*EditorContext_);
ImGui::End();
if (pushedPadding) {
ImGui::PopStyleVar();
DrawEditorToolbar();
for (const auto& panelProvider : ModuleRegistry_.GetPanelProviders()) {
if (panelProvider->GetPanelId() == "Scene") {
continue;
}
bool& panelOpen = ModuleRegistry_.AccessPanelOpenState(panelProvider->GetPanelId());
if (!panelOpen) {
continue;
}
bool pushedTransparentColors = false;
if (panelProvider->WantsTransparentBackground()) {
ImGui::SetNextWindowBgAlpha(0.0F);
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.0F, 0.0F, 0.0F, 0.0F));
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.0F, 0.0F, 0.0F, 0.0F));
ImGui::PushStyleColor(ImGuiCol_PopupBg, ImVec4(0.0F, 0.0F, 0.0F, 0.0F));
pushedTransparentColors = true;
}
bool pushedPadding = false;
if (panelProvider->WantsZeroPadding()) {
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0F, 0.0F));
pushedPadding = true;
}
const ImGuiWindowFlags panelWindowFlags = static_cast<ImGuiWindowFlags>(panelProvider->GetWindowFlags());
const std::string panelWindowName = MetaCoreBuildDockWindowName(*panelProvider);
ImGui::Begin(panelWindowName.c_str(), &panelOpen, panelWindowFlags);
panelProvider->DrawPanel(*EditorContext_);
ImGui::End();
if (pushedPadding) {
ImGui::PopStyleVar();
}
if (pushedTransparentColors) {
ImGui::PopStyleColor(3);
}
}
if (pushedTransparentColors) {
ImGui::PopStyleColor(3);
DrawSceneViewWindow();
DrawGameViewWindow();
DrawPlaceholderDockWindow("UI Editor###ui_editor", "UI Editor");
DrawPlaceholderDockWindow("Anim Clip 2D###animclip2d_editor", "Animation Clip 2D");
DrawPlaceholderDockWindow("Anim FSM###animfsm_editor", "Animation FSM");
ApplyPendingDockTabSelections();
DrawEditorStatusBar();
}
}
void MetaCoreEditorApp::DrawEditorToolbar() {
constexpr ImGuiWindowFlags toolbarWindowFlags =
ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_NoScrollbar |
ImGuiWindowFlags_NoScrollWithMouse |
ImGuiWindowFlags_NoSavedSettings;
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8.0F, 4.0F));
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(8.0F, 4.0F));
if (ImGui::Begin("Toolbar###toolbar", nullptr, toolbarWindowFlags)) {
auto drawButton = [](const char* label, bool selected, bool enabled = true) {
if (selected) {
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.20F, 0.34F, 0.58F, 1.0F));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.25F, 0.40F, 0.68F, 1.0F));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.16F, 0.29F, 0.50F, 1.0F));
} else if (!enabled) {
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.16F, 0.16F, 0.16F, 1.0F));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.16F, 0.16F, 0.16F, 1.0F));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.16F, 0.16F, 0.16F, 1.0F));
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.45F, 0.45F, 0.45F, 1.0F));
}
const bool clicked = enabled && ImGui::Button(label, ImVec2(0.0F, 24.0F));
if (selected) {
ImGui::PopStyleColor(3);
} else if (!enabled) {
ImGui::PopStyleColor(4);
}
return clicked;
};
const float windowWidth = ImGui::GetWindowWidth();
const float playControlsWidth = 300.0F;
ImGui::SetCursorPosX(std::max(8.0F, (windowWidth - playControlsWidth) * 0.5F));
if (const auto playModeService = ModuleRegistry_.ResolveService<MetaCoreIPlayModeService>();
playModeService != nullptr) {
const MetaCorePlayModeState playState = playModeService->GetState();
const bool isPlaying = playState == MetaCorePlayModeState::Playing || playState == MetaCorePlayModeState::Paused;
const bool isPaused = playState == MetaCorePlayModeState::Paused;
if (playState == MetaCorePlayModeState::Edit) {
if (drawButton("Play", false)) {
(void)playModeService->EnterPlayMode(*EditorContext_);
}
} else {
if (drawButton("Stop", true)) {
(void)playModeService->ExitPlayMode(*EditorContext_);
}
}
ImGui::SameLine(0.0F, 4.0F);
if (isPaused) {
if (drawButton("Resume", true)) {
(void)playModeService->ResumePlayMode(*EditorContext_);
}
} else if (drawButton("Pause", false, isPlaying)) {
(void)playModeService->PausePlayMode(*EditorContext_);
}
ImGui::SameLine(0.0F, 4.0F);
if (drawButton("Step", false, isPaused)) {
(void)playModeService->StepPlayMode(*EditorContext_, 1.0F / 60.0F);
}
if (isPlaying) {
ImGui::SameLine(0.0F, 8.0F);
ImGui::Text("Time %.2fs", playModeService->GetElapsedPlayTimeSeconds());
}
} else {
(void)drawButton("Play", false, false);
ImGui::SameLine(0.0F, 4.0F);
(void)drawButton("Pause", false, false);
ImGui::SameLine(0.0F, 4.0F);
(void)drawButton("Step", false, false);
}
constexpr float rightControlsWidth = 180.0F;
ImGui::SameLine(std::max(8.0F, windowWidth - rightControlsWidth));
if (ImGui::Button("Gizmos", ImVec2(78.0F, 24.0F))) {
ImGui::OpenPopup("MetaCoreToolbarGizmosPopup");
}
ImGui::SameLine(0.0F, 4.0F);
if (ImGui::Button("Camera", ImVec2(78.0F, 24.0F))) {
ImGui::OpenPopup("MetaCoreToolbarCameraPopup");
}
if (ImGui::BeginPopup("MetaCoreToolbarGizmosPopup")) {
if (ImGui::MenuItem("View", nullptr, EditorContext_->GetGizmoOperation() == MetaCoreGizmoOperation::None)) {
EditorContext_->SetGizmoOperation(MetaCoreGizmoOperation::None);
}
if (ImGui::MenuItem("Move", nullptr, EditorContext_->GetGizmoOperation() == MetaCoreGizmoOperation::Translate)) {
EditorContext_->SetGizmoOperation(MetaCoreGizmoOperation::Translate);
}
if (ImGui::MenuItem("Rotate", nullptr, EditorContext_->GetGizmoOperation() == MetaCoreGizmoOperation::Rotate)) {
EditorContext_->SetGizmoOperation(MetaCoreGizmoOperation::Rotate);
}
if (ImGui::MenuItem("Scale", nullptr, EditorContext_->GetGizmoOperation() == MetaCoreGizmoOperation::Scale)) {
EditorContext_->SetGizmoOperation(MetaCoreGizmoOperation::Scale);
}
ImGui::Separator();
if (ImGui::MenuItem("Local", nullptr, EditorContext_->GetGizmoMode() == MetaCoreGizmoMode::Local)) {
EditorContext_->SetGizmoMode(MetaCoreGizmoMode::Local);
}
if (ImGui::MenuItem("Global", nullptr, EditorContext_->GetGizmoMode() == MetaCoreGizmoMode::Global)) {
EditorContext_->SetGizmoMode(MetaCoreGizmoMode::Global);
}
ImGui::Separator();
bool snapEnabled = EditorContext_->GetGizmoSnapSettings().Enabled;
if (ImGui::Checkbox("Snap", &snapEnabled)) {
EditorContext_->SetGizmoSnapEnabled(snapEnabled);
}
bool showGrid = EditorContext_->GetShowViewportGrid();
if (ImGui::Checkbox("Show Grid", &showGrid)) {
EditorContext_->SetShowViewportGrid(showGrid);
}
ImGui::EndPopup();
}
if (ImGui::BeginPopup("MetaCoreToolbarCameraPopup")) {
MetaCoreSceneView sceneView = EditorContext_->GetCameraController().BuildSceneView();
ImGui::TextUnformatted("Scene Camera");
ImGui::Separator();
bool cameraChanged = false;
float fieldOfViewDegrees = sceneView.VerticalFieldOfViewDegrees;
ImGui::SetNextItemWidth(180.0F);
if (ImGui::SliderFloat("FOV", &fieldOfViewDegrees, 20.0F, 110.0F, "%.1f")) {
sceneView.VerticalFieldOfViewDegrees = fieldOfViewDegrees;
cameraChanged = true;
}
ImGui::SetNextItemWidth(220.0F);
if (ImGui::DragFloat3("Position", glm::value_ptr(sceneView.CameraPosition), 0.05F)) {
cameraChanged = true;
}
ImGui::SetNextItemWidth(220.0F);
if (ImGui::DragFloat3("Target", glm::value_ptr(sceneView.CameraTarget), 0.05F)) {
cameraChanged = true;
}
if (cameraChanged) {
EditorContext_->GetCameraController().ApplySceneView(sceneView);
}
if (ImGui::Button("Focus Selection", ImVec2(-1.0F, 0.0F))) {
if (MetaCoreGameObject selectedObject = EditorContext_->GetSelectedGameObject(); selectedObject) {
EditorContext_->GetCameraController().FocusGameObject(selectedObject);
}
}
if (ImGui::Button("Reset Camera", ImVec2(-1.0F, 0.0F))) {
MetaCoreSceneView defaultSceneView{};
defaultSceneView.CameraPosition = {0.0F, 2.5F, 6.5F};
defaultSceneView.CameraTarget = {0.0F, 0.7F, 0.0F};
defaultSceneView.CameraUp = {0.0F, 1.0F, 0.0F};
defaultSceneView.VerticalFieldOfViewDegrees = 60.0F;
EditorContext_->GetCameraController().ApplySceneView(defaultSceneView);
}
ImGui::EndPopup();
}
}
ImGui::End();
ImGui::PopStyleVar(2);
}
if (ModuleRegistry_.IsPanelOpen("Scene")) {
ImGuiDockNode* centralNode = ImGui::DockBuilderGetCentralNode(dockSpaceId);
if (centralNode != nullptr) {
// MetaCore main viewport keeps a Unity-like Scene / Game mental model.
ImGui::SetNextWindowPos(centralNode->Pos);
ImGui::SetNextWindowSize(ImVec2(centralNode->Size.x, 30.0F));
ImGui::Begin("ViewportTabs", nullptr, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoNav);
void MetaCoreEditorApp::DrawEditorStatusBar() {
const ImGuiViewport* mainViewport = ImGui::GetMainViewport();
constexpr float statusBarHeight = 24.0F;
ImGui::SetNextWindowPos(ImVec2(mainViewport->WorkPos.x, mainViewport->WorkPos.y + mainViewport->WorkSize.y - statusBarHeight));
ImGui::SetNextWindowSize(ImVec2(mainViewport->WorkSize.x, statusBarHeight));
ImGui::SetNextWindowViewport(mainViewport->ID);
static int activeTab = 0; // 0: Scene, 1: Game
if (ImGui::Selectable(" 场景 ", activeTab == 0, 0, ImVec2(60, 0))) activeTab = 0;
ImGui::SameLine();
if (ImGui::Selectable(" 游戏 ", activeTab == 1, 0, ImVec2(60, 0))) activeTab = 1;
constexpr ImGuiWindowFlags statusWindowFlags =
ImGuiWindowFlags_NoDocking |
ImGuiWindowFlags_NoTitleBar |
ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoScrollbar |
ImGuiWindowFlags_NoSavedSettings;
ImGui::End();
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8.0F, 3.0F));
ImGui::Begin("MetaCoreStatusBar", nullptr, statusWindowFlags);
ImGui::TextUnformatted("MetaCore");
ImGui::SameLine(0.0F, 16.0F);
if (const auto playModeService = ModuleRegistry_.ResolveService<MetaCoreIPlayModeService>();
playModeService != nullptr) {
const MetaCorePlayModeState playState = playModeService->GetState();
const char* stateText = playState == MetaCorePlayModeState::Edit ? "Edit" :
(playState == MetaCorePlayModeState::Playing ? "Playing" : "Paused");
ImGui::Text("Mode: %s", stateText);
}
if (const auto assetDatabaseService = ModuleRegistry_.ResolveService<MetaCoreIAssetDatabaseService>();
assetDatabaseService != nullptr && assetDatabaseService->HasProject()) {
ImGui::SameLine(0.0F, 16.0F);
const std::string projectRoot = assetDatabaseService->GetProjectDescriptor().RootPath.string();
ImGui::Text("Project: %s", projectRoot.c_str());
}
ImGui::End();
ImGui::PopStyleVar();
}
constexpr float sceneToolbarHeight = 34.0F;
constexpr float tabHeaderHeight = 30.0F;
MetaCoreSceneViewportState& viewportState = EditorContext_->GetSceneViewportState();
const ImVec2 mainViewportPosition = ImGui::GetMainViewport()->Pos;
viewportState.Left = centralNode->Pos.x;
viewportState.Top = centralNode->Pos.y + sceneToolbarHeight + tabHeaderHeight;
viewportState.Width = centralNode->Size.x;
if (viewportState.Width < 1.0F) {
viewportState.Width = 1.0F;
}
viewportState.Height = centralNode->Size.y - sceneToolbarHeight - tabHeaderHeight;
if (viewportState.Height < 1.0F) {
viewportState.Height = 1.0F;
}
void MetaCoreEditorApp::DrawSceneViewWindow() {
if (!ModuleRegistry_.IsPanelOpen("Scene")) {
SceneInteractionService_.ResetFrameState();
ViewportRenderer_.SetViewportRect(MetaCoreViewportRect{});
return;
}
// --- 补丁三重构开始 ---
bool& sceneOpen = ModuleRegistry_.AccessPanelOpenState("Scene");
bool drewSceneViewport = false;
bool gizmoUsing = false;
// 1. 准备 GizmoCanvas 窗口属性
ImGui::SetNextWindowPos(ImVec2(centralNode->Pos.x, centralNode->Pos.y + sceneToolbarHeight + tabHeaderHeight));
ImGui::SetNextWindowSize(ImVec2(centralNode->Size.x, centralNode->Size.y - sceneToolbarHeight - tabHeaderHeight));
ImGui::SetNextWindowBgAlpha(0.0F);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0F);
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0F);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0F, 0.0F));
constexpr ImGuiWindowFlags sceneWindowFlags =
ImGuiWindowFlags_NoScrollbar |
ImGuiWindowFlags_NoScrollWithMouse;
// 【问题二修复】:如果当前没有处于资产拖拽过程中,则屏蔽输入以允许相机漫游操作;
// 如果正在拖放资产(如模型或预制体),则恢复输入以便 BeginDragDropTarget 能够正确接收放置。
ImGuiWindowFlags gizmoCanvasFlags =
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoDocking |
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings |
ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus |
ImGuiWindowFlags_NoBackground;
if (ImGui::GetDragDropPayload() == nullptr) {
gizmoCanvasFlags |= ImGuiWindowFlags_NoInputs;
}
bool gizmoCanvasOpen = true;
bool gizmoUsing = false;
bool gizmoHovering = false;
if (ImGui::Begin("MetaCoreSceneGizmoCanvas", &gizmoCanvasOpen, gizmoCanvasFlags)) {
// 2. 拿到最精确的屏幕绝对位置和画布大小
ImVec2 viewportPos = ImGui::GetCursorScreenPos();
ImVec2 viewportSize = ImGui::GetContentRegionAvail();
// 3. 全局统一更新 viewportState
viewportState.Left = viewportPos.x;
viewportState.Top = viewportPos.y;
viewportState.Width = viewportSize.x;
viewportState.Height = viewportSize.y;
// 处理交互状态
const ImVec2 mousePosition = ImGui::GetMousePos();
viewportState.Hovered =
mousePosition.x >= viewportState.Left &&
mousePosition.x <= viewportState.Left + viewportState.Width &&
mousePosition.y >= viewportState.Top &&
mousePosition.y <= viewportState.Top + viewportState.Height;
viewportState.Focused = viewportState.Hovered && !ImGui::GetIO().WantCaptureMouse;
// 处理聚焦快捷键
if (viewportState.Focused && !ImGui::GetIO().WantCaptureKeyboard && EditorContext_->GetInput().WasKeyPressed(MetaCoreInputKey::Focus)) {
if (MetaCoreGameObject selectedObject = EditorContext_->GetSelectedGameObject(); selectedObject) {
EditorContext_->GetCameraController().FocusGameObject(selectedObject);
}
}
// 4. 同步尺寸给 Filament (SetViewportRect 内部会处理 Resize)
ViewportRenderer_.SetViewportRect(MetaCoreViewportRect{
viewportState.Left, viewportState.Top, viewportState.Width, viewportState.Height
});
// 5. 更新相机并构建 SceneView (在渲染之前!)
gizmoUsing = ImGuizmo::IsUsing();
if (!gizmoUsing) {
EditorContext_->GetCameraController().Update(viewportState, EditorContext_->GetInput());
}
MetaCoreSceneView sceneView = EditorContext_->GetCameraController().BuildSceneView();
sceneView.SelectedObjectId = EditorContext_->GetSelectedObjectId();
// 6. 驱动 Filament 渲染
ViewportRenderer_.RenderSceneToViewport(Scene_, sceneView);
// 7. 绘制画面 (应用 UV 翻转Filament Y轴起点在底部ImGui 在顶部)
void* texPtr = ViewportRenderer_.GetFilamentTexturePointer();
if (texPtr) {
ImGui::Image(texPtr, viewportSize, ImVec2(0, 1), ImVec2(1, 0));
}
// 7.5. 视口拖拽接收器:从项目面板拖拽资产进入 3D 视口时,在此处响应放置并完成实例化
if (ImGui::BeginDragDropTarget()) {
(void)MetaCoreHandleProjectAssetDrop(*EditorContext_, std::nullopt);
ImGui::EndDragDropTarget();
}
// 8. 绘制 Gizmo (此时坐标和矩阵已完全对齐)
if (GMetaCoreEnableImGuizmo) {
SceneInteractionService_.HandleGizmoManipulation(*EditorContext_);
gizmoHovering = ImGuizmo::IsOver();
gizmoUsing = ImGuizmo::IsUsing();
// View Cube
const float cameraDistance = glm::distance(sceneView.CameraPosition, sceneView.CameraTarget);
const ImVec2 viewCubePos = ImVec2(
viewportState.Left + viewportState.Width - 110.0F,
viewportState.Top + 20.0F
);
glm::mat4 cubeViewMatrix = glm::lookAt(sceneView.CameraPosition, sceneView.CameraTarget, sceneView.CameraUp);
const glm::mat4 originalCubeViewMatrix = cubeViewMatrix;
ImGuizmo::ViewManipulate(
glm::value_ptr(cubeViewMatrix),
cameraDistance,
viewCubePos,
ImVec2(80, 80),
0x10101010
);
// 如果 ViewCube 被操作,同步回相机控制器
if (cubeViewMatrix != originalCubeViewMatrix) {
const glm::mat4 cubeCameraWorldMatrix = glm::inverse(cubeViewMatrix);
MetaCoreSceneView updatedView = sceneView;
updatedView.CameraPosition = glm::vec3(cubeCameraWorldMatrix[3]);
EditorContext_->GetCameraController().ApplySceneView(updatedView);
}
}
// 绘制 Overlays
MetaCoreDrawViewportGridOverlay(*EditorContext_, viewportState);
MetaCoreDrawWorldOriginOverlay(*EditorContext_, sceneView, viewportState);
MetaCoreDrawSelectionBoundsOverlay(*EditorContext_, Scene_, sceneView, viewportState);
MetaCoreDrawSelectionHierarchyOverlay(*EditorContext_, Scene_, sceneView, viewportState);
// 启用资产拖拽到 3D 场景视口的高亮响应遮罩,并显示操作提示
MetaCoreDrawSceneViewportDropTarget(*EditorContext_, viewportPos, viewportSize);
// 处理拾取
if (viewportState.Hovered && !gizmoHovering && !gizmoUsing &&
!ImGui::GetIO().WantCaptureMouse && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
const MetaCoreId pickedObjectId = SceneInteractionService_.PickGameObjectFromViewport(
Scene_,
sceneView,
viewportState,
EditorContext_->GetInput().GetCursorPosition()
);
SceneInteractionService_.ApplyViewportSelection(*EditorContext_, pickedObjectId);
}
}
ImGui::End();
ImGui::PopStyleVar(3);
SceneInteractionService_.HandleGizmoEndUse(*EditorContext_, gizmoUsing);
SceneInteractionService_.DrawViewportToolbar(*EditorContext_);
// --- 补丁三重构结束 ---
} else {
SceneInteractionService_.ResetFrameState();
ViewportRenderer_.SetViewportRect(MetaCoreViewportRect{});
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0F, 0.0F));
if (ImGui::Begin("\u573A\u666F###scene_view", &sceneOpen, sceneWindowFlags)) {
MetaCoreSceneViewportState& viewportState = EditorContext_->GetSceneViewportState();
ImVec2 viewportPos = ImGui::GetCursorScreenPos();
ImVec2 viewportSize = ImGui::GetContentRegionAvail();
if (viewportSize.x < 1.0F) {
viewportSize.x = 1.0F;
}
if (viewportSize.y < 1.0F) {
viewportSize.y = 1.0F;
}
viewportState.Left = viewportPos.x;
viewportState.Top = viewportPos.y;
viewportState.Width = viewportSize.x;
viewportState.Height = viewportSize.y;
const ImVec2 mousePosition = ImGui::GetMousePos();
viewportState.Hovered =
mousePosition.x >= viewportState.Left &&
mousePosition.x <= viewportState.Left + viewportState.Width &&
mousePosition.y >= viewportState.Top &&
mousePosition.y <= viewportState.Top + viewportState.Height;
viewportState.Focused = viewportState.Hovered && ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows);
if (viewportState.Hovered &&
!ImGui::GetIO().WantCaptureKeyboard &&
EditorContext_->GetInput().WasKeyPressed(MetaCoreInputKey::Focus)) {
if (MetaCoreGameObject selectedObject = EditorContext_->GetSelectedGameObject(); selectedObject) {
EditorContext_->GetCameraController().FocusGameObject(selectedObject);
}
}
ViewportRenderer_.SetViewportRect(MetaCoreViewportRect{
viewportState.Left, viewportState.Top, viewportState.Width, viewportState.Height
});
gizmoUsing = ImGuizmo::IsUsing();
if (!gizmoUsing && viewportState.Hovered) {
EditorContext_->GetCameraController().Update(viewportState, EditorContext_->GetInput());
}
MetaCoreSceneView sceneView = EditorContext_->GetCameraController().BuildSceneView();
sceneView.SelectedObjectId = EditorContext_->GetSelectedObjectId();
ViewportRenderer_.RenderSceneToViewport(Scene_, sceneView);
if (void* texPtr = ViewportRenderer_.GetFilamentTexturePointer(); texPtr != nullptr) {
ImGui::Image(texPtr, viewportSize, ImVec2(0, 1), ImVec2(1, 0));
} else {
ImGui::Dummy(viewportSize);
}
if (ImGui::BeginDragDropTarget()) {
(void)MetaCoreHandleProjectAssetDrop(*EditorContext_, std::nullopt);
ImGui::EndDragDropTarget();
}
bool gizmoHovering = false;
if (GMetaCoreEnableImGuizmo) {
SceneInteractionService_.HandleGizmoManipulation(*EditorContext_);
gizmoHovering = ImGuizmo::IsOver();
gizmoUsing = ImGuizmo::IsUsing();
const float cameraDistance = glm::distance(sceneView.CameraPosition, sceneView.CameraTarget);
const ImVec2 viewCubePos = ImVec2(
viewportState.Left + viewportState.Width - 110.0F,
viewportState.Top + 20.0F
);
glm::mat4 cubeViewMatrix = glm::lookAt(sceneView.CameraPosition, sceneView.CameraTarget, sceneView.CameraUp);
const glm::mat4 originalCubeViewMatrix = cubeViewMatrix;
ImGuizmo::ViewManipulate(
glm::value_ptr(cubeViewMatrix),
cameraDistance,
viewCubePos,
ImVec2(80, 80),
0x10101010
);
if (cubeViewMatrix != originalCubeViewMatrix) {
const glm::mat4 cubeCameraWorldMatrix = glm::inverse(cubeViewMatrix);
MetaCoreSceneView updatedView = sceneView;
updatedView.CameraPosition = glm::vec3(cubeCameraWorldMatrix[3]);
EditorContext_->GetCameraController().ApplySceneView(updatedView);
}
}
MetaCoreDrawViewportGridOverlay(*EditorContext_, viewportState);
MetaCoreDrawWorldOriginOverlay(*EditorContext_, sceneView, viewportState);
MetaCoreDrawSelectionBoundsOverlay(*EditorContext_, Scene_, sceneView, viewportState);
MetaCoreDrawSelectionHierarchyOverlay(*EditorContext_, Scene_, sceneView, viewportState);
MetaCoreDrawSceneViewportDropTarget(*EditorContext_, viewportPos, viewportSize);
if (viewportState.Hovered && !gizmoHovering && !gizmoUsing && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
const MetaCoreId pickedObjectId = SceneInteractionService_.PickGameObjectFromViewport(
Scene_,
sceneView,
viewportState,
EditorContext_->GetInput().GetCursorPosition()
);
SceneInteractionService_.ApplyViewportSelection(*EditorContext_, pickedObjectId);
}
drewSceneViewport = true;
}
ImGui::End();
ImGui::PopStyleVar();
if (drewSceneViewport) {
SceneInteractionService_.HandleGizmoEndUse(*EditorContext_, gizmoUsing);
} else {
SceneInteractionService_.ResetFrameState();
ViewportRenderer_.SetViewportRect(MetaCoreViewportRect{});
}
}
void MetaCoreEditorApp::DrawGameViewWindow() {
constexpr ImGuiWindowFlags gameWindowFlags =
ImGuiWindowFlags_NoScrollbar |
ImGuiWindowFlags_NoScrollWithMouse;
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0F, 0.0F));
if (ImGui::Begin("\u6E38\u620F###game_view", nullptr, gameWindowFlags)) {
const ImVec2 origin = ImGui::GetCursorScreenPos();
ImVec2 size = ImGui::GetContentRegionAvail();
if (size.x < 1.0F) {
size.x = 1.0F;
}
if (size.y < 1.0F) {
size.y = 1.0F;
}
ImDrawList* drawList = ImGui::GetWindowDrawList();
drawList->AddRectFilled(origin, ImVec2(origin.x + size.x, origin.y + size.y), IM_COL32(18, 19, 22, 255));
const char* label = "Game View";
const ImVec2 textSize = ImGui::CalcTextSize(label);
drawList->AddText(
ImVec2(origin.x + (size.x - textSize.x) * 0.5F, origin.y + (size.y - textSize.y) * 0.5F),
IM_COL32(170, 178, 190, 255),
label
);
ImGui::Dummy(size);
}
ImGui::End();
ImGui::PopStyleVar();
}
void MetaCoreEditorApp::DrawPlaceholderDockWindow(const char* label, const char* caption) {
constexpr ImGuiWindowFlags placeholderWindowFlags =
ImGuiWindowFlags_NoScrollbar |
ImGuiWindowFlags_NoScrollWithMouse;
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0F, 0.0F));
if (ImGui::Begin(label, nullptr, placeholderWindowFlags)) {
const ImVec2 origin = ImGui::GetCursorScreenPos();
ImVec2 size = ImGui::GetContentRegionAvail();
if (size.x < 1.0F) {
size.x = 1.0F;
}
if (size.y < 1.0F) {
size.y = 1.0F;
}
ImDrawList* drawList = ImGui::GetWindowDrawList();
drawList->AddRectFilled(origin, ImVec2(origin.x + size.x, origin.y + size.y), IM_COL32(24, 25, 28, 255));
const ImVec2 textSize = ImGui::CalcTextSize(caption);
drawList->AddText(
ImVec2(origin.x + (size.x - textSize.x) * 0.5F, origin.y + (size.y - textSize.y) * 0.5F),
IM_COL32(145, 152, 164, 255),
caption
);
ImGui::Dummy(size);
}
ImGui::End();
ImGui::PopStyleVar();
}
void MetaCoreEditorApp::QueueDockTabSelection(std::string windowId) {
if (windowId.empty()) {
return;
}
if (std::find(PendingDockTabSelections_.begin(), PendingDockTabSelections_.end(), windowId) == PendingDockTabSelections_.end()) {
PendingDockTabSelections_.push_back(std::move(windowId));
}
}
void MetaCoreEditorApp::ApplyPendingDockTabSelections() {
if (PendingDockTabSelections_.empty()) {
return;
}
std::vector<std::string> pending;
pending.swap(PendingDockTabSelections_);
for (const std::string& windowId : pending) {
const std::string imguiName = "###" + windowId;
ImGuiWindow* window = ImGui::FindWindowByName(imguiName.c_str());
if (window == nullptr) {
PendingDockTabSelections_.push_back(windowId);
continue;
}
ImGuiDockNode* dockNode = window->DockNode;
if (dockNode != nullptr) {
dockNode->SelectedTabId = window->TabId;
dockNode->VisibleWindow = window;
if (dockNode->TabBar != nullptr) {
dockNode->TabBar->SelectedTabId = window->TabId;
dockNode->TabBar->NextSelectedTabId = window->TabId;
dockNode->TabBar->VisibleTabId = window->TabId;
}
ImGui::MarkIniSettingsDirty(window);
}
ImGui::FocusWindow(window);
}
}
void MetaCoreEditorApp::EnsureDefaultDockLayout(unsigned int dockSpaceId) {
if (EditorContext_->HasDockLayoutBuilt()) {
return;
}
const ImGuiViewport* viewport = ImGui::GetMainViewport();
constexpr float statusBarHeight = 24.0F;
ImGui::DockBuilderRemoveNode(dockSpaceId);
ImGui::DockBuilderAddNode(dockSpaceId, ImGuiDockNodeFlags_DockSpace);
ImGui::DockBuilderSetNodeSize(dockSpaceId, ImGui::GetMainViewport()->Size);
ImGui::DockBuilderSetNodePos(dockSpaceId, viewport->WorkPos);
ImGui::DockBuilderSetNodeSize(
dockSpaceId,
ImVec2(viewport->WorkSize.x, viewport->WorkSize.y - statusBarHeight)
);
ImGuiID mainNodeId = dockSpaceId;
const ImGuiID leftNodeId = ImGui::DockBuilderSplitNode(mainNodeId, ImGuiDir_Left, 0.18F, nullptr, &mainNodeId);
const ImGuiID rightNodeId = ImGui::DockBuilderSplitNode(mainNodeId, ImGuiDir_Right, 0.24F, nullptr, &mainNodeId);
const ImGuiID bottomNodeId = ImGui::DockBuilderSplitNode(mainNodeId, ImGuiDir_Down, 0.28F, nullptr, &mainNodeId);
ImGuiID dockMain = 0;
ImGuiID dockRight = 0;
ImGui::DockBuilderSplitNode(dockSpaceId, ImGuiDir_Right, 0.25F, &dockRight, &dockMain);
ImGui::DockBuilderDockWindow("\u5C42\u7EA7", leftNodeId);
ImGui::DockBuilderDockWindow("\u68C0\u67E5\u5668", rightNodeId);
ImGui::DockBuilderDockWindow("\u9879\u76EE", bottomNodeId);
ImGui::DockBuilderDockWindow("\u63A7\u5236\u53F0", bottomNodeId);
ImGuiID dockTop = 0;
ImGuiID dockBottom = 0;
ImGui::DockBuilderSplitNode(dockMain, ImGuiDir_Down, 0.30F, &dockBottom, &dockTop);
ImGuiID dockLeft = 0;
ImGuiID dockCenterTop = 0;
ImGui::DockBuilderSplitNode(dockTop, ImGuiDir_Left, 0.20F, &dockLeft, &dockCenterTop);
ImGuiID dockToolbar = 0;
ImGuiID dockScene = 0;
ImGui::DockBuilderSplitNode(dockCenterTop, ImGuiDir_Up, 0.04F, &dockToolbar, &dockScene);
ImGui::DockBuilderSetNodeSize(dockToolbar, ImVec2(viewport->WorkSize.x, 36.0F));
if (ImGuiDockNode* toolbarNode = ImGui::DockBuilderGetNode(dockToolbar); toolbarNode != nullptr) {
toolbarNode->SetLocalFlags(
toolbarNode->LocalFlags |
ImGuiDockNodeFlags_NoTabBar |
ImGuiDockNodeFlags_NoDockingSplit |
ImGuiDockNodeFlags_NoResize |
ImGuiDockNodeFlags_NoUndocking
);
}
ImGui::DockBuilderDockWindow("###hierarchy", dockLeft);
ImGui::DockBuilderDockWindow("###inspector", dockRight);
ImGui::DockBuilderDockWindow("###toolbar", dockToolbar);
ImGui::DockBuilderDockWindow("###scene_view", dockScene);
ImGui::DockBuilderDockWindow("###game_view", dockScene);
ImGui::DockBuilderDockWindow("###ui_editor", dockScene);
ImGui::DockBuilderDockWindow("###animclip2d_editor", dockScene);
ImGui::DockBuilderDockWindow("###animfsm_editor", dockScene);
ImGui::DockBuilderDockWindow("###console", dockBottom);
ImGui::DockBuilderDockWindow("###project", dockBottom);
ImGui::DockBuilderFinish(dockSpaceId);
QueueDockTabSelection("scene_view");
EditorContext_->SetDockLayoutBuilt(true);
}

View File

@ -11,6 +11,8 @@
#include "MetaCorePlatform/MetaCoreWindow.h"
#include "MetaCoreRender/MetaCoreEditorViewportRenderer.h"
#include "MetaCoreRender/MetaCoreRenderDevice.h"
#include "MetaCoreScene/MetaCoreComponents.h"
#include "MetaCoreScene/MetaCoreSceneSerializer.h"
#include <algorithm>
#include <cmath>
@ -159,15 +161,242 @@ private:
return registry;
}
[[nodiscard]] MetaCoreRuntimeProjectDocument MetaCoreBuildDefaultRuntimeProjectDocument() {
MetaCoreRuntimeProjectDocument document;
document.StartupScenePath = std::filesystem::path("Scenes") / "Main.mcscene.json";
document.DataSourcesPath = std::filesystem::path("Runtime") / "DataSources.mcruntime";
document.BindingsPath = std::filesystem::path("Runtime") / "Bindings.mcruntime";
document.DiagnosticsPath = std::filesystem::path("Runtime") / "Diagnostics.mcruntimestate";
[[nodiscard]] MetaCoreRuntimeProjectDocument MetaCoreLoadRuntimeProjectDocument(
const std::filesystem::path& runtimeDirectory,
const std::filesystem::path& runtimeDirectoryRelative = std::filesystem::path("Runtime")
) {
const MetaCoreTypeRegistry runtimeRegistry = MetaCoreBuildRuntimeDataTypeRegistry();
MetaCoreRuntimeProjectDocument document = MetaCoreReadRuntimeProjectDocument(
runtimeDirectory / "ProjectRuntime.mcruntimecfg",
runtimeRegistry
).value_or(MetaCoreBuildDefaultRuntimeProjectDocument(runtimeDirectoryRelative));
MetaCoreApplyRuntimeProjectDefaults(document, runtimeDirectoryRelative);
return document;
}
[[nodiscard]] MetaCoreTypeRegistry MetaCoreBuildUiDocumentTypeRegistry() {
MetaCoreTypeRegistry registry;
MetaCoreRegisterFoundationGeneratedTypes(registry);
MetaCoreRegisterSceneGeneratedTypes(registry);
return registry;
}
[[nodiscard]] std::filesystem::path MetaCoreResolveProjectPath(
const std::filesystem::path& projectRoot,
const std::filesystem::path& path
) {
return path.is_absolute() ? path : (projectRoot / path).lexically_normal();
}
[[nodiscard]] const std::string* MetaCoreFindRuntimeDataSourceSetting(
const MetaCoreDataSourceDefinition& sourceDefinition,
std::string_view key
) {
const auto iterator = std::find_if(
sourceDefinition.ConnectionSettings.begin(),
sourceDefinition.ConnectionSettings.end(),
[key](const MetaCoreDataSourceSetting& setting) {
return setting.Key == key;
}
);
return iterator == sourceDefinition.ConnectionSettings.end() ? nullptr : &iterator->Value;
}
[[nodiscard]] std::vector<MetaCoreRuntimeConfigIssue> MetaCoreValidateRuntimeDataReplayFilePaths(
const MetaCoreProjectDescriptor& project,
const MetaCoreRuntimeDataSourcesDocument& sourcesDocument
) {
std::vector<MetaCoreRuntimeConfigIssue> issues;
for (const MetaCoreDataSourceDefinition& sourceDefinition : sourcesDocument.Sources) {
if (sourceDefinition.AdapterType != "file_replay") {
continue;
}
const std::string* replayFilePath = MetaCoreFindRuntimeDataSourceSetting(sourceDefinition, "file_path");
if (replayFilePath == nullptr || replayFilePath->empty()) {
continue;
}
const std::filesystem::path relativeReplayPath(*replayFilePath);
if (MetaCoreIsUnsafeRuntimeProjectPath(relativeReplayPath)) {
issues.push_back(MetaCoreRuntimeConfigIssue{
MetaCoreRuntimeConfigIssueSeverity::Error,
sourceDefinition.Id,
"file_replay file_path must be a project-relative path without parent traversal"
});
continue;
}
if (!relativeReplayPath.is_absolute()) {
std::error_code errorCode;
if (!std::filesystem::exists(project.RootPath / relativeReplayPath.lexically_normal(), errorCode)) {
issues.push_back(MetaCoreRuntimeConfigIssue{
MetaCoreRuntimeConfigIssueSeverity::Warning,
sourceDefinition.Id,
"file_replay file_path does not exist yet: " + relativeReplayPath.generic_string()
});
}
}
}
return issues;
}
[[nodiscard]] const char* MetaCoreRequiredComponentForRuntimeBindingTarget(MetaCoreRuntimeBindingTarget target) {
switch (target) {
case MetaCoreRuntimeBindingTarget::TransformPosition:
return "Transform";
case MetaCoreRuntimeBindingTarget::MeshRendererVisible:
case MetaCoreRuntimeBindingTarget::MeshRendererBaseColor:
return "MeshRenderer";
case MetaCoreRuntimeBindingTarget::LightIntensity:
case MetaCoreRuntimeBindingTarget::LightColor:
return "Light";
}
return "Component";
}
[[nodiscard]] bool MetaCoreRuntimeBindingTargetHasRequiredComponent(
const MetaCoreGameObject& gameObject,
MetaCoreRuntimeBindingTarget target
) {
switch (target) {
case MetaCoreRuntimeBindingTarget::TransformPosition:
return gameObject.HasComponent<MetaCoreTransformComponent>();
case MetaCoreRuntimeBindingTarget::MeshRendererVisible:
case MetaCoreRuntimeBindingTarget::MeshRendererBaseColor:
return gameObject.HasComponent<MetaCoreMeshRendererComponent>();
case MetaCoreRuntimeBindingTarget::LightIntensity:
case MetaCoreRuntimeBindingTarget::LightColor:
return gameObject.HasComponent<MetaCoreLightComponent>();
}
return false;
}
[[nodiscard]] std::vector<MetaCoreRuntimeConfigIssue> MetaCoreValidateRuntimeSceneBindingsAgainstScene(
const MetaCoreScene& scene,
const MetaCoreRuntimeBindingsDocument& bindingsDocument
) {
std::vector<MetaCoreRuntimeConfigIssue> issues;
for (const MetaCoreSceneBindingDefinition& binding : bindingsDocument.Bindings) {
if (binding.TargetObjectId == 0) {
continue;
}
const MetaCoreGameObject targetObject = scene.FindGameObject(binding.TargetObjectId);
if (!targetObject) {
issues.push_back(MetaCoreRuntimeConfigIssue{
MetaCoreRuntimeConfigIssueSeverity::Error,
binding.BindingId,
"Target scene object does not exist: " + std::to_string(binding.TargetObjectId)
});
continue;
}
if (!MetaCoreRuntimeBindingTargetHasRequiredComponent(targetObject, binding.Target)) {
issues.push_back(MetaCoreRuntimeConfigIssue{
MetaCoreRuntimeConfigIssueSeverity::Error,
binding.BindingId,
"Target scene object does not have required component: " +
std::string(MetaCoreRequiredComponentForRuntimeBindingTarget(binding.Target))
});
}
}
return issues;
}
[[nodiscard]] bool MetaCoreRuntimeDataHasUiBindings(
const MetaCoreRuntimeBindingsDocument& bindingsDocument
) {
return std::any_of(
bindingsDocument.UiBindings.begin(),
bindingsDocument.UiBindings.end(),
[](const MetaCoreUiBindingDefinition& binding) {
return binding.Target == MetaCoreRuntimeUiBindingTarget::Text;
}
);
}
[[nodiscard]] std::vector<MetaCoreRuntimeConfigIssue> MetaCoreValidateRuntimeUiBindingsAgainstStartupUi(
const MetaCoreProjectDescriptor& project,
const MetaCoreRuntimeProjectDocument& runtimeProjectDocument,
const MetaCoreRuntimeBindingsDocument& bindingsDocument
) {
std::vector<MetaCoreRuntimeConfigIssue> issues;
if (!MetaCoreRuntimeDataHasUiBindings(bindingsDocument)) {
return issues;
}
if (runtimeProjectDocument.StartupUiPath.empty()) {
issues.push_back(MetaCoreRuntimeConfigIssue{
MetaCoreRuntimeConfigIssueSeverity::Error,
"RuntimeProject",
"Startup UI is required when RuntimeData UI bindings are configured"
});
return issues;
}
if (MetaCoreIsUnsafeRuntimeProjectPath(runtimeProjectDocument.StartupUiPath)) {
issues.push_back(MetaCoreRuntimeConfigIssue{
MetaCoreRuntimeConfigIssueSeverity::Error,
"RuntimeProject",
"Startup UI must be a project-relative path without parent traversal"
});
return issues;
}
const std::filesystem::path startupUiPath =
MetaCoreResolveProjectPath(project.RootPath, runtimeProjectDocument.StartupUiPath);
if (!std::filesystem::exists(startupUiPath)) {
issues.push_back(MetaCoreRuntimeConfigIssue{
MetaCoreRuntimeConfigIssueSeverity::Error,
"RuntimeProject",
"Startup UI does not exist: " + runtimeProjectDocument.StartupUiPath.generic_string()
});
return issues;
}
const MetaCoreTypeRegistry uiRegistry = MetaCoreBuildUiDocumentTypeRegistry();
const auto startupUiDocument = MetaCoreSceneSerializer::LoadUiFromJson(startupUiPath, uiRegistry);
if (!startupUiDocument.has_value()) {
issues.push_back(MetaCoreRuntimeConfigIssue{
MetaCoreRuntimeConfigIssueSeverity::Error,
"RuntimeProject",
"Startup UI is unreadable: " + runtimeProjectDocument.StartupUiPath.generic_string()
});
return issues;
}
for (const MetaCoreUiBindingDefinition& binding : bindingsDocument.UiBindings) {
if (binding.Target != MetaCoreRuntimeUiBindingTarget::Text) {
continue;
}
const auto nodeIterator = std::find_if(
startupUiDocument->Nodes.begin(),
startupUiDocument->Nodes.end(),
[&](const MetaCoreUiNodeDocument& node) {
return node.Id == binding.TargetNodeId;
}
);
if (nodeIterator == startupUiDocument->Nodes.end()) {
issues.push_back(MetaCoreRuntimeConfigIssue{
MetaCoreRuntimeConfigIssueSeverity::Error,
binding.BindingId,
"Target UI node does not exist in Startup UI: " + binding.TargetNodeId
});
continue;
}
if (nodeIterator->Type != MetaCoreUiNodeType::Text) {
issues.push_back(MetaCoreRuntimeConfigIssue{
MetaCoreRuntimeConfigIssueSeverity::Error,
binding.BindingId,
"Target UI node is not a Text node: " + binding.TargetNodeId
});
}
}
return issues;
}
} // namespace
MetaCoreEditorContext::MetaCoreEditorContext(
@ -339,6 +568,9 @@ MetaCoreGizmoOperation MetaCoreEditorContext::GetGizmoOperation() const { return
void MetaCoreEditorContext::SetGizmoOperation(MetaCoreGizmoOperation operation) { GizmoOperation_ = operation; }
MetaCoreGizmoMode MetaCoreEditorContext::GetGizmoMode() const { return GizmoMode_; }
void MetaCoreEditorContext::SetGizmoMode(MetaCoreGizmoMode mode) { GizmoMode_ = mode; }
const MetaCoreGizmoSnapSettings& MetaCoreEditorContext::GetGizmoSnapSettings() const { return GizmoSnapSettings_; }
void MetaCoreEditorContext::SetGizmoSnapSettings(const MetaCoreGizmoSnapSettings& settings) { GizmoSnapSettings_ = settings; }
void MetaCoreEditorContext::SetGizmoSnapEnabled(bool enabled) { GizmoSnapSettings_.Enabled = enabled; }
bool MetaCoreEditorContext::GetShowViewportGrid() const { return ShowViewportGrid_; }
void MetaCoreEditorContext::SetShowViewportGrid(bool show) { ShowViewportGrid_ = show; }
bool MetaCoreEditorContext::GetShowWorldOrigin() const { return ShowWorldOrigin_; }
@ -460,7 +692,10 @@ bool MetaCoreEditorContext::EnsureRuntimeDataConfigLoaded() {
return false;
}
const MetaCoreProjectDescriptor& project = assetDatabaseService->GetProjectDescriptor();
const std::filesystem::path runtimeDirectory = ResolveRuntimeDirectory();
const std::filesystem::path runtimeDirectoryRelative =
MetaCoreBuildRuntimeDirectoryRelativePath(project.RootPath, runtimeDirectory);
std::error_code errorCode;
std::filesystem::create_directories(runtimeDirectory, errorCode);
@ -469,12 +704,29 @@ bool MetaCoreEditorContext::EnsureRuntimeDataConfigLoaded() {
runtimeDirectory / "ProjectRuntime.mcruntimecfg",
runtimeRegistry
);
MetaCoreRuntimeProjectDocument runtimeProjectDocument =
loadedProjectRuntime.value_or(MetaCoreBuildDefaultRuntimeProjectDocument(runtimeDirectoryRelative));
MetaCoreApplyRuntimeProjectDefaults(runtimeProjectDocument, runtimeDirectoryRelative);
const auto runtimeProjectPathIssues = MetaCoreValidateRuntimeProjectPaths(runtimeProjectDocument);
const bool hasRuntimeProjectPathErrors = std::any_of(
runtimeProjectPathIssues.begin(),
runtimeProjectPathIssues.end(),
[](const MetaCoreRuntimeConfigIssue& issue) {
return issue.Severity == MetaCoreRuntimeConfigIssueSeverity::Error;
}
);
for (const MetaCoreRuntimeConfigIssue& issue : runtimeProjectPathIssues) {
AddConsoleMessage(MetaCoreLogLevel::Error, "RuntimeData", issue.Scope + ": " + issue.Message);
}
if (hasRuntimeProjectPathErrors) {
return false;
}
const auto loadedSources = MetaCoreReadRuntimeDataSourcesDocument(
runtimeDirectory / "DataSources.mcruntime",
MetaCoreResolveProjectPath(project.RootPath, runtimeProjectDocument.DataSourcesPath),
runtimeRegistry
);
const auto loadedBindings = MetaCoreReadRuntimeBindingsDocument(
runtimeDirectory / "Bindings.mcruntime",
MetaCoreResolveProjectPath(project.RootPath, runtimeProjectDocument.BindingsPath),
runtimeRegistry
);
@ -491,6 +743,99 @@ bool MetaCoreEditorContext::EnsureRuntimeDataConfigLoaded() {
return true;
}
std::vector<MetaCoreRuntimeConfigIssue> MetaCoreEditorContext::ValidateRuntimeDataConfig() {
std::vector<MetaCoreRuntimeConfigIssue> validationIssues;
if (!EnsureRuntimeDataConfigLoaded()) {
validationIssues.push_back(MetaCoreRuntimeConfigIssue{
MetaCoreRuntimeConfigIssueSeverity::Error,
"RuntimeData",
"RuntimeData config is not available"
});
return validationIssues;
}
const auto assetDatabaseService = ModuleRegistry_.ResolveService<MetaCoreIAssetDatabaseService>();
if (assetDatabaseService == nullptr || !assetDatabaseService->HasProject()) {
validationIssues.push_back(MetaCoreRuntimeConfigIssue{
MetaCoreRuntimeConfigIssueSeverity::Error,
"RuntimeData",
"Project is not available"
});
return validationIssues;
}
const std::filesystem::path runtimeDirectory = ResolveRuntimeDirectory();
const std::filesystem::path runtimeDirectoryRelative = MetaCoreBuildRuntimeDirectoryRelativePath(
assetDatabaseService->GetProjectDescriptor().RootPath,
runtimeDirectory
);
MetaCoreRuntimeProjectDocument runtimeProjectDocument =
MetaCoreLoadRuntimeProjectDocument(runtimeDirectory, runtimeDirectoryRelative);
validationIssues = MetaCoreValidateRuntimeDataDocuments(RuntimeDataSourcesDocument_, RuntimeBindingsDocument_);
auto runtimeProjectPathIssues = MetaCoreValidateRuntimeProjectPaths(runtimeProjectDocument);
validationIssues.insert(
validationIssues.end(),
std::make_move_iterator(runtimeProjectPathIssues.begin()),
std::make_move_iterator(runtimeProjectPathIssues.end())
);
auto replayPathIssues = MetaCoreValidateRuntimeDataReplayFilePaths(
assetDatabaseService->GetProjectDescriptor(),
RuntimeDataSourcesDocument_
);
validationIssues.insert(
validationIssues.end(),
std::make_move_iterator(replayPathIssues.begin()),
std::make_move_iterator(replayPathIssues.end())
);
auto sceneBindingIssues = MetaCoreValidateRuntimeSceneBindingsAgainstScene(Scene_, RuntimeBindingsDocument_);
validationIssues.insert(
validationIssues.end(),
std::make_move_iterator(sceneBindingIssues.begin()),
std::make_move_iterator(sceneBindingIssues.end())
);
auto uiValidationIssues = MetaCoreValidateRuntimeUiBindingsAgainstStartupUi(
assetDatabaseService->GetProjectDescriptor(),
runtimeProjectDocument,
RuntimeBindingsDocument_
);
validationIssues.insert(
validationIssues.end(),
std::make_move_iterator(uiValidationIssues.begin()),
std::make_move_iterator(uiValidationIssues.end())
);
return validationIssues;
}
std::optional<MetaCoreRuntimeDiagnosticsSnapshot> MetaCoreEditorContext::LoadRuntimeDiagnosticsSnapshot() const {
const auto assetDatabaseService = ModuleRegistry_.ResolveService<MetaCoreIAssetDatabaseService>();
if (assetDatabaseService == nullptr || !assetDatabaseService->HasProject()) {
return std::nullopt;
}
const std::filesystem::path runtimeDirectory = ResolveRuntimeDirectory();
const std::filesystem::path runtimeDirectoryRelative = MetaCoreBuildRuntimeDirectoryRelativePath(
assetDatabaseService->GetProjectDescriptor().RootPath,
runtimeDirectory
);
const MetaCoreRuntimeProjectDocument runtimeProjectDocument =
MetaCoreLoadRuntimeProjectDocument(runtimeDirectory, runtimeDirectoryRelative);
if (MetaCoreIsUnsafeRuntimeProjectPath(runtimeProjectDocument.DiagnosticsPath)) {
return std::nullopt;
}
const MetaCoreTypeRegistry runtimeRegistry = MetaCoreBuildRuntimeDataTypeRegistry();
const std::filesystem::path diagnosticsPath = MetaCoreResolveProjectPath(
assetDatabaseService->GetProjectDescriptor().RootPath,
runtimeProjectDocument.DiagnosticsPath
);
return MetaCoreReadRuntimeDiagnosticsSnapshot(diagnosticsPath, runtimeRegistry);
}
bool MetaCoreEditorContext::SaveRuntimeDataConfig() {
if (!EnsureRuntimeDataConfigLoaded()) {
return false;
@ -506,7 +851,13 @@ bool MetaCoreEditorContext::SaveRuntimeDataConfig() {
std::error_code errorCode;
std::filesystem::create_directories(runtimeDirectory, errorCode);
const auto validationIssues = MetaCoreValidateRuntimeDataDocuments(RuntimeDataSourcesDocument_, RuntimeBindingsDocument_);
const std::filesystem::path runtimeDirectoryRelative = MetaCoreBuildRuntimeDirectoryRelativePath(
assetDatabaseService->GetProjectDescriptor().RootPath,
runtimeDirectory
);
MetaCoreRuntimeProjectDocument runtimeProjectDocument =
MetaCoreLoadRuntimeProjectDocument(runtimeDirectory, runtimeDirectoryRelative);
auto validationIssues = ValidateRuntimeDataConfig();
const bool hasValidationErrors = std::any_of(validationIssues.begin(), validationIssues.end(), [](const MetaCoreRuntimeConfigIssue& issue) {
return issue.Severity == MetaCoreRuntimeConfigIssueSeverity::Error;
});
@ -523,19 +874,28 @@ bool MetaCoreEditorContext::SaveRuntimeDataConfig() {
}
const MetaCoreTypeRegistry runtimeRegistry = MetaCoreBuildRuntimeDataTypeRegistry();
const MetaCoreRuntimeProjectDocument runtimeProjectDocument = MetaCoreBuildDefaultRuntimeProjectDocument();
const bool wroteProjectRuntime = MetaCoreWriteRuntimeProjectDocument(
runtimeDirectory / "ProjectRuntime.mcruntimecfg",
runtimeProjectDocument,
runtimeRegistry
);
const std::filesystem::path sourcesPath = MetaCoreResolveProjectPath(
assetDatabaseService->GetProjectDescriptor().RootPath,
runtimeProjectDocument.DataSourcesPath
);
const std::filesystem::path bindingsPath = MetaCoreResolveProjectPath(
assetDatabaseService->GetProjectDescriptor().RootPath,
runtimeProjectDocument.BindingsPath
);
std::filesystem::create_directories(sourcesPath.parent_path(), errorCode);
std::filesystem::create_directories(bindingsPath.parent_path(), errorCode);
const bool wroteSources = MetaCoreWriteRuntimeDataSourcesDocument(
runtimeDirectory / "DataSources.mcruntime",
sourcesPath,
RuntimeDataSourcesDocument_,
runtimeRegistry
);
const bool wroteBindings = MetaCoreWriteRuntimeBindingsDocument(
runtimeDirectory / "Bindings.mcruntime",
bindingsPath,
RuntimeBindingsDocument_,
runtimeRegistry
);
@ -646,7 +1006,8 @@ void MetaCoreEditorContext::RefreshSceneDirtyState() {
std::filesystem::path MetaCoreEditorContext::ResolveRuntimeDirectory() const {
if (const auto assetDatabaseService = ModuleRegistry_.ResolveService<MetaCoreIAssetDatabaseService>();
assetDatabaseService != nullptr && assetDatabaseService->HasProject()) {
return assetDatabaseService->GetProjectDescriptor().RootPath / "Runtime";
const MetaCoreProjectDescriptor& project = assetDatabaseService->GetProjectDescriptor();
return !project.RuntimePath.empty() ? project.RuntimePath : (project.RootPath / "Runtime");
}
return std::filesystem::current_path() / "Runtime";
}

View File

@ -24,6 +24,7 @@
#include <algorithm>
#include <limits>
#include <unordered_map>
#include <unordered_set>
namespace MetaCore {
namespace {
@ -51,6 +52,55 @@ glm::mat4 MetaCoreBuildWorldTransformMatrix(const MetaCoreScene& scene, MetaCore
// 使用 MetaCoreTransformUtils.h 中的 MetaCoreApplyMatrixToTransform
std::vector<MetaCoreId> MetaCoreBuildSelectionTransformRoots(
const MetaCoreScene& scene,
const std::vector<MetaCoreId>& selectedObjectIds,
MetaCoreId manipulatedObjectId
) {
std::vector<MetaCoreId> candidateIds;
candidateIds.reserve(selectedObjectIds.empty() ? 1 : selectedObjectIds.size());
const bool manipulatedIsSelected =
std::find(selectedObjectIds.begin(), selectedObjectIds.end(), manipulatedObjectId) != selectedObjectIds.end();
if (manipulatedIsSelected) {
candidateIds = selectedObjectIds;
} else if (manipulatedObjectId != 0) {
candidateIds.push_back(manipulatedObjectId);
}
std::unordered_set<MetaCoreId> candidateSet(candidateIds.begin(), candidateIds.end());
std::vector<MetaCoreId> rootIds;
rootIds.reserve(candidateIds.size());
for (MetaCoreId objectId : candidateIds) {
if (objectId == 0 || !scene.FindGameObject(objectId)) {
continue;
}
bool hasSelectedAncestor = false;
MetaCoreGameObject object = scene.FindGameObject(objectId);
MetaCoreId parentId = object ? object.GetParentId() : 0;
while (parentId != 0) {
if (candidateSet.contains(parentId)) {
hasSelectedAncestor = true;
break;
}
const MetaCoreGameObject parentObject = scene.FindGameObject(parentId);
if (!parentObject) {
break;
}
parentId = parentObject.GetParentId();
}
if (!hasSelectedAncestor) {
rootIds.push_back(objectId);
}
}
return rootIds;
}
ImGuizmo::OPERATION MetaCoreToImGuizmoOperation(MetaCoreGizmoOperation operation) {
switch (operation) {
case MetaCoreGizmoOperation::Translate: return ImGuizmo::TRANSLATE;
@ -346,6 +396,57 @@ void MetaCoreSceneInteractionService::FocusVisibleScene(MetaCoreEditorContext& e
editorContext.GetCameraController().FocusBounds(center, radius);
}
bool MetaCoreSceneInteractionService::ApplyWorldTransformDeltaToSelection(
MetaCoreEditorContext& editorContext,
MetaCoreId manipulatedObjectId,
const glm::mat4& originalWorldMatrix,
const glm::mat4& newWorldMatrix
) const {
if (manipulatedObjectId == 0 || !editorContext.GetScene().FindGameObject(manipulatedObjectId)) {
return false;
}
if (newWorldMatrix == originalWorldMatrix) {
return false;
}
const glm::mat4 deltaMatrix = newWorldMatrix * glm::inverse(originalWorldMatrix);
const std::vector<MetaCoreId> rootIds = MetaCoreBuildSelectionTransformRoots(
editorContext.GetScene(),
editorContext.GetSelectedObjectIds(),
manipulatedObjectId
);
if (rootIds.empty()) {
return false;
}
bool changed = false;
for (MetaCoreId objectId : rootIds) {
MetaCoreGameObject object = editorContext.GetScene().FindGameObject(objectId);
if (!object) {
continue;
}
const glm::mat4 currentWorldMatrix = MetaCoreBuildWorldTransformMatrix(editorContext.GetScene(), objectId);
const glm::mat4 targetWorldMatrix =
objectId == manipulatedObjectId ? newWorldMatrix : deltaMatrix * currentWorldMatrix;
glm::mat4 targetLocalMatrix = targetWorldMatrix;
if (object.GetParentId() != 0) {
const glm::mat4 parentWorldMatrix = MetaCoreBuildWorldTransformMatrix(editorContext.GetScene(), object.GetParentId());
targetLocalMatrix = glm::inverse(parentWorldMatrix) * targetWorldMatrix;
}
MetaCoreApplyMatrixToTransform(targetLocalMatrix, object.GetComponent<MetaCoreTransformComponent>());
changed = true;
}
if (changed) {
editorContext.GetScene().IncrementRevision();
}
return changed;
}
void MetaCoreSceneInteractionService::HandleGizmoManipulation(MetaCoreEditorContext& editorContext) {
if (editorContext.GetGizmoOperation() == MetaCoreGizmoOperation::None) {
return;
@ -391,28 +492,31 @@ void MetaCoreSceneInteractionService::HandleGizmoManipulation(MetaCoreEditorCont
ImGuizmo::PushID(reinterpret_cast<void*>(selectedObject.GetId()));
glm::mat4 gizmoMatrix = worldMatrixMetaCore;
const MetaCoreGizmoSnapSettings& snapSettings = editorContext.GetGizmoSnapSettings();
const float snapStep = snapSettings.StepForOperation(currentOp);
const float snapValues[3] = {snapStep, snapStep, snapStep};
ImGuizmo::Manipulate(
glm::value_ptr(gizmoViewMatrix),
glm::value_ptr(gizmoProjectionMatrix),
MetaCoreToImGuizmoOperation(currentOp),
effectiveMode,
glm::value_ptr(gizmoMatrix)
glm::value_ptr(gizmoMatrix),
nullptr,
snapSettings.Enabled ? snapValues : nullptr
);
const bool gizmoUsing = ImGuizmo::IsUsing();
ImGuizmo::PopID();
if (gizmoUsing && gizmoMatrix != originalWorldMatrixMetaCore) {
glm::mat4 newLocalMatrix = gizmoMatrix;
if (selectedObject.GetParentId() != 0) {
const glm::mat4 parentWorldMatrixMetaCore = MetaCoreBuildWorldTransformMatrix(editorContext.GetScene(), selectedObject.GetParentId());
newLocalMatrix = glm::inverse(parentWorldMatrixMetaCore) * gizmoMatrix;
}
MetaCoreApplyMatrixToTransform(newLocalMatrix, selectedObject.GetComponent<MetaCoreTransformComponent>());
}
HandleGizmoBeginUse(editorContext, gizmoUsing);
if (gizmoUsing && gizmoMatrix != originalWorldMatrixMetaCore) {
(void)ApplyWorldTransformDeltaToSelection(
editorContext,
selectedObject.GetId(),
originalWorldMatrixMetaCore,
gizmoMatrix
);
}
HandleGizmoEndUse(editorContext, gizmoUsing);
}
@ -452,6 +556,31 @@ void MetaCoreSceneInteractionService::DrawViewportToolbar(MetaCoreEditorContext&
return clicked;
};
if (const auto playModeService = editorContext.GetModuleRegistry().ResolveService<MetaCoreIPlayModeService>();
playModeService != nullptr) {
const MetaCorePlayModeState playState = playModeService->GetState();
if (playState == MetaCorePlayModeState::Edit) {
if (drawToolbarToggle(" Play ", false)) {
(void)playModeService->EnterPlayMode(editorContext);
}
} else {
if (drawToolbarToggle(" Stop ", true)) {
(void)playModeService->ExitPlayMode(editorContext);
}
ImGui::SameLine(0, 2);
if (playState == MetaCorePlayModeState::Playing) {
if (drawToolbarToggle(" Pause ", false)) {
(void)playModeService->PausePlayMode(editorContext);
}
} else {
if (drawToolbarToggle(" Resume ", true)) {
(void)playModeService->ResumePlayMode(editorContext);
}
}
}
ImGui::SameLine(0, 12);
}
// Tools (Unity-Style Icons/Labels)
if (drawToolbarToggle(" 视图 ", editorContext.GetGizmoOperation() == MetaCoreGizmoOperation::None)) {
editorContext.SetGizmoOperation(MetaCoreGizmoOperation::None);
@ -480,6 +609,12 @@ void MetaCoreSceneInteractionService::DrawViewportToolbar(MetaCoreEditorContext&
editorContext.SetGizmoMode(MetaCoreGizmoMode::Global);
}
ImGui::SameLine(0, 12);
const MetaCoreGizmoSnapSettings& snapSettings = editorContext.GetGizmoSnapSettings();
if (drawToolbarToggle(" Snap ", snapSettings.Enabled)) {
editorContext.SetGizmoSnapEnabled(!snapSettings.Enabled);
}
ImGui::SameLine(0, 12);
const bool hasSelection = (bool)editorContext.GetSelectedGameObject();
if (!hasSelection) {
@ -565,6 +700,10 @@ void MetaCoreSceneInteractionService::HandleShortcuts(MetaCoreEditorContext& edi
}
}
if (ImGui::IsKeyPressed(ImGuiKey_X)) {
editorContext.SetGizmoSnapEnabled(!editorContext.GetGizmoSnapSettings().Enabled);
}
if (ImGui::IsKeyPressed(ImGuiKey_F) && (bool)editorContext.GetSelectedGameObject()) {
FocusSelectedObject(editorContext);
}

View File

@ -10,7 +10,9 @@
#include "MetaCoreRender/MetaCoreRenderDevice.h"
#include "MetaCoreScene/MetaCoreScene.h"
#include <filesystem>
#include <memory>
#include <string>
#include <vector>
namespace MetaCore {
@ -30,7 +32,15 @@ private:
bool InitializeImGui();
void ShutdownImGui();
void DrawEditorFrame();
void DrawEditorToolbar();
void DrawEditorStatusBar();
void DrawSceneViewWindow();
void DrawGameViewWindow();
void DrawPlaceholderDockWindow(const char* label, const char* caption);
void SyncImGuiLayoutIniPath();
void EnsureDefaultDockLayout(unsigned int dockSpaceId);
void QueueDockTabSelection(std::string windowId);
void ApplyPendingDockTabSelections();
MetaCoreWindow Window_{};
MetaCoreRenderDevice RenderDevice_{};
@ -40,7 +50,10 @@ private:
MetaCoreEditorModuleRegistry ModuleRegistry_{};
MetaCoreSceneInteractionService SceneInteractionService_{};
std::vector<std::unique_ptr<MetaCoreIModule>> Modules_{};
std::vector<std::string> PendingDockTabSelections_{};
std::unique_ptr<MetaCoreEditorContext> EditorContext_{};
std::filesystem::path ImGuiLayoutProjectRoot_{};
std::string ImGuiIniPath_{};
bool Initialized_ = false;
};

View File

@ -74,33 +74,4 @@ struct MetaCoreImportedAssetDocument {
std::uint64_t SourceHash = 0;
};
MC_STRUCT()
struct MetaCoreCookManifestEntry {
MC_GENERATED_BODY()
MC_PROPERTY()
MetaCoreAssetGuid AssetGuid{};
MC_PROPERTY()
std::filesystem::path SourcePackagePath{};
MC_PROPERTY()
std::filesystem::path CookedPath{};
MC_PROPERTY()
std::uint64_t SourceHash = 0;
MC_PROPERTY()
std::uint64_t CookedKey = 0;
};
MC_STRUCT()
struct MetaCoreCookManifestDocument {
MC_GENERATED_BODY()
MC_PROPERTY()
std::vector<MetaCoreCookManifestEntry> Entries{};
};
} // namespace MetaCore

View File

@ -12,6 +12,7 @@
#include <functional>
#include <filesystem>
#include <memory>
#include <optional>
#include <string>
#include <vector>
@ -42,6 +43,25 @@ enum class MetaCoreReparentTransformRule {
KeepLocal
};
struct MetaCoreGizmoSnapSettings {
bool Enabled = false;
float TranslationStep = 0.5F;
float RotationStepDegrees = 15.0F;
float ScaleStep = 0.1F;
[[nodiscard]] float StepForOperation(MetaCoreGizmoOperation operation) const {
switch (operation) {
case MetaCoreGizmoOperation::Rotate:
return RotationStepDegrees;
case MetaCoreGizmoOperation::Scale:
return ScaleStep;
case MetaCoreGizmoOperation::Translate:
default:
return TranslationStep;
}
}
};
MC_STRUCT()
struct MetaCoreEditorStateSnapshot {
MC_GENERATED_BODY()
@ -140,6 +160,9 @@ public:
void SetGizmoOperation(MetaCoreGizmoOperation operation);
[[nodiscard]] MetaCoreGizmoMode GetGizmoMode() const;
void SetGizmoMode(MetaCoreGizmoMode mode);
[[nodiscard]] const MetaCoreGizmoSnapSettings& GetGizmoSnapSettings() const;
void SetGizmoSnapSettings(const MetaCoreGizmoSnapSettings& settings);
void SetGizmoSnapEnabled(bool enabled);
[[nodiscard]] bool GetShowViewportGrid() const;
void SetShowViewportGrid(bool show);
[[nodiscard]] bool GetShowWorldOrigin() const;
@ -164,6 +187,8 @@ public:
[[nodiscard]] MetaCoreId ConsumeRenameRequestObjectId();
void AddConsoleMessage(MetaCoreLogLevel level, const std::string& category, const std::string& message);
[[nodiscard]] bool EnsureRuntimeDataConfigLoaded();
[[nodiscard]] std::vector<MetaCoreRuntimeConfigIssue> ValidateRuntimeDataConfig();
[[nodiscard]] std::optional<MetaCoreRuntimeDiagnosticsSnapshot> LoadRuntimeDiagnosticsSnapshot() const;
[[nodiscard]] bool SaveRuntimeDataConfig();
[[nodiscard]] MetaCoreRuntimeDataSourcesDocument& AccessRuntimeDataSourcesDocument();
[[nodiscard]] MetaCoreRuntimeBindingsDocument& AccessRuntimeBindingsDocument();
@ -200,6 +225,7 @@ private:
MetaCoreId SelectionAnchorId_ = 0;
MetaCoreGizmoOperation GizmoOperation_ = MetaCoreGizmoOperation::Translate;
MetaCoreGizmoMode GizmoMode_ = MetaCoreGizmoMode::Local;
MetaCoreGizmoSnapSettings GizmoSnapSettings_{};
bool ShowViewportGrid_ = true;
bool ShowWorldOrigin_ = true;
MetaCoreReparentTransformRule ReparentTransformRule_ = MetaCoreReparentTransformRule::KeepWorld;

View File

@ -144,6 +144,7 @@ public:
[[nodiscard]] virtual std::vector<std::filesystem::path> GetDirectoriesUnder(const std::filesystem::path& relativeDirectory) const = 0;
[[nodiscard]] virtual std::vector<MetaCoreAssetRecord> GetAssetsUnder(const std::filesystem::path& relativeDirectory) const = 0;
[[nodiscard]] virtual std::optional<MetaCoreAssetRecord> FindAssetByRelativePath(const std::filesystem::path& relativePath) const = 0;
[[nodiscard]] virtual bool ReimportAsset(const MetaCoreAssetGuid& assetGuid) = 0;
virtual bool Refresh() = 0;
virtual bool CreateFolder(const std::filesystem::path& relativeDirectory) = 0;
virtual bool RegisterScenePath(const std::filesystem::path& relativeScenePath, bool makeStartupScene) = 0;
@ -190,6 +191,11 @@ public:
const MetaCoreMaterialAssetDocument& document
) = 0;
[[nodiscard]] virtual bool ApplyMaterialAssetPreviewToScene(
MetaCoreEditorContext& editorContext,
const MetaCoreAssetGuid& materialGuid
) const = 0;
[[nodiscard]] virtual std::optional<MetaCoreResolvedGeneratedAssetRecord> ResolveGeneratedAsset(
const MetaCoreAssetGuid& assetGuid
) const = 0;
@ -202,6 +208,42 @@ public:
[[nodiscard]] virtual std::filesystem::path GetCookedPathForAsset(const MetaCoreAssetGuid& assetGuid) const = 0;
};
struct MetaCoreBuildPlayerPackageRequest {
std::filesystem::path PlayerExecutablePath{};
std::filesystem::path OutputDirectory{};
bool CookBeforePackage = true;
bool CopyLooseProjectContent = true;
bool CopyRuntimeConfig = true;
bool UseCookedAssetsInPackage = false;
};
struct MetaCoreBuildDependencyReportEntry {
MetaCoreAssetGuid AssetGuid{};
MetaCoreAssetGuid ReferencedBy{};
std::string Reason{};
std::string AssetType{};
std::filesystem::path AssetPath{};
std::filesystem::path CookedPath{};
std::string Status{};
std::string Message{};
};
struct MetaCoreBuildPlayerPackageResult {
bool Success = false;
std::filesystem::path OutputRoot{};
std::vector<std::filesystem::path> CopiedFiles{};
std::vector<std::filesystem::path> CookedAssets{};
std::vector<MetaCoreBuildDependencyReportEntry> DependencyReport{};
std::string Error{};
};
class MetaCoreIBuildService : public MetaCoreIEditorService {
public:
[[nodiscard]] virtual MetaCoreBuildPlayerPackageResult BuildPlayerPackage(
const MetaCoreBuildPlayerPackageRequest& request
) = 0;
};
class MetaCoreIScenePersistenceService : public MetaCoreIEditorService {
public:
[[nodiscard]] virtual bool HasOpenScene() const = 0;
@ -272,13 +314,26 @@ public:
) = 0;
[[nodiscard]] virtual bool ApplySelectedPrefabInstance(MetaCoreEditorContext& editorContext) = 0;
[[nodiscard]] virtual bool RevertSelectedPrefabInstance(MetaCoreEditorContext& editorContext) = 0;
[[nodiscard]] virtual bool BreakSelectedPrefabInstance(MetaCoreEditorContext& editorContext) = 0;
};
class MetaCoreIComponentTypeRegistry : public MetaCoreIEditorService {
public:
struct MetaCoreComponentLifecycle {
std::function<void(MetaCoreEditorContext&, MetaCoreGameObject&)> OnStart{};
std::function<void(MetaCoreEditorContext&, MetaCoreGameObject&, float)> OnUpdate{};
std::function<void(MetaCoreEditorContext&, MetaCoreGameObject&)> OnDestroy{};
std::function<void(MetaCoreEditorContext&, MetaCoreGameObject&)> OnDrawGizmos{};
std::function<void(MetaCoreEditorContext&, MetaCoreGameObject&)> OnDrawGizmosSelected{};
};
struct MetaCoreComponentDescriptor {
std::string TypeId{};
std::string DisplayName{};
std::string Category{};
const MetaCoreStructDescriptor* ReflectedType = nullptr;
std::function<void*(MetaCoreGameObject&)> MutableComponent{};
std::function<const void*(const MetaCoreGameObject&)> ConstComponent{};
std::function<bool(const MetaCoreGameObject&)> HasComponent{};
std::function<bool(MetaCoreGameObject&)> AddComponent{};
std::function<bool(MetaCoreGameObject&)> RemoveComponent{};
@ -286,11 +341,13 @@ public:
std::function<std::optional<std::vector<std::byte>>(const MetaCoreGameObject&, const MetaCoreTypeRegistry&)> CopyComponentPayload{};
std::function<bool(MetaCoreGameObject&, std::span<const std::byte>, const MetaCoreTypeRegistry&)> PasteComponentPayload{};
std::function<void(MetaCoreEditorContext&, MetaCoreGameObject&)> DrawInspector{};
MetaCoreComponentLifecycle Lifecycle{};
};
[[nodiscard]] virtual std::vector<std::string> GetRegisteredComponentTypeIds() const = 0;
[[nodiscard]] virtual const std::vector<MetaCoreComponentDescriptor>& GetComponentDescriptors() const = 0;
[[nodiscard]] virtual const MetaCoreComponentDescriptor* FindDescriptor(std::string_view typeId) const = 0;
[[nodiscard]] virtual bool RegisterComponentDescriptor(MetaCoreComponentDescriptor descriptor) = 0;
[[nodiscard]] virtual bool CopyComponent(std::string_view typeId, const MetaCoreGameObject& gameObject) = 0;
[[nodiscard]] virtual bool CanPasteComponent(std::string_view typeId) const = 0;
[[nodiscard]] virtual bool PasteComponent(std::string_view typeId, MetaCoreGameObject& gameObject) const = 0;
@ -306,6 +363,13 @@ class MetaCoreIPlayModeService : public MetaCoreIEditorService {
public:
[[nodiscard]] virtual MetaCorePlayModeState GetState() const = 0;
[[nodiscard]] virtual bool CanEnterPlayMode() const = 0;
[[nodiscard]] virtual bool EnterPlayMode(MetaCoreEditorContext& editorContext) = 0;
[[nodiscard]] virtual bool ExitPlayMode(MetaCoreEditorContext& editorContext) = 0;
[[nodiscard]] virtual bool PausePlayMode(MetaCoreEditorContext& editorContext) = 0;
[[nodiscard]] virtual bool ResumePlayMode(MetaCoreEditorContext& editorContext) = 0;
[[nodiscard]] virtual bool StepPlayMode(MetaCoreEditorContext& editorContext, float deltaSeconds) = 0;
[[nodiscard]] virtual float GetElapsedPlayTimeSeconds() const = 0;
virtual void TickPlayMode(MetaCoreEditorContext& editorContext, float deltaSeconds) = 0;
};
} // namespace MetaCore

View File

@ -3,6 +3,7 @@
#include "MetaCoreEditor/MetaCoreEditorContext.h"
#include "MetaCoreRender/MetaCoreRenderTypes.h"
#include <glm/mat4x4.hpp>
#include <glm/vec2.hpp>
namespace MetaCore {
@ -25,6 +26,12 @@ public:
void HandleGizmoEndUse(MetaCoreEditorContext& editorContext, bool gizmoUsing);
void FocusSelectedObject(MetaCoreEditorContext& editorContext) const;
void FocusVisibleScene(MetaCoreEditorContext& editorContext) const;
[[nodiscard]] bool ApplyWorldTransformDeltaToSelection(
MetaCoreEditorContext& editorContext,
MetaCoreId manipulatedObjectId,
const glm::mat4& originalWorldMatrix,
const glm::mat4& newWorldMatrix
) const;
void HandleGizmoManipulation(MetaCoreEditorContext& editorContext);
void DrawViewportToolbar(MetaCoreEditorContext& editorContext);
void HandleShortcuts(MetaCoreEditorContext& editorContext);

View File

@ -14,6 +14,11 @@ const MetaCoreStructDescriptor* MetaCoreTypeRegistry::FindStructByName(std::stri
return descriptorIterator == StructsByRuntimeType_.end() ? nullptr : &descriptorIterator->second;
}
const MetaCoreEnumDescriptor* MetaCoreTypeRegistry::FindEnumByRuntimeType(std::type_index runtimeType) const {
const auto descriptorIterator = EnumsByRuntimeType_.find(runtimeType);
return descriptorIterator == EnumsByRuntimeType_.end() ? nullptr : &descriptorIterator->second;
}
void MetaCoreTypeRegistry::Clear() {
StructsByRuntimeType_.clear();
StructNames_.clear();

View File

@ -142,6 +142,34 @@ struct MetaCoreCustomVersion {
std::uint32_t Version = 0;
};
MC_STRUCT()
struct MetaCoreCookManifestEntry {
MC_GENERATED_BODY()
MC_PROPERTY()
MetaCoreAssetGuid AssetGuid{};
MC_PROPERTY()
std::filesystem::path SourcePackagePath{};
MC_PROPERTY()
std::filesystem::path CookedPath{};
MC_PROPERTY()
std::uint64_t SourceHash = 0;
MC_PROPERTY()
std::uint64_t CookedKey = 0;
};
MC_STRUCT()
struct MetaCoreCookManifestDocument {
MC_GENERATED_BODY()
MC_PROPERTY()
std::vector<MetaCoreCookManifestEntry> Entries{};
};
struct MetaCorePackageDocument {
MetaCorePackageHeader Header{};
std::vector<std::string> NameTable{};
@ -193,4 +221,3 @@ private:
};
} // namespace MetaCore

View File

@ -24,8 +24,46 @@ namespace MetaCore {
using MetaCoreTypeId = std::uint64_t;
enum class MetaCoreFieldValueKind {
Unknown = 0,
Bool,
SignedInteger,
UnsignedInteger,
FloatingPoint,
String,
Path,
Vec3,
Enum,
Struct,
Vector,
Optional,
Array
};
struct MetaCoreFieldEditorMetadata {
std::string DisplayName{};
std::string Group{};
std::string Tooltip{};
std::string RawSpec{};
std::string ResourceType{};
std::optional<double> Min{};
std::optional<double> Max{};
std::optional<double> Step{};
bool ReadOnly = false;
bool Hidden = false;
bool ResourceReference = false;
};
struct MetaCoreFieldDescriptor {
std::string Name{};
std::string TypeName{};
MetaCoreTypeId TypeId = 0;
MetaCoreFieldValueKind ValueKind = MetaCoreFieldValueKind::Unknown;
std::type_index RuntimeType = typeid(void);
std::size_t Size = 0;
MetaCoreFieldEditorMetadata Editor{};
std::function<void*(void*)> MutableValue{};
std::function<const void*(const void*)> ConstValue{};
std::function<bool(const void*, MetaCoreArchiveWriter&, const class MetaCoreTypeRegistry&)> Serialize{};
std::function<bool(void*, MetaCoreArchiveReader&, const class MetaCoreTypeRegistry&)> Deserialize{};
};
@ -38,10 +76,16 @@ struct MetaCoreStructDescriptor {
std::vector<MetaCoreFieldDescriptor> Fields{};
};
struct MetaCoreEnumValueDescriptor {
std::string Name{};
std::int64_t Value = 0;
};
struct MetaCoreEnumDescriptor {
MetaCoreTypeId TypeId = 0;
std::string Name{};
std::type_index RuntimeType = typeid(void);
std::vector<MetaCoreEnumValueDescriptor> Values{};
};
class MetaCoreTypeRegistry {
@ -59,6 +103,7 @@ public:
[[nodiscard]] const MetaCoreEnumDescriptor* FindEnum() const;
[[nodiscard]] const MetaCoreStructDescriptor* FindStructByName(std::string_view name) const;
[[nodiscard]] const MetaCoreEnumDescriptor* FindEnumByRuntimeType(std::type_index runtimeType) const;
void Clear();
private:
@ -75,7 +120,7 @@ public:
}
template <auto MemberPointer>
MetaCoreStructRegistrationBuilder& Field(std::string_view name);
MetaCoreStructRegistrationBuilder& Field(std::string_view name, MetaCoreFieldEditorMetadata editor = {});
private:
MetaCoreStructDescriptor& Descriptor_;
@ -118,7 +163,20 @@ template <typename T>
);
template <typename TEnum>
void MetaCoreRegisterGeneratedEnum(
class MetaCoreEnumRegistrationBuilder {
public:
explicit MetaCoreEnumRegistrationBuilder(MetaCoreEnumDescriptor& descriptor)
: Descriptor_(descriptor) {
}
MetaCoreEnumRegistrationBuilder& Value(std::string_view name, TEnum value);
private:
MetaCoreEnumDescriptor& Descriptor_;
};
template <typename TEnum>
MetaCoreEnumRegistrationBuilder<TEnum> MetaCoreRegisterGeneratedEnum(
MetaCoreTypeRegistry& registry,
std::string_view name
);
@ -159,6 +217,46 @@ struct MetaCoreIsStdArray<std::array<TValue, TSize>> : std::true_type {
template <typename T>
inline constexpr bool GMetaCoreAlwaysFalse = false;
template <typename T>
struct MetaCoreMemberPointerTraits;
template <typename TObject, typename TValue>
struct MetaCoreMemberPointerTraits<TValue TObject::*> {
using ObjectType = TObject;
using ValueType = TValue;
};
template <typename T>
[[nodiscard]] constexpr MetaCoreFieldValueKind MetaCoreDetectFieldValueKind() {
using TValue = std::remove_cvref_t<T>;
if constexpr (std::is_same_v<TValue, bool>) {
return MetaCoreFieldValueKind::Bool;
} else if constexpr (std::is_integral_v<TValue> && std::is_signed_v<TValue>) {
return MetaCoreFieldValueKind::SignedInteger;
} else if constexpr (std::is_integral_v<TValue> && std::is_unsigned_v<TValue>) {
return MetaCoreFieldValueKind::UnsignedInteger;
} else if constexpr (std::is_floating_point_v<TValue>) {
return MetaCoreFieldValueKind::FloatingPoint;
} else if constexpr (std::is_same_v<TValue, std::string>) {
return MetaCoreFieldValueKind::String;
} else if constexpr (std::is_same_v<TValue, std::filesystem::path>) {
return MetaCoreFieldValueKind::Path;
} else if constexpr (std::is_same_v<TValue, glm::vec3>) {
return MetaCoreFieldValueKind::Vec3;
} else if constexpr (std::is_enum_v<TValue>) {
return MetaCoreFieldValueKind::Enum;
} else if constexpr (MetaCoreIsVector<TValue>::value) {
return MetaCoreFieldValueKind::Vector;
} else if constexpr (MetaCoreIsOptional<TValue>::value) {
return MetaCoreFieldValueKind::Optional;
} else if constexpr (MetaCoreIsStdArray<TValue>::value) {
return MetaCoreFieldValueKind::Array;
} else {
return MetaCoreFieldValueKind::Struct;
}
}
} // namespace Detail
template <typename T>
@ -179,6 +277,7 @@ MetaCoreEnumDescriptor& MetaCoreTypeRegistry::RegisterEnum(std::string_view name
descriptor.TypeId = MetaCoreMakeTypeId(name);
descriptor.Name = std::string(name);
descriptor.RuntimeType = std::type_index(typeid(T));
descriptor.Values.clear();
return descriptor;
}
@ -196,9 +295,33 @@ const MetaCoreEnumDescriptor* MetaCoreTypeRegistry::FindEnum() const {
template <typename T>
template <auto MemberPointer>
MetaCoreStructRegistrationBuilder<T>& MetaCoreStructRegistrationBuilder<T>::Field(std::string_view name) {
MetaCoreStructRegistrationBuilder<T>& MetaCoreStructRegistrationBuilder<T>::Field(
std::string_view name,
MetaCoreFieldEditorMetadata editor
) {
using FieldType = std::remove_cvref_t<typename Detail::MetaCoreMemberPointerTraits<decltype(MemberPointer)>::ValueType>;
static_assert(std::is_same_v<typename Detail::MetaCoreMemberPointerTraits<decltype(MemberPointer)>::ObjectType, T>);
if (editor.DisplayName.empty()) {
editor.DisplayName = std::string(name);
}
Descriptor_.Fields.push_back(MetaCoreFieldDescriptor{
std::string(name),
typeid(FieldType).name(),
MetaCoreMakeTypeId(typeid(FieldType).name()),
Detail::MetaCoreDetectFieldValueKind<FieldType>(),
std::type_index(typeid(FieldType)),
sizeof(FieldType),
std::move(editor),
[](void* instance) -> void* {
auto& typedInstance = *static_cast<T*>(instance);
return &(typedInstance.*MemberPointer);
},
[](const void* instance) -> const void* {
const auto& typedInstance = *static_cast<const T*>(instance);
return &(typedInstance.*MemberPointer);
},
[](const void* instance, MetaCoreArchiveWriter& writer, const MetaCoreTypeRegistry& registry) {
const auto& typedInstance = *static_cast<const T*>(instance);
return MetaCoreSerializeValue(writer, typedInstance.*MemberPointer, registry);
@ -211,6 +334,19 @@ MetaCoreStructRegistrationBuilder<T>& MetaCoreStructRegistrationBuilder<T>::Fiel
return *this;
}
template <typename TEnum>
MetaCoreEnumRegistrationBuilder<TEnum>& MetaCoreEnumRegistrationBuilder<TEnum>::Value(
std::string_view name,
TEnum value
) {
static_assert(std::is_enum_v<TEnum>);
Descriptor_.Values.push_back(MetaCoreEnumValueDescriptor{
std::string(name),
static_cast<std::int64_t>(value)
});
return *this;
}
template <typename T>
MetaCoreStructRegistrationBuilder<T> MetaCoreRegisterGeneratedStruct(
MetaCoreTypeRegistry& registry,
@ -221,8 +357,8 @@ MetaCoreStructRegistrationBuilder<T> MetaCoreRegisterGeneratedStruct(
}
template <typename TEnum>
void MetaCoreRegisterGeneratedEnum(MetaCoreTypeRegistry& registry, std::string_view name) {
(void)registry.RegisterEnum<TEnum>(name);
MetaCoreEnumRegistrationBuilder<TEnum> MetaCoreRegisterGeneratedEnum(MetaCoreTypeRegistry& registry, std::string_view name) {
return MetaCoreEnumRegistrationBuilder<TEnum>(registry.RegisterEnum<TEnum>(name));
}
template <typename T>

View File

@ -94,6 +94,10 @@ void MetaCoreEditorViewportRenderer::RenderSceneToViewport(MetaCoreScene& scene,
FilamentSceneBridge_.SyncScene(scene, false, useScenePrimaryCamera);
}
void MetaCoreEditorViewportRenderer::SetRuntimeUiOverlayFrame(const MetaCoreRuntimeUiFrame& frame, int width, int height) {
FilamentSceneBridge_.SetRuntimeUiOverlayFrame(frame, width, height);
}
void MetaCoreEditorViewportRenderer::RenderAll() {
FilamentSceneBridge_.RenderAll();
}

View File

@ -5,6 +5,7 @@
#include "MetaCoreRender/MetaCoreRenderTypes.h"
#include "MetaCoreRender/MetaCoreImGuiHelper.h"
#include "MetaCoreRender/MetaCoreSceneRenderSync.h"
#include "MetaCoreRender/MetaCoreRuntimeUiRenderer.h"
#include <imgui.h>
#include "MetaCoreFoundation/MetaCoreAssetRegistry.h"
@ -150,6 +151,11 @@ public:
} else {
auto [width, height] = window.GetFramebufferSize();
View_->setViewport({0, 0, static_cast<uint32_t>(width), static_cast<uint32_t>(height)});
RuntimeUIView_ = Engine_->createView();
RuntimeUiHelper_ = new MetaCoreImGuiHelper(Engine_, RuntimeUIView_, "", nullptr);
RuntimeUiHelper_->setDisplaySize(width, height);
RuntimeUIView_->setViewport({0, 0, static_cast<uint32_t>(width), static_cast<uint32_t>(height)});
}
// 确保开启后处理为了HDR
@ -226,6 +232,7 @@ public:
}
SceneLightEntities_.clear();
EntitiesInScene_.clear();
Engine_->flushAndWait();
// 销毁 gltfio 资源
std::cout << "[DEBUG] Destroying gltfio resources..." << std::endl;
@ -238,6 +245,15 @@ public:
delete MaterialProvider_;
MaterialProvider_ = nullptr;
}
if (ImGuiHelper_) {
delete ImGuiHelper_;
ImGuiHelper_ = nullptr;
}
if (RuntimeUiHelper_) {
delete RuntimeUiHelper_;
RuntimeUiHelper_ = nullptr;
}
Engine_->flushAndWait();
// 销毁离屏渲染资源
if (RenderTarget_) {
@ -258,14 +274,14 @@ public:
}
// 销毁 View
Engine_->destroy(View_);
if (RuntimeUIView_) {
Engine_->destroy(RuntimeUIView_);
RuntimeUIView_ = nullptr;
}
if (UIView_) {
Engine_->destroy(UIView_);
UIView_ = nullptr;
}
if (ImGuiHelper_) {
delete ImGuiHelper_;
ImGuiHelper_ = nullptr;
}
// 销毁相机组件和实体
if (Camera_) {
@ -278,6 +294,7 @@ public:
Engine_->destroy(Scene_);
Engine_->destroy(Renderer_);
Engine_->destroy(SwapChain_);
Engine_->flushAndWait();
delete NameManager_;
NameManager_ = nullptr;
@ -899,11 +916,42 @@ public:
filament::Camera::Fov::VERTICAL
);
}
void SetRuntimeUiOverlayFrame(const MetaCoreRuntimeUiFrame& frame, int width, int height) {
RuntimeUiFrame_ = frame;
RuntimeUiWidth_ = width;
RuntimeUiHeight_ = height;
}
void RenderAll() {
if (!Renderer_ || !SwapChain_ || !View_) {
return;
}
bool renderRuntimeUi = false;
if (!RenderTarget_ && RuntimeUIView_ && RuntimeUiHelper_) {
const auto viewport = View_->getViewport();
const int overlayWidth = RuntimeUiWidth_ > 0 ? RuntimeUiWidth_ : static_cast<int>(viewport.width);
const int overlayHeight = RuntimeUiHeight_ > 0 ? RuntimeUiHeight_ : static_cast<int>(viewport.height);
if (overlayWidth > 0 && overlayHeight > 0) {
RuntimeUiHelper_->setDisplaySize(overlayWidth, overlayHeight);
RuntimeUIView_->setViewport({
0,
0,
static_cast<uint32_t>(overlayWidth),
static_cast<uint32_t>(overlayHeight)
});
const bool hasRuntimeUi = !RuntimeUiFrame_.Commands.empty();
const bool preparedRuntimeUi = RuntimeUiHelper_->processRuntimeUiFrame(
RuntimeUiFrame_,
overlayWidth,
overlayHeight
);
renderRuntimeUi = preparedRuntimeUi && hasRuntimeUi;
}
}
if (Renderer_->beginFrame(SwapChain_)) {
if (RenderTarget_) {
// 1. 渲染 3D 离屏视口 (输出到 RenderTarget)
@ -936,6 +984,13 @@ public:
Renderer_->setClearOptions(options);
Renderer_->render(View_);
if (renderRuntimeUi) {
options.clearColor = {0.0f, 0.0f, 0.0f, 0.0f};
options.clear = false;
Renderer_->setClearOptions(options);
Renderer_->render(RuntimeUIView_);
}
}
Renderer_->endFrame();
@ -1289,6 +1344,11 @@ private:
filament::View* UIView_ = nullptr;
MetaCoreImGuiHelper* ImGuiHelper_ = nullptr;
filament::View* RuntimeUIView_ = nullptr;
MetaCoreImGuiHelper* RuntimeUiHelper_ = nullptr;
MetaCoreRuntimeUiFrame RuntimeUiFrame_{};
int RuntimeUiWidth_ = 0;
int RuntimeUiHeight_ = 0;
GLuint GLTextureId_ = 0;
filament::Texture* FilamentTexture_ = nullptr;
@ -1331,6 +1391,10 @@ void MetaCoreFilamentSceneBridge::ApplySceneView(const MetaCoreSceneView& sceneV
Impl_->ApplySceneView(sceneView);
}
void MetaCoreFilamentSceneBridge::SetRuntimeUiOverlayFrame(const MetaCoreRuntimeUiFrame& frame, int width, int height) {
Impl_->SetRuntimeUiOverlayFrame(frame, width, height);
}
void MetaCoreFilamentSceneBridge::RenderAll() {
Impl_->RenderAll();
}

View File

@ -1,5 +1,11 @@
#include "MetaCoreRender/MetaCoreImGuiHelper.h"
#include "MetaCoreRender/MetaCoreRuntimeUiRenderer.h"
#include <algorithm>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <limits>
#include <vector>
#include <unordered_map>
#include <fstream>
@ -115,28 +121,74 @@ void MetaCoreImGuiHelper::createAtlasTexture(Engine* engine) {
if (mMaterial2d) {
mMaterial2d->setDefaultParameter("albedo", mTexture, mSampler);
}
}
MetaCoreImGuiHelper::~MetaCoreImGuiHelper() {
mEngine->destroy(mScene);
mEngine->destroy(mRenderable);
mEngine->destroyCameraComponent(mCameraEntity);
if (mView != nullptr) {
mView->setScene(nullptr);
mView->setCamera(nullptr);
}
if (mScene != nullptr) {
mScene->remove(mRenderable);
}
for (auto& mi : mMaterial2dInstances) {
mEngine->destroy(mi);
if (mi != nullptr) {
mEngine->destroy(mi);
}
}
mMaterial2dInstances.clear();
if (mMaterial2d != nullptr) {
mEngine->destroy(mMaterial2d);
mMaterial2d = nullptr;
}
if (mTexture != nullptr) {
mEngine->destroy(mTexture);
mTexture = nullptr;
}
if (mWhiteTexture != nullptr) {
mEngine->destroy(mWhiteTexture);
mWhiteTexture = nullptr;
}
mEngine->destroy(mMaterial2d);
mEngine->destroy(mTexture);
for (auto& vb : mVertexBuffers) {
mEngine->destroy(vb);
if (vb != nullptr) {
mEngine->destroy(vb);
}
}
mVertexBuffers.clear();
for (auto& ib : mIndexBuffers) {
mEngine->destroy(ib);
if (ib != nullptr) {
mEngine->destroy(ib);
}
}
mIndexBuffers.clear();
for (auto itextures: mImGuiTextures) {
mEngine->destroy(itextures);
for (auto* texture : mImGuiTextures) {
if (texture != nullptr) {
mEngine->destroy(texture);
}
}
mImGuiTextures.clear();
for (auto& [handle, textureState] : mRuntimeUiTextures) {
(void)handle;
if (textureState.Texture != nullptr) {
mEngine->destroy(textureState.Texture);
textureState.Texture = nullptr;
}
}
mRuntimeUiTextures.clear();
if (mScene != nullptr) {
mEngine->destroy(mScene);
mScene = nullptr;
}
mEngine->destroy(mRenderable);
if (mCamera != nullptr) {
mEngine->destroyCameraComponent(mCameraEntity);
mCamera = nullptr;
}
EntityManager& em = utils::EntityManager::get();
@ -263,6 +315,247 @@ void MetaCoreImGuiHelper::processImGuiCommands(ImDrawData* commands, const ImGui
}
}
filament::Texture* MetaCoreImGuiHelper::getWhiteTexture() {
if (mWhiteTexture != nullptr) {
return mWhiteTexture;
}
const std::uint8_t whitePixel[4] = {255, 255, 255, 255};
void* whitePixelData = malloc(sizeof(whitePixel));
if (whitePixelData == nullptr) {
return nullptr;
}
std::memcpy(whitePixelData, whitePixel, sizeof(whitePixel));
Texture::PixelBufferDescriptor whitePixelBuffer(
whitePixelData,
sizeof(whitePixel),
Texture::Format::RGBA,
Texture::Type::UBYTE,
[](void* buffer, size_t, void*) {
free(buffer);
},
nullptr);
mWhiteTexture = Texture::Builder()
.width(1)
.height(1)
.levels(1)
.format(Texture::InternalFormat::RGBA8)
.sampler(Texture::Sampler::SAMPLER_2D)
.build(*mEngine);
mWhiteTexture->setImage(*mEngine, 0, std::move(whitePixelBuffer));
return mWhiteTexture;
}
filament::Texture* MetaCoreImGuiHelper::syncRuntimeUiTexture(const MetaCoreRuntimeUiFrame& frame, std::uint64_t handle) {
if (handle == 0) {
return nullptr;
}
const auto textureIterator = std::find_if(
frame.Textures.begin(),
frame.Textures.end(),
[handle](const MetaCoreRuntimeUiTexture& texture) {
return texture.Handle == handle;
});
if (textureIterator == frame.Textures.end()) {
return nullptr;
}
const MetaCoreRuntimeUiTexture& texture = *textureIterator;
if (texture.Width <= 0 || texture.Height <= 0) {
return nullptr;
}
const std::size_t expectedByteCount =
static_cast<std::size_t>(texture.Width) * static_cast<std::size_t>(texture.Height) * 4U;
if (expectedByteCount == 0 || texture.Rgba.size() < expectedByteCount) {
return nullptr;
}
RuntimeUiTextureState& state = mRuntimeUiTextures[handle];
if (state.Texture != nullptr &&
state.Width == texture.Width &&
state.Height == texture.Height &&
state.Revision == texture.Revision) {
return state.Texture;
}
if (state.Texture != nullptr) {
mEngine->destroy(state.Texture);
state.Texture = nullptr;
}
void* pixelData = malloc(expectedByteCount);
if (pixelData == nullptr) {
return nullptr;
}
std::memcpy(pixelData, texture.Rgba.data(), expectedByteCount);
Texture::PixelBufferDescriptor pixelBuffer(
pixelData,
expectedByteCount,
Texture::Format::RGBA,
Texture::Type::UBYTE,
[](void* buffer, size_t, void*) {
free(buffer);
},
nullptr);
state.Texture = Texture::Builder()
.width(static_cast<uint32_t>(texture.Width))
.height(static_cast<uint32_t>(texture.Height))
.levels(1)
.format(Texture::InternalFormat::RGBA8)
.sampler(Texture::Sampler::SAMPLER_2D)
.build(*mEngine);
state.Texture->setImage(*mEngine, 0, std::move(pixelBuffer));
state.Width = texture.Width;
state.Height = texture.Height;
state.Revision = texture.Revision;
return state.Texture;
}
bool MetaCoreImGuiHelper::processRuntimeUiFrame(const MetaCoreRuntimeUiFrame& frame, int width, int height) {
ImGui::SetCurrentContext(mImGuiContext);
mHasSynced = false;
auto& rcm = mEngine->getRenderableManager();
rcm.destroy(mRenderable);
if (width <= 0 || height <= 0) {
return false;
}
if (frame.Commands.empty() || frame.Vertices.empty() || frame.Indices.empty()) {
return true;
}
if (frame.Vertices.size() > static_cast<std::size_t>(std::numeric_limits<std::uint16_t>::max())) {
return false;
}
std::unordered_set<std::uint64_t> activeTextureHandles;
activeTextureHandles.reserve(frame.Textures.size());
for (const MetaCoreRuntimeUiTexture& texture : frame.Textures) {
if (texture.Handle != 0) {
activeTextureHandles.insert(texture.Handle);
}
}
for (auto iterator = mRuntimeUiTextures.begin(); iterator != mRuntimeUiTextures.end();) {
if (activeTextureHandles.contains(iterator->first)) {
++iterator;
continue;
}
if (iterator->second.Texture != nullptr) {
mEngine->destroy(iterator->second.Texture);
}
iterator = mRuntimeUiTextures.erase(iterator);
}
createBuffers(1);
std::vector<ImDrawVert> vertices;
vertices.reserve(frame.Vertices.size());
for (const MetaCoreRuntimeUiDrawVertex& vertex : frame.Vertices) {
ImDrawVert converted{};
converted.pos = ImVec2(vertex.X, vertex.Y);
converted.uv = ImVec2(vertex.U, vertex.V);
converted.col = IM_COL32(vertex.R, vertex.G, vertex.B, vertex.A);
vertices.push_back(converted);
}
std::vector<std::uint16_t> indices;
indices.reserve(frame.Indices.size());
for (const std::uint32_t index : frame.Indices) {
if (index > static_cast<std::uint32_t>(std::numeric_limits<std::uint16_t>::max())) {
return false;
}
indices.push_back(static_cast<std::uint16_t>(index));
}
populateVertexData(
0,
vertices.size() * sizeof(ImDrawVert),
vertices.data(),
indices.size() * sizeof(std::uint16_t),
indices.data());
std::size_t primitiveCount = 0;
for (const MetaCoreRuntimeUiDrawCommand& command : frame.Commands) {
if (command.IndexCount == 0 ||
command.VertexCount == 0 ||
command.VertexOffset + command.VertexCount > frame.Vertices.size() ||
command.IndexOffset + command.IndexCount > frame.Indices.size()) {
continue;
}
++primitiveCount;
}
if (primitiveCount == 0) {
return true;
}
auto rbuilder = RenderableManager::Builder(primitiveCount);
rbuilder.boundingBox({{ 0, 0, 0 }, { 10000, 10000, 10000 }}).culling(false);
int primitiveIndex = 0;
int material2dIndex = 0;
for (const MetaCoreRuntimeUiDrawCommand& command : frame.Commands) {
if (command.IndexCount == 0 ||
command.VertexCount == 0 ||
command.VertexOffset + command.VertexCount > frame.Vertices.size() ||
command.IndexOffset + command.IndexCount > frame.Indices.size()) {
continue;
}
if (material2dIndex == static_cast<int>(mMaterial2dInstances.size())) {
if (mMaterial2d) {
mMaterial2dInstances.push_back(mMaterial2d->createInstance());
} else {
mMaterial2dInstances.push_back(nullptr);
}
}
MaterialInstance* materialInstance = mMaterial2dInstances[material2dIndex++];
if (!materialInstance) {
continue;
}
if (command.ScissorEnabled) {
const int left = std::clamp(command.ScissorLeft, 0, width);
const int top = std::clamp(command.ScissorTop, 0, height);
const int right = std::clamp(command.ScissorRight, left, width);
const int bottom = std::clamp(command.ScissorBottom, top, height);
materialInstance->setScissor(
static_cast<uint32_t>(left),
static_cast<uint32_t>(height - bottom),
static_cast<uint32_t>(right - left),
static_cast<uint32_t>(bottom - top));
} else {
materialInstance->unsetScissor();
}
filament::Texture* runtimeTexture = syncRuntimeUiTexture(frame, command.TextureHandle);
filament::Texture* fallbackTexture = getWhiteTexture();
materialInstance->setParameter(
"albedo",
runtimeTexture != nullptr ? runtimeTexture : (fallbackTexture != nullptr ? fallbackTexture : mTexture),
mSampler);
rbuilder
.geometry(primitiveIndex, RenderableManager::PrimitiveType::TRIANGLES,
mVertexBuffers[0], mIndexBuffers[0],
static_cast<std::uint32_t>(command.IndexOffset),
static_cast<std::uint32_t>(command.IndexCount))
.blendOrder(primitiveIndex, static_cast<std::uint16_t>(primitiveIndex))
.material(primitiveIndex, materialInstance);
++primitiveIndex;
}
if (primitiveIndex > 0) {
rbuilder.build(*mEngine, mRenderable);
}
return true;
}
void MetaCoreImGuiHelper::createVertexBuffer(size_t bufferIndex, size_t capacity) {
syncThreads();
if (bufferIndex < mVertexBuffers.size() && mVertexBuffers[bufferIndex]) {

View File

@ -0,0 +1,788 @@
#include "MetaCoreRender/MetaCoreRuntimeUiRenderer.h"
#include <RmlUi/Core.h>
#include <algorithm>
#include <cmath>
#include <chrono>
#include <cstring>
#include <cstdint>
#include <memory>
#include <string>
#include <unordered_map>
#include <utility>
namespace MetaCore {
namespace {
[[nodiscard]] float MetaCoreRuntimeUiEdge(
const MetaCoreRuntimeUiDrawVertex& a,
const MetaCoreRuntimeUiDrawVertex& b,
float x,
float y
) {
return (x - a.X) * (b.Y - a.Y) - (y - a.Y) * (b.X - a.X);
}
[[nodiscard]] std::uint8_t MetaCoreRuntimeUiByte(float value) {
return static_cast<std::uint8_t>(std::clamp(value, 0.0F, 255.0F));
}
[[nodiscard]] std::string MetaCoreEscapeRuntimeUiRmlText(std::string_view value) {
std::string escaped;
escaped.reserve(value.size());
for (char c : value) {
switch (c) {
case '&': escaped += "&amp;"; break;
case '<': escaped += "&lt;"; break;
case '>': escaped += "&gt;"; break;
case '"': escaped += "&quot;"; break;
case '\'': escaped += "&apos;"; break;
default: escaped.push_back(c); break;
}
}
return escaped;
}
void MetaCoreRuntimeUiBlendPixel(
MetaCoreRuntimeUiRasterFrame& rasterFrame,
std::int32_t x,
std::int32_t y,
float red,
float green,
float blue,
float alpha
) {
if (x < 0 || y < 0 || x >= rasterFrame.Width || y >= rasterFrame.Height) {
return;
}
const std::size_t pixelOffset =
(static_cast<std::size_t>(y) * static_cast<std::size_t>(rasterFrame.Width) + static_cast<std::size_t>(x)) * 4U;
if (pixelOffset + 3U >= rasterFrame.Rgba.size()) {
return;
}
const float sourceAlpha = std::clamp(alpha / 255.0F, 0.0F, 1.0F);
const float inverseAlpha = 1.0F - sourceAlpha;
rasterFrame.Rgba[pixelOffset + 0U] = MetaCoreRuntimeUiByte(red + static_cast<float>(rasterFrame.Rgba[pixelOffset + 0U]) * inverseAlpha);
rasterFrame.Rgba[pixelOffset + 1U] = MetaCoreRuntimeUiByte(green + static_cast<float>(rasterFrame.Rgba[pixelOffset + 1U]) * inverseAlpha);
rasterFrame.Rgba[pixelOffset + 2U] = MetaCoreRuntimeUiByte(blue + static_cast<float>(rasterFrame.Rgba[pixelOffset + 2U]) * inverseAlpha);
rasterFrame.Rgba[pixelOffset + 3U] = MetaCoreRuntimeUiByte(alpha + static_cast<float>(rasterFrame.Rgba[pixelOffset + 3U]) * inverseAlpha);
}
void MetaCoreRuntimeUiRasterizeTriangle(
MetaCoreRuntimeUiRasterFrame& rasterFrame,
const MetaCoreRuntimeUiDrawCommand& command,
const MetaCoreRuntimeUiDrawVertex& v0,
const MetaCoreRuntimeUiDrawVertex& v1,
const MetaCoreRuntimeUiDrawVertex& v2
) {
const float area = MetaCoreRuntimeUiEdge(v0, v1, v2.X, v2.Y);
if (std::abs(area) <= 0.0001F) {
return;
}
std::int32_t minX = static_cast<std::int32_t>(std::floor(std::min({v0.X, v1.X, v2.X})));
std::int32_t minY = static_cast<std::int32_t>(std::floor(std::min({v0.Y, v1.Y, v2.Y})));
std::int32_t maxX = static_cast<std::int32_t>(std::ceil(std::max({v0.X, v1.X, v2.X})));
std::int32_t maxY = static_cast<std::int32_t>(std::ceil(std::max({v0.Y, v1.Y, v2.Y})));
minX = std::clamp(minX, 0, std::max(0, rasterFrame.Width - 1));
minY = std::clamp(minY, 0, std::max(0, rasterFrame.Height - 1));
maxX = std::clamp(maxX, 0, std::max(0, rasterFrame.Width - 1));
maxY = std::clamp(maxY, 0, std::max(0, rasterFrame.Height - 1));
if (command.ScissorEnabled) {
minX = std::max(minX, command.ScissorLeft);
minY = std::max(minY, command.ScissorTop);
maxX = std::min(maxX, command.ScissorRight);
maxY = std::min(maxY, command.ScissorBottom);
}
if (minX > maxX || minY > maxY) {
return;
}
const bool positiveArea = area > 0.0F;
for (std::int32_t y = minY; y <= maxY; ++y) {
for (std::int32_t x = minX; x <= maxX; ++x) {
const float sampleX = static_cast<float>(x) + 0.5F;
const float sampleY = static_cast<float>(y) + 0.5F;
const float w0 = MetaCoreRuntimeUiEdge(v1, v2, sampleX, sampleY);
const float w1 = MetaCoreRuntimeUiEdge(v2, v0, sampleX, sampleY);
const float w2 = MetaCoreRuntimeUiEdge(v0, v1, sampleX, sampleY);
const bool inside = positiveArea
? (w0 >= 0.0F && w1 >= 0.0F && w2 >= 0.0F)
: (w0 <= 0.0F && w1 <= 0.0F && w2 <= 0.0F);
if (!inside) {
continue;
}
const float invArea = 1.0F / area;
const float b0 = w0 * invArea;
const float b1 = w1 * invArea;
const float b2 = w2 * invArea;
MetaCoreRuntimeUiBlendPixel(
rasterFrame,
x,
y,
b0 * static_cast<float>(v0.R) + b1 * static_cast<float>(v1.R) + b2 * static_cast<float>(v2.R),
b0 * static_cast<float>(v0.G) + b1 * static_cast<float>(v1.G) + b2 * static_cast<float>(v2.G),
b0 * static_cast<float>(v0.B) + b1 * static_cast<float>(v1.B) + b2 * static_cast<float>(v2.B),
b0 * static_cast<float>(v0.A) + b1 * static_cast<float>(v1.A) + b2 * static_cast<float>(v2.A)
);
}
}
}
void MetaCoreRuntimeUiRasterizeFrame(
const MetaCoreRuntimeUiFrame& frame,
MetaCoreRuntimeUiRasterFrame& rasterFrame,
MetaCoreRuntimeUiRenderStats& stats,
std::int32_t width,
std::int32_t height
) {
rasterFrame.Width = std::max<std::int32_t>(0, width);
rasterFrame.Height = std::max<std::int32_t>(0, height);
const std::size_t pixelCount =
static_cast<std::size_t>(rasterFrame.Width) * static_cast<std::size_t>(rasterFrame.Height);
rasterFrame.Rgba.assign(pixelCount * 4U, 0);
for (const MetaCoreRuntimeUiDrawCommand& command : frame.Commands) {
const std::size_t triangleCount = command.IndexCount / 3U;
for (std::size_t triangleIndex = 0; triangleIndex < triangleCount; ++triangleIndex) {
const std::size_t i0 = command.IndexOffset + triangleIndex * 3U + 0U;
const std::size_t i1 = command.IndexOffset + triangleIndex * 3U + 1U;
const std::size_t i2 = command.IndexOffset + triangleIndex * 3U + 2U;
if (i2 >= frame.Indices.size()) {
continue;
}
const std::uint32_t v0Index = frame.Indices[i0];
const std::uint32_t v1Index = frame.Indices[i1];
const std::uint32_t v2Index = frame.Indices[i2];
if (v0Index >= frame.Vertices.size() ||
v1Index >= frame.Vertices.size() ||
v2Index >= frame.Vertices.size()) {
continue;
}
MetaCoreRuntimeUiRasterizeTriangle(
rasterFrame,
command,
frame.Vertices[v0Index],
frame.Vertices[v1Index],
frame.Vertices[v2Index]
);
}
}
std::size_t touchedPixelCount = 0;
for (std::size_t pixelIndex = 0; pixelIndex < pixelCount; ++pixelIndex) {
if (rasterFrame.Rgba[pixelIndex * 4U + 3U] > 0) {
++touchedPixelCount;
}
}
stats.RasterPixelCount = pixelCount;
stats.RasterTouchedPixelCount = touchedPixelCount;
}
class MetaCoreRmlSystemInterface final : public Rml::SystemInterface {
public:
double GetElapsedTime() override {
return ElapsedSeconds_;
}
void Advance(double deltaSeconds) {
ElapsedSeconds_ += std::max(0.0, deltaSeconds);
}
bool LogMessage(Rml::Log::Type type, const Rml::String& message) override {
LastLogType_ = type;
LastMessage_ = message;
return true;
}
[[nodiscard]] const std::string& GetLastMessage() const {
return LastMessage_;
}
private:
double ElapsedSeconds_ = 0.0;
Rml::Log::Type LastLogType_ = Rml::Log::LT_INFO;
std::string LastMessage_{};
};
struct MetaCoreRmlMemoryFile {
std::string Data{};
std::size_t Cursor = 0;
};
[[nodiscard]] std::string MetaCoreNormalizeRmlPath(std::string path) {
std::replace(path.begin(), path.end(), '\\', '/');
while (path.find("//") != std::string::npos) {
path.erase(path.find("//"), 1);
}
return path;
}
[[nodiscard]] std::string MetaCoreRmlBasename(const std::string& path) {
const std::string normalized = MetaCoreNormalizeRmlPath(path);
const auto slash = normalized.find_last_of('/');
return slash == std::string::npos ? normalized : normalized.substr(slash + 1);
}
class MetaCoreRmlMemoryFileInterface final : public Rml::FileInterface {
public:
void SetVirtualFile(std::string path, std::string data) {
if (path.empty()) {
path = "MetaCoreRuntimeUi.rcss";
}
const std::string normalized = MetaCoreNormalizeRmlPath(std::move(path));
VirtualFiles_[normalized] = data;
VirtualFiles_[MetaCoreRmlBasename(normalized)] = std::move(data);
}
void Clear() {
VirtualFiles_.clear();
OpenFiles_.clear();
}
Rml::FileHandle Open(const Rml::String& path) override {
const std::string normalized = MetaCoreNormalizeRmlPath(path);
auto iterator = VirtualFiles_.find(normalized);
if (iterator == VirtualFiles_.end()) {
iterator = VirtualFiles_.find(MetaCoreRmlBasename(normalized));
}
if (iterator == VirtualFiles_.end()) {
return 0;
}
const Rml::FileHandle handle = NextHandle_++;
OpenFiles_.emplace(handle, MetaCoreRmlMemoryFile{iterator->second, 0});
return handle;
}
void Close(Rml::FileHandle file) override {
OpenFiles_.erase(file);
}
size_t Read(void* buffer, size_t size, Rml::FileHandle file) override {
auto iterator = OpenFiles_.find(file);
if (iterator == OpenFiles_.end() || buffer == nullptr || size == 0) {
return 0;
}
MetaCoreRmlMemoryFile& memoryFile = iterator->second;
const std::size_t remaining = memoryFile.Cursor < memoryFile.Data.size()
? memoryFile.Data.size() - memoryFile.Cursor
: 0;
const std::size_t bytesToRead = std::min(size, remaining);
if (bytesToRead > 0) {
std::memcpy(buffer, memoryFile.Data.data() + memoryFile.Cursor, bytesToRead);
memoryFile.Cursor += bytesToRead;
}
return bytesToRead;
}
bool Seek(Rml::FileHandle file, long offset, int origin) override {
auto iterator = OpenFiles_.find(file);
if (iterator == OpenFiles_.end()) {
return false;
}
MetaCoreRmlMemoryFile& memoryFile = iterator->second;
long base = 0;
if (origin == SEEK_CUR) {
base = static_cast<long>(memoryFile.Cursor);
} else if (origin == SEEK_END) {
base = static_cast<long>(memoryFile.Data.size());
}
const long next = base + offset;
if (next < 0) {
return false;
}
memoryFile.Cursor = std::min<std::size_t>(
static_cast<std::size_t>(next),
memoryFile.Data.size()
);
return true;
}
size_t Tell(Rml::FileHandle file) override {
auto iterator = OpenFiles_.find(file);
return iterator == OpenFiles_.end() ? 0 : iterator->second.Cursor;
}
private:
Rml::FileHandle NextHandle_ = 1;
std::unordered_map<std::string, std::string> VirtualFiles_{};
std::unordered_map<Rml::FileHandle, MetaCoreRmlMemoryFile> OpenFiles_{};
};
struct MetaCoreRmlSharedRuntime {
[[nodiscard]] bool Acquire(MetaCoreRuntimeUiRenderStats& stats) {
if (ReferenceCount == 0) {
FileInterface.Clear();
Rml::SetSystemInterface(&SystemInterface);
Rml::SetFileInterface(&FileInterface);
Rml::SetFontEngineInterface(&FontInterface);
if (!Rml::Initialise()) {
stats.LastError = SystemInterface.GetLastMessage().empty()
? "RmlUi core initialization failed"
: "RmlUi core initialization failed: " + SystemInterface.GetLastMessage();
return false;
}
Initialized = true;
}
++ReferenceCount;
stats.RmlInitialized = Initialized;
return Initialized;
}
void Release() {
if (ReferenceCount == 0) {
return;
}
--ReferenceCount;
if (ReferenceCount == 0 && Initialized) {
Rml::Shutdown();
FileInterface.Clear();
Initialized = false;
}
}
MetaCoreRmlSystemInterface SystemInterface{};
MetaCoreRmlMemoryFileInterface FileInterface{};
Rml::FontEngineInterface FontInterface{};
std::size_t ReferenceCount = 0;
bool Initialized = false;
};
[[nodiscard]] MetaCoreRmlSharedRuntime& MetaCoreGetRmlSharedRuntime() {
static MetaCoreRmlSharedRuntime runtime;
return runtime;
}
class MetaCoreRmlDiagnosticRenderInterface final : public Rml::RenderInterface {
public:
MetaCoreRmlDiagnosticRenderInterface(
MetaCoreRuntimeUiRenderStats& stats,
MetaCoreRuntimeUiFrame& frame
)
: Stats_(stats),
Frame_(frame) {}
Rml::CompiledGeometryHandle CompileGeometry(Rml::Span<const Rml::Vertex> vertices, Rml::Span<const int> indices) override {
const Rml::CompiledGeometryHandle handle = NextGeometryHandle_++;
MetaCoreRmlGeometryRecord record;
record.Vertices.reserve(vertices.size());
for (const Rml::Vertex& vertex : vertices) {
record.Vertices.push_back(MetaCoreRuntimeUiDrawVertex{
vertex.position.x,
vertex.position.y,
vertex.tex_coord.x,
vertex.tex_coord.y,
vertex.colour.red,
vertex.colour.green,
vertex.colour.blue,
vertex.colour.alpha
});
}
record.Indices.reserve(indices.size());
for (const int index : indices) {
if (index >= 0) {
record.Indices.push_back(static_cast<std::uint32_t>(index));
}
}
Geometries_.emplace(handle, std::move(record));
Stats_.CompiledGeometryCount = Geometries_.size();
return handle;
}
void RenderGeometry(Rml::CompiledGeometryHandle geometry, Rml::Vector2f translation, Rml::TextureHandle texture) override {
++Stats_.RenderGeometryCalls;
const auto geometryIterator = Geometries_.find(geometry);
if (geometryIterator == Geometries_.end()) {
return;
}
const MetaCoreRmlGeometryRecord& record = geometryIterator->second;
const std::size_t vertexOffset = Frame_.Vertices.size();
const std::size_t indexOffset = Frame_.Indices.size();
Frame_.Vertices.reserve(Frame_.Vertices.size() + record.Vertices.size());
for (MetaCoreRuntimeUiDrawVertex vertex : record.Vertices) {
vertex.X += translation.x;
vertex.Y += translation.y;
Frame_.Vertices.push_back(vertex);
}
Frame_.Indices.reserve(Frame_.Indices.size() + record.Indices.size());
for (const std::uint32_t index : record.Indices) {
Frame_.Indices.push_back(static_cast<std::uint32_t>(vertexOffset) + index);
}
Frame_.Commands.push_back(MetaCoreRuntimeUiDrawCommand{
vertexOffset,
record.Vertices.size(),
indexOffset,
record.Indices.size(),
static_cast<std::uint64_t>(texture),
ScissorEnabled_,
ScissorRegion_.Left(),
ScissorRegion_.Top(),
ScissorRegion_.Right(),
ScissorRegion_.Bottom()
});
SyncFrameStats();
}
void ReleaseGeometry(Rml::CompiledGeometryHandle geometry) override {
Geometries_.erase(geometry);
Stats_.CompiledGeometryCount = Geometries_.size();
}
Rml::TextureHandle LoadTexture(Rml::Vector2i& textureDimensions, const Rml::String& source) override {
(void)source;
++Stats_.TextureLoadRequests;
textureDimensions = Rml::Vector2i(0, 0);
return 0;
}
Rml::TextureHandle GenerateTexture(Rml::Span<const Rml::byte> source, Rml::Vector2i sourceDimensions) override {
++Stats_.TextureGenerateRequests;
if (sourceDimensions.x <= 0 || sourceDimensions.y <= 0) {
return 0;
}
const std::size_t width = static_cast<std::size_t>(sourceDimensions.x);
const std::size_t height = static_cast<std::size_t>(sourceDimensions.y);
const std::size_t expectedByteCount = width * height * 4U;
if (expectedByteCount == 0 || source.empty()) {
return 0;
}
const Rml::TextureHandle handle = NextTextureHandle_++;
MetaCoreRmlTextureRecord record;
record.Handle = static_cast<std::uint64_t>(handle);
record.Width = sourceDimensions.x;
record.Height = sourceDimensions.y;
record.Revision = NextTextureRevision_++;
record.Rgba.resize(expectedByteCount, 0);
const auto* sourceBytes = reinterpret_cast<const std::uint8_t*>(source.data());
const std::size_t copyByteCount = std::min(expectedByteCount, source.size());
std::copy(sourceBytes, sourceBytes + copyByteCount, record.Rgba.begin());
Textures_.emplace(handle, std::move(record));
return handle;
}
void ReleaseTexture(Rml::TextureHandle texture) override {
Textures_.erase(texture);
}
void EnableScissorRegion(bool enable) override {
ScissorEnabled_ = enable;
}
void SetScissorRegion(Rml::Rectanglei region) override {
ScissorRegion_ = region;
}
void BeginFrame() {
Frame_ = MetaCoreRuntimeUiFrame{};
SyncFrameStats();
SyncTextureStats();
}
void SyncFrameStats() {
Stats_.DrawCommandCount = Frame_.Commands.size();
Stats_.DrawVertexCount = Frame_.Vertices.size();
Stats_.DrawIndexCount = Frame_.Indices.size();
}
void SyncTexturesToFrame() {
Frame_.Textures.clear();
Frame_.Textures.reserve(Textures_.size());
for (const auto& [handle, record] : Textures_) {
(void)handle;
if (record.Handle == 0 || record.Width <= 0 || record.Height <= 0 || record.Rgba.empty()) {
continue;
}
Frame_.Textures.push_back(MetaCoreRuntimeUiTexture{
record.Handle,
record.Width,
record.Height,
record.Revision,
record.Rgba
});
}
SyncTextureStats();
}
void SyncTextureStats() {
Stats_.TextureCount = Frame_.Textures.size();
Stats_.TexturePixelCount = 0;
for (const MetaCoreRuntimeUiTexture& texture : Frame_.Textures) {
if (texture.Width > 0 && texture.Height > 0) {
Stats_.TexturePixelCount += static_cast<std::size_t>(texture.Width) * static_cast<std::size_t>(texture.Height);
}
}
}
private:
struct MetaCoreRmlGeometryRecord {
std::vector<MetaCoreRuntimeUiDrawVertex> Vertices{};
std::vector<std::uint32_t> Indices{};
};
struct MetaCoreRmlTextureRecord {
std::uint64_t Handle = 0;
std::int32_t Width = 0;
std::int32_t Height = 0;
std::uint64_t Revision = 0;
std::vector<std::uint8_t> Rgba{};
};
MetaCoreRuntimeUiRenderStats& Stats_;
MetaCoreRuntimeUiFrame& Frame_;
Rml::CompiledGeometryHandle NextGeometryHandle_ = 1;
Rml::TextureHandle NextTextureHandle_ = 1;
std::uint64_t NextTextureRevision_ = 1;
std::unordered_map<Rml::CompiledGeometryHandle, MetaCoreRmlGeometryRecord> Geometries_{};
std::unordered_map<Rml::TextureHandle, MetaCoreRmlTextureRecord> Textures_{};
bool ScissorEnabled_ = false;
Rml::Rectanglei ScissorRegion_{};
};
std::uint64_t MetaCoreNextRuntimeUiContextId() {
static std::uint64_t nextContextId = 1;
return nextContextId++;
}
} // namespace
struct MetaCoreRuntimeUiRenderer::MetaCoreRuntimeUiRendererImpl {
explicit MetaCoreRuntimeUiRendererImpl(MetaCoreRuntimeUiRenderStats& stats)
: RenderInterface(stats, Frame) {}
MetaCoreCompiledRmlUiDocument Document{};
MetaCoreRuntimeUiFrame Frame{};
MetaCoreRuntimeUiRasterFrame RasterFrame{};
MetaCoreRmlDiagnosticRenderInterface RenderInterface;
Rml::Context* Context = nullptr;
Rml::ElementDocument* LoadedDocument = nullptr;
std::string ContextName{};
bool SharedRuntimeAcquired = false;
};
MetaCoreRuntimeUiRenderer::MetaCoreRuntimeUiRenderer() = default;
MetaCoreRuntimeUiRenderer::~MetaCoreRuntimeUiRenderer() {
Shutdown();
}
bool MetaCoreRuntimeUiRenderer::Initialize() {
Shutdown();
Impl_ = std::make_unique<MetaCoreRuntimeUiRendererImpl>(Stats_);
Impl_->SharedRuntimeAcquired = MetaCoreGetRmlSharedRuntime().Acquire(Stats_);
if (!Impl_->SharedRuntimeAcquired) {
Impl_.reset();
return false;
}
Impl_->ContextName = "MetaCoreRuntimeUi_" + std::to_string(MetaCoreNextRuntimeUiContextId());
Impl_->Context = Rml::CreateContext(
Impl_->ContextName,
Rml::Vector2i(1, 1),
&Impl_->RenderInterface
);
if (Impl_->Context == nullptr) {
Stats_.LastError = "RmlUi context creation failed";
MetaCoreGetRmlSharedRuntime().Release();
Impl_.reset();
return false;
}
Stats_.Initialized = true;
Stats_.RmlInitialized = true;
Stats_.RmlContextCreated = true;
Stats_.RmlDocumentLoaded = false;
Stats_.LastError.clear();
return true;
}
void MetaCoreRuntimeUiRenderer::Shutdown() {
if (Impl_ != nullptr) {
if (Impl_->Context != nullptr && Impl_->LoadedDocument != nullptr) {
Impl_->Context->UnloadDocument(Impl_->LoadedDocument);
Impl_->Context->Update();
Impl_->LoadedDocument = nullptr;
}
if (!Impl_->ContextName.empty()) {
(void)Rml::RemoveContext(Impl_->ContextName);
Impl_->Context = nullptr;
}
if (Impl_->SharedRuntimeAcquired) {
Rml::ReleaseCompiledGeometry(&Impl_->RenderInterface);
Rml::ReleaseTextures(&Impl_->RenderInterface);
Rml::ReleaseRenderManagers();
MetaCoreGetRmlSharedRuntime().Release();
Impl_->SharedRuntimeAcquired = false;
}
}
Impl_.reset();
Stats_ = MetaCoreRuntimeUiRenderStats{};
}
bool MetaCoreRuntimeUiRenderer::LoadCompiledDocument(const MetaCoreCompiledRmlUiDocument& document) {
if (!Stats_.Initialized || Impl_ == nullptr || Impl_->Context == nullptr) {
Stats_.LastError = "Runtime UI renderer is not initialized";
return false;
}
if (document.Rml.empty()) {
Stats_.Loaded = false;
Stats_.RmlDocumentLoaded = false;
Stats_.RmlBytes = 0;
Stats_.RcssBytes = 0;
Stats_.LastError = "Compiled runtime UI RML is empty";
return false;
}
if (Impl_->LoadedDocument != nullptr) {
Impl_->Context->UnloadDocument(Impl_->LoadedDocument);
Impl_->Context->Update();
Impl_->LoadedDocument = nullptr;
}
Impl_->Document = document;
const std::string stylesheetHref = Impl_->Document.StylesheetHref.empty()
? "MetaCoreRuntimeUi.rcss"
: Impl_->Document.StylesheetHref;
MetaCoreGetRmlSharedRuntime().FileInterface.SetVirtualFile(stylesheetHref, Impl_->Document.Rcss);
Impl_->LoadedDocument = Impl_->Context->LoadDocumentFromMemory(
Impl_->Document.Rml,
"MetaCoreRuntimeUi.rml"
);
if (Impl_->LoadedDocument == nullptr) {
Stats_.Loaded = false;
Stats_.RmlDocumentLoaded = false;
Stats_.LastError = "RmlUi failed to load compiled runtime UI document";
return false;
}
Impl_->LoadedDocument->Show(Rml::ModalFlag::None, Rml::FocusFlag::None);
Impl_->Context->Update();
Stats_.Loaded = true;
Stats_.RmlDocumentLoaded = true;
Stats_.RmlBytes = Impl_->Document.Rml.size();
Stats_.RcssBytes = Impl_->Document.Rcss.size();
Stats_.LastError.clear();
return true;
}
bool MetaCoreRuntimeUiRenderer::HasNode(std::string_view nodeId) const {
if (!Stats_.Initialized || !Stats_.Loaded || Impl_ == nullptr || Impl_->LoadedDocument == nullptr) {
return false;
}
const std::string elementId = MetaCoreBuildRuntimeUiNodeElementId(nodeId);
return Impl_->LoadedDocument->GetElementById(elementId) != nullptr;
}
bool MetaCoreRuntimeUiRenderer::SetNodeText(std::string_view nodeId, std::string_view text) {
if (!Stats_.Initialized || !Stats_.Loaded || Impl_ == nullptr || Impl_->LoadedDocument == nullptr) {
Stats_.LastError = "Runtime UI document is not loaded";
return false;
}
const std::string elementId = MetaCoreBuildRuntimeUiNodeElementId(nodeId);
Rml::Element* element = Impl_->LoadedDocument->GetElementById(elementId);
if (element == nullptr) {
Stats_.LastError = "Runtime UI node was not found: " + std::string(nodeId);
return false;
}
element->SetInnerRML(MetaCoreEscapeRuntimeUiRmlText(text));
if (Impl_->Context != nullptr) {
Impl_->Context->Update();
}
Stats_.LastError.clear();
return true;
}
void MetaCoreRuntimeUiRenderer::Resize(std::int32_t width, std::int32_t height) {
Stats_.Width = std::max<std::int32_t>(0, width);
Stats_.Height = std::max<std::int32_t>(0, height);
if (Impl_ != nullptr && Impl_->Context != nullptr) {
Impl_->Context->SetDimensions(Rml::Vector2i(
std::max<std::int32_t>(1, width),
std::max<std::int32_t>(1, height)
));
}
}
void MetaCoreRuntimeUiRenderer::BeginFrame(float deltaSeconds) {
if (!Stats_.Initialized) {
return;
}
Stats_.LastDeltaSeconds = deltaSeconds;
++Stats_.FrameCount;
MetaCoreGetRmlSharedRuntime().SystemInterface.Advance(deltaSeconds);
if (Impl_ != nullptr && Impl_->Context != nullptr) {
Impl_->Context->Update();
}
}
void MetaCoreRuntimeUiRenderer::Render() {
if (!Stats_.Initialized || !Stats_.Loaded || Impl_ == nullptr || Impl_->Context == nullptr) {
return;
}
Impl_->RenderInterface.BeginFrame();
if (!Impl_->Context->Render()) {
Stats_.LastError = "RmlUi context render failed";
}
Impl_->RenderInterface.SyncTexturesToFrame();
MetaCoreRuntimeUiRasterizeFrame(
Impl_->Frame,
Impl_->RasterFrame,
Stats_,
Stats_.Width,
Stats_.Height
);
}
const MetaCoreRuntimeUiRenderStats& MetaCoreRuntimeUiRenderer::GetStats() const {
return Stats_;
}
const MetaCoreRuntimeUiFrame& MetaCoreRuntimeUiRenderer::GetLastFrame() const {
static const MetaCoreRuntimeUiFrame emptyFrame{};
return Impl_ == nullptr ? emptyFrame : Impl_->Frame;
}
const MetaCoreRuntimeUiRasterFrame& MetaCoreRuntimeUiRenderer::GetLastRasterFrame() const {
static const MetaCoreRuntimeUiRasterFrame emptyRasterFrame{};
return Impl_ == nullptr ? emptyRasterFrame : Impl_->RasterFrame;
}
} // namespace MetaCore

View File

@ -12,6 +12,7 @@ namespace MetaCore {
class MetaCoreRenderDevice;
class MetaCoreWindow;
class MetaCoreScene;
struct MetaCoreRuntimeUiFrame;
class MetaCoreEditorViewportRenderer {
public:
@ -19,6 +20,7 @@ public:
void Shutdown();
void SetViewportRect(const MetaCoreViewportRect& viewportRect);
void RenderSceneToViewport(MetaCoreScene& scene, const MetaCoreSceneView& sceneView, bool useScenePrimaryCamera = false);
void SetRuntimeUiOverlayFrame(const MetaCoreRuntimeUiFrame& frame, int width, int height);
void RenderAll();
void SetProjectRootPath(const std::filesystem::path& projectRootPath);
[[nodiscard]] uint32_t GetFilamentGLTextureId() const;

View File

@ -14,6 +14,7 @@ class MetaCoreWindow;
class MetaCoreScene;
struct MetaCoreSceneView;
struct MetaCoreSceneRenderSyncSnapshot;
struct MetaCoreRuntimeUiFrame;
/**
* @brief Filament MetaCore Filament
@ -30,6 +31,7 @@ public:
void SyncScene(MetaCoreScene& scene, bool compatibilityMeshOnly = false, bool useScenePrimaryCamera = false);
void SyncScene(const MetaCoreSceneRenderSyncSnapshot& snapshot, MetaCoreScene* scene = nullptr, bool compatibilityMeshOnly = false, bool useScenePrimaryCamera = false);
void ApplySceneView(const MetaCoreSceneView& sceneView);
void SetRuntimeUiOverlayFrame(const MetaCoreRuntimeUiFrame& frame, int width, int height);
void RenderAll();
[[nodiscard]] uint32_t GetGLTextureId() const;
[[nodiscard]] void* GetFilamentTexturePointer() const;

View File

@ -10,9 +10,11 @@
#include <filament/IndexBuffer.h>
#include <utils/Entity.h>
#include <cstdint>
#include <filesystem>
#include <functional>
#include <vector>
#include <unordered_map>
#include <unordered_set>
struct ImDrawData;
@ -21,6 +23,8 @@ struct ImGuiContext;
namespace MetaCore {
struct MetaCoreRuntimeUiFrame;
class MetaCoreImGuiHelper {
public:
using Callback = std::function<void(filament::Engine*, filament::View*)>;
@ -35,17 +39,27 @@ public:
void render(float timeStepInSeconds, Callback imguiCommands);
void processImGuiCommands(ImDrawData* commands, const ImGuiIO& io);
[[nodiscard]] bool processRuntimeUiFrame(const MetaCoreRuntimeUiFrame& frame, int width, int height);
void createAtlasTexture(filament::Engine* engine);
filament::View* getView() const { return mView; }
private:
struct RuntimeUiTextureState {
filament::Texture* Texture = nullptr;
std::int32_t Width = 0;
std::int32_t Height = 0;
std::uint64_t Revision = 0;
};
void createBuffers(int numRequiredBuffers);
void populateVertexData(size_t bufferIndex, size_t vbSizeInBytes, void* vbData,
size_t ibSizeInBytes, void* ibData);
void createVertexBuffer(size_t bufferIndex, size_t capacity);
void createIndexBuffer(size_t bufferIndex, size_t capacity);
[[nodiscard]] filament::Texture* getWhiteTexture();
[[nodiscard]] filament::Texture* syncRuntimeUiTexture(const MetaCoreRuntimeUiFrame& frame, std::uint64_t handle);
void syncThreads();
filament::Engine* mEngine;
@ -59,6 +73,7 @@ private:
utils::Entity mRenderable;
utils::Entity mCameraEntity;
filament::Texture* mTexture = nullptr;
filament::Texture* mWhiteTexture = nullptr;
bool mHasSynced = false;
ImGuiContext* mImGuiContext;
filament::TextureSampler mSampler;
@ -66,6 +81,7 @@ private:
bool mOwnsImGuiContext = false;
std::filesystem::path mSettingsPath;
std::unordered_set<filament::Texture*> mImGuiTextures;
std::unordered_map<std::uint64_t, RuntimeUiTextureState> mRuntimeUiTextures;
};
} // namespace MetaCore

View File

@ -0,0 +1,115 @@
#pragma once
#include "MetaCoreScene/MetaCoreUiRmlCompiler.h"
#include <cstdint>
#include <cstddef>
#include <memory>
#include <string>
#include <string_view>
#include <vector>
namespace MetaCore {
struct MetaCoreRuntimeUiDrawVertex {
float X = 0.0F;
float Y = 0.0F;
float U = 0.0F;
float V = 0.0F;
std::uint8_t R = 255;
std::uint8_t G = 255;
std::uint8_t B = 255;
std::uint8_t A = 255;
};
struct MetaCoreRuntimeUiDrawCommand {
std::size_t VertexOffset = 0;
std::size_t VertexCount = 0;
std::size_t IndexOffset = 0;
std::size_t IndexCount = 0;
std::uint64_t TextureHandle = 0;
bool ScissorEnabled = false;
std::int32_t ScissorLeft = 0;
std::int32_t ScissorTop = 0;
std::int32_t ScissorRight = 0;
std::int32_t ScissorBottom = 0;
};
struct MetaCoreRuntimeUiTexture {
std::uint64_t Handle = 0;
std::int32_t Width = 0;
std::int32_t Height = 0;
std::uint64_t Revision = 0;
std::vector<std::uint8_t> Rgba{};
};
struct MetaCoreRuntimeUiFrame {
std::vector<MetaCoreRuntimeUiDrawVertex> Vertices{};
std::vector<std::uint32_t> Indices{};
std::vector<MetaCoreRuntimeUiDrawCommand> Commands{};
std::vector<MetaCoreRuntimeUiTexture> Textures{};
};
struct MetaCoreRuntimeUiRasterFrame {
std::int32_t Width = 0;
std::int32_t Height = 0;
std::vector<std::uint8_t> Rgba{};
};
struct MetaCoreRuntimeUiRenderStats {
bool Initialized = false;
bool Loaded = false;
bool RmlInitialized = false;
bool RmlContextCreated = false;
bool RmlDocumentLoaded = false;
std::int32_t Width = 0;
std::int32_t Height = 0;
std::size_t RmlBytes = 0;
std::size_t RcssBytes = 0;
std::uint64_t FrameCount = 0;
std::uint64_t RenderGeometryCalls = 0;
std::uint64_t TextureLoadRequests = 0;
std::uint64_t TextureGenerateRequests = 0;
std::size_t CompiledGeometryCount = 0;
std::size_t DrawCommandCount = 0;
std::size_t DrawVertexCount = 0;
std::size_t DrawIndexCount = 0;
std::size_t TextureCount = 0;
std::size_t TexturePixelCount = 0;
std::size_t RasterPixelCount = 0;
std::size_t RasterTouchedPixelCount = 0;
float LastDeltaSeconds = 0.0F;
std::string LastError{};
};
class MetaCoreRuntimeUiRenderer {
public:
MetaCoreRuntimeUiRenderer();
~MetaCoreRuntimeUiRenderer();
MetaCoreRuntimeUiRenderer(const MetaCoreRuntimeUiRenderer&) = delete;
MetaCoreRuntimeUiRenderer& operator=(const MetaCoreRuntimeUiRenderer&) = delete;
MetaCoreRuntimeUiRenderer(MetaCoreRuntimeUiRenderer&&) noexcept = delete;
MetaCoreRuntimeUiRenderer& operator=(MetaCoreRuntimeUiRenderer&&) noexcept = delete;
[[nodiscard]] bool Initialize();
void Shutdown();
[[nodiscard]] bool LoadCompiledDocument(const MetaCoreCompiledRmlUiDocument& document);
[[nodiscard]] bool HasNode(std::string_view nodeId) const;
[[nodiscard]] bool SetNodeText(std::string_view nodeId, std::string_view text);
void Resize(std::int32_t width, std::int32_t height);
void BeginFrame(float deltaSeconds);
void Render();
[[nodiscard]] const MetaCoreRuntimeUiRenderStats& GetStats() const;
[[nodiscard]] const MetaCoreRuntimeUiFrame& GetLastFrame() const;
[[nodiscard]] const MetaCoreRuntimeUiRasterFrame& GetLastRasterFrame() const;
private:
struct MetaCoreRuntimeUiRendererImpl;
std::unique_ptr<MetaCoreRuntimeUiRendererImpl> Impl_{};
MetaCoreRuntimeUiRenderStats Stats_{};
};
} // namespace MetaCore

View File

@ -31,6 +31,15 @@ void MetaCoreRuntimeDataDispatcher::ApplyUpdates(const std::vector<MetaCoreRunti
continue;
}
if (update.Value.Type != dataPointDefinition->ValueType) {
MarkBindingFault(bindingDefinition.BindingId, "Update type does not match data point definition");
continue;
}
if (update.Value.Quality == MetaCoreRuntimeDataQuality::Bad) {
MarkBindingFault(bindingDefinition.BindingId, "Update quality is bad");
continue;
}
MetaCoreGameObject gameObject = Scene_.FindGameObject(bindingDefinition.TargetObjectId);
if (!gameObject) {
MarkBindingFault(bindingDefinition.BindingId, "Target object missing");

View File

@ -2,6 +2,7 @@
#include <algorithm>
#include <fstream>
#include <unordered_map>
#include <unordered_set>
namespace MetaCore {
@ -57,6 +58,33 @@ template <typename T>
return document;
}
[[nodiscard]] MetaCoreRuntimeValueType MetaCoreExpectedValueTypeForBindingTarget(
MetaCoreRuntimeBindingTarget target
) {
switch (target) {
case MetaCoreRuntimeBindingTarget::MeshRendererVisible:
return MetaCoreRuntimeValueType::Bool;
case MetaCoreRuntimeBindingTarget::LightIntensity:
return MetaCoreRuntimeValueType::Double;
case MetaCoreRuntimeBindingTarget::TransformPosition:
case MetaCoreRuntimeBindingTarget::MeshRendererBaseColor:
case MetaCoreRuntimeBindingTarget::LightColor:
default:
return MetaCoreRuntimeValueType::Vec3;
}
}
[[nodiscard]] const char* MetaCoreRuntimeValueTypeName(MetaCoreRuntimeValueType type) {
switch (type) {
case MetaCoreRuntimeValueType::Bool: return "Bool";
case MetaCoreRuntimeValueType::Int64: return "Int64";
case MetaCoreRuntimeValueType::Double: return "Double";
case MetaCoreRuntimeValueType::String: return "String";
case MetaCoreRuntimeValueType::Vec3: return "Vec3";
}
return "Unknown";
}
} // namespace
bool MetaCoreWriteRuntimeDataSourcesDocument(
@ -104,6 +132,109 @@ std::optional<MetaCoreRuntimeProjectDocument> MetaCoreReadRuntimeProjectDocument
return MetaCoreReadBinaryDocument<MetaCoreRuntimeProjectDocument>(path, registry);
}
bool MetaCoreIsUnsafeRuntimeProjectPath(const std::filesystem::path& path) {
if (path.is_absolute()) {
return true;
}
for (const auto& part : path.lexically_normal()) {
if (part == "..") {
return true;
}
}
return false;
}
std::filesystem::path MetaCoreBuildRuntimeDirectoryRelativePath(
const std::filesystem::path& projectRoot,
const std::filesystem::path& runtimeDirectory
) {
std::filesystem::path relativeRuntimeDirectory = !runtimeDirectory.empty()
? runtimeDirectory.lexically_relative(projectRoot)
: std::filesystem::path("Runtime");
if (relativeRuntimeDirectory.empty() ||
relativeRuntimeDirectory == "." ||
MetaCoreIsUnsafeRuntimeProjectPath(relativeRuntimeDirectory)) {
relativeRuntimeDirectory = "Runtime";
}
return relativeRuntimeDirectory.lexically_normal();
}
MetaCoreRuntimeProjectDocument MetaCoreBuildDefaultRuntimeProjectDocument(
const std::filesystem::path& runtimeDirectoryRelative
) {
MetaCoreRuntimeProjectDocument document;
document.StartupScenePath = std::filesystem::path("Scenes") / "Main.mcscene.json";
document.StartupUiPath = std::filesystem::path("Assets") / "UI" / "Hud.mcui.json";
document.BuildProfileName = "Development";
document.TargetPlatform = "Windows";
document.OutputDirectory = std::filesystem::path("Build") / "Windows";
document.CookedAssetsDirectory = std::filesystem::path("Library") / "Cooked" / "Windows";
document.UseCookedAssets = false;
document.DataSourcesPath = runtimeDirectoryRelative / "DataSources.mcruntime";
document.BindingsPath = runtimeDirectoryRelative / "Bindings.mcruntime";
document.DiagnosticsPath = runtimeDirectoryRelative / "Diagnostics.mcruntimestate";
return document;
}
void MetaCoreApplyRuntimeProjectDefaults(
MetaCoreRuntimeProjectDocument& document,
const std::filesystem::path& runtimeDirectoryRelative
) {
const MetaCoreRuntimeProjectDocument defaults =
MetaCoreBuildDefaultRuntimeProjectDocument(runtimeDirectoryRelative);
if (document.StartupScenePath.empty()) {
document.StartupScenePath = defaults.StartupScenePath;
}
if (document.StartupUiPath.empty()) {
document.StartupUiPath = defaults.StartupUiPath;
}
if (document.BuildProfileName.empty()) {
document.BuildProfileName = defaults.BuildProfileName;
}
if (document.TargetPlatform.empty()) {
document.TargetPlatform = defaults.TargetPlatform;
}
if (document.OutputDirectory.empty()) {
document.OutputDirectory = defaults.OutputDirectory;
}
if (document.CookedAssetsDirectory.empty()) {
document.CookedAssetsDirectory = defaults.CookedAssetsDirectory;
}
if (document.DataSourcesPath.empty()) {
document.DataSourcesPath = defaults.DataSourcesPath;
}
if (document.BindingsPath.empty()) {
document.BindingsPath = defaults.BindingsPath;
}
if (document.DiagnosticsPath.empty()) {
document.DiagnosticsPath = defaults.DiagnosticsPath;
}
}
std::vector<MetaCoreRuntimeConfigIssue> MetaCoreValidateRuntimeProjectPaths(
const MetaCoreRuntimeProjectDocument& document
) {
std::vector<MetaCoreRuntimeConfigIssue> issues;
const auto validatePath = [&](const std::filesystem::path& path, const char* label) {
if (!path.empty() && MetaCoreIsUnsafeRuntimeProjectPath(path)) {
issues.push_back(MetaCoreRuntimeConfigIssue{
MetaCoreRuntimeConfigIssueSeverity::Error,
"RuntimeProject",
std::string(label) + " must be a project-relative path without parent traversal"
});
}
};
validatePath(document.StartupScenePath, "StartupScenePath");
validatePath(document.StartupUiPath, "StartupUiPath");
validatePath(document.CookedAssetsDirectory, "CookedAssetsDirectory");
validatePath(document.DataSourcesPath, "DataSourcesPath");
validatePath(document.BindingsPath, "BindingsPath");
validatePath(document.DiagnosticsPath, "DiagnosticsPath");
return issues;
}
bool MetaCoreWriteRuntimeDiagnosticsSnapshot(
const std::filesystem::path& path,
const MetaCoreRuntimeDiagnosticsSnapshot& snapshot,
@ -164,6 +295,7 @@ std::vector<MetaCoreRuntimeConfigIssue> MetaCoreValidateRuntimeDataDocuments(
}
std::unordered_set<std::string> dataPointIds;
std::unordered_map<std::string, MetaCoreRuntimeValueType> dataPointValueTypes;
for (const auto& dataPoint : sourcesDocument.DataPoints) {
if (dataPoint.Id.empty()) {
issues.push_back({MetaCoreRuntimeConfigIssueSeverity::Error, "DataPoint", "存在空的 DataPointId"});
@ -175,6 +307,7 @@ std::vector<MetaCoreRuntimeConfigIssue> MetaCoreValidateRuntimeDataDocuments(
if (dataPoint.SourceId.empty() || !sourceIds.contains(dataPoint.SourceId)) {
issues.push_back({MetaCoreRuntimeConfigIssueSeverity::Error, dataPoint.Id, "DataPoint 引用了不存在的 SourceId"});
}
dataPointValueTypes[dataPoint.Id] = dataPoint.ValueType;
if (dataPoint.ExternalAddress.empty()) {
issues.push_back({MetaCoreRuntimeConfigIssueSeverity::Warning, dataPoint.Id, "ExternalAddress 为空"});
}
@ -192,11 +325,40 @@ std::vector<MetaCoreRuntimeConfigIssue> MetaCoreValidateRuntimeDataDocuments(
if (binding.DataPointId.empty() || !dataPointIds.contains(binding.DataPointId)) {
issues.push_back({MetaCoreRuntimeConfigIssueSeverity::Error, binding.BindingId, "Binding 引用了不存在的 DataPointId"});
}
else if (const auto dataPointType = dataPointValueTypes.find(binding.DataPointId);
dataPointType != dataPointValueTypes.end()) {
const MetaCoreRuntimeValueType expectedType =
MetaCoreExpectedValueTypeForBindingTarget(binding.Target);
if (dataPointType->second != expectedType) {
issues.push_back({
MetaCoreRuntimeConfigIssueSeverity::Error,
binding.BindingId,
"Binding target expects " + std::string(MetaCoreRuntimeValueTypeName(expectedType)) +
" but DataPoint is " + MetaCoreRuntimeValueTypeName(dataPointType->second)
});
}
}
if (binding.TargetObjectId == 0) {
issues.push_back({MetaCoreRuntimeConfigIssueSeverity::Error, binding.BindingId, "Binding 缺少 TargetObjectId"});
}
}
for (const auto& binding : bindingsDocument.UiBindings) {
if (binding.BindingId.empty()) {
issues.push_back({MetaCoreRuntimeConfigIssueSeverity::Error, "UiBinding", "UI BindingId is empty"});
continue;
}
if (!bindingIds.insert(binding.BindingId).second) {
issues.push_back({MetaCoreRuntimeConfigIssueSeverity::Error, binding.BindingId, "BindingId duplicate"});
}
if (binding.DataPointId.empty() || !dataPointIds.contains(binding.DataPointId)) {
issues.push_back({MetaCoreRuntimeConfigIssueSeverity::Error, binding.BindingId, "UI binding references a missing DataPointId"});
}
if (binding.TargetNodeId.empty()) {
issues.push_back({MetaCoreRuntimeConfigIssueSeverity::Error, binding.BindingId, "UI binding missing TargetNodeId"});
}
}
if (sourcesDocument.Sources.empty()) {
issues.push_back({MetaCoreRuntimeConfigIssueSeverity::Warning, "Runtime", "当前没有任何 Source"});
}

View File

@ -6,6 +6,7 @@
#include <filesystem>
#include <cstdint>
#include <optional>
#include <string>
#include <vector>
@ -82,6 +83,26 @@ struct MetaCoreSceneBindingDefinition {
MetaCoreRuntimeMissingDataPolicy MissingDataPolicy = MetaCoreRuntimeMissingDataPolicy::KeepLastValue;
};
MC_STRUCT()
struct MetaCoreUiBindingDefinition {
MC_GENERATED_BODY()
MC_PROPERTY()
std::string BindingId{};
MC_PROPERTY()
std::string DataPointId{};
MC_PROPERTY()
std::string TargetNodeId{};
MC_PROPERTY()
MetaCoreRuntimeUiBindingTarget Target = MetaCoreRuntimeUiBindingTarget::Text;
MC_PROPERTY()
MetaCoreRuntimeMissingDataPolicy MissingDataPolicy = MetaCoreRuntimeMissingDataPolicy::KeepLastValue;
};
MC_STRUCT()
struct MetaCoreRuntimeDataSourcesDocument {
MC_GENERATED_BODY()
@ -99,6 +120,9 @@ struct MetaCoreRuntimeBindingsDocument {
MC_PROPERTY()
std::vector<MetaCoreSceneBindingDefinition> Bindings{};
MC_PROPERTY()
std::vector<MetaCoreUiBindingDefinition> UiBindings{};
};
MC_STRUCT()
@ -108,6 +132,24 @@ struct MetaCoreRuntimeProjectDocument {
MC_PROPERTY()
std::filesystem::path StartupScenePath{};
MC_PROPERTY()
std::filesystem::path StartupUiPath{};
MC_PROPERTY()
std::string BuildProfileName{"Development"};
MC_PROPERTY()
std::string TargetPlatform{"Windows"};
MC_PROPERTY()
std::filesystem::path OutputDirectory{};
MC_PROPERTY()
std::filesystem::path CookedAssetsDirectory{};
MC_PROPERTY()
bool UseCookedAssets = false;
MC_PROPERTY()
std::filesystem::path DataSourcesPath{};
@ -151,6 +193,26 @@ struct MetaCoreRuntimeProjectDocument {
const MetaCoreTypeRegistry& registry
);
[[nodiscard]] bool MetaCoreIsUnsafeRuntimeProjectPath(const std::filesystem::path& path);
[[nodiscard]] std::filesystem::path MetaCoreBuildRuntimeDirectoryRelativePath(
const std::filesystem::path& projectRoot,
const std::filesystem::path& runtimeDirectory
);
[[nodiscard]] MetaCoreRuntimeProjectDocument MetaCoreBuildDefaultRuntimeProjectDocument(
const std::filesystem::path& runtimeDirectoryRelative = std::filesystem::path("Runtime")
);
void MetaCoreApplyRuntimeProjectDefaults(
MetaCoreRuntimeProjectDocument& document,
const std::filesystem::path& runtimeDirectoryRelative = std::filesystem::path("Runtime")
);
[[nodiscard]] std::vector<MetaCoreRuntimeConfigIssue> MetaCoreValidateRuntimeProjectPaths(
const MetaCoreRuntimeProjectDocument& document
);
[[nodiscard]] bool MetaCoreWriteRuntimeDiagnosticsSnapshot(
const std::filesystem::path& path,
const MetaCoreRuntimeDiagnosticsSnapshot& snapshot,

View File

@ -5,6 +5,7 @@
#include <cstdint>
#include <string>
#include <vector>
#include <glm/vec3.hpp>
@ -44,6 +45,11 @@ enum class MetaCoreRuntimeBindingTarget : std::uint32_t {
LightColor
};
MC_ENUM()
enum class MetaCoreRuntimeUiBindingTarget : std::uint32_t {
Text = 0
};
MC_ENUM()
enum class MetaCoreRuntimeMissingDataPolicy : std::uint32_t {
KeepLastValue = 0,

View File

@ -4,6 +4,7 @@
#include "MetaCoreFoundation/MetaCoreHash.h"
#include "MetaCoreFoundation/MetaCorePackage.h"
#include "MetaCoreFoundation/MetaCoreProject.h"
#include "MetaCoreScene/MetaCoreSceneSerializer.h"
#include <fstream>
#include <sstream>
@ -155,6 +156,9 @@ std::optional<MetaCoreSceneDocument> MetaCoreLoadStartupSceneDocument(const std:
if (!startupScenePath.has_value()) {
return std::nullopt;
}
if (startupScenePath->extension() == ".json") {
return MetaCoreSceneSerializer::LoadSceneFromJson(*startupScenePath, MetaCoreBuildScenePackageTypeRegistry());
}
return MetaCoreReadScenePackage(*startupScenePath);
}

View File

@ -0,0 +1,279 @@
#include "MetaCoreScene/MetaCoreUiRmlCompiler.h"
#include <algorithm>
#include <cctype>
#include <cmath>
#include <sstream>
#include <string>
#include <unordered_map>
#include <unordered_set>
namespace MetaCore {
namespace {
[[nodiscard]] std::string MetaCoreEscapeRml(std::string_view value) {
std::string escaped;
escaped.reserve(value.size());
for (char c : value) {
switch (c) {
case '&': escaped += "&amp;"; break;
case '<': escaped += "&lt;"; break;
case '>': escaped += "&gt;"; break;
case '"': escaped += "&quot;"; break;
case '\'': escaped += "&apos;"; break;
default: escaped.push_back(c); break;
}
}
return escaped;
}
[[nodiscard]] std::string MetaCoreSanitizeIdentifier(std::string_view value, std::string_view fallback) {
std::string sanitized;
sanitized.reserve(value.size());
for (char c : value) {
const auto uc = static_cast<unsigned char>(c);
if (std::isalnum(uc) != 0) {
sanitized.push_back(static_cast<char>(std::tolower(uc)));
} else if (c == '_' || c == '-' || c == '.') {
sanitized.push_back(c == '.' ? '-' : c);
} else {
sanitized.push_back('-');
}
}
while (!sanitized.empty() && sanitized.front() == '-') {
sanitized.erase(sanitized.begin());
}
while (!sanitized.empty() && sanitized.back() == '-') {
sanitized.pop_back();
}
if (sanitized.empty()) {
sanitized = std::string(fallback);
}
if (std::isdigit(static_cast<unsigned char>(sanitized.front())) != 0) {
sanitized.insert(sanitized.begin(), 'n');
}
return sanitized;
}
[[nodiscard]] std::string MetaCoreUiNodeTypeClass(MetaCoreUiNodeType type) {
switch (type) {
case MetaCoreUiNodeType::Text: return "mcui-text";
case MetaCoreUiNodeType::Image: return "mcui-image";
case MetaCoreUiNodeType::Button: return "mcui-button";
case MetaCoreUiNodeType::Panel:
default: return "mcui-panel";
}
}
[[nodiscard]] std::string MetaCoreUiTagName(MetaCoreUiNodeType type) {
switch (type) {
case MetaCoreUiNodeType::Text: return "p";
case MetaCoreUiNodeType::Button: return "button";
case MetaCoreUiNodeType::Image:
case MetaCoreUiNodeType::Panel:
default: return "div";
}
}
[[nodiscard]] int MetaCoreColorChannel(float value) {
const float clamped = std::clamp(value, 0.0F, 1.0F);
return static_cast<int>(std::round(clamped * 255.0F));
}
[[nodiscard]] std::string MetaCoreRcssColor(const glm::vec3& color) {
std::ostringstream stream;
stream << "rgb("
<< MetaCoreColorChannel(color.r) << ", "
<< MetaCoreColorChannel(color.g) << ", "
<< MetaCoreColorChannel(color.b) << ")";
return stream.str();
}
[[nodiscard]] std::string MetaCoreRcssTextAlign(MetaCoreUiHorizontalAlignment alignment) {
switch (alignment) {
case MetaCoreUiHorizontalAlignment::Center: return "center";
case MetaCoreUiHorizontalAlignment::Right: return "right";
case MetaCoreUiHorizontalAlignment::Stretch:
case MetaCoreUiHorizontalAlignment::Left:
default: return "left";
}
}
[[nodiscard]] bool MetaCoreIsStretch(float minAnchor, float maxAnchor, float size) {
return std::abs(minAnchor) <= 0.0001F &&
std::abs(maxAnchor - 1.0F) <= 0.0001F &&
std::abs(size) <= 0.0001F;
}
void MetaCoreAppendNodeRcss(
std::ostringstream& output,
const MetaCoreUiNodeDocument& node,
std::string_view className
) {
output << "." << className << " {\n";
output << " position: absolute;\n";
output << " box-sizing: border-box;\n";
if (!node.Visible) {
output << " display: none;\n";
}
const auto& rect = node.RectTransform;
if (MetaCoreIsStretch(rect.AnchorMin.x, rect.AnchorMax.x, rect.Size.x)) {
output << " left: 0px;\n";
output << " right: 0px;\n";
} else {
output << " left: " << rect.Position.x << "px;\n";
output << " width: " << std::max(rect.Size.x, 0.0F) << "px;\n";
}
if (MetaCoreIsStretch(rect.AnchorMin.y, rect.AnchorMax.y, rect.Size.y)) {
output << " top: 0px;\n";
output << " bottom: 0px;\n";
} else {
output << " top: " << rect.Position.y << "px;\n";
output << " height: " << std::max(rect.Size.y, 0.0F) << "px;\n";
}
output << " padding: " << std::max(node.Style.Padding.y, 0.0F)
<< "px " << std::max(node.Style.Padding.x, 0.0F) << "px;\n";
output << " background-color: " << MetaCoreRcssColor(node.Style.BackgroundColor) << ";\n";
output << " color: " << MetaCoreRcssColor(node.Style.TextColor) << ";\n";
output << " font-size: " << std::max(node.Style.FontSize, 1.0F) << "px;\n";
output << " text-align: " << MetaCoreRcssTextAlign(node.Style.HorizontalAlignment) << ";\n";
if (node.Type == MetaCoreUiNodeType::Button) {
output << " border-width: 0px;\n";
}
if (node.Type == MetaCoreUiNodeType::Image) {
output << " background-color: " << MetaCoreRcssColor(node.Style.TintColor) << ";\n";
}
output << "}\n\n";
}
void MetaCoreAppendRmlNode(
std::ostringstream& output,
const MetaCoreUiNodeDocument& node,
const std::unordered_map<std::string, const MetaCoreUiNodeDocument*>& nodesById,
const std::unordered_map<std::string, std::string>& classById,
std::unordered_set<std::string>& activeStack,
int depth
) {
if (activeStack.contains(node.Id)) {
return;
}
activeStack.insert(node.Id);
const std::string indent(static_cast<std::size_t>(depth) * 2U, ' ');
const std::string tagName = MetaCoreUiTagName(node.Type);
const auto classIterator = classById.find(node.Id);
const std::string className = classIterator != classById.end() ? classIterator->second : "mcui-node";
const std::string nodeId = MetaCoreBuildRuntimeUiNodeElementId(node.Id);
output << indent << "<" << tagName
<< " id=\"" << MetaCoreEscapeRml(nodeId) << "\""
<< " class=\"mcui-node " << MetaCoreUiNodeTypeClass(node.Type) << " " << className << "\""
<< " data-metacore-id=\"" << MetaCoreEscapeRml(node.Id) << "\"";
if (node.Type == MetaCoreUiNodeType::Image && node.Style.ImageAssetGuid.IsValid()) {
output << " data-image-guid=\"" << MetaCoreEscapeRml(node.Style.ImageAssetGuid.ToString()) << "\"";
}
output << ">";
if (node.Type == MetaCoreUiNodeType::Text || node.Type == MetaCoreUiNodeType::Button) {
output << MetaCoreEscapeRml(node.Text);
}
if (!node.Children.empty()) {
output << "\n";
for (const std::string& childId : node.Children) {
const auto childIterator = nodesById.find(childId);
if (childIterator != nodesById.end()) {
MetaCoreAppendRmlNode(output, *childIterator->second, nodesById, classById, activeStack, depth + 1);
}
}
output << indent;
}
output << "</" << tagName << ">\n";
activeStack.erase(node.Id);
}
} // namespace
std::string MetaCoreBuildRuntimeUiNodeElementId(std::string_view nodeId) {
return MetaCoreSanitizeIdentifier(nodeId, "node");
}
MetaCoreCompiledRmlUiDocument MetaCoreCompileUiDocumentToRml(
const MetaCoreUiDocument& document,
std::string_view stylesheetHref
) {
std::unordered_map<std::string, const MetaCoreUiNodeDocument*> nodesById;
std::unordered_map<std::string, std::string> classById;
nodesById.reserve(document.Nodes.size());
classById.reserve(document.Nodes.size());
for (std::size_t index = 0; index < document.Nodes.size(); ++index) {
const MetaCoreUiNodeDocument& node = document.Nodes[index];
if (!node.Id.empty()) {
nodesById[node.Id] = &node;
classById[node.Id] = "mcui-node-" + std::to_string(index);
}
}
MetaCoreCompiledRmlUiDocument compiled;
compiled.StylesheetHref = std::string(stylesheetHref);
std::ostringstream rml;
rml << "<rml>\n";
rml << " <head>\n";
rml << " <link type=\"text/rcss\" href=\"" << MetaCoreEscapeRml(stylesheetHref) << "\" />\n";
rml << " </head>\n";
rml << " <body>\n";
rml << " <div id=\"metacore-ui-root\" class=\"mcui-document\" data-metacore-document=\""
<< MetaCoreEscapeRml(document.Name) << "\">\n";
std::unordered_set<std::string> activeStack;
for (const std::string& rootId : document.RootNodeIds) {
const auto rootIterator = nodesById.find(rootId);
if (rootIterator != nodesById.end()) {
MetaCoreAppendRmlNode(rml, *rootIterator->second, nodesById, classById, activeStack, 3);
}
}
rml << " </div>\n";
rml << " </body>\n";
rml << "</rml>\n";
compiled.Rml = rml.str();
std::ostringstream rcss;
rcss << "body {\n";
rcss << " margin: 0px;\n";
rcss << " padding: 0px;\n";
rcss << "}\n\n";
rcss << ".mcui-document {\n";
rcss << " position: relative;\n";
rcss << " width: " << std::max(document.ReferenceWidth, 1) << "px;\n";
rcss << " height: " << std::max(document.ReferenceHeight, 1) << "px;\n";
rcss << "}\n\n";
rcss << ".mcui-node {\n";
rcss << " overflow: hidden;\n";
rcss << "}\n\n";
for (const MetaCoreUiNodeDocument& node : document.Nodes) {
if (node.Id.empty()) {
continue;
}
const auto classIterator = classById.find(node.Id);
if (classIterator != classById.end()) {
MetaCoreAppendNodeRcss(rcss, node, classIterator->second);
}
}
compiled.Rcss = rcss.str();
return compiled;
}
} // namespace MetaCore

View File

@ -35,11 +35,11 @@ MC_STRUCT()
struct MetaCoreTransformComponent {
MC_GENERATED_BODY()
MC_PROPERTY()
MC_PROPERTY(DisplayName = "Position", Group = "Transform")
glm::vec3 Position{0.0F, 0.0F, 0.0F};
MC_PROPERTY()
MC_PROPERTY(DisplayName = "Rotation", Group = "Transform")
glm::vec3 RotationEulerDegrees{0.0F, 0.0F, 0.0F};
MC_PROPERTY()
MC_PROPERTY(DisplayName = "Scale", Group = "Transform")
glm::vec3 Scale{1.0F, 1.0F, 1.0F};
};

View File

@ -0,0 +1,23 @@
#pragma once
#include "MetaCoreScene/MetaCoreSceneDocument.h"
#include <string>
#include <string_view>
namespace MetaCore {
struct MetaCoreCompiledRmlUiDocument {
std::string Rml{};
std::string Rcss{};
std::string StylesheetHref{};
};
[[nodiscard]] std::string MetaCoreBuildRuntimeUiNodeElementId(std::string_view nodeId);
[[nodiscard]] MetaCoreCompiledRmlUiDocument MetaCoreCompileUiDocumentToRml(
const MetaCoreUiDocument& document,
std::string_view stylesheetHref = "MetaCoreRuntimeUi.rcss"
);
} // namespace MetaCore

View File

@ -0,0 +1,126 @@
{
"Name": "RuntimeHud",
"ReferenceWidth": 1280,
"ReferenceHeight": 720,
"RootNodeIds": [
"hud.root"
],
"Nodes": [
{
"Id": "hud.root",
"Name": "HUD Root",
"Type": 0,
"ParentId": "",
"Children": [
"hud.title",
"hud.status",
"hud.button"
],
"Visible": true,
"RectTransform": {
"AnchorMin": [0.0, 0.0, 0.0],
"AnchorMax": [0.0, 0.0, 0.0],
"Pivot": [0.0, 0.0, 0.0],
"Position": [24.0, 24.0, 0.0],
"Size": [360.0, 164.0, 0.0]
},
"Style": {
"BackgroundColor": [0.05, 0.08, 0.12],
"TextColor": [1.0, 1.0, 1.0],
"TintColor": [1.0, 1.0, 1.0],
"FontSize": 16.0,
"Padding": [12.0, 12.0, 0.0],
"HorizontalAlignment": 0,
"VerticalAlignment": 0,
"ImageAssetGuid": "",
"PreserveAspect": false
},
"Text": "",
"Interactable": false
},
{
"Id": "hud.title",
"Name": "Title",
"Type": 1,
"ParentId": "hud.root",
"Children": [],
"Visible": true,
"RectTransform": {
"AnchorMin": [0.0, 0.0, 0.0],
"AnchorMax": [0.0, 0.0, 0.0],
"Pivot": [0.0, 0.0, 0.0],
"Position": [16.0, 14.0, 0.0],
"Size": [320.0, 36.0, 0.0]
},
"Style": {
"BackgroundColor": [0.08, 0.11, 0.16],
"TextColor": [0.95, 0.98, 1.0],
"TintColor": [1.0, 1.0, 1.0],
"FontSize": 24.0,
"Padding": [8.0, 4.0, 0.0],
"HorizontalAlignment": 0,
"VerticalAlignment": 0,
"ImageAssetGuid": "",
"PreserveAspect": false
},
"Text": "MetaCore Runtime",
"Interactable": false
},
{
"Id": "hud.status",
"Name": "Status Strip",
"Type": 0,
"ParentId": "hud.root",
"Children": [],
"Visible": true,
"RectTransform": {
"AnchorMin": [0.0, 0.0, 0.0],
"AnchorMax": [0.0, 0.0, 0.0],
"Pivot": [0.0, 0.0, 0.0],
"Position": [16.0, 62.0, 0.0],
"Size": [220.0, 34.0, 0.0]
},
"Style": {
"BackgroundColor": [0.12, 0.42, 0.82],
"TextColor": [1.0, 1.0, 1.0],
"TintColor": [1.0, 1.0, 1.0],
"FontSize": 16.0,
"Padding": [8.0, 4.0, 0.0],
"HorizontalAlignment": 0,
"VerticalAlignment": 0,
"ImageAssetGuid": "",
"PreserveAspect": false
},
"Text": "",
"Interactable": false
},
{
"Id": "hud.button",
"Name": "Action Button",
"Type": 3,
"ParentId": "hud.root",
"Children": [],
"Visible": true,
"RectTransform": {
"AnchorMin": [0.0, 0.0, 0.0],
"AnchorMax": [0.0, 0.0, 0.0],
"Pivot": [0.0, 0.0, 0.0],
"Position": [16.0, 110.0, 0.0],
"Size": [144.0, 38.0, 0.0]
},
"Style": {
"BackgroundColor": [0.18, 0.72, 0.36],
"TextColor": [0.02, 0.04, 0.03],
"TintColor": [1.0, 1.0, 1.0],
"FontSize": 17.0,
"Padding": [10.0, 5.0, 0.0],
"HorizontalAlignment": 1,
"VerticalAlignment": 0,
"ImageAssetGuid": "",
"PreserveAspect": false
},
"Text": "Run",
"Interactable": true
}
]
}

View File

@ -0,0 +1,69 @@
# MetaCore Infernux Reference Roadmap
Status: active implementation reference
Updated: 2026-06-01
## Goal
Use the local Infernux source tree as a Unity-like behavior reference while keeping MetaCore's native architecture intact.
MetaCore keeps:
- C++20 static components as the first scripting surface.
- EnTT scene storage with GameObject/Component editor semantics.
- Filament rendering.
- ImGui editor UI.
- RmlUi runtime UI.
- JSON authoring assets and Cooked runtime assets.
Infernux is used only to understand complete engine workflows. It is not a build dependency, and its Python/Vulkan architecture is not imported into MetaCore.
Local reference path:
```text
D:/MetaCore/.codex/external/Infernux
```
## Reference Policy
Allowed:
- Read Infernux source before implementing a MetaCore feature.
- Extract user-visible behavior, state transitions, data flow, and acceptance tests.
- Recreate workflows in MetaCore's C++/Filament/RmlUi architecture.
Not allowed for the mainline implementation:
- Add Infernux as a dependency.
- Import pybind11 or the Python production layer.
- Port the Vulkan RenderGraph or RenderStack.
- Use Infernux packaging based on Nuitka/PyInstaller.
- Use ImGui as MetaCore runtime UI.
Small MIT-licensed code snippets may only be copied after an explicit licensing review and with attribution. The default is behavior-level reimplementation.
## Module Map
| Milestone | Infernux reference | MetaCore implementation target | Implementation rule |
| --- | --- | --- | --- |
| M1 Component/Inspector | `components/component.py`, `components/serialized_field.py`, `engine/ui/inspector_components.py`, `engine/ui/inspector_utils.py` | `MetaCoreReflection`, `MetaCoreIComponentTypeRegistry`, editor Inspector | Replace Python descriptors with C++ reflection metadata and static registration. |
| M2 AssetDatabase/Project | C++ `AssetDatabase`, Python Project panel integration | `MetaCoreIAssetDatabaseService`, Project panel, import pipeline | Keep `.mcmeta`, GUID, and AssetRecord as MetaCore ownership model. |
| M3 Prefab | `engine/_scene_prefab.py`, prefab undo commands | `.mcprefab.json`, `MetaCoreIPrefabService`, `PrefabInstanceMetadata` | Recreate Create/Instantiate/Apply/Revert/Break behavior using MetaCore scene documents. |
| M4 Play Mode | `engine/play_mode.py`, `_play_mode_serialization.py` | `MetaCoreIPlayModeService`, runtime scene clone, C++ lifecycle | Use scene snapshots/clones; do not introduce Python runtime scripts. |
| M5 Scene View/Gizmos | `_scene_view_gizmo.py`, `gizmos/*`, C++ `EditorTools` | ImGuizmo path first, C++ `OnDrawGizmos` later | Keep ImGuizmo initially; only move to Filament-drawn gizmos after workflow gaps are proven. |
| M6 Materials | material assets, previewer, inspector material fields | Filament material assets and MeshRenderer material references | Copy behavior, not Vulkan shader/resource code. |
| M7 Runtime UI | `ui/ui_canvas.py`, `ui/ui_text.py`, `ui/ui_image.py`, `ui/ui_button.py` | RmlUi UI documents and Player rendering | Match Canvas/Text/Image/Button workflow through RmlUi. |
| M8 Build Settings | `engine/ui/build_settings_panel.py`, game builder | Build Settings document, Cook, Player package | Use MetaCore Cook/Package instead of Python app bundling. |
| M9 RuntimeData | Infernux runtime diagnostics patterns as loose reference | Existing `MetaCoreRuntimeData` | RuntimeData integrates after the engine workflow is stable. |
## Implementation Order
1. M1: C++ component reflection and automatic Inspector foundation.
2. M2: AssetDatabase and Project panel production workflow.
3. M3: Prefab workflow.
4. M4: Play Mode isolation and C++ lifecycle.
5. M5-M9: Scene interaction, materials, runtime UI, build pipeline, RuntimeData integration.
## Acceptance Rule
Before implementing each milestone, inspect the matching Infernux module and write down the behavior being mirrored. The MetaCore implementation is accepted only when the same workflow works through MetaCore-native systems.

View File

@ -0,0 +1,104 @@
if(NOT DEFINED METACORE_RUNTIME_CONFIG_TOOL)
message(FATAL_ERROR "METACORE_RUNTIME_CONFIG_TOOL is required")
endif()
if(NOT DEFINED METACORE_RUNTIME_CONFIG_OUTPUT_ROOT)
message(FATAL_ERROR "METACORE_RUNTIME_CONFIG_OUTPUT_ROOT is required")
endif()
function(metacore_expect_exists path description)
if(NOT EXISTS "${path}")
message(FATAL_ERROR "${description} does not exist: ${path}")
endif()
endfunction()
function(metacore_expect_not_exists path description)
if(EXISTS "${path}")
message(FATAL_ERROR "${description} should not exist: ${path}")
endif()
endfunction()
function(metacore_expect_contains path pattern description)
file(READ "${path}" content)
string(FIND "${content}" "${pattern}" pattern_index)
if(pattern_index EQUAL -1)
message(FATAL_ERROR "${description} missing '${pattern}' in ${path}")
endif()
endfunction()
file(REMOVE_RECURSE "${METACORE_RUNTIME_CONFIG_OUTPUT_ROOT}")
set(file_project_root "${METACORE_RUNTIME_CONFIG_OUTPUT_ROOT}/file_replay")
set(file_runtime_dir "${file_project_root}/Runtime")
execute_process(
COMMAND "${METACORE_RUNTIME_CONFIG_TOOL}" "${file_runtime_dir}"
RESULT_VARIABLE file_result
OUTPUT_VARIABLE file_stdout
ERROR_VARIABLE file_stderr
)
if(NOT file_result EQUAL 0)
message(FATAL_ERROR "MetaCoreRuntimeConfigTool file_replay failed: ${file_stderr}")
endif()
string(FIND "${file_stdout}" "mode=file_replay" file_mode_index)
if(file_mode_index EQUAL -1)
message(FATAL_ERROR "MetaCoreRuntimeConfigTool file_replay output did not report mode=file_replay: ${file_stdout}")
endif()
metacore_expect_exists("${file_runtime_dir}/ProjectRuntime.mcruntimecfg" "Runtime project document")
metacore_expect_exists("${file_runtime_dir}/DataSources.mcruntime" "Runtime data sources document")
metacore_expect_exists("${file_runtime_dir}/Bindings.mcruntime" "Runtime bindings document")
metacore_expect_exists("${file_runtime_dir}/RuntimeReplay.mcstream" "Runtime replay stream")
metacore_expect_exists("${file_project_root}/MetaCore.project.json" "Project descriptor")
metacore_expect_exists("${file_project_root}/Scenes/Main.mcscene.json" "Startup scene")
metacore_expect_exists("${file_project_root}/Assets/UI/Hud.mcui.json" "Startup UI")
metacore_expect_contains("${file_project_root}/MetaCore.project.json" "\"runtime_directory\": \"Runtime\"" "Project descriptor")
metacore_expect_contains("${file_project_root}/MetaCore.project.json" "\"startup_scene\": \"Scenes/Main.mcscene.json\"" "Project descriptor")
metacore_expect_contains("${file_runtime_dir}/RuntimeReplay.mcstream" "runtime.status string RuntimeData replay started" "Replay stream")
metacore_expect_contains("${file_project_root}/Assets/UI/Hud.mcui.json" "runtime.status" "Startup UI")
set(custom_project_root "${METACORE_RUNTIME_CONFIG_OUTPUT_ROOT}/custom_runtime")
set(custom_runtime_dir "${custom_project_root}/ConfigRuntime")
execute_process(
COMMAND "${METACORE_RUNTIME_CONFIG_TOOL}" "${custom_runtime_dir}"
RESULT_VARIABLE custom_result
OUTPUT_VARIABLE custom_stdout
ERROR_VARIABLE custom_stderr
)
if(NOT custom_result EQUAL 0)
message(FATAL_ERROR "MetaCoreRuntimeConfigTool custom runtime failed: ${custom_stderr}")
endif()
metacore_expect_exists("${custom_runtime_dir}/ProjectRuntime.mcruntimecfg" "Custom runtime project document")
metacore_expect_exists("${custom_runtime_dir}/DataSources.mcruntime" "Custom runtime data sources document")
metacore_expect_exists("${custom_runtime_dir}/Bindings.mcruntime" "Custom runtime bindings document")
metacore_expect_exists("${custom_runtime_dir}/RuntimeReplay.mcstream" "Custom runtime replay stream")
metacore_expect_exists("${custom_project_root}/MetaCore.project.json" "Custom project descriptor")
metacore_expect_exists("${custom_project_root}/Scenes/Main.mcscene.json" "Custom startup scene")
metacore_expect_contains("${custom_project_root}/MetaCore.project.json" "\"runtime_directory\": \"ConfigRuntime\"" "Custom project descriptor")
metacore_expect_contains("${custom_project_root}/MetaCore.project.json" "\"startup_scene\": \"Scenes/Main.mcscene.json\"" "Custom project descriptor")
set(tcp_project_root "${METACORE_RUNTIME_CONFIG_OUTPUT_ROOT}/tcp")
set(tcp_runtime_dir "${tcp_project_root}/Runtime")
execute_process(
COMMAND "${METACORE_RUNTIME_CONFIG_TOOL}" "${tcp_runtime_dir}" "--tcp"
RESULT_VARIABLE tcp_result
OUTPUT_VARIABLE tcp_stdout
ERROR_VARIABLE tcp_stderr
)
if(NOT tcp_result EQUAL 0)
message(FATAL_ERROR "MetaCoreRuntimeConfigTool tcp failed: ${tcp_stderr}")
endif()
string(FIND "${tcp_stdout}" "mode=tcp" tcp_mode_index)
if(tcp_mode_index EQUAL -1)
message(FATAL_ERROR "MetaCoreRuntimeConfigTool tcp output did not report mode=tcp: ${tcp_stdout}")
endif()
metacore_expect_exists("${tcp_runtime_dir}/ProjectRuntime.mcruntimecfg" "TCP runtime project document")
metacore_expect_exists("${tcp_runtime_dir}/DataSources.mcruntime" "TCP runtime data sources document")
metacore_expect_exists("${tcp_runtime_dir}/Bindings.mcruntime" "TCP runtime bindings document")
metacore_expect_not_exists("${tcp_runtime_dir}/RuntimeReplay.mcstream" "TCP runtime replay stream")
metacore_expect_exists("${tcp_project_root}/MetaCore.project.json" "TCP project descriptor")
metacore_expect_exists("${tcp_project_root}/Scenes/Main.mcscene.json" "TCP startup scene")
metacore_expect_exists("${tcp_project_root}/Assets/UI/Hud.mcui.json" "TCP startup UI")
metacore_expect_contains("${tcp_project_root}/MetaCore.project.json" "\"runtime_directory\": \"Runtime\"" "TCP project descriptor")
metacore_expect_contains("${tcp_project_root}/MetaCore.project.json" "\"startup_scene\": \"Scenes/Main.mcscene.json\"" "TCP project descriptor")
metacore_expect_contains("${tcp_project_root}/Assets/UI/Hud.mcui.json" "runtime.status" "TCP startup UI")

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,159 @@
#include "MetaCoreEditor/MetaCoreBuiltinModules.h"
#include "MetaCoreEditor/MetaCoreEditorServices.h"
#include <cstdlib>
#include <filesystem>
#include <iostream>
#include <memory>
#include <string>
#include <string_view>
namespace {
void MetaCorePrintUsage() {
std::cerr
<< "Usage: MetaCoreBuildPackageTool <project-root> [options]\n"
<< "Options:\n"
<< " --player <path> Path to MetaCorePlayer.exe\n"
<< " --output <directory> Build output base directory\n"
<< " --no-cook Package without cooking first\n"
<< " --no-loose-content Do not copy loose Assets/Scenes\n"
<< " --no-runtime-config Do not copy Runtime configuration\n"
<< " --use-cooked-assets Enable cooked asset loading in packaged runtime config\n";
}
[[nodiscard]] bool MetaCoreSetProjectPathEnvironment(const std::filesystem::path& projectRoot) {
#if defined(_WIN32)
return _putenv_s("METACORE_PROJECT_PATH", projectRoot.string().c_str()) == 0;
#else
return setenv("METACORE_PROJECT_PATH", projectRoot.string().c_str(), 1) == 0;
#endif
}
[[nodiscard]] bool MetaCoreConsumePathOption(
int& index,
int argc,
char* argv[],
std::filesystem::path& outputPath
) {
if (index + 1 >= argc) {
return false;
}
++index;
outputPath = argv[index];
return true;
}
void MetaCorePrintDependencyReport(
const MetaCore::MetaCoreBuildPlayerPackageResult& result,
std::ostream& output
) {
output << "Dependencies: " << result.DependencyReport.size() << '\n';
for (const MetaCore::MetaCoreBuildDependencyReportEntry& entry : result.DependencyReport) {
if (entry.Status == "Cooked" || entry.Status == "SkippedGeneratedSubAsset") {
continue;
}
output << " [" << entry.Status << "] " << entry.AssetGuid.ToString()
<< " reason=" << entry.Reason;
if (entry.ReferencedBy.IsValid()) {
output << " referenced_by=" << entry.ReferencedBy.ToString();
}
if (!entry.AssetType.empty()) {
output << " type=" << entry.AssetType;
}
if (!entry.AssetPath.empty()) {
output << " path=" << entry.AssetPath.generic_string();
}
if (!entry.Message.empty()) {
output << " message=" << entry.Message;
}
output << '\n';
}
}
} // namespace
int main(int argc, char* argv[]) {
if (argc < 2) {
MetaCorePrintUsage();
return 1;
}
if (std::string_view(argv[1]) == "--help" || std::string_view(argv[1]) == "-h") {
MetaCorePrintUsage();
return 0;
}
const std::filesystem::path projectRoot = std::filesystem::absolute(argv[1]).lexically_normal();
if (!std::filesystem::exists(projectRoot / "MetaCore.project.json")) {
std::cerr << "MetaCore project descriptor was not found: "
<< (projectRoot / "MetaCore.project.json").string() << '\n';
return 1;
}
MetaCore::MetaCoreBuildPlayerPackageRequest request;
for (int index = 2; index < argc; ++index) {
const std::string_view argument = argv[index];
if (argument == "--player") {
if (!MetaCoreConsumePathOption(index, argc, argv, request.PlayerExecutablePath)) {
std::cerr << "--player requires a path\n";
return 1;
}
} else if (argument == "--output") {
if (!MetaCoreConsumePathOption(index, argc, argv, request.OutputDirectory)) {
std::cerr << "--output requires a directory\n";
return 1;
}
} else if (argument == "--no-cook") {
request.CookBeforePackage = false;
} else if (argument == "--no-loose-content") {
request.CopyLooseProjectContent = false;
} else if (argument == "--no-runtime-config") {
request.CopyRuntimeConfig = false;
} else if (argument == "--use-cooked-assets") {
request.UseCookedAssetsInPackage = true;
} else if (argument == "--help" || argument == "-h") {
MetaCorePrintUsage();
return 0;
} else {
std::cerr << "Unknown option: " << argument << '\n';
MetaCorePrintUsage();
return 1;
}
}
if (!MetaCoreSetProjectPathEnvironment(projectRoot)) {
std::cerr << "Failed to set METACORE_PROJECT_PATH\n";
return 1;
}
MetaCore::MetaCoreEditorModuleRegistry moduleRegistry;
const std::unique_ptr<MetaCore::MetaCoreIModule> coreServicesModule =
MetaCore::MetaCoreCreateBuiltinCoreServicesModule();
coreServicesModule->Startup(moduleRegistry);
const auto buildService = moduleRegistry.ResolveService<MetaCore::MetaCoreIBuildService>();
if (buildService == nullptr) {
coreServicesModule->Shutdown(moduleRegistry);
moduleRegistry.ShutdownServices();
std::cerr << "BuildService is not available\n";
return 1;
}
const MetaCore::MetaCoreBuildPlayerPackageResult result =
buildService->BuildPlayerPackage(request);
coreServicesModule->Shutdown(moduleRegistry);
moduleRegistry.ShutdownServices();
if (!result.Success) {
std::cerr << "Build package failed: " << result.Error << '\n';
MetaCorePrintDependencyReport(result, std::cerr);
return 1;
}
std::cout << "Build package created: " << result.OutputRoot.string() << '\n'
<< "Copied files: " << result.CopiedFiles.size() << '\n'
<< "Cooked assets: " << result.CookedAssets.size() << '\n';
MetaCorePrintDependencyReport(result, std::cout);
return 0;
}

View File

@ -1,3 +1,4 @@
#include <algorithm>
#include <cctype>
#include <filesystem>
#include <fstream>
@ -8,6 +9,7 @@
#include <stdexcept>
#include <string>
#include <string_view>
#include <unordered_map>
#include <vector>
namespace {
@ -20,12 +22,18 @@ enum class MetaCoreReflectedKind {
struct MetaCoreReflectedField {
std::string Name{};
std::string PropertySpec{};
};
struct MetaCoreReflectedEnumValue {
std::string Name{};
};
struct MetaCoreReflectedType {
MetaCoreReflectedKind Kind = MetaCoreReflectedKind::Struct;
std::string Name{};
std::vector<MetaCoreReflectedField> Fields{};
std::vector<MetaCoreReflectedEnumValue> EnumValues{};
};
[[nodiscard]] std::string MetaCoreTrim(std::string value) {
@ -52,6 +60,210 @@ struct MetaCoreReflectedType {
return full.substr(markerIndex + marker.size());
}
[[nodiscard]] std::string MetaCoreExtractMacroArgument(std::string_view trimmed, std::string_view macroName) {
const std::size_t prefixLength = macroName.size();
if (trimmed.size() <= prefixLength || trimmed[prefixLength] != '(') {
return {};
}
const std::size_t closeIndex = trimmed.rfind(')');
if (closeIndex == std::string_view::npos || closeIndex <= prefixLength) {
return {};
}
return MetaCoreTrim(std::string(trimmed.substr(prefixLength + 1, closeIndex - prefixLength - 1)));
}
[[nodiscard]] std::string MetaCoreEscapeCppString(std::string_view value) {
std::string escaped;
escaped.reserve(value.size());
for (const char character : value) {
switch (character) {
case '\\': escaped += "\\\\"; break;
case '"': escaped += "\\\""; break;
case '\n': escaped += "\\n"; break;
case '\r': escaped += "\\r"; break;
case '\t': escaped += "\\t"; break;
default: escaped.push_back(character); break;
}
}
return escaped;
}
[[nodiscard]] std::string MetaCoreToLower(std::string value) {
std::transform(value.begin(), value.end(), value.begin(), [](unsigned char character) {
return static_cast<char>(std::tolower(character));
});
return value;
}
[[nodiscard]] std::string MetaCoreStripQuotes(std::string value) {
value = MetaCoreTrim(std::move(value));
if (value.size() >= 2 && value.front() == '"' && value.back() == '"') {
return value.substr(1, value.size() - 2);
}
return value;
}
[[nodiscard]] bool MetaCoreIsTruthyMetadataValue(std::string value) {
value = MetaCoreToLower(MetaCoreTrim(std::move(value)));
return value.empty() || value == "true" || value == "1" || value == "yes";
}
[[nodiscard]] std::size_t MetaCoreFindUnquotedEquals(std::string_view value) {
bool inString = false;
bool escaping = false;
for (std::size_t index = 0; index < value.size(); ++index) {
const char character = value[index];
if (escaping) {
escaping = false;
continue;
}
if (character == '\\') {
escaping = inString;
continue;
}
if (character == '"') {
inString = !inString;
continue;
}
if (!inString && character == '=') {
return index;
}
}
return std::string_view::npos;
}
[[nodiscard]] std::vector<std::string> MetaCoreSplitPropertySpec(std::string_view spec) {
std::vector<std::string> tokens;
std::string token;
bool inString = false;
bool escaping = false;
int nestedDepth = 0;
for (const char character : spec) {
if (escaping) {
token.push_back(character);
escaping = false;
continue;
}
if (character == '\\') {
token.push_back(character);
escaping = inString;
continue;
}
if (character == '"') {
token.push_back(character);
inString = !inString;
continue;
}
if (!inString && (character == '(' || character == '[' || character == '{')) {
++nestedDepth;
} else if (!inString && (character == ')' || character == ']' || character == '}')) {
--nestedDepth;
}
if (!inString && nestedDepth == 0 && character == ',') {
tokens.push_back(MetaCoreTrim(token));
token.clear();
continue;
}
token.push_back(character);
}
if (!MetaCoreTrim(token).empty()) {
tokens.push_back(MetaCoreTrim(token));
}
return tokens;
}
[[nodiscard]] std::unordered_map<std::string, std::string> MetaCoreParsePropertySpec(std::string_view spec) {
std::unordered_map<std::string, std::string> metadata;
for (const std::string& token : MetaCoreSplitPropertySpec(spec)) {
const std::size_t equalsIndex = MetaCoreFindUnquotedEquals(token);
if (equalsIndex == std::string_view::npos) {
metadata.emplace(MetaCoreTrim(token), "true");
continue;
}
metadata.insert_or_assign(
MetaCoreTrim(token.substr(0, equalsIndex)),
MetaCoreTrim(token.substr(equalsIndex + 1))
);
}
return metadata;
}
[[nodiscard]] std::optional<MetaCoreReflectedEnumValue> MetaCoreParseEnumValue(std::string value) {
const std::size_t commentIndex = value.find("//");
if (commentIndex != std::string::npos) {
value = value.substr(0, commentIndex);
}
value = MetaCoreTrim(std::move(value));
if (value.empty() || value == "{" || value == "};") {
return std::nullopt;
}
if (!value.empty() && value.back() == ',') {
value.pop_back();
}
const std::size_t equalsIndex = MetaCoreFindUnquotedEquals(value);
if (equalsIndex != std::string::npos) {
value = value.substr(0, equalsIndex);
}
value = MetaCoreTrim(std::move(value));
if (value.empty()) {
return std::nullopt;
}
if (!std::regex_match(value, std::regex(R"([A-Za-z_]\w*)"))) {
return std::nullopt;
}
return MetaCoreReflectedEnumValue{value};
}
void MetaCoreEmitStringMetadataAssignment(
std::ostringstream& output,
const std::unordered_map<std::string, std::string>& metadata,
std::string_view metadataVariable,
std::string_view propertyName,
std::string_view fieldName
) {
const auto iterator = metadata.find(std::string(propertyName));
if (iterator == metadata.end()) {
return;
}
output << " " << metadataVariable << "." << fieldName << " = \""
<< MetaCoreEscapeCppString(MetaCoreStripQuotes(iterator->second)) << "\";\n";
}
void MetaCoreEmitOptionalDoubleMetadataAssignment(
std::ostringstream& output,
const std::unordered_map<std::string, std::string>& metadata,
std::string_view metadataVariable,
std::string_view propertyName,
std::string_view fieldName
) {
const auto iterator = metadata.find(std::string(propertyName));
if (iterator == metadata.end()) {
return;
}
output << " " << metadataVariable << "." << fieldName << " = "
<< MetaCoreStripQuotes(iterator->second) << ";\n";
}
void MetaCoreEmitBoolMetadataAssignment(
std::ostringstream& output,
const std::unordered_map<std::string, std::string>& metadata,
std::string_view metadataVariable,
std::string_view propertyName,
std::string_view fieldName
) {
const auto iterator = metadata.find(std::string(propertyName));
if (iterator == metadata.end()) {
return;
}
output << " " << metadataVariable << "." << fieldName << " = "
<< (MetaCoreIsTruthyMetadataValue(iterator->second) ? "true" : "false") << ";\n";
}
[[nodiscard]] std::vector<MetaCoreReflectedType> MetaCoreParseHeader(const std::filesystem::path& path) {
std::ifstream input(path);
if (!input.is_open()) {
@ -65,6 +277,7 @@ struct MetaCoreReflectedType {
std::optional<MetaCoreReflectedKind> pendingKind;
MetaCoreReflectedType* currentType = nullptr;
bool expectingField = false;
std::string pendingPropertySpec;
std::string line;
while (std::getline(input, line)) {
@ -87,6 +300,7 @@ struct MetaCoreReflectedType {
}
if (trimmed.starts_with("MC_PROPERTY")) {
expectingField = true;
pendingPropertySpec = MetaCoreExtractMacroArgument(trimmed, "MC_PROPERTY");
continue;
}
if (trimmed.starts_with("MC_GENERATED_BODY")) {
@ -99,6 +313,7 @@ struct MetaCoreReflectedType {
reflectedTypes.push_back(MetaCoreReflectedType{
pendingKind.value(),
match[2].str(),
{},
{}
});
currentType = &reflectedTypes.back();
@ -114,6 +329,13 @@ struct MetaCoreReflectedType {
continue;
}
if (currentType != nullptr && currentType->Kind == MetaCoreReflectedKind::Enum) {
if (auto enumValue = MetaCoreParseEnumValue(trimmed); enumValue.has_value()) {
currentType->EnumValues.push_back(std::move(*enumValue));
}
continue;
}
if (currentType == nullptr || currentType->Kind == MetaCoreReflectedKind::Enum || !expectingField) {
continue;
}
@ -124,9 +346,11 @@ struct MetaCoreReflectedType {
}
currentType->Fields.push_back(MetaCoreReflectedField{
match[2].str()
match[2].str(),
pendingPropertySpec
});
expectingField = false;
pendingPropertySpec.clear();
}
return reflectedTypes;
@ -153,13 +377,42 @@ struct MetaCoreReflectedType {
for (const auto& reflectedType : reflectedTypes) {
if (reflectedType.Kind == MetaCoreReflectedKind::Enum) {
output << " MetaCoreRegisterGeneratedEnum<" << reflectedType.Name << ">(registry, \"" << reflectedType.Name << "\");\n";
output << " auto " << reflectedType.Name << "Builder = MetaCoreRegisterGeneratedEnum<"
<< reflectedType.Name << ">(registry, \"" << reflectedType.Name << "\");\n";
for (const auto& enumValue : reflectedType.EnumValues) {
output << " " << reflectedType.Name << "Builder.Value(\""
<< MetaCoreEscapeCppString(enumValue.Name) << "\", "
<< reflectedType.Name << "::" << enumValue.Name << ");\n";
}
continue;
}
output << " auto " << reflectedType.Name << "Builder = MetaCoreRegisterGeneratedStruct<"
<< reflectedType.Name << ">(registry, \"" << reflectedType.Name << "\");\n";
for (const auto& field : reflectedType.Fields) {
if (!field.PropertySpec.empty()) {
const std::string metadataVariable =
reflectedType.Name + field.Name + "EditorMetadata";
const auto metadata = MetaCoreParsePropertySpec(field.PropertySpec);
output << " MetaCoreFieldEditorMetadata " << metadataVariable << "{};\n";
output << " " << metadataVariable << ".RawSpec = \""
<< MetaCoreEscapeCppString(field.PropertySpec) << "\";\n";
MetaCoreEmitStringMetadataAssignment(output, metadata, metadataVariable, "DisplayName", "DisplayName");
MetaCoreEmitStringMetadataAssignment(output, metadata, metadataVariable, "Group", "Group");
MetaCoreEmitStringMetadataAssignment(output, metadata, metadataVariable, "Tooltip", "Tooltip");
MetaCoreEmitStringMetadataAssignment(output, metadata, metadataVariable, "ResourceType", "ResourceType");
MetaCoreEmitStringMetadataAssignment(output, metadata, metadataVariable, "Resource", "ResourceType");
MetaCoreEmitOptionalDoubleMetadataAssignment(output, metadata, metadataVariable, "Min", "Min");
MetaCoreEmitOptionalDoubleMetadataAssignment(output, metadata, metadataVariable, "Max", "Max");
MetaCoreEmitOptionalDoubleMetadataAssignment(output, metadata, metadataVariable, "Step", "Step");
MetaCoreEmitBoolMetadataAssignment(output, metadata, metadataVariable, "ReadOnly", "ReadOnly");
MetaCoreEmitBoolMetadataAssignment(output, metadata, metadataVariable, "Hidden", "Hidden");
MetaCoreEmitBoolMetadataAssignment(output, metadata, metadataVariable, "ResourceReference", "ResourceReference");
output << " " << reflectedType.Name << "Builder.Field<&" << reflectedType.Name << "::"
<< field.Name << ">(\"" << field.Name << "\", " << metadataVariable << ");\n";
continue;
}
output << " " << reflectedType.Name << "Builder.Field<&" << reflectedType.Name << "::"
<< field.Name << ">(\"" << field.Name << "\");\n";
}

View File

@ -1,22 +1,148 @@
#include "MetaCoreFoundation/MetaCoreGeneratedReflection.h"
#include "MetaCoreFoundation/MetaCoreProject.h"
#include "MetaCoreRuntimeData/MetaCoreRuntimeDataProject.h"
#include "MetaCoreScene/MetaCoreScene.h"
#include "MetaCoreScene/MetaCoreScenePackage.h"
#include "MetaCoreScene/MetaCoreSceneSerializer.h"
#include <algorithm>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <string>
namespace {
[[nodiscard]] MetaCore::MetaCoreSceneDocument MetaCoreBuildPilotSceneDocument() {
MetaCore::MetaCoreSceneDocument document;
document.Name = "Main";
const MetaCore::MetaCoreScene scene = MetaCore::MetaCoreCreateDefaultScene();
MetaCore::MetaCoreScene scene;
MetaCore::MetaCoreGameObject mainCamera = scene.CreateGameObjectWithId(1, "Main Camera");
mainCamera.AddComponent<MetaCore::MetaCoreCameraComponent>().IsPrimary = true;
mainCamera.GetComponent<MetaCore::MetaCoreTransformComponent>().Position = glm::vec3(0.0F, 2.4F, 7.0F);
mainCamera.GetComponent<MetaCore::MetaCoreTransformComponent>().RotationEulerDegrees = glm::vec3(-16.0F, 0.0F, 0.0F);
MetaCore::MetaCoreGameObject keyLight = scene.CreateGameObjectWithId(2, "Directional Light");
keyLight.AddComponent<MetaCore::MetaCoreLightComponent>();
keyLight.GetComponent<MetaCore::MetaCoreTransformComponent>().RotationEulerDegrees = glm::vec3(-45.0F, 30.0F, 0.0F);
MetaCore::MetaCoreGameObject cube = scene.CreateGameObjectWithId(3, "Runtime Cube");
cube.AddComponent<MetaCore::MetaCoreMeshRendererComponent>().BaseColor = glm::vec3(0.4F, 0.6F, 0.9F);
cube.GetComponent<MetaCore::MetaCoreTransformComponent>().Position = glm::vec3(0.0F, 0.5F, 0.0F);
MetaCore::MetaCoreGameObject valve = scene.CreateGameObjectWithId(4, "Runtime Valve");
valve.AddComponent<MetaCore::MetaCoreMeshRendererComponent>().BaseColor = glm::vec3(0.9F, 0.55F, 0.25F);
valve.GetComponent<MetaCore::MetaCoreTransformComponent>().Position = glm::vec3(-2.0F, 0.5F, 0.0F);
valve.GetComponent<MetaCore::MetaCoreTransformComponent>().Scale = glm::vec3(0.65F, 0.65F, 0.65F);
MetaCore::MetaCoreGameObject tank = scene.CreateGameObjectWithId(5, "Runtime Tank");
tank.AddComponent<MetaCore::MetaCoreMeshRendererComponent>().BaseColor = glm::vec3(0.35F, 0.65F, 0.90F);
tank.GetComponent<MetaCore::MetaCoreTransformComponent>().Position = glm::vec3(2.0F, 0.5F, 0.0F);
tank.GetComponent<MetaCore::MetaCoreTransformComponent>().Scale = glm::vec3(1.25F, 1.25F, 1.25F);
MetaCore::MetaCoreGameObject alarm = scene.CreateGameObjectWithId(6, "Runtime Alarm Light");
alarm.AddComponent<MetaCore::MetaCoreLightComponent>().Intensity = 0.0F;
alarm.GetComponent<MetaCore::MetaCoreTransformComponent>().Position = glm::vec3(0.0F, 3.0F, 0.0F);
MetaCore::MetaCoreIdGenerator::EnsureAbove(6);
document.GameObjects = scene.CaptureSnapshot().GameObjects;
return document;
}
[[nodiscard]] MetaCore::MetaCoreUiDocument MetaCoreBuildPilotUiDocument() {
MetaCore::MetaCoreUiDocument document;
document.Name = "RuntimeDataHud";
document.ReferenceWidth = 1280;
document.ReferenceHeight = 720;
document.RootNodeIds = {"runtime.root"};
MetaCore::MetaCoreUiNodeDocument root;
root.Id = "runtime.root";
root.Name = "Runtime Root";
root.Type = MetaCore::MetaCoreUiNodeType::Panel;
root.Children = {"runtime.status"};
root.RectTransform.AnchorMin = glm::vec3(0.0F, 0.0F, 0.0F);
root.RectTransform.AnchorMax = glm::vec3(1.0F, 1.0F, 0.0F);
root.RectTransform.Size = glm::vec3(0.0F, 0.0F, 0.0F);
root.Style.BackgroundColor = glm::vec3(0.0F, 0.0F, 0.0F);
MetaCore::MetaCoreUiNodeDocument status;
status.Id = "runtime.status";
status.Name = "Runtime Status";
status.Type = MetaCore::MetaCoreUiNodeType::Text;
status.ParentId = "runtime.root";
status.Text = "RuntimeData waiting";
status.RectTransform.Position = glm::vec3(24.0F, 24.0F, 0.0F);
status.RectTransform.Size = glm::vec3(560.0F, 40.0F, 0.0F);
status.Style.FontSize = 18.0F;
status.Style.TextColor = glm::vec3(0.88F, 0.94F, 1.0F);
document.Nodes = {root, status};
return document;
}
[[nodiscard]] const MetaCore::MetaCoreGameObjectData* MetaCoreFindSceneObject(
const MetaCore::MetaCoreSceneDocument& sceneDocument,
MetaCore::MetaCoreId objectId
) {
const auto iterator = std::find_if(
sceneDocument.GameObjects.begin(),
sceneDocument.GameObjects.end(),
[objectId](const MetaCore::MetaCoreGameObjectData& objectData) {
return objectData.Id == objectId;
}
);
return iterator == sceneDocument.GameObjects.end() ? nullptr : &(*iterator);
}
[[nodiscard]] bool MetaCoreValidatePilotRuntimeDocuments(
const MetaCore::MetaCoreSceneDocument& sceneDocument,
const MetaCore::MetaCoreUiDocument& uiDocument,
const MetaCore::MetaCoreRuntimeBindingsDocument& bindings,
std::string& error
) {
for (const MetaCore::MetaCoreSceneBindingDefinition& binding : bindings.Bindings) {
const MetaCore::MetaCoreGameObjectData* targetObject =
MetaCoreFindSceneObject(sceneDocument, binding.TargetObjectId);
if (targetObject == nullptr) {
error = "Scene binding target object does not exist: " + binding.BindingId;
return false;
}
const bool targetHasRequiredComponent =
binding.Target == MetaCore::MetaCoreRuntimeBindingTarget::TransformPosition ||
((binding.Target == MetaCore::MetaCoreRuntimeBindingTarget::MeshRendererVisible ||
binding.Target == MetaCore::MetaCoreRuntimeBindingTarget::MeshRendererBaseColor) &&
targetObject->MeshRenderer.has_value()) ||
((binding.Target == MetaCore::MetaCoreRuntimeBindingTarget::LightIntensity ||
binding.Target == MetaCore::MetaCoreRuntimeBindingTarget::LightColor) &&
targetObject->Light.has_value());
if (!targetHasRequiredComponent) {
error = "Scene binding target object is missing the required component: " + binding.BindingId;
return false;
}
}
for (const MetaCore::MetaCoreUiBindingDefinition& binding : bindings.UiBindings) {
if (binding.Target != MetaCore::MetaCoreRuntimeUiBindingTarget::Text) {
continue;
}
const auto nodeIterator = std::find_if(
uiDocument.Nodes.begin(),
uiDocument.Nodes.end(),
[&](const MetaCore::MetaCoreUiNodeDocument& node) {
return node.Id == binding.TargetNodeId;
}
);
if (nodeIterator == uiDocument.Nodes.end() || nodeIterator->Type != MetaCore::MetaCoreUiNodeType::Text) {
error = "UI binding target Text node does not exist: " + binding.BindingId;
return false;
}
}
return true;
}
} // namespace
int main(int argc, char* argv[]) {
@ -28,8 +154,17 @@ int main(int argc, char* argv[]) {
const bool generateTcpConfig = argc >= 3 && std::string_view(argv[2]) == "--tcp";
MetaCore::MetaCoreTypeRegistry registry;
MetaCore::MetaCoreRegisterFoundationGeneratedTypes(registry);
MetaCore::MetaCoreRegisterSceneGeneratedTypes(registry);
MetaCore::MetaCoreRegisterRuntimeDataGeneratedTypes(registry);
const std::filesystem::path runtimeDirectory = std::filesystem::absolute(argv[1]).lexically_normal();
const std::filesystem::path projectRoot = runtimeDirectory.parent_path();
const std::filesystem::path runtimeDirectoryRelative =
MetaCore::MetaCoreBuildRuntimeDirectoryRelativePath(projectRoot, runtimeDirectory);
const std::filesystem::path sceneRelativePath = std::filesystem::path("Scenes") / "Main.mcscene.json";
const std::filesystem::path uiRelativePath = std::filesystem::path("Assets") / "UI" / "Hud.mcui.json";
MetaCore::MetaCoreRuntimeDataSourcesDocument sources;
sources.Sources.push_back(MetaCore::MetaCoreDataSourceDefinition{
generateTcpConfig ? "tcp-source" : "replay-source",
@ -42,7 +177,7 @@ int main(int argc, char* argv[]) {
}
: std::vector<MetaCore::MetaCoreDataSourceSetting>{
// 动态计算相对于项目根目录的播放流文件路径,替代原先硬编码的 TestProject
MetaCore::MetaCoreDataSourceSetting{"file_path", (std::filesystem::path(argv[1]).filename() / "RuntimeReplay.mcstream").generic_string()}
MetaCore::MetaCoreDataSourceSetting{"file_path", (runtimeDirectoryRelative / "RuntimeReplay.mcstream").generic_string()}
},
true,
1000
@ -83,6 +218,12 @@ int main(int argc, char* argv[]) {
"alarm.intensity",
MetaCore::MetaCoreRuntimeValueType::Double
});
sources.DataPoints.push_back(MetaCore::MetaCoreDataPointDefinition{
"runtime.status",
generateTcpConfig ? "tcp-source" : "replay-source",
"runtime.status",
MetaCore::MetaCoreRuntimeValueType::String
});
MetaCore::MetaCoreRuntimeBindingsDocument bindings;
bindings.Bindings.push_back(MetaCore::MetaCoreSceneBindingDefinition{
@ -127,10 +268,24 @@ int main(int argc, char* argv[]) {
MetaCore::MetaCoreRuntimeBindingTarget::LightIntensity,
MetaCore::MetaCoreRuntimeMissingDataPolicy::KeepLastValue
});
bindings.UiBindings.push_back(MetaCore::MetaCoreUiBindingDefinition{
"binding.runtime.status",
"runtime.status",
"runtime.status",
MetaCore::MetaCoreRuntimeUiBindingTarget::Text,
MetaCore::MetaCoreRuntimeMissingDataPolicy::KeepLastValue
});
const std::filesystem::path runtimeDirectory = argv[1];
const std::filesystem::path projectRoot = runtimeDirectory.parent_path();
const std::filesystem::path scenePath = projectRoot / "Scenes" / "Main.mcscene";
const MetaCore::MetaCoreSceneDocument sceneDocument = MetaCoreBuildPilotSceneDocument();
const MetaCore::MetaCoreUiDocument uiDocument = MetaCoreBuildPilotUiDocument();
std::string validationError;
if (!MetaCoreValidatePilotRuntimeDocuments(sceneDocument, uiDocument, bindings, validationError)) {
std::cerr << "Generated runtime config is invalid: " << validationError << '\n';
return 1;
}
const std::filesystem::path scenePath = projectRoot / sceneRelativePath;
const std::filesystem::path uiPath = projectRoot / uiRelativePath;
std::filesystem::create_directories(runtimeDirectory);
if (!MetaCore::MetaCoreWriteRuntimeDataSourcesDocument(runtimeDirectory / "DataSources.mcruntime", sources, registry)) {
std::cerr << "Failed to write DataSources.mcruntime\n";
@ -140,18 +295,35 @@ int main(int argc, char* argv[]) {
std::cerr << "Failed to write Bindings.mcruntime\n";
return 1;
}
MetaCore::MetaCoreRuntimeProjectDocument runtimeProjectDocument;
runtimeProjectDocument.StartupScenePath = std::filesystem::path("Scenes") / "Main.mcscene";
runtimeProjectDocument.DataSourcesPath = std::filesystem::path("Runtime") / "DataSources.mcruntime";
runtimeProjectDocument.BindingsPath = std::filesystem::path("Runtime") / "Bindings.mcruntime";
runtimeProjectDocument.DiagnosticsPath = std::filesystem::path("Runtime") / "Diagnostics.mcruntimestate";
MetaCore::MetaCoreRuntimeProjectDocument runtimeProjectDocument =
MetaCore::MetaCoreBuildDefaultRuntimeProjectDocument(runtimeDirectoryRelative);
runtimeProjectDocument.StartupScenePath = sceneRelativePath;
runtimeProjectDocument.StartupUiPath = uiRelativePath;
if (!MetaCore::MetaCoreWriteRuntimeProjectDocument(runtimeDirectory / "ProjectRuntime.mcruntimecfg", runtimeProjectDocument, registry)) {
std::cerr << "Failed to write ProjectRuntime.mcruntimecfg\n";
return 1;
}
MetaCore::MetaCoreProjectFileDocument projectDocument;
projectDocument.Name = generateTcpConfig ? "MetaCoreTcpRuntimeDataPilot" : "MetaCoreRuntimeDataPilot";
projectDocument.RuntimeDirectory = runtimeDirectoryRelative;
projectDocument.UiDirectory = std::filesystem::path("Assets") / "UI";
projectDocument.BuildDirectory = std::filesystem::path("Build");
projectDocument.StartupScenePath = sceneRelativePath;
projectDocument.ScenePaths = {sceneRelativePath};
if (!MetaCore::MetaCoreWriteProjectFile(MetaCore::MetaCoreGetProjectFilePath(projectRoot), projectDocument)) {
std::cerr << "Failed to write MetaCore.project.json\n";
return 1;
}
std::filesystem::create_directories(scenePath.parent_path());
if (!MetaCore::MetaCoreWriteScenePackage(scenePath, MetaCoreBuildPilotSceneDocument())) {
std::cerr << "Failed to write Main.mcscene\n";
if (!MetaCore::MetaCoreSceneSerializer::SaveSceneToJson(scenePath, sceneDocument, registry)) {
std::cerr << "Failed to write Main.mcscene.json\n";
return 1;
}
std::filesystem::create_directories(uiPath.parent_path());
if (!MetaCore::MetaCoreSceneSerializer::SaveUiToJson(uiPath, uiDocument, registry)) {
std::cerr << "Failed to write Hud.mcui.json\n";
return 1;
}
@ -169,20 +341,25 @@ int main(int argc, char* argv[]) {
<< "0.00 valve.visible bool true\n"
<< "0.00 tank.base_color vec3 0.35 0.65 0.90\n"
<< "0.00 alarm.intensity double 0.0\n"
<< "0.00 runtime.status string RuntimeData replay started\n"
<< "0.50 cube.position vec3 1.5 0.5 0.0\n"
<< "0.50 cube.base_color vec3 0.8 0.6 0.5\n"
<< "0.50 tank.base_color vec3 0.20 0.80 0.25\n"
<< "0.50 alarm.intensity double 2.0\n"
<< "0.50 runtime.status string Replay frame 0.50s active\n"
<< "1.00 cube.visible bool false\n"
<< "1.00 valve.visible bool false\n"
<< "1.00 runtime.status string Replay toggled visibility\n"
<< "1.50 cube.position vec3 -1.5 0.5 0.0\n"
<< "1.50 cube.visible bool true\n"
<< "1.50 valve.visible bool true\n"
<< "1.50 alarm.intensity double 0.0\n";
<< "1.50 alarm.intensity double 0.0\n"
<< "1.50 runtime.status string Replay restored scene\n";
}
std::cout << "Runtime config generated at " << runtimeDirectory.string()
<< " scene=" << scenePath.string()
<< " ui=" << uiPath.string()
<< " mode=" << (generateTcpConfig ? "tcp" : "file_replay") << '\n';
return 0;
}

View File

@ -104,7 +104,9 @@ int main(int argc, char* argv[]) {
"cube.base_color vec3 " + std::to_string(r) + " " + std::to_string(g) + " " + std::to_string(b) + "\n" +
"valve.visible bool " + std::string(valveVisible ? "true" : "false") + "\n" +
"tank.base_color vec3 " + std::to_string(tankR) + " " + std::to_string(tankG) + " " + std::to_string(tankB) + "\n" +
"alarm.intensity double " + std::to_string(alarmIntensity) + "\n";
"alarm.intensity double " + std::to_string(alarmIntensity) + "\n" +
"runtime.status string TCP frame " + std::to_string(frame) +
(valveVisible ? " valve online\n" : " valve hidden\n");
const int sent = send(clientSocket, payload.c_str(), static_cast<int>(payload.size()), 0);
if (sent == SOCKET_ERROR) {

View File

@ -12,6 +12,18 @@
"win32-binding",
"opengl3-binding"
]
},
{
"name": "rmlui",
"default-features": false
},
{
"name": "qtbase",
"default-features": false,
"features": [
"gui",
"widgets"
]
}
]
}