Procházet zdrojové kódy

Merge branch 'main' into default_3rdparty

scottr před 4 roky
rodič
revize
d30fd8b1dd
28 změnil soubory, kde provedl 1152 přidání a 705 odebrání
  1. 4 1
      Code/Framework/AzCore/AzCore/Serialization/EditContext.h
  2. 6 4
      Code/Framework/AzCore/AzCore/Serialization/SerializeContext.h
  3. 110 109
      Code/Tools/ProjectManager/Source/PythonBindings.cpp
  4. 2 2
      Gems/Atom/Feature/Common/Assets/Models/sphere.fbx
  5. 3 2
      Gems/ScriptCanvas/Code/Editor/Components/EditorGraph.cpp
  6. 36 4
      Gems/ScriptCanvas/Code/Include/ScriptCanvas/AutoGen/ScriptCanvasNodeable_Header.jinja
  7. 86 74
      Gems/ScriptCanvas/Code/Include/ScriptCanvas/AutoGen/ScriptCanvasNodeable_Source.jinja
  8. 1 4
      Gems/ScriptCanvas/Code/Include/ScriptCanvas/AutoGen/ScriptCanvas_Nodeable_Macros.jinja
  9. 0 1
      Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/Core.h
  10. 5 3
      Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/Datum.cpp
  11. 1 1
      Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/Datum.h
  12. 8 3
      Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/Node.cpp
  13. 2 0
      Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/Node.h
  14. 2 0
      Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/NodeableNode.h
  15. 2 0
      Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/PureData.h
  16. 185 8
      Gems/ScriptCanvas/Code/Include/ScriptCanvas/Grammar/AbstractCodeModel.cpp
  17. 2 0
      Gems/ScriptCanvas/Code/Include/ScriptCanvas/Grammar/AbstractCodeModel.h
  18. 4 4
      Gems/ScriptCanvas/Code/Include/ScriptCanvas/Grammar/ParsingUtilities.cpp
  19. 28 0
      Gems/ScriptCanvas/Code/Include/ScriptCanvas/Grammar/Primitives.h
  20. 37 1
      Gems/ScriptCanvas/Code/Include/ScriptCanvas/Grammar/PrimitivesExecution.cpp
  21. 8 0
      Gems/ScriptCanvas/Code/Include/ScriptCanvas/Grammar/PrimitivesExecution.h
  22. 6 0
      Gems/ScriptCanvas/Code/Include/ScriptCanvas/Results/ErrorText.h
  23. 183 208
      Gems/ScriptCanvasTesting/Assets/ScriptCanvas/UnitTests/LY_SC_UnitTest_RunAllTransformNodes.scriptcanvas
  24. 64 33
      scripts/o3de/o3de/manifest.py
  25. 248 242
      scripts/o3de/o3de/print_registration.py
  26. 7 0
      scripts/o3de/tests/CMakeLists.txt
  27. 111 0
      scripts/o3de/tests/unit_test_manifest.py
  28. 1 1
      scripts/o3de/tests/unit_test_utils.py

+ 4 - 1
Code/Framework/AzCore/AzCore/Serialization/EditContext.h

@@ -127,13 +127,14 @@ namespace AZ
      */
     class EditContext
     {
+    public:
         /// @cond EXCLUDE_DOCS
         class ClassBuilder;
         class EnumBuilder;
         using ClassInfo = ClassBuilder; ///< @deprecated Use EditContext::ClassBuilder
         using EnumInfo = EnumBuilder; ///< @deprecated Use EditContext::EnumBuilder
         /// @endcond
-    public:
+
         AZ_CLASS_ALLOCATOR(EditContext, SystemAllocator, 0);
 
         /**
@@ -186,6 +187,7 @@ namespace AZ
          *  look at the unit tests and example to see use cases.
          *
          */
+    public:
         class ClassBuilder
         {
             friend EditContext;
@@ -399,6 +401,7 @@ namespace AZ
             EnumBuilder* Value(const char* name, E value);
         };
 
+    private:
         typedef AZStd::list<Edit::ClassData> ClassDataListType;
         typedef AZStd::unordered_map<AZ::Uuid, Edit::ElementData> EnumDataMapType;
 

+ 6 - 4
Code/Framework/AzCore/AzCore/Serialization/SerializeContext.h

@@ -101,6 +101,9 @@ namespace AZ
     class SerializeContext
         : public ReflectContext
     {
+        static const unsigned int VersionClassDeprecated = (unsigned int)-1;
+
+    public:
         /// @cond EXCLUDE_DOCS
         friend class EditContext;
         class ClassBuilder;
@@ -108,9 +111,6 @@ namespace AZ
         /// @endcond
         class EnumBuilder;
 
-        static const unsigned int VersionClassDeprecated = (unsigned int)-1;
-
-    public:
         class ClassData;
         struct EnumerateInstanceCallContext;
         struct ClassElement;
@@ -1131,6 +1131,7 @@ namespace AZ
          *      ->Version(3,&MyVersionConverter)
          *      ->Field("data",&MyStruct::m_data);
          */
+    public:
         class ClassBuilder
         {
             friend class SerializeContext;
@@ -1330,7 +1331,8 @@ namespace AZ
             AZStd::vector<AttributeSharedPair, AZStdFunctorAllocator>* m_currentAttributes = nullptr;
         };
 
-        EditContext*    m_editContext;  ///< Pointer to optional edit context.
+    private:
+        EditContext* m_editContext;  ///< Pointer to optional edit context.
         UuidToClassMap  m_uuidMap;      ///< Map for all class in this serialize context
         AZStd::unordered_multimap<AZ::Crc32, AZ::Uuid> m_classNameToUuid;  /// Map all class names to their uuid
         AZStd::unordered_multimap<Uuid, GenericClassInfo*>  m_uuidGenericMap;      ///< Uuid to ClassData map of reflected classes with GenericTypeInfo

+ 110 - 109
Code/Tools/ProjectManager/Source/PythonBindings.cpp

@@ -294,7 +294,8 @@ namespace O3DE::ProjectManager
             RegisterThisEngine();
 
             return result == 0 && !PyErr_Occurred();
-        } catch ([[maybe_unused]] const std::exception& e)
+        }
+        catch ([[maybe_unused]] const std::exception& e)
         {
             AZ_Warning("ProjectManagerWindow", false, "Py_Initialize() failed with %s", e.what());
             return false;
@@ -320,25 +321,25 @@ namespace O3DE::ProjectManager
         bool registrationResult = true; // already registered is considered successful
         bool pythonResult = ExecuteWithLock(
             [&]
+        {
+            // check current engine path against all other registered engines
+            // to see if we are already registered
+            auto allEngines = m_manifest.attr("get_engines")();
+            if (pybind11::isinstance<pybind11::list>(allEngines))
             {
-                // check current engine path against all other registered engines
-                // to see if we are already registered
-                auto allEngines = m_manifest.attr("get_engines")();
-                if (pybind11::isinstance<pybind11::list>(allEngines))
+                for (auto engine : allEngines)
                 {
-                    for (auto engine : allEngines)
+                    AZ::IO::FixedMaxPath enginePath(Py_To_String(engine["path"]));
+                    if (enginePath.Compare(m_enginePath) == 0)
                     {
-                        AZ::IO::FixedMaxPath enginePath(Py_To_String(engine["path"]));
-                        if (enginePath.Compare(m_enginePath) == 0)
-                        {
-                            return;
-                        }
+                        return;
                     }
                 }
+            }
 
-                auto result = m_register.attr("register")(m_enginePath.c_str());
-                registrationResult = (result.cast<int>() == 0);
-            });
+            auto result = m_register.attr("register")(m_enginePath.c_str());
+            registrationResult = (result.cast<int>() == 0);
+        });
 
         bool finalResult = (registrationResult && pythonResult);
         AZ_Assert(finalResult, "Registration of this engine failed!");
@@ -378,12 +379,12 @@ namespace O3DE::ProjectManager
             auto o3deData = m_manifest.attr("load_o3de_manifest")();
             if (pybind11::isinstance<pybind11::dict>(o3deData))
             {
-                engineInfo.m_path                    = Py_To_String(enginePath);
-                engineInfo.m_defaultGemsFolder       = Py_To_String(o3deData["default_gems_folder"]);
-                engineInfo.m_defaultProjectsFolder   = Py_To_String(o3deData["default_projects_folder"]);
+                engineInfo.m_path = Py_To_String(enginePath);
+                engineInfo.m_defaultGemsFolder = Py_To_String(o3deData["default_gems_folder"]);
+                engineInfo.m_defaultProjectsFolder = Py_To_String(o3deData["default_projects_folder"]);
                 engineInfo.m_defaultRestrictedFolder = Py_To_String(o3deData["default_restricted_folder"]);
-                engineInfo.m_defaultTemplatesFolder  = Py_To_String(o3deData["default_templates_folder"]);
-                engineInfo.m_thirdPartyPath          = Py_To_String(o3deData["default_third_party_folder"]);
+                engineInfo.m_defaultTemplatesFolder = Py_To_String(o3deData["default_templates_folder"]);
+                engineInfo.m_thirdPartyPath = Py_To_String(o3deData["default_third_party_folder"]);
             }
 
             auto engineData = m_manifest.attr("get_engine_json_data")(pybind11::none(), enginePath);
@@ -391,8 +392,8 @@ namespace O3DE::ProjectManager
             {
                 try
                 {
-                    engineInfo.m_version = Py_To_String_Optional(engineData,"O3DEVersion","0.0.0.0");
-                    engineInfo.m_name    = Py_To_String_Optional(engineData,"engine_name","O3DE");
+                    engineInfo.m_version = Py_To_String_Optional(engineData, "O3DEVersion", "0.0.0.0");
+                    engineInfo.m_name = Py_To_String_Optional(engineData, "engine_name", "O3DE");
                 }
                 catch ([[maybe_unused]] const std::exception& e)
                 {
@@ -416,20 +417,20 @@ namespace O3DE::ProjectManager
     bool PythonBindings::SetEngineInfo(const EngineInfo& engineInfo)
     {
         bool result = ExecuteWithLock([&] {
-            pybind11::str enginePath             = engineInfo.m_path.toStdString();
-            pybind11::str defaultProjectsFolder  = engineInfo.m_defaultProjectsFolder.toStdString();
-            pybind11::str defaultGemsFolder      = engineInfo.m_defaultGemsFolder.toStdString();
+            pybind11::str enginePath = engineInfo.m_path.toStdString();
+            pybind11::str defaultProjectsFolder = engineInfo.m_defaultProjectsFolder.toStdString();
+            pybind11::str defaultGemsFolder = engineInfo.m_defaultGemsFolder.toStdString();
             pybind11::str defaultTemplatesFolder = engineInfo.m_defaultTemplatesFolder.toStdString();
             pybind11::str defaultThridPartyFolder = engineInfo.m_thirdPartyPath.toStdString();
 
             auto registrationResult = m_register.attr("register")(
-                enginePath,       // engine_path 
-                pybind11::none(), // project_path 
+                enginePath,       // engine_path
+                pybind11::none(), // project_path
                 pybind11::none(), // gem_path
-                pybind11::none(), // external_subdir_path 
-                pybind11::none(), // template_path 
-                pybind11::none(), // restricted_path 
-                pybind11::none(), // repo_uri 
+                pybind11::none(), // external_subdir_path
+                pybind11::none(), // template_path
+                pybind11::none(), // restricted_path
+                pybind11::none(), // repo_uri
                 pybind11::none(), // default_engines_folder
                 defaultProjectsFolder,
                 defaultGemsFolder,
@@ -465,12 +466,12 @@ namespace O3DE::ProjectManager
         QVector<GemInfo> gems;
 
         auto result = ExecuteWithLockErrorHandling([&]
+        {
+            for (auto path : m_manifest.attr("get_engine_gems")())
             {
-                for (auto path : m_manifest.attr("get_engine_gems")())
-                {
-                    gems.push_back(GemInfoFromPath(path));
-                }
-            });
+                gems.push_back(GemInfoFromPath(path));
+            }
+        });
         if (!result.IsSuccess())
         {
             return AZ::Failure<AZStd::string>(result.GetError().c_str());
@@ -485,13 +486,13 @@ namespace O3DE::ProjectManager
         QVector<GemInfo> gems;
 
         auto result = ExecuteWithLockErrorHandling([&]
+        {
+            pybind11::str pyProjectPath = projectPath.toStdString();
+            for (auto path : m_manifest.attr("get_all_gems")(pyProjectPath))
             {
-                pybind11::str pyProjectPath = projectPath.toStdString();
-                for (auto path : m_manifest.attr("get_all_gems")(pyProjectPath))
-                {
-                    gems.push_back(GemInfoFromPath(path));
-                }
-            });
+                gems.push_back(GemInfoFromPath(path));
+            }
+        });
         if (!result.IsSuccess())
         {
             return AZ::Failure<AZStd::string>(result.GetError().c_str());
@@ -506,12 +507,12 @@ namespace O3DE::ProjectManager
         // Retrieve the path to the cmake file that lists the enabled gems.
         pybind11::str enabledGemsFilename;
         auto result = ExecuteWithLockErrorHandling([&]
-            {
-                const pybind11::str pyProjectPath = projectPath.toStdString();
-                enabledGemsFilename = m_cmake.attr("get_enabled_gem_cmake_file")(
-                    pybind11::none(), // project_name
-                    pyProjectPath); // project_path
-            });
+        {
+            const pybind11::str pyProjectPath = projectPath.toStdString();
+            enabledGemsFilename = m_cmake.attr("get_enabled_gem_cmake_file")(
+                pybind11::none(), // project_name
+                pyProjectPath); // project_path
+        });
         if (!result.IsSuccess())
         {
             return AZ::Failure<AZStd::string>(result.GetError().c_str());
@@ -520,13 +521,13 @@ namespace O3DE::ProjectManager
         // Retrieve the actual list of names from the cmake file.
         QVector<AZStd::string> gemNames;
         result = ExecuteWithLockErrorHandling([&]
+        {
+            const auto pyGemNames = m_cmake.attr("get_enabled_gems")(enabledGemsFilename);
+            for (auto gemName : pyGemNames)
             {
-                const auto pyGemNames = m_cmake.attr("get_enabled_gems")(enabledGemsFilename);
-                for (auto gemName : pyGemNames)
-                {
-                    gemNames.push_back(Py_To_String(gemName));
-                }
-            });
+                gemNames.push_back(Py_To_String(gemName));
+            }
+        });
         if (!result.IsSuccess())
         {
             return AZ::Failure<AZStd::string>(result.GetError().c_str());
@@ -540,13 +541,13 @@ namespace O3DE::ProjectManager
         bool registrationResult = false;
         bool result = ExecuteWithLock(
             [&]
-            {
-                pybind11::str projectPath = path.toStdString();
-                auto pythonRegistrationResult = m_register.attr("register")(pybind11::none(), projectPath);
+        {
+            pybind11::str projectPath = path.toStdString();
+            auto pythonRegistrationResult = m_register.attr("register")(pybind11::none(), projectPath);
 
-                // Returns an exit code so boolify it then invert result
-                registrationResult = !pythonRegistrationResult.cast<bool>();
-            });
+            // Returns an exit code so boolify it then invert result
+            registrationResult = !pythonRegistrationResult.cast<bool>();
+        });
 
         return result && registrationResult;
     }
@@ -556,30 +557,30 @@ namespace O3DE::ProjectManager
         bool registrationResult = false;
         bool result = ExecuteWithLock(
             [&]
-            {
-                pybind11::str projectPath = path.toStdString();
-                auto pythonRegistrationResult = m_register.attr("register")(
-                    pybind11::none(),   // engine_path
-                    projectPath,        // project_path
-                    pybind11::none(),   // gem_path
-                    pybind11::none(),   // external_subdir_path
-                    pybind11::none(),   // template_path
-                    pybind11::none(),   // restricted_path
-                    pybind11::none(),   // repo_uri
-                    pybind11::none(),   // default_engines_folder
-                    pybind11::none(),   // default_projects_folder
-                    pybind11::none(),   // default_gems_folder
-                    pybind11::none(),   // default_templates_folder
-                    pybind11::none(),   // default_restricted_folder
-                    pybind11::none(),   // external_subdir_engine_path
-                    pybind11::none(),   // external_subdir_project_path
-                    true,               // remove
-                    false               // force
+        {
+            pybind11::str projectPath = path.toStdString();
+            auto pythonRegistrationResult = m_register.attr("register")(
+                pybind11::none(),   // engine_path
+                projectPath,        // project_path
+                pybind11::none(),   // gem_path
+                pybind11::none(),   // external_subdir_path
+                pybind11::none(),   // template_path
+                pybind11::none(),   // restricted_path
+                pybind11::none(),   // repo_uri
+                pybind11::none(),   // default_engines_folder
+                pybind11::none(),   // default_projects_folder
+                pybind11::none(),   // default_gems_folder
+                pybind11::none(),   // default_templates_folder
+                pybind11::none(),   // default_restricted_folder
+                pybind11::none(),   // external_subdir_engine_path
+                pybind11::none(),   // external_subdir_project_path
+                true,               // remove
+                false               // force
                 );
-                
-                // Returns an exit code so boolify it then invert result
-                registrationResult = !pythonRegistrationResult.cast<bool>();
-            });
+
+            // Returns an exit code so boolify it then invert result
+            registrationResult = !pythonRegistrationResult.cast<bool>();
+        });
 
         return result && registrationResult;
     }
@@ -637,12 +638,12 @@ namespace O3DE::ProjectManager
             try
             {
                 // required
-                gemInfo.m_name        = Py_To_String(data["gem_name"]);
+                gemInfo.m_name = Py_To_String(data["gem_name"]);
 
                 // optional
                 gemInfo.m_displayName = Py_To_String_Optional(data, "DisplayName", gemInfo.m_name);
-                gemInfo.m_summary     = Py_To_String_Optional(data, "Summary", "");
-                gemInfo.m_version     = Py_To_String_Optional(data, "Version", "");
+                gemInfo.m_summary = Py_To_String_Optional(data, "Summary", "");
+                gemInfo.m_version = Py_To_String_Optional(data, "Version", "");
 
                 if (data.contains("Tags"))
                 {
@@ -673,7 +674,7 @@ namespace O3DE::ProjectManager
             try
             {
                 projectInfo.m_projectName = Py_To_String(projectData["project_name"]);
-                projectInfo.m_displayName = Py_To_String_Optional(projectData,"display_name", projectInfo.m_projectName);
+                projectInfo.m_displayName = Py_To_String_Optional(projectData, "display_name", projectInfo.m_projectName);
             }
             catch ([[maybe_unused]] const std::exception& e)
             {
@@ -715,33 +716,33 @@ namespace O3DE::ProjectManager
     AZ::Outcome<void, AZStd::string> PythonBindings::AddGemToProject(const QString& gemPath, const QString& projectPath)
     {
         return ExecuteWithLockErrorHandling([&]
-            {
-                pybind11::str pyGemPath = gemPath.toStdString();
-                pybind11::str pyProjectPath = projectPath.toStdString();
-
-                m_enableGemProject.attr("enable_gem_in_project")(
-                    pybind11::none(), // gem name not needed as path is provided
-                    pyGemPath,
-                    pybind11::none(), // project name not needed as path is provided
-                    pyProjectPath
+        {
+            pybind11::str pyGemPath = gemPath.toStdString();
+            pybind11::str pyProjectPath = projectPath.toStdString();
+
+            m_enableGemProject.attr("enable_gem_in_project")(
+                pybind11::none(), // gem name not needed as path is provided
+                pyGemPath,
+                pybind11::none(), // project name not needed as path is provided
+                pyProjectPath
                 );
-            });
+        });
     }
 
     AZ::Outcome<void, AZStd::string> PythonBindings::RemoveGemFromProject(const QString& gemPath, const QString& projectPath)
     {
         return ExecuteWithLockErrorHandling([&]
-            {
-                pybind11::str pyGemPath = gemPath.toStdString();
-                pybind11::str pyProjectPath = projectPath.toStdString();
-
-                m_disableGemProject.attr("disable_gem_in_project")(
-                    pybind11::none(), // gem name not needed as path is provided
-                    pyGemPath,
-                    pybind11::none(), // project name not needed as path is provided
-                    pyProjectPath
+        {
+            pybind11::str pyGemPath = gemPath.toStdString();
+            pybind11::str pyProjectPath = projectPath.toStdString();
+
+            m_disableGemProject.attr("disable_gem_in_project")(
+                pybind11::none(), // gem name not needed as path is provided
+                pyGemPath,
+                pybind11::none(), // project name not needed as path is provided
+                pyProjectPath
                 );
-            });
+        });
     }
 
     bool PythonBindings::UpdateProject([[maybe_unused]] const ProjectInfo& projectInfo)
@@ -761,8 +762,8 @@ namespace O3DE::ProjectManager
             {
                 // required
                 templateInfo.m_displayName = Py_To_String(data["display_name"]);
-                templateInfo.m_name        = Py_To_String(data["template_name"]);
-                templateInfo.m_summary     = Py_To_String(data["summary"]);
+                templateInfo.m_name = Py_To_String(data["template_name"]);
+                templateInfo.m_summary = Py_To_String(data["summary"]);
 
                 // optional
                 if (data.contains("canonical_tags"))
@@ -794,7 +795,7 @@ namespace O3DE::ProjectManager
         QVector<ProjectTemplateInfo> templates;
 
         bool result = ExecuteWithLock([&] {
-            for (auto path : m_manifest.attr("get_project_templates")())
+            for (auto path : m_manifest.attr("get_templates_for_project_creation")())
             {
                 templates.push_back(ProjectTemplateInfoFromPath(path));
             }

+ 2 - 2
Gems/Atom/Feature/Common/Assets/Models/sphere.fbx

@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:a476e99b55cf2a76fef6775c5a57dad29f8ffcb942c625bab04c89051a72a560
-size 62626
+oid sha256:838830c99f344f5b68e5e85c9bc52751350caf48e662c9c2b767ab77039bbd8f
+size 103472

+ 3 - 2
Gems/ScriptCanvas/Code/Editor/Components/EditorGraph.cpp

@@ -91,7 +91,8 @@ AZ_POP_DISABLE_WARNING
 #include <ScriptCanvas/Variable/VariableBus.h>
 #include <ScriptCanvas/Libraries/UnitTesting/UnitTestingLibrary.h>
 
-////
+    AZ_CVAR(bool, g_disableDeprecatedNodeUpdates, false, {}, AZ::ConsoleFunctorFlags::Null,
+        "Disables automatic update attempts of deprecated nodes, so that graphs that require and update can be viewed in their original form");
 
 namespace EditorGraphCpp
 {
@@ -3642,7 +3643,7 @@ namespace ScriptCanvasEditor
 
                 if (scriptCanvasNode)
                 {
-                    if (scriptCanvasNode->IsDeprecated())
+                     if (scriptCanvasNode->IsDeprecated() && !g_disableDeprecatedNodeUpdates)
                     {
                         ScriptCanvas::NodeConfiguration nodeConfig = scriptCanvasNode->GetReplacementNodeConfiguration();
                         if (nodeConfig.IsValid())

+ 36 - 4
Gems/ScriptCanvas/Code/Include/ScriptCanvas/AutoGen/ScriptCanvasNodeable_Header.jinja

@@ -22,6 +22,11 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 
 #pragma once
 
+#include <AzCore/RTTI/TypeInfo.h>
+#include <AzCore/Serialization/SerializeContext.h>
+#include <AzCore/Serialization/EditContext.h>
+#include <AzCore/RTTI/BehaviorContext.h>
+
 #include <ScriptCanvas/Core/Nodeable.h>
 #include <ScriptCanvas/Core/NodeableNode.h>
 
@@ -43,6 +48,11 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 {%      endif %}
 {% endif %}
 
+{%- set attribute_Base = Class.attrib['Base']  %}
+{% if not Class.attrib['Base'] is defined %}
+{%    set attribute_Base = "ScriptCanvas::Nodeable" %}
+{% endif %}
+
 {% if attribute_Namespace is defined %}
 namespace {{attribute_Namespace}}
 {
@@ -66,6 +76,9 @@ namespace {{attribute_Namespace}}
 public: \
     AZ_RTTI({{className}}, "{{nodeableClassName|createHashGuid}}"{% if Class.attrib['Base'] is defined %}, {{ Class.attrib['Base'] }}{% endif %}); \
     static void Reflect(AZ::ReflectContext* reflection); \
+    static void ExtendReflectionSerialize([[maybe_unused]] AZ::SerializeContext::ClassBuilder* builder){% if Class.attrib['ExtendReflectionSerialize'] is defined %};{% else %}{}{% endif %} \
+    static void ExtendReflectionEdit([[maybe_unused]] AZ::EditContext::ClassBuilder* builder){% if Class.attrib['ExtendReflectionEdit'] is defined %};{% else %}{}{% endif %} \
+    static void ExtendReflectionBehavior([[maybe_unused]] AZ::BehaviorContext::ClassBuilder<{{className}}>* builder){% if Class.attrib['ExtendReflectionBehavior'] is defined %};{% else %}{}{% endif %} \
     static const char* GetDescription() { return "{{ macros.GetAttributeAsString(Class.attrib, 'Description') }}"; } \
     ScriptCanvas::NodePropertyInterface* GetPropertyInterface(AZ::Crc32 propertyId) override; \
     bool IsActive() const override { return false; } \
@@ -83,13 +96,33 @@ public: \
               AZ_COMPONENT({{nodeableNodeName}}, {% if Class.attrib['NodeableUuid'] is defined %}"{{Class.attrib['NodeableUuid']}}"{% else %}"{{nodeableNodeName|createHashGuid}}"{% endif %}, ScriptCanvas::Nodes::NodeableNode);
 
               static void Reflect(AZ::ReflectContext* context);
+
+              static void ExtendReflectionSerialize([[maybe_unused]] AZ::SerializeContext::ClassBuilder* builder){% if Class.attrib['ExtendReflectionSerialize'] is defined %};{% else %}{}{% endif %}
+
+              static void ExtendReflectionEdit([[maybe_unused]] AZ::EditContext::ClassBuilder* builder){% if Class.attrib['ExtendReflectionEdit'] is defined %};{% else %}{}{% endif %}
+
+              static void ExtendReflectionBehavior([[maybe_unused]] AZ::BehaviorContext::ClassBuilder<{{nodeableNodeName}}>* builder){% if Class.attrib['ExtendReflectionBehavior'] is defined %};{% else %}{}{% endif %}
+
               void ConfigureSlots() override;
+
+{% if Class.attrib['ExtendConfigureSlots'] is defined %}
+              void ExtendConfigureSlots([[maybe_unused]] SlotExecution::Ins& ins, [[maybe_unused]] SlotExecution::Outs& latents);
+
+{% else %}
+              /* no slot configuration extension, Use Class attribute 'ExtendConfigureSlots' to extend them */
+
+{% endif %}
               void ConfigureVisualExtensions() override;
+
               size_t GenerateFingerprint() const override;
-{%            if Class.attrib['EntryPoint'] is defined and Class.attrib['EntryPoint'] == "true" %}
+
+{% if Class.attrib['EntryPoint'] is defined and Class.attrib['EntryPoint'] == "true" %}
               bool IsEntryPoint() const override { return true; }
-{%            endif %}
+
+{% endif %}
               {{nodeableNodeName}}();
+
+              {{Class.attrib['NodeDeclarations']}}
           };
       }
 {% endif %}
@@ -100,6 +133,5 @@ public: \
 
 {{ macros.ReportErrors() }}
 
-
 {%  endfor %}
-{% endfor %}
+{% endfor %}

+ 86 - 74
Gems/ScriptCanvas/Code/Include/ScriptCanvas/AutoGen/ScriptCanvasNodeable_Source.jinja

@@ -19,15 +19,10 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 //
 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////
 
-#include <AzCore/RTTI/BehaviorContext.h>
-#include <AzCore/Serialization/EditContext.h>
-#include <AzCore/RTTI/TypeInfo.h>
-
 #include <ScriptCanvas/Core/Contracts.h>
 #include <ScriptCanvas/Core/NodeableNode.h>
 #include <ScriptCanvas/Core/SlotExecutionMap.h>
 #include <ScriptCanvas/Grammar/ParsingUtilities.h>
-
 #include <ScriptCanvas/Utils/VersionConverters.h>
 
 {% for xml in dataFiles %}
@@ -47,13 +42,18 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 {%- set attribute_Category = Class.attrib['Category']  %}
 {%- set attribute_Uuid = Class.attrib['Uuid']  %}
 {%- set attribute_Icon = Class.attrib['Icon']  %}
-{%- set attribute_Base = Class.attrib['Base']  %}
 {%- set attribute_GeneratePropertyFriend = Class.attrib['GeneratePropertyFriend']  %}
 {%- set attribute_Version = Class.attrib['Version'] %}
 {%- set attribute_VersionConverter = Class.attrib['VersionConverter'] %}
 {%- set attribute_EventHandler = Class.attrib['EventHandler'] %}
 {%- set attribute_Deprecated = Class.attrib['Deprecated'] %}
 
+
+{%- set attribute_Base = Class.attrib['Base']  %}
+{% if not Class.attrib['Base'] is defined %}
+{%    set attribute_Base = "ScriptCanvas::Nodeable" %}
+{% endif %}
+
 {% set attribute_Namespace = undefined %}
 {%- if Class.attrib['Namespace'] is defined %}
 {%      if Class.attrib['Namespace'] != "None" %}
@@ -95,19 +95,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 {{CollectDisplayGroups('Output')}}
 {{CollectDisplayGroups('Parameter')}}
 
-{# FOR DEBUGGING / DIAGNOSTIC
-
-// Standalone (No DisplayGroup) {{ global_standaloneTagMap }}
-{% for key, value in global_standaloneTagMap.items() %}
-// {{key}} : {{value}} 
-{% endfor %}
-
-// DisplayGrouped {{ global_displayGroupMap }}
-{% for key, value in global_displayGroupMap.items() %}
-// {{key}} : {{value}}
-{% endfor %}
-#}
-
 {# ----------------------------------------------------------------------------------------- #}
 
 {% if attribute_Namespace is defined %}
@@ -115,33 +102,6 @@ namespace {{attribute_Namespace}}
 {
 {% endif %}
 
-{# Standard "In" function }
-{{nodemacro.FunctionSignature(attribute_QualifiedName, Class)}}
-{
-{%-  for parameter in Class.findall('Parameter') -%}
-{%     if parameter.attrib['Input'] is defined and parameter.attrib['Input'] == "True" %}
-
-//    this->{{parameter.attrib['Name']}} = arg{{loop.index0}};
-
-{%-     endif -%}
-{%  endfor %}
-
-{%  set returnNames = [] %}
-{%  set returnTypes = [] %}
-{%-     for return in Class.findall('Parameter') -%}
-{%-         if return.attrib['Output'] is defined and return.attrib['Output'] == "True" -%}
-{%              if returnTypes.append(return.attrib['Type']) %}{% endif %}
-{%              if returnNames.append("this->" + return.attrib['Name']) %}{% endif %}
-{%-         endif -%}
-{%-     endfor -%}
-
-{%  if returnNames|length() == 1 %}
-return {{returnNames[0]}};
-{%  elif returnNames|length() > 1 %}
-    return AZStd::tuple<{{returnTypes|join(", ")}}>({{returnNames|join(", ")}});
-{%  endif %}
-}
-#}
 {%- set nodeableNodeName = attribute_Name + 'Node' %}
 {% set list_outputs = [] %}
 {% for output in Class.iter('Output') %}
@@ -156,43 +116,81 @@ return {{returnNames[0]}};
 {% for item in Class.iter('Output') %}
 {%      if item.attrib['DisplayGroup'] is defined %}{% set displayGroup = item.attrib['DisplayGroup'] %}{% endif %}
 {% endfor %}
+{% set branches = [] %}
+{% for method in Class.findall('Input') %}
+{%      for branch in method.findall('Branch') %}
+{%			if branches.append(branch) %}{% endif %}
+{%		endfor %}
+{% endfor %}
 {# ExecutionOuts #}
 // ExecutionOuts begin
 {{ nodemacro.ExecutionOutDefinitions(Class, attribute_QualifiedName)}}
+{% if not Class.attrib['ExtendConfigureSlots'] is defined %} size_t {{attribute_QualifiedName}}::GetRequiredOutCount() const { return {{Class.findall('Output')|length + branches|length}}; }{% endif %}
 // ExecutionOuts end
 {# Reflect #}
+
+{% if Class.attrib['ExtendReflectionSerialize'] is defined %}
+{%      set ExtendReflectionSerialize = "defined" %}
+{%      set preSerialize = "serializeBuilder" %}
+{%      set postSerialize = ";" %}
+{% else %}
+{%      set preSerialize = "" %}
+{%      set postSerialize = "" %}
+{% endif %}
+
+{% if Class.attrib['ExtendReflectionEdit'] is defined %}
+{%      set ExtendReflectionEdit = "defined" %}
+{%      set preEdit = "editorBuilder" %}
+{%      set postEdit = ";" %}
+{% else %}
+{%      set preEdit = "" %}
+{%      set postEdit = "" %}
+{% endif %}
+
+{% if Class.attrib['ExtendReflectionBehavior'] is defined %}
+{%      set ExtendReflectionBehavior = "defined" %}
+{%      set preBehavior = "behaviorBuilder" %}
+{%      set postBehavior = ";" %}
+{% else %}
+{%      set preBehavior = "" %}
+{%      set postBehavior = "" %}
+{% endif %}
+
 void {{attribute_QualifiedName}}::Reflect(AZ::ReflectContext* context)
 {
     using namespace ScriptCanvas;
 
     if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
     {
-        serializeContext->Class<{{ attribute_Name }}{% if attribute_Base is defined %}, {{ attribute_Base }}{% endif %}>()
+{% if ExtendReflectionSerialize is defined %}   auto {{preSerialize}} = {% else %}   {% endif %}serializeContext->Class<{{ attribute_Name }}{% if attribute_Base is defined %}, {{ attribute_Base }}{% endif %}>(){{postSerialize}}
 {% if attribute_EventHandler is defined %}
-            ->EventHandler<{{ attribute_EventHandler }}>()
+            {{preSerialize}}->EventHandler<{{ attribute_EventHandler }}>(){{postSerialize}}
 {% endif %}
 
 {# Serialized Properties #}
 {% for Property in Class.iter('Property') %}
 {% set property_Name = Property.attrib['Name'] %}
-            ->Field("{{ property_Name }}", &{{ attribute_Name }}::{{ property_Name | replace(' ','') }})
+            {{preSerialize}}->Field("{{ property_Name }}", &{{ attribute_Name }}::{{ property_Name | replace(' ','') }}){{postSerialize}}
 {% endfor %}
         ;
 
+{% if ExtendReflectionSerialize is defined %}
+        ExtendReflectionSerialize(&{{preSerialize}});
+{% endif %}
+
         if (AZ::EditContext* editContext = serializeContext->GetEditContext())
         {
-            editContext->Class<{{ attribute_QualifiedName }}>("{{ attribute_PreferredClassName }}", "{{ attribute_Description }}")
-                       ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
-                            ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
-
+{% if ExtendReflectionEdit is defined %}     auto {{preEdit}} = {% else %}   {% endif %}editContext->Class<{{ attribute_QualifiedName }}>("{{ attribute_PreferredClassName }}", "{{ attribute_Description }}"){{postEdit}}
+                       {{preEdit}}->ClassElement(AZ::Edit::ClassElements::EditorData, ""){{postEdit}}
+                       {{preEdit}}->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly){{postEdit}}
 {% if attribute_Category is defined %}
-                            ->Attribute(AZ::Edit::Attributes::Category, "{{ attribute_Category }}")
+                        {{preEdit}}->Attribute(AZ::Edit::Attributes::Category, "{{ attribute_Category }}"){{postEdit}}
 {%- endif %}
 {% if attribute_Icon is defined %}
-                            ->Attribute(AZ::Edit::Attributes::Icon, "{{ attribute_Icon }}")
+                        {{preEdit}}->Attribute(AZ::Edit::Attributes::Icon, "{{ attribute_Icon }}"){{postEdit}}
 {%- endif %}
 {% if attribute_Deprecated is defined %}
-                            ->Attribute(AZ::Edit::Attributes::Deprecated, "{{ attribute_Deprecated }}")
+                        {{preEdit}}->Attribute(AZ::Edit::Attributes::Deprecated, "{{ attribute_Deprecated }}"){{postEdit}}
 {%- endif %}
 {% set uihandler = 'AZ::Edit::UIHandlers::Default' %}
 {% for item in Class.iter('Property') %}
@@ -203,29 +201,34 @@ void {{attribute_QualifiedName}}::Reflect(AZ::ReflectContext* context)
 {% if item.attrib['Description'] is defined %}
     {% set description = item.attrib['Description'] %}
 {% endif %}
-                            // {{ item.attrib['Name'] }}
-                            ->DataElement({{ uihandler }}, &{{ attribute_Name }}::{{ item.attrib['Name'] }}, "{{ item.attrib['Name'] }}", "{{ description }}")
+        // {{ item.attrib['Name'] }}
+                        {{preEdit}}->DataElement({{ uihandler }}, &{{ attribute_Name }}::{{ item.attrib['Name'] }}, "{{ item.attrib['Name'] }}", "{{ description }}"){{postEdit}}
 {%          for EditAttribute in item.iter('EditAttribute') %}
-                                ->Attribute({{ EditAttribute.attrib['Key'] }}, {{ EditAttribute.attrib['Value'] }})
+                        {{preEdit}}->Attribute({{ EditAttribute.attrib['Key'] }}, {{ EditAttribute.attrib['Value'] }}){{postEdit}}
 {%          endfor %}
 {% endfor %}
                             ;
+{% if ExtendReflectionEdit is defined %}
+                            ExtendReflectionEdit(&{{preEdit}});
+{% endif %}
             }
         }
 
     // Behavior Context Reflection
     if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
     {
-        behaviorContext->Class<{{ attribute_Name }}>("{{ attribute_Name }}")
-            ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::List)
-            ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
+{% if ExtendReflectionBehavior is defined %}    auto {{preBehavior}} = {% else %}    {% endif %}behaviorContext->Class<{{ attribute_Name }}>("{{ attribute_Name }}"){{postBehavior}}
+            {{preBehavior}}->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::List){{postBehavior}}
+            {{preBehavior}}->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common){{postBehavior}}
 {% for inputMethod in Class.iter('Input') %}
 {% set methodName = inputMethod.attrib['Name'] %}
             // {{ inputMethod.attrib['Name'] }}
-            ->Method(Grammar::ToIdentifier("{{ macros.SlotName(methodName) }}").c_str(), &{{ attribute_Name }}::{{ macros.CleanName(methodName) }})
+            {{preBehavior}}->Method(Grammar::ToIdentifier("{{ macros.SlotName(methodName) }}").c_str(), &{{ attribute_Name }}::{{ macros.CleanName(methodName) }}){{postBehavior}}
 {% endfor %}
-
         ;
+{% if ExtendReflectionBehavior is defined %}
+        ExtendReflectionBehavior(&{{preBehavior}});
+{% endif %}
     }
 }
 
@@ -253,27 +256,32 @@ Nodes::{{ nodeableNodeName }}::{{ nodeableNodeName }}()
 {# NodeableNode Reflection #}
 void Nodes::{{ nodeableNodeName }}::Reflect(AZ::ReflectContext* context)
 {
-
     {{ attribute_QualifiedName }}::Reflect(context);
 
     // Serialization Context Reflection
     if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
     {
-        serializeContext->Class<{{ nodeableNodeName }}, NodeableNode>()
+        {%if ExtendReflectionSerialize is defined%}auto {{preSerialize}} = {%endif%}serializeContext->Class<{{ nodeableNodeName }}, NodeableNode>(){{postSerialize}}
 {% if attribute_Version is defined %}
-            ->Version({{ attribute_Version }}{% if attribute_VersionConverter is defined %}, &{{ attribute_VersionConverter }}{% endif %})
+            {{preSerialize}}->Version({{ attribute_Version }}{% if attribute_VersionConverter is defined %}, &{{ attribute_VersionConverter }}{% endif %}){{postSerialize}}
 {% else %}
-            ->Version(0)
+            {{preSerialize}}->Version(0){{postSerialize}}
+{% endif %}
+            ;
+{% if ExtendReflectionSerialize is defined %}
+        ExtendReflectionSerialize(&{{preSerialize}});
 {% endif %}
-        ;
 
         if (AZ::EditContext* editContext = serializeContext->GetEditContext())
         {
-            editContext->Class<{{ nodeableNodeName }}>("{{ attribute_PreferredClassName }}", "{{ attribute_Description }}")
-                       ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
-                            ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
-                            ->Attribute(AZ::Edit::Attributes::AutoExpand, true)
-                        ;
+            {% if ExtendReflectionEdit is defined %}auto {{preEdit}} = {%endif%}editContext->Class<{{ nodeableNodeName }}>("{{ attribute_PreferredClassName }}", "{{ attribute_Description }}"){{postEdit}}
+                {{preEdit}}->ClassElement(AZ::Edit::ClassElements::EditorData, ""){{postEdit}}
+                {{preEdit}}->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly){{postEdit}}
+                {{preEdit}}->Attribute(AZ::Edit::Attributes::AutoExpand, true){{postEdit}}
+                ;
+{% if ExtendReflectionEdit is defined %}
+            ExtendReflectionEdit(&{{preEdit}});
+{% endif %}
         }
     }
 }
@@ -324,6 +332,7 @@ void Nodes::{{ nodeableNodeName }}::ConfigureVisualExtensions()
         RegisterExtension(visualExtensions);
     }
 {% endfor %}
+    OnConfigureVisualExtensions();
 }
 
 {# ConfigureSlots #}
@@ -457,6 +466,9 @@ void Nodes::{{ nodeableNodeName }}::ConfigureSlots()
 {% endfor %}
 #}
 
+{% if Class.attrib['ExtendConfigureSlots'] is defined %}    
+    ExtendConfigureSlots(ins, outs);
+{% endif %}
     // Generate the execution map
     m_slotExecutionMap = SlotExecution::Map(AZStd::move(ins), AZStd::move(outs));
 

+ 1 - 4
Gems/ScriptCanvas/Code/Include/ScriptCanvas/AutoGen/ScriptCanvas_Nodeable_Macros.jinja

@@ -349,7 +349,4 @@ void {{qualifiedName}}::Call{{CleanName(outName)}}({{ExecutionOutReturnDefinitio
 {%-     for executionOut in Class.findall('Output') -%}
                 {{ ExecutionOutDefinition(Class, qualifiedName, executionOut, loop.index0 + branches|length) }}
 {%-     endfor %}
-
-size_t {{qualifiedName}}::GetRequiredOutCount() const { return {{Class.findall('Output')|length + branches|length}}; }
-
-{% endmacro %}
+{% endmacro %}

+ 0 - 1
Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/Core.h

@@ -250,7 +250,6 @@ namespace ScriptCanvas
     };
 
     using ScriptCanvasSettingsRequestBus = AZ::EBus<ScriptCanvasSettingsRequests>;
-
 }
 
 namespace AZStd

+ 5 - 3
Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/Datum.cpp

@@ -1476,9 +1476,11 @@ namespace ScriptCanvas
 
     const void* Datum::GetValueAddress() const
     {
-        return m_type.GetType() != Data::eType::BehaviorContextObject 
-             ? AZStd::any_cast<void>(&m_storage) 
-             : (*AZStd::any_cast<BehaviorContextObjectPtr>(&m_storage))->Get();
+        return !m_storage.empty()
+            ? m_type.GetType() != Data::eType::BehaviorContextObject 
+                 ?  AZStd::any_cast<void>(&m_storage) 
+                 : (*AZStd::any_cast<BehaviorContextObjectPtr>(&m_storage))->Get()
+            : nullptr;
     }
 
     bool Datum::Initialize(const Data::Type& type, eOriginality originality, const void* source, const AZ::Uuid& sourceTypeID)

+ 1 - 1
Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/Datum.h

@@ -389,7 +389,7 @@ namespace ScriptCanvas
 
     bool Datum::Empty() const
     {
-        return GetValueAddress() == nullptr;
+        return m_storage.empty() || GetValueAddress() == nullptr;
     }
 
     template<typename t_Value>

+ 8 - 3
Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/Node.cpp

@@ -3577,12 +3577,12 @@ namespace ScriptCanvas
         }
 
         if (targetSlotType == CombinedSlotType::DataOut
-            && executionSlot.GetType() == CombinedSlotType::ExecutionIn
-            && executionInCount > 1)
+        && executionSlot.GetType() == CombinedSlotType::ExecutionIn
+        && executionInCount > 1)
         {
             if (!executionChildSlot || executionChildSlot->GetType() != CombinedSlotType::ExecutionOut)
             {
-                return AZ::Failure(AZStd::string("Data out by ExcutionIn must have child out slot"));
+                return AZ::Failure(AZStd::string("Data out by ExecutionIn must have child out slot"));
             }
         }
 
@@ -3626,6 +3626,11 @@ namespace ScriptCanvas
         return {};
     }
 
+    Grammar::MultipleFunctionCallFromSingleSlotInfo Node::GetMultipleFunctionCallFromSingleSlotInfo([[maybe_unused]] const Slot& slot) const
+    {
+        return {};
+    }
+
     VariableId Node::GetVariableIdRead(const Slot*) const
     {
         return {};

+ 2 - 0
Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/Node.h

@@ -715,6 +715,8 @@ namespace ScriptCanvas
 
         virtual PropertyFields GetPropertyFields() const;
 
+        virtual Grammar::MultipleFunctionCallFromSingleSlotInfo GetMultipleFunctionCallFromSingleSlotInfo(const Slot& slot) const;
+
         virtual VariableId GetVariableIdRead(const Slot*) const;
 
         virtual VariableId GetVariableIdWritten(const Slot*) const;

+ 2 - 0
Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/NodeableNode.h

@@ -73,6 +73,8 @@ namespace ScriptCanvas
             
             void ConfigureSlots() override;
 
+            virtual void OnConfigureVisualExtensions() {}
+
             AZ::Outcome<const AZ::BehaviorClass*, AZStd::string> GetBehaviorContextClass() const;
 
             ConstSlotsOutcome GetBehaviorContextOutName(const Slot& inSlot) const;

+ 2 - 0
Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/PureData.h

@@ -46,6 +46,8 @@ namespace ScriptCanvas
 
         const AZStd::unordered_map<AZStd::string, AZStd::pair<SlotId, SlotId>>& GetPropertyNameSlotMap() const;
 
+        AZ_INLINE AZ::Outcome<DependencyReport, void> GetDependencies() const override { return AZ::Success(DependencyReport{}); }
+
         ~PureData() override;
 
     protected:

+ 185 - 8
Gems/ScriptCanvas/Code/Include/ScriptCanvas/Grammar/AbstractCodeModel.cpp

@@ -2249,6 +2249,10 @@ namespace ScriptCanvas
             }
 #endif
             AddAllVariablesPreParse();
+            if (!IsErrorFree())
+            {
+                return;
+            }
 
             for (auto& nodeEntity : m_source.m_graphData->m_nodes)
             {
@@ -2270,6 +2274,16 @@ namespace ScriptCanvas
                 {
                     AddError(nullptr, ValidationConstPtr(aznew NullEntityInGraph()));
                 }
+
+                if (!IsErrorFree())
+                {
+                    return;
+                }
+            }
+
+            if (!IsErrorFree())
+            {
+                return;
             }
 
             ParseAutoConnectedEBusHandlerVariables();
@@ -2638,7 +2652,8 @@ namespace ScriptCanvas
             AZStd::vector<VariableId> inputVariableIds;
             AZStd::unordered_map<VariableId, Grammar::VariableConstPtr> inputVariablesById;
 
-            for (auto variable : GetVariables())
+            auto& variables = GetVariables();
+            for (auto variable : variables)
             {
                 auto constructionRequirement = ParseConstructionRequirement(variable);
 
@@ -2655,18 +2670,30 @@ namespace ScriptCanvas
 
                 case VariableConstructionRequirement::InputNodeable:
                 {
+                    if (variable->m_datum.Empty())
+                    {
+                        AddError(nullptr, aznew Internal::ParseError(AZ::EntityId{}, "Empty nodeable datum in variable, probably due to a problem with azrtti declarations"));
+                        break;
+                    }
+
                     // I solemnly swear no harm shall come to the nodeable
                     const Nodeable* nodeableSource = reinterpret_cast<const Nodeable*>(variable->m_datum.GetAsDanger());
-                    AZ_Assert(nodeableSource != nullptr, "the must be a raw nodeable held by this pointer");
-                    AZ_Assert(azrtti_typeid(nodeableSource) != azrtti_typeid<Nodeable>(), "type problem with nodeable");
+
+                    if (!nodeableSource)
+                    {
+                        AddError(nullptr, aznew Internal::ParseError(AZ::EntityId{}, "No raw nodeable held by variable"));
+                        break;
+                    }
+
                     nodeablesById.push_back({ variable->m_nodeableNodeId, const_cast<Nodeable*>(nodeableSource) });
                 }
                 break;
 
                 case VariableConstructionRequirement::InputVariable:
                 {
-                    inputVariableIds.push_back(variable->m_sourceVariableId);
-                    inputVariablesById.insert({ variable->m_sourceVariableId, variable });
+                    auto variableID = variable->m_sourceVariableId.IsValid() ? variable->m_sourceVariableId : VariableId::MakeVariableId();
+                    inputVariableIds.push_back(variableID);
+                    inputVariablesById.insert({ variableID, variable });
                     // sort revealed a datum copy issue: type is not preserved, workaround below
                     // m_runtimeInputs.m_variables.emplace_back(variable->m_sourceVariableId, variable->m_datum);
                 }
@@ -4310,6 +4337,7 @@ namespace ScriptCanvas
             {
                 if (auto variable = FindVariable(execution->GetNodeId()))
                 {
+                    execution->MarkInputHasThisPointer();
                     execution->AddInput({ nullptr, variable, DebugDataSource::FromInternal() });
                 }
                 else
@@ -4327,6 +4355,7 @@ namespace ScriptCanvas
                 {
                     auto variable = AZStd::make_shared<Variable>();
                     variable->m_datum = Datum(eventHandling->m_handlerName);
+                    execution->MarkInputHasThisPointer();
                     execution->AddInput({ nullptr, variable, DebugDataSource::FromInternal() });
                 }
                 else
@@ -4338,6 +4367,7 @@ namespace ScriptCanvas
             {
                 if (auto variable = FindVariable(execution->GetNodeId()))
                 {
+                    execution->MarkInputHasThisPointer();
                     execution->AddInput({ nullptr, variable, DebugDataSource::FromInternal() });
                 }
                 else
@@ -4367,12 +4397,159 @@ namespace ScriptCanvas
         void AbstractCodeModel::ParseMultiExecutionPost(ExecutionTreePtr execution)
         {
             ParsePropertyExtractionsPost(execution);
+            ParseMultipleFunctionCallPost(execution);
         }
 
         void AbstractCodeModel::ParseMultiExecutionPre(ExecutionTreePtr execution)
         {
             ParsePropertyExtractionsPre(execution);
-        }        
+        }
+
+        void AbstractCodeModel::ParseMultipleFunctionCallPost(ExecutionTreePtr execution)
+        {
+            auto& id = execution->GetId();
+            MultipleFunctionCallFromSingleSlotInfo info = id.m_node->GetMultipleFunctionCallFromSingleSlotInfo(*id.m_slot);
+
+            if (info.functionCalls.empty())
+            {
+                return;
+            }
+
+            auto parent = execution->ModParent();
+
+            if (!parent)
+            {
+                AddError(execution, aznew Internal::ParseError(id.m_node->GetEntityId(), "Null parent in MultipleFunctionCall"));
+                return;
+            }
+
+            size_t indexInParentCall = parent->FindChildIndex(execution);
+            if (indexInParentCall >= parent->GetChildrenCount())
+            {
+                AddError(execution, aznew Internal::ParseError(id.m_node->GetEntityId(), ParseErrors::MultipleFunctionCallFromSingleSlotNoChildren));
+                return;
+            }
+
+            ExecutionChild* executionChildInParent = &parent->ModChild(indexInParentCall);
+            
+            const size_t executionInputCount = execution->GetInputCount();
+            const size_t thisInputOffset = execution->InputHasThisPointer() ? 1 : 0;
+            
+            // the original index has ALL the input from the slots on the node
+            // create multiple calls with separate function call nodes, but ONLY take the inputs required
+            // as indicated by the function call info
+
+            AZStd::unordered_set<const Slot*> usedSlots;
+            bool variadicIsFound = false;
+
+            auto createChild = [&](auto parentCall, ExecutionChild* childInParent, auto& functionCallInfo)
+            {
+                auto child = CreateChild(parentCall, id.m_node, id.m_slot);
+                child->SetSymbol(Symbol::FunctionCall);
+                child->SetName(functionCallInfo.functionName);
+                child->SetNameLexicalScope(functionCallInfo.lexicalScope);
+                childInParent->m_execution = child;
+                return child;
+            };
+
+            auto addThisInput = [&](auto functionCall)
+            {
+                if (thisInputOffset != 0)
+                {
+                    if (executionInputCount == 0)
+                    {
+                        AddError(execution, aznew Internal::ParseError(id.m_node->GetEntityId(), ParseErrors::MultipleFunctionCallFromSingleSlotNotEnoughInputForThis));
+                        return;
+                    }
+
+                    const ExecutionInput& input = execution->GetInput(0);
+                    usedSlots.insert(input.m_slot);
+                    functionCall->AddInput(input);
+                }
+            };
+
+            auto addSlotInput = [&](auto functionCall, size_t inputIndex)
+            {
+                if (inputIndex >= executionInputCount)
+                {
+                    AddError(execution, aznew Internal::ParseError(id.m_node->GetEntityId(), ParseErrors::MultipleFunctionCallFromSingleSlotNotEnoughInput));
+                    return;
+                }
+
+                const ExecutionInput& input = execution->GetInput(inputIndex);
+
+                if (usedSlots.contains(input.m_slot))
+                {
+                    AddError(execution, aznew Internal::ParseError(id.m_node->GetEntityId(), ParseErrors::MultipleFunctionCallFromSingleSlotNotEnoughInput));
+                    return;
+                }
+
+                usedSlots.insert(input.m_slot);
+
+                if (input.m_value->m_source == execution)
+                {
+                    input.m_value->m_source = functionCall;
+                }
+
+                functionCall->AddInput(input);
+            };
+
+            auto addCall = [&](auto& functionCallInfo, auto childInParent, size_t startingIndex, size_t sentinel, size_t variadicOffset = 0)
+            {
+                auto child = createChild(parent, childInParent, functionCallInfo);
+                addThisInput(child);
+
+                for (size_t index = startingIndex; index < sentinel; ++index)
+                {
+                    const size_t inputIndex = index + thisInputOffset + variadicOffset;
+                    addSlotInput(child, inputIndex);
+                }
+
+                child->AddChild({});
+                childInParent = &child->ModChild(0);
+                return AZStd::make_pair(childInParent, child);
+            };
+
+            // loop through each call...
+            for (auto& functionCallInfo : info.functionCalls)
+            {
+                // ...first add any pre-variadic calls, using the starting index and the number of args, since they could come in any order, not input slot order...
+                if (!functionCallInfo.isVariadic)
+                {
+                    AZStd::pair<ExecutionChild*, ExecutionTreePtr> childInParentAndParent = addCall(functionCallInfo, executionChildInParent, functionCallInfo.startingIndex, functionCallInfo.startingIndex + functionCallInfo.numArguments);
+                    executionChildInParent = childInParentAndParent.first;
+                    parent = childInParentAndParent.second;
+                }
+                else
+                {
+                    // ...then add only one variadic call if there is one...
+                    if (variadicIsFound)
+                    {
+                        AddError(execution, aznew Internal::ParseError(id.m_node->GetEntityId(), ParseErrors::MultipleFunctionCallFromSingleSlotMultipleVariadic));
+                        return;
+                    }
+
+                    variadicIsFound = true;
+                    const size_t sentinel = executionInputCount == 0 ? 0 : executionInputCount - thisInputOffset;
+                    // ... by looping through the remaining slots, striding by functionCallInfo.numArguments, making repeated calls to the function
+                    for (size_t slotInputIndex = functionCallInfo.startingIndex; slotInputIndex < sentinel; slotInputIndex += functionCallInfo.numArguments)
+                    {
+                        AZStd::pair<ExecutionChild*, ExecutionTreePtr> childInParentAndParent = addCall(functionCallInfo, executionChildInParent, 0, functionCallInfo.numArguments, slotInputIndex);
+                        executionChildInParent = childInParentAndParent.first;
+                        parent = childInParentAndParent.second;
+                    }
+                }
+            }
+
+            if (info.errorOnUnusedSlot && usedSlots.size() != executionInputCount)
+            {
+                AddError(execution, aznew Internal::ParseError(id.m_node->GetEntityId(), ParseErrors::MultipleFunctionCallFromSingleSlotUnused));
+            }
+
+            // parent now refers to the last child call created
+            parent->SwapChildren(execution);
+            execution->Clear();
+        }
 
         void AbstractCodeModel::ParseNodelingVariables(const Node& node, NodelingType nodelingType)
         {
@@ -5146,6 +5323,6 @@ namespace ScriptCanvas
             return type == Data::eType::BehaviorContextObject;
         }
 
-    } 
+    }
 
-} 
+}

+ 2 - 0
Gems/ScriptCanvas/Code/Include/ScriptCanvas/Grammar/AbstractCodeModel.h

@@ -387,6 +387,8 @@ namespace ScriptCanvas
 
             void ParseMultiExecutionPre(ExecutionTreePtr execution);
 
+            void ParseMultipleFunctionCallPost(ExecutionTreePtr execution);
+
             void ParseNodelingVariables(const Node& node, NodelingType nodelingType);
 
             void ParseOperatorArithmetic(ExecutionTreePtr execution);

+ 4 - 4
Gems/ScriptCanvas/Code/Include/ScriptCanvas/Grammar/ParsingUtilities.cpp

@@ -1132,13 +1132,13 @@ namespace ScriptCanvas
             }
             else if (variable->m_isExposedToConstruction)
             {
-                if (variable->m_sourceVariableId.IsValid())
+                if (variable->m_nodeableNodeId.IsValid())
                 {
-                    return VariableConstructionRequirement::InputVariable;
+                    return VariableConstructionRequirement::InputNodeable;
                 }
-                else if (variable->m_nodeableNodeId.IsValid())
+                else if (variable->m_sourceVariableId.IsValid())
                 {
-                    return VariableConstructionRequirement::InputNodeable;
+                    return VariableConstructionRequirement::InputVariable;
                 }
                 else
                 {

+ 28 - 0
Gems/ScriptCanvas/Code/Include/ScriptCanvas/Grammar/Primitives.h

@@ -164,6 +164,34 @@ namespace ScriptCanvas
             virtual void PostParseExecutionTreeBody(AbstractCodeModel& /*model*/, ExecutionTreePtr /*execution*/) {}
         };
 
+        // for now, no return values supported
+        struct MultipleFunctionCallFromSingleSlotEntry
+        {
+            AZ_TYPE_INFO(MultipleFunctionCallFromSingleSlotEntry, "{360A23A3-C490-4047-B71E-64E290E441D3}");
+            AZ_CLASS_ALLOCATOR(MultipleFunctionCallFromSingleSlotEntry, AZ::SystemAllocator, 0);
+
+            bool isVariadic = false;
+            AZStd::string functionName;
+            LexicalScope lexicalScope;
+            size_t numArguments = 0; // stride in case isVariadic == true
+            size_t startingIndex = 0; // the index of the slot order
+        };
+
+        // for now, no return values supported
+        struct MultipleFunctionCallFromSingleSlotInfo
+        {
+            AZ_TYPE_INFO(MultipleFunctionCallFromSingleSlotInfo, "{DF51F08A-8B28-4851-9888-9AB7CC0B90D2}");
+            AZ_CLASS_ALLOCATOR(MultipleFunctionCallFromSingleSlotInfo, AZ::SystemAllocator, 0);
+
+            // this could likely be implemented, but needs care to duplicate input that the execution-slot created
+            // bool errorOnReusedSlot = false;
+
+            bool errorOnUnusedSlot = false;
+
+            // calls are executed in the order they arrive in the vector
+            AZStd::vector<MultipleFunctionCallFromSingleSlotEntry> functionCalls;
+        };
+
         struct NodeableParse
             : public AZStd::enable_shared_from_this<NodeableParse>
         {

+ 37 - 1
Gems/ScriptCanvas/Code/Include/ScriptCanvas/Grammar/PrimitivesExecution.cpp

@@ -313,6 +313,11 @@ namespace ScriptCanvas
             return !m_returnValues.empty();
         }
 
+        bool ExecutionTree::InputHasThisPointer() const
+        {
+            return m_inputHasThisPointer;
+        }
+
         bool ExecutionTree::IsInfiniteLoopDetectionPoint() const
         {
             return m_isInfiniteLoopDetectionPoint;
@@ -377,6 +382,11 @@ namespace ScriptCanvas
             m_isInfiniteLoopDetectionPoint = true;
         }
 
+        void ExecutionTree::MarkInputHasThisPointer()
+        {
+            m_inputHasThisPointer = true;
+        }
+
         void ExecutionTree::MarkInputOutputPreprocessed()
         {
             m_isInputOutputPreprocessed = true;
@@ -587,6 +597,32 @@ namespace ScriptCanvas
             m_symbol = val;
         }
 
-    } 
+        void ExecutionTree::SwapChildren(ExecutionTreePtr execution)
+        {
+            if (execution)
+            {
+                m_children.swap(execution->m_children);
+
+                for (auto& child : m_children)
+                {
+                    if (child.m_execution)
+                    {
+                        child.m_execution->SetParent(shared_from_this());
+                    }
+                }
 
+                for (auto& orphan : execution->m_children)
+                {
+                    if (orphan.m_execution)
+                    {
+                        orphan.m_execution->SetParent(execution);
+                    }
+                }
+            }
+            else
+            {
+                ClearChildren();
+            }
+        }
+    }
 } 

+ 8 - 0
Gems/ScriptCanvas/Code/Include/ScriptCanvas/Grammar/PrimitivesExecution.h

@@ -190,6 +190,8 @@ namespace ScriptCanvas
 
             bool HasReturnValues() const;
 
+            bool InputHasThisPointer() const;
+
             bool IsInfiniteLoopDetectionPoint() const;
 
             void InsertChild(size_t index, const ExecutionChild& child);
@@ -208,6 +210,8 @@ namespace ScriptCanvas
 
             void MarkInfiniteLoopDetectionPoint();
 
+            void MarkInputHasThisPointer();
+
             void MarkInputOutputPreprocessed();
 
             void MarkInternalOut();
@@ -262,6 +266,8 @@ namespace ScriptCanvas
 
             void SetSymbol(Symbol val);
 
+            void SwapChildren(ExecutionTreePtr execution);
+
         private:
             // the (possible) slot(s) through which execution exited, along with associated output
             AZStd::vector<ExecutionChild> m_children;
@@ -275,6 +281,8 @@ namespace ScriptCanvas
 
             bool m_isInfiniteLoopDetectionPoint = false;
 
+            bool m_inputHasThisPointer = false;
+
             bool m_isInputOutputPreprocessed = false;
 
             bool m_isInternalOut = false;

+ 6 - 0
Gems/ScriptCanvas/Code/Include/ScriptCanvas/Results/ErrorText.h

@@ -57,6 +57,12 @@ namespace ScriptCanvas
         constexpr const char* MissingVariableForEBusHandlerAddress = "missing variable for ebus handler address";
         constexpr const char* MissingVariableForEBusHandlerAddressConnected = "missing variable for ebus handler address";
         constexpr const char* MultipleExecutionOutConnections = "This node has multiple, unordered execution Out connections";
+        constexpr const char* MultipleFunctionCallFromSingleSlotMultipleVariadic = "Only one variadic call (the last one) is supported in the multi-call per single slot.";
+        constexpr const char* MultipleFunctionCallFromSingleSlotNoChildren = "Node missing from parent children.";
+        constexpr const char* MultipleFunctionCallFromSingleSlotNotEnoughInput = "Not enough input to support multi call input information.";
+        constexpr const char* MultipleFunctionCallFromSingleSlotNotEnoughInputForThis = "Node doesn't have enough input for a parsed this pointer.";
+        constexpr const char* MultipleFunctionCallFromSingleSlotReused = "Multiple function slot reused an input slot";
+        constexpr const char* MultipleFunctionCallFromSingleSlotUnused = "Multiple function slot left an input slot unused.";
         constexpr const char* MultipleSimulaneousInputValues = "Multiple values routed to the same single input with no way to discern which to take.";
         constexpr const char* MultipleStartNodes = "Multiple Start nodes in a single graph. Only one is allowed.";
         constexpr const char* NoChildrenAfterRoot = "No children after parsing function root";

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 183 - 208
Gems/ScriptCanvasTesting/Assets/ScriptCanvas/UnitTests/LY_SC_UnitTest_RunAllTransformNodes.scriptcanvas


+ 64 - 33
scripts/o3de/o3de/manifest.py

@@ -310,64 +310,95 @@ def get_project_external_subdirectories(project_path: pathlib.Path) -> list:
                project_object['external_subdirectories'])) if 'external_subdirectories' in project_object else []
 
 
+def get_project_templates(project_path: pathlib.Path) -> list:
+    project_object = get_project_json_data(project_path=project_path)
+    return list(map(lambda rel_path: (pathlib.Path(project_path) / rel_path).as_posix(),
+                      project_object['templates']))
+
+
+def get_project_restricted(project_path: pathlib.Path) -> list:
+    project_object = get_project_json_data(project_path=project_path)
+    return list(map(lambda rel_path: (pathlib.Path(project_path) / rel_path).as_posix(),
+                    project_object['restricted'])) if 'restricted' in project_object else []
+
+
 # Combined manifest queries
 def get_all_projects() -> list:
-    projects_data = set(get_projects())
-    projects_data.update(get_engine_projects())
-    return list(projects_data)
+    projects_data = get_projects()
+    projects_data.extend(get_engine_projects())
+    # Remove duplicates from the list
+    return list(dict.fromkeys(projects_data))
 
 
 def get_all_gems(project_path: pathlib.Path = None) -> list:
-    gems_data = set(get_gems())
-    gems_data.update(get_engine_gems())
+    gems_data = get_gems()
+    gems_data.extend(get_engine_gems())
     if project_path:
-        gems_data.update(get_project_gems(project_path))
-    return list(gems_data)
+        gems_data.extend(get_project_gems(project_path))
+    return list(dict.fromkeys(gems_data))
 
 
 def get_all_external_subdirectories(project_path: pathlib.Path = None) -> list:
-    external_subdirectories_data = set(get_external_subdirectories())
-    external_subdirectories_data.update(get_engine_external_subdirectories())
+    external_subdirectories_data = get_external_subdirectories()
+    external_subdirectories_data.extend(get_engine_external_subdirectories())
     if project_path:
-        external_subdirectories_data.update(get_project_external_subdirectories(project_path))
-    return list(templates_data)
+        external_subdirectories_data.extend(get_project_external_subdirectories(project_path))
+    return list(dict.fromkeys(external_subdirectories_data))
 
 
-def get_all_templates() -> list:
-    templates_data = set(get_templates())
-    templates_data.update(get_engine_templates())
-    return list(templates_data)
+def get_all_templates(project_path: pathlib.Path = None) -> list:
+    templates_data = get_templates()
+    templates_data.extend(get_engine_templates())
+    if project_path:
+        templates_data.extend(get_project_templates(project_path))
+    return list(dict.fromkeys(templates_data))
 
 
 def get_all_restricted() -> list:
-    restricted_data = set(get_restricted())
-    restricted_data.update(get_engine_restricted())
-    return list(gems_data)
+    restricted_data = get_restricted()
+    restricted_data.extend(get_engine_restricted())
+    if project_path:
+        restricted_data.extend(get_project_restricted(project_path))
+    return list(dict.fromkeys(restricted_data))
 
 
 # Template functions
-def get_project_templates():  # temporary until we have a better way to do this... maybe template_type element
+def get_templates_for_project_creation():
     project_templates = []
-    for template in get_all_templates():
-        if 'Project' in template:
-            project_templates.append(template)
+    for template_path in get_all_templates():
+        template_path = pathlib.Path(template_path)
+        template_json_path = pathlib.Path(template_path) / 'template.json'
+        if not validation.valid_o3de_template_json(template_json_path):
+            continue
+
+        project_json_path = template_path / 'Template' / 'project.json'
+        if validation.valid_o3de_project_json(project_json_path):
+            project_templates.append(template_path)
     return project_templates
 
 
-def get_gem_templates():  # temporary until we have a better way to do this... maybe template_type element
+def get_templates_for_gem_creation():
     gem_templates = []
-    for template in get_all_templates():
-        if 'Gem' in template:
-            gem_templates.append(template)
+    for template_path in get_all_templates():
+        template_path = pathlib.Path(template_path)
+        template_json_path = pathlib.Path(template_path) / 'template.json'
+        if not validation.valid_o3de_template_json(template_json_path):
+            continue
+
+        gem_json_path = template_path / 'Template' / 'gem.json'
+        if validation.valid_o3de_gem_json(gem_json_path):
+            gem_templates.append(template_path)
     return gem_templates
 
 
-def get_generic_templates():  # temporary until we have a better way to do this... maybe template_type element
-    generic_templates = []
-    for template in get_all_templates():
-        if 'Project' not in template and  'Gem' not in template:
-            generic_templates.append(template)
-    return generic_templates
+def get_templates_for_generic_creation():  # temporary until we have a better way to do this... maybe template_type element
+    def filter_project_and_gem_templates_out(template_path,
+                                             templates_for_project_creation = get_templates_for_project_creation(),
+                                             templates_for_gem_creation = get_templates_for_gem_creation()):
+        template_path = pathlib.Path(template_path)
+        return template_path not in templates_for_project_creation and template_path not in templates_for_gem_creation
+
+    return list(filter(filter_project_and_gem_templates_out, get_all_templates()))
 
 
 def get_all_restricted() -> list:
@@ -523,7 +554,7 @@ def get_template_json_data(template_name: str = None,
     return None
 
 
-def get_restricted_data(restricted_name: str = None,
+def get_restricted_json_data(restricted_name: str = None,
                         restricted_path: str or pathlib.Path = None) -> dict or None:
     if not restricted_name and not restricted_path:
         logger.error('Must specify either a Restricted name or Restricted Path.')

+ 248 - 242
scripts/o3de/o3de/print_registration.py

@@ -13,6 +13,7 @@ import argparse
 import json
 import hashlib
 import logging
+import pathlib
 import sys
 import urllib.parse
 
@@ -21,219 +22,251 @@ from o3de import manifest, validation
 logger = logging.getLogger()
 logging.basicConfig()
 
-def print_this_engine(verbose: int) -> None:
+
+def get_project_path(project_path: pathlib.Path, project_name: str) -> pathlib.Path:
+    if not project_name and not project_path:
+        logger.error(f'Must either specify a Project path or Project Name.')
+        return None
+
+    if not project_path:
+        project_path = manifest.get_registered(project_name=project_name)
+    if not project_path:
+        logger.error(f'Unable to locate project path from the registered manifest json files:'
+                     f' {str(pathlib.Path("~/.o3de/o3de_manifest.json").expanduser())}, engine.json')
+        return None
+
+    if not project_path.is_dir():
+        logger.error(f'Project path {project_path} is not a folder.')
+        return None
+
+    return project_path
+
+
+def print_this_engine(verbose: int) -> int:
     engine_data = manifest.get_this_engine()
     print(json.dumps(engine_data, indent=4))
+    result = True
     if verbose > 0:
-        print_engines_data(engine_data)
+        result = print_manifest_json_data(engine_data, 'engine.json', 'This Engine',
+                                          manifest.get_engine_json_data, 'engine_path')
+    return 0 if result else 1
 
 
 def print_engines(verbose: int) -> None:
     engines_data = manifest.get_engines()
     print(json.dumps(engines_data, indent=4))
+
     if verbose > 0:
-        print_engines_data(engines_data)
+        return print_manifest_json_data(engines_data, 'engine.json', 'Engines',
+                                          manifest.get_engine_json_data, 'engine_path')
+    return 0
 
 
-def print_projects(verbose: int) -> None:
+def print_projects(verbose: int) -> int:
     projects_data = manifest.get_projects()
     print(json.dumps(projects_data, indent=4))
+
     if verbose > 0:
-        print_projects_data(projects_data)
+        return print_manifest_json_data(projects_data, 'project.json', 'Projects',
+                                          manifest.get_project_json_data, 'project_path')
+    return 0
 
 
-def print_gems(verbose: int) -> None:
+def print_gems(verbose: int) -> int:
     gems_data = manifest.get_gems()
     print(json.dumps(gems_data, indent=4))
+
     if verbose > 0:
-        print_gems_data(gems_data)
+        return print_manifest_json_data(gems_data, 'gem.json', 'Gems',
+                                          manifest.get_gem_json_data, 'gem_path')
+    return 0
 
 
-def print_templates(verbose: int) -> None:
+def print_templates(verbose: int) -> int:
     templates_data = manifest.get_templates()
     print(json.dumps(templates_data, indent=4))
+
     if verbose > 0:
-        print_templates_data(templates_data)
+        return print_manifest_json_data(templates_data, 'template.json', 'Templates',
+                                          manifest.get_template_json_data, 'template_path')
+    return 0
 
 
-def print_restricted(verbose: int) -> None:
+def print_restricted(verbose: int) -> int:
     restricted_data = manifest.get_restricted()
     print(json.dumps(restricted_data, indent=4))
+
     if verbose > 0:
-        print_restricted_data(restricted_data)
+        return print_manifest_json_data(restricted_data, 'restricted.json', 'Restricted',
+                                          manifest.get_restricted_json_data, 'restricted_path')
+    return 0
+
 
-def print_engine_projects(verbose: int) -> None:
+# Engine output methods
+def print_engine_projects(verbose: int) -> int:
     engine_projects_data = manifest.get_engine_projects()
     print(json.dumps(engine_projects_data, indent=4))
+
     if verbose > 0:
-        print_projects_data(engine_projects_data)
+        return print_manifest_json_data(engine_projects_data, 'project.json', 'Projects',
+                                          manifest.get_project_json_data, 'project_path')
+    return 0
 
 
-def print_engine_gems(verbose: int) -> None:
+def print_engine_gems(verbose: int) -> int:
     engine_gems_data = manifest.get_engine_gems()
     print(json.dumps(engine_gems_data, indent=4))
+
     if verbose > 0:
-        print_gems_data(engine_gems_data)
+        return print_manifest_json_data(engine_gems_data, 'gem.json', 'Gems',
+                                          manifest.get_gem_json_data, 'gem_path')
+    return 0
 
 
-def print_engine_templates(verbose: int) -> None:
+def print_engine_templates(verbose: int) -> int:
     engine_templates_data = manifest.get_engine_templates()
     print(json.dumps(engine_templates_data, indent=4))
+
     if verbose > 0:
-        print_templates_data(engine_templates_data)
+        return print_manifest_json_data(engine_templates_data, 'template.json', 'Templates',
+                                          manifest.get_template_json_data, 'template_path')
+    return 0
 
 
-def print_engine_restricted(verbose: int) -> None:
+def print_engine_restricted(verbose: int) -> int:
     engine_restricted_data = manifest.get_engine_restricted()
     print(json.dumps(engine_restricted_data, indent=4))
+
     if verbose > 0:
-        print_restricted_data(engine_restricted_data)
+        return print_manifest_json_data(engine_restricted_data, 'restricted.json', 'Restricted',
+                                          manifest.get_restricted_json_data, 'restricted_path')
+    return 0
 
 
-def print_engine_external_subdirectories(verbose: int) -> None:
+def print_engine_external_subdirectories() -> int:
     external_subdirs_data = manifest.get_engine_external_subdirectories()
     print(json.dumps(external_subdirs_data, indent=4))
+    return 0
+
+
+# Project output methods
+def print_project_gems(verbose: int, project_path: pathlib.Path, project_name: str) -> int:
+    project_path = get_project_path(project_path, project_name)
+    if not project_path:
+        return 1
+
+    project_gems_data = manifest.get_project_gems(project_path)
+    print(json.dumps(project_gems_data, indent=4))
+
+    if verbose > 0:
+        return print_manifest_json_data(project_gems_data, 'gem.json', 'Gems',
+                                          manifest.get_gem_json_data, 'gem_path')
+    return 0
+
+
+def print_project_external_subdirectories(project_path: pathlib.Path, project_name: str) -> int:
+    project_path = get_project_path(project_path, project_name)
+    if not project_path:
+        return 1
+
+    external_subdirs_data = manifest.get_project_external_subdirectories(project_path)
+    print(json.dumps(external_subdirs_data, indent=4))
+    return 0
+
+
+def print_project_templates(verbose: int, project_path: pathlib.Path, project_name: str) -> int:
+    project_path = get_project_path(project_path, project_name)
+    if not project_path:
+        return 1
+
+    project_templates_data = manifest.get_project_templates(project_path)
+    print(json.dumps(project_templates_data, indent=4))
+    if verbose > 0:
+        return print_manifest_json_data(project_templates_data, 'template.json', 'Templates',
+                                          manifest.get_template_json_data, 'template_path')
+    return 0
 
 
-def print_all_projects(verbose: int) -> None:
+def print_project_restricted(verbose: int, project_path: pathlib.Path, project_name: str) -> int:
+    project_path = get_project_path(project_path, project_name)
+    if not project_path:
+        return 1
+
+    project_restricted_data = manifest.get_project_restricted(project_path)
+    print(json.dumps(project_restricted_data, indent=4))
+    if verbose > 0:
+        return print_manifest_json_data(project_restricted_data, 'restricted.json', 'Restricted',
+                                          manifest.get_restricted_json_data, 'restricted_path')
+    return 0
+
+
+def print_all_projects(verbose: int) -> int:
     all_projects_data = manifest.get_all_projects()
     print(json.dumps(all_projects_data, indent=4))
+
     if verbose > 0:
-        print_projects_data(all_projects_data)
+        return print_manifest_json_data(all_projects_data, 'project.json', 'Projects',
+                                 manifest.get_project_json_data, 'project_path')
+    return 0
 
 
-def print_all_gems(verbose: int) -> None:
+def print_all_gems(verbose: int) -> int:
     all_gems_data = manifest.get_all_gems()
     print(json.dumps(all_gems_data, indent=4))
+
     if verbose > 0:
-        print_gems_data(all_gems_data)
+        return print_manifest_json_data(all_gems_data, 'gem.json', 'Gems',
+                                          manifest.get_gem_json_data, 'gem_path')
+    return 0
+
 
+def print_all_external_subdirectories() -> int:
+    all_external_subdirectories_data = manifest.get_all_external_subdirectories()
+    print(json.dumps(all_external_subdirectories_data, indent=4))
+    return 0
 
-def print_all_templates(verbose: int) -> None:
+def print_all_templates(verbose: int) -> int:
     all_templates_data = manifest.get_all_templates()
     print(json.dumps(all_templates_data, indent=4))
+
     if verbose > 0:
-        print_templates_data(all_templates_data)
+        return print_manifest_json_data(all_templates_data, 'template.json', 'Templates',
+                                 manifest.get_template_json_data, 'template_path')
+    return 0
 
 
-def print_all_restricted(verbose: int) -> None:
+def print_all_restricted(verbose: int) -> int:
     all_restricted_data = manifest.get_all_restricted()
     print(json.dumps(all_restricted_data, indent=4))
-    if verbose > 0:
-        print_restricted_data(all_restricted_data)
 
-
-def print_engines_data(engines_data: dict) -> None:
-    print('\n')
-    print("Engines================================================")
-    for engine_object in engines_data:
-        # if it's not local it should be in the cache
-        engine_uri = engine_object['path']
-        parsed_uri = urllib.parse.urlparse(engine_uri)
-        if parsed_uri.scheme == 'http' or \
-                parsed_uri.scheme == 'https' or \
-                parsed_uri.scheme == 'ftp' or \
-                parsed_uri.scheme == 'ftps':
-            repo_sha256 = hashlib.sha256(engine_uri.encode())
-            cache_folder = manifest.get_o3de_cache_folder()
-            engine = cache_folder / str(repo_sha256.hexdigest() + '.json')
-            print(f'{engine_uri}/engine.json cached as:')
-        else:
-            engine_json = pathlib.Path(engine_uri).resolve() / 'engine.json'
-
-        with engine_json.open('r') as f:
-            try:
-                engine_json_data = json.load(f)
-            except json.JSONDecodeError as e:
-                logger.warn(f'{engine_json} failed to load: {str(e)}')
-            else:
-                print(engine_json)
-                print(json.dumps(engine_json_data, indent=4))
-        print('\n')
-
-
-def print_projects_data(projects_data: dict) -> None:
-    print('\n')
-    print("Projects================================================")
-    for project_uri in projects_data:
-        # if it's not local it should be in the cache
-        parsed_uri = urllib.parse.urlparse(project_uri)
-        if parsed_uri.scheme == 'http' or \
-                parsed_uri.scheme == 'https' or \
-                parsed_uri.scheme == 'ftp' or \
-                parsed_uri.scheme == 'ftps':
-            repo_sha256 = hashlib.sha256(project_uri.encode())
-            cache_folder = manifest.get_o3de_cache_folder()
-            project_json = cache_folder / str(repo_sha256.hexdigest() + '.json')
-        else:
-            project_json = pathlib.Path(project_uri).resolve() / 'project.json'
-
-        with project_json.open('r') as f:
-            try:
-                project_json_data = json.load(f)
-            except json.JSONDecodeError as e:
-                logger.warn(f'{project_json} failed to load: {str(e)}')
-            else:
-                print(project_json)
-                print(json.dumps(project_json_data, indent=4))
-        print('\n')
+    if verbose > 0:
+        return print_manifest_json_data(all_restricted_data, 'restricted.json', 'Restricted',
+                                 manifest.get_restricted_json_data, 'restricted_path')
+    return 0
 
 
-def print_gems_data(gems_data: dict) -> None:
+def print_manifest_json_data(uri_json_data: dict, json_filename: str,
+                             print_prefix: str, get_json_func: callable, get_json_data_kw: str) -> int:
     print('\n')
-    print("Gems================================================")
-    for gem_uri in gems_data:
+    print(f"{print_prefix}================================================")
+    for manifest_uri in uri_json_data:
         # if it's not local it should be in the cache
-        parsed_uri = urllib.parse.urlparse(gem_uri)
-        if parsed_uri.scheme == 'http' or \
-                parsed_uri.scheme == 'https' or \
-                parsed_uri.scheme == 'ftp' or \
-                parsed_uri.scheme == 'ftps':
-            repo_sha256 = hashlib.sha256(gem_uri.encode())
+        parsed_uri = urllib.parse.urlparse(manifest_uri)
+        if parsed_uri.scheme in ['http', 'https', 'ftp', 'ftps']:
+            repo_sha256 = hashlib.sha256(manifest_uri.encode())
             cache_folder = manifest.get_o3de_cache_folder()
-            gem_json = cache_folder / str(repo_sha256.hexdigest() + '.json')
+            manifest_json_path = cache_folder / str(repo_sha256.hexdigest() + '.json')
         else:
-            gem_json = pathlib.Path(gem_uri).resolve() / 'gem.json'
-
-        with gem_json.open('r') as f:
-            try:
-                gem_json_data = json.load(f)
-            except json.JSONDecodeError as e:
-                logger.warn(f'{gem_json} failed to load: {str(e)}')
-            else:
-                print(gem_json)
-                print(json.dumps(gem_json_data, indent=4))
-        print('\n')
+            manifest_json_path = pathlib.Path(manifest_uri).resolve() / json_filename
 
-
-def print_templates_data(templates_data: dict) -> None:
-    print('\n')
-    print("Templates================================================")
-    for template_uri in templates_data:
-        # if it's not local it should be in the cache
-        parsed_uri = urllib.parse.urlparse(template_uri)
-        if parsed_uri.scheme == 'http' or \
-                parsed_uri.scheme == 'https' or \
-                parsed_uri.scheme == 'ftp' or \
-                parsed_uri.scheme == 'ftps':
-            repo_sha256 = hashlib.sha256(template_uri.encode())
-            cache_folder = manifest.get_o3de_cache_folder()
-            template_json = cache_folder / str(repo_sha256.hexdigest() + '.json')
-        else:
-            template_json = pathlib.Path(template_uri).resolve() / 'template.json'
-
-        with template_json.open('r') as f:
-            try:
-                template_json_data = json.load(f)
-            except json.JSONDecodeError as e:
-                logger.warn(f'{template_json} failed to load: {str(e)}')
-            else:
-                print(template_json)
-                print(json.dumps(template_json_data, indent=4))
-        print('\n')
+        json_data = get_json_func(**{get_json_data_kwargs: manifest_json_path})
+        if json_data:
+            print(manifest_json_path)
+            print(json.dumps(json_data, indent=4) + '\n')
+    return 0
 
 
-def print_repos_data(repos_data: dict) -> None:
+def print_repos_data(repos_data: dict) -> int:
     print('\n')
     print("Repos================================================")
     cache_folder = manifest.get_o3de_cache_folder()
@@ -251,29 +284,16 @@ def print_repos_data(repos_data: dict) -> None:
                     print(cache_file)
                     print(json.dumps(repo_json_data, indent=4))
         print('\n')
-
-
-def print_restricted_data(restricted_data: dict) -> None:
-    print('\n')
-    print("Restricted================================================")
-    for restricted_path in restricted_data:
-        restricted_json = pathlib.Path(restricted_path).resolve() / 'restricted.json'
-        with restricted_json.open('r') as f:
-            try:
-                restricted_json_data = json.load(f)
-            except json.JSONDecodeError as e:
-                logger.warn(f'{restricted_json} failed to load: {str(e)}')
-            else:
-                print(restricted_json)
-                print(json.dumps(restricted_json_data, indent=4))
-        print('\n')
+    return 0
 
 
 def register_show_repos(verbose: int) -> None:
-    repos_data = get_repos()
+    repos_data = manifest.get_repos()
     print(json.dumps(repos_data, indent=4))
+
     if verbose > 0:
-        print_repos_data(repos_data)
+        return print_repos_data(repos_data) == 0
+    return 0
 
 
 def register_show(verbose: int) -> None:
@@ -281,13 +301,15 @@ def register_show(verbose: int) -> None:
     print(f"{manifest.get_o3de_manifest()}:")
     print(json.dumps(json_data, indent=4))
 
+    result = True
     if verbose > 0:
-        print_engines_data(manifest.get_engines())
-        print_projects_data(manifest.get_all_projects())
-        print_gems_data(manifest.get_gems())
-        print_templates_data(manifest.get_all_templates())
-        print_restricted_data(manifest.get_all_restricted())
-        print_repos_data(manifest.get_repos())
+        result = print_manifest_json_data(manifest.get_engines()) == 0 and result
+        result = print_manifest_json_data(manifest.get_all_projects()) == 0 and result
+        result = print_manifest_json_data(manifest.get_gems()) == 0 and result
+        result = print_manifest_json_data(manifest.get_all_templates()) == 0 and result
+        result = print_manifest_json_data(manifest.get_all_restricted()) == 0 and result
+        result = print_repos_data(manifest.get_repos()) == 0 and result
+    return 0 if result else 1
 
 
 def _run_register_show(args: argparse) -> int:
@@ -295,75 +317,53 @@ def _run_register_show(args: argparse) -> int:
         manifest.override_home_folder = args.override_home_folder
 
     if args.this_engine:
-        print_this_engine(args.verbose)
-        return 0
-
+        return print_this_engine(args.verbose)
     elif args.engines:
-        print_engines(args.verbose)
-        return 0
+        return print_engines(args.verbose)
     elif args.projects:
-        print_projects(args.verbose)
-        return 0
+        return print_projects(args.verbose)
     elif args.gems:
-        print_gems(args.verbose)
-        return 0
+        return print_gems(args.verbose)
     elif args.templates:
-        print_templates(args.verbose)
-        return 0
+        return print_templates(args.verbose)
     elif args.repos:
-        register_show_repos(args.verbose)
-        return 0
+        return register_show_repos(args.verbose)
     elif args.restricted:
-        print_restricted(args.verbose)
-        return 0
+        return print_restricted(args.verbose)
 
     elif args.engine_projects:
-        print_engine_projects(args.verbose)
-        return 0
+        return print_engine_projects(args.verbose)
     elif args.engine_gems:
-        print_engine_gems(args.verbose)
-        return 0
+        return print_engine_gems(args.verbose)
+    elif args.engine_external_subdirectories:
+        return print_engine_external_subdirectories()
     elif args.engine_templates:
-        print_engine_templates(args.verbose)
-        return 0
+        return print_engine_templates(args.verbose)
     elif args.engine_restricted:
-        print_engine_restricted(args.verbose)
-        return 0
-    elif args.engine_external_subdirectories:
-        print_engine_external_subdirectories(args.verbose)
-        return 0
+        return print_engine_restricted(args.verbose)
+
+    elif args.project_gems:
+        return print_project_gems(args.verbose, args.project_path, args.project_name)
+    elif args.project_external_subdirectories:
+        return print_project_external_subdirectories(args.project_path, args.project_name)
+    elif args.project_templates:
+        return print_project_templates(args.verbose, args.project_path, args.project_name)
+    elif args.project_restricted:
+        return print_project_restricted(args.verbose, args.project_path, args.project_name)
 
     elif args.all_projects:
-        print_all_projects(args.verbose)
-        return 0
+        return print_all_projects(args.verbose)
     elif args.all_gems:
-        print_all_gems(args.verbose)
-        return 0
+        return print_all_gems(args.verbose)
+    elif args.all_external_subdirectories:
+        return print_all_external_subdirectories()
     elif args.all_templates:
-        print_all_templates(args.verbose)
-        return 0
+        return print_all_templates(args.verbose)
     elif args.all_restricted:
-        print_all_restricted(args.verbose)
-        return 0
-
-    elif args.downloadables:
-        print_downloadables(args.verbose)
-        return 0
-    if args.downloadable_engines:
-        print_downloadable_engines(args.verbose)
-        return 0
-    elif args.downloadable_projects:
-        print_downloadable_projects(args.verbose)
-        return 0
-    elif args.downloadable_gems:
-        print_downloadable_gems(args.verbose)
-        return 0
-    elif args.downloadable_templates:
-        print_downloadable_templates(args.verbose)
-        return 0
+        return print_all_restricted(args.verbose)
+
     else:
-        register_show(args.verbose)
-        return 0
+        return register_show(args.verbose)
 
 
 def add_parser_args(parser):
@@ -376,78 +376,84 @@ def add_parser_args(parser):
     group = parser.add_mutually_exclusive_group(required=False)
     group.add_argument('-te', '--this-engine', action='store_true', required=False,
                        default=False,
-                       help='Just the local engines.')
+                       help='Output the current engine path.')
 
     group.add_argument('-e', '--engines', action='store_true', required=False,
                        default=False,
-                       help='Just the local engines.')
+                       help='Output the engines registered in the global ~/.o3de/o3de_manifest.json.')
     group.add_argument('-p', '--projects', action='store_true', required=False,
                        default=False,
-                       help='Just the local projects.')
+                       help='Output the projects registered in the global ~/.o3de/o3de_manifest.json.')
     group.add_argument('-g', '--gems', action='store_true', required=False,
                        default=False,
-                       help='Just the local gems.')
+                       help='Output the gems registered in the global ~/.o3de/o3de_manifest.json.')
     group.add_argument('-t', '--templates', action='store_true', required=False,
                        default=False,
-                       help='Just the local templates.')
+                       help='Output the templates registered in the global ~/.o3de/o3de_manifest.json.')
     group.add_argument('-r', '--repos', action='store_true', required=False,
                        default=False,
-                       help='Just the local repos. Ignores repos.')
+                       help='Output the repos registered in the global ~/.o3de/o3de_manifest.json. Ignores repos.')
     group.add_argument('-rs', '--restricted', action='store_true', required=False,
                        default=False,
-                       help='The local restricted folders.')
+                       help='Output the restricted directories registered in the global ~/.o3de/o3de_manifest.json.')
 
     group.add_argument('-ep', '--engine-projects', action='store_true', required=False,
                        default=False,
-                       help='Just the local projects. Ignores repos.')
+                       help='Output the projects registered in the current engine engine.json. Ignores repos.')
     group.add_argument('-eg', '--engine-gems', action='store_true', required=False,
                        default=False,
-                       help='Just the local gems. Ignores repos')
+                       help='Output the gems registered in the current engine engine.json. Ignores repos')
     group.add_argument('-et', '--engine-templates', action='store_true', required=False,
                        default=False,
-                       help='Just the local templates. Ignores repos.')
+                       help='Output the templates registered in the current engine engine.json. Ignores repos.')
     group.add_argument('-ers', '--engine-restricted', action='store_true', required=False,
                        default=False,
-                       help='The restricted folders.')
-    group.add_argument('-x', '--engine-external-subdirectories', action='store_true', required=False,
+                       help='Output the restricted directories registered in the current engine engine.json.')
+    group.add_argument('-ees', '--engine-external-subdirectories', action='store_true', required=False,
                        default=False,
-                       help='The external subdirectories.')
+                       help='Output the external subdirectories registered in the current engine engine.json.')
 
-    group.add_argument('-ap', '--all-projects', action='store_true', required=False,
+    group.add_argument('-pg', '--project-gems', action='store_true',
                        default=False,
-                       help='Just the local projects. Ignores repos.')
-    group.add_argument('-ag', '--all-gems', action='store_true', required=False,
+                       help='Returns the gems registered with the project.json.')
+    group.add_argument('-pt', '--project-templates', action='store_true',
                        default=False,
-                       help='Just the local gems. Ignores repos')
-    group.add_argument('-at', '--all-templates', action='store_true', required=False,
+                       help='Returns the templates registered with the project.json.')
+    group.add_argument('-prs', '--project-restricted', action='store_true',
                        default=False,
-                       help='Just the local templates. Ignores repos.')
-    group.add_argument('-ars', '--all-restricted', action='store_true', required=False,
+                       help='Returns the restricted directories registered with the project.json.')
+    group.add_argument('-pes', '--project-external-subdirectories', action='store_true',
                        default=False,
-                       help='The restricted folders.')
+                       help='Returns the external subdirectories register with the project.json.')
 
-    group.add_argument('-d', '--downloadables', action='store_true', required=False,
+    group.add_argument('-ap', '--all-projects', action='store_true', required=False,
                        default=False,
-                       help='Combine all repos into a single list of resources.')
-    group.add_argument('-de', '--downloadable-engines', action='store_true', required=False,
+                       help='Output all projects registered in the ~/.o3de/o3de_manifest.json and the current engine.json. Ignores repos.')
+    group.add_argument('-ag', '--all-gems', action='store_true', required=False,
                        default=False,
-                       help='Combine all repos engines into a single list of resources.')
-    group.add_argument('-dp', '--downloadable-projects', action='store_true', required=False,
+                       help='Output all gems registered in the ~/.o3de/o3de_manifest.json and the current engine.json. Ignores repos')
+    group.add_argument('-at', '--all-templates', action='store_true', required=False,
                        default=False,
-                       help='Combine all repos projects into a single list of resources.')
-    group.add_argument('-dg', '--downloadable-gems', action='store_true', required=False,
+                       help='Output all templates registered in the ~/.o3de/o3de_manifest.json and the current engine.json. Ignores repos.')
+    group.add_argument('-ares', '--all-restricted', action='store_true', required=False,
                        default=False,
-                       help='Combine all repos gems into a single list of resources.')
-    group.add_argument('-dt', '--downloadable-templates', action='store_true', required=False,
+                       help='Output all restricted directory registered in the ~/.o3de/o3de_manifest.json and the current engine.json.')
+    group.add_argument('-aes', '--all-external-subdirectories', action='store_true',
                        default=False,
-                       help='Combine all repos templates into a single list of resources.')
+                       help='Output all external subdirectories registered in the ~/.o3de/o3de_manifest.json and the current engine.json.')
 
     parser.add_argument('-v', '--verbose', action='count', required=False,
-                                         default=0,
-                                         help='How verbose do you want the output to be.')
+                        default=0,
+                        help='How verbose do you want the output to be.')
+
+    project_group = parser.add_mutually_exclusive_group(required=False)
+    project_group.add_argument('-pp', '--project-path', type=pathlib.Path,
+                       help='The path to a project.')
+    project_group.add_argument('-pn', '--project-name', type=str,
+                       help='The name of a project.')
 
     parser.add_argument('-ohf', '--override-home-folder', type=str, required=False,
-                                         help='By default the home folder is the user folder, override it to this folder.')
+                        help='By default the home folder is the user folder, override it to this folder.')
 
     parser.set_defaults(func=_run_register_show)
 

+ 7 - 0
scripts/o3de/tests/CMakeLists.txt

@@ -34,3 +34,10 @@ ly_add_pytest(
     TEST_SUITE smoke
     EXCLUDE_TEST_RUN_TARGET_FROM_IDE
 )
+
+ly_add_pytest(
+    NAME o3de_manifest
+    PATH ${CMAKE_CURRENT_LIST_DIR}/unit_test_manifest.py
+    TEST_SUITE smoke
+    EXCLUDE_TEST_RUN_TARGET_FROM_IDE
+)

+ 111 - 0
scripts/o3de/tests/unit_test_manifest.py

@@ -0,0 +1,111 @@
+#
+# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
+# its licensors.
+#
+# For complete copyright and license terms please see the LICENSE at the root of this
+# distribution (the "License"). All use of this software is governed by the License,
+# or, if provided, by the license below or the license accompanying this file. Do not
+# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#
+
+import argparse
+import json
+import logging
+import pytest
+import pathlib
+from unittest.mock import patch
+
+from o3de import manifest
+
+
[email protected]("valid_project_json_paths, valid_gem_json_paths", [
+    pytest.param([pathlib.Path('D:/o3de/Templates/DefaultProject/Template/project.json')],
+                 [pathlib.Path('D:/o3de/Templates/DefaultGem/Template/gem.json')])
+])
+class TestGetTemplatesForCreation:
+    @staticmethod
+    def get_templates() -> list:
+        return []
+
+    @staticmethod
+    def get_project_templates() -> list:
+        return []
+
+    @staticmethod
+    def get_engine_templates() -> list:
+        return [pathlib.Path('D:/o3de/Templates/DefaultProject'), pathlib.Path('D:/o3de/Templates/DefaultGem')]
+
+
+    @pytest.mark.parametrize("expected_template_paths", [
+            pytest.param([])
+        ]
+    )
+    def test_get_templates_for_generic_creation(self, valid_project_json_paths, valid_gem_json_paths,
+                                                expected_template_paths):
+        def validate_project_json(template_path) -> bool:
+            return pathlib.Path(template_path) in valid_project_json_paths
+
+        def validate_gem_json(template_path) -> bool:
+            return pathlib.Path(template_path) in valid_gem_json_paths
+
+        with patch('o3de.manifest.get_templates', side_effect=self.get_templates) as get_templates_patch, \
+                patch('o3de.manifest.get_project_templates', side_effect=self.get_project_templates)\
+                        as get_project_templates_patch, \
+                patch('o3de.manifest.get_engine_templates', side_effect=self.get_engine_templates)\
+                        as get_engine_templates_patch, \
+            patch('o3de.validation.valid_o3de_template_json', return_value=True) as validate_template_json,\
+            patch('o3de.validation.valid_o3de_project_json', side_effect=validate_project_json) as validate_project_json,\
+            patch('o3de.validation.valid_o3de_gem_json', side_effect=validate_gem_json) as validate_gem_json:
+            templates = manifest.get_templates_for_generic_creation()
+            assert templates == expected_template_paths
+
+
+    @pytest.mark.parametrize("expected_template_paths", [
+            pytest.param([pathlib.Path('D:/o3de/Templates/DefaultProject')])
+        ]
+    )
+    def test_get_templates_for_gem_creation(self, valid_project_json_paths, valid_gem_json_paths,
+                                                expected_template_paths):
+        def validate_project_json(template_path) -> bool:
+            return pathlib.Path(template_path) in valid_project_json_paths
+
+        def validate_gem_json(template_path) -> bool:
+            return pathlib.Path(template_path) in valid_gem_json_paths
+
+        with patch('o3de.manifest.get_templates', side_effect=self.get_templates) as get_templates_patch, \
+                patch('o3de.manifest.get_project_templates', side_effect=self.get_project_templates) \
+                        as get_project_templates_patch, \
+                patch('o3de.manifest.get_engine_templates', side_effect=self.get_engine_templates) \
+                        as get_engine_templates_patch, \
+                patch('o3de.validation.valid_o3de_template_json', return_value=True) as validate_template_json, \
+                patch('o3de.validation.valid_o3de_project_json',
+                      side_effect=validate_project_json) as validate_project_json, \
+                patch('o3de.validation.valid_o3de_gem_json', side_effect=validate_gem_json) as validate_gem_json:
+            templates = manifest.get_templates_for_project_creation()
+            assert templates == expected_template_paths
+
+
+    @pytest.mark.parametrize("expected_template_paths", [
+            pytest.param([pathlib.Path('D:/o3de/Templates/DefaultGem')])
+        ]
+    )
+    def test_get_templates_for_project_creation(self, valid_project_json_paths, valid_gem_json_paths,
+                                                expected_template_paths):
+        def validate_project_json(template_path) -> bool:
+            return pathlib.Path(template_path) in valid_project_json_paths
+
+        def validate_gem_json(template_path) -> bool:
+            return pathlib.Path(template_path) in valid_gem_json_paths
+
+        with patch('o3de.manifest.get_templates', side_effect=self.get_templates) as get_templates_patch, \
+                patch('o3de.manifest.get_project_templates', side_effect=self.get_project_templates) \
+                        as get_project_templates_patch, \
+                patch('o3de.manifest.get_engine_templates', side_effect=self.get_engine_templates) \
+                        as get_engine_templates_patch, \
+                patch('o3de.validation.valid_o3de_template_json', return_value=True) as validate_template_json, \
+                patch('o3de.validation.valid_o3de_project_json',
+                      side_effect=validate_project_json) as validate_project_json, \
+                patch('o3de.validation.valid_o3de_gem_json', side_effect=validate_gem_json) as validate_gem_json:
+            templates = manifest.get_templates_for_gem_creation()
+            assert templates == expected_template_paths

+ 1 - 1
scripts/o3de/tests/unit_test_utils.py

@@ -11,7 +11,7 @@
 
 import pytest
 
-from . import utils
+from o3de import utils
 
 @pytest.mark.parametrize(
     "value, expected_result", [

Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů