diff --git a/Core/GDCore/Project/EventsBasedObject.cpp b/Core/GDCore/Project/EventsBasedObject.cpp index 9fe374dce7e3..a1780820fdf4 100644 --- a/Core/GDCore/Project/EventsBasedObject.cpp +++ b/Core/GDCore/Project/EventsBasedObject.cpp @@ -82,4 +82,10 @@ void EventsBasedObject::UnserializeFrom(gd::Project& project, } } +void EventsBasedObject::UnserializeDefaultVariantFrom( + gd::Project &project, const SerializerElement &element) { + defaultVariant.UnserializeFrom(project, element); + defaultVariant.SetName(""); +} + } // namespace gd diff --git a/Core/GDCore/Project/EventsBasedObject.h b/Core/GDCore/Project/EventsBasedObject.h index dfea20aafc48..60d6b988895e 100644 --- a/Core/GDCore/Project/EventsBasedObject.h +++ b/Core/GDCore/Project/EventsBasedObject.h @@ -388,6 +388,9 @@ class GD_CORE_API EventsBasedObject: public AbstractEventsBasedEntity { void UnserializeFrom(gd::Project& project, const SerializerElement& element) override; + void UnserializeDefaultVariantFrom(gd::Project &project, + const SerializerElement &element); + private: gd::String defaultName; gd::String assetStoreTag; diff --git a/Core/GDCore/Project/EventsFunctionsExtension.cpp b/Core/GDCore/Project/EventsFunctionsExtension.cpp index bbc6d475402e..d5eb494d0a44 100644 --- a/Core/GDCore/Project/EventsFunctionsExtension.cpp +++ b/Core/GDCore/Project/EventsFunctionsExtension.cpp @@ -227,6 +227,25 @@ void EventsFunctionsExtension::UnserializeExtensionDeclarationFrom( } } +void EventsFunctionsExtension::UnserializeExtensionDefaultVariantsFrom( + gd::Project& project, + const SerializerElement& element) { + auto &eventsBasedObjectsElement = element.GetChild("eventsBasedObjects"); + eventsBasedObjectsElement.ConsiderAsArrayOf("eventsBasedObject"); + for (std::size_t elementIndex = 0; + elementIndex < eventsBasedObjectsElement.GetChildrenCount(); + ++elementIndex) { + const SerializerElement& eventsBasedObjectElement = + eventsBasedObjectsElement.GetChild(elementIndex); + const gd::String& eventsBasedObjectName = eventsBasedObjectElement.GetStringAttribute("name"); + size_t extensionIndex = eventsBasedObjects.GetPosition( + eventsBasedObjects.Get(eventsBasedObjectName)); + + eventsBasedObjects.at(extensionIndex) + .UnserializeDefaultVariantFrom(project, eventsBasedObjectElement); + } +} + void EventsFunctionsExtension::UnserializeExtensionImplementationFrom( gd::Project& project, const SerializerElement& element) { @@ -249,93 +268,20 @@ void EventsFunctionsExtension::UnserializeExtensionImplementationFrom( auto &eventsBasedObjectsElement = element.GetChild("eventsBasedObjects"); eventsBasedObjectsElement.ConsiderAsArrayOf("eventsBasedObject"); - for (gd::String &eventsBasedObjectName : - GetUnserializingOrderEventsBasedObjectNames(eventsBasedObjectsElement)) { + for (std::size_t elementIndex = 0; + elementIndex < eventsBasedObjectsElement.GetChildrenCount(); + ++elementIndex) { + const SerializerElement& eventsBasedObjectElement = + eventsBasedObjectsElement.GetChild(elementIndex); + const gd::String& eventsBasedObjectName = eventsBasedObjectElement.GetStringAttribute("name"); size_t extensionIndex = eventsBasedObjects.GetPosition( eventsBasedObjects.Get(eventsBasedObjectName)); - const SerializerElement &eventsBasedObjectElement = - eventsBasedObjectsElement.GetChild(extensionIndex); eventsBasedObjects.at(extensionIndex) .UnserializeFrom(project, eventsBasedObjectElement); } } -std::vector -EventsFunctionsExtension::GetUnserializingOrderEventsBasedObjectNames( - const gd::SerializerElement &eventsBasedObjectsElement) { - - // Child-objects need the event-based objects they use to be loaded completely - // before they are unserialized. - - // At the beginning, everything is yet to be loaded. - std::vector remainingEventsBasedObjectNames( - eventsBasedObjects.size()); - for (std::size_t i = 0; i < eventsBasedObjects.size(); ++i) { - remainingEventsBasedObjectNames[i] = eventsBasedObjects.at(i).GetName(); - } - - // Helper allowing to find if an object depends on at least one other object from - // the extension that is not loaded yet. - auto &extensionName = name; - auto isDependentFromRemainingEventsBasedObjects = - [&remainingEventsBasedObjectNames, - &extensionName](const gd::SerializerElement &eventsBasedObjectElement) { - auto &objectsElement = eventsBasedObjectElement.GetChild("objects"); - objectsElement.ConsiderAsArrayOf("object"); - - for (std::size_t objectIndex = 0; - objectIndex < objectsElement.GetChildrenCount(); ++objectIndex) { - const gd::String &objectType = - objectsElement.GetChild(objectIndex).GetStringAttribute("type"); - - gd::String usedExtensionName = - PlatformExtension::GetExtensionFromFullObjectType(objectType); - if (usedExtensionName != extensionName) { - // The object comes from another extension: the project is already responsible - // for loading extensions in the proper order. - continue; - } - gd::String eventsBasedObjectName = - gd::PlatformExtension::GetObjectNameFromFullObjectType( - objectType); - - if (std::find(remainingEventsBasedObjectNames.begin(), - remainingEventsBasedObjectNames.end(), - eventsBasedObjectName) != - remainingEventsBasedObjectNames.end()) { - return true; - } - } - return false; - }; - - // Find the order of loading so that the objects are loaded when all the objects - // they depend on are already loaded. - std::vector loadOrderEventsBasedObjectNames; - bool foundAnyEventsBasedObject = true; - while (foundAnyEventsBasedObject) { - foundAnyEventsBasedObject = false; - for (std::size_t i = 0; i < remainingEventsBasedObjectNames.size(); ++i) { - auto eventsBasedObjectName = remainingEventsBasedObjectNames[i]; - size_t extensionIndex = eventsBasedObjects.GetPosition( - eventsBasedObjects.Get(eventsBasedObjectName)); - const SerializerElement &eventsBasedObjectElement = - eventsBasedObjectsElement.GetChild(extensionIndex); - - if (!isDependentFromRemainingEventsBasedObjects( - eventsBasedObjectElement)) { - loadOrderEventsBasedObjectNames.push_back(eventsBasedObjectName); - remainingEventsBasedObjectNames.erase( - remainingEventsBasedObjectNames.begin() + i); - i--; - foundAnyEventsBasedObject = true; - } - } - } - return loadOrderEventsBasedObjectNames; -} - bool EventsFunctionsExtension::IsExtensionLifecycleEventsFunction( const gd::String& eventsFunctionName) { // The list of all supported lifecycle events function names. diff --git a/Core/GDCore/Project/EventsFunctionsExtension.h b/Core/GDCore/Project/EventsFunctionsExtension.h index 4e9fd88bd49a..c7ffc878808e 100644 --- a/Core/GDCore/Project/EventsFunctionsExtension.h +++ b/Core/GDCore/Project/EventsFunctionsExtension.h @@ -317,6 +317,12 @@ class GD_CORE_API EventsFunctionsExtension { gd::Project& project, const gd::SerializerElement& element); + /** + * \brief Load default variants of all the events-based objects. + */ + void UnserializeExtensionDefaultVariantsFrom(gd::Project &project, + const SerializerElement &element); + /** * \brief Load free functions, behaviors and objects implementation * (in opposition to load just their "declaration" by reading their name). @@ -394,9 +400,6 @@ class GD_CORE_API EventsFunctionsExtension { return dependency; } - std::vector GetUnserializingOrderEventsBasedObjectNames( - const gd::SerializerElement &eventsBasedObjectsElement); - gd::String version; gd::String extensionNamespace; gd::String shortDescription; diff --git a/Core/GDCore/Project/Project.cpp b/Core/GDCore/Project/Project.cpp index 8b04e795b961..64d26d32cc94 100644 --- a/Core/GDCore/Project/Project.cpp +++ b/Core/GDCore/Project/Project.cpp @@ -933,7 +933,6 @@ void Project::UnserializeAndInsertExtensionsFrom( eventsFunctionsExtensionsElement.ConsiderAsArrayOf( "eventsFunctionsExtension"); - std::map extensionNameToElementIndex; std::map objectTypeToVariantsElement; // First, only unserialize behaviors and objects names. @@ -946,7 +945,6 @@ void Project::UnserializeAndInsertExtensionsFrom( const SerializerElement& eventsFunctionsExtensionElement = eventsFunctionsExtensionsElement.GetChild(i); const gd::String& name = eventsFunctionsExtensionElement.GetStringAttribute("name"); - extensionNameToElementIndex[name] = i; gd::EventsFunctionsExtension& eventsFunctionsExtension = HasEventsFunctionsExtensionNamed(name) @@ -967,9 +965,14 @@ void Project::UnserializeAndInsertExtensionsFrom( *this, eventsFunctionsExtensionElement); } - // Then unserialize functions, behaviors and objects content. - for (gd::String &extensionName : - GetUnserializingOrderExtensionNames(eventsFunctionsExtensionsElement)) { + // Then unserialize default variants to be able to parse legacy children + // overridings in the next step. + for (std::size_t elementIndex = 0; + elementIndex < eventsFunctionsExtensionsElement.GetChildrenCount(); + ++elementIndex) { + const SerializerElement& eventsFunctionsExtensionElement = + eventsFunctionsExtensionsElement.GetChild(elementIndex); + const gd::String& extensionName = eventsFunctionsExtensionElement.GetStringAttribute("name"); size_t extensionIndex = GetEventsFunctionsExtensionPosition(extensionName); if (extensionIndex == gd::String::npos) { @@ -978,19 +981,46 @@ void Project::UnserializeAndInsertExtensionsFrom( continue; } auto& partiallyLoadedExtension = eventsFunctionsExtensions.at(extensionIndex); + partiallyLoadedExtension + ->UnserializeExtensionDefaultVariantsFrom( + *this, eventsFunctionsExtensionElement); + } - if (extensionNameToElementIndex.find(extensionName) == extensionNameToElementIndex.end()) { - // Should never happen because the extension element is present. - gd::LogError("Can't find extension element to unserialize for " + extensionName + " in second pass of unserialization."); - continue; - } - size_t elementIndex = extensionNameToElementIndex[extensionName]; - const SerializerElement &eventsFunctionsExtensionElement = + // Then unserialize functions, behaviors and objects content. + for (std::size_t elementIndex = 0; + elementIndex < eventsFunctionsExtensionsElement.GetChildrenCount(); + ++elementIndex) { + const SerializerElement& eventsFunctionsExtensionElement = eventsFunctionsExtensionsElement.GetChild(elementIndex); + const gd::String& extensionName = eventsFunctionsExtensionElement.GetStringAttribute("name"); + size_t extensionIndex = GetEventsFunctionsExtensionPosition(extensionName); + if (extensionIndex == gd::String::npos) { + // Should never happen because the extension was added in the first pass. + gd::LogError("Can't find extension " + extensionName + " in the list of extensions in second pass of unserialization."); + continue; + } + auto& partiallyLoadedExtension = eventsFunctionsExtensions.at(extensionIndex); partiallyLoadedExtension ->UnserializeExtensionImplementationFrom( *this, eventsFunctionsExtensionElement); + } + + // Unserialize variants at the end in case their objects has children overriding. + for (std::size_t elementIndex = 0; + elementIndex < eventsFunctionsExtensionsElement.GetChildrenCount(); + ++elementIndex) { + const SerializerElement& eventsFunctionsExtensionElement = + eventsFunctionsExtensionsElement.GetChild(elementIndex); + const gd::String& extensionName = eventsFunctionsExtensionElement.GetStringAttribute("name"); + + size_t extensionIndex = GetEventsFunctionsExtensionPosition(extensionName); + if (extensionIndex == gd::String::npos) { + // Should never happen because the extension was added in the first pass. + gd::LogError("Can't find extension " + extensionName + " in the list of extensions in third pass of unserialization."); + continue; + } + auto& partiallyLoadedExtension = eventsFunctionsExtensions.at(extensionIndex); for (auto &pair : objectTypeToVariantsElement) { auto &objectType = pair.first; @@ -1003,91 +1033,6 @@ void Project::UnserializeAndInsertExtensionsFrom( } } -std::vector Project::GetUnserializingOrderExtensionNames( - const gd::SerializerElement &eventsFunctionsExtensionsElement) { - eventsFunctionsExtensionsElement.ConsiderAsArrayOf( - "eventsFunctionsExtension"); - - // Some extension have custom objects, which have child objects coming from other extension. - // These child objects must be loaded completely before the parent custom obejct can be unserialized. - // This implies: an order on the extension unserialization (and no cycles). - - // At the beginning, everything is yet to be loaded. - std::map extensionNameToElementIndex; - std::vector remainingExtensionNames( - eventsFunctionsExtensionsElement.GetChildrenCount()); - for (std::size_t i = 0; i < eventsFunctionsExtensionsElement.GetChildrenCount(); ++i) { - const SerializerElement& eventsFunctionsExtensionElement = - eventsFunctionsExtensionsElement.GetChild(i); - const gd::String& name = eventsFunctionsExtensionElement.GetStringAttribute("name"); - - remainingExtensionNames[i] = name; - extensionNameToElementIndex[name] = i; - } - - // Helper allowing to find if an extension has an object that depends on - // at least one other object from another extension that is not loaded yet. - auto isDependentFromRemainingExtensions = - [&remainingExtensionNames]( - const gd::SerializerElement &eventsFunctionsExtensionElement) { - auto &eventsBasedObjectsElement = - eventsFunctionsExtensionElement.GetChild("eventsBasedObjects"); - eventsBasedObjectsElement.ConsiderAsArrayOf("eventsBasedObject"); - for (std::size_t eventsBasedObjectsIndex = 0; - eventsBasedObjectsIndex < - eventsBasedObjectsElement.GetChildrenCount(); - ++eventsBasedObjectsIndex) { - auto &objectsElement = - eventsBasedObjectsElement.GetChild(eventsBasedObjectsIndex) - .GetChild("objects"); - objectsElement.ConsiderAsArrayOf("object"); - - for (std::size_t objectIndex = 0; - objectIndex < objectsElement.GetChildrenCount(); ++objectIndex) { - const gd::String &objectType = - objectsElement.GetChild(objectIndex).GetStringAttribute("type"); - - gd::String extensionName = - eventsFunctionsExtensionElement.GetStringAttribute("name"); - gd::String usedExtensionName = - gd::PlatformExtension::GetExtensionFromFullObjectType(objectType); - - if (usedExtensionName != extensionName && - std::find(remainingExtensionNames.begin(), - remainingExtensionNames.end(), - usedExtensionName) != remainingExtensionNames.end()) { - return true; - } - } - } - return false; - }; - - // Find the order of loading so that the extensions are loaded when all the other - // extensions they depend on are already loaded. - std::vector loadOrderExtensionNames; - bool foundAnyExtension = true; - while (foundAnyExtension) { - foundAnyExtension = false; - for (std::size_t i = 0; i < remainingExtensionNames.size(); ++i) { - auto extensionName = remainingExtensionNames[i]; - - size_t elementIndex = extensionNameToElementIndex[extensionName]; - const SerializerElement &eventsFunctionsExtensionElement = - eventsFunctionsExtensionsElement.GetChild(elementIndex); - - if (!isDependentFromRemainingExtensions( - eventsFunctionsExtensionElement)) { - loadOrderExtensionNames.push_back(extensionName); - remainingExtensionNames.erase(remainingExtensionNames.begin() + i); - i--; - foundAnyExtension = true; - } - } - } - return loadOrderExtensionNames; -} - void Project::SerializeTo(SerializerElement& element) const { SerializerElement& versionElement = element.AddChild("gdVersion"); versionElement.SetAttribute("major", gd::VersionWrapper::Major()); diff --git a/Core/GDCore/Project/Project.h b/Core/GDCore/Project/Project.h index 05acfce412e7..28687d516fe5 100644 --- a/Core/GDCore/Project/Project.h +++ b/Core/GDCore/Project/Project.h @@ -1101,18 +1101,6 @@ class GD_CORE_API Project { return wholeProjectDiagnosticReport; } - /** - * @brief Get the project extensions names in the order they have to be - * unserialized. - * - * Child-objects need the event-based objects they use to be loaded completely - * before they are unserialized. - * - * \warning This is only public to allow testing - don't use it in the editor. - */ - static std::vector GetUnserializingOrderExtensionNames( - const gd::SerializerElement& eventsFunctionsExtensionsElement); - private: /** * Initialize from another game. Used by copy-ctor and assign-op. diff --git a/Core/tests/ObjectSerialization.cpp b/Core/tests/ObjectSerialization.cpp index 815142ac2435..320e37c700bb 100644 --- a/Core/tests/ObjectSerialization.cpp +++ b/Core/tests/ObjectSerialization.cpp @@ -216,9 +216,9 @@ TEST_CASE("ObjectSerialization", "[common]") { CheckCustomObjectConfiguration(readProject); } - SECTION( - "Save and load a project with an objects folder structure containing " - "missing object references") { + // clang-format off + SECTION("Save and load a project with an objects folder structure containing missing object references") { + // clang-format on gd::Platform platform; gd::Project writtenProject; SetupProjectWithSprite(writtenProject, platform); @@ -299,8 +299,9 @@ TEST_CASE("ObjectSerialization", "[common]") { REQUIRE(behaviorElement.GetStringAttribute("name") == "MyBehavior"); } - // Event-based object dependency cycles are not tested because they are forbidden by the editor. + // clang-format off SECTION("Save and load a project with custom object dependencies from different extensions") { + // clang-format on gd::Platform platform; gd::Project writtenProject; SetupProjectWithDummyPlatform(writtenProject, platform); @@ -317,6 +318,99 @@ TEST_CASE("ObjectSerialization", "[common]") { auto &eventsBasedObject = eventsExtension.GetEventsBasedObjects().InsertNew( "MyEventsBasedObject", 0); + eventsBasedObject.GetEventsFunctions().InsertNewEventsFunction("MyFunction", 0); + auto &childObject = eventsBasedObject.GetObjects().InsertNewObject( + writtenProject, "MyExtension::Sprite", "MyChildSprite", 0); + // Add a variant that will be used by the other extension. + auto *variant = eventsBasedObject.GetDefaultVariant().Clone(); + variant->SetName("MyVariant"); + variant = &eventsBasedObject.GetVariants().InsertVariant(*variant, 0); + auto &spriteConfiguration = + variant->GetObjects().GetObject("MyChildSprite").GetConfiguration(); + SetupSpriteConfiguration(spriteConfiguration); + } + // An event-based object with a custom object child that overrides its + // configuration. + { + auto &eventsBasedObject = + eventsExtensionWithDependency.GetEventsBasedObjects().InsertNew( + "MyEventsBasedObjectWithDependency", 0); + eventsBasedObject.GetEventsFunctions().InsertNewEventsFunction("MyFunction", 0); + auto &childObject = eventsBasedObject.GetObjects().InsertNewObject( + writtenProject, "MyEventsExtension::MyEventsBasedObject", + "MyChildCustomObject", 0); + auto *customObjectConfiguration = + dynamic_cast( + &childObject.GetConfiguration()); + customObjectConfiguration->SetVariantName("MyVariant"); + } + } + + SerializerElement projectElement; + writtenProject.SerializeTo(projectElement); + + gd::Project readProject; + readProject.AddPlatform(platform); + readProject.UnserializeFrom(projectElement); + + REQUIRE(readProject.GetEventsFunctionsExtensionsCount() == 2); + { + auto &eventsExtensionWithDependency = + readProject.GetEventsFunctionsExtension(0); + REQUIRE(eventsExtensionWithDependency.GetEventsBasedObjects().GetCount() == + 1); + auto &eventsBasedObject = + eventsExtensionWithDependency.GetEventsBasedObjects().Get(0); + REQUIRE(eventsBasedObject.GetEventsFunctions().GetEventsFunctionsCount() == 1); + REQUIRE(eventsBasedObject.GetObjects().GetObjectsCount() == 1); + auto &childObject = eventsBasedObject.GetObjects().GetObject(0); + REQUIRE(childObject.GetName() == "MyChildCustomObject"); + REQUIRE(childObject.GetType() == "MyEventsExtension::MyEventsBasedObject"); + auto *customObjectConfiguration = + dynamic_cast( + &childObject.GetConfiguration()); + REQUIRE(customObjectConfiguration != nullptr); + REQUIRE(customObjectConfiguration->GetVariantName() == "MyVariant"); + } + { + auto &eventsExtension = readProject.GetEventsFunctionsExtension(1); + REQUIRE(eventsExtension.GetEventsBasedObjects().GetCount() == 1); + auto &eventsBasedObject = eventsExtension.GetEventsBasedObjects().Get(0); + REQUIRE(eventsBasedObject.GetEventsFunctions().GetEventsFunctionsCount() == 1); + REQUIRE(eventsBasedObject.GetObjects().GetObjectsCount() == 1); + auto &childObject = eventsBasedObject.GetObjects().GetObject(0); + REQUIRE(childObject.GetName() == "MyChildSprite"); + REQUIRE(childObject.GetType() == "MyExtension::Sprite"); + + REQUIRE(eventsBasedObject.GetVariants().HasVariantNamed("MyVariant")); + auto &variant = eventsBasedObject.GetVariants().GetVariant("MyVariant"); + + auto &spriteConfiguration = + variant.GetObjects().GetObject("MyChildSprite").GetConfiguration(); + CheckSpriteConfiguration(spriteConfiguration); + } + } + + // clang-format off + SECTION("Save and load a project with custom object dependencies from different extensions with a legacy children overridings") { + // clang-format on + gd::Platform platform; + gd::Project writtenProject; + SetupProjectWithDummyPlatform(writtenProject, platform); + + { + // The extension with the dependency is added first to make the + // implementation change the order in which extensions are loaded. + auto &eventsExtensionWithDependency = + writtenProject.InsertNewEventsFunctionsExtension( + "MyEventsExtensionWithDependency", 0); + auto &eventsExtension = writtenProject.InsertNewEventsFunctionsExtension( + "MyEventsExtension", 1); + { + auto &eventsBasedObject = + eventsExtension.GetEventsBasedObjects().InsertNew( + "MyEventsBasedObject", 0); + eventsBasedObject.GetEventsFunctions().InsertNewEventsFunction("MyFunction", 0); auto &childObject = eventsBasedObject.GetObjects().InsertNewObject( writtenProject, "MyExtension::Sprite", "MyChildSprite", 0); } @@ -326,6 +420,7 @@ TEST_CASE("ObjectSerialization", "[common]") { auto &eventsBasedObject = eventsExtensionWithDependency.GetEventsBasedObjects().InsertNew( "MyEventsBasedObjectWithDependency", 0); + eventsBasedObject.GetEventsFunctions().InsertNewEventsFunction("MyFunction", 0); auto &childObject = eventsBasedObject.GetObjects().InsertNewObject( writtenProject, "MyEventsExtension::MyEventsBasedObject", "MyChildCustomObject", 0); @@ -347,23 +442,202 @@ TEST_CASE("ObjectSerialization", "[common]") { readProject.UnserializeFrom(projectElement); REQUIRE(readProject.GetEventsFunctionsExtensionsCount() == 2); - auto &eventsExtensionWithDependency = - readProject.GetEventsFunctionsExtension(0); - REQUIRE(eventsExtensionWithDependency.GetEventsBasedObjects().GetCount() == + { + auto &eventsExtensionWithDependency = + readProject.GetEventsFunctionsExtension(0); + REQUIRE(eventsExtensionWithDependency.GetEventsBasedObjects().GetCount() == + 1); + auto &eventsBasedObject = + eventsExtensionWithDependency.GetEventsBasedObjects().Get(0); + REQUIRE(eventsBasedObject.GetEventsFunctions().GetEventsFunctionsCount() == 1); + REQUIRE(eventsBasedObject.GetObjects().GetObjectsCount() == 1); + auto &childObject = eventsBasedObject.GetObjects().GetObject(0); + REQUIRE(childObject.GetName() == "MyChildCustomObject"); + REQUIRE(childObject.GetType() == "MyEventsExtension::MyEventsBasedObject"); + auto *customObjectConfiguration = + dynamic_cast( + &childObject.GetConfiguration()); + REQUIRE(customObjectConfiguration != nullptr); + auto &spriteConfiguration = + customObjectConfiguration->GetChildObjectConfiguration("MyChildSprite"); + CheckSpriteConfiguration(spriteConfiguration); + } + { + auto &eventsExtension = readProject.GetEventsFunctionsExtension(1); + REQUIRE(eventsExtension.GetEventsBasedObjects().GetCount() == 1); + auto &eventsBasedObject = eventsExtension.GetEventsBasedObjects().Get(0); + REQUIRE(eventsBasedObject.GetEventsFunctions().GetEventsFunctionsCount() == 1); + REQUIRE(eventsBasedObject.GetObjects().GetObjectsCount() == 1); + auto &childObject = eventsBasedObject.GetObjects().GetObject(0); + REQUIRE(childObject.GetName() == "MyChildSprite"); + REQUIRE(childObject.GetType() == "MyExtension::Sprite"); + } + } + + // clang-format off + SECTION("Save and load a project with custom object dependencies from different extensions both ways") { + // clang-format on + gd::Platform platform; + gd::Project writtenProject; + SetupProjectWithDummyPlatform(writtenProject, platform); + + { + auto &eventsExtensionA = writtenProject.InsertNewEventsFunctionsExtension( + "MyEventsExtensionA", 0); + auto &eventsExtensionB = writtenProject.InsertNewEventsFunctionsExtension( + "MyEventsExtensionB", 1); + { + auto &eventsBasedObject = + eventsExtensionA.GetEventsBasedObjects().InsertNew( + "MyEventsBasedObject", 0); + eventsBasedObject.GetEventsFunctions().InsertNewEventsFunction( + "MyFunction", 0); + auto &childObject = eventsBasedObject.GetObjects().InsertNewObject( + writtenProject, "MyExtension::Sprite", "MyChildSprite", 0); + // Add a variant that will be used by the other extension. + auto *variant = eventsBasedObject.GetDefaultVariant().Clone(); + variant->SetName("MyVariant"); + variant = &eventsBasedObject.GetVariants().InsertVariant(*variant, 0); + auto &spriteConfiguration = + variant->GetObjects().GetObject("MyChildSprite").GetConfiguration(); + SetupSpriteConfiguration(spriteConfiguration); + } + { + auto &eventsBasedObject = + eventsExtensionB.GetEventsBasedObjects().InsertNew( + "MyEventsBasedObject", 0); + eventsBasedObject.GetEventsFunctions().InsertNewEventsFunction( + "MyFunction", 0); + auto &childObject = eventsBasedObject.GetObjects().InsertNewObject( + writtenProject, "MyExtension::Sprite", "MyChildSprite", 0); + // Add a variant that will be used by the other extension. + auto *variant = eventsBasedObject.GetDefaultVariant().Clone(); + variant->SetName("MyVariant"); + variant = &eventsBasedObject.GetVariants().InsertVariant(*variant, 0); + auto &spriteConfiguration = + variant->GetObjects().GetObject("MyChildSprite").GetConfiguration(); + SetupSpriteConfiguration(spriteConfiguration); + } + // An event-based object with a custom object child that overrides its + // configuration. + { + auto &eventsBasedObject = + eventsExtensionA.GetEventsBasedObjects().InsertNew( + "MyEventsBasedObjectWithDependency", 0); + eventsBasedObject.GetEventsFunctions().InsertNewEventsFunction( + "MyFunction", 0); + auto &childObject = eventsBasedObject.GetObjects().InsertNewObject( + writtenProject, "MyEventsExtensionB::MyEventsBasedObject", + "MyChildCustomObject", 0); + auto *customObjectConfiguration = + dynamic_cast( + &childObject.GetConfiguration()); + customObjectConfiguration->SetVariantName("MyVariant"); + } + { + auto &eventsBasedObject = + eventsExtensionB.GetEventsBasedObjects().InsertNew( + "MyEventsBasedObjectWithDependency", 0); + eventsBasedObject.GetEventsFunctions().InsertNewEventsFunction( + "MyFunction", 0); + auto &childObject = eventsBasedObject.GetObjects().InsertNewObject( + writtenProject, "MyEventsExtensionA::MyEventsBasedObject", + "MyChildCustomObject", 0); + auto *customObjectConfiguration = + dynamic_cast( + &childObject.GetConfiguration()); + customObjectConfiguration->SetVariantName("MyVariant"); + } + } + + SerializerElement projectElement; + writtenProject.SerializeTo(projectElement); + + gd::Project readProject; + readProject.AddPlatform(platform); + readProject.UnserializeFrom(projectElement); + + REQUIRE(readProject.GetEventsFunctionsExtensionsCount() == 2); + { + auto &eventsExtensionA = + readProject.GetEventsFunctionsExtension(0); + REQUIRE(eventsExtensionA.GetEventsBasedObjects().GetCount() == + 2); + { + auto &eventsBasedObject = + eventsExtensionA.GetEventsBasedObjects().Get(0); + REQUIRE( + eventsBasedObject.GetEventsFunctions().GetEventsFunctionsCount() == 1); - auto &eventsBasedObject = - eventsExtensionWithDependency.GetEventsBasedObjects().Get(0); - REQUIRE(eventsBasedObject.GetObjects().GetObjectsCount() == 1); - auto &childObject = eventsBasedObject.GetObjects().GetObject(0); - REQUIRE(childObject.GetName() == "MyChildCustomObject"); - REQUIRE(childObject.GetType() == "MyEventsExtension::MyEventsBasedObject"); - auto *customObjectConfiguration = - dynamic_cast( - &childObject.GetConfiguration()); - REQUIRE(customObjectConfiguration != nullptr); - auto &spriteConfiguration = - customObjectConfiguration->GetChildObjectConfiguration("MyChildSprite"); - CheckSpriteConfiguration(spriteConfiguration); + REQUIRE(eventsBasedObject.GetObjects().GetObjectsCount() == 1); + auto &childObject = eventsBasedObject.GetObjects().GetObject(0); + REQUIRE(childObject.GetName() == "MyChildCustomObject"); + REQUIRE(childObject.GetType() == + "MyEventsExtensionB::MyEventsBasedObject"); + auto *customObjectConfiguration = + dynamic_cast( + &childObject.GetConfiguration()); + REQUIRE(customObjectConfiguration != nullptr); + REQUIRE(customObjectConfiguration->GetVariantName() == "MyVariant"); + } + { + auto &eventsBasedObject = + eventsExtensionA.GetEventsBasedObjects().Get(1); + REQUIRE( + eventsBasedObject.GetEventsFunctions().GetEventsFunctionsCount() == + 1); + REQUIRE(eventsBasedObject.GetObjects().GetObjectsCount() == 1); + auto &childObject = eventsBasedObject.GetObjects().GetObject(0); + REQUIRE(childObject.GetName() == "MyChildSprite"); + REQUIRE(childObject.GetType() == "MyExtension::Sprite"); + + REQUIRE(eventsBasedObject.GetVariants().HasVariantNamed("MyVariant")); + auto &variant = eventsBasedObject.GetVariants().GetVariant("MyVariant"); + + auto &spriteConfiguration = + variant.GetObjects().GetObject("MyChildSprite").GetConfiguration(); + CheckSpriteConfiguration(spriteConfiguration); + } + } + { + auto &eventsExtensionB = readProject.GetEventsFunctionsExtension(1); + REQUIRE(eventsExtensionB.GetEventsBasedObjects().GetCount() == 2); + { + auto &eventsBasedObject = + eventsExtensionB.GetEventsBasedObjects().Get(0); + REQUIRE( + eventsBasedObject.GetEventsFunctions().GetEventsFunctionsCount() == + 1); + REQUIRE(eventsBasedObject.GetObjects().GetObjectsCount() == 1); + auto &childObject = eventsBasedObject.GetObjects().GetObject(0); + REQUIRE(childObject.GetName() == "MyChildCustomObject"); + REQUIRE(childObject.GetType() == + "MyEventsExtensionA::MyEventsBasedObject"); + auto *customObjectConfiguration = + dynamic_cast( + &childObject.GetConfiguration()); + REQUIRE(customObjectConfiguration != nullptr); + REQUIRE(customObjectConfiguration->GetVariantName() == "MyVariant"); + } + { + auto &eventsBasedObject = + eventsExtensionB.GetEventsBasedObjects().Get(1); + REQUIRE( + eventsBasedObject.GetEventsFunctions().GetEventsFunctionsCount() == + 1); + REQUIRE(eventsBasedObject.GetObjects().GetObjectsCount() == 1); + auto &childObject = eventsBasedObject.GetObjects().GetObject(0); + REQUIRE(childObject.GetName() == "MyChildSprite"); + REQUIRE(childObject.GetType() == "MyExtension::Sprite"); + + REQUIRE(eventsBasedObject.GetVariants().HasVariantNamed("MyVariant")); + auto &variant = eventsBasedObject.GetVariants().GetVariant("MyVariant"); + + auto &spriteConfiguration = + variant.GetObjects().GetObject("MyChildSprite").GetConfiguration(); + CheckSpriteConfiguration(spriteConfiguration); + } + } } SECTION("Save and load a project with custom object dependencies inside an extension") { diff --git a/Core/tests/Project-GetUnserializingOrderExtensionNames.cpp b/Core/tests/Project-GetUnserializingOrderExtensionNames.cpp deleted file mode 100644 index fc85a5967421..000000000000 --- a/Core/tests/Project-GetUnserializingOrderExtensionNames.cpp +++ /dev/null @@ -1,312 +0,0 @@ -/* - * GDevelop Core - * Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights - * reserved. This project is released under the MIT License. - */ -/** - * @file Tests covering common features of GDevelop Core. - */ -#include - -#include "DummyPlatform.h" -#include "GDCore/Extensions/Metadata/MetadataProvider.h" -#include "GDCore/Extensions/Platform.h" -#include "GDCore/Extensions/PlatformExtension.h" -#include "GDCore/Project/Object.h" -#include "GDCore/Project/Project.h" -#include "GDCore/String.h" -#include "catch.hpp" - -TEST_CASE("Project::GetUnserializingOrderExtensionNames", "[common]") { - SECTION("Unserialization order is correct when nothing to load") { - gd::SerializerElement emptyElement; - - std::vector orderedNames = - gd::Project::GetUnserializingOrderExtensionNames(emptyElement); - REQUIRE(orderedNames.size() == 0); - } - SECTION("One extension with no dependencies") { - gd::SerializerElement extensionsElement = gd::Serializer::FromJSON( - R"([ - { - "author": "", - "category": "Input", - "extensionNamespace": "", - "fullName": "3D character keyboard mapper", - "helpPath": "", - "iconUrl": "fake-icon-url", - "name": "Extension1", - "previewIconUrl": "fake-preview-icon-url", - "shortDescription": "3D platformer and 3D shooter keyboard controls.", - "version": "1.0.0", - "description": "3D platformer and 3D shooter keyboard controls.", - "tags": [], - "authorIds": [], - "dependencies": [], - "globalVariables": [], - "sceneVariables": [], - "eventsFunctions": [], - "eventsBasedBehaviors": [], - "eventsBasedObjects": [] - } - ])"); - - std::vector orderedNames = - gd::Project::GetUnserializingOrderExtensionNames(extensionsElement); - REQUIRE(orderedNames.size() == 1); - REQUIRE(orderedNames[0] == "Extension1"); - } - - SECTION("One extension with a dependency outside the loaded extensions") { - gd::SerializerElement extensionsElement = gd::Serializer::FromJSON( - R"([ - { - "author": "", - "category": "Input", - "extensionNamespace": "", - "fullName": "3D character keyboard mapper", - "helpPath": "", - "iconUrl": "fake-icon-url", - "name": "Extension1DependsOtherExtension", - "previewIconUrl": "fake-preview-icon-url", - "shortDescription": "3D platformer and 3D shooter keyboard controls.", - "version": "1.0.0", - "description": "3D platformer and 3D shooter keyboard controls.", - "tags": [], - "authorIds": [], - "dependencies": [], - "globalVariables": [], - "sceneVariables": [], - "eventsFunctions": [], - "eventsBasedBehaviors": [], - "eventsBasedObjects": [ - { - "areaMaxX": 64, - "areaMaxY": 64, - "areaMaxZ": 64, - "areaMinX": 0, - "areaMinY": 0, - "areaMinZ": 0, - "defaultName": "Joystick", - "description": "Joystick for touchscreens.", - "fullName": "Multitouch Joystick", - "name": "SpriteMultitouchJoystick", - "eventsFunctions": [], - "propertyDescriptors": [], - "objects": [ - { - "name": "Thumb", - "type": "OtherExtension::Whatever" - } - ], - "objectsFolderStructure": { - "folderName": "__ROOT", - "children": [] - }, - "objectsGroups": [], - "layers": [], - "instances": [] - } - ] - } - ])"); - - std::vector orderedNames = - gd::Project::GetUnserializingOrderExtensionNames(extensionsElement); - REQUIRE(orderedNames.size() == 1); - REQUIRE(orderedNames[0] == "Extension1DependsOtherExtension"); - } - - SECTION("4 extensions with dependencies on each others") { - gd::SerializerElement extensionsElement = gd::Serializer::FromJSON( - R"([ - { - "author": "", - "category": "Input", - "extensionNamespace": "", - "fullName": "3D character keyboard mapper", - "helpPath": "", - "iconUrl": "fake-icon-url", - "name": "Extension4DependsOn1And3", - "previewIconUrl": "fake-preview-icon-url", - "shortDescription": "3D platformer and 3D shooter keyboard controls.", - "version": "1.0.0", - "description": "3D platformer and 3D shooter keyboard controls.", - "tags": [], - "authorIds": [], - "dependencies": [], - "globalVariables": [], - "sceneVariables": [], - "eventsFunctions": [], - "eventsBasedBehaviors": [], - "eventsBasedObjects": [ - { - "areaMaxX": 64, - "areaMaxY": 64, - "areaMaxZ": 64, - "areaMinX": 0, - "areaMinY": 0, - "areaMinZ": 0, - "defaultName": "Joystick", - "description": "Joystick for touchscreens.", - "fullName": "Multitouch Joystick", - "name": "SpriteMultitouchJoystick", - "eventsFunctions": [], - "propertyDescriptors": [], - "objects": [ - { - "name": "Thumb", - "type": "OtherExtension::Whatever" - }, - { - "name": "Thumb2", - "type": "Extension1DependsNothing::Whatever" - } - ], - "objectsFolderStructure": { - "folderName": "__ROOT", - "children": [] - }, - "objectsGroups": [], - "layers": [], - "instances": [] - }, - { - "areaMaxX": 64, - "areaMaxY": 64, - "areaMaxZ": 64, - "areaMinX": 0, - "areaMinY": 0, - "areaMinZ": 0, - "defaultName": "Joystick", - "description": "Joystick for touchscreens.", - "fullName": "Multitouch Joystick", - "name": "SpriteMultitouchJoystick", - "eventsFunctions": [], - "propertyDescriptors": [], - "objects": [ - { - "name": "Thumb", - "type": "OtherExtension::Whatever" - }, - { - "name": "Thumb2", - "type": "Extension3DependingOn2::Whatever" - } - ], - "objectsFolderStructure": { - "folderName": "__ROOT", - "children": [] - }, - "objectsGroups": [], - "layers": [], - "instances": [] - } - ] - }, - { - "author": "", - "category": "Input", - "extensionNamespace": "", - "fullName": "3D character keyboard mapper", - "helpPath": "", - "iconUrl": "fake-icon-url", - "name": "Extension3DependingOn2", - "previewIconUrl": "fake-preview-icon-url", - "shortDescription": "3D platformer and 3D shooter keyboard controls.", - "version": "1.0.0", - "description": "3D platformer and 3D shooter keyboard controls.", - "tags": [], - "authorIds": [], - "dependencies": [], - "globalVariables": [], - "sceneVariables": [], - "eventsFunctions": [], - "eventsBasedBehaviors": [], - "eventsBasedObjects": [ - { - "areaMaxX": 64, - "areaMaxY": 64, - "areaMaxZ": 64, - "areaMinX": 0, - "areaMinY": 0, - "areaMinZ": 0, - "defaultName": "Joystick", - "description": "Joystick for touchscreens.", - "fullName": "Multitouch Joystick", - "name": "SpriteMultitouchJoystick", - "eventsFunctions": [], - "propertyDescriptors": [], - "objects": [ - { - "name": "Thumb", - "type": "OtherExtension::Whatever" - }, - { - "name": "Thumb2", - "type": "Extension2DependsNothing::Whatever" - } - ], - "objectsFolderStructure": { - "folderName": "__ROOT", - "children": [] - }, - "objectsGroups": [], - "layers": [], - "instances": [] - } - ] - }, - { - "author": "", - "category": "Input", - "extensionNamespace": "", - "fullName": "3D character keyboard mapper", - "helpPath": "", - "iconUrl": "fake-icon-url", - "name": "Extension2DependsNothing", - "previewIconUrl": "fake-preview-icon-url", - "shortDescription": "3D platformer and 3D shooter keyboard controls.", - "version": "1.0.0", - "description": "3D platformer and 3D shooter keyboard controls.", - "tags": [], - "authorIds": [], - "dependencies": [], - "globalVariables": [], - "sceneVariables": [], - "eventsFunctions": [], - "eventsBasedBehaviors": [], - "eventsBasedObjects": [] - }, - { - "author": "", - "category": "Input", - "extensionNamespace": "", - "fullName": "3D character keyboard mapper", - "helpPath": "", - "iconUrl": "fake-icon-url", - "name": "Extension1DependsNothing", - "previewIconUrl": "fake-preview-icon-url", - "shortDescription": "3D platformer and 3D shooter keyboard controls.", - "version": "1.0.0", - "description": "3D platformer and 3D shooter keyboard controls.", - "tags": [], - "authorIds": [], - "dependencies": [], - "globalVariables": [], - "sceneVariables": [], - "eventsFunctions": [], - "eventsBasedBehaviors": [], - "eventsBasedObjects": [] - } - ])"); - - std::vector orderedNames = - gd::Project::GetUnserializingOrderExtensionNames(extensionsElement); - REQUIRE(orderedNames.size() == 4); - REQUIRE(orderedNames[0] == "Extension2DependsNothing"); - REQUIRE(orderedNames[1] == "Extension1DependsNothing"); - REQUIRE(orderedNames[2] == "Extension3DependingOn2"); - REQUIRE(orderedNames[3] == "Extension4DependsOn1And3"); - } -}