feat(editor): align MetaCore workflows with Infernux
This commit is contained in:
parent
93fddb0296
commit
695fc30d34
1075
Apps/MetaCoreLauncher/main.cpp
Normal file
1075
Apps/MetaCoreLauncher/main.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@ -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();
|
||||
|
||||
@ -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
|
||||
|
||||
126
SandboxProject/Assets/UI/Hud.mcui.json
Normal file
126
SandboxProject/Assets/UI/Hud.mcui.json
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
||||
8
SandboxProject/Assets/UI/Hud.mcui.json.mcmeta
Normal file
8
SandboxProject/Assets/UI/Hud.mcui.json.mcmeta
Normal 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
File diff suppressed because it is too large
Load Diff
@ -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);
|
||||
}
|
||||
|
||||
@ -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";
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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;
|
||||
};
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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]) {
|
||||
|
||||
788
Source/MetaCoreRender/Private/MetaCoreRuntimeUiRenderer.cpp
Normal file
788
Source/MetaCoreRender/Private/MetaCoreRuntimeUiRenderer.cpp
Normal 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 += "&"; break;
|
||||
case '<': escaped += "<"; break;
|
||||
case '>': escaped += ">"; break;
|
||||
case '"': escaped += """; break;
|
||||
case '\'': escaped += "'"; 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
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
@ -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");
|
||||
|
||||
@ -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"});
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
279
Source/MetaCoreScene/Private/MetaCoreUiRmlCompiler.cpp
Normal file
279
Source/MetaCoreScene/Private/MetaCoreUiRmlCompiler.cpp
Normal 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 += "&"; break;
|
||||
case '<': escaped += "<"; break;
|
||||
case '>': escaped += ">"; break;
|
||||
case '"': escaped += """; break;
|
||||
case '\'': escaped += "'"; 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
|
||||
@ -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};
|
||||
};
|
||||
|
||||
|
||||
@ -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
|
||||
126
TestProject/Assets/UI/Hud.mcui.json
Normal file
126
TestProject/Assets/UI/Hud.mcui.json
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
||||
69
docs/designs/metacore-infernux-reference-roadmap.md
Normal file
69
docs/designs/metacore-infernux-reference-roadmap.md
Normal 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.
|
||||
104
tests/MetaCoreRuntimeConfigToolSmoke.cmake
Normal file
104
tests/MetaCoreRuntimeConfigToolSmoke.cmake
Normal 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
159
tools/MetaCoreBuildPackageTool/main.cpp
Normal file
159
tools/MetaCoreBuildPackageTool/main.cpp
Normal 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;
|
||||
}
|
||||
@ -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";
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
12
vcpkg.json
12
vcpkg.json
@ -12,6 +12,18 @@
|
||||
"win32-binding",
|
||||
"opengl3-binding"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "rmlui",
|
||||
"default-features": false
|
||||
},
|
||||
{
|
||||
"name": "qtbase",
|
||||
"default-features": false,
|
||||
"features": [
|
||||
"gui",
|
||||
"widgets"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user