diff --git a/Source/MetaCoreEditor/Private/MetaCoreBuiltinCoreServicesModule.cpp b/Source/MetaCoreEditor/Private/MetaCoreBuiltinCoreServicesModule.cpp index aed1c74..858fa53 100644 --- a/Source/MetaCoreEditor/Private/MetaCoreBuiltinCoreServicesModule.cpp +++ b/Source/MetaCoreEditor/Private/MetaCoreBuiltinCoreServicesModule.cpp @@ -2588,6 +2588,93 @@ bool MetaCoreDeserializeComponentValue( return MetaCoreDeserializeFromBytes(payload, component, registry); } +std::optional> MetaCoreSerializeReflectedComponentValue( + const MetaCoreStructDescriptor& descriptor, + const void* component, + const MetaCoreTypeRegistry& registry +) { + if (component == nullptr) { + return std::nullopt; + } + + MetaCoreArchiveWriter writer; + writer.WriteBytes(&descriptor.Version, sizeof(descriptor.Version)); + const std::uint32_t fieldCount = static_cast(descriptor.Fields.size()); + writer.WriteBytes(&fieldCount, sizeof(fieldCount)); + + for (const MetaCoreFieldDescriptor& field : descriptor.Fields) { + if (!MetaCoreSerializeValue(writer, field.Name, registry)) { + return std::nullopt; + } + + MetaCoreArchiveWriter fieldWriter; + if (!field.Serialize || !field.Serialize(component, fieldWriter, registry)) { + return std::nullopt; + } + + const std::uint64_t fieldSize = static_cast(fieldWriter.GetSize()); + writer.WriteBytes(&fieldSize, sizeof(fieldSize)); + writer.WriteSpan(fieldWriter.GetBuffer()); + } + + return writer.GetBuffer(); +} + +bool MetaCoreDeserializeReflectedComponentValue( + const MetaCoreStructDescriptor& descriptor, + std::span payload, + void* component, + const MetaCoreTypeRegistry& registry +) { + if (component == nullptr) { + return false; + } + + MetaCoreArchiveReader reader(payload); + std::uint32_t serializedVersion = 0; + std::uint32_t fieldCount = 0; + if (!reader.ReadBytes(&serializedVersion, sizeof(serializedVersion)) || + !reader.ReadBytes(&fieldCount, sizeof(fieldCount))) { + return false; + } + + (void)serializedVersion; + + for (std::uint32_t index = 0; index < fieldCount; ++index) { + std::string fieldName; + std::uint64_t fieldSize = 0; + if (!MetaCoreDeserializeValue(reader, fieldName, registry) || + !reader.ReadBytes(&fieldSize, sizeof(fieldSize))) { + return false; + } + + const auto fieldBytes = reader.ReadSpan(static_cast(fieldSize)); + if (!fieldBytes.has_value()) { + return false; + } + + const auto fieldIterator = std::find_if( + descriptor.Fields.begin(), + descriptor.Fields.end(), + [&](const MetaCoreFieldDescriptor& field) { + return field.Name == fieldName; + } + ); + if (fieldIterator == descriptor.Fields.end()) { + continue; + } + + MetaCoreArchiveReader fieldReader(*fieldBytes); + if (!fieldIterator->Deserialize || + !fieldIterator->Deserialize(component, fieldReader, registry) || + !fieldReader.IsEof()) { + return false; + } + } + + return reader.IsEof(); +} + [[nodiscard]] std::string MetaCorePathToPortableString(const std::filesystem::path& path) { return path.lexically_normal().generic_string(); } @@ -5823,7 +5910,7 @@ public: : sceneDocument->Name; SavedSnapshotBytes_ = MetaCoreSerializeSceneSnapshotBytes( ReflectionRegistry_->GetTypeRegistry(), - editorContext.GetScene().CaptureSnapshot() + editorContext.CaptureStateSnapshot().SceneSnapshot ).value_or(std::vector{}); SetDirtyState(false); @@ -5871,9 +5958,11 @@ public: sceneGuid = MetaCoreAssetGuid::Generate(); } + const MetaCoreEditorStateSnapshot savedState = editorContext.CaptureStateSnapshot(); + MetaCoreSceneDocument sceneDocument; sceneDocument.Name = CurrentSceneName_.empty() ? MetaCoreSceneDisplayNameFromPath(normalizedScenePath) : CurrentSceneName_; - sceneDocument.GameObjects = editorContext.GetScene().CaptureSnapshot().GameObjects; + sceneDocument.GameObjects = savedState.SceneSnapshot.GameObjects; sceneDocument.Selection.SelectedObjectIds = editorContext.GetSelectedObjectIds(); sceneDocument.Selection.ActiveObjectId = editorContext.GetActiveObjectId(); sceneDocument.Selection.SelectionAnchorId = editorContext.GetSelectionAnchorId(); @@ -5901,7 +5990,7 @@ public: CurrentSceneName_ = sceneDocument.Name; SavedSnapshotBytes_ = MetaCoreSerializeSceneSnapshotBytes( ReflectionRegistry_->GetTypeRegistry(), - editorContext.GetScene().CaptureSnapshot() + savedState.SceneSnapshot ).value_or(std::vector{}); SetDirtyState(false); @@ -6167,7 +6256,7 @@ public: if (!gameObject.HasComponent()) { return false; } - gameObject.AddComponent(MetaCoreCameraComponent{}); + gameObject.GetComponent() = MetaCoreCameraComponent{}; return true; }, [](const MetaCoreGameObject& gameObject, const MetaCoreTypeRegistry& registry) -> std::optional> { @@ -6217,7 +6306,7 @@ public: if (!gameObject.HasComponent()) { return false; } - gameObject.AddComponent(MetaCoreLightComponent{}); + gameObject.GetComponent() = MetaCoreLightComponent{}; return true; }, [](const MetaCoreGameObject& gameObject, const MetaCoreTypeRegistry& registry) -> std::optional> { @@ -6267,7 +6356,7 @@ public: if (!gameObject.HasComponent()) { return false; } - gameObject.AddComponent(MetaCoreMeshRendererComponent{}); + gameObject.GetComponent() = MetaCoreMeshRendererComponent{}; return true; }, [](const MetaCoreGameObject& gameObject, const MetaCoreTypeRegistry& registry) -> std::optional> { @@ -6318,6 +6407,51 @@ public: if (descriptor.Category.empty()) { descriptor.Category = "Scripts"; } + if (!descriptor.CopyComponentPayload && + descriptor.ReflectedType != nullptr && + descriptor.ConstComponent && + descriptor.HasComponent) { + const MetaCoreStructDescriptor* reflectedType = descriptor.ReflectedType; + auto constComponent = descriptor.ConstComponent; + auto hasComponent = descriptor.HasComponent; + descriptor.CopyComponentPayload = + [reflectedType, constComponent, hasComponent]( + const MetaCoreGameObject& gameObject, + const MetaCoreTypeRegistry& registry + ) -> std::optional> { + if (!hasComponent(gameObject)) { + return std::nullopt; + } + return MetaCoreSerializeReflectedComponentValue(*reflectedType, constComponent(gameObject), registry); + }; + } + if (!descriptor.PasteComponentPayload && + descriptor.ReflectedType != nullptr && + descriptor.MutableComponent && + descriptor.HasComponent) { + const MetaCoreStructDescriptor* reflectedType = descriptor.ReflectedType; + auto mutableComponent = descriptor.MutableComponent; + auto hasComponent = descriptor.HasComponent; + auto addComponent = descriptor.AddComponent; + descriptor.PasteComponentPayload = + [reflectedType, mutableComponent, hasComponent, addComponent]( + MetaCoreGameObject& gameObject, + std::span payload, + const MetaCoreTypeRegistry& registry + ) { + if (!hasComponent(gameObject)) { + if (!addComponent || !addComponent(gameObject)) { + return false; + } + } + return MetaCoreDeserializeReflectedComponentValue( + *reflectedType, + payload, + mutableComponent(gameObject), + registry + ); + }; + } Descriptors_.push_back(std::move(descriptor)); return true; @@ -6365,6 +6499,30 @@ public: ); } + void DrawComponentGizmos(MetaCoreEditorContext& editorContext) override { + const std::vector orderedObjectIds = editorContext.GetScene().BuildHierarchyPreorder(); + for (const MetaCoreComponentDescriptor& descriptor : Descriptors_) { + if (!descriptor.HasComponent || + (!descriptor.Lifecycle.OnDrawGizmos && !descriptor.Lifecycle.OnDrawGizmosSelected)) { + continue; + } + + for (MetaCoreId objectId : orderedObjectIds) { + MetaCoreGameObject gameObject = editorContext.GetScene().FindGameObject(objectId); + if (!gameObject || !descriptor.HasComponent(gameObject)) { + continue; + } + + if (descriptor.Lifecycle.OnDrawGizmos) { + descriptor.Lifecycle.OnDrawGizmos(editorContext, gameObject); + } + if (descriptor.Lifecycle.OnDrawGizmosSelected && editorContext.IsObjectSelected(objectId)) { + descriptor.Lifecycle.OnDrawGizmosSelected(editorContext, gameObject); + } + } + } + } + private: std::vector Descriptors_{}; std::shared_ptr ReflectionRegistry_{}; diff --git a/Source/MetaCoreEditor/Private/MetaCoreBuiltinEditorModule.cpp b/Source/MetaCoreEditor/Private/MetaCoreBuiltinEditorModule.cpp index 8212abf..b3808a9 100644 --- a/Source/MetaCoreEditor/Private/MetaCoreBuiltinEditorModule.cpp +++ b/Source/MetaCoreEditor/Private/MetaCoreBuiltinEditorModule.cpp @@ -6059,7 +6059,7 @@ public: ImGui::Spacing(); ImGui::Separator(); - if (ImGui::Button("添加组件", ImVec2(-1, 26))) { + if (ImGui::Button("Add Component", ImVec2(-1, 26))) { ImGui::OpenPopup("AddComponentPopup"); } diff --git a/Source/MetaCoreEditor/Private/MetaCoreEditorApp.cpp b/Source/MetaCoreEditor/Private/MetaCoreEditorApp.cpp index 441ef13..8642cf8 100644 --- a/Source/MetaCoreEditor/Private/MetaCoreEditorApp.cpp +++ b/Source/MetaCoreEditor/Private/MetaCoreEditorApp.cpp @@ -1225,6 +1225,11 @@ void MetaCoreEditorApp::DrawSceneViewWindow() { ImGui::EndDragDropTarget(); } + if (const auto componentRegistry = ModuleRegistry_.ResolveService(); + componentRegistry != nullptr) { + componentRegistry->DrawComponentGizmos(*EditorContext_); + } + bool gizmoHovering = false; if (GMetaCoreEnableImGuizmo) { SceneInteractionService_.HandleGizmoManipulation(*EditorContext_); diff --git a/Source/MetaCoreEditor/Private/MetaCoreEditorContext.cpp b/Source/MetaCoreEditor/Private/MetaCoreEditorContext.cpp index a42fd50..832a1cb 100644 --- a/Source/MetaCoreEditor/Private/MetaCoreEditorContext.cpp +++ b/Source/MetaCoreEditor/Private/MetaCoreEditorContext.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include namespace MetaCore { @@ -124,9 +125,26 @@ private: } } + if (lhs.SerializedComponents.size() != rhs.SerializedComponents.size()) { + return false; + } + for (std::size_t index = 0; index < lhs.SerializedComponents.size(); ++index) { + if (lhs.SerializedComponents[index].TypeId != rhs.SerializedComponents[index].TypeId || + lhs.SerializedComponents[index].Payload != rhs.SerializedComponents[index].Payload) { + return false; + } + } + return true; } +[[nodiscard]] bool MetaCoreIsNativeSceneComponentType(std::string_view typeId) { + return typeId == "Transform" || + typeId == "Camera" || + typeId == "Light" || + typeId == "MeshRenderer"; +} + [[nodiscard]] bool MetaCoreStateSnapshotEqual(const MetaCoreEditorStateSnapshot& lhs, const MetaCoreEditorStateSnapshot& rhs) { if (lhs.SceneSnapshot.GameObjects.size() != rhs.SceneSnapshot.GameObjects.size()) { return false; @@ -616,6 +634,7 @@ bool MetaCoreEditorContext::RedoCommand() { MetaCoreEditorStateSnapshot MetaCoreEditorContext::CaptureStateSnapshot() const { MetaCoreEditorStateSnapshot snapshot; snapshot.SceneSnapshot = Scene_.CaptureSnapshot(); + CaptureSerializedComponents(snapshot.SceneSnapshot); snapshot.SelectionSnapshot.SelectedObjectIds = SelectedObjectIds_; snapshot.SelectionSnapshot.ActiveObjectId = ActiveObjectId_; snapshot.SelectionSnapshot.SelectionAnchorId = SelectionAnchorId_; @@ -624,6 +643,7 @@ MetaCoreEditorStateSnapshot MetaCoreEditorContext::CaptureStateSnapshot() const void MetaCoreEditorContext::RestoreStateSnapshot(const MetaCoreEditorStateSnapshot& snapshot) { Scene_.RestoreSnapshot(snapshot.SceneSnapshot); + RestoreSerializedComponents(snapshot.SceneSnapshot); SetSelection(snapshot.SelectionSnapshot.SelectedObjectIds, snapshot.SelectionSnapshot.ActiveObjectId); if (snapshot.SelectionSnapshot.SelectionAnchorId != 0 && (bool)Scene_.FindGameObject(snapshot.SelectionSnapshot.SelectionAnchorId)) { @@ -964,6 +984,97 @@ void MetaCoreEditorContext::ClearSelectedAssetSubId() { void MetaCoreEditorContext::SetDockLayoutBuilt(bool built) { DockLayoutBuilt_ = built; } bool MetaCoreEditorContext::HasDockLayoutBuilt() const { return DockLayoutBuilt_; } +void MetaCoreEditorContext::CaptureSerializedComponents(MetaCoreSceneSnapshot& snapshot) const { + const auto componentRegistry = ModuleRegistry_.ResolveService(); + const auto reflectionRegistry = ModuleRegistry_.ResolveService(); + if (componentRegistry == nullptr || reflectionRegistry == nullptr) { + return; + } + + const MetaCoreTypeRegistry& typeRegistry = reflectionRegistry->GetTypeRegistry(); + for (MetaCoreGameObjectData& objectData : snapshot.GameObjects) { + objectData.SerializedComponents.clear(); + const MetaCoreGameObject gameObject = Scene_.FindGameObject(objectData.Id); + if (!gameObject) { + continue; + } + + for (const MetaCoreIComponentTypeRegistry::MetaCoreComponentDescriptor& descriptor : componentRegistry->GetComponentDescriptors()) { + if (MetaCoreIsNativeSceneComponentType(descriptor.TypeId) || + !descriptor.HasComponent || + !descriptor.CopyComponentPayload || + !descriptor.HasComponent(gameObject)) { + continue; + } + + const auto payload = descriptor.CopyComponentPayload(gameObject, typeRegistry); + if (!payload.has_value() || payload->empty()) { + continue; + } + + objectData.SerializedComponents.push_back(MetaCoreSerializedComponentData{ + descriptor.TypeId, + *payload + }); + } + + std::sort( + objectData.SerializedComponents.begin(), + objectData.SerializedComponents.end(), + [](const MetaCoreSerializedComponentData& lhs, const MetaCoreSerializedComponentData& rhs) { + return lhs.TypeId < rhs.TypeId; + } + ); + } +} + +void MetaCoreEditorContext::RestoreSerializedComponents(const MetaCoreSceneSnapshot& snapshot) { + const auto componentRegistry = ModuleRegistry_.ResolveService(); + const auto reflectionRegistry = ModuleRegistry_.ResolveService(); + if (componentRegistry == nullptr || reflectionRegistry == nullptr) { + return; + } + + const MetaCoreTypeRegistry& typeRegistry = reflectionRegistry->GetTypeRegistry(); + for (const MetaCoreGameObjectData& objectData : snapshot.GameObjects) { + if (objectData.SerializedComponents.empty()) { + continue; + } + + MetaCoreGameObject gameObject = Scene_.FindGameObject(objectData.Id); + if (!gameObject) { + continue; + } + + for (const MetaCoreSerializedComponentData& componentData : objectData.SerializedComponents) { + if (MetaCoreIsNativeSceneComponentType(componentData.TypeId) || componentData.Payload.empty()) { + continue; + } + + const auto* descriptor = componentRegistry->FindDescriptor(componentData.TypeId); + if (descriptor == nullptr || !descriptor->PasteComponentPayload) { + AddConsoleMessage( + MetaCoreLogLevel::Warning, + "Scene", + "Skipped serialized component without registered C++ type: " + componentData.TypeId + ); + continue; + } + + if (!descriptor->PasteComponentPayload( + gameObject, + std::span(componentData.Payload.data(), componentData.Payload.size()), + typeRegistry)) { + AddConsoleMessage( + MetaCoreLogLevel::Warning, + "Scene", + "Failed to restore serialized component: " + componentData.TypeId + ); + } + } + } +} + void MetaCoreEditorContext::NormalizeSelection() { std::vector sanitizedSelection; sanitizedSelection.reserve(SelectedObjectIds_.size()); @@ -999,7 +1110,7 @@ void MetaCoreEditorContext::NotifySelectionChanged() { void MetaCoreEditorContext::RefreshSceneDirtyState() { if (const auto scenePersistenceService = ModuleRegistry_.ResolveService(); scenePersistenceService != nullptr) { - scenePersistenceService->UpdateDirtyState(Scene_.CaptureSnapshot()); + scenePersistenceService->UpdateDirtyState(CaptureStateSnapshot().SceneSnapshot); } } diff --git a/Source/MetaCoreEditor/Public/MetaCoreEditor/MetaCoreEditorContext.h b/Source/MetaCoreEditor/Public/MetaCoreEditor/MetaCoreEditorContext.h index 406012d..67d09aa 100644 --- a/Source/MetaCoreEditor/Public/MetaCoreEditor/MetaCoreEditorContext.h +++ b/Source/MetaCoreEditor/Public/MetaCoreEditor/MetaCoreEditorContext.h @@ -207,6 +207,8 @@ public: [[nodiscard]] bool HasDockLayoutBuilt() const; private: + void CaptureSerializedComponents(MetaCoreSceneSnapshot& snapshot) const; + void RestoreSerializedComponents(const MetaCoreSceneSnapshot& snapshot); void NormalizeSelection(); void NotifySelectionChanged(); void RefreshSceneDirtyState(); diff --git a/Source/MetaCoreEditor/Public/MetaCoreEditor/MetaCoreEditorServices.h b/Source/MetaCoreEditor/Public/MetaCoreEditor/MetaCoreEditorServices.h index f5f5637..b063f19 100644 --- a/Source/MetaCoreEditor/Public/MetaCoreEditor/MetaCoreEditorServices.h +++ b/Source/MetaCoreEditor/Public/MetaCoreEditor/MetaCoreEditorServices.h @@ -351,6 +351,7 @@ public: [[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; + virtual void DrawComponentGizmos(MetaCoreEditorContext& editorContext) = 0; }; enum class MetaCorePlayModeState { diff --git a/Source/MetaCoreFoundation/Public/MetaCoreFoundation/MetaCoreReflection.h b/Source/MetaCoreFoundation/Public/MetaCoreFoundation/MetaCoreReflection.h index e7241b2..f993c51 100644 --- a/Source/MetaCoreFoundation/Public/MetaCoreFoundation/MetaCoreReflection.h +++ b/Source/MetaCoreFoundation/Public/MetaCoreFoundation/MetaCoreReflection.h @@ -46,6 +46,7 @@ struct MetaCoreFieldEditorMetadata { std::string Tooltip{}; std::string RawSpec{}; std::string ResourceType{}; + std::string DefaultValue{}; std::optional Min{}; std::optional Max{}; std::optional Step{}; diff --git a/Source/MetaCoreScene/Private/MetaCoreSceneSerializer.cpp b/Source/MetaCoreScene/Private/MetaCoreSceneSerializer.cpp index 507b330..5499512 100644 --- a/Source/MetaCoreScene/Private/MetaCoreSceneSerializer.cpp +++ b/Source/MetaCoreScene/Private/MetaCoreSceneSerializer.cpp @@ -32,6 +32,47 @@ static MetaCoreAssetGuid StringToGuid(const std::string& str) { return str.empty() ? MetaCoreAssetGuid{} : MetaCoreAssetGuid::Parse(str).value_or(MetaCoreAssetGuid{}); } +static char MetaCoreHexDigit(std::byte value) { + constexpr char digits[] = "0123456789abcdef"; + return digits[static_cast(value) & 0x0F]; +} + +static std::string BytesToHexString(const std::vector& bytes) { + std::string encoded; + encoded.reserve(bytes.size() * 2U); + for (std::byte byte : bytes) { + encoded.push_back(MetaCoreHexDigit(byte >> 4)); + encoded.push_back(MetaCoreHexDigit(byte)); + } + return encoded; +} + +static int HexValue(char character) { + if (character >= '0' && character <= '9') return character - '0'; + if (character >= 'a' && character <= 'f') return 10 + character - 'a'; + if (character >= 'A' && character <= 'F') return 10 + character - 'A'; + return -1; +} + +static std::vector HexStringToBytes(const std::string& encoded) { + std::vector bytes; + if (encoded.size() % 2U != 0U) { + return bytes; + } + + bytes.reserve(encoded.size() / 2U); + for (std::size_t index = 0; index < encoded.size(); index += 2U) { + const int high = HexValue(encoded[index]); + const int low = HexValue(encoded[index + 1U]); + if (high < 0 || low < 0) { + bytes.clear(); + return bytes; + } + bytes.push_back(static_cast((high << 4) | low)); + } + return bytes; +} + // 辅助方法:将单个 MetaCoreGameObjectData 转换为 json 格式 static json GameObjectDataToJson(const MetaCoreGameObjectData& obj, const MetaCoreTypeRegistry& registry) { json objJson = json::object(); @@ -131,6 +172,20 @@ static json GameObjectDataToJson(const MetaCoreGameObjectData& obj, const MetaCo } objJson["PrefabInstance"] = prefabJson; } + } else if (objField.Name == "SerializedComponents" && !obj.SerializedComponents.empty()) { + json componentsJson = json::array(); + for (const MetaCoreSerializedComponentData& component : obj.SerializedComponents) { + if (component.TypeId.empty() || component.Payload.empty()) { + continue; + } + componentsJson.push_back(json{ + {"TypeId", component.TypeId}, + {"Payload", BytesToHexString(component.Payload)} + }); + } + if (!componentsJson.empty()) { + objJson["Components"] = std::move(componentsJson); + } } } return objJson; @@ -239,6 +294,18 @@ static MetaCoreGameObjectData JsonToGameObjectData(const json& objJson, const Me } obj.PrefabInstance = prefab; } + } else if (objField.Name == "SerializedComponents" && objJson.contains("Components") && objJson["Components"].is_array()) { + for (const auto& componentJson : objJson["Components"]) { + if (!componentJson.contains("TypeId") || !componentJson.contains("Payload")) { + continue; + } + MetaCoreSerializedComponentData component{}; + component.TypeId = componentJson["TypeId"].get(); + component.Payload = HexStringToBytes(componentJson["Payload"].get()); + if (!component.TypeId.empty() && !component.Payload.empty()) { + obj.SerializedComponents.push_back(std::move(component)); + } + } } } return obj; diff --git a/Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreComponents.h b/Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreComponents.h index aa578c7..6ede3ca 100644 --- a/Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreComponents.h +++ b/Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreComponents.h @@ -47,7 +47,7 @@ MC_STRUCT() struct MetaCoreCameraComponent { MC_GENERATED_BODY() - MC_PROPERTY() + MC_PROPERTY(DisplayName = "Field Of View", Group = "Camera", Min = 1.0, Max = 179.0, Step = 1.0, DefaultValue = 60.0) float FieldOfViewDegrees = 60.0F; MC_PROPERTY() float NearClip = 0.1F; diff --git a/Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreGameObject.h b/Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreGameObject.h index 80f9cc0..408de2c 100644 --- a/Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreGameObject.h +++ b/Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreGameObject.h @@ -6,8 +6,10 @@ #include "MetaCoreScene/MetaCoreComponents.h" #include +#include #include #include +#include namespace MetaCore { @@ -49,6 +51,17 @@ struct MetaCoreNameComponent { std::string Name; }; +MC_STRUCT() +struct MetaCoreSerializedComponentData { + MC_GENERATED_BODY() + + MC_PROPERTY() + std::string TypeId{}; + + MC_PROPERTY() + std::vector Payload{}; +}; + // The ECS handle class MetaCoreGameObject { public: @@ -129,6 +142,8 @@ struct MetaCoreGameObjectData { std::optional PrefabInstance; MC_PROPERTY() std::optional ModelRootTag; + MC_PROPERTY() + std::vector SerializedComponents{}; }; } // namespace MetaCore diff --git a/tests/MetaCoreSmokeTests.cpp b/tests/MetaCoreSmokeTests.cpp index d4e5c8f..dfe8c99 100644 --- a/tests/MetaCoreSmokeTests.cpp +++ b/tests/MetaCoreSmokeTests.cpp @@ -48,6 +48,90 @@ namespace { +struct MetaCoreSmokeScriptComponent { + float Speed = 2.5F; + bool Enabled = true; + std::string Target = "Player"; + MetaCore::MetaCoreMeshAlphaMode AlphaMode = MetaCore::MetaCoreMeshAlphaMode::Opaque; +}; + +void MetaCoreExpect(bool condition, const char* message); + +const MetaCore::MetaCoreIComponentTypeRegistry::MetaCoreComponentDescriptor* MetaCoreRegisterSmokeScriptComponentForTesting( + MetaCore::MetaCoreIComponentTypeRegistry& componentRegistry, + MetaCore::MetaCoreIReflectionRegistry& reflectionRegistry, + std::string typeId = "SmokeScript" +) { + MetaCore::MetaCoreFieldEditorMetadata speedMetadata{}; + speedMetadata.DisplayName = "Speed"; + speedMetadata.Group = "Script"; + speedMetadata.Min = 0.0; + speedMetadata.Max = 10.0; + speedMetadata.Step = 0.25; + speedMetadata.DefaultValue = "2.5"; + + MetaCore::MetaCoreFieldEditorMetadata targetMetadata{}; + targetMetadata.DisplayName = "Target"; + targetMetadata.Group = "Script"; + targetMetadata.DefaultValue = "Player"; + + auto smokeScriptBuilder = MetaCore::MetaCoreRegisterGeneratedStruct( + reflectionRegistry.AccessTypeRegistry(), + "MetaCoreSmokeScriptComponent" + ); + smokeScriptBuilder + .Field<&MetaCoreSmokeScriptComponent::Speed>("Speed", speedMetadata) + .Field<&MetaCoreSmokeScriptComponent::Enabled>("Enabled") + .Field<&MetaCoreSmokeScriptComponent::Target>("Target", targetMetadata) + .Field<&MetaCoreSmokeScriptComponent::AlphaMode>("AlphaMode"); + + const MetaCore::MetaCoreStructDescriptor* smokeScriptReflection = + reflectionRegistry.GetTypeRegistry().FindStruct(); + MetaCoreExpect(smokeScriptReflection != nullptr, "Static C++ smoke script reflection should be registered"); + + MetaCore::MetaCoreIComponentTypeRegistry::MetaCoreComponentDescriptor smokeScriptDescriptor{}; + smokeScriptDescriptor.TypeId = std::move(typeId); + smokeScriptDescriptor.DisplayName = "Smoke Script"; + smokeScriptDescriptor.ReflectedType = smokeScriptReflection; + smokeScriptDescriptor.MutableComponent = [](MetaCore::MetaCoreGameObject& gameObject) -> void* { + return gameObject.HasComponent() ? &gameObject.GetComponent() : nullptr; + }; + smokeScriptDescriptor.ConstComponent = [](const MetaCore::MetaCoreGameObject& gameObject) -> const void* { + return gameObject.HasComponent() ? &gameObject.GetComponent() : nullptr; + }; + smokeScriptDescriptor.HasComponent = [](const MetaCore::MetaCoreGameObject& gameObject) { + return gameObject.HasComponent(); + }; + smokeScriptDescriptor.AddComponent = [](MetaCore::MetaCoreGameObject& gameObject) { + if (gameObject.HasComponent()) { + return false; + } + gameObject.AddComponent(); + return true; + }; + smokeScriptDescriptor.RemoveComponent = [](MetaCore::MetaCoreGameObject& gameObject) { + if (!gameObject.HasComponent()) { + return false; + } + gameObject.RemoveComponent(); + return true; + }; + smokeScriptDescriptor.ResetComponent = [](MetaCore::MetaCoreGameObject& gameObject) { + if (!gameObject.HasComponent()) { + return false; + } + gameObject.GetComponent() = MetaCoreSmokeScriptComponent{}; + return true; + }; + + const std::string registeredTypeId = smokeScriptDescriptor.TypeId; + MetaCoreExpect( + componentRegistry.RegisterComponentDescriptor(std::move(smokeScriptDescriptor)), + "Component registry should accept a static C++ smoke script descriptor" + ); + return componentRegistry.FindDescriptor(registeredTypeId); +} + void MetaCoreExpect(bool condition, const char* message) { if (!condition) { std::cerr << "MetaCoreSmokeTests failed: " << message << '\n'; @@ -662,6 +746,121 @@ void MetaCoreTestComponentRegistryDescriptors() { "Registered component should support reflected field mutation" ); + MetaCore::MetaCoreFieldEditorMetadata speedMetadata{}; + speedMetadata.DisplayName = "Speed"; + speedMetadata.Group = "Script"; + speedMetadata.Min = 0.0; + speedMetadata.Max = 10.0; + speedMetadata.Step = 0.25; + speedMetadata.DefaultValue = "2.5"; + + MetaCore::MetaCoreFieldEditorMetadata targetMetadata{}; + targetMetadata.DisplayName = "Target"; + targetMetadata.Group = "Script"; + targetMetadata.DefaultValue = "Player"; + + auto smokeScriptBuilder = MetaCore::MetaCoreRegisterGeneratedStruct( + reflectionRegistry->AccessTypeRegistry(), + "MetaCoreSmokeScriptComponent" + ); + smokeScriptBuilder + .Field<&MetaCoreSmokeScriptComponent::Speed>("Speed", speedMetadata) + .Field<&MetaCoreSmokeScriptComponent::Enabled>("Enabled") + .Field<&MetaCoreSmokeScriptComponent::Target>("Target", targetMetadata) + .Field<&MetaCoreSmokeScriptComponent::AlphaMode>("AlphaMode"); + + const MetaCore::MetaCoreStructDescriptor* smokeScriptReflection = + reflectionRegistry->GetTypeRegistry().FindStruct(); + MetaCoreExpect(smokeScriptReflection != nullptr, "Static C++ smoke script reflection should be registered"); + + MetaCore::MetaCoreIComponentTypeRegistry::MetaCoreComponentDescriptor smokeScriptDescriptor{}; + smokeScriptDescriptor.TypeId = "SmokeScript"; + smokeScriptDescriptor.DisplayName = "Smoke Script"; + smokeScriptDescriptor.ReflectedType = smokeScriptReflection; + smokeScriptDescriptor.MutableComponent = [](MetaCore::MetaCoreGameObject& gameObject) -> void* { + return gameObject.HasComponent() ? &gameObject.GetComponent() : nullptr; + }; + smokeScriptDescriptor.ConstComponent = [](const MetaCore::MetaCoreGameObject& gameObject) -> const void* { + return gameObject.HasComponent() ? &gameObject.GetComponent() : nullptr; + }; + smokeScriptDescriptor.HasComponent = [](const MetaCore::MetaCoreGameObject& gameObject) { + return gameObject.HasComponent(); + }; + smokeScriptDescriptor.AddComponent = [](MetaCore::MetaCoreGameObject& gameObject) { + if (gameObject.HasComponent()) { + return false; + } + gameObject.AddComponent(); + return true; + }; + smokeScriptDescriptor.RemoveComponent = [](MetaCore::MetaCoreGameObject& gameObject) { + if (!gameObject.HasComponent()) { + return false; + } + gameObject.RemoveComponent(); + return true; + }; + smokeScriptDescriptor.ResetComponent = [](MetaCore::MetaCoreGameObject& gameObject) { + if (!gameObject.HasComponent()) { + return false; + } + gameObject.GetComponent() = MetaCoreSmokeScriptComponent{}; + return true; + }; + + MetaCoreExpect( + componentRegistry->RegisterComponentDescriptor(std::move(smokeScriptDescriptor)), + "Component registry should accept a static C++ smoke script descriptor" + ); + const auto* registeredSmokeScriptDescriptor = componentRegistry->FindDescriptor("SmokeScript"); + MetaCoreExpect(registeredSmokeScriptDescriptor != nullptr, "Static C++ smoke script should be discoverable"); + MetaCoreExpect(registeredSmokeScriptDescriptor->Category == "Scripts", "Static C++ smoke script should default to Scripts category"); + MetaCoreExpect( + static_cast(registeredSmokeScriptDescriptor->CopyComponentPayload), + "Static C++ smoke script should receive default reflected copy support" + ); + MetaCoreExpect( + static_cast(registeredSmokeScriptDescriptor->PasteComponentPayload), + "Static C++ smoke script should receive default reflected paste support" + ); + MetaCoreExpect(registeredSmokeScriptDescriptor->AddComponent(object), "Static C++ smoke script should be addable"); + auto& smokeScript = object.GetComponent(); + smokeScript.Speed = 6.75F; + smokeScript.Enabled = false; + smokeScript.Target = "Valve_A"; + smokeScript.AlphaMode = MetaCore::MetaCoreMeshAlphaMode::Blend; + + const auto speedFieldIterator = std::find_if( + registeredSmokeScriptDescriptor->ReflectedType->Fields.begin(), + registeredSmokeScriptDescriptor->ReflectedType->Fields.end(), + [](const MetaCore::MetaCoreFieldDescriptor& field) { + return field.Name == "Speed"; + } + ); + MetaCoreExpect( + speedFieldIterator != registeredSmokeScriptDescriptor->ReflectedType->Fields.end(), + "Static C++ smoke script should expose Speed field" + ); + MetaCoreExpect(speedFieldIterator->Editor.DefaultValue == "2.5", "Static C++ smoke script Speed should preserve default metadata"); + auto* reflectedSpeed = static_cast(speedFieldIterator->MutableValue(registeredSmokeScriptDescriptor->MutableComponent(object))); + *reflectedSpeed = 7.25F; + MetaCoreExpect(std::abs(object.GetComponent().Speed - 7.25F) <= 0.0001F, "Static C++ smoke script field mutation should work"); + + MetaCoreExpect(componentRegistry->CopyComponent("SmokeScript", object), "Static C++ smoke script should copy through default reflected payload"); + MetaCore::MetaCoreGameObject scriptPasteTarget = scene.CreateGameObject("SmokeScriptPasteTarget"); + MetaCoreExpect(!scriptPasteTarget.HasComponent(), "Paste target should not start with smoke script"); + MetaCoreExpect(componentRegistry->PasteComponent("SmokeScript", scriptPasteTarget), "Static C++ smoke script should paste through default reflected payload"); + MetaCoreExpect(scriptPasteTarget.HasComponent(), "Paste should add static C++ smoke script to target"); + const auto& pastedSmokeScript = scriptPasteTarget.GetComponent(); + MetaCoreExpect(std::abs(pastedSmokeScript.Speed - 7.25F) <= 0.0001F, "Pasted static C++ smoke script Speed should match"); + MetaCoreExpect(!pastedSmokeScript.Enabled, "Pasted static C++ smoke script Enabled should match"); + MetaCoreExpect(pastedSmokeScript.Target == "Valve_A", "Pasted static C++ smoke script Target should match"); + MetaCoreExpect(pastedSmokeScript.AlphaMode == MetaCore::MetaCoreMeshAlphaMode::Blend, "Pasted static C++ smoke script enum should match"); + + MetaCoreExpect(registeredSmokeScriptDescriptor->ResetComponent(scriptPasteTarget), "Static C++ smoke script should reset"); + MetaCoreExpect(std::abs(scriptPasteTarget.GetComponent().Speed - 2.5F) <= 0.0001F, "Reset static C++ smoke script Speed should return to default"); + MetaCoreExpect(scriptPasteTarget.GetComponent().Target == "Player", "Reset static C++ smoke script Target should return to default"); + coreServicesModule->Shutdown(moduleRegistry); moduleRegistry.ShutdownServices(); } @@ -706,6 +905,27 @@ void MetaCoreTestReflectionFieldEditorDescriptors() { const auto* constPosition = static_cast(positionField->ConstValue(&transform)); MetaCoreExpectVec3Near(*constPosition, glm::vec3(4.0F, 5.0F, 6.0F), "ConstValue should read the field"); + const MetaCore::MetaCoreStructDescriptor* cameraDescriptor = + registry.FindStruct(); + MetaCoreExpect(cameraDescriptor != nullptr, "Camera reflection descriptor should exist"); + const auto fovFieldIterator = std::find_if( + cameraDescriptor->Fields.begin(), + cameraDescriptor->Fields.end(), + [](const MetaCore::MetaCoreFieldDescriptor& field) { + return field.Name == "FieldOfViewDegrees"; + } + ); + MetaCoreExpect( + fovFieldIterator != cameraDescriptor->Fields.end(), + "Camera FOV field descriptor should exist" + ); + MetaCoreExpect(fovFieldIterator->Editor.DisplayName == "Field Of View", "Camera FOV should preserve display metadata"); + MetaCoreExpect(fovFieldIterator->Editor.Group == "Camera", "Camera FOV should preserve group metadata"); + MetaCoreExpect(fovFieldIterator->Editor.Min.has_value() && std::abs(*fovFieldIterator->Editor.Min - 1.0) <= 0.0001, "Camera FOV should preserve min metadata"); + MetaCoreExpect(fovFieldIterator->Editor.Max.has_value() && std::abs(*fovFieldIterator->Editor.Max - 179.0) <= 0.0001, "Camera FOV should preserve max metadata"); + MetaCoreExpect(fovFieldIterator->Editor.Step.has_value() && std::abs(*fovFieldIterator->Editor.Step - 1.0) <= 0.0001, "Camera FOV should preserve step metadata"); + MetaCoreExpect(fovFieldIterator->Editor.DefaultValue == "60.0", "Camera FOV should preserve default value metadata"); + const MetaCore::MetaCoreEnumDescriptor* alphaModeEnum = registry.FindEnum(); MetaCoreExpect(alphaModeEnum != nullptr, "Alpha mode enum reflection descriptor should exist"); @@ -782,6 +1002,8 @@ void MetaCoreTestPlayModeLifecycleAndSceneIsolation() { int startCount = 0; int updateCount = 0; int destroyCount = 0; + int drawGizmosCount = 0; + int drawSelectedGizmosCount = 0; MetaCore::MetaCoreIComponentTypeRegistry::MetaCoreComponentDescriptor lifecycleDescriptor{}; lifecycleDescriptor.TypeId = "LifecycleModelRootTag"; @@ -822,6 +1044,12 @@ void MetaCoreTestPlayModeLifecycleAndSceneIsolation() { lifecycleDescriptor.Lifecycle.OnDestroy = [&](MetaCore::MetaCoreEditorContext&, MetaCore::MetaCoreGameObject&) { ++destroyCount; }; + lifecycleDescriptor.Lifecycle.OnDrawGizmos = [&](MetaCore::MetaCoreEditorContext&, MetaCore::MetaCoreGameObject&) { + ++drawGizmosCount; + }; + lifecycleDescriptor.Lifecycle.OnDrawGizmosSelected = [&](MetaCore::MetaCoreEditorContext&, MetaCore::MetaCoreGameObject&) { + ++drawSelectedGizmosCount; + }; MetaCoreExpect( componentRegistry->RegisterComponentDescriptor(std::move(lifecycleDescriptor)), @@ -829,6 +1057,10 @@ void MetaCoreTestPlayModeLifecycleAndSceneIsolation() { ); MetaCoreExpect(playModeService->GetState() == MetaCore::MetaCorePlayModeState::Edit, "Initial play mode state should be Edit"); + componentRegistry->DrawComponentGizmos(editorContext); + MetaCoreExpect(drawGizmosCount == 1, "OnDrawGizmos should run for registered static C++ components"); + MetaCoreExpect(drawSelectedGizmosCount == 1, "OnDrawGizmosSelected should run for selected registered static C++ components"); + MetaCoreExpect(playModeService->EnterPlayMode(editorContext), "EnterPlayMode should succeed"); MetaCoreExpect(playModeService->GetState() == MetaCore::MetaCorePlayModeState::Playing, "Play mode state should be Playing"); MetaCoreExpect(startCount == 1, "OnStart should run once on enter play mode"); @@ -1172,6 +1404,135 @@ void MetaCoreTestJsonSceneSaveCurrentSceneUsesMcsceneJson() { std::filesystem::remove_all(tempProjectRoot); } +void MetaCoreTestStaticCppComponentScenePersistenceAndUndo() { + const std::filesystem::path tempProjectRoot = + std::filesystem::temp_directory_path() / "MetaCoreStaticCppComponentSceneProject"; + std::filesystem::remove_all(tempProjectRoot); + std::filesystem::create_directories(tempProjectRoot / "Assets"); + std::filesystem::create_directories(tempProjectRoot / "Scenes"); + std::filesystem::create_directories(tempProjectRoot / "Library"); + + { + std::ofstream projectFile(tempProjectRoot / "MetaCore.project.json", std::ios::trunc); + projectFile << "{\n" + << " \"name\": \"StaticCppComponentProject\",\n" + << " \"version\": \"0.1.0\",\n" + << " \"scenes\": [],\n" + << " \"startup_scene\": \"\"\n" + << "}\n"; + } + + _putenv_s("METACORE_PROJECT_PATH", tempProjectRoot.string().c_str()); + + MetaCore::MetaCoreWindow window; + MetaCore::MetaCoreRenderDevice renderDevice; + MetaCore::MetaCoreEditorViewportRenderer viewportRenderer; + MetaCore::MetaCoreScene scene; + MetaCore::MetaCoreLogService logService; + MetaCore::MetaCoreEditorModuleRegistry moduleRegistry; + + auto coreServicesModule = MetaCore::MetaCoreCreateBuiltinCoreServicesModule(); + coreServicesModule->Startup(moduleRegistry); + + const auto componentRegistry = moduleRegistry.ResolveService(); + const auto reflectionRegistry = moduleRegistry.ResolveService(); + const auto scenePersistenceService = moduleRegistry.ResolveService(); + MetaCoreExpect(componentRegistry != nullptr, "Component registry should be available for static C++ persistence"); + MetaCoreExpect(reflectionRegistry != nullptr, "Reflection registry should be available for static C++ persistence"); + MetaCoreExpect(scenePersistenceService != nullptr, "Scene persistence should be available for static C++ persistence"); + + const auto* smokeScriptDescriptor = MetaCoreRegisterSmokeScriptComponentForTesting( + *componentRegistry, + *reflectionRegistry, + "PersistentSmokeScript" + ); + MetaCoreExpect(smokeScriptDescriptor != nullptr, "Persistent smoke script should be registered"); + + MetaCore::MetaCoreEditorContext editorContext( + window, + renderDevice, + viewportRenderer, + scene, + logService, + moduleRegistry + ); + + MetaCore::MetaCoreGameObject object = scene.CreateGameObject("ScriptedValve"); + const MetaCore::MetaCoreId objectId = object.GetId(); + MetaCoreExpect(smokeScriptDescriptor->AddComponent(object), "Static C++ component should be addable before save"); + object.GetComponent().Speed = 4.5F; + object.GetComponent().Enabled = false; + object.GetComponent().Target = "Valve_A"; + object.GetComponent().AlphaMode = MetaCore::MetaCoreMeshAlphaMode::Blend; + editorContext.SelectOnly(objectId); + + const std::filesystem::path scenePath = std::filesystem::path("Scenes") / "StaticCppComponent.mcscene.json"; + MetaCoreExpect(scenePersistenceService->SaveSceneAs(editorContext, scenePath), "Static C++ component scene should save"); + MetaCoreExpect(!scenePersistenceService->IsSceneDirty(), "Static C++ component scene should be clean after save"); + + const auto loadedDocument = MetaCore::MetaCoreSceneSerializer::LoadSceneFromJson( + tempProjectRoot / scenePath, + reflectionRegistry->GetTypeRegistry() + ); + MetaCoreExpect(loadedDocument.has_value(), "Saved static C++ component scene should parse"); + const auto documentObjectIterator = std::find_if( + loadedDocument->GameObjects.begin(), + loadedDocument->GameObjects.end(), + [objectId](const MetaCore::MetaCoreGameObjectData& gameObjectData) { + return gameObjectData.Id == objectId; + } + ); + MetaCoreExpect(documentObjectIterator != loadedDocument->GameObjects.end(), "Saved scene document should include scripted object"); + const bool hasSerializedSmokeScript = std::any_of( + documentObjectIterator->SerializedComponents.begin(), + documentObjectIterator->SerializedComponents.end(), + [](const MetaCore::MetaCoreSerializedComponentData& componentData) { + return componentData.TypeId == "PersistentSmokeScript" && !componentData.Payload.empty(); + } + ); + MetaCoreExpect(hasSerializedSmokeScript, "Saved scene document should include serialized static C++ component payload"); + + scene.RestoreSnapshot(MetaCore::MetaCoreSceneSnapshot{}); + editorContext.ClearSelection(); + MetaCoreExpect(scene.GetGameObjects().empty(), "Scene should be empty before static C++ component reload"); + + MetaCoreExpect(scenePersistenceService->LoadScene(editorContext, scenePath), "Static C++ component scene should reload"); + MetaCore::MetaCoreGameObject reloadedObject = scene.FindGameObject(objectId); + MetaCoreExpect(reloadedObject, "Reloaded scripted object should exist"); + MetaCoreExpect(reloadedObject.HasComponent(), "Reloaded object should restore static C++ component"); + MetaCoreExpect(std::abs(reloadedObject.GetComponent().Speed - 4.5F) <= 0.0001F, "Reloaded static C++ Speed should match"); + MetaCoreExpect(!reloadedObject.GetComponent().Enabled, "Reloaded static C++ Enabled should match"); + MetaCoreExpect(reloadedObject.GetComponent().Target == "Valve_A", "Reloaded static C++ Target should match"); + MetaCoreExpect(reloadedObject.GetComponent().AlphaMode == MetaCore::MetaCoreMeshAlphaMode::Blend, "Reloaded static C++ enum should match"); + + const bool modified = editorContext.ExecuteSnapshotCommand("Modify static C++ component", [&]() { + MetaCore::MetaCoreGameObject editedObject = scene.FindGameObject(objectId); + if (!editedObject || !editedObject.HasComponent()) { + return false; + } + editedObject.GetComponent().Speed = 9.0F; + editedObject.GetComponent().Target = "Valve_B"; + return true; + }); + MetaCoreExpect(modified, "Static C++ component field mutation should create an undoable command"); + MetaCoreExpect(scenePersistenceService->IsSceneDirty(), "Static C++ component field mutation should mark scene dirty"); + MetaCoreExpect(std::abs(scene.FindGameObject(objectId).GetComponent().Speed - 9.0F) <= 0.0001F, "Static C++ mutation should apply"); + + MetaCoreExpect(editorContext.UndoCommand(), "Undo static C++ component mutation should succeed"); + MetaCoreExpect(scene.FindGameObject(objectId).HasComponent(), "Undo should keep static C++ component present"); + MetaCoreExpect(std::abs(scene.FindGameObject(objectId).GetComponent().Speed - 4.5F) <= 0.0001F, "Undo should restore static C++ Speed"); + MetaCoreExpect(scene.FindGameObject(objectId).GetComponent().Target == "Valve_A", "Undo should restore static C++ Target"); + + MetaCoreExpect(editorContext.RedoCommand(), "Redo static C++ component mutation should succeed"); + MetaCoreExpect(std::abs(scene.FindGameObject(objectId).GetComponent().Speed - 9.0F) <= 0.0001F, "Redo should restore edited static C++ Speed"); + MetaCoreExpect(scene.FindGameObject(objectId).GetComponent().Target == "Valve_B", "Redo should restore edited static C++ Target"); + + coreServicesModule->Shutdown(moduleRegistry); + moduleRegistry.ShutdownServices(); + _putenv_s("METACORE_PROJECT_PATH", ""); + std::filesystem::remove_all(tempProjectRoot); +} + void MetaCoreTestImportPipelineAndCook() { const unsigned char ppmDataV1[] = { 0x50, 0x36, 0x0A, 0x31, 0x20, 0x31, 0x0A, 0x32, @@ -5169,6 +5530,8 @@ int main() { MetaCoreTestSceneRenderSyncBuildsRenderableCameraLightSnapshot(); std::cout << "[RUN] MetaCoreTestJsonSceneSaveCurrentSceneUsesMcsceneJson..." << std::endl; MetaCoreTestJsonSceneSaveCurrentSceneUsesMcsceneJson(); + std::cout << "[RUN] MetaCoreTestStaticCppComponentScenePersistenceAndUndo..." << std::endl; + MetaCoreTestStaticCppComponentScenePersistenceAndUndo(); std::cout << "[RUN] MetaCoreTestImportPipelineAndCook..." << std::endl; MetaCoreTestImportPipelineAndCook(); std::cout << "[RUN] MetaCoreTestProjectDescriptorAndGltfImporterSkeleton..." << std::endl; diff --git a/tools/MetaCoreHeaderTool/main.cpp b/tools/MetaCoreHeaderTool/main.cpp index 4c9e172..ce11c8b 100644 --- a/tools/MetaCoreHeaderTool/main.cpp +++ b/tools/MetaCoreHeaderTool/main.cpp @@ -402,6 +402,8 @@ void MetaCoreEmitBoolMetadataAssignment( MetaCoreEmitStringMetadataAssignment(output, metadata, metadataVariable, "Tooltip", "Tooltip"); MetaCoreEmitStringMetadataAssignment(output, metadata, metadataVariable, "ResourceType", "ResourceType"); MetaCoreEmitStringMetadataAssignment(output, metadata, metadataVariable, "Resource", "ResourceType"); + MetaCoreEmitStringMetadataAssignment(output, metadata, metadataVariable, "DefaultValue", "DefaultValue"); + MetaCoreEmitStringMetadataAssignment(output, metadata, metadataVariable, "Default", "DefaultValue"); MetaCoreEmitOptionalDoubleMetadataAssignment(output, metadata, metadataVariable, "Min", "Min"); MetaCoreEmitOptionalDoubleMetadataAssignment(output, metadata, metadataVariable, "Max", "Max"); MetaCoreEmitOptionalDoubleMetadataAssignment(output, metadata, metadataVariable, "Step", "Step");