Integrating latest 47acbe8
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzCore/Memory/SystemAllocator.h>
|
||||
#include <AzCore/Module/Module.h>
|
||||
|
||||
#include <PythonSystemComponent.h>
|
||||
#include <PythonReflectionComponent.h>
|
||||
#include <PythonMarshalComponent.h>
|
||||
#include <PythonLogSymbolsComponent.h>
|
||||
|
||||
namespace EditorPythonBindings
|
||||
{
|
||||
class EditorPythonBindingsModule
|
||||
: public AZ::Module
|
||||
{
|
||||
public:
|
||||
AZ_RTTI(EditorPythonBindingsModule, "{851B9E35-4FD5-49B1-8207-E40D4BBA36CC}", AZ::Module);
|
||||
AZ_CLASS_ALLOCATOR(EditorPythonBindingsModule, AZ::SystemAllocator, 0);
|
||||
|
||||
EditorPythonBindingsModule()
|
||||
: AZ::Module()
|
||||
{
|
||||
m_descriptors.insert(m_descriptors.end(),
|
||||
{
|
||||
PythonSystemComponent::CreateDescriptor(),
|
||||
PythonReflectionComponent::CreateDescriptor(),
|
||||
PythonMarshalComponent::CreateDescriptor(),
|
||||
PythonLogSymbolsComponent::CreateDescriptor(),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add required SystemComponents to the SystemEntity.
|
||||
*/
|
||||
AZ::ComponentTypeList GetRequiredSystemComponents() const override
|
||||
{
|
||||
return AZ::ComponentTypeList
|
||||
{
|
||||
azrtti_typeid<PythonSystemComponent>(),
|
||||
azrtti_typeid<PythonReflectionComponent>(),
|
||||
azrtti_typeid<PythonMarshalComponent>(),
|
||||
azrtti_typeid<PythonLogSymbolsComponent>()
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// DO NOT MODIFY THIS LINE UNLESS YOU RENAME THE GEM
|
||||
// The first parameter should be GemName_GemIdLower
|
||||
// The second should be the fully qualified name of the class above
|
||||
AZ_DECLARE_MODULE_CLASS(Gem_EditorPythonBindings, EditorPythonBindings::EditorPythonBindingsModule)
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
set(LY_COMPILE_OPTIONS
|
||||
PRIVATE
|
||||
-fexceptions # The macro PYBIND11_EMBEDDED_MODULE uses a try catch block
|
||||
)
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
set(LY_COMPILE_OPTIONS
|
||||
PRIVATE
|
||||
-fexceptions # The macro PYBIND11_EMBEDDED_MODULE uses a try catch block
|
||||
)
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
set(LY_COMPILE_OPTIONS
|
||||
PRIVATE
|
||||
/EHsc # The macro PYBIND11_EMBEDDED_MODULE uses a try catch block
|
||||
)
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
set(LY_COMPILE_OPTIONS
|
||||
PRIVATE
|
||||
/EHsc # The macro PYBIND11_EMBEDDED_MODULE uses a try catch block
|
||||
)
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzCore/PlatformDef.h>
|
||||
#include <AzCore/std/containers/unordered_set.h>
|
||||
#include <AzCore/IO/SystemFile.h>
|
||||
#include <AzCore/IO/Path/Path.h>
|
||||
#include <AzFramework/StringFunc/StringFunc.h>
|
||||
|
||||
namespace Platform
|
||||
{
|
||||
bool InsertPythonLibraryPath(AZStd::unordered_set<AZStd::string>& paths, const char* pythonPackage, const char* engineRoot, const char* subPath)
|
||||
{
|
||||
// append lib path to Python paths
|
||||
AZ::IO::FixedMaxPath libPath = engineRoot;
|
||||
libPath /= AZ::IO::FixedMaxPathString::format(subPath, pythonPackage);
|
||||
libPath = libPath.LexicallyNormal();
|
||||
if (AZ::IO::SystemFile::Exists(libPath.c_str()))
|
||||
{
|
||||
paths.insert(libPath.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
AZ_Warning("python", false, "Python library path should exist! path:%s", libPath.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
bool InsertPythonBinaryLibraryPaths(AZStd::unordered_set<AZStd::string>& paths, const char* pythonPackage, const char* engineRoot)
|
||||
{
|
||||
bool succeeded = true;
|
||||
|
||||
// append lib path to Python paths
|
||||
succeeded = succeeded && InsertPythonLibraryPath(paths, pythonPackage, engineRoot, "python/runtime/%s/python/lib");
|
||||
|
||||
// append lib-dynload path
|
||||
succeeded = succeeded && InsertPythonLibraryPath(paths, pythonPackage, engineRoot, "python/runtime/%s/python/lib/python3.7/lib-dynload");
|
||||
|
||||
// append base path to dynamic link libraries
|
||||
succeeded = succeeded && InsertPythonLibraryPath(paths, pythonPackage, engineRoot, "python/runtime/%s/python/lib/python3.7");
|
||||
|
||||
// append path to site-packages
|
||||
succeeded = succeeded && InsertPythonLibraryPath(paths, pythonPackage, engineRoot, "python/runtime/%s/python/lib/python3.7/site-packages");
|
||||
return succeeded;
|
||||
}
|
||||
|
||||
AZStd::string GetPythonHomePath(const char* pythonPackage, const char* engineRoot)
|
||||
{
|
||||
// append lib path to Python paths
|
||||
AZ::IO::FixedMaxPath libPath = engineRoot;
|
||||
libPath /= AZ::IO::FixedMaxPathString::format("python/runtime/%s/python", pythonPackage);
|
||||
libPath = libPath.LexicallyNormal();
|
||||
return libPath.String();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
set(LY_COMPILE_OPTIONS
|
||||
PRIVATE
|
||||
-fexceptions # The macro PYBIND11_EMBEDDED_MODULE uses a try catch block
|
||||
)
|
||||
@@ -0,0 +1,14 @@
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
set(FILES
|
||||
../Linux/PythonSystemComponent_linux.cpp
|
||||
)
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzCore/PlatformDef.h>
|
||||
#include <AzCore/std/containers/unordered_set.h>
|
||||
#include <AzCore/IO/SystemFile.h>
|
||||
#include <AzCore/IO/Path/Path.h>
|
||||
#include <AzFramework/StringFunc/StringFunc.h>
|
||||
|
||||
namespace Platform
|
||||
{
|
||||
bool InsertPythonLibraryPath(AZStd::unordered_set<AZStd::string>& paths, const char* pythonPackage, const char* engineRoot, const char* subPath)
|
||||
{
|
||||
// append lib path to Python paths
|
||||
AZ::IO::FixedMaxPath libPath = engineRoot;
|
||||
libPath /= AZ::IO::FixedMaxPathString::format(subPath, pythonPackage);
|
||||
libPath = libPath.LexicallyNormal();
|
||||
if (AZ::IO::SystemFile::Exists(libPath.c_str()))
|
||||
{
|
||||
paths.insert(libPath.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
AZ_Warning("python", false, "Python library path should exist! path:%s", libPath.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
bool InsertPythonBinaryLibraryPaths(AZStd::unordered_set<AZStd::string>& paths, const char* pythonPackage, const char* engineRoot)
|
||||
{
|
||||
// append lib path to Python paths
|
||||
bool succeeded = true;
|
||||
|
||||
succeeded = succeeded && InsertPythonLibraryPath(paths, pythonPackage, engineRoot, "python/runtime/%s/Python.framework/Versions/3.7/lib");
|
||||
|
||||
// append lib-dynload path
|
||||
succeeded = succeeded && InsertPythonLibraryPath(paths, pythonPackage, engineRoot, "python/runtime/%s/Python.framework/Versions/3.7/lib/python3.7/lib-dynload");
|
||||
|
||||
// append base path to dynamic link libraries
|
||||
succeeded = succeeded && InsertPythonLibraryPath(paths, pythonPackage, engineRoot, "python/runtime/%s/Python.framework/Versions/3.7/lib/python3.7");
|
||||
|
||||
// append path to site-packages
|
||||
succeeded = succeeded && InsertPythonLibraryPath(paths, pythonPackage, engineRoot, "python/runtime/%s/Python.framework/Versions/3.7/lib/python3.7/site-packages");
|
||||
return succeeded;
|
||||
}
|
||||
|
||||
AZStd::string GetPythonHomePath(const char* pythonPackage, const char* engineRoot)
|
||||
{
|
||||
// append lib path to Python paths
|
||||
AZ::IO::FixedMaxPath libPath = engineRoot;
|
||||
libPath /= AZ::IO::FixedMaxPathString::format("python/runtime/%s/Python.framework/Versions/3.7", pythonPackage);
|
||||
libPath = libPath.LexicallyNormal();
|
||||
return libPath.String();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
@@ -0,0 +1,15 @@
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
set(FILES
|
||||
../Mac/PythonSystemComponent_mac.cpp
|
||||
)
|
||||
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzCore/PlatformDef.h>
|
||||
#include <AzCore/std/containers/unordered_set.h>
|
||||
#include <AzCore/IO/SystemFile.h>
|
||||
#include <AzCore/IO/Path/Path.h>
|
||||
#include <AzFramework/StringFunc/StringFunc.h>
|
||||
|
||||
namespace Platform
|
||||
{
|
||||
bool InsertPythonLibraryPath(AZStd::unordered_set<AZStd::string>& paths, const char* pythonPackage, const char* engineRoot, const char* subPath)
|
||||
{
|
||||
// append lib path to Python paths
|
||||
AZ::IO::FixedMaxPath libPath = engineRoot;
|
||||
libPath /= AZ::IO::FixedMaxPathString::format(subPath, pythonPackage);
|
||||
libPath = libPath.LexicallyNormal();
|
||||
if (AZ::IO::SystemFile::Exists(libPath.c_str()))
|
||||
{
|
||||
paths.insert(libPath.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
AZ_Warning("python", false, "Python library path should exist! path:%s", libPath.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
bool InsertPythonBinaryLibraryPaths(AZStd::unordered_set<AZStd::string>& paths, const char* pythonPackage, const char* engineRoot)
|
||||
{
|
||||
// append lib path to Python paths
|
||||
bool succeeded = true;
|
||||
|
||||
succeeded = succeeded && InsertPythonLibraryPath(paths, pythonPackage, engineRoot, "python/runtime/%s/python");
|
||||
succeeded = succeeded && InsertPythonLibraryPath(paths, pythonPackage, engineRoot, "python/runtime/%s/python/lib");
|
||||
succeeded = succeeded && InsertPythonLibraryPath(paths, pythonPackage, engineRoot, "python/runtime/%s/python/lib/site-packages");
|
||||
succeeded = succeeded && InsertPythonLibraryPath(paths, pythonPackage, engineRoot, "python/runtime/%s/python/DLLs");
|
||||
return succeeded;
|
||||
}
|
||||
|
||||
AZStd::string GetPythonHomePath(const char* pythonPackage, const char* engineRoot)
|
||||
{
|
||||
// append lib path to Python paths
|
||||
AZ::IO::FixedMaxPath libPath = engineRoot;
|
||||
libPath /= AZ::IO::FixedMaxPathString::format("python/runtime/%s/python", pythonPackage);
|
||||
libPath = libPath.LexicallyNormal();
|
||||
return libPath.String();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
set(LY_COMPILE_DEFINITIONS
|
||||
PRIVATE
|
||||
HAVE_ROUND # defined for Windows since http://p-nand-q.com/python/building-python-33-with-vs2013.html
|
||||
)
|
||||
@@ -0,0 +1,14 @@
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
set(FILES
|
||||
../Windows/PythonSystemComponent_windows.cpp
|
||||
)
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
// 'The problem is that with this line, "slots" is a keyword by default in Qt.' https://stackoverflow.com/questions/23068700/embedding-python3-in-qt-5
|
||||
#pragma push_macro("slots")
|
||||
#undef slots
|
||||
|
||||
#include <Python.h>
|
||||
|
||||
#pragma pop_macro("slots")
|
||||
@@ -0,0 +1,766 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <PythonLogSymbolsComponent.h>
|
||||
|
||||
#include <Source/PythonCommon.h>
|
||||
#include <Source/PythonUtility.h>
|
||||
#include <Source/PythonTypeCasters.h>
|
||||
#include <Source/PythonProxyBus.h>
|
||||
#include <Source/PythonProxyObject.h>
|
||||
#include <pybind11/embed.h>
|
||||
|
||||
#include <AzCore/PlatformDef.h>
|
||||
#include <AzCore/RTTI/AttributeReader.h>
|
||||
#include <AzCore/RTTI/BehaviorContext.h>
|
||||
#include <AzCore/IO/SystemFile.h>
|
||||
#include <AzCore/IO/FileIO.h>
|
||||
#include <AzCore/std/sort.h>
|
||||
#include <AzCore/Serialization/Utils.h>
|
||||
|
||||
#include <AzFramework/CommandLine/CommandRegistrationBus.h>
|
||||
#include <AzFramework/StringFunc/StringFunc.h>
|
||||
|
||||
namespace EditorPythonBindings
|
||||
{
|
||||
namespace Internal
|
||||
{
|
||||
struct FileHandle final
|
||||
{
|
||||
explicit FileHandle(AZ::IO::HandleType handle)
|
||||
: m_handle(handle)
|
||||
{}
|
||||
|
||||
~FileHandle()
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
void Close()
|
||||
{
|
||||
if (IsValid())
|
||||
{
|
||||
AZ::IO::FileIOBase::GetInstance()->Close(m_handle);
|
||||
}
|
||||
m_handle = AZ::IO::InvalidHandle;
|
||||
}
|
||||
|
||||
bool IsValid() const
|
||||
{
|
||||
return m_handle != AZ::IO::InvalidHandle;
|
||||
}
|
||||
|
||||
operator AZ::IO::HandleType() const { return m_handle; }
|
||||
|
||||
AZ::IO::HandleType m_handle;
|
||||
};
|
||||
|
||||
void Indent(int level, AZStd::string& buffer)
|
||||
{
|
||||
buffer.append(level * 4, ' ');
|
||||
}
|
||||
|
||||
void AddCommentBlock(int level, const AZStd::string& comment, AZStd::string& buffer)
|
||||
{
|
||||
Indent(level, buffer);
|
||||
AzFramework::StringFunc::Append(buffer, "\"\"\"\n");
|
||||
Indent(level, buffer);
|
||||
AzFramework::StringFunc::Append(buffer, comment.c_str());
|
||||
Indent(level, buffer);
|
||||
AzFramework::StringFunc::Append(buffer, "\"\"\"\n");
|
||||
}
|
||||
}
|
||||
|
||||
void PythonLogSymbolsComponent::Reflect(AZ::ReflectContext* context)
|
||||
{
|
||||
if (auto&& serialize = azrtti_cast<AZ::SerializeContext*>(context))
|
||||
{
|
||||
serialize->Class<PythonLogSymbolsComponent, AZ::Component>()
|
||||
->Version(0);
|
||||
}
|
||||
}
|
||||
|
||||
void PythonLogSymbolsComponent::Activate()
|
||||
{
|
||||
PythonSymbolEventBus::Handler::BusConnect();
|
||||
EditorPythonBindingsNotificationBus::Handler::BusConnect();
|
||||
AZ::Interface<AzToolsFramework::EditorPythonConsoleInterface>::Register(this);
|
||||
}
|
||||
|
||||
void PythonLogSymbolsComponent::Deactivate()
|
||||
{
|
||||
AZ::Interface<AzToolsFramework::EditorPythonConsoleInterface>::Unregister(this);
|
||||
PythonSymbolEventBus::Handler::BusDisconnect();
|
||||
EditorPythonBindingsNotificationBus::Handler::BusDisconnect();
|
||||
}
|
||||
|
||||
void PythonLogSymbolsComponent::OnPostInitialize()
|
||||
{
|
||||
m_basePath.clear();
|
||||
if (AZ::IO::FileIOBase::GetInstance()->GetAlias("@user@"))
|
||||
{
|
||||
// clear out the previous symbols path
|
||||
char pythonSymbolsPath[AZ_MAX_PATH_LEN];
|
||||
AZ::IO::FileIOBase::GetInstance()->ResolvePath("@user@/python_symbols", pythonSymbolsPath, AZ_MAX_PATH_LEN);
|
||||
AZ::IO::FileIOBase::GetInstance()->CreatePath(pythonSymbolsPath);
|
||||
m_basePath = pythonSymbolsPath;
|
||||
}
|
||||
EditorPythonBindingsNotificationBus::Handler::BusDisconnect();
|
||||
}
|
||||
|
||||
void PythonLogSymbolsComponent::WriteMethod(AZ::IO::HandleType handle, AZStd::string_view methodName, const AZ::BehaviorMethod& behaviorMethod, const AZ::BehaviorClass* behaviorClass)
|
||||
{
|
||||
AZStd::string buffer;
|
||||
int indentLevel = 0;
|
||||
AZStd::vector<AZStd::string> pythonArgs;
|
||||
const bool isMemberLike = behaviorClass ? PythonProxyObjectManagement::IsMemberLike(behaviorMethod, behaviorClass->m_typeId) : false;
|
||||
|
||||
if (isMemberLike)
|
||||
{
|
||||
indentLevel = 1;
|
||||
Internal::Indent(indentLevel, buffer);
|
||||
pythonArgs.emplace_back("self");
|
||||
}
|
||||
else
|
||||
{
|
||||
indentLevel = 0;
|
||||
}
|
||||
|
||||
AzFramework::StringFunc::Append(buffer, "def ");
|
||||
if (isMemberLike || !behaviorClass)
|
||||
{
|
||||
AzFramework::StringFunc::Append(buffer, methodName.data());
|
||||
}
|
||||
else
|
||||
{
|
||||
AzFramework::StringFunc::Append(buffer, behaviorClass->m_name.c_str());
|
||||
AzFramework::StringFunc::Append(buffer, "_");
|
||||
AzFramework::StringFunc::Append(buffer, methodName.data());
|
||||
}
|
||||
AzFramework::StringFunc::Append(buffer, "(");
|
||||
|
||||
AZStd::string bufferArg;
|
||||
for (size_t argIndex = 0; argIndex < behaviorMethod.GetNumArguments(); ++argIndex)
|
||||
{
|
||||
const AZStd::string* name = behaviorMethod.GetArgumentName(argIndex);
|
||||
if (!name || name->empty())
|
||||
{
|
||||
bufferArg = AZStd::string::format(" arg%zu", argIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
bufferArg = *name;
|
||||
}
|
||||
|
||||
AZStd::string_view type = FetchPythonType(*behaviorMethod.GetArgument(argIndex));
|
||||
if (!type.empty())
|
||||
{
|
||||
AzFramework::StringFunc::Append(bufferArg, ": ");
|
||||
AzFramework::StringFunc::Append(bufferArg, type.data());
|
||||
}
|
||||
|
||||
pythonArgs.push_back(bufferArg);
|
||||
bufferArg.clear();
|
||||
}
|
||||
|
||||
AZStd::string argsList;
|
||||
AzFramework::StringFunc::Join(buffer, pythonArgs.begin(), pythonArgs.end(), ",");
|
||||
AzFramework::StringFunc::Append(buffer, ") -> None:\n");
|
||||
Internal::Indent(indentLevel + 1, buffer);
|
||||
AzFramework::StringFunc::Append(buffer, "pass\n\n");
|
||||
|
||||
AZ::IO::FileIOBase::GetInstance()->Write(handle, buffer.c_str(), buffer.size());
|
||||
}
|
||||
|
||||
void PythonLogSymbolsComponent::WriteProperty(AZ::IO::HandleType handle, int level, AZStd::string_view propertyName, const AZ::BehaviorProperty& property, [[maybe_unused]] const AZ::BehaviorClass * behaviorClass)
|
||||
{
|
||||
AZStd::string buffer;
|
||||
|
||||
// property declaration
|
||||
Internal::Indent(level, buffer);
|
||||
AzFramework::StringFunc::Append(buffer, "@property\n");
|
||||
|
||||
Internal::Indent(level, buffer);
|
||||
AzFramework::StringFunc::Append(buffer, "def ");
|
||||
AzFramework::StringFunc::Append(buffer, propertyName.data());
|
||||
AzFramework::StringFunc::Append(buffer, "(self) -> ");
|
||||
|
||||
AZStd::string_view type = FetchPythonTypeAndTraits(property.GetTypeId(), AZ::BehaviorParameter::TR_NONE);
|
||||
if (type.empty())
|
||||
{
|
||||
AzFramework::StringFunc::Append(buffer, "Any");
|
||||
}
|
||||
else
|
||||
{
|
||||
AzFramework::StringFunc::Append(buffer, type.data());
|
||||
}
|
||||
AzFramework::StringFunc::Append(buffer, ":\n");
|
||||
Internal::Indent(level + 1, buffer);
|
||||
AzFramework::StringFunc::Append(buffer, "pass\n\n");
|
||||
|
||||
AZ::IO::FileIOBase::GetInstance()->Write(handle, buffer.c_str(), buffer.size());
|
||||
}
|
||||
|
||||
void PythonLogSymbolsComponent::LogClass(AZStd::string_view moduleName, AZ::BehaviorClass* behaviorClass)
|
||||
{
|
||||
LogClassWithName(moduleName, behaviorClass, behaviorClass->m_name.c_str());
|
||||
}
|
||||
|
||||
void PythonLogSymbolsComponent::LogClassWithName(AZStd::string_view moduleName, AZ::BehaviorClass* behaviorClass, AZStd::string_view className)
|
||||
{
|
||||
Internal::FileHandle fileHandle(OpenModuleAt(moduleName));
|
||||
if (fileHandle.IsValid())
|
||||
{
|
||||
// Behavior Class types with member methods and properties
|
||||
AZStd::string buffer;
|
||||
AzFramework::StringFunc::Append(buffer, "class ");
|
||||
AzFramework::StringFunc::Append(buffer, className.data());
|
||||
AzFramework::StringFunc::Append(buffer, ":\n");
|
||||
AZ::IO::FileIOBase::GetInstance()->Write(fileHandle, buffer.c_str(), buffer.size());
|
||||
buffer.clear();
|
||||
|
||||
if (behaviorClass->m_methods.empty() && behaviorClass->m_properties.empty())
|
||||
{
|
||||
AZStd::string body{ " # behavior class type with no methods or properties \n" };
|
||||
Internal::Indent(1, body);
|
||||
AzFramework::StringFunc::Append(body, "pass\n\n");
|
||||
AZ::IO::FileIOBase::GetInstance()->Write(fileHandle, body.c_str(), body.size());
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const auto& properyEntry : behaviorClass->m_properties)
|
||||
{
|
||||
AZ::BehaviorProperty* property = properyEntry.second;
|
||||
AZStd::string propertyName{ properyEntry.first };
|
||||
Scope::FetchScriptName(property->m_attributes, propertyName);
|
||||
WriteProperty(fileHandle, 1, propertyName, *property, behaviorClass);
|
||||
}
|
||||
|
||||
for (const auto& methodEntry : behaviorClass->m_methods)
|
||||
{
|
||||
AZ::BehaviorMethod* method = methodEntry.second;
|
||||
if (method && PythonProxyObjectManagement::IsMemberLike(*method, behaviorClass->m_typeId))
|
||||
{
|
||||
AZStd::string baseMethodName{ methodEntry.first };
|
||||
Scope::FetchScriptName(method->m_attributes, baseMethodName);
|
||||
WriteMethod(fileHandle, baseMethodName, *method, behaviorClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PythonLogSymbolsComponent::LogClassMethod(AZStd::string_view moduleName, AZStd::string_view globalMethodName, AZ::BehaviorClass* behaviorClass, AZ::BehaviorMethod* behaviorMethod)
|
||||
{
|
||||
AZ_UNUSED(behaviorClass);
|
||||
Internal::FileHandle fileHandle(OpenModuleAt(moduleName));
|
||||
if (fileHandle.IsValid())
|
||||
{
|
||||
WriteMethod(fileHandle, globalMethodName, *behaviorMethod, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void PythonLogSymbolsComponent::LogBus(AZStd::string_view moduleName, AZStd::string_view busName, AZ::BehaviorEBus* behaviorEBus)
|
||||
{
|
||||
if (behaviorEBus->m_events.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Internal::FileHandle fileHandle(OpenModuleAt(moduleName));
|
||||
if (fileHandle.IsValid())
|
||||
{
|
||||
AZStd::string buffer;
|
||||
|
||||
const auto& eventSenderEntry = behaviorEBus->m_events.begin();
|
||||
const AZ::BehaviorEBusEventSender& sender = eventSenderEntry->second;
|
||||
|
||||
AzFramework::StringFunc::Append(buffer, "def ");
|
||||
AzFramework::StringFunc::Append(buffer, busName.data());
|
||||
bool isBroadcast = false;
|
||||
if (sender.m_event)
|
||||
{
|
||||
AZStd::string_view addressType = FetchPythonType(behaviorEBus->m_idParam);
|
||||
if (addressType.empty())
|
||||
{
|
||||
AzFramework::StringFunc::Append(buffer, "(busCallType: int, busEventName: str, address: Any, args: Tuple[Any])");
|
||||
}
|
||||
else
|
||||
{
|
||||
AzFramework::StringFunc::Append(buffer, "(busCallType: int, busEventName: str, address: ");
|
||||
AzFramework::StringFunc::Append(buffer, AZStd::string::format(AZ_STRING_FORMAT, AZ_STRING_ARG(addressType)).c_str());
|
||||
AzFramework::StringFunc::Append(buffer, ", args: Tuple[Any])");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AzFramework::StringFunc::Append(buffer, "(busCallType: int, busEventName: str, args: Tuple[Any])");
|
||||
isBroadcast = true;
|
||||
}
|
||||
|
||||
AzFramework::StringFunc::Append(buffer, " -> Any:\n");
|
||||
AZ::IO::FileIOBase::GetInstance()->Write(fileHandle, buffer.c_str(), buffer.size());
|
||||
buffer.clear();
|
||||
|
||||
auto eventInfoBuilder = [this](const AZ::BehaviorMethod* behaviorMethod, AZStd::string& inOutStrBuffer, [[maybe_unused]] TypeMap& typeCache)
|
||||
{
|
||||
AzFramework::StringFunc::Append(inOutStrBuffer, "(");
|
||||
|
||||
size_t numArguments = behaviorMethod->GetNumArguments();
|
||||
|
||||
const AZ::BehaviorParameter* busIdArg = behaviorMethod->GetBusIdArgument();
|
||||
|
||||
for (size_t i = 0; i < numArguments; ++i)
|
||||
{
|
||||
const AZ::BehaviorParameter* argParam = behaviorMethod->GetArgument(i);
|
||||
if (argParam == busIdArg)
|
||||
{
|
||||
// address argument is part of the bus call, skip from event argument list
|
||||
continue;
|
||||
}
|
||||
|
||||
AZStd::string_view argType = FetchPythonTypeAndTraits(argParam->m_typeId, argParam->m_traits);
|
||||
AzFramework::StringFunc::Append(inOutStrBuffer, argType.data());
|
||||
|
||||
if (i < (numArguments - 1))
|
||||
{
|
||||
AzFramework::StringFunc::Append(inOutStrBuffer, ", ");
|
||||
}
|
||||
}
|
||||
|
||||
const AZ::BehaviorParameter* resultParam = behaviorMethod->GetResult();
|
||||
AZStd::string_view returnType = FetchPythonType(*resultParam);
|
||||
AZStd::string returnTypeStr = AZStd::string::format(") -> " AZ_STRING_FORMAT" \n", AZ_STRING_ARG(returnType));
|
||||
AzFramework::StringFunc::Append(inOutStrBuffer, returnTypeStr.c_str());
|
||||
};
|
||||
|
||||
// record the event names the behavior can send, their parameters and return type
|
||||
AZStd::string comment = behaviorEBus->m_toolTip;
|
||||
|
||||
if (!behaviorEBus->m_events.empty())
|
||||
{
|
||||
AzFramework::StringFunc::Append(comment, "The following bus Call types, Event names and Argument types are supported by this bus:\n");
|
||||
AZStd::vector<AZStd::string> events;
|
||||
for (const auto& eventSenderEntry2 : behaviorEBus->m_events)
|
||||
{
|
||||
const AZStd::string& eventName = eventSenderEntry2.first;
|
||||
AZStd::string eventNameStr = AZStd::string::format("'%s', ", eventName.c_str());
|
||||
|
||||
// prefer m_event info over m_broadcast
|
||||
if (!isBroadcast && eventSenderEntry2.second.m_event != nullptr)
|
||||
{
|
||||
AZStd::string eventInfo;
|
||||
AzFramework::StringFunc::Append(eventInfo, "bus.Event, ");
|
||||
AzFramework::StringFunc::Append(eventInfo, eventNameStr.c_str());
|
||||
eventInfoBuilder(eventSenderEntry2.second.m_event, eventInfo, m_typeCache);
|
||||
events.push_back(eventInfo);
|
||||
}
|
||||
else if (isBroadcast && eventSenderEntry2.second.m_broadcast != nullptr)
|
||||
{
|
||||
AZStd::string eventInfo;
|
||||
AzFramework::StringFunc::Append(eventInfo, "bus.Broadcast, ");
|
||||
AzFramework::StringFunc::Append(eventInfo, eventNameStr.c_str());
|
||||
eventInfoBuilder(eventSenderEntry2.second.m_broadcast, eventInfo, m_typeCache);
|
||||
events.push_back(eventInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Warning("python", false, "Event %s is expected to have valid event information.", eventName.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
AZStd::sort(events.begin(), events.end());
|
||||
|
||||
for (auto& eventInfo : events)
|
||||
{
|
||||
Internal::Indent(1, comment);
|
||||
AzFramework::StringFunc::Append(comment, eventInfo.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
Internal::AddCommentBlock(1, comment, buffer);
|
||||
|
||||
Internal::Indent(1, buffer);
|
||||
AzFramework::StringFunc::Append(buffer, "pass\n\n");
|
||||
|
||||
AZ::IO::FileIOBase::GetInstance()->Write(fileHandle, buffer.c_str(), buffer.size());
|
||||
|
||||
// can the EBus create & destroy a handler?
|
||||
if (behaviorEBus->m_createHandler && behaviorEBus->m_destroyHandler)
|
||||
{
|
||||
buffer.clear();
|
||||
AzFramework::StringFunc::Append(buffer, "def ");
|
||||
AzFramework::StringFunc::Append(buffer, busName.data());
|
||||
AzFramework::StringFunc::Append(buffer, "Handler() -> None:\n");
|
||||
Internal::Indent(1, buffer);
|
||||
AzFramework::StringFunc::Append(buffer, "pass\n\n");
|
||||
AZ::IO::FileIOBase::GetInstance()->Write(fileHandle, buffer.c_str(), buffer.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PythonLogSymbolsComponent::LogGlobalMethod(AZStd::string_view moduleName, AZStd::string_view methodName, AZ::BehaviorMethod* behaviorMethod)
|
||||
{
|
||||
Internal::FileHandle fileHandle(OpenModuleAt(moduleName));
|
||||
if (fileHandle.IsValid())
|
||||
{
|
||||
WriteMethod(fileHandle, methodName, *behaviorMethod, nullptr);
|
||||
}
|
||||
|
||||
auto functionMapIt = m_globalFunctionMap.find(moduleName);
|
||||
if (functionMapIt == m_globalFunctionMap.end())
|
||||
{
|
||||
auto moduleSetIt = m_moduleSet.find(moduleName);
|
||||
if (moduleSetIt != m_moduleSet.end())
|
||||
{
|
||||
m_globalFunctionMap[*moduleSetIt] = { AZStd::make_pair(behaviorMethod, methodName) };
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GlobalFunctionList& globalFunctionList = functionMapIt->second;
|
||||
globalFunctionList.emplace_back(AZStd::make_pair(behaviorMethod, methodName));
|
||||
}
|
||||
}
|
||||
|
||||
void PythonLogSymbolsComponent::LogGlobalProperty(AZStd::string_view moduleName, AZStd::string_view propertyName, AZ::BehaviorProperty* behaviorProperty)
|
||||
{
|
||||
if (!behaviorProperty->m_getter || !behaviorProperty->m_getter->GetResult())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Internal::FileHandle fileHandle(OpenModuleAt(moduleName));
|
||||
if (fileHandle.IsValid())
|
||||
{
|
||||
AZStd::string buffer;
|
||||
|
||||
// add header
|
||||
AZ::u64 filesize = 0;
|
||||
AZ::IO::FileIOBase::GetInstance()->Size(fileHandle, filesize);
|
||||
if (filesize == 0)
|
||||
{
|
||||
AzFramework::StringFunc::Append(buffer, "class property():\n");
|
||||
}
|
||||
|
||||
Internal::Indent(1, buffer);
|
||||
AzFramework::StringFunc::Append(buffer, propertyName.data());
|
||||
AzFramework::StringFunc::Append(buffer, ": ClassVar[");
|
||||
|
||||
const AZ::BehaviorParameter* resultParam = behaviorProperty->m_getter->GetResult();
|
||||
AZStd::string_view type = FetchPythonTypeAndTraits(resultParam->m_typeId, resultParam->m_traits);
|
||||
if (type.empty())
|
||||
{
|
||||
AzFramework::StringFunc::Append(buffer, "Any");
|
||||
}
|
||||
else
|
||||
{
|
||||
AzFramework::StringFunc::Append(buffer, type.data());
|
||||
}
|
||||
AzFramework::StringFunc::Append(buffer, "] = None");
|
||||
|
||||
if (behaviorProperty->m_getter && !behaviorProperty->m_setter)
|
||||
{
|
||||
AzFramework::StringFunc::Append(buffer, " # read only");
|
||||
}
|
||||
AzFramework::StringFunc::Append(buffer, "\n");
|
||||
|
||||
AZ::IO::FileIOBase::GetInstance()->Write(fileHandle, buffer.c_str(), buffer.size());
|
||||
}
|
||||
}
|
||||
|
||||
void PythonLogSymbolsComponent::Finalize()
|
||||
{
|
||||
Internal::FileHandle fileHandle(OpenInitFileAt("azlmbr.bus"));
|
||||
if (fileHandle)
|
||||
{
|
||||
AZStd::string buffer;
|
||||
AzFramework::StringFunc::Append(buffer, "# Bus dispatch types:\n");
|
||||
AzFramework::StringFunc::Append(buffer, "from typing_extensions import Final\n");
|
||||
AzFramework::StringFunc::Append(buffer, "Broadcast: Final[int] = 0\n");
|
||||
AzFramework::StringFunc::Append(buffer, "Event: Final[int] = 1\n");
|
||||
AzFramework::StringFunc::Append(buffer, "QueueBroadcast: Final[int] = 2\n");
|
||||
AzFramework::StringFunc::Append(buffer, "QueueEvent: Final[int] = 3\n");
|
||||
AZ::IO::FileIOBase::GetInstance()->Write(fileHandle, buffer.c_str(), buffer.size());
|
||||
}
|
||||
fileHandle.Close();
|
||||
}
|
||||
|
||||
void PythonLogSymbolsComponent::GetModuleList(AZStd::vector<AZStd::string_view>& moduleList) const
|
||||
{
|
||||
moduleList.clear();
|
||||
moduleList.reserve(m_moduleSet.size());
|
||||
AZStd::copy(m_moduleSet.begin(), m_moduleSet.end(), AZStd::back_inserter(moduleList));
|
||||
}
|
||||
|
||||
void PythonLogSymbolsComponent::GetGlobalFunctionList(GlobalFunctionCollection& globalFunctionCollection) const
|
||||
{
|
||||
globalFunctionCollection.clear();
|
||||
|
||||
for (const auto& globalFunctionMapEntry : m_globalFunctionMap)
|
||||
{
|
||||
const AZStd::string_view moduleName{ globalFunctionMapEntry.first };
|
||||
const GlobalFunctionList& moduleFunctionList = globalFunctionMapEntry.second;
|
||||
|
||||
AZStd::transform(moduleFunctionList.begin(), moduleFunctionList.end(), AZStd::back_inserter(globalFunctionCollection), [moduleName](auto& entry) -> auto
|
||||
{
|
||||
const GlobalFunctionEntry& globalFunctionEntry = entry;
|
||||
const AZ::BehaviorMethod* behaviorMethod = entry.first;
|
||||
return AzToolsFramework::EditorPythonConsoleInterface::GlobalFunction({ moduleName, globalFunctionEntry.second, behaviorMethod->m_debugDescription });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
AZStd::string PythonLogSymbolsComponent::FetchListType(const AZ::TypeId& typeId)
|
||||
{
|
||||
AZStd::string type = "list";
|
||||
|
||||
AZStd::vector<AZ::Uuid> typeList = AZ::Utils::GetContainedTypes(typeId);
|
||||
if (!typeList.empty())
|
||||
{
|
||||
// trait info not available, so defaulting to TR_NONE
|
||||
AZStd::string_view itemType = FetchPythonTypeAndTraits(typeList[0], AZ::BehaviorParameter::TR_NONE);
|
||||
if (!itemType.empty())
|
||||
{
|
||||
type = AZStd::string::format("List[" AZ_STRING_FORMAT "]", AZ_STRING_ARG(itemType));
|
||||
}
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
AZStd::string PythonLogSymbolsComponent::FetchMapType(const AZ::TypeId& typeId)
|
||||
{
|
||||
AZStd::string type = "dict";
|
||||
|
||||
AZStd::vector<AZ::Uuid> typeList = AZ::Utils::GetContainedTypes(typeId);
|
||||
if (!typeList.empty())
|
||||
{
|
||||
// trait info not available, so defaulting to TR_NONE
|
||||
AZStd::string_view kType = FetchPythonTypeAndTraits(typeList[0], AZ::BehaviorParameter::TR_NONE);
|
||||
AZStd::string_view vType = FetchPythonTypeAndTraits(typeList[1], AZ::BehaviorParameter::TR_NONE);
|
||||
if (!kType.empty() && !vType.empty())
|
||||
{
|
||||
type = AZStd::string::format("Dict[" AZ_STRING_FORMAT ", " AZ_STRING_FORMAT "]",
|
||||
AZ_STRING_ARG(kType), AZ_STRING_ARG(vType));
|
||||
}
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
AZStd::string PythonLogSymbolsComponent::FetchOutcomeType(const AZ::TypeId& typeId)
|
||||
{
|
||||
AZStd::string type = "Outcome";
|
||||
AZStd::pair<AZ::Uuid, AZ::Uuid> outcomeTypes = AZ::Utils::GetOutcomeTypes(typeId);
|
||||
|
||||
// trait info not available, so defaulting to TR_NONE
|
||||
AZStd::string_view valueT = FetchPythonTypeAndTraits(outcomeTypes.first, AZ::BehaviorParameter::TR_NONE);
|
||||
AZStd::string_view errorT = FetchPythonTypeAndTraits(outcomeTypes.second, AZ::BehaviorParameter::TR_NONE);
|
||||
if (!valueT.empty() && !errorT.empty())
|
||||
{
|
||||
type = AZStd::string::format("Outcome[" AZ_STRING_FORMAT ", " AZ_STRING_FORMAT "]",
|
||||
AZ_STRING_ARG(valueT), AZ_STRING_ARG(errorT));
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
AZStd::string PythonLogSymbolsComponent::TypeNameFallback(const AZ::TypeId& typeId)
|
||||
{
|
||||
// fall back to class data m_name
|
||||
AZ::SerializeContext* serializeContext = nullptr;
|
||||
AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext);
|
||||
|
||||
if (serializeContext)
|
||||
{
|
||||
auto classData = serializeContext->FindClassData(typeId);
|
||||
if (classData)
|
||||
{
|
||||
return classData->m_name;
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
AZStd::string_view PythonLogSymbolsComponent::FetchPythonTypeAndTraits(const AZ::TypeId& typeId, AZ::u32 traits)
|
||||
{
|
||||
if (m_typeCache.find(typeId) == m_typeCache.end())
|
||||
{
|
||||
AZStd::string type;
|
||||
if (AZ::AzTypeInfo<AZStd::string_view>::Uuid() == typeId ||
|
||||
AZ::AzTypeInfo<AZStd::string>::Uuid() == typeId)
|
||||
{
|
||||
type = "str";
|
||||
}
|
||||
else if (AZ::AzTypeInfo<char>::Uuid() == typeId &&
|
||||
traits & AZ::BehaviorParameter::TR_POINTER &&
|
||||
traits & AZ::BehaviorParameter::TR_CONST)
|
||||
{
|
||||
type = "str";
|
||||
}
|
||||
else if (AZ::AzTypeInfo<float>::Uuid() == typeId ||
|
||||
AZ::AzTypeInfo<double>::Uuid() == typeId)
|
||||
{
|
||||
type = "float";
|
||||
}
|
||||
else if (AZ::AzTypeInfo<bool>::Uuid() == typeId)
|
||||
{
|
||||
type = "bool";
|
||||
}
|
||||
else if (AZ::AzTypeInfo<AZ::s8>::Uuid() == typeId ||
|
||||
AZ::AzTypeInfo<AZ::u8>::Uuid() == typeId ||
|
||||
AZ::AzTypeInfo<AZ::s16>::Uuid() == typeId ||
|
||||
AZ::AzTypeInfo<AZ::u16>::Uuid() == typeId ||
|
||||
AZ::AzTypeInfo<AZ::s32>::Uuid() == typeId ||
|
||||
AZ::AzTypeInfo<AZ::u32>::Uuid() == typeId ||
|
||||
AZ::AzTypeInfo<AZ::s64>::Uuid() == typeId ||
|
||||
AZ::AzTypeInfo<AZ::u64>::Uuid() == typeId)
|
||||
{
|
||||
type = "int";
|
||||
}
|
||||
else if (AZ::AzTypeInfo<AZStd::vector<AZ::u8>>::Uuid() == typeId)
|
||||
{
|
||||
type = "bytes";
|
||||
}
|
||||
else if (AZ::AzTypeInfo<AZStd::any>::Uuid() == typeId)
|
||||
{
|
||||
type = "object";
|
||||
}
|
||||
else if (AZ::AzTypeInfo<void>::Uuid() == typeId)
|
||||
{
|
||||
type = "None";
|
||||
}
|
||||
else if (AZ::Utils::IsVectorContainerType(typeId))
|
||||
{
|
||||
type = FetchListType(typeId);
|
||||
}
|
||||
else if (AZ::Utils::IsMapContainerType(typeId))
|
||||
{
|
||||
type = FetchMapType(typeId);
|
||||
}
|
||||
else if (AZ::Utils::IsOutcomeType(typeId))
|
||||
{
|
||||
type = FetchOutcomeType(typeId);
|
||||
}
|
||||
else
|
||||
{
|
||||
type = TypeNameFallback(typeId);
|
||||
}
|
||||
|
||||
m_typeCache[typeId] = type;
|
||||
}
|
||||
|
||||
return m_typeCache[typeId];
|
||||
}
|
||||
|
||||
AZStd::string_view PythonLogSymbolsComponent::FetchPythonType(const AZ::BehaviorParameter& param)
|
||||
{
|
||||
AZStd::string_view pythonType = FetchPythonTypeAndTraits(param.m_typeId, param.m_traits);
|
||||
|
||||
if (pythonType.empty())
|
||||
{
|
||||
if (AZ::StringFunc::Equal(param.m_name, "void"))
|
||||
{
|
||||
return "None";
|
||||
}
|
||||
|
||||
return param.m_name;
|
||||
}
|
||||
return pythonType;
|
||||
}
|
||||
|
||||
AZ::IO::HandleType PythonLogSymbolsComponent::OpenInitFileAt(AZStd::string_view moduleName)
|
||||
{
|
||||
if (m_basePath.empty())
|
||||
{
|
||||
return AZ::IO::InvalidHandle;
|
||||
}
|
||||
|
||||
// creates the __init__.py file in this path
|
||||
AZStd::string modulePath(moduleName);
|
||||
AzFramework::StringFunc::Replace(modulePath, ".", AZ_CORRECT_FILESYSTEM_SEPARATOR_STRING);
|
||||
|
||||
AZStd::string initFile;
|
||||
AzFramework::StringFunc::Path::Join(m_basePath.c_str(), modulePath.c_str(), initFile);
|
||||
AzFramework::StringFunc::Append(initFile, AZ_CORRECT_FILESYSTEM_SEPARATOR_STRING);
|
||||
AzFramework::StringFunc::Append(initFile, "__init__.pyi");
|
||||
|
||||
AZ::IO::OpenMode openMode = AZ::IO::OpenMode::ModeText | AZ::IO::OpenMode::ModeWrite;
|
||||
AZ::IO::HandleType fileHandle = AZ::IO::InvalidHandle;
|
||||
AZ::IO::Result result = AZ::IO::FileIOBase::GetInstance()->Open(initFile.c_str(), openMode, fileHandle);
|
||||
AZ_Warning("python", result, "Could not open %s to write Python symbols.", initFile.c_str());
|
||||
if (result)
|
||||
{
|
||||
return fileHandle;
|
||||
}
|
||||
|
||||
return AZ::IO::InvalidHandle;
|
||||
}
|
||||
|
||||
AZ::IO::HandleType PythonLogSymbolsComponent::OpenModuleAt(AZStd::string_view moduleName)
|
||||
{
|
||||
if (m_basePath.empty())
|
||||
{
|
||||
return AZ::IO::InvalidHandle;
|
||||
}
|
||||
|
||||
bool resetFile = false;
|
||||
if (m_moduleSet.find(moduleName) == m_moduleSet.end())
|
||||
{
|
||||
m_moduleSet.insert(moduleName);
|
||||
resetFile = true;
|
||||
}
|
||||
|
||||
AZStd::vector<AZStd::string> moduleParts;
|
||||
AzFramework::StringFunc::Tokenize(moduleName.data(), moduleParts, '.');
|
||||
|
||||
// prepare target PYI file
|
||||
AZStd::string targetModule = moduleParts.back();
|
||||
moduleParts.pop_back();
|
||||
AzFramework::StringFunc::Append(targetModule, ".pyi");
|
||||
|
||||
AZStd::string modulePath;
|
||||
AzFramework::StringFunc::Append(modulePath, m_basePath.c_str());
|
||||
AzFramework::StringFunc::Append(modulePath, AZ_CORRECT_FILESYSTEM_SEPARATOR_STRING);
|
||||
AzFramework::StringFunc::Join(modulePath, moduleParts.begin(), moduleParts.end(), AZ_CORRECT_FILESYSTEM_SEPARATOR_STRING);
|
||||
|
||||
// prepare the path
|
||||
AZ::IO::FileIOBase::GetInstance()->CreatePath(modulePath.c_str());
|
||||
|
||||
// assemble the file path
|
||||
AzFramework::StringFunc::Append(modulePath, AZ_CORRECT_FILESYSTEM_SEPARATOR_STRING);
|
||||
AzFramework::StringFunc::Append(modulePath, targetModule.c_str());
|
||||
AzFramework::StringFunc::AssetDatabasePath::Normalize(modulePath);
|
||||
|
||||
AZ::IO::OpenMode openMode = AZ::IO::OpenMode::ModeText;
|
||||
if (AZ::IO::SystemFile::Exists(modulePath.c_str()))
|
||||
{
|
||||
openMode |= (resetFile) ? AZ::IO::OpenMode::ModeWrite : AZ::IO::OpenMode::ModeAppend;
|
||||
}
|
||||
else
|
||||
{
|
||||
openMode |= AZ::IO::OpenMode::ModeWrite;
|
||||
}
|
||||
|
||||
AZ::IO::HandleType fileHandle = AZ::IO::InvalidHandle;
|
||||
AZ::IO::Result result = AZ::IO::FileIOBase::GetInstance()->Open(modulePath.c_str(), openMode, fileHandle);
|
||||
AZ_Warning("python", result, "Could not open %s to write Python module symbols.", modulePath.c_str());
|
||||
if (result)
|
||||
{
|
||||
return fileHandle;
|
||||
}
|
||||
|
||||
return AZ::IO::InvalidHandle;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/Component/Component.h>
|
||||
#include <AzCore/IO/SystemFile.h>
|
||||
#include <AzCore/IO/FileIO.h>
|
||||
|
||||
#include <Source/PythonCommon.h>
|
||||
#include <Source/PythonUtility.h>
|
||||
#include <Source/PythonSymbolsBus.h>
|
||||
|
||||
#include <EditorPythonBindings/EditorPythonBindingsBus.h>
|
||||
#include <AzToolsFramework/API/EditorPythonConsoleBus.h>
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
class BehaviorClass;
|
||||
class BehaviorMethod;
|
||||
class BehaviorProperty;
|
||||
}
|
||||
|
||||
namespace EditorPythonBindings
|
||||
{
|
||||
//! Exports Python symbols to the log folder for Python script developers to include into their local projects
|
||||
class PythonLogSymbolsComponent
|
||||
: public AZ::Component
|
||||
, private EditorPythonBindingsNotificationBus::Handler
|
||||
, private PythonSymbolEventBus::Handler
|
||||
, private AzToolsFramework::EditorPythonConsoleInterface
|
||||
{
|
||||
public:
|
||||
AZ_COMPONENT(PythonLogSymbolsComponent, "{F1873D04-C472-41A2-8AA4-48B0CE4A5979}", AZ::Component);
|
||||
|
||||
static void Reflect(AZ::ReflectContext* context);
|
||||
|
||||
protected:
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// AZ::Component interface implementation
|
||||
void Activate() override;
|
||||
void Deactivate() override;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// EditorPythonBindingsNotificationBus::Handler
|
||||
void OnPostInitialize() override;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// PythonSymbolEventBus::Handler
|
||||
void LogClass(AZStd::string_view moduleName, AZ::BehaviorClass* behaviorClass) override;
|
||||
void LogClassWithName(AZStd::string_view moduleName, AZ::BehaviorClass* behaviorClass, AZStd::string_view className) override;
|
||||
void LogClassMethod(AZStd::string_view moduleName, AZStd::string_view globalMethodName, AZ::BehaviorClass* behaviorClass, AZ::BehaviorMethod* behaviorMethod) override;
|
||||
void LogBus(AZStd::string_view moduleName, AZStd::string_view busName, AZ::BehaviorEBus* behaviorEBus) override;
|
||||
void LogGlobalMethod(AZStd::string_view moduleName, AZStd::string_view methodName, AZ::BehaviorMethod* behaviorMethod) override;
|
||||
void LogGlobalProperty(AZStd::string_view moduleName, AZStd::string_view propertyName, AZ::BehaviorProperty* behaviorProperty) override;
|
||||
void Finalize() override;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// EditorPythonConsoleInterface
|
||||
void GetModuleList(AZStd::vector<AZStd::string_view>& moduleList) const override;
|
||||
void GetGlobalFunctionList(GlobalFunctionCollection& globalFunctionCollection) const override;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// Python type deduction
|
||||
AZStd::string_view FetchPythonTypeAndTraits(const AZ::TypeId& typeId, AZ::u32 traits);
|
||||
AZStd::string_view FetchPythonType(const AZ::BehaviorParameter& param);
|
||||
|
||||
private:
|
||||
using ModuleSet = AZStd::unordered_set<AZStd::string>;
|
||||
using GlobalFunctionEntry = AZStd::pair<const AZ::BehaviorMethod*, AZStd::string>;
|
||||
using GlobalFunctionList = AZStd::vector<GlobalFunctionEntry>;
|
||||
using GlobalFunctionMap = AZStd::unordered_map<AZStd::string_view, GlobalFunctionList>;
|
||||
using TypeMap = AZStd::unordered_map<AZ::TypeId, AZStd::string>;
|
||||
|
||||
AZStd::string m_basePath;
|
||||
ModuleSet m_moduleSet;
|
||||
GlobalFunctionMap m_globalFunctionMap;
|
||||
TypeMap m_typeCache;
|
||||
|
||||
AZStd::string FetchListType(const AZ::TypeId& typeId);
|
||||
AZStd::string FetchMapType(const AZ::TypeId& typeId);
|
||||
AZStd::string FetchOutcomeType(const AZ::TypeId& typeId);
|
||||
AZStd::string TypeNameFallback(const AZ::TypeId& typeId);
|
||||
AZ::IO::HandleType OpenInitFileAt(AZStd::string_view moduleName);
|
||||
AZ::IO::HandleType OpenModuleAt(AZStd::string_view moduleName);
|
||||
void WriteMethod(AZ::IO::HandleType handle, AZStd::string_view methodName, const AZ::BehaviorMethod& behaviorMethod, const AZ::BehaviorClass* behaviorClass);
|
||||
void WriteProperty(AZ::IO::HandleType handle, int level, AZStd::string_view propertyName, const AZ::BehaviorProperty& property, const AZ::BehaviorClass* behaviorClass);
|
||||
};
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <EditorPythonBindings/EditorPythonBindingsSymbols.h>
|
||||
|
||||
#include <Source/PythonUtility.h>
|
||||
#include <Source/PythonCommon.h>
|
||||
#include <pybind11/pybind11.h>
|
||||
|
||||
#include <AzCore/Component/Component.h>
|
||||
#include <AzCore/EBus/EBus.h>
|
||||
#include <AzCore/RTTI/AttributeReader.h>
|
||||
#include <AzCore/RTTI/BehaviorContext.h>
|
||||
#include <AzCore/std/functional.h>
|
||||
#include <AzCore/std/optional.h>
|
||||
#include <AzCore/std/string/string_view.h>
|
||||
|
||||
namespace EditorPythonBindings
|
||||
{
|
||||
//! An abstract to marshal between Behavior and Python type values
|
||||
class PythonMarshalTypeRequests
|
||||
: public AZ::EBusTraits
|
||||
{
|
||||
public:
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// EBusTraits overrides
|
||||
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
|
||||
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
|
||||
using BusIdType = AZ::TypeId;
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
using DeallocateFunction = Convert::VariableDeleter;
|
||||
using BehaviorTraits = AZ::BehaviorParameter::Traits;
|
||||
|
||||
//! Marshals a Python value to a BehaviorValueParameter plus an optional function to deallocate it after usage
|
||||
//! @return returns a pair of a flag to indicate success and an function to deallocate
|
||||
using BehaviorValueResult = AZStd::pair<bool, DeallocateFunction>;
|
||||
virtual AZStd::optional<BehaviorValueResult> PythonToBehaviorValueParameter(BehaviorTraits traits, pybind11::object pyObj, AZ::BehaviorValueParameter& outValue) = 0;
|
||||
|
||||
//! Marshals a BehaviorValueParameter to a Python value object
|
||||
//! @return returns a pair of a valid Python object and an optional function to deallocate after sent to Python
|
||||
using PythonValueResult = AZStd::pair<pybind11::object, DeallocateFunction>;
|
||||
virtual AZStd::optional<PythonValueResult> BehaviorValueParameterToPython(AZ::BehaviorValueParameter& behaviorValue) = 0;
|
||||
|
||||
//! Validates that a particular Python object can convert into a Behavior value parameter type
|
||||
virtual bool CanConvertPythonToBehaviorValue(BehaviorTraits traits, pybind11::object pyObj) const = 0;
|
||||
};
|
||||
using PythonMarshalTypeRequestBus = AZ::EBus<PythonMarshalTypeRequests>;
|
||||
|
||||
//! Handles marshaling of built-in Behavior types like numbers, strings, and lists
|
||||
class PythonMarshalComponent
|
||||
: public AZ::Component
|
||||
, protected PythonMarshalTypeRequestBus::MultiHandler
|
||||
{
|
||||
public:
|
||||
AZ_COMPONENT(PythonMarshalComponent, PythonMarshalComponentTypeId, AZ::Component);
|
||||
|
||||
static void Reflect(AZ::ReflectContext* context);
|
||||
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
|
||||
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
|
||||
static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required);
|
||||
|
||||
class TypeConverter
|
||||
{
|
||||
public:
|
||||
virtual AZStd::optional<PythonMarshalComponent::BehaviorValueResult> PythonToBehaviorValueParameter(PythonMarshalTypeRequests::BehaviorTraits traits, pybind11::object pyObj, AZ::BehaviorValueParameter& outValue) = 0;
|
||||
virtual AZStd::optional<PythonMarshalComponent::PythonValueResult> BehaviorValueParameterToPython(AZ::BehaviorValueParameter& behaviorValue) = 0;
|
||||
virtual bool CanConvertPythonToBehaviorValue(BehaviorTraits traits, pybind11::object pyObj) const = 0;
|
||||
virtual ~TypeConverter() = default;
|
||||
};
|
||||
using TypeConverterPointer = AZStd::shared_ptr<TypeConverter>;
|
||||
|
||||
void RegisterTypeConverter(const AZ::TypeId& typeId, TypeConverterPointer typeConverterPointer);
|
||||
|
||||
protected:
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// AZ::Component interface implementation
|
||||
void Activate() override;
|
||||
void Deactivate() override;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// PythonMarshalTypeRequestBus interface implementation
|
||||
AZStd::optional<PythonMarshalTypeRequests::BehaviorValueResult> PythonToBehaviorValueParameter(PythonMarshalTypeRequests::BehaviorTraits traits, pybind11::object pyObj, AZ::BehaviorValueParameter& outValue) override;
|
||||
AZStd::optional<PythonMarshalTypeRequests::PythonValueResult> BehaviorValueParameterToPython(AZ::BehaviorValueParameter& behaviorValue) override;
|
||||
bool CanConvertPythonToBehaviorValue(BehaviorTraits traits, pybind11::object pyObj) const override;
|
||||
|
||||
private:
|
||||
using TypeConverterMap = AZStd::unordered_map<AZ::TypeId, TypeConverterPointer>;
|
||||
TypeConverterMap m_typeConverterMap;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,408 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <PythonProxyBus.h>
|
||||
|
||||
#include <Source/PythonUtility.h>
|
||||
#include <Source/PythonTypeCasters.h>
|
||||
|
||||
#include <Source/PythonCommon.h>
|
||||
#include <Source/PythonSymbolsBus.h>
|
||||
#include <pybind11/embed.h>
|
||||
|
||||
#include <AzCore/PlatformDef.h>
|
||||
#include <AzCore/RTTI/BehaviorContext.h>
|
||||
#include <AzCore/RTTI/AttributeReader.h>
|
||||
#include <AzCore/std/optional.h>
|
||||
#include <AzFramework/StringFunc/StringFunc.h>
|
||||
|
||||
namespace EditorPythonBindings
|
||||
{
|
||||
namespace Internal
|
||||
{
|
||||
enum class EventType
|
||||
{
|
||||
Broadcast,
|
||||
Event,
|
||||
QueueBroadcast,
|
||||
QueueEvent
|
||||
};
|
||||
|
||||
pybind11::object InvokeEbus(AZ::BehaviorEBus& behaviorEBus, EventType eventType, AZStd::string_view eventName, pybind11::args pythonArgs)
|
||||
{
|
||||
auto eventIterator = behaviorEBus.m_events.find(eventName);
|
||||
AZ_Warning("python", eventIterator != behaviorEBus.m_events.end(), "Event %.*s does not exist in EBus %s", aznumeric_cast<int>(eventName.size()), eventName.data(), behaviorEBus.m_name.c_str());
|
||||
if (eventIterator == behaviorEBus.m_events.end())
|
||||
{
|
||||
return pybind11::cast<pybind11::none>(Py_None);
|
||||
}
|
||||
|
||||
auto& behaviorEBusEventSender = eventIterator->second;
|
||||
|
||||
switch (eventType)
|
||||
{
|
||||
case EventType::Broadcast:
|
||||
{
|
||||
AZ_Warning("python", behaviorEBusEventSender.m_broadcast, "EventSender: function %.*s in EBus %s does not support the bus.Broadcast event type.", static_cast<int>(eventName.size()), eventName.data(), behaviorEBus.m_name.c_str());
|
||||
if (behaviorEBusEventSender.m_broadcast)
|
||||
{
|
||||
return Call::StaticMethod(behaviorEBusEventSender.m_broadcast, pythonArgs);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EventType::Event:
|
||||
{
|
||||
AZ_Warning("python", behaviorEBusEventSender.m_event, "EventSender: function %.*s in EBus %s does not support the bus.Event event type.", static_cast<int>(eventName.size()), eventName.data(), behaviorEBus.m_name.c_str());
|
||||
if (behaviorEBusEventSender.m_event)
|
||||
{
|
||||
return Call::StaticMethod(behaviorEBusEventSender.m_event, pythonArgs);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EventType::QueueBroadcast:
|
||||
{
|
||||
AZ_Warning("python", behaviorEBusEventSender.m_queueBroadcast, "EventSender: function %.*s in EBus %s does not support the bus.QueueBroadcast event type.", static_cast<int>(eventName.size()), eventName.data(), behaviorEBus.m_name.c_str());
|
||||
if (behaviorEBusEventSender.m_queueBroadcast)
|
||||
{
|
||||
return Call::StaticMethod(behaviorEBusEventSender.m_queueBroadcast, pythonArgs);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EventType::QueueEvent:
|
||||
{
|
||||
AZ_Warning("python", behaviorEBusEventSender.m_queueEvent, "EventSender: function %.*s in EBus %s does not support the bus.QueueEvent event type.", static_cast<int>(eventName.size()), eventName.data(), behaviorEBus.m_name.c_str());
|
||||
if (behaviorEBusEventSender.m_queueEvent)
|
||||
{
|
||||
return Call::StaticMethod(behaviorEBusEventSender.m_queueEvent, pythonArgs);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
AZ_Error("python", false, "Unknown EBus call type %d", eventType);
|
||||
break;
|
||||
}
|
||||
|
||||
return pybind11::cast<pybind11::none>(Py_None);
|
||||
}
|
||||
|
||||
class PythonProxyNotificationHandler final
|
||||
{
|
||||
public:
|
||||
AZ_CLASS_ALLOCATOR(PythonProxyNotificationHandler, AZ::SystemAllocator, 0);
|
||||
|
||||
PythonProxyNotificationHandler(AZStd::string_view busName)
|
||||
{
|
||||
AZ::BehaviorContext* behaviorContext(nullptr);
|
||||
AZ::ComponentApplicationBus::BroadcastResult(behaviorContext, &AZ::ComponentApplicationRequests::GetBehaviorContext);
|
||||
if (!behaviorContext)
|
||||
{
|
||||
AZ_Error("python", false, "A behavior context is required to bind the buses!");
|
||||
return;
|
||||
}
|
||||
|
||||
auto behaviorEBusEntry = behaviorContext->m_ebuses.find(busName);
|
||||
if (behaviorEBusEntry == behaviorContext->m_ebuses.end())
|
||||
{
|
||||
AZ_Error("python", false, "There is no EBus by the name of %.*s", static_cast<int>(busName.size()), busName.data());
|
||||
return;
|
||||
}
|
||||
|
||||
AZ_Assert(behaviorEBusEntry->second, "A null EBus:%s is in the Behavior Context!", behaviorEBusEntry->first.c_str());
|
||||
m_ebus = behaviorEBusEntry->second;
|
||||
}
|
||||
|
||||
~PythonProxyNotificationHandler()
|
||||
{
|
||||
Disconnect();
|
||||
}
|
||||
|
||||
bool IsConnected() const
|
||||
{
|
||||
if (m_handler)
|
||||
{
|
||||
return m_handler->IsConnected();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Connect(pybind11::object busId)
|
||||
{
|
||||
if (!m_ebus)
|
||||
{
|
||||
AZ_Error("python", false, "EBus not set.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!CreateHandler(*m_ebus))
|
||||
{
|
||||
AZ_Error("python", false, "Could not create a handler for ebus");
|
||||
return false;
|
||||
}
|
||||
|
||||
// does the EBus require an address to connect?
|
||||
if (m_ebus->m_idParam.m_typeId.IsNull())
|
||||
{
|
||||
AZ_Warning("python", busId.is_none(), "Connecting to an singleton EBus but was given a non-None busId(%s)", pybind11::cast<AZStd::string>(busId).c_str());
|
||||
return m_handler->Connect();
|
||||
}
|
||||
else if (busId.is_none())
|
||||
{
|
||||
AZ_Warning("python", busId.is_none(), "Connecting to an EBus that requires an address but was given a None busId");
|
||||
return false;
|
||||
}
|
||||
|
||||
Convert::StackVariableAllocator stackVariableAllocator;
|
||||
AZ::BehaviorValueParameter busAddress;
|
||||
|
||||
if (!Convert::PythonToBehaviorValueParameter(m_ebus->m_idParam, busId, busAddress, stackVariableAllocator))
|
||||
{
|
||||
AZ_Warning("python", busId.is_none(), "Could not convert busId(%s) to address type (%s)",
|
||||
pybind11::cast<AZStd::string>(busId).c_str(), m_ebus->m_idParam.m_typeId.ToString<AZStd::string>().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
return m_handler->Connect(&busAddress);
|
||||
}
|
||||
|
||||
bool Disconnect()
|
||||
{
|
||||
if (!m_handler)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
m_handler->Disconnect();
|
||||
|
||||
if (m_ebus)
|
||||
{
|
||||
DestroyHandler(*m_ebus);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AddCallback(AZStd::string_view eventName, pybind11::function callback)
|
||||
{
|
||||
if (!PyCallable_Check(callback.ptr()))
|
||||
{
|
||||
AZ_Error("python", false, "The callback needs to be a callable python function.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_handler)
|
||||
{
|
||||
AZ_Error("python", false, "No EBus connection deteced; missing call or failed call to connect()?");
|
||||
return false;
|
||||
}
|
||||
|
||||
const AZ::BehaviorEBusHandler::EventArray& events = m_handler->GetEvents();
|
||||
for (int iEvent = 0; iEvent < static_cast<int>(events.size()); ++iEvent)
|
||||
{
|
||||
const AZ::BehaviorEBusHandler::BusForwarderEvent& e = events[iEvent];
|
||||
if (eventName == e.m_name)
|
||||
{
|
||||
AZStd::string eventNameValue{ eventName };
|
||||
const auto& callbackIt = m_callbackMap.find(eventNameValue);
|
||||
AZ_Warning("python", m_callbackMap.end() == callbackIt, "Replacing callback for eventName:%s", eventNameValue.c_str());
|
||||
m_callbackMap[eventNameValue] = callback;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected:
|
||||
void DestroyHandler(const AZ::BehaviorEBus& ebus)
|
||||
{
|
||||
if (m_handler)
|
||||
{
|
||||
AZ_Warning("python", ebus.m_destroyHandler, "Ebus (%s) does not have a handler destroyer.", ebus.m_name.c_str());
|
||||
if (ebus.m_destroyHandler)
|
||||
{
|
||||
ebus.m_destroyHandler->Invoke(m_handler);
|
||||
}
|
||||
}
|
||||
m_handler = nullptr;
|
||||
m_callbackMap.clear();
|
||||
}
|
||||
|
||||
bool CreateHandler(const AZ::BehaviorEBus& ebus)
|
||||
{
|
||||
DestroyHandler(ebus);
|
||||
|
||||
AZ_Warning("python", ebus.m_createHandler, "Ebus (%s) does not have a handler creator.", ebus.m_name.c_str());
|
||||
if (!ebus.m_createHandler)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ebus.m_createHandler->InvokeResult(m_handler))
|
||||
{
|
||||
AZ_Warning("python", ebus.m_createHandler, "Ebus (%s) failed to create a handler.", ebus.m_name.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_handler)
|
||||
{
|
||||
const AZ::BehaviorEBusHandler::EventArray& events = m_handler->GetEvents();
|
||||
for (int iEvent = 0; iEvent < static_cast<int>(events.size()); ++iEvent)
|
||||
{
|
||||
const AZ::BehaviorEBusHandler::BusForwarderEvent& e = events[iEvent];
|
||||
m_handler->InstallGenericHook(iEvent, &PythonProxyNotificationHandler::OnEventGenericHook, this);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void OnEventGenericHook(void* userData, const char* eventName, int eventIndex, AZ::BehaviorValueParameter* result, int numParameters, AZ::BehaviorValueParameter* parameters)
|
||||
{
|
||||
reinterpret_cast<PythonProxyNotificationHandler*>(userData)->OnEventGenericHook(eventName, eventIndex, result, numParameters, parameters);
|
||||
}
|
||||
|
||||
void OnEventGenericHook(const char* eventName, [[maybe_unused]] int eventIndex, [[maybe_unused]] AZ::BehaviorValueParameter* result, int numParameters, AZ::BehaviorValueParameter* parameters)
|
||||
{
|
||||
// find the callback for the event
|
||||
const auto& callbackEntry = m_callbackMap.find(eventName);
|
||||
if (callbackEntry == m_callbackMap.end())
|
||||
{
|
||||
return;
|
||||
}
|
||||
pybind11::function callback = callbackEntry->second;
|
||||
|
||||
// build the parameters to send to callback
|
||||
Convert::StackVariableAllocator stackVariableAllocator;
|
||||
pybind11::tuple pythonParamters(numParameters);
|
||||
for (int index = 0; index < numParameters; ++index)
|
||||
{
|
||||
AZ::BehaviorValueParameter& behaviorValueParameter{ *(parameters + index) };
|
||||
pythonParamters[index] = Convert::BehaviorValueParameterToPython(behaviorValueParameter, stackVariableAllocator);
|
||||
|
||||
if (pythonParamters[index].is_none())
|
||||
{
|
||||
AZ_Warning("python", false, "Ebus(%s) event(%s) failed to convert parameter at index(%d)", m_ebus->m_name.c_str(), eventName, index);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
pybind11::object pyResult = callback(pythonParamters);
|
||||
|
||||
// store the result
|
||||
if (result && pyResult.is_none() == false)
|
||||
{
|
||||
// reset/prepare the stack allocator
|
||||
m_stackVariableAllocator = {};
|
||||
|
||||
AZ::BehaviorValueParameter coverted;
|
||||
const AZ::u32 traits = result->m_traits;
|
||||
if (Convert::PythonToBehaviorValueParameter(*result, pyResult, coverted, m_stackVariableAllocator))
|
||||
{
|
||||
result->Set(coverted);
|
||||
result->m_value = coverted.GetValueAddress();
|
||||
if ((traits & AZ::BehaviorParameter::TR_POINTER) == AZ::BehaviorParameter::TR_POINTER)
|
||||
{
|
||||
result->m_value = &result->m_value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch ([[maybe_unused]] const std::exception& e)
|
||||
{
|
||||
AZ_Error("python", false, "Python callback threw an exception %s", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
const AZ::BehaviorEBus* m_ebus = nullptr;
|
||||
AZ::BehaviorEBusHandler* m_handler = nullptr;
|
||||
AZStd::unordered_map<AZStd::string, pybind11::function> m_callbackMap;
|
||||
Convert::StackVariableAllocator m_stackVariableAllocator;
|
||||
};
|
||||
}
|
||||
|
||||
namespace PythonProxyBusManagement
|
||||
{
|
||||
void CreateSubmodule(pybind11::module baseModule)
|
||||
{
|
||||
AZ::BehaviorContext* behaviorContext(nullptr);
|
||||
AZ::ComponentApplicationBus::BroadcastResult(behaviorContext, &AZ::ComponentApplicationRequests::GetBehaviorContext);
|
||||
if (!behaviorContext)
|
||||
{
|
||||
AZ_Error("python", false, "A behavior context is required to bind the buses!");
|
||||
return;
|
||||
}
|
||||
|
||||
auto busModule = baseModule.def_submodule("bus");
|
||||
Module::PackageMapType modulePackageMap;
|
||||
|
||||
// export possible ways an EBus can be invoked
|
||||
pybind11::enum_<Internal::EventType>(busModule, "EventType")
|
||||
.value("Event", Internal::EventType::Event)
|
||||
.value("Broadcast", Internal::EventType::Broadcast)
|
||||
.value("QueueEvent", Internal::EventType::QueueEvent)
|
||||
.value("QueueBroadcast", Internal::EventType::QueueBroadcast)
|
||||
.export_values();
|
||||
|
||||
// export the EBuses flagged for Automation or Common scope
|
||||
for (auto&& busEntry : behaviorContext->m_ebuses)
|
||||
{
|
||||
AZStd::string& ebusName = busEntry.first;
|
||||
AZ::BehaviorEBus* behaviorEBus = busEntry.second;
|
||||
if (Scope::IsBehaviorFlaggedForEditor(behaviorEBus->m_attributes))
|
||||
{
|
||||
auto busCaller = pybind11::cpp_function([behaviorEBus](Internal::EventType eventType, AZStd::string_view eventName, pybind11::args pythonArgs)
|
||||
{
|
||||
return Internal::InvokeEbus(*behaviorEBus, eventType, eventName, pythonArgs);
|
||||
});
|
||||
|
||||
auto createPythonProxyNotificationHandler = pybind11::cpp_function([behaviorEBus]()
|
||||
{
|
||||
return aznew Internal::PythonProxyNotificationHandler(behaviorEBus->m_name.c_str());
|
||||
});
|
||||
|
||||
pybind11::module thisBusModule = busModule;
|
||||
auto moduleName = Module::GetName(behaviorEBus->m_attributes);
|
||||
if (moduleName)
|
||||
{
|
||||
// this will place the bus into either:
|
||||
// 1) if the module is valid, then azlmbr.<module name>.<ebus name>
|
||||
// 2) or, then azlmbr.bus.<ebus name>
|
||||
thisBusModule = Module::DeterminePackageModule(modulePackageMap, *moduleName, baseModule, busModule, true);
|
||||
}
|
||||
|
||||
// for each notification handler type, make a convenient Python type to make the script more Python-ic
|
||||
if (behaviorEBus->m_createHandler && behaviorEBus->m_destroyHandler)
|
||||
{
|
||||
AZStd::string ebusNotificationName{ AZStd::string::format("%sHandler", ebusName.c_str()) };
|
||||
thisBusModule.attr(ebusNotificationName.c_str()) = createPythonProxyNotificationHandler;
|
||||
}
|
||||
|
||||
// is a request EBus
|
||||
thisBusModule.attr(ebusName.c_str()) = busCaller;
|
||||
|
||||
// log the bus symbol
|
||||
AZStd::string subModuleName = pybind11::cast<AZStd::string>(thisBusModule.attr("__name__"));
|
||||
PythonSymbolEventBus::Broadcast(&PythonSymbolEventBus::Events::LogBus, subModuleName, ebusName, behaviorEBus);
|
||||
}
|
||||
}
|
||||
|
||||
// export possible ways an EBus can be invoked
|
||||
pybind11::class_<Internal::PythonProxyNotificationHandler>(busModule, "NotificationHandler")
|
||||
.def(pybind11::init<AZStd::string_view>())
|
||||
.def("is_connected", &Internal::PythonProxyNotificationHandler::IsConnected)
|
||||
.def("connect", &Internal::PythonProxyNotificationHandler::Connect, pybind11::arg("busId") = pybind11::none())
|
||||
.def("disconnect", &Internal::PythonProxyNotificationHandler::Disconnect)
|
||||
.def("add_callback", &Internal::PythonProxyNotificationHandler::AddCallback)
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <Source/PythonCommon.h>
|
||||
#include <pybind11/pybind11.h>
|
||||
|
||||
namespace EditorPythonBindings
|
||||
{
|
||||
namespace PythonProxyBusManagement
|
||||
{
|
||||
//! Creates the 'azlmbr.bus' module so that Python script can use Lumberyard buses
|
||||
void CreateSubmodule(pybind11::module module);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,956 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <PythonProxyObject.h>
|
||||
|
||||
#include <AzFramework/StringFunc/StringFunc.h>
|
||||
|
||||
#include <Source/PythonCommon.h>
|
||||
#include <Source/PythonUtility.h>
|
||||
#include <Source/PythonMarshalComponent.h>
|
||||
#include <Source/PythonTypeCasters.h>
|
||||
#include <Source/PythonSymbolsBus.h>
|
||||
|
||||
#include <pybind11/embed.h>
|
||||
|
||||
#include <AzCore/PlatformDef.h>
|
||||
#include <AzCore/RTTI/BehaviorContext.h>
|
||||
#include <AzCore/RTTI/AttributeReader.h>
|
||||
|
||||
namespace EditorPythonBindings
|
||||
{
|
||||
namespace Operator
|
||||
{
|
||||
constexpr const char s_isEqual[] = "__eq__";
|
||||
constexpr const char s_notEqual[] = "__ne__";
|
||||
constexpr const char s_greaterThan[] = "__gt__";
|
||||
constexpr const char s_greaterThanOrEqual[] = "__ge__";
|
||||
constexpr const char s_lessThan[] = "__lt__";
|
||||
constexpr const char s_lessThanOrEqual[] = "__le__";
|
||||
}
|
||||
|
||||
namespace Builtins
|
||||
{
|
||||
constexpr const char s_repr[] = "__repr__";
|
||||
constexpr const char s_str[] = "__str__";
|
||||
}
|
||||
|
||||
namespace Naming
|
||||
{
|
||||
void StripReplace(AZStd::string& inout, AZStd::string_view prefix, char bracketIn, char bracketOut, AZStd::string_view replacement)
|
||||
{
|
||||
size_t pos = inout.find(prefix);
|
||||
while (pos != AZStd::string::npos)
|
||||
{
|
||||
const char* const start = &inout[pos];
|
||||
pos += prefix.size();
|
||||
const char* end = &inout[pos];
|
||||
int bracketCount = 1;
|
||||
do
|
||||
{
|
||||
if (pos == inout.size())
|
||||
{
|
||||
break;
|
||||
}
|
||||
else if (inout[pos] == bracketIn)
|
||||
{
|
||||
bracketCount++;
|
||||
}
|
||||
else if (inout[pos] == bracketOut)
|
||||
{
|
||||
bracketCount--;
|
||||
}
|
||||
end++;
|
||||
pos++;
|
||||
}
|
||||
while (bracketCount > 0);
|
||||
|
||||
AZStd::string target{ start, end };
|
||||
AZ::StringFunc::Replace(inout, target.c_str(), replacement.data());
|
||||
|
||||
pos = inout.find(prefix);
|
||||
}
|
||||
}
|
||||
|
||||
AZStd::optional<AZStd::string> GetPythonSyntax(const AZ::BehaviorClass& behaviorClass)
|
||||
{
|
||||
constexpr const char* invalidCharacters = " :<>,*&";
|
||||
if (behaviorClass.m_name.find_first_of(invalidCharacters) == AZStd::string::npos)
|
||||
{
|
||||
// this class name is not using invalid characters
|
||||
return AZStd::nullopt;
|
||||
}
|
||||
|
||||
AZStd::string syntaxName = behaviorClass.m_name;
|
||||
|
||||
// replace common core template types and name spaces like AZStd
|
||||
StripReplace(syntaxName, "AZStd::basic_string<", '<', '>', "string");
|
||||
AZ::StringFunc::Replace(syntaxName, "AZStd", "");
|
||||
|
||||
AZStd::vector<AZStd::string> tokens;
|
||||
AZ::StringFunc::Tokenize(syntaxName, tokens, invalidCharacters, false, false);
|
||||
syntaxName.clear();
|
||||
AZ::StringFunc::Join(syntaxName, tokens.begin(), tokens.end(), "_");
|
||||
return syntaxName;
|
||||
}
|
||||
}
|
||||
|
||||
PythonProxyObject::PythonProxyObject(const AZ::TypeId& typeId)
|
||||
{
|
||||
const AZ::BehaviorClass* behaviorClass = AZ::BehaviorContextHelper::GetClass(typeId);
|
||||
if (behaviorClass)
|
||||
{
|
||||
CreateDefault(behaviorClass);
|
||||
}
|
||||
}
|
||||
|
||||
PythonProxyObject::PythonProxyObject(const char* typeName)
|
||||
{
|
||||
SetByTypeName(typeName);
|
||||
}
|
||||
|
||||
pybind11::object PythonProxyObject::Construct(const AZ::BehaviorClass& behaviorClass, pybind11::args args)
|
||||
{
|
||||
// nothing to construct with ...
|
||||
if (args.size() == 0 || behaviorClass.m_constructors.empty())
|
||||
{
|
||||
if (!CreateDefault(&behaviorClass))
|
||||
{
|
||||
return pybind11::cast<pybind11::none>(Py_None);
|
||||
}
|
||||
return pybind11::cast(this);
|
||||
}
|
||||
|
||||
// find the right constructor
|
||||
for (AZ::BehaviorMethod* constructor : behaviorClass.m_constructors)
|
||||
{
|
||||
const size_t numArgsPlusSelf = args.size() + 1;
|
||||
AZ_Error("python", constructor, "Missing constructor value in behavior class %s", behaviorClass.m_name.c_str());
|
||||
if (constructor && constructor->GetNumArguments() == numArgsPlusSelf)
|
||||
{
|
||||
bool match = true;
|
||||
for (size_t index = 0; index < args.size(); ++index)
|
||||
{
|
||||
const AZ::BehaviorParameter* behaviorArg = constructor->GetArgument(index + 1);
|
||||
pybind11::object pythonArg = args[index];
|
||||
if (!behaviorArg || !CanConvertPythonToBehaviorValue(*behaviorArg, pythonArg))
|
||||
{
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (match)
|
||||
{
|
||||
// prepare wrapped object instance
|
||||
m_wrappedObject.m_address = behaviorClass.Allocate();
|
||||
m_wrappedObject.m_typeId = behaviorClass.m_typeId;
|
||||
PrepareWrappedObject(behaviorClass);
|
||||
|
||||
// execute constructor
|
||||
Call::ClassMethod(constructor, m_wrappedObject, args);
|
||||
return pybind11::cast(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
return pybind11::cast<pybind11::none>(Py_None);
|
||||
}
|
||||
|
||||
bool PythonProxyObject::CanConvertPythonToBehaviorValue(const AZ::BehaviorParameter& behaviorArg, pybind11::object pythonArg) const
|
||||
{
|
||||
bool canConvert = false;
|
||||
PythonMarshalTypeRequestBus::EventResult(
|
||||
canConvert,
|
||||
behaviorArg.m_typeId,
|
||||
&PythonMarshalTypeRequestBus::Events::CanConvertPythonToBehaviorValue,
|
||||
static_cast<PythonMarshalTypeRequests::BehaviorTraits>(behaviorArg.m_traits),
|
||||
pythonArg);
|
||||
|
||||
if (canConvert)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// is already a wrapped type?
|
||||
if (pybind11::isinstance<PythonProxyObject>(pythonArg))
|
||||
{
|
||||
auto&& proxyObj = pybind11::cast<PythonProxyObject*>(pythonArg);
|
||||
if (proxyObj)
|
||||
{
|
||||
return behaviorArg.m_azRtti->IsTypeOf(proxyObj->GetWrappedType().value());
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
PythonProxyObject::PythonProxyObject(const AZ::BehaviorObject& object)
|
||||
{
|
||||
const AZ::BehaviorClass* behaviorClass = AZ::BehaviorContextHelper::GetClass(object.m_typeId);
|
||||
if (behaviorClass)
|
||||
{
|
||||
m_wrappedObject = behaviorClass->Clone(object);
|
||||
PrepareWrappedObject(*behaviorClass);
|
||||
}
|
||||
}
|
||||
|
||||
PythonProxyObject::~PythonProxyObject()
|
||||
{
|
||||
ReleaseWrappedObject();
|
||||
}
|
||||
|
||||
const char* PythonProxyObject::GetWrappedTypeName() const
|
||||
{
|
||||
return m_wrappedObjectTypeName.c_str();
|
||||
}
|
||||
|
||||
void PythonProxyObject::SetPropertyValue(const char* attributeName, pybind11::object value)
|
||||
{
|
||||
if (!m_wrappedObject.IsValid())
|
||||
{
|
||||
PyErr_SetString(PyExc_RuntimeError, "The wrapped Proxy Object has not been setup correctly; missing call set_type()?");
|
||||
AZ_Error("python", false, "The wrapped Proxy Object has not been setup correctly; missing call set_type()?");
|
||||
return;
|
||||
}
|
||||
|
||||
auto behaviorPropertyIter = m_properties.find(AZ::Crc32(attributeName));
|
||||
if (behaviorPropertyIter != m_properties.end())
|
||||
{
|
||||
AZ::BehaviorProperty* property = behaviorPropertyIter->second;
|
||||
AZ_Error("python", property->m_setter, "%s is not a writable property in class %s.", attributeName, m_wrappedObjectTypeName.c_str());
|
||||
if (property->m_setter)
|
||||
{
|
||||
EditorPythonBindings::Call::ClassMethod(property->m_setter, m_wrappedObject, pybind11::args(pybind11::make_tuple(value)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pybind11::object PythonProxyObject::GetPropertyValue(const char* attributeName)
|
||||
{
|
||||
if (!m_wrappedObject.IsValid())
|
||||
{
|
||||
PyErr_SetString(PyExc_RuntimeError, "The wrapped Proxy Object has not been setup correctly; missing call set_type()?");
|
||||
AZ_Error("python", false, "The wrapped Proxy Object has not been setup correctly; missing call set_type()?");
|
||||
return pybind11::cast<pybind11::none>(Py_None);
|
||||
}
|
||||
|
||||
AZ::Crc32 crcAttributeName(attributeName);
|
||||
|
||||
// the attribute could refer to a method
|
||||
auto methodEntry = m_methods.find(crcAttributeName);
|
||||
if (methodEntry != m_methods.end())
|
||||
{
|
||||
AZ::BehaviorMethod* method = methodEntry->second;
|
||||
return pybind11::cpp_function([this, method](pybind11::args pythonArgs)
|
||||
{
|
||||
return EditorPythonBindings::Call::ClassMethod(method, m_wrappedObject, pythonArgs);
|
||||
});
|
||||
}
|
||||
|
||||
// the attribute could refer to a property
|
||||
auto behaviorPropertyIter = m_properties.find(crcAttributeName);
|
||||
if (behaviorPropertyIter != m_properties.end())
|
||||
{
|
||||
AZ::BehaviorProperty* property = behaviorPropertyIter->second;
|
||||
AZ_Error("python", property->m_getter, "%s is not a readable property in class %s.", attributeName, m_wrappedObjectTypeName.c_str());
|
||||
if (property->m_getter)
|
||||
{
|
||||
return EditorPythonBindings::Call::ClassMethod(property->m_getter, m_wrappedObject, pybind11::args());
|
||||
}
|
||||
}
|
||||
|
||||
return pybind11::cast<pybind11::none>(Py_None);
|
||||
}
|
||||
|
||||
bool PythonProxyObject::SetByTypeName(const char* typeName)
|
||||
{
|
||||
const AZ::BehaviorClass* behaviorClass = AZ::BehaviorContextHelper::GetClass(AZStd::string(typeName));
|
||||
if (behaviorClass)
|
||||
{
|
||||
return CreateDefault(behaviorClass);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
pybind11::object PythonProxyObject::Invoke(const char* methodName, pybind11::args pythonArgs)
|
||||
{
|
||||
const AZ::BehaviorClass* behaviorClass = AZ::BehaviorContextHelper::GetClass(m_wrappedObject.m_typeId);
|
||||
if (behaviorClass)
|
||||
{
|
||||
auto behaviorMethodIter = behaviorClass->m_methods.find(methodName);
|
||||
if (behaviorMethodIter != behaviorClass->m_methods.end())
|
||||
{
|
||||
AZ::BehaviorMethod* method = behaviorMethodIter->second;
|
||||
AZ_Error("python", method, "%s is not a method in class %s!", methodName, m_wrappedObjectTypeName.c_str());
|
||||
|
||||
if (method && PythonProxyObjectManagement::IsMemberLike(*method, m_wrappedObject.m_typeId))
|
||||
{
|
||||
return EditorPythonBindings::Call::ClassMethod(method, m_wrappedObject, pythonArgs);
|
||||
}
|
||||
}
|
||||
}
|
||||
return pybind11::cast<pybind11::none>(Py_None);
|
||||
}
|
||||
|
||||
AZStd::optional<AZ::TypeId> PythonProxyObject::GetWrappedType() const
|
||||
{
|
||||
if (m_wrappedObject.IsValid())
|
||||
{
|
||||
return AZStd::make_optional(m_wrappedObject.m_typeId);
|
||||
}
|
||||
return AZStd::nullopt;
|
||||
}
|
||||
|
||||
AZStd::optional<AZ::BehaviorObject*> PythonProxyObject::GetBehaviorObject()
|
||||
{
|
||||
if (m_wrappedObject.IsValid())
|
||||
{
|
||||
return AZStd::make_optional(&m_wrappedObject);
|
||||
}
|
||||
return AZStd::nullopt;
|
||||
}
|
||||
|
||||
void PythonProxyObject::PrepareWrappedObject(const AZ::BehaviorClass& behaviorClass)
|
||||
{
|
||||
m_ownership = Ownership::Owned;
|
||||
m_wrappedObjectTypeName = behaviorClass.m_name;
|
||||
|
||||
// is this Behavior Class flagged to usage for tool bindings?
|
||||
if (!Scope::IsBehaviorFlaggedForEditor(behaviorClass.m_attributes))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PopulateComparisonOperators(behaviorClass);
|
||||
PopulateMethodsAndProperties(behaviorClass);
|
||||
|
||||
for (auto&& baseClassId : behaviorClass.m_baseClasses)
|
||||
{
|
||||
const AZ::BehaviorClass* baseClass = AZ::BehaviorContextHelper::GetClass(baseClassId);
|
||||
if (baseClass)
|
||||
{
|
||||
PopulateMethodsAndProperties(*baseClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PythonProxyObject::PopulateComparisonOperators(const AZ::BehaviorClass& behaviorClass)
|
||||
{
|
||||
using namespace AZ::Script;
|
||||
for (auto&& equalMethodCandidatePair : behaviorClass.m_methods)
|
||||
{
|
||||
const AZ::AttributeArray& attributes = equalMethodCandidatePair.second->m_attributes;
|
||||
AZ::Attribute* operatorAttribute = AZ::FindAttribute(Attributes::Operator, attributes);
|
||||
if (!operatorAttribute)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Attributes::OperatorType operatorType;
|
||||
AZ::AttributeReader scopeAttributeReader(nullptr, operatorAttribute);
|
||||
if (!scopeAttributeReader.Read<Attributes::OperatorType>(operatorType))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
AZ::Crc32 namedKey;
|
||||
if (operatorType == Attributes::OperatorType::Equal)
|
||||
{
|
||||
namedKey = AZ::Crc32{ Operator::s_isEqual };
|
||||
}
|
||||
else if (operatorType == Attributes::OperatorType::LessThan)
|
||||
{
|
||||
namedKey = AZ::Crc32{ Operator::s_lessThan };
|
||||
}
|
||||
else if (operatorType == Attributes::OperatorType::LessEqualThan)
|
||||
{
|
||||
namedKey = AZ::Crc32{ Operator::s_lessThanOrEqual };
|
||||
}
|
||||
else
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (m_methods.find(namedKey) == m_methods.end())
|
||||
{
|
||||
m_methods[namedKey] = equalMethodCandidatePair.second;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PythonProxyObject::PopulateMethodsAndProperties(const AZ::BehaviorClass& behaviorClass)
|
||||
{
|
||||
AZStd::string baseName;
|
||||
|
||||
// cache all the methods for this behavior class
|
||||
for (const auto& methodEntry : behaviorClass.m_methods)
|
||||
{
|
||||
AZ::BehaviorMethod* method = methodEntry.second;
|
||||
AZ_Error("python", method, "Missing method entry:%s value in behavior class:%s", methodEntry.first.c_str(), m_wrappedObjectTypeName.c_str());
|
||||
if (method && PythonProxyObjectManagement::IsMemberLike(*method, m_wrappedObject.m_typeId))
|
||||
{
|
||||
baseName = methodEntry.first;
|
||||
Scope::FetchScriptName(method->m_attributes, baseName);
|
||||
AZ::Crc32 namedKey(baseName);
|
||||
if (m_methods.find(namedKey) == m_methods.end())
|
||||
{
|
||||
m_methods[namedKey] = method;
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_TracePrintf("python", "Skipping duplicate method named %s\n", baseName.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// cache all the properties for this behavior class
|
||||
for (const auto& behaviorProperty : behaviorClass.m_properties)
|
||||
{
|
||||
AZ::BehaviorProperty* property = behaviorProperty.second;
|
||||
AZ_Error("python", property, "Missing property %s in behavior class:%s", behaviorProperty.first.c_str(), m_wrappedObjectTypeName.c_str());
|
||||
if (property)
|
||||
{
|
||||
baseName = behaviorProperty.first;
|
||||
Scope::FetchScriptName(property->m_attributes, baseName);
|
||||
AZ::Crc32 namedKey(baseName);
|
||||
if (m_properties.find(namedKey) == m_properties.end())
|
||||
{
|
||||
m_properties[namedKey] = property;
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_TracePrintf("python", "Skipping duplicate property named %s\n", baseName.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pybind11::object PythonProxyObject::GetWrappedObjectRepr()
|
||||
{
|
||||
const AZ::Crc32 reprNamedKey { Builtins::s_repr };
|
||||
|
||||
// Attempt to call the object's __repr__ implementation first to get the most accurate representation.
|
||||
AZ::BehaviorMethod* reprMethod = nullptr;
|
||||
auto methodEntry = m_methods.find(reprNamedKey);
|
||||
if (methodEntry != m_methods.end())
|
||||
{
|
||||
reprMethod = methodEntry->second;
|
||||
|
||||
pybind11::object result = Call::ClassMethod(reprMethod, m_wrappedObject, pybind11::args());
|
||||
if (!result.is_none())
|
||||
{
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Warning("python", false, "The %s method in type (%s) did not return a valid value.", Builtins::s_repr, m_wrappedObjectTypeName.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// There's no __repr__ implementation in the object, so use a basic representation and cache it.
|
||||
AZ_Warning("python", false, "The type (%s) does not implement the %s method.", m_wrappedObjectTypeName.c_str(), Builtins::s_repr);
|
||||
if (m_wrappedObjectCachedRepr.empty())
|
||||
{
|
||||
pybind11::module builtinsModule = pybind11::module::import("builtins");
|
||||
auto idFunc = builtinsModule.attr("id");
|
||||
pybind11::object resId = idFunc(this);
|
||||
AZStd::string wrappedObjectId = pybind11::str(resId).operator std::string().c_str();
|
||||
m_wrappedObjectCachedRepr = AZStd::string::format("<%s via PythonProxyObject at %s>", m_wrappedObjectTypeName.c_str(), wrappedObjectId.c_str());
|
||||
}
|
||||
|
||||
return pybind11::str(m_wrappedObjectCachedRepr.c_str());
|
||||
}
|
||||
|
||||
pybind11::object PythonProxyObject::GetWrappedObjectStr()
|
||||
{
|
||||
// Inspect methods with attributes to find the ToString attribute
|
||||
AZ::BehaviorMethod* strMethod = nullptr;
|
||||
|
||||
using namespace AZ::Script;
|
||||
for (auto&& strMethodCandidatePair : m_methods)
|
||||
{
|
||||
const AZ::AttributeArray& attributes = strMethodCandidatePair.second->m_attributes;
|
||||
AZ::Attribute* operatorAttribute = AZ::FindAttribute(Attributes::Operator, attributes);
|
||||
if (!operatorAttribute)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Attributes::OperatorType operatorType;
|
||||
AZ::AttributeReader scopeAttributeReader(nullptr, operatorAttribute);
|
||||
if (!scopeAttributeReader.Read<Attributes::OperatorType>(operatorType))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (operatorType == Attributes::OperatorType::ToString)
|
||||
{
|
||||
if (strMethod == nullptr)
|
||||
{
|
||||
strMethod = strMethodCandidatePair.second;
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Warning("python", false, "The type (%s) has more than one method with OperatorType::ToString, using the first found.", m_wrappedObjectTypeName.c_str());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (strMethod != nullptr)
|
||||
{
|
||||
pybind11::object result = Call::ClassMethod(strMethod, m_wrappedObject, pybind11::args());
|
||||
if (!result.is_none())
|
||||
{
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Warning("python", false, "The %s method in type (%s) did not return a valid value.", Builtins::s_str, m_wrappedObjectTypeName.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to __repr__ because there's no __str__ implementation in the object,
|
||||
// so use a basic representation and cache it.
|
||||
AZ_TracePrintf("python", "The type (%s) does not implement the %s method or did not return a valid value, trying %s.", m_wrappedObjectTypeName.c_str(), Builtins::s_str, Builtins::s_repr);
|
||||
return GetWrappedObjectRepr();
|
||||
}
|
||||
|
||||
void PythonProxyObject::ReleaseWrappedObject()
|
||||
{
|
||||
if (m_wrappedObject.IsValid() && m_ownership == Ownership::Owned)
|
||||
{
|
||||
const AZ::BehaviorClass* behaviorClass = AZ::BehaviorContextHelper::GetClass(m_wrappedObject.m_typeId);
|
||||
if (behaviorClass)
|
||||
{
|
||||
behaviorClass->Destroy(m_wrappedObject);
|
||||
m_wrappedObject = {};
|
||||
m_wrappedObjectTypeName.clear();
|
||||
m_wrappedObjectCachedRepr.clear();
|
||||
m_methods.clear();
|
||||
m_properties.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool PythonProxyObject::CreateDefault(const AZ::BehaviorClass* behaviorClass)
|
||||
{
|
||||
AZ_Error("python", behaviorClass, "Expecting a non-null BehaviorClass");
|
||||
if (behaviorClass)
|
||||
{
|
||||
if (Scope::IsBehaviorFlaggedForEditor(behaviorClass->m_attributes))
|
||||
{
|
||||
m_wrappedObject = behaviorClass->Create();
|
||||
PrepareWrappedObject(*behaviorClass);
|
||||
return true;
|
||||
}
|
||||
AZ_Warning("python", false, "The behavior class (%s) is not flagged for Editor use.", behaviorClass->m_name.c_str());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PythonProxyObject::DoEqualityEvaluation(pybind11::object pythonOther)
|
||||
{
|
||||
constexpr AZ::Crc32 namedEqKey(Operator::s_isEqual);
|
||||
auto&& equalOperatorMethodEntry = m_methods.find(namedEqKey);
|
||||
if (equalOperatorMethodEntry != m_methods.end())
|
||||
{
|
||||
AZ::BehaviorMethod* method = equalOperatorMethodEntry->second;
|
||||
pybind11::object result = Call::ClassMethod(method, m_wrappedObject, pybind11::args(pybind11::make_tuple(pythonOther)));
|
||||
if (result.is_none())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return result.cast<bool>();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PythonProxyObject::DoComparisonEvaluation(pybind11::object pythonOther, Comparison comparison)
|
||||
{
|
||||
bool invertLogic = false;
|
||||
AZ::Crc32 namedKey;
|
||||
if (comparison == Comparison::LessThan)
|
||||
{
|
||||
namedKey = AZ::Crc32{ Operator::s_lessThan };
|
||||
}
|
||||
else if (comparison == Comparison::LessThanOrEquals)
|
||||
{
|
||||
namedKey = AZ::Crc32{ Operator::s_lessThanOrEqual };
|
||||
}
|
||||
else if (comparison == Comparison::GreaterThan)
|
||||
{
|
||||
namedKey = AZ::Crc32{ Operator::s_lessThan };
|
||||
invertLogic = true;
|
||||
}
|
||||
else if (comparison == Comparison::GreaterThanOrEquals)
|
||||
{
|
||||
namedKey = AZ::Crc32{ Operator::s_lessThan };
|
||||
invertLogic = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
auto&& equalOperatorMethodEntry = m_methods.find(namedKey);
|
||||
if (equalOperatorMethodEntry != m_methods.end())
|
||||
{
|
||||
AZ::BehaviorMethod* method = equalOperatorMethodEntry->second;
|
||||
pybind11::object result = Call::ClassMethod(method, m_wrappedObject, pybind11::args(pybind11::make_tuple(pythonOther)));
|
||||
if (result.is_none())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if (invertLogic)
|
||||
{
|
||||
const bool greaterThanResult = !result.cast<bool>();
|
||||
|
||||
// an additional check for "GreaterThanOrEquals" if the result of "LessThan" failed since the invert
|
||||
// of '3 <= 3' would fail since the 'or equals' would return true and be inverted to false
|
||||
if (comparison == Comparison::GreaterThanOrEquals && greaterThanResult == false)
|
||||
{
|
||||
return DoEqualityEvaluation(pythonOther);
|
||||
}
|
||||
|
||||
return greaterThanResult;
|
||||
}
|
||||
return result.cast<bool>();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
namespace PythonProxyObjectManagement
|
||||
{
|
||||
bool IsMemberLike(const AZ::BehaviorMethod& method, const AZ::TypeId& typeId)
|
||||
{
|
||||
return method.IsMember() || (method.GetNumArguments() > 0 && method.GetArgument(0)->m_typeId == typeId);
|
||||
}
|
||||
|
||||
bool IsClassConstant(const AZ::BehaviorProperty* property)
|
||||
{
|
||||
bool value = false;
|
||||
AZ::Attribute* classConstantAttribute = AZ::FindAttribute(AZ::Script::Attributes::ClassConstantValue, property->m_attributes);
|
||||
if (classConstantAttribute)
|
||||
{
|
||||
AZ::AttributeReader attributeReader(nullptr, classConstantAttribute);
|
||||
attributeReader.Read<bool>(value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
pybind11::object CreatePythonProxyObject(const AZ::TypeId& typeId, void* data)
|
||||
{
|
||||
PythonProxyObject* instance = nullptr;
|
||||
if (!data)
|
||||
{
|
||||
instance = aznew PythonProxyObject(typeId);
|
||||
}
|
||||
else
|
||||
{
|
||||
instance = aznew PythonProxyObject(AZ::BehaviorObject(data, typeId));
|
||||
}
|
||||
|
||||
if (!instance->GetWrappedType())
|
||||
{
|
||||
delete instance;
|
||||
PyErr_SetString(PyExc_TypeError, "Failed to create proxy object by type name.");
|
||||
return pybind11::cast<pybind11::none>(Py_None);
|
||||
}
|
||||
return pybind11::cast(instance);
|
||||
}
|
||||
|
||||
pybind11::object CreatePythonProxyObjectByTypename(const char* classTypename)
|
||||
{
|
||||
const AZ::BehaviorClass* behaviorClass = AZ::BehaviorContextHelper::GetClass(AZStd::string(classTypename));
|
||||
AZ_Warning("python", behaviorClass, "Missing Behavior Class for typename:%s", classTypename);
|
||||
if (!behaviorClass)
|
||||
{
|
||||
return pybind11::cast<pybind11::none>(Py_None);
|
||||
}
|
||||
return CreatePythonProxyObject(behaviorClass->m_typeId, nullptr);
|
||||
}
|
||||
|
||||
pybind11::object ConstructPythonProxyObjectByTypename(const char* classTypename, pybind11::args args)
|
||||
{
|
||||
const AZ::BehaviorClass* behaviorClass = AZ::BehaviorContextHelper::GetClass(AZStd::string(classTypename));
|
||||
AZ_Warning("python", behaviorClass, "Missing Behavior Class for typename:%s", classTypename);
|
||||
if (!behaviorClass)
|
||||
{
|
||||
return pybind11::cast<pybind11::none>(Py_None);
|
||||
}
|
||||
|
||||
PythonProxyObject* instance = aznew PythonProxyObject();
|
||||
pybind11::object pythonInstance = instance->Construct(*behaviorClass, args);
|
||||
if (pythonInstance.is_none())
|
||||
{
|
||||
delete instance;
|
||||
PyErr_SetString(PyExc_TypeError, "Failed to construct proxy object with provided args.");
|
||||
return pybind11::cast<pybind11::none>(Py_None);
|
||||
}
|
||||
return pybind11::cast(instance);
|
||||
}
|
||||
|
||||
void ExportStaticBehaviorClassElements(pybind11::module parentModule, pybind11::module defaultModule)
|
||||
{
|
||||
AZ::BehaviorContext* behaviorContext = nullptr;
|
||||
AZ::ComponentApplicationBus::BroadcastResult(behaviorContext, &AZ::ComponentApplicationRequests::GetBehaviorContext);
|
||||
AZ_Error("python", behaviorContext, "Behavior context not available");
|
||||
if (!behaviorContext)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// this will make the base package modules for namespace "azlmbr.*" and "azlmbr.default" for behavior that does not specify a module name
|
||||
Module::PackageMapType modulePackageMap;
|
||||
|
||||
for (const auto& classEntry : behaviorContext->m_classes)
|
||||
{
|
||||
AZ::BehaviorClass* behaviorClass = classEntry.second;
|
||||
|
||||
// is this Behavior Class flagged to usage for Editor.exe bindings?
|
||||
if (!Scope::IsBehaviorFlaggedForEditor(behaviorClass->m_attributes))
|
||||
{
|
||||
continue; // skip this class
|
||||
}
|
||||
|
||||
// find the target module of the behavior's static methods
|
||||
auto moduleName = Module::GetName(behaviorClass->m_attributes);
|
||||
pybind11::module subModule = Module::DeterminePackageModule(modulePackageMap, moduleName ? *moduleName : "", parentModule, defaultModule, false);
|
||||
|
||||
// early detection of instance based elements like constructors or properties
|
||||
bool hasMemberMethods = behaviorClass->m_constructors.empty() == false;
|
||||
bool hasMemberProperties = behaviorClass->m_properties.empty() == false;
|
||||
|
||||
// does this class define methods that may be reflected in a Python module?
|
||||
if (!behaviorClass->m_methods.empty())
|
||||
{
|
||||
// add the non-member methods as Python 'free' function
|
||||
for (const auto& methodEntry : behaviorClass->m_methods)
|
||||
{
|
||||
const AZStd::string& methodName = methodEntry.first;
|
||||
AZ::BehaviorMethod* behaviorMethod = methodEntry.second;
|
||||
if (!PythonProxyObjectManagement::IsMemberLike(*behaviorMethod, behaviorClass->m_typeId))
|
||||
{
|
||||
// the name of the static method will be "azlmbr.<sub_module>.<Behavior Class>_<Behavior Method>"
|
||||
AZStd::string globalMethodName = AZStd::string::format("%s_%s", behaviorClass->m_name.c_str(), methodName.c_str());
|
||||
|
||||
if (behaviorMethod->HasResult())
|
||||
{
|
||||
subModule.def(globalMethodName.c_str(), [behaviorMethod](pybind11::args args)
|
||||
{
|
||||
return Call::StaticMethod(behaviorMethod, args);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
subModule.def(globalMethodName.c_str(), [behaviorMethod](pybind11::args args)
|
||||
{
|
||||
Call::StaticMethod(behaviorMethod, args);
|
||||
});
|
||||
}
|
||||
|
||||
AZStd::string subModuleName = pybind11::cast<AZStd::string>(subModule.attr("__name__"));
|
||||
PythonSymbolEventBus::Broadcast(&PythonSymbolEventBus::Events::LogClassMethod, subModuleName, globalMethodName, behaviorClass, behaviorMethod);
|
||||
}
|
||||
else
|
||||
{
|
||||
// any member method means the class should be exported to Python
|
||||
hasMemberMethods = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// expose all the constant class properties for Python to use
|
||||
for (const auto& propertyEntry : behaviorClass->m_properties)
|
||||
{
|
||||
const AZStd::string& propertyEntryName = propertyEntry.first;
|
||||
AZ::BehaviorProperty* behaviorProperty = propertyEntry.second;
|
||||
|
||||
if (IsClassConstant(behaviorProperty))
|
||||
{
|
||||
// the name of the property will be "azlmbr.<Module>.<Behavior Class>_<Behavior Property>"
|
||||
AZStd::string constantPropertyName =
|
||||
AZStd::string::format("%s_%s", behaviorClass->m_name.c_str(), propertyEntryName.c_str());
|
||||
|
||||
pybind11::object constantValue = Call::StaticMethod(behaviorProperty->m_getter, {});
|
||||
pybind11::setattr(subModule, constantPropertyName.c_str(), constantValue);
|
||||
|
||||
AZStd::string subModuleName = pybind11::cast<AZStd::string>(subModule.attr("__name__"));
|
||||
PythonSymbolEventBus::Broadcast(&PythonSymbolEventBus::Events::LogGlobalProperty, subModuleName, constantPropertyName, behaviorProperty);
|
||||
}
|
||||
}
|
||||
|
||||
// if the Behavior Class has any properties, methods, or constructors then export it
|
||||
const bool exportBehaviorClass = (hasMemberMethods || hasMemberProperties);
|
||||
|
||||
// register all Behavior Class types with a Python function to construct an instance
|
||||
if (exportBehaviorClass)
|
||||
{
|
||||
const char* behaviorClassName = behaviorClass->m_name.c_str();
|
||||
subModule.attr(behaviorClassName) = pybind11::cpp_function([behaviorClassName](pybind11::args pythonArgs)
|
||||
{
|
||||
return ConstructPythonProxyObjectByTypename(behaviorClassName, pythonArgs);
|
||||
});
|
||||
|
||||
AZStd::string subModuleName = pybind11::cast<AZStd::string>(subModule.attr("__name__"));
|
||||
|
||||
// register an alternative class name that passes the Python syntax
|
||||
auto syntaxName = Naming::GetPythonSyntax(*behaviorClass);
|
||||
if (syntaxName)
|
||||
{
|
||||
const char* properSyntax = syntaxName.value().c_str();
|
||||
subModule.attr(properSyntax) = pybind11::cpp_function([behaviorClassName](pybind11::args pythonArgs)
|
||||
{
|
||||
return ConstructPythonProxyObjectByTypename(behaviorClassName, pythonArgs);
|
||||
});
|
||||
PythonSymbolEventBus::Broadcast(&PythonSymbolEventBus::Events::LogClassWithName, subModuleName, behaviorClass, properSyntax);
|
||||
}
|
||||
else
|
||||
{
|
||||
PythonSymbolEventBus::Broadcast(&PythonSymbolEventBus::Events::LogClass, subModuleName, behaviorClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pybind11::list ListBehaviorAttributes(const PythonProxyObject& pythonProxyObject)
|
||||
{
|
||||
pybind11::list items;
|
||||
AZStd::string baseName;
|
||||
|
||||
auto typeId = pythonProxyObject.GetWrappedType();
|
||||
if (!typeId)
|
||||
{
|
||||
return items;
|
||||
}
|
||||
|
||||
const AZ::BehaviorClass* behaviorClass = AZ::BehaviorContextHelper::GetClass(typeId.value());
|
||||
if (!behaviorClass)
|
||||
{
|
||||
return items;
|
||||
}
|
||||
|
||||
if (!Scope::IsBehaviorFlaggedForEditor(behaviorClass->m_attributes))
|
||||
{
|
||||
return items;
|
||||
}
|
||||
|
||||
for (const auto& methodEntry : behaviorClass->m_methods)
|
||||
{
|
||||
AZ::BehaviorMethod* method = methodEntry.second;
|
||||
if (method && PythonProxyObjectManagement::IsMemberLike(*method, typeId.value()))
|
||||
{
|
||||
baseName = methodEntry.first;
|
||||
Scope::FetchScriptName(method->m_attributes, baseName);
|
||||
items.append(pybind11::str(baseName.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& behaviorProperty : behaviorClass->m_properties)
|
||||
{
|
||||
AZ::BehaviorProperty* property = behaviorProperty.second;
|
||||
if (property)
|
||||
{
|
||||
baseName = behaviorProperty.first;
|
||||
Scope::FetchScriptName(property->m_attributes, baseName);
|
||||
items.append(pybind11::str(baseName.c_str()));
|
||||
}
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
pybind11::list ListBehaviorClasses(bool onlyIncludeScopedForAutomation)
|
||||
{
|
||||
pybind11::list items;
|
||||
AZ::BehaviorContext* behaviorContext(nullptr);
|
||||
AZ::ComponentApplicationBus::BroadcastResult(behaviorContext, &AZ::ComponentApplicationRequests::GetBehaviorContext);
|
||||
if (!behaviorContext)
|
||||
{
|
||||
AZ_Error("python", false, "A behavior context is required!");
|
||||
return items;
|
||||
}
|
||||
|
||||
for (auto&& classEntry : behaviorContext->m_classes)
|
||||
{
|
||||
auto&& behaviorClass = classEntry.second;
|
||||
if (onlyIncludeScopedForAutomation )
|
||||
{
|
||||
if (Scope::IsBehaviorFlaggedForEditor(behaviorClass->m_attributes))
|
||||
{
|
||||
items.append(pybind11::str(classEntry.first.c_str()));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
items.append(pybind11::str(classEntry.first.c_str()));
|
||||
}
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
void CreateSubmodule(pybind11::module parentModule, pybind11::module defaultModule)
|
||||
{
|
||||
ExportStaticBehaviorClassElements(parentModule, defaultModule);
|
||||
|
||||
auto objectModule = parentModule.def_submodule("object");
|
||||
objectModule.def("create", &CreatePythonProxyObjectByTypename);
|
||||
objectModule.def("construct", &ConstructPythonProxyObjectByTypename);
|
||||
objectModule.def("dir", &ListBehaviorAttributes);
|
||||
objectModule.def("list_classes", &ListBehaviorClasses, pybind11::arg("onlyIncludeScopedForAutomation") = true);
|
||||
|
||||
pybind11::class_<PythonProxyObject>(objectModule, "PythonProxyObject", pybind11::dynamic_attr())
|
||||
.def(pybind11::init<>())
|
||||
.def(pybind11::init<const char*>())
|
||||
.def_property_readonly("typename", &PythonProxyObject::GetWrappedTypeName)
|
||||
.def("set_type", &PythonProxyObject::SetByTypeName)
|
||||
.def("set_property", &PythonProxyObject::SetPropertyValue)
|
||||
.def("get_property", &PythonProxyObject::GetPropertyValue)
|
||||
.def("invoke", &PythonProxyObject::Invoke)
|
||||
.def(Operator::s_isEqual, [](PythonProxyObject& self, pybind11::object rhs)
|
||||
{
|
||||
return self.DoEqualityEvaluation(rhs);
|
||||
})
|
||||
.def(Operator::s_notEqual, [](PythonProxyObject& self, pybind11::object rhs)
|
||||
{
|
||||
return self.DoEqualityEvaluation(rhs) == false;
|
||||
})
|
||||
.def(Operator::s_greaterThan, [](PythonProxyObject& self, pybind11::object rhs)
|
||||
{
|
||||
return self.DoComparisonEvaluation(rhs, PythonProxyObject::Comparison::GreaterThan);
|
||||
})
|
||||
.def(Operator::s_greaterThanOrEqual, [](PythonProxyObject& self, pybind11::object rhs)
|
||||
{
|
||||
return self.DoComparisonEvaluation(rhs, PythonProxyObject::Comparison::GreaterThanOrEquals);
|
||||
})
|
||||
.def(Operator::s_lessThan, [](PythonProxyObject& self, pybind11::object rhs)
|
||||
{
|
||||
return self.DoComparisonEvaluation(rhs, PythonProxyObject::Comparison::LessThan);
|
||||
})
|
||||
.def(Operator::s_lessThanOrEqual, [](PythonProxyObject& self, pybind11::object rhs)
|
||||
{
|
||||
return self.DoComparisonEvaluation(rhs, PythonProxyObject::Comparison::LessThanOrEquals);
|
||||
})
|
||||
.def("__setattr__", &PythonProxyObject::SetPropertyValue)
|
||||
.def("__getattr__", &PythonProxyObject::GetPropertyValue)
|
||||
.def(Builtins::s_repr, [](PythonProxyObject& self)
|
||||
{
|
||||
return self.GetWrappedObjectRepr();
|
||||
})
|
||||
.def(Builtins::s_str, [](PythonProxyObject& self)
|
||||
{
|
||||
return self.GetWrappedObjectStr();
|
||||
})
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/Memory/Memory.h>
|
||||
#include <AzCore/RTTI/BehaviorContext.h>
|
||||
#include <AzCore/RTTI/RTTI.h>
|
||||
#include <AzCore/std/optional.h>
|
||||
|
||||
#include <Source/PythonCommon.h>
|
||||
#include <pybind11/pybind11.h>
|
||||
|
||||
namespace EditorPythonBindings
|
||||
{
|
||||
//! Wraps an instance of a Behavior Class that is flagged for 'Editor'
|
||||
class PythonProxyObject final
|
||||
{
|
||||
public:
|
||||
AZ_TYPE_INFO(PythonProxyObject, "{448A4480-CCA8-4F14-9F17-41B0491F9FD1}");
|
||||
AZ_CLASS_ALLOCATOR(PythonProxyObject, AZ::SystemAllocator, 0);
|
||||
|
||||
PythonProxyObject() = default;
|
||||
explicit PythonProxyObject(const AZ::TypeId& typeId);
|
||||
explicit PythonProxyObject(const char* typeName);
|
||||
explicit PythonProxyObject(const AZ::BehaviorObject& object);
|
||||
|
||||
~PythonProxyObject();
|
||||
|
||||
//! Gets the AZ RTTI type of the BehaviorObject
|
||||
AZStd::optional<AZ::TypeId> GetWrappedType() const;
|
||||
|
||||
//! Returns the wrapped behavior object pointer if it is valid
|
||||
AZStd::optional<AZ::BehaviorObject*> GetBehaviorObject();
|
||||
|
||||
//! Gets the name of the type of the wrapped BehaviorObject
|
||||
const char* GetWrappedTypeName() const;
|
||||
|
||||
//! Assigns a value to a property (by name) a value; the types must match
|
||||
void SetPropertyValue(const char* propertyName, pybind11::object value);
|
||||
|
||||
//! Gets the value or callable held by a property of a wrapped BehaviorObject
|
||||
pybind11::object GetPropertyValue(const char* attributeName);
|
||||
|
||||
//! Creates a default constructed instance a 'typeName'
|
||||
bool SetByTypeName(const char* typeName);
|
||||
|
||||
//! Invokes a method by name on a wrapped BehaviorObject
|
||||
pybind11::object Invoke(const char* methodName, pybind11::args pythonArgs);
|
||||
|
||||
//! Constructs a BehaviorClass using Python arguments
|
||||
pybind11::object Construct(const AZ::BehaviorClass& behaviorClass, pybind11::args args);
|
||||
|
||||
//! Performs an equality operation to compare this object with another object
|
||||
bool DoEqualityEvaluation(pybind11::object pythonOther);
|
||||
|
||||
//! Perform a comparison of a Python operator
|
||||
enum class Comparison
|
||||
{
|
||||
LessThan,
|
||||
LessThanOrEquals,
|
||||
GreaterThan,
|
||||
GreaterThanOrEquals
|
||||
};
|
||||
bool DoComparisonEvaluation(pybind11::object pythonOther, Comparison comparison);
|
||||
|
||||
//! Gets the wrapped object's __repr__
|
||||
pybind11::object GetWrappedObjectRepr();
|
||||
|
||||
//! Gets the wrapped object's __str__
|
||||
pybind11::object GetWrappedObjectStr();
|
||||
|
||||
protected:
|
||||
void PrepareWrappedObject(const AZ::BehaviorClass& behaviorClass);
|
||||
void ReleaseWrappedObject();
|
||||
bool CreateDefault(const AZ::BehaviorClass* behaviorClass);
|
||||
void PopulateMethodsAndProperties(const AZ::BehaviorClass& behaviorClass);
|
||||
void PopulateComparisonOperators(const AZ::BehaviorClass& behaviorClass);
|
||||
bool CanConvertPythonToBehaviorValue(const AZ::BehaviorParameter& behaviorArg, pybind11::object pythonArg) const;
|
||||
|
||||
|
||||
private:
|
||||
enum class Ownership
|
||||
{
|
||||
None,
|
||||
Owned,
|
||||
Released
|
||||
};
|
||||
|
||||
AZ::BehaviorObject m_wrappedObject;
|
||||
AZStd::string m_wrappedObjectTypeName;
|
||||
AZStd::string m_wrappedObjectCachedRepr;
|
||||
Ownership m_ownership = Ownership::None;
|
||||
AZStd::unordered_map<AZ::Crc32, AZ::BehaviorMethod*> m_methods;
|
||||
AZStd::unordered_map<AZ::Crc32, AZ::BehaviorProperty*> m_properties;
|
||||
};
|
||||
|
||||
namespace PythonProxyObjectManagement
|
||||
{
|
||||
//! Creates the 'azlmbr.object' module so that Python script developers can manage proxy objects
|
||||
void CreateSubmodule(pybind11::module parentModule, pybind11::module defaultModule);
|
||||
|
||||
//! Creates a Python object storing a BehaviorObject backed by a BehaviorClass
|
||||
pybind11::object CreatePythonProxyObject(const AZ::TypeId& typeId, void* data);
|
||||
|
||||
//! Checks if function can be reflected as a class member method
|
||||
bool IsMemberLike(const AZ::BehaviorMethod& method, const AZ::TypeId& typeId);
|
||||
}
|
||||
}
|
||||
|
||||
namespace pybind11
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
//! Type caster specialization PythonProxyObject to convert between Python <-> AZ Reflection
|
||||
template <>
|
||||
struct type_caster<EditorPythonBindings::PythonProxyObject>
|
||||
: public type_caster_base<EditorPythonBindings::PythonProxyObject>
|
||||
{
|
||||
public:
|
||||
|
||||
// Conversion (Python -> C++)
|
||||
bool load(handle src, bool convert)
|
||||
{
|
||||
return type_caster_base<EditorPythonBindings::PythonProxyObject>::load(src, convert);
|
||||
}
|
||||
|
||||
// Conversion (C++ -> Python)
|
||||
static handle cast(const EditorPythonBindings::PythonProxyObject* src, return_value_policy policy, handle parent)
|
||||
{
|
||||
return type_caster_base<EditorPythonBindings::PythonProxyObject>::cast(src, policy, parent);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,387 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <PythonReflectionComponent.h>
|
||||
|
||||
#include <AzFramework/StringFunc/StringFunc.h>
|
||||
|
||||
#include <Source/PythonCommon.h>
|
||||
#include <Source/PythonUtility.h>
|
||||
#include <Source/PythonTypeCasters.h>
|
||||
#include <Source/PythonProxyBus.h>
|
||||
#include <Source/PythonProxyObject.h>
|
||||
#include <Source/PythonSymbolsBus.h>
|
||||
#include <pybind11/embed.h>
|
||||
|
||||
#include <AzCore/PlatformDef.h>
|
||||
#include <AzCore/RTTI/AttributeReader.h>
|
||||
#include <AzCore/RTTI/BehaviorContext.h>
|
||||
#include <AzCore/std/smart_ptr/make_shared.h>
|
||||
#include <AzCore/Serialization/EditContextConstants.inl>
|
||||
|
||||
#include <AzCore/PlatformDef.h>
|
||||
#include <AzCore/IO/SystemFile.h>
|
||||
#include <AzCore/IO/SystemFile.h>
|
||||
#include <AzFramework/IO/LocalFileIO.h>
|
||||
|
||||
namespace EditorPythonBindings
|
||||
{
|
||||
namespace Internal
|
||||
{
|
||||
static constexpr const char* s_azlmbr = "azlmbr";
|
||||
static constexpr const char* s_default = "default";
|
||||
static constexpr const char* s_globals = "globals";
|
||||
|
||||
// a structure for pybind11 to bind to hold constants, properties, and enums from the Behavior Context
|
||||
struct StaticPropertyHolder final
|
||||
{
|
||||
AZ_CLASS_ALLOCATOR(StaticPropertyHolder, AZ::SystemAllocator, 0);
|
||||
|
||||
StaticPropertyHolder() = default;
|
||||
~StaticPropertyHolder() = default;
|
||||
|
||||
bool AddToScope(pybind11::module scope)
|
||||
{
|
||||
m_behaviorContext = nullptr;
|
||||
AZ::ComponentApplicationBus::BroadcastResult(m_behaviorContext, &AZ::ComponentApplicationRequests::GetBehaviorContext);
|
||||
AZ_Error("python", m_behaviorContext, "Behavior context not available");
|
||||
if (m_behaviorContext == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
m_fullName = PyModule_GetName(scope.ptr());
|
||||
|
||||
pybind11::setattr(scope, "__getattr__", pybind11::cpp_function([this](const char* attribute)
|
||||
{
|
||||
return this->GetPropertyValue(attribute);
|
||||
}));
|
||||
pybind11::setattr(scope, "__setattr__", pybind11::cpp_function([this](const char* attribute, pybind11::object value)
|
||||
{
|
||||
return this->SetPropertyValue(attribute, value);
|
||||
}));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void AddProperty(AZStd::string_view name, AZ::BehaviorProperty* behaviorProperty)
|
||||
{
|
||||
AZStd::string baseName(name);
|
||||
Scope::FetchScriptName(behaviorProperty->m_attributes, baseName);
|
||||
AZ::Crc32 namedKey(baseName);
|
||||
if (m_properties.find(namedKey) == m_properties.end())
|
||||
{
|
||||
m_properties[namedKey] = behaviorProperty;
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Warning("python", false, "Skipping duplicate property named %s\n", baseName.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
void SetPropertyValue(const char* attributeName, pybind11::object value)
|
||||
{
|
||||
auto behaviorPropertyIter = m_properties.find(AZ::Crc32(attributeName));
|
||||
if (behaviorPropertyIter != m_properties.end())
|
||||
{
|
||||
AZ::BehaviorProperty* property = behaviorPropertyIter->second;
|
||||
AZ_Error("python", property->m_setter, "%s is not a writable property in %s.", attributeName, m_fullName.c_str());
|
||||
if (property->m_setter)
|
||||
{
|
||||
EditorPythonBindings::Call::StaticMethod(property->m_setter, pybind11::args(pybind11::make_tuple(value)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pybind11::object GetPropertyValue(const char* attributeName)
|
||||
{
|
||||
AZ::Crc32 crcAttributeName(attributeName);
|
||||
auto behaviorPropertyIter = m_properties.find(crcAttributeName);
|
||||
if (behaviorPropertyIter != m_properties.end())
|
||||
{
|
||||
AZ::BehaviorProperty* property = behaviorPropertyIter->second;
|
||||
AZ_Error("python", property->m_getter, "%s is not a readable property in %s.", attributeName, m_fullName.c_str());
|
||||
if (property->m_getter)
|
||||
{
|
||||
return EditorPythonBindings::Call::StaticMethod(property->m_getter, pybind11::args());
|
||||
}
|
||||
}
|
||||
return pybind11::cast<pybind11::none>(Py_None);
|
||||
}
|
||||
|
||||
AZ::BehaviorContext* m_behaviorContext = nullptr;
|
||||
AZStd::unordered_map<AZ::Crc32, AZ::BehaviorProperty*> m_properties;
|
||||
AZStd::string m_fullName;
|
||||
};
|
||||
|
||||
using StaticPropertyHolderPointer = AZStd::unique_ptr<StaticPropertyHolder>;
|
||||
using StaticPropertyHolderMapEntry = AZStd::pair<pybind11::module, StaticPropertyHolderPointer>;
|
||||
|
||||
struct StaticPropertyHolderMap final
|
||||
: public AZStd::unordered_map<AZStd::string, StaticPropertyHolderMapEntry>
|
||||
{
|
||||
Module::PackageMapType m_packageMap;
|
||||
|
||||
void AddToScope()
|
||||
{
|
||||
for (auto&& element : *this)
|
||||
{
|
||||
StaticPropertyHolderMapEntry& entry = element.second;
|
||||
entry.second->AddToScope(entry.first);
|
||||
}
|
||||
}
|
||||
|
||||
void AddProperty(pybind11::module scope, const AZStd::string& propertyName, AZ::BehaviorProperty* behaviorProperty)
|
||||
{
|
||||
AZStd::string scopeName = PyModule_GetName(scope.ptr());
|
||||
auto&& iter = find(scopeName);
|
||||
if (iter == end())
|
||||
{
|
||||
StaticPropertyHolder* holder = aznew StaticPropertyHolder();
|
||||
insert(AZStd::make_pair(scopeName, StaticPropertyHolderMapEntry{ scope, holder }));
|
||||
holder->AddProperty(propertyName, behaviorProperty);
|
||||
}
|
||||
else
|
||||
{
|
||||
StaticPropertyHolderMapEntry& entry = iter->second;
|
||||
entry.second->AddProperty(propertyName, behaviorProperty);
|
||||
}
|
||||
PythonSymbolEventBus::Broadcast(&PythonSymbolEventBus::Events::LogGlobalProperty, scopeName, propertyName, behaviorProperty);
|
||||
}
|
||||
|
||||
pybind11::module DetermineScope(pybind11::module scope, const AZStd::string& fullName)
|
||||
{
|
||||
return Module::DeterminePackageModule(m_packageMap, fullName, scope, scope, false);
|
||||
}
|
||||
};
|
||||
|
||||
AZStd::string PyResolvePath(AZStd::string_view path)
|
||||
{
|
||||
char pyPath[AZ_MAX_PATH_LEN];
|
||||
AZ::IO::FileIOBase::GetInstance()->ResolvePath(path.data(), pyPath, AZ_MAX_PATH_LEN);
|
||||
return { pyPath };
|
||||
}
|
||||
|
||||
void RegisterAliasIfExists(pybind11::module pathsModule, AZStd::string_view alias, AZStd::string_view attribute)
|
||||
{
|
||||
const char* aliasPath = AZ::IO::FileIOBase::GetInstance()->GetAlias(alias.data());
|
||||
if (aliasPath)
|
||||
{
|
||||
pathsModule.attr(attribute.data()) = aliasPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
pathsModule.attr(attribute.data()) = "";
|
||||
}
|
||||
}
|
||||
|
||||
void RegisterPaths(pybind11::module parentModule)
|
||||
{
|
||||
pybind11::module pathsModule = parentModule.def_submodule("paths");
|
||||
pathsModule.def("resolve_path", [](const char* path)
|
||||
{
|
||||
return PyResolvePath(path);
|
||||
});
|
||||
pathsModule.def("ensure_alias", [](const char* alias, const char* path)
|
||||
{
|
||||
const char* aliasPath = AZ::IO::FileIOBase::GetInstance()->GetAlias(alias);
|
||||
if (aliasPath == nullptr)
|
||||
{
|
||||
AZ::IO::FileIOBase::GetInstance()->SetAlias(alias, path);
|
||||
}
|
||||
});
|
||||
|
||||
RegisterAliasIfExists(pathsModule, "@devroot@", "devroot");
|
||||
RegisterAliasIfExists(pathsModule, "@engroot@", "engroot");
|
||||
RegisterAliasIfExists(pathsModule, "@assets@", "assets");
|
||||
RegisterAliasIfExists(pathsModule, "@devassets@", "devassets");
|
||||
RegisterAliasIfExists(pathsModule, "@log@", "log");
|
||||
RegisterAliasIfExists(pathsModule, "@root@", "root");
|
||||
|
||||
const char* executableFolder = nullptr;
|
||||
AZ::ComponentApplicationBus::BroadcastResult(executableFolder, &AZ::ComponentApplicationBus::Events::GetExecutableFolder);
|
||||
if (executableFolder)
|
||||
{
|
||||
pathsModule.attr("executableFolder") = executableFolder;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// PythonReflectionComponent
|
||||
|
||||
void PythonReflectionComponent::Reflect(AZ::ReflectContext* context)
|
||||
{
|
||||
if (auto&& serialize = azrtti_cast<AZ::SerializeContext*>(context))
|
||||
{
|
||||
serialize->Class<PythonReflectionComponent, AZ::Component>()
|
||||
->Version(1)
|
||||
->Attribute(AZ::Edit::Attributes::SystemComponentTags, AZStd::vector<AZ::Crc32>{AZ_CRC_CE("AssetBuilder")})
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
void PythonReflectionComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
|
||||
{
|
||||
provided.push_back(PythonReflectionService);
|
||||
}
|
||||
|
||||
void PythonReflectionComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
|
||||
{
|
||||
incompatible.push_back(PythonReflectionService);
|
||||
}
|
||||
|
||||
void PythonReflectionComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
|
||||
{
|
||||
required.push_back(PythonEmbeddedService);
|
||||
}
|
||||
|
||||
void PythonReflectionComponent::Activate()
|
||||
{
|
||||
EditorPythonBindings::EditorPythonBindingsNotificationBus::Handler::BusConnect();
|
||||
}
|
||||
|
||||
void PythonReflectionComponent::Deactivate()
|
||||
{
|
||||
OnPreFinalize();
|
||||
}
|
||||
|
||||
void PythonReflectionComponent::ExportGlobalsFromBehaviorContext(pybind11::module parentModule)
|
||||
{
|
||||
AZ::BehaviorContext* behaviorContext = nullptr;
|
||||
AZ::ComponentApplicationBus::BroadcastResult(behaviorContext, &AZ::ComponentApplicationRequests::GetBehaviorContext);
|
||||
AZ_Error("Editor", behaviorContext, "Behavior context not available");
|
||||
if (!behaviorContext)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// when a global method does not have a Module attribute put into the 'azlmbr.globals' module
|
||||
auto globalsModule = parentModule.def_submodule(Internal::s_globals);
|
||||
Module::PackageMapType modulePackageMap;
|
||||
|
||||
// add global methods flagged for Automation as Python global functions
|
||||
for (const auto& methodEntry : behaviorContext->m_methods)
|
||||
{
|
||||
const AZStd::string& methodName = methodEntry.first;
|
||||
AZ::BehaviorMethod* behaviorMethod = methodEntry.second;
|
||||
if (Scope::IsBehaviorFlaggedForEditor(behaviorMethod->m_attributes))
|
||||
{
|
||||
pybind11::module targetModule;
|
||||
auto moduleNameResult = Module::GetName(behaviorMethod->m_attributes);
|
||||
if(moduleNameResult)
|
||||
{
|
||||
targetModule = Module::DeterminePackageModule(modulePackageMap, *moduleNameResult, parentModule, globalsModule, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
targetModule = globalsModule;
|
||||
}
|
||||
|
||||
if (behaviorMethod->HasResult())
|
||||
{
|
||||
targetModule.def(methodName.c_str(), [behaviorMethod](pybind11::args args)
|
||||
{
|
||||
return Call::StaticMethod(behaviorMethod, args);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
targetModule.def(methodName.c_str(), [behaviorMethod](pybind11::args args)
|
||||
{
|
||||
Call::StaticMethod(behaviorMethod, args);
|
||||
});
|
||||
}
|
||||
|
||||
// log global method symbol
|
||||
AZStd::string subModuleName = pybind11::cast<AZStd::string>(targetModule.attr("__name__"));
|
||||
PythonSymbolEventBus::Broadcast(&PythonSymbolEventBus::Events::LogGlobalMethod, subModuleName, methodName, behaviorMethod);
|
||||
}
|
||||
}
|
||||
|
||||
// add global properties flagged for Automation as Python static class properties
|
||||
m_staticPropertyHolderMap = AZStd::make_shared<Internal::StaticPropertyHolderMap>();
|
||||
struct GlobalPropertyHolder {};
|
||||
pybind11::class_<GlobalPropertyHolder> staticPropertyHolder(globalsModule, "property");
|
||||
for (const auto& propertyEntry : behaviorContext->m_properties)
|
||||
{
|
||||
const AZStd::string& propertyName = propertyEntry.first;
|
||||
AZ::BehaviorProperty* behaviorProperty = propertyEntry.second;
|
||||
if (Scope::IsBehaviorFlaggedForEditor(behaviorProperty->m_attributes))
|
||||
{
|
||||
auto propertyScopeName = Module::GetName(behaviorProperty->m_attributes);
|
||||
if (propertyScopeName)
|
||||
{
|
||||
pybind11::module scope = m_staticPropertyHolderMap->DetermineScope(parentModule, *propertyScopeName);
|
||||
m_staticPropertyHolderMap->AddProperty(scope, propertyName, behaviorProperty);
|
||||
}
|
||||
|
||||
// log global property symbol
|
||||
AZStd::string subModuleName = pybind11::cast<AZStd::string>(globalsModule.attr("__name__"));
|
||||
PythonSymbolEventBus::Broadcast(&PythonSymbolEventBus::Events::LogGlobalProperty, subModuleName, propertyName, behaviorProperty);
|
||||
|
||||
if (behaviorProperty->m_getter && behaviorProperty->m_setter)
|
||||
{
|
||||
staticPropertyHolder.def_property_static(
|
||||
propertyName.c_str(),
|
||||
[behaviorProperty](pybind11::object) { return Call::StaticMethod(behaviorProperty->m_getter, {}); },
|
||||
[behaviorProperty](pybind11::object, pybind11::args args) { return Call::StaticMethod(behaviorProperty->m_setter, args); }
|
||||
);
|
||||
}
|
||||
else if (behaviorProperty->m_getter)
|
||||
{
|
||||
staticPropertyHolder.def_property_static(
|
||||
propertyName.c_str(),
|
||||
[behaviorProperty](pybind11::object) { return Call::StaticMethod(behaviorProperty->m_getter, {}); },
|
||||
pybind11::cpp_function()
|
||||
);
|
||||
}
|
||||
else if (behaviorProperty->m_setter)
|
||||
{
|
||||
AZ_Warning("python", false, "Global property %s only has a m_setter; write only properties not supported", propertyName.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Error("python", false, "Global property %s has neither a m_getter or m_setter", propertyName.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_staticPropertyHolderMap->AddToScope();
|
||||
}
|
||||
|
||||
void PythonReflectionComponent::OnPreFinalize()
|
||||
{
|
||||
m_staticPropertyHolderMap.reset();
|
||||
EditorPythonBindings::EditorPythonBindingsNotificationBus::Handler::BusDisconnect();
|
||||
}
|
||||
|
||||
void PythonReflectionComponent::OnImportModule(PyObject* module)
|
||||
{
|
||||
pybind11::module parentModule = pybind11::cast<pybind11::module>(module);
|
||||
std::string pythonModuleName = pybind11::cast<std::string>(parentModule.attr("__name__"));
|
||||
if (AzFramework::StringFunc::Equal(pythonModuleName.c_str(), Internal::s_azlmbr))
|
||||
{
|
||||
// declare the default module to capture behavior that did not define a "Module" attribute
|
||||
pybind11::module defaultModule = parentModule.def_submodule(Internal::s_default);
|
||||
|
||||
ExportGlobalsFromBehaviorContext(parentModule);
|
||||
PythonProxyObjectManagement::CreateSubmodule(parentModule, defaultModule);
|
||||
PythonProxyBusManagement::CreateSubmodule(parentModule);
|
||||
Internal::RegisterPaths(parentModule);
|
||||
|
||||
PythonSymbolEventBus::Broadcast(&PythonSymbolEventBus::Events::Finalize);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <EditorPythonBindings/EditorPythonBindingsSymbols.h>
|
||||
|
||||
#include <AzCore/Component/Component.h>
|
||||
#include <EditorPythonBindings/EditorPythonBindingsBus.h>
|
||||
#include <Source/PythonCommon.h>
|
||||
#include <pybind11/pybind11.h>
|
||||
|
||||
namespace EditorPythonBindings
|
||||
{
|
||||
namespace Internal
|
||||
{
|
||||
struct StaticPropertyHolderMap;
|
||||
}
|
||||
|
||||
//! Inspects the Behavior Context for methods to expose as Python bindings
|
||||
class PythonReflectionComponent
|
||||
: public AZ::Component
|
||||
, private EditorPythonBindings::EditorPythonBindingsNotificationBus::Handler
|
||||
{
|
||||
public:
|
||||
AZ_COMPONENT(PythonReflectionComponent, PythonReflectionComponentTypeId, AZ::Component);
|
||||
|
||||
static void Reflect(AZ::ReflectContext* context);
|
||||
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
|
||||
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
|
||||
static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required);
|
||||
|
||||
protected:
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// AZ::Component interface implementation
|
||||
void Activate() override;
|
||||
void Deactivate() override;
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// EditorPythonBindings::EditorPythonBindingsNotificationBus interface implementation
|
||||
void OnPreFinalize() override;
|
||||
void OnImportModule(PyObject* module) override;
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private:
|
||||
void ExportGlobalsFromBehaviorContext(pybind11::module parentModule);
|
||||
AZStd::shared_ptr<Internal::StaticPropertyHolderMap> m_staticPropertyHolderMap;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/EBus/EBus.h>
|
||||
|
||||
namespace EditorPythonBindings
|
||||
{
|
||||
//! An interface to track exported Python symbols
|
||||
class PythonSymbolEvents
|
||||
: public AZ::EBusTraits
|
||||
{
|
||||
public:
|
||||
//! logs a behavior class type
|
||||
virtual void LogClass(AZStd::string_view moduleName, AZ::BehaviorClass* behaviorClass) = 0;
|
||||
|
||||
//! logs a behavior class type with an override to its name
|
||||
virtual void LogClassWithName(AZStd::string_view moduleName, AZ::BehaviorClass* behaviorClass, AZStd::string_view className) = 0;
|
||||
|
||||
//! logs a static class method with a specified global method name
|
||||
virtual void LogClassMethod(AZStd::string_view moduleName, AZStd::string_view globalMethodName, AZ::BehaviorClass* behaviorClass, AZ::BehaviorMethod* behaviorMethod) = 0;
|
||||
|
||||
//! logs a behavior bus with a specified bus name
|
||||
virtual void LogBus(AZStd::string_view moduleName, AZStd::string_view busName, AZ::BehaviorEBus* behaviorEBus) = 0;
|
||||
|
||||
//! logs a global method from the behavior context registry with a specified method name
|
||||
virtual void LogGlobalMethod(AZStd::string_view moduleName, AZStd::string_view methodName, AZ::BehaviorMethod* behaviorMethod) = 0;
|
||||
|
||||
//! logs a global property, enum, or constant from the behavior context registry with a specified property name
|
||||
virtual void LogGlobalProperty(AZStd::string_view moduleName, AZStd::string_view propertyName, AZ::BehaviorProperty* behaviorProperty) = 0;
|
||||
|
||||
//! signals the end of the logging of symbols
|
||||
virtual void Finalize() = 0;
|
||||
};
|
||||
using PythonSymbolEventBus = AZ::EBus<PythonSymbolEvents>;
|
||||
|
||||
} // namespace EditorPythonBindings
|
||||
|
||||
@@ -0,0 +1,716 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <PythonSystemComponent.h>
|
||||
#include <EditorPythonBindings/EditorPythonBindingsBus.h>
|
||||
|
||||
#include <Source/PythonCommon.h>
|
||||
#include <pybind11/pybind11.h>
|
||||
#include <pybind11/embed.h>
|
||||
#include <pybind11/eval.h>
|
||||
#include <osdefs.h> // for DELIM
|
||||
|
||||
#include <AzCore/Component/EntityId.h>
|
||||
#include <AzCore/IO/SystemFile.h>
|
||||
#include <AzCore/Module/DynamicModuleHandle.h>
|
||||
#include <AzCore/Module/Module.h>
|
||||
#include <AzCore/Module/ModuleManagerBus.h>
|
||||
#include <AzCore/PlatformDef.h>
|
||||
#include <AzCore/Serialization/EditContext.h>
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
|
||||
#include <AzCore/std/string/conversions.h>
|
||||
#include <AzCore/StringFunc/StringFunc.h>
|
||||
|
||||
#include <AzFramework/API/ApplicationAPI.h>
|
||||
#include <AzFramework/Asset/AssetSystemComponent.h>
|
||||
#include <AzFramework/IO/LocalFileIO.h>
|
||||
#include <AzFramework/CommandLine/CommandRegistrationBus.h>
|
||||
#include <AzFramework/StringFunc/StringFunc.h>
|
||||
|
||||
#include <AzToolsFramework/API/EditorPythonConsoleBus.h>
|
||||
|
||||
namespace Platform
|
||||
{
|
||||
// Implemented in each different platform's implentation files, as it differs per platform.
|
||||
bool InsertPythonBinaryLibraryPaths(AZStd::unordered_set<AZStd::string>& paths, const char* pythonPackage, const char* engineRoot);
|
||||
AZStd::string GetPythonHomePath(const char* pythonPackage, const char* engineRoot);
|
||||
}
|
||||
|
||||
// this is called the first time a Python script contains "import azlmbr"
|
||||
PYBIND11_EMBEDDED_MODULE(azlmbr, m)
|
||||
{
|
||||
EditorPythonBindings::EditorPythonBindingsNotificationBus::Broadcast(&EditorPythonBindings::EditorPythonBindingsNotificationBus::Events::OnImportModule, m.ptr());
|
||||
}
|
||||
|
||||
namespace RedirectOutput
|
||||
{
|
||||
using RedirectOutputFunc = AZStd::function<void(const char*)>;
|
||||
|
||||
struct RedirectOutput
|
||||
{
|
||||
PyObject_HEAD
|
||||
RedirectOutputFunc write;
|
||||
};
|
||||
|
||||
PyObject* RedirectWrite(PyObject* self, PyObject* args)
|
||||
{
|
||||
std::size_t written(0);
|
||||
RedirectOutput* selfimpl = reinterpret_cast<RedirectOutput*>(self);
|
||||
if (selfimpl->write)
|
||||
{
|
||||
char* data;
|
||||
if (!PyArg_ParseTuple(args, "s", &data))
|
||||
{
|
||||
return PyLong_FromSize_t(0);
|
||||
}
|
||||
selfimpl->write(data);
|
||||
written = strlen(data);
|
||||
}
|
||||
return PyLong_FromSize_t(written);
|
||||
}
|
||||
|
||||
PyObject* RedirectFlush([[maybe_unused]] PyObject* self, [[maybe_unused]] PyObject* args)
|
||||
{
|
||||
// no-op
|
||||
return Py_BuildValue("");
|
||||
}
|
||||
|
||||
PyMethodDef RedirectMethods[] =
|
||||
{
|
||||
{"write", RedirectWrite, METH_VARARGS, "sys.stdout.write"},
|
||||
{"flush", RedirectFlush, METH_VARARGS, "sys.stdout.flush"},
|
||||
{"write", RedirectWrite, METH_VARARGS, "sys.stderr.write"},
|
||||
{"flush", RedirectFlush, METH_VARARGS, "sys.stderr.flush"},
|
||||
{0, 0, 0, 0} // sentinel
|
||||
};
|
||||
|
||||
PyTypeObject RedirectOutputType =
|
||||
{
|
||||
PyVarObject_HEAD_INIT(0, 0)
|
||||
"azlmbr_redirect.RedirectOutputType", // tp_name
|
||||
sizeof(RedirectOutput), /* tp_basicsize */
|
||||
0, /* tp_itemsize */
|
||||
0, /* tp_dealloc */
|
||||
0, /* tp_print */
|
||||
0, /* tp_getattr */
|
||||
0, /* tp_setattr */
|
||||
0, /* tp_reserved */
|
||||
0, /* tp_repr */
|
||||
0, /* tp_as_number */
|
||||
0, /* tp_as_sequence */
|
||||
0, /* tp_as_mapping */
|
||||
0, /* tp_hash */
|
||||
0, /* tp_call */
|
||||
0, /* tp_str */
|
||||
0, /* tp_getattro */
|
||||
0, /* tp_setattro */
|
||||
0, /* tp_as_buffer */
|
||||
Py_TPFLAGS_DEFAULT, /* tp_flags */
|
||||
"azlmbr_redirect objects", /* tp_doc */
|
||||
0, /* tp_traverse */
|
||||
0, /* tp_clear */
|
||||
0, /* tp_richcompare */
|
||||
0, /* tp_weaklistoffset */
|
||||
0, /* tp_iter */
|
||||
0, /* tp_iternext */
|
||||
RedirectMethods, /* tp_methods */
|
||||
0, /* tp_members */
|
||||
0, /* tp_getset */
|
||||
0, /* tp_base */
|
||||
0, /* tp_dict */
|
||||
0, /* tp_descr_get */
|
||||
0, /* tp_descr_set */
|
||||
0, /* tp_dictoffset */
|
||||
0, /* tp_init */
|
||||
0, /* tp_alloc */
|
||||
0 /* tp_new */
|
||||
};
|
||||
|
||||
PyModuleDef RedirectOutputModule = { PyModuleDef_HEAD_INIT, "azlmbr_redirect", 0, -1, 0, };
|
||||
|
||||
// Internal state
|
||||
PyObject* g_redirect_stdout = nullptr;
|
||||
PyObject* g_redirect_stdout_saved = nullptr;
|
||||
PyObject* g_redirect_stderr = nullptr;
|
||||
PyObject* g_redirect_stderr_saved = nullptr;
|
||||
|
||||
PyMODINIT_FUNC PyInit_RedirectOutput(void)
|
||||
{
|
||||
g_redirect_stdout = nullptr;
|
||||
g_redirect_stdout_saved = nullptr;
|
||||
g_redirect_stderr = nullptr;
|
||||
g_redirect_stderr_saved = nullptr;
|
||||
|
||||
RedirectOutputType.tp_new = PyType_GenericNew;
|
||||
if (PyType_Ready(&RedirectOutputType) < 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
PyObject* m = PyModule_Create(&RedirectOutputModule);
|
||||
if (m)
|
||||
{
|
||||
Py_INCREF(&RedirectOutputType);
|
||||
PyModule_AddObject(m, "Redirect", reinterpret_cast<PyObject*>(&RedirectOutputType));
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
void SetRedirection(const char* funcname, PyObject*& saved, PyObject*& current, RedirectOutputFunc func)
|
||||
{
|
||||
if (PyType_Ready(&RedirectOutputType) < 0)
|
||||
{
|
||||
AZ_Warning("python", false, "RedirectOutputType not ready!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!current)
|
||||
{
|
||||
saved = PySys_GetObject(funcname); // borrowed
|
||||
current = RedirectOutputType.tp_new(&RedirectOutputType, 0, 0);
|
||||
}
|
||||
|
||||
RedirectOutput* redirectOutput = reinterpret_cast<RedirectOutput*>(current);
|
||||
redirectOutput->write = func;
|
||||
PySys_SetObject(funcname, current);
|
||||
}
|
||||
|
||||
void ResetRedirection(const char* funcname, PyObject*& saved, PyObject*& current)
|
||||
{
|
||||
if (current)
|
||||
{
|
||||
PySys_SetObject(funcname, saved);
|
||||
}
|
||||
Py_XDECREF(current);
|
||||
current = nullptr;
|
||||
}
|
||||
|
||||
PyObject* s_RedirectModule = nullptr;
|
||||
|
||||
void Intialize(PyObject* module)
|
||||
{
|
||||
using namespace AzToolsFramework;
|
||||
|
||||
s_RedirectModule = module;
|
||||
|
||||
SetRedirection("stdout", g_redirect_stdout_saved, g_redirect_stdout, [](const char* msg)
|
||||
{
|
||||
EditorPythonConsoleNotificationBus::Broadcast(&EditorPythonConsoleNotificationBus::Events::OnTraceMessage, msg);
|
||||
});
|
||||
|
||||
SetRedirection("stderr", g_redirect_stderr_saved, g_redirect_stderr, [](const char* msg)
|
||||
{
|
||||
EditorPythonConsoleNotificationBus::Broadcast(&EditorPythonConsoleNotificationBus::Events::OnErrorMessage, msg);
|
||||
});
|
||||
|
||||
PySys_WriteStdout("RedirectOutput installed");
|
||||
}
|
||||
|
||||
void Shutdown()
|
||||
{
|
||||
ResetRedirection("stdout", g_redirect_stdout_saved, g_redirect_stdout);
|
||||
ResetRedirection("stderr", g_redirect_stderr_saved, g_redirect_stderr);
|
||||
Py_XDECREF(s_RedirectModule);
|
||||
s_RedirectModule = nullptr;
|
||||
}
|
||||
} // namespace RedirectOutput
|
||||
|
||||
namespace EditorPythonBindings
|
||||
{
|
||||
void PythonSystemComponent::Reflect(AZ::ReflectContext* context)
|
||||
{
|
||||
if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
|
||||
{
|
||||
serialize->Class<PythonSystemComponent, AZ::Component>()
|
||||
->Version(1)
|
||||
->Attribute(AZ::Edit::Attributes::SystemComponentTags, AZStd::vector<AZ::Crc32>{AZ_CRC_CE("AssetBuilder")})
|
||||
;
|
||||
|
||||
if (AZ::EditContext* ec = serialize->GetEditContext())
|
||||
{
|
||||
ec->Class<PythonSystemComponent>("PythonSystemComponent", "The Python interpreter")
|
||||
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
|
||||
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("System"))
|
||||
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PythonSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
|
||||
{
|
||||
provided.push_back(PythonEmbeddedService);
|
||||
}
|
||||
|
||||
void PythonSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
|
||||
{
|
||||
incompatible.push_back(PythonEmbeddedService);
|
||||
}
|
||||
|
||||
void PythonSystemComponent::Activate()
|
||||
{
|
||||
AZ::Interface<AzToolsFramework::EditorPythonEventsInterface>::Register(this);
|
||||
AzToolsFramework::EditorPythonRunnerRequestBus::Handler::BusConnect();
|
||||
}
|
||||
|
||||
void PythonSystemComponent::Deactivate()
|
||||
{
|
||||
AzToolsFramework::EditorPythonRunnerRequestBus::Handler::BusDisconnect();
|
||||
AZ::Interface<AzToolsFramework::EditorPythonEventsInterface>::Unregister(this);
|
||||
StopPython(true);
|
||||
}
|
||||
|
||||
bool PythonSystemComponent::StartPython([[maybe_unused]] bool silenceWarnings)
|
||||
{
|
||||
struct ReleaseInitalizeWaiterScope final
|
||||
{
|
||||
using ReleaseFunction = AZStd::function<void(void)>;
|
||||
|
||||
ReleaseInitalizeWaiterScope(ReleaseFunction releaseFunction)
|
||||
{
|
||||
m_releaseFunction = AZStd::move(releaseFunction);
|
||||
}
|
||||
~ReleaseInitalizeWaiterScope()
|
||||
{
|
||||
m_releaseFunction();
|
||||
}
|
||||
ReleaseFunction m_releaseFunction;
|
||||
};
|
||||
ReleaseInitalizeWaiterScope scope([this]()
|
||||
{
|
||||
m_initalizeWaiter.release(m_initalizeWaiterCount);
|
||||
m_initalizeWaiterCount = 0;
|
||||
});
|
||||
|
||||
if (Py_IsInitialized())
|
||||
{
|
||||
AZ_Warning("python", silenceWarnings, "Python is already active!");
|
||||
return false;
|
||||
}
|
||||
|
||||
PythonPathStack pythonPathStack;
|
||||
DiscoverPythonPaths(pythonPathStack);
|
||||
|
||||
EditorPythonBindingsNotificationBus::Broadcast(&EditorPythonBindingsNotificationBus::Events::OnPreInitialize);
|
||||
if (StartPythonInterpreter(pythonPathStack))
|
||||
{
|
||||
EditorPythonBindingsNotificationBus::Broadcast(&EditorPythonBindingsNotificationBus::Events::OnPostInitialize);
|
||||
// initialize internal base module and bootstrap scripts
|
||||
ExecuteByString("import azlmbr", false);
|
||||
ExecuteBootstrapScripts(pythonPathStack);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PythonSystemComponent::StopPython([[maybe_unused]] bool silenceWarnings)
|
||||
{
|
||||
if (!Py_IsInitialized())
|
||||
{
|
||||
AZ_Warning("python", silenceWarnings, "Python is not active!");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool result = false;
|
||||
EditorPythonBindingsNotificationBus::Broadcast(&EditorPythonBindingsNotificationBus::Events::OnPreFinalize);
|
||||
AzToolsFramework::EditorPythonRunnerRequestBus::Handler::BusDisconnect();
|
||||
|
||||
result = StopPythonInterpreter();
|
||||
EditorPythonBindingsNotificationBus::Broadcast(&EditorPythonBindingsNotificationBus::Events::OnPostFinalize);
|
||||
return result;
|
||||
}
|
||||
|
||||
void PythonSystemComponent::WaitForInitialization()
|
||||
{
|
||||
m_initalizeWaiterCount++;
|
||||
m_initalizeWaiter.acquire();
|
||||
}
|
||||
|
||||
void PythonSystemComponent::ExecuteWithLock(AZStd::function<void()> executionCallback)
|
||||
{
|
||||
AZStd::lock_guard<decltype(m_lock)> lock(m_lock);
|
||||
pybind11::gil_scoped_release release;
|
||||
pybind11::gil_scoped_acquire acquire;
|
||||
executionCallback();
|
||||
}
|
||||
|
||||
void PythonSystemComponent::DiscoverPythonPaths(PythonPathStack& pythonPathStack)
|
||||
{
|
||||
// the order of the Python paths is the order the Python bootstrap scripts will execute
|
||||
|
||||
AZStd::string gameFolder;
|
||||
auto settingsRegistry = AZ::SettingsRegistry::Get();
|
||||
settingsRegistry->Get(gameFolder, AZ::SettingsRegistryInterface::FixedValueString::format("%s/%s",
|
||||
AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey, AzFramework::AssetSystem::ProjectName));
|
||||
|
||||
if (gameFolder.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto resolveScriptPath = [&pythonPathStack](AZStd::string_view path)
|
||||
{
|
||||
AZStd::string editorScriptsPath;
|
||||
AzFramework::StringFunc::Path::Join(path.data(), "Editor/Scripts", editorScriptsPath);
|
||||
if (AZ::IO::SystemFile::Exists(editorScriptsPath.c_str()))
|
||||
{
|
||||
pythonPathStack.emplace_back(editorScriptsPath);
|
||||
}
|
||||
};
|
||||
|
||||
// The discovery order will be:
|
||||
// - engine
|
||||
// - gems
|
||||
// - project
|
||||
// - user(dev)
|
||||
|
||||
// engine
|
||||
const char* engineRoot = nullptr;
|
||||
AzFramework::ApplicationRequests::Bus::BroadcastResult(engineRoot, &AzFramework::ApplicationRequests::GetEngineRoot);
|
||||
resolveScriptPath(engineRoot);
|
||||
|
||||
// gems
|
||||
auto moduleCallback = [this, &pythonPathStack, resolveScriptPath, engineRoot](const AZ::ModuleData& moduleData) -> bool
|
||||
{
|
||||
if (moduleData.GetDynamicModuleHandle())
|
||||
{
|
||||
const AZ::OSString& modulePath = moduleData.GetDynamicModuleHandle()->GetFilename();
|
||||
AZStd::string fileName;
|
||||
AzFramework::StringFunc::Path::GetFileName(modulePath.c_str(), fileName);
|
||||
|
||||
AZStd::vector<AZStd::string> tokens;
|
||||
AzFramework::StringFunc::Tokenize(fileName.c_str(), tokens, '.');
|
||||
if (tokens.size() > 2 && tokens[0] == "Gem")
|
||||
{
|
||||
resolveScriptPath(AZStd::string::format("%s/Gems/%s", engineRoot, tokens[1].c_str()));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
AZ::ModuleManagerRequestBus::Broadcast(&AZ::ModuleManagerRequestBus::Events::EnumerateModules, moduleCallback);
|
||||
|
||||
// project
|
||||
const char* appRoot = nullptr;
|
||||
AzFramework::ApplicationRequests::Bus::BroadcastResult(appRoot, &AzFramework::ApplicationRequests::GetAppRoot);
|
||||
resolveScriptPath(AZStd::string::format("%s/%s", appRoot, gameFolder.c_str()));
|
||||
|
||||
// user
|
||||
AZStd::string assetsType;
|
||||
AZ::SettingsRegistryMergeUtils::PlatformGet(*settingsRegistry, assetsType,
|
||||
AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey, AzFramework::AssetSystem::Assets);
|
||||
if (!assetsType.empty())
|
||||
{
|
||||
// the pattern to user path is <appRoot>/Cache/<gameFolder>/<assetsType>/user e.g. c:/myroot/dev/Cache/MyProject/pc/user
|
||||
AZStd::string userRelativePath = AZStd::string::format("Cache/%s/%s/user", gameFolder.c_str(), assetsType.c_str());
|
||||
AZStd::string userCachePath;
|
||||
AzFramework::StringFunc::Path::Join(appRoot, userRelativePath.c_str(), userCachePath);
|
||||
resolveScriptPath(userCachePath);
|
||||
}
|
||||
}
|
||||
|
||||
void PythonSystemComponent::ExecuteBootstrapScripts(const PythonPathStack& pythonPathStack)
|
||||
{
|
||||
for(const auto& path : pythonPathStack)
|
||||
{
|
||||
AZStd::string bootstrapPath;
|
||||
AzFramework::StringFunc::Path::Join(path.c_str(), "bootstrap.py", bootstrapPath);
|
||||
if (AZ::IO::SystemFile::Exists(bootstrapPath.c_str()))
|
||||
{
|
||||
ExecuteByFilename(bootstrapPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool PythonSystemComponent::StartPythonInterpreter(const PythonPathStack& pythonPathStack)
|
||||
{
|
||||
AZStd::unordered_set<AZStd::string> pyPackageSites(pythonPathStack.begin(), pythonPathStack.end());
|
||||
|
||||
const char* engineRoot = nullptr;
|
||||
AzFramework::ApplicationRequests::Bus::BroadcastResult(engineRoot, &AzFramework::ApplicationRequests::GetEngineRoot);
|
||||
|
||||
// set PYTHON_HOME
|
||||
AZStd::string pyBasePath = Platform::GetPythonHomePath(PY_PACKAGE, engineRoot);
|
||||
if (!AZ::IO::SystemFile::Exists(pyBasePath.c_str()))
|
||||
{
|
||||
AZ_Warning("python", false, "Python home path must exist! path:%s", pyBasePath.c_str());
|
||||
return false;
|
||||
}
|
||||
AZStd::wstring pyHomePath;
|
||||
AZStd::to_wstring(pyHomePath, pyBasePath);
|
||||
Py_SetPythonHome(pyHomePath.c_str());
|
||||
|
||||
// display basic Python information
|
||||
AZ_TracePrintf("python", "Py_GetVersion=%s \n", Py_GetVersion());
|
||||
AZ_TracePrintf("python", "Py_GetPath=%ls \n", Py_GetPath());
|
||||
AZ_TracePrintf("python", "Py_GetExecPrefix=%ls \n", Py_GetExecPrefix());
|
||||
AZ_TracePrintf("python", "Py_GetProgramFullPath=%ls \n", Py_GetProgramFullPath());
|
||||
|
||||
PyImport_AppendInittab("azlmbr_redirect", RedirectOutput::PyInit_RedirectOutput);
|
||||
try
|
||||
{
|
||||
// ignore system location for sites site-packages
|
||||
Py_IsolatedFlag = 1; // -I - Also sets Py_NoUserSiteDirectory. If removed PyNoUserSiteDirectory should be set.
|
||||
Py_IgnoreEnvironmentFlag = 1; // -E
|
||||
|
||||
const bool initializeSignalHandlers = true;
|
||||
pybind11::initialize_interpreter(initializeSignalHandlers);
|
||||
|
||||
// Add custom site packages after initializing the interpreter above. Calling Py_SetPath before initialization
|
||||
// alters the behavior of the initializer to not compute default search paths. See https://docs.python.org/3/c-api/init.html#c.Py_SetPath
|
||||
if (pyPackageSites.size())
|
||||
{
|
||||
ExtendSysPath(pyPackageSites);
|
||||
}
|
||||
RedirectOutput::Intialize(PyImport_ImportModule("azlmbr_redirect"));
|
||||
|
||||
// Acquire GIL before calling Python code
|
||||
AZStd::lock_guard<decltype(m_lock)> lock(m_lock);
|
||||
pybind11::gil_scoped_acquire acquire;
|
||||
|
||||
// print Python version using AZ logging
|
||||
const int verRet = PyRun_SimpleStringFlags("import sys \nprint (sys.version) \n", nullptr);
|
||||
AZ_Error("python", verRet == 0, "Error trying to fetch the version number in Python!");
|
||||
return verRet == 0 && !PyErr_Occurred();
|
||||
}
|
||||
catch ([[maybe_unused]] const std::exception& e)
|
||||
{
|
||||
AZ_Warning("python", false, "Py_Initialize() failed with %s!", e.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool PythonSystemComponent::ExtendSysPath(const AZStd::unordered_set<AZStd::string>& extendPaths)
|
||||
{
|
||||
AZStd::string oldPath{ Py_EncodeLocale(Py_GetPath(), nullptr) };
|
||||
AZStd::vector<AZStd::string> pathParts;
|
||||
AZ::StringFunc::Tokenize(oldPath, pathParts, DELIM, false, false);
|
||||
AZStd::unordered_set<AZStd::string> oldPathSet{ pathParts.begin(), pathParts.end() };
|
||||
bool appended{ false };
|
||||
AZStd::string pathAppend{ "import sys\n" };
|
||||
for (const auto& thisStr : extendPaths)
|
||||
{
|
||||
if (oldPathSet.find(thisStr) == oldPathSet.end())
|
||||
{
|
||||
pathAppend.append(AZStd::string::format("sys.path.append('%s')\n", thisStr.c_str()));
|
||||
appended = true;
|
||||
}
|
||||
}
|
||||
if (appended)
|
||||
{
|
||||
ExecuteByString(pathAppend.c_str(), true);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PythonSystemComponent::StopPythonInterpreter()
|
||||
{
|
||||
if (Py_IsInitialized())
|
||||
{
|
||||
RedirectOutput::Shutdown();
|
||||
pybind11::finalize_interpreter();
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Warning("python", false, "Did not finalize since Py_IsInitialized() was false.");
|
||||
}
|
||||
return !PyErr_Occurred();
|
||||
}
|
||||
|
||||
void PythonSystemComponent::ExecuteByString(AZStd::string_view script, bool printResult)
|
||||
{
|
||||
if (!Py_IsInitialized())
|
||||
{
|
||||
AZ_Error("python", false, "Can not ExecuteByString() since the embeded Python VM is not ready.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!script.empty())
|
||||
{
|
||||
// Acquire GIL before calling Python code
|
||||
AZStd::lock_guard<decltype(m_lock)> lock(m_lock);
|
||||
pybind11::gil_scoped_acquire acquire;
|
||||
|
||||
// Acquire scope for __main__ for executing our script
|
||||
pybind11::object scope = pybind11::module::import("__main__").attr("__dict__");
|
||||
|
||||
bool shouldPrintValue = false;
|
||||
|
||||
if (printResult)
|
||||
{
|
||||
// Attempt to compile our code to determine if it's an expression
|
||||
// i.e. a Python code object with only an rvalue
|
||||
// If it is, it can be evaled to produce a PyObject
|
||||
// If it's not, we can't evaluate it into a result and should fall back to exec
|
||||
shouldPrintValue = true;
|
||||
|
||||
using namespace pybind11::literals;
|
||||
|
||||
// codeop.compile_command is a thin wrapper around the Python compile builtin
|
||||
// We attempt to compile using symbol="eval" to see if the string is valid for eval
|
||||
// This is similar to what the Python REPL does internally
|
||||
pybind11::object codeop = pybind11::module::import("codeop");
|
||||
pybind11::object compileCommand = codeop.attr("compile_command");
|
||||
try
|
||||
{
|
||||
compileCommand(script.data(), "symbol"_a="eval");
|
||||
}
|
||||
catch (const pybind11::error_already_set&)
|
||||
{
|
||||
shouldPrintValue = false;
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (shouldPrintValue)
|
||||
{
|
||||
// We're an expression, run and print the result
|
||||
pybind11::object result = pybind11::eval(script.data(), scope);
|
||||
pybind11::print(result);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Just exec the code block
|
||||
pybind11::exec(script.data(), scope);
|
||||
}
|
||||
}
|
||||
catch (pybind11::error_already_set& pythonError)
|
||||
{
|
||||
// Release the exception stack and let Python print it to stderr
|
||||
pythonError.restore();
|
||||
PyErr_Print();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PythonSystemComponent::ExecuteByFilename(AZStd::string_view filename)
|
||||
{
|
||||
AZStd::vector<AZStd::string_view> args;
|
||||
ExecuteByFilenameWithArgs(filename, args);
|
||||
}
|
||||
|
||||
void PythonSystemComponent::ExecuteByFilenameAsTest(AZStd::string_view filename, const AZStd::vector<AZStd::string_view>& args)
|
||||
{
|
||||
const Result evalResult = EvaluateFile(filename, args);
|
||||
if (evalResult == Result::Okay)
|
||||
{
|
||||
// all good, the test script will need to exit the application now
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// something when wrong with executing the test script
|
||||
AZ::Debug::Trace::Terminate(1);
|
||||
}
|
||||
}
|
||||
|
||||
void PythonSystemComponent::ExecuteByFilenameWithArgs(AZStd::string_view filename, const AZStd::vector<AZStd::string_view>& args)
|
||||
{
|
||||
EvaluateFile(filename, args);
|
||||
}
|
||||
|
||||
PythonSystemComponent::Result PythonSystemComponent::EvaluateFile(AZStd::string_view filename, const AZStd::vector<AZStd::string_view>& args)
|
||||
{
|
||||
if (!Py_IsInitialized())
|
||||
{
|
||||
AZ_Error("python", false, "Can not evaluate file since the embedded Python VM is not ready.");
|
||||
return Result::Error_IsNotInitialized;
|
||||
}
|
||||
|
||||
if (filename.empty())
|
||||
{
|
||||
AZ_Error("python", false, "Invalid empty filename detected.");
|
||||
return Result::Error_InvalidFilename;
|
||||
}
|
||||
|
||||
// support the alias version of a script such as @devroot@/Editor/Scripts/select_story_anim_objects.py
|
||||
AZStd::string theFilename(filename);
|
||||
{
|
||||
char resolvedPath[AZ_MAX_PATH_LEN] = { 0 };
|
||||
AZ::IO::FileIOBase::GetDirectInstance()->ResolvePath(theFilename.c_str(), resolvedPath, AZ_MAX_PATH_LEN);
|
||||
theFilename = resolvedPath;
|
||||
}
|
||||
|
||||
if (!AZ::IO::FileIOBase::GetInstance()->Exists(theFilename.c_str()))
|
||||
{
|
||||
AZ_Error("python", false, "Missing Python file named (%s)", theFilename.c_str());
|
||||
return Result::Error_MissingFile;
|
||||
}
|
||||
|
||||
FILE* file = _Py_fopen(theFilename.data(), "rb");
|
||||
if (!file)
|
||||
{
|
||||
AZ_Error("python", false, "Missing Python file named (%s)", theFilename.c_str());
|
||||
return Result::Error_FileOpenValidation;
|
||||
}
|
||||
|
||||
Result pythonScriptResult = Result::Okay;
|
||||
try
|
||||
{
|
||||
// Acquire GIL before calling Python code
|
||||
AZStd::lock_guard<decltype(m_lock)> lock(m_lock);
|
||||
pybind11::gil_scoped_acquire acquire;
|
||||
|
||||
// Create standard "argc" / "argv" command-line parameters to pass in to the Python script via sys.argv.
|
||||
// argc = number of parameters. This will always be at least 1, since the first parameter is the script name.
|
||||
// argv = the list of parameters, in wchar format.
|
||||
// Our expectation is that the args passed into this function does *not* already contain the script name.
|
||||
int argc = aznumeric_cast<int>(args.size()) + 1;
|
||||
|
||||
// Note: This allocates from PyMem to ensure that Python has access to the memory.
|
||||
wchar_t** argv = static_cast<wchar_t**>(PyMem_Malloc(argc * sizeof(wchar_t*)));
|
||||
|
||||
// Python 3.x is expecting wchar* strings for the command-line args.
|
||||
argv[0] = Py_DecodeLocale(theFilename.c_str(), nullptr);
|
||||
|
||||
for (int arg = 0; arg < args.size(); arg++)
|
||||
{
|
||||
argv[arg + 1] = Py_DecodeLocale(args[arg].data(), nullptr);
|
||||
}
|
||||
// Tell Python the command-line args.
|
||||
// Note that this has a side effect of adding the script's path to the set of directories checked for "import" commands.
|
||||
const int updatePath = 1;
|
||||
PySys_SetArgvEx(argc, argv, updatePath);
|
||||
|
||||
PyCompilerFlags flags;
|
||||
flags.cf_flags = 0;
|
||||
const int bAutoCloseFile = true;
|
||||
const int returnCode = PyRun_SimpleFileExFlags(file, theFilename.c_str(), bAutoCloseFile, &flags);
|
||||
if (returnCode != 0)
|
||||
{
|
||||
AZStd::string message = AZStd::string::format("Detected script failure in Python script(%s); return code %d!", theFilename.c_str(), returnCode);
|
||||
AZ_Warning("python", false, message.c_str());
|
||||
using namespace AzToolsFramework;
|
||||
EditorPythonConsoleNotificationBus::Broadcast(&EditorPythonConsoleNotificationBus::Events::OnExceptionMessage, message.c_str());
|
||||
pythonScriptResult = Result::Error_PythonException;
|
||||
}
|
||||
|
||||
// Free any memory allocated for the command-line args.
|
||||
for (int arg = 0; arg < argc; arg++)
|
||||
{
|
||||
PyMem_RawFree(argv[arg]);
|
||||
}
|
||||
PyMem_Free(argv);
|
||||
}
|
||||
catch ([[maybe_unused]] const std::exception& e)
|
||||
{
|
||||
AZ_Error("python", false, "Detected an internal exception %s while running script (%s)!", e.what(), theFilename.c_str());
|
||||
return Result::Error_InternalException;
|
||||
}
|
||||
return pythonScriptResult;
|
||||
}
|
||||
|
||||
} // namespace EditorPythonBindings
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <EditorPythonBindings/EditorPythonBindingsSymbols.h>
|
||||
|
||||
#include <AzCore/Component/Component.h>
|
||||
#include <AzCore/std/containers/unordered_set.h>
|
||||
#include <AzToolsFramework/API/EditorPythonConsoleBus.h>
|
||||
#include <AzToolsFramework/API/EditorPythonRunnerRequestsBus.h>
|
||||
#include <AzCore/std/parallel/semaphore.h>
|
||||
|
||||
namespace EditorPythonBindings
|
||||
{
|
||||
/**
|
||||
* Manages the Python interpreter inside this Gem (Editor only)
|
||||
* - redirects the Python standard output and error streams to AZ_TracePrintf and AZ_Warning, respectively
|
||||
*/
|
||||
class PythonSystemComponent
|
||||
: public AZ::Component
|
||||
, protected AzToolsFramework::EditorPythonEventsInterface
|
||||
, protected AzToolsFramework::EditorPythonRunnerRequestBus::Handler
|
||||
{
|
||||
public:
|
||||
AZ_COMPONENT(PythonSystemComponent, PythonSystemComponentTypeId, AZ::Component);
|
||||
|
||||
static void Reflect(AZ::ReflectContext* context);
|
||||
|
||||
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
|
||||
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
|
||||
|
||||
protected:
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// AZ::Component interface implementation
|
||||
void Activate() override;
|
||||
void Deactivate() override;
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// AzToolsFramework::EditorPythonEventsInterface
|
||||
bool StartPython(bool silenceWarnings = false) override;
|
||||
bool StopPython(bool silenceWarnings = false) override;
|
||||
void WaitForInitialization() override;
|
||||
void ExecuteWithLock(AZStd::function<void()> executionCallback) override;
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// AzToolsFramework::EditorPythonRunnerRequestBus::Handler interface implementation
|
||||
void ExecuteByString(AZStd::string_view script, bool printResult) override;
|
||||
void ExecuteByFilename(AZStd::string_view filename) override;
|
||||
void ExecuteByFilenameWithArgs(AZStd::string_view filename, const AZStd::vector<AZStd::string_view>& args) override;
|
||||
void ExecuteByFilenameAsTest(AZStd::string_view filename, const AZStd::vector<AZStd::string_view>& args) override;
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private:
|
||||
// handle multiple Python initializers and threads
|
||||
AZStd::atomic_int m_initalizeWaiterCount {0};
|
||||
AZStd::semaphore m_initalizeWaiter;
|
||||
AZStd::recursive_mutex m_lock;
|
||||
|
||||
enum class Result
|
||||
{
|
||||
Okay,
|
||||
Error_IsNotInitialized,
|
||||
Error_InvalidFilename,
|
||||
Error_MissingFile,
|
||||
Error_FileOpenValidation,
|
||||
Error_InternalException,
|
||||
Error_PythonException,
|
||||
};
|
||||
Result EvaluateFile(AZStd::string_view filename, const AZStd::vector<AZStd::string_view>& args);
|
||||
|
||||
// bootstrap logic and data
|
||||
using PythonPathStack = AZStd::vector<AZStd::string>;
|
||||
void DiscoverPythonPaths(PythonPathStack& pythonPathStack);
|
||||
void ExecuteBootstrapScripts(const PythonPathStack& pythonPathStack);
|
||||
bool ExtendSysPath(const AZStd::unordered_set<AZStd::string>& extendPaths);
|
||||
|
||||
// starts the Python interpreter
|
||||
bool StartPythonInterpreter(const PythonPathStack& pythonPathStack);
|
||||
bool StopPythonInterpreter();
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <Source/PythonCommon.h>
|
||||
#include <pybind11/pybind11.h>
|
||||
#include <AzCore/std/string/string.h>
|
||||
|
||||
namespace pybind11
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
//! Converts AZStd::string to/from Python String
|
||||
template <>
|
||||
struct type_caster<AZStd::string>
|
||||
{
|
||||
public:
|
||||
PYBIND11_TYPE_CASTER(AZStd::string, _("AZStd::string"));
|
||||
|
||||
bool load(handle pythonSource, bool)
|
||||
{
|
||||
Py_ssize_t size = 0;
|
||||
const char* pythonString = PyUnicode_AsUTF8AndSize(pythonSource.ptr(), &size);
|
||||
if (PyErr_Occurred() || !pythonString)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
value.assign(pythonString, size);
|
||||
return true;
|
||||
}
|
||||
|
||||
static handle cast(const AZStd::string& src, return_value_policy, handle)
|
||||
{
|
||||
return pybind11::str(src.c_str()).release();
|
||||
}
|
||||
};
|
||||
|
||||
//! Converts AZStd::string_view to/from Python String
|
||||
template <>
|
||||
struct type_caster<AZStd::string_view>
|
||||
{
|
||||
public:
|
||||
PYBIND11_TYPE_CASTER(AZStd::string_view, _("AZStd::string_view"));
|
||||
|
||||
bool load(handle pythonSource, bool)
|
||||
{
|
||||
Py_ssize_t size = 0;
|
||||
const char* pythonString = PyUnicode_AsUTF8AndSize(pythonSource.ptr(), &size);
|
||||
if (PyErr_Occurred() || !pythonString)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
value = { pythonString, static_cast<size_t>(size) };
|
||||
return true;
|
||||
}
|
||||
|
||||
static handle cast(const AZStd::string_view& src, return_value_policy, handle)
|
||||
{
|
||||
return pybind11::str(src.data(), src.size()).release();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,724 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <Source/PythonUtility.h>
|
||||
#include <Source/PythonProxyObject.h>
|
||||
#include <Source/PythonTypeCasters.h>
|
||||
#include <Source/PythonMarshalComponent.h>
|
||||
|
||||
#include <AzCore/RTTI/BehaviorContext.h>
|
||||
#include <AzCore/RTTI/TypeInfo.h>
|
||||
#include <AzFramework/StringFunc/StringFunc.h>
|
||||
#include <EditorPythonBindings/CustomTypeBindingBus.h>
|
||||
#include <pybind11/embed.h>
|
||||
|
||||
namespace EditorPythonBindings
|
||||
{
|
||||
namespace Module
|
||||
{
|
||||
pybind11::module DeterminePackageModule(PackageMapType& modulePackageMap, AZStd::string_view moduleName, pybind11::module parentModule, pybind11::module fallbackModule, [[maybe_unused]] bool alertUsingFallback)
|
||||
{
|
||||
if (moduleName.empty() || !moduleName[0])
|
||||
{
|
||||
AZ_Warning("python", !alertUsingFallback, "Could not determine missing or empty module; using fallback module");
|
||||
return fallbackModule;
|
||||
}
|
||||
else if (parentModule.is_none())
|
||||
{
|
||||
AZ_Warning("python", !alertUsingFallback, "Could not determine using None parent module; using fallback module");
|
||||
return fallbackModule;
|
||||
}
|
||||
|
||||
AZStd::string parentModuleName(PyModule_GetName(parentModule.ptr()));
|
||||
modulePackageMap[parentModuleName] = parentModule;
|
||||
pybind11::module currentModule = parentModule;
|
||||
|
||||
AZStd::string fullModuleName(parentModuleName);
|
||||
fullModuleName.append(".");
|
||||
fullModuleName.append(moduleName);
|
||||
|
||||
AZStd::vector<AZStd::string> moduleParts;
|
||||
AzFramework::StringFunc::Tokenize(fullModuleName.c_str(), moduleParts, ".", false, false);
|
||||
for (int modulePartsIndex = 0; modulePartsIndex < moduleParts.size(); ++modulePartsIndex)
|
||||
{
|
||||
AZStd::string currentModulePath;
|
||||
AzFramework::StringFunc::Join(currentModulePath, moduleParts.begin(), moduleParts.begin() + modulePartsIndex + 1, ".");
|
||||
|
||||
auto itPackageEntry = modulePackageMap.find(currentModulePath.c_str());
|
||||
if (itPackageEntry != modulePackageMap.end())
|
||||
{
|
||||
currentModule = itPackageEntry->second;
|
||||
}
|
||||
else
|
||||
{
|
||||
PyObject* newModule = PyImport_AddModule(currentModulePath.c_str());
|
||||
if (!newModule)
|
||||
{
|
||||
AZ_Warning("python", false, "Could not add module named %s; using fallback module", currentModulePath.c_str());
|
||||
return fallbackModule;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto newSubModule = pybind11::reinterpret_borrow<pybind11::module>(newModule);
|
||||
modulePackageMap[currentModulePath] = newSubModule;
|
||||
const char* subModuleName = moduleParts[modulePartsIndex].c_str();
|
||||
currentModule.attr(subModuleName) = newSubModule;
|
||||
currentModule = newSubModule;
|
||||
}
|
||||
}
|
||||
}
|
||||
return currentModule;
|
||||
}
|
||||
}
|
||||
|
||||
namespace Internal
|
||||
{
|
||||
void LogSerializeTypeInfo(const AZ::TypeId& typeId)
|
||||
{
|
||||
AZStd::string info = AZStd::string::format("Serialize class info for typeId %s (", typeId.ToString<AZStd::string>().c_str());
|
||||
|
||||
AZ::SerializeContext* serializeContext{ nullptr };
|
||||
AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext);
|
||||
if (serializeContext)
|
||||
{
|
||||
auto&& classInfo = serializeContext->FindClassData(typeId);
|
||||
if (classInfo)
|
||||
{
|
||||
info = AZStd::string::format("name:%s version:%d isContainer:%s",
|
||||
classInfo->m_name, classInfo->m_version, classInfo->m_container ? "true" : "false");
|
||||
}
|
||||
|
||||
auto&& genericClassInfo = serializeContext->FindGenericClassInfo(typeId);
|
||||
if (genericClassInfo)
|
||||
{
|
||||
info += " generic:true";
|
||||
info += AZStd::string::format(" specialized typeId: %s",
|
||||
genericClassInfo->GetSpecializedTypeId().ToString<AZStd::string>().c_str());
|
||||
info += AZStd::string::format(" generic typeId: %s",
|
||||
genericClassInfo->GetGenericTypeId().ToString<AZStd::string>().c_str());
|
||||
size_t numTemplatedArguments = genericClassInfo->GetNumTemplatedArguments();
|
||||
info += AZStd::string::format(" template arguments %zu", genericClassInfo->GetNumTemplatedArguments());
|
||||
for (size_t index = 0; index < numTemplatedArguments; ++index)
|
||||
{
|
||||
info += AZStd::string::format(" [%zu] template type: %s",
|
||||
index,
|
||||
genericClassInfo->GetTemplatedTypeId(index).ToString<AZStd::string>().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
info += ")";
|
||||
AZ_Warning("python", false, "Serialize generic class info %s", info.c_str());
|
||||
}
|
||||
|
||||
AZStd::optional<AZ::TypeId> IsEnumClass(const AZ::BehaviorParameter& behaviorParameter)
|
||||
{
|
||||
if (behaviorParameter.m_azRtti)
|
||||
{
|
||||
// If the underlying type of the supplied type is different, then T is an enum
|
||||
const AZ::TypeId& underlyingTypeId = AZ::Internal::GetUnderlyingTypeId(*behaviorParameter.m_azRtti);
|
||||
if (underlyingTypeId != behaviorParameter.m_typeId)
|
||||
{
|
||||
return AZStd::make_optional(underlyingTypeId);
|
||||
}
|
||||
}
|
||||
return AZStd::nullopt;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool ConvertPythonFromEnumClass(const AZ::TypeId& underlyingTypeId, AZ::BehaviorValueParameter& behaviorValue, AZ::s64& outboundPythonValue)
|
||||
{
|
||||
if (underlyingTypeId == AZ::AzTypeInfo<T>::Uuid())
|
||||
{
|
||||
outboundPythonValue = aznumeric_cast<AZ::s64>(*behaviorValue.GetAsUnsafe<T>());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
AZStd::optional<pybind11::object> ConvertFromEnumClass(AZ::BehaviorValueParameter& behaviorValue)
|
||||
{
|
||||
if (!behaviorValue.m_azRtti)
|
||||
{
|
||||
return AZStd::nullopt;
|
||||
}
|
||||
const AZ::TypeId& underlyingTypeId = AZ::Internal::GetUnderlyingTypeId(*behaviorValue.m_azRtti);
|
||||
if (underlyingTypeId != behaviorValue.m_typeId)
|
||||
{
|
||||
AZ::s64 outboundPythonValue = 0;
|
||||
|
||||
bool converted =
|
||||
ConvertPythonFromEnumClass<AZ::u8>(underlyingTypeId, behaviorValue, outboundPythonValue) ||
|
||||
ConvertPythonFromEnumClass<AZ::u16>(underlyingTypeId, behaviorValue, outboundPythonValue) ||
|
||||
ConvertPythonFromEnumClass<AZ::u32>(underlyingTypeId, behaviorValue, outboundPythonValue) ||
|
||||
ConvertPythonFromEnumClass<AZ::u64>(underlyingTypeId, behaviorValue, outboundPythonValue) ||
|
||||
ConvertPythonFromEnumClass<AZ::s8>(underlyingTypeId, behaviorValue, outboundPythonValue) ||
|
||||
ConvertPythonFromEnumClass<AZ::s16>(underlyingTypeId, behaviorValue, outboundPythonValue) ||
|
||||
ConvertPythonFromEnumClass<AZ::s32>(underlyingTypeId, behaviorValue, outboundPythonValue) ||
|
||||
ConvertPythonFromEnumClass<AZ::s64>(underlyingTypeId, behaviorValue, outboundPythonValue);
|
||||
|
||||
AZ_Error("python", converted, "Enumeration backed by a non-numeric integer type.");
|
||||
return converted ? AZStd::make_optional(pybind11::cast<AZ::s64>(outboundPythonValue)) : AZStd::nullopt;
|
||||
}
|
||||
return AZStd::nullopt;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool ConvertBehaviorParameterEnum(pybind11::object obj, const AZ::TypeId& underlyingTypeId, AZ::BehaviorValueParameter& parameter)
|
||||
{
|
||||
if (underlyingTypeId == AZ::AzTypeInfo<T>::Uuid())
|
||||
{
|
||||
void* value = parameter.m_tempData.allocate(sizeof(T), AZStd::alignment_of<T>::value, 0);
|
||||
*reinterpret_cast<T*>(value) = pybind11::cast<T>(obj);
|
||||
|
||||
if (parameter.m_traits & AZ::BehaviorParameter::TR_POINTER)
|
||||
{
|
||||
*reinterpret_cast<void**>(parameter.m_value) = reinterpret_cast<T*>(&value);
|
||||
}
|
||||
else
|
||||
{
|
||||
parameter.m_value = value;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ConvertEnumClassFromPython(pybind11::object obj, const AZ::BehaviorParameter& behaviorArgument, AZ::BehaviorValueParameter& parameter)
|
||||
{
|
||||
if (behaviorArgument.m_azRtti)
|
||||
{
|
||||
// If the underlying type of the supplied type is different, then T is an enum
|
||||
const AZ::TypeId underlyingTypeId = AZ::Internal::GetUnderlyingTypeId(*behaviorArgument.m_azRtti);
|
||||
if (underlyingTypeId != behaviorArgument.m_typeId)
|
||||
{
|
||||
parameter.m_name = behaviorArgument.m_name;
|
||||
parameter.m_azRtti = behaviorArgument.m_azRtti;
|
||||
parameter.m_traits = behaviorArgument.m_traits;
|
||||
parameter.m_typeId = behaviorArgument.m_typeId;
|
||||
|
||||
bool handled =
|
||||
ConvertBehaviorParameterEnum<AZ::u8>(obj, underlyingTypeId, parameter) ||
|
||||
ConvertBehaviorParameterEnum<AZ::u16>(obj, underlyingTypeId, parameter) ||
|
||||
ConvertBehaviorParameterEnum<AZ::u32>(obj, underlyingTypeId, parameter) ||
|
||||
ConvertBehaviorParameterEnum<AZ::u64>(obj, underlyingTypeId, parameter) ||
|
||||
ConvertBehaviorParameterEnum<AZ::s8>(obj, underlyingTypeId, parameter) ||
|
||||
ConvertBehaviorParameterEnum<AZ::s16>(obj, underlyingTypeId, parameter) ||
|
||||
ConvertBehaviorParameterEnum<AZ::s32>(obj, underlyingTypeId, parameter) ||
|
||||
ConvertBehaviorParameterEnum<AZ::s64>(obj, underlyingTypeId, parameter) ;
|
||||
|
||||
AZ_Error("python", handled, "Enumeration backed by a non-numeric integer type.");
|
||||
return handled;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// type checks
|
||||
bool IsPrimitiveType(const AZ::TypeId& typeId)
|
||||
{
|
||||
return (typeId == AZ::AzTypeInfo<bool>::Uuid() ||
|
||||
typeId == AZ::AzTypeInfo<char>::Uuid() ||
|
||||
typeId == AZ::AzTypeInfo<float>::Uuid() ||
|
||||
typeId == AZ::AzTypeInfo<double>::Uuid() ||
|
||||
typeId == AZ::AzTypeInfo<AZ::s8>::Uuid() ||
|
||||
typeId == AZ::AzTypeInfo<AZ::u8>::Uuid() ||
|
||||
typeId == AZ::AzTypeInfo<AZ::s16>::Uuid() ||
|
||||
typeId == AZ::AzTypeInfo<AZ::u16>::Uuid() ||
|
||||
typeId == AZ::AzTypeInfo<AZ::s32>::Uuid() ||
|
||||
typeId == AZ::AzTypeInfo<AZ::u32>::Uuid() ||
|
||||
typeId == AZ::AzTypeInfo<AZ::s64>::Uuid() ||
|
||||
typeId == AZ::AzTypeInfo<AZ::u64>::Uuid() );
|
||||
}
|
||||
|
||||
bool IsPointerType(const AZ::u32 traits)
|
||||
{
|
||||
return (((traits & AZ::BehaviorParameter::TR_POINTER) == AZ::BehaviorParameter::TR_POINTER) ||
|
||||
((traits & AZ::BehaviorParameter::TR_REFERENCE) == AZ::BehaviorParameter::TR_REFERENCE));
|
||||
}
|
||||
|
||||
// allocation patterns
|
||||
|
||||
void StoreVariableCustomTypeDeleter(
|
||||
CustomTypeBindingNotifications::ValueHandle handle,
|
||||
AZ::TypeId typeId,
|
||||
Convert::StackVariableAllocator& stackVariableAllocator)
|
||||
{
|
||||
auto deallocateValue = [typeId = std::move(typeId), handle]() mutable
|
||||
{
|
||||
CustomTypeBindingNotificationBus::Event(
|
||||
typeId,
|
||||
&CustomTypeBindingNotificationBus::Events::CleanUpValue,
|
||||
handle);
|
||||
};
|
||||
stackVariableAllocator.StoreVariableDeleter(deallocateValue);
|
||||
}
|
||||
|
||||
bool AllocateBehaviorObjectByClass(const AZ::BehaviorClass* behaviorClass, AZ::BehaviorObject& behaviorObject)
|
||||
{
|
||||
if (behaviorClass)
|
||||
{
|
||||
if (behaviorClass->m_defaultConstructor)
|
||||
{
|
||||
AZ::BehaviorObject newBehaviorObject = behaviorClass->Create();
|
||||
behaviorObject.m_typeId = newBehaviorObject.m_typeId;
|
||||
behaviorObject.m_address = newBehaviorObject.m_address;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Warning("python", behaviorClass->m_defaultConstructor, "Missing default constructor for AZ::BehaviorClass for typeId:%s", behaviorClass->m_name.c_str());
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AllocateBehaviorValueParameter(const AZ::BehaviorMethod* behaviorMethod, AZ::BehaviorValueParameter& result, Convert::StackVariableAllocator& stackVariableAllocator)
|
||||
{
|
||||
if (const AZ::BehaviorParameter* resultType = behaviorMethod->GetResult())
|
||||
{
|
||||
result.Set(*resultType);
|
||||
|
||||
if (auto underlyingTypeId = Internal::IsEnumClass(result); underlyingTypeId)
|
||||
{
|
||||
result.m_typeId = underlyingTypeId.value();
|
||||
}
|
||||
|
||||
if (resultType->m_traits & AZ::BehaviorParameter::TR_POINTER)
|
||||
{
|
||||
result.m_value = result.m_tempData.allocate(sizeof(intptr_t), alignof(intptr_t));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (resultType->m_traits & AZ::BehaviorParameter::TR_REFERENCE)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (IsPrimitiveType(resultType->m_typeId))
|
||||
{
|
||||
result.m_value = result.m_tempData.allocate(sizeof(intptr_t), alignof(intptr_t));
|
||||
return true;
|
||||
}
|
||||
|
||||
AZ::BehaviorContext* behaviorContext(nullptr);
|
||||
AZ::ComponentApplicationBus::BroadcastResult(behaviorContext, &AZ::ComponentApplicationRequests::GetBehaviorContext);
|
||||
if (!behaviorContext)
|
||||
{
|
||||
AZ_Assert(false, "A behavior context is required!");
|
||||
return false;
|
||||
}
|
||||
|
||||
const AZ::BehaviorClass* behaviorClass = AZ::BehaviorContextHelper::GetClass(behaviorContext, resultType->m_typeId);
|
||||
if (behaviorClass)
|
||||
{
|
||||
AZ::BehaviorObject behaviorObject;
|
||||
if (AllocateBehaviorObjectByClass(behaviorClass, behaviorObject))
|
||||
{
|
||||
result.m_value = behaviorObject.m_address;
|
||||
result.m_typeId = resultType->m_typeId;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CustomTypeBindingNotifications::AllocationHandle allocationHandleResult;
|
||||
CustomTypeBindingNotificationBus::EventResult(
|
||||
allocationHandleResult,
|
||||
result.m_typeId,
|
||||
&CustomTypeBindingNotificationBus::Events::AllocateDefault);
|
||||
|
||||
if (allocationHandleResult)
|
||||
{
|
||||
CustomTypeBindingNotifications::ValueHandle handle = allocationHandleResult.value().first;
|
||||
const AZ::BehaviorObject& behaviorObject = allocationHandleResult.value().second;
|
||||
StoreVariableCustomTypeDeleter(handle, behaviorObject.m_typeId, stackVariableAllocator);
|
||||
result.m_value = behaviorObject.m_address;
|
||||
result.m_typeId = behaviorObject.m_typeId;
|
||||
return true;
|
||||
}
|
||||
|
||||
// So far no allocation scheme has been found for this typeId, but the SerializeContext might have more information
|
||||
// so this code tries to pull out more type information about the typeId so that the user can get more human readable
|
||||
// information than a UUID
|
||||
LogSerializeTypeInfo(resultType->m_typeId);
|
||||
AZ_Error("python", behaviorClass, "A behavior class is missing for %s!",
|
||||
resultType->m_typeId.ToString<AZStd::string>().c_str());
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void DeallocateBehaviorValueParameter(AZ::BehaviorValueParameter& valueParameter)
|
||||
{
|
||||
if (IsPointerType(valueParameter.m_traits) || IsPrimitiveType(valueParameter.m_typeId))
|
||||
{
|
||||
// no constructor was used
|
||||
return;
|
||||
}
|
||||
|
||||
AZ::BehaviorContext* behaviorContext(nullptr);
|
||||
AZ::ComponentApplicationBus::BroadcastResult(behaviorContext, &AZ::ComponentApplicationRequests::GetBehaviorContext);
|
||||
if (!behaviorContext)
|
||||
{
|
||||
AZ_Assert(false, "A behavior context is required!");
|
||||
}
|
||||
|
||||
const AZ::BehaviorClass* behaviorClass = AZ::BehaviorContextHelper::GetClass(behaviorContext, valueParameter.m_typeId);
|
||||
if (behaviorClass)
|
||||
{
|
||||
AZ::BehaviorObject behaviorObject;
|
||||
behaviorObject.m_address = valueParameter.m_value;
|
||||
behaviorObject.m_typeId = valueParameter.m_typeId;
|
||||
behaviorClass->Destroy(behaviorObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace Convert
|
||||
{
|
||||
// StackVariableAllocator
|
||||
|
||||
StackVariableAllocator::~StackVariableAllocator()
|
||||
{
|
||||
for (auto& cleanUp : m_cleanUpItems)
|
||||
{
|
||||
cleanUp();
|
||||
}
|
||||
}
|
||||
|
||||
void StackVariableAllocator::StoreVariableDeleter(VariableDeleter&& deleter)
|
||||
{
|
||||
m_cleanUpItems.emplace_back(deleter);
|
||||
}
|
||||
|
||||
// from Python to BehaviorValueParameter
|
||||
|
||||
bool PythonProxyObjectToBehaviorValueParameter(const AZ::BehaviorParameter& behaviorArgument, pybind11::object pyObj, AZ::BehaviorValueParameter& parameter)
|
||||
{
|
||||
auto behaviorObject = pybind11::cast<EditorPythonBindings::PythonProxyObject*>(pyObj)->GetBehaviorObject();
|
||||
if (behaviorObject)
|
||||
{
|
||||
const AZ::BehaviorClass* behaviorClass = AZ::BehaviorContextHelper::GetClass(behaviorObject.value()->m_typeId);
|
||||
if (!behaviorClass)
|
||||
{
|
||||
AZ_Warning("python", false, "Missing BehaviorClass for typeId %s", behaviorObject.value()->m_typeId.ToString<AZStd::string>().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (behaviorClass->m_azRtti)
|
||||
{
|
||||
// is exact type or can be down casted?
|
||||
if (!behaviorClass->m_azRtti->IsTypeOf(behaviorArgument.m_typeId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (behaviorObject.value()->m_typeId != behaviorArgument.m_typeId)
|
||||
{
|
||||
// type mismatch detected
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((behaviorArgument.m_traits & AZ::BehaviorParameter::TR_POINTER) == AZ::BehaviorParameter::TR_POINTER)
|
||||
{
|
||||
parameter.m_value = &behaviorObject.value()->m_address;
|
||||
}
|
||||
else
|
||||
{
|
||||
parameter.m_value = behaviorObject.value()->m_address;
|
||||
}
|
||||
parameter.m_typeId = behaviorClass->m_typeId;
|
||||
parameter.m_azRtti = behaviorClass->m_azRtti;
|
||||
parameter.m_traits = behaviorArgument.m_traits;
|
||||
parameter.m_name = behaviorArgument.m_name;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CustomPythonToBehavior(
|
||||
const AZ::BehaviorParameter& behaviorArgument,
|
||||
pybind11::object pyObj,
|
||||
AZ::BehaviorValueParameter& outBehavior,
|
||||
StackVariableAllocator& stackVariableAllocator)
|
||||
{
|
||||
AZStd::optional<CustomTypeBindingNotifications::ValueHandle> handle;
|
||||
CustomTypeBindingNotificationBus::EventResult(
|
||||
handle,
|
||||
behaviorArgument.m_typeId,
|
||||
&CustomTypeBindingNotificationBus::Events::PythonToBehavior,
|
||||
pyObj.ptr(),
|
||||
static_cast<AZ::BehaviorParameter::Traits>(behaviorArgument.m_traits),
|
||||
outBehavior);
|
||||
|
||||
if (handle)
|
||||
{
|
||||
Internal::StoreVariableCustomTypeDeleter(handle.value(), behaviorArgument.m_typeId, stackVariableAllocator);
|
||||
outBehavior.m_typeId = behaviorArgument.m_typeId;
|
||||
outBehavior.m_traits = behaviorArgument.m_traits;
|
||||
outBehavior.m_name = behaviorArgument.m_name;
|
||||
outBehavior.m_azRtti = behaviorArgument.m_azRtti;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PythonToBehaviorValueParameter(const AZ::BehaviorParameter& behaviorArgument, pybind11::object pyObj, AZ::BehaviorValueParameter& parameter, Convert::StackVariableAllocator& stackVariableAllocator)
|
||||
{
|
||||
AZStd::optional<PythonMarshalTypeRequests::BehaviorValueResult> result;
|
||||
PythonMarshalTypeRequests::BehaviorTraits traits = static_cast<PythonMarshalTypeRequests::BehaviorTraits>(behaviorArgument.m_traits);
|
||||
PythonMarshalTypeRequestBus::EventResult(result, behaviorArgument.m_typeId, &PythonMarshalTypeRequestBus::Events::PythonToBehaviorValueParameter, traits, pyObj, parameter);
|
||||
if (result && result.value().first)
|
||||
{
|
||||
auto deleter = AZStd::move(result.value().second);
|
||||
if (deleter)
|
||||
{
|
||||
stackVariableAllocator.StoreVariableDeleter(AZStd::move(deleter));
|
||||
}
|
||||
parameter.m_typeId = behaviorArgument.m_typeId;
|
||||
parameter.m_traits = behaviorArgument.m_traits;
|
||||
parameter.m_name = behaviorArgument.m_name;
|
||||
parameter.m_azRtti = behaviorArgument.m_azRtti;
|
||||
return true;
|
||||
}
|
||||
else if (auto underlyingTypeId = Internal::IsEnumClass(behaviorArgument); underlyingTypeId)
|
||||
{
|
||||
AZ::BehaviorParameter tempArg;
|
||||
tempArg.m_azRtti = behaviorArgument.m_azRtti;
|
||||
tempArg.m_traits = behaviorArgument.m_traits;
|
||||
tempArg.m_name = behaviorArgument.m_name;
|
||||
tempArg.m_typeId = underlyingTypeId.value();
|
||||
if (PythonToBehaviorValueParameter(tempArg, pyObj, parameter, stackVariableAllocator))
|
||||
{
|
||||
parameter.m_typeId = behaviorArgument.m_typeId;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (pybind11::isinstance<EditorPythonBindings::PythonProxyObject>(pyObj))
|
||||
{
|
||||
return PythonProxyObjectToBehaviorValueParameter(behaviorArgument, pyObj, parameter);
|
||||
}
|
||||
else if (CustomPythonToBehavior(behaviorArgument, pyObj, parameter, stackVariableAllocator))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// from BehaviorValueParameter to Python
|
||||
|
||||
AZStd::optional<pybind11::object> CustomBehaviorToPython(AZ::BehaviorValueParameter& behaviorValue, Convert::StackVariableAllocator& stackVariableAllocator)
|
||||
{
|
||||
AZStd::optional<CustomTypeBindingNotifications::ValueHandle> handle;
|
||||
PyObject* outPyObj = nullptr;
|
||||
CustomTypeBindingNotificationBus::EventResult(
|
||||
handle,
|
||||
behaviorValue.m_typeId,
|
||||
&CustomTypeBindingNotificationBus::Events::BehaviorToPython,
|
||||
behaviorValue,
|
||||
outPyObj);
|
||||
|
||||
if (outPyObj != nullptr && handle)
|
||||
{
|
||||
Internal::StoreVariableCustomTypeDeleter(handle.value(), behaviorValue.m_typeId, stackVariableAllocator);
|
||||
return { pybind11::reinterpret_borrow<pybind11::object>(outPyObj) };
|
||||
}
|
||||
|
||||
return AZStd::nullopt;
|
||||
}
|
||||
|
||||
pybind11::object BehaviorValueParameterToPython(AZ::BehaviorValueParameter& behaviorValue, Convert::StackVariableAllocator& stackVariableAllocator)
|
||||
{
|
||||
auto pyValue = Internal::ConvertFromEnumClass(behaviorValue);
|
||||
if (pyValue.has_value())
|
||||
{
|
||||
return pyValue.value();
|
||||
}
|
||||
|
||||
AZStd::optional<PythonMarshalTypeRequests::PythonValueResult> result;
|
||||
PythonMarshalTypeRequestBus::EventResult(result, behaviorValue.m_typeId, &PythonMarshalTypeRequestBus::Events::BehaviorValueParameterToPython, behaviorValue);
|
||||
if (result.has_value())
|
||||
{
|
||||
auto deleter = AZStd::move(result.value().second);
|
||||
if (deleter)
|
||||
{
|
||||
stackVariableAllocator.StoreVariableDeleter(AZStd::move(deleter));
|
||||
}
|
||||
return result.value().first;
|
||||
}
|
||||
else if (auto customResult = CustomBehaviorToPython(behaviorValue, stackVariableAllocator); customResult)
|
||||
{
|
||||
return customResult.value();
|
||||
}
|
||||
else if (behaviorValue.m_typeId != AZ::Uuid::CreateNull() && behaviorValue.GetValueAddress())
|
||||
{
|
||||
return PythonProxyObjectManagement::CreatePythonProxyObject(behaviorValue.m_typeId, behaviorValue.GetValueAddress());
|
||||
}
|
||||
AZ_Warning("python", false, "Cannot convert type %s",
|
||||
behaviorValue.m_name ? behaviorValue.m_name : behaviorValue.m_typeId.ToString<AZStd::string>().c_str());
|
||||
return pybind11::cast<pybind11::none>(Py_None);
|
||||
}
|
||||
|
||||
AZStd::string GetPythonTypeName(pybind11::object pyObj)
|
||||
{
|
||||
if (pybind11::isinstance<PythonProxyObject>(pyObj))
|
||||
{
|
||||
return pybind11::cast<PythonProxyObject*>(pyObj)->GetWrappedTypeName();
|
||||
}
|
||||
return pybind11::cast<AZStd::string>(pybind11::str(pyObj.get_type()));
|
||||
}
|
||||
}
|
||||
|
||||
namespace Call
|
||||
{
|
||||
constexpr size_t MaxBehaviorMethodArguments = 32;
|
||||
using BehaviorMethodArgumentArray = AZStd::array<AZ::BehaviorValueParameter, MaxBehaviorMethodArguments>;
|
||||
|
||||
pybind11::object InvokeBehaviorMethodWithResult(AZ::BehaviorMethod* behaviorMethod, pybind11::args pythonInputArgs, AZ::BehaviorObject self, AZ::BehaviorValueParameter& result)
|
||||
{
|
||||
if (behaviorMethod->GetNumArguments() > MaxBehaviorMethodArguments || pythonInputArgs.size() > MaxBehaviorMethodArguments)
|
||||
{
|
||||
AZ_Error("python", false, "Too many arguments for class method; set:%zu max:%zu", behaviorMethod->GetMinNumberOfArguments(), MaxBehaviorMethodArguments);
|
||||
return pybind11::cast<pybind11::none>(Py_None);
|
||||
}
|
||||
|
||||
Convert::StackVariableAllocator stackVariableAllocator;
|
||||
BehaviorMethodArgumentArray parameters;
|
||||
int parameterCount = 0;
|
||||
if (self.IsValid())
|
||||
{
|
||||
// record the "this" pointer's metadata like its RTTI so that it can be
|
||||
// down casted to a parent class type if needed to invoke a parent method
|
||||
AZ::BehaviorValueParameter theThisPointer;
|
||||
if (const AZ::BehaviorParameter* thisInfo = behaviorMethod->GetArgument(0))
|
||||
{
|
||||
// avoiding the "Special handling for the generic object holder." since it assumes
|
||||
// the BehaviorObject.m_value is a pointer; the reference version is already dereferenced
|
||||
if ((thisInfo->m_traits & AZ::BehaviorParameter::TR_POINTER) == AZ::BehaviorParameter::TR_POINTER)
|
||||
{
|
||||
theThisPointer.m_value = &self.m_address;
|
||||
}
|
||||
else
|
||||
{
|
||||
theThisPointer.m_value = self.m_address;
|
||||
}
|
||||
theThisPointer.Set(*thisInfo);
|
||||
parameters[0].Set(theThisPointer);
|
||||
++parameterCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Warning("python", false, "Missing self info index 0 in class method %s", behaviorMethod->m_name.c_str());
|
||||
return pybind11::cast<pybind11::none>(Py_None);
|
||||
}
|
||||
}
|
||||
|
||||
// prepare the parameters for the BehaviorMethod
|
||||
for (auto pythonArg : pythonInputArgs)
|
||||
{
|
||||
if (parameterCount < behaviorMethod->GetNumArguments())
|
||||
{
|
||||
auto currentPythonArg = pybind11::cast<pybind11::object>(pythonArg);
|
||||
const AZ::BehaviorParameter* behaviorArgument = behaviorMethod->GetArgument(parameterCount);
|
||||
if (!behaviorArgument)
|
||||
{
|
||||
AZ_Warning("python", false, "Missing argument at index %d in class method %s", parameterCount, behaviorMethod->m_name.c_str());
|
||||
return pybind11::cast<pybind11::none>(Py_None);
|
||||
}
|
||||
if (!Convert::PythonToBehaviorValueParameter(*behaviorArgument, currentPythonArg, parameters[parameterCount], stackVariableAllocator))
|
||||
{
|
||||
AZ_Warning("python", false, "BehaviorMethod %s: Parameter at [%d] index expects (%s:%s) for method but got type (%s)",
|
||||
behaviorMethod->m_name.c_str(), parameterCount,
|
||||
behaviorArgument->m_name, behaviorArgument->m_typeId.ToString<AZStd::string>().c_str(),
|
||||
Convert::GetPythonTypeName(currentPythonArg).c_str());
|
||||
return pybind11::cast<pybind11::none>(Py_None);
|
||||
}
|
||||
++parameterCount;
|
||||
}
|
||||
}
|
||||
|
||||
// did the Python script send the right amount of arguments?
|
||||
const auto totalPythonArgs = pythonInputArgs.size() + (self.IsValid() ? 1 : 0); // +1 for the 'this' coming in from a marshaled Python/BehaviorObject
|
||||
if (totalPythonArgs < behaviorMethod->GetMinNumberOfArguments())
|
||||
{
|
||||
AZ_Warning("python", false, "Method %s requires at least %zu parameters got %zu", behaviorMethod->m_name.c_str(), behaviorMethod->GetMinNumberOfArguments(), totalPythonArgs);
|
||||
return pybind11::cast<pybind11::none>(Py_None);
|
||||
}
|
||||
else if (totalPythonArgs > behaviorMethod->GetNumArguments())
|
||||
{
|
||||
AZ_Warning("python", false, "Method %s requires %zu parameters but it got more (%zu) - excess parameters will not be used.", behaviorMethod->m_name.c_str(), behaviorMethod->GetMinNumberOfArguments(), totalPythonArgs);
|
||||
}
|
||||
|
||||
if (behaviorMethod->HasResult())
|
||||
{
|
||||
if (Internal::AllocateBehaviorValueParameter(behaviorMethod, result, stackVariableAllocator))
|
||||
{
|
||||
if (behaviorMethod->Call(parameters.begin(), static_cast<unsigned int>(totalPythonArgs), &result))
|
||||
{
|
||||
result.m_azRtti = behaviorMethod->GetResult()->m_azRtti;
|
||||
result.m_typeId = behaviorMethod->GetResult()->m_typeId;
|
||||
result.m_traits = behaviorMethod->GetResult()->m_traits;
|
||||
return Convert::BehaviorValueParameterToPython(result, stackVariableAllocator);
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Warning("python", false, "Failed to call class method %s", behaviorMethod->m_name.c_str());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Warning("python", false, "Failed to allocate return value for method %s", behaviorMethod->m_name.c_str());
|
||||
}
|
||||
}
|
||||
else if (!behaviorMethod->Call(parameters.begin(), static_cast<unsigned int>(totalPythonArgs)))
|
||||
{
|
||||
AZ_Warning("python", false, "Failed to invoke class method %s", behaviorMethod->m_name.c_str());
|
||||
}
|
||||
return pybind11::cast<pybind11::none>(Py_None);
|
||||
}
|
||||
|
||||
pybind11::object InvokeBehaviorMethod(AZ::BehaviorMethod* behaviorMethod, pybind11::args pythonInputArgs, AZ::BehaviorObject self)
|
||||
{
|
||||
AZ::BehaviorValueParameter result;
|
||||
result.m_value = nullptr;
|
||||
pybind11::object pythonOutput = InvokeBehaviorMethodWithResult(behaviorMethod, pythonInputArgs, self, result);
|
||||
if (result.m_value)
|
||||
{
|
||||
Internal::DeallocateBehaviorValueParameter(result);
|
||||
}
|
||||
|
||||
return pythonOutput;
|
||||
}
|
||||
|
||||
pybind11::object StaticMethod(AZ::BehaviorMethod* behaviorMethod, pybind11::args pythonInputArgs)
|
||||
{
|
||||
return InvokeBehaviorMethod(behaviorMethod, pythonInputArgs, {});
|
||||
}
|
||||
|
||||
pybind11::object ClassMethod(AZ::BehaviorMethod* behaviorMethod, AZ::BehaviorObject self, pybind11::args pythonInputArgs)
|
||||
{
|
||||
if (behaviorMethod->GetNumArguments() == 0)
|
||||
{
|
||||
AZ_Error("python", false, "A member level function should require at least one argument");
|
||||
}
|
||||
else if (!self.IsValid())
|
||||
{
|
||||
AZ_Error("python", false, "Method %s requires at valid self object to invoke", behaviorMethod->m_name.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
return InvokeBehaviorMethod(behaviorMethod, pythonInputArgs, self);
|
||||
}
|
||||
return pybind11::cast<pybind11::none>(Py_None);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <Source/PythonCommon.h>
|
||||
#include <pybind11/pybind11.h>
|
||||
#include <AzCore/RTTI/BehaviorContext.h>
|
||||
#include <AzCore/RTTI/AttributeReader.h>
|
||||
#include <AzCore/std/string/string_view.h>
|
||||
#include <AzCore/std/optional.h>
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
struct BehaviorParameter;
|
||||
struct BehaviorValueParameter;
|
||||
class BehaviorMethod;
|
||||
}
|
||||
|
||||
namespace EditorPythonBindings
|
||||
{
|
||||
namespace Scope
|
||||
{
|
||||
inline bool IsBehaviorFlaggedForEditor(const AZ::AttributeArray& attributes)
|
||||
{
|
||||
// defaults to Launcher
|
||||
AZ::Script::Attributes::ScopeFlags scopeType = AZ::Script::Attributes::ScopeFlags::Launcher;
|
||||
AZ::Attribute* scopeAttribute = AZ::FindAttribute(AZ::Script::Attributes::Scope, attributes);
|
||||
if (scopeAttribute)
|
||||
{
|
||||
AZ::AttributeReader scopeAttributeReader(nullptr, scopeAttribute);
|
||||
scopeAttributeReader.Read<AZ::Script::Attributes::ScopeFlags>(scopeType);
|
||||
}
|
||||
return (scopeType == AZ::Script::Attributes::ScopeFlags::Automation || scopeType == AZ::Script::Attributes::ScopeFlags::Common);
|
||||
}
|
||||
|
||||
inline void FetchScriptName(const AZ::AttributeArray& attributes, AZStd::string& baseName)
|
||||
{
|
||||
AZ::Attribute* scriptNameAttribute = AZ::FindAttribute(AZ::Script::Attributes::Alias, attributes);
|
||||
if (scriptNameAttribute)
|
||||
{
|
||||
AZ::AttributeReader scopeAttributeReader(nullptr, scriptNameAttribute);
|
||||
scopeAttributeReader.Read<AZStd::string>(baseName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace Module
|
||||
{
|
||||
using PackageMapType = AZStd::unordered_map<AZStd::string, pybind11::module>;
|
||||
|
||||
//! Finds or creates a sub-module to add a base parent module; create all the sub-modules as well
|
||||
//! @param modulePackageMap keeps track of the known modules
|
||||
//! @param moduleName can be a dot separated string such as "mygen.mypackage.mymodule"
|
||||
//! @param parentModule the module to add new sub-modules
|
||||
//! @param fallbackModule the module to add new sub-modules
|
||||
//! @param alertUsingFallback issue a warning if using the fallback module
|
||||
//! @return the new submodule
|
||||
pybind11::module DeterminePackageModule(PackageMapType& modulePackageMap, AZStd::string_view moduleName, pybind11::module parentModule, pybind11::module fallbackModule, bool alertUsingFallback);
|
||||
|
||||
inline AZStd::optional<AZStd::string_view> GetName(const AZ::AttributeArray& attributes)
|
||||
{
|
||||
AZ::Attribute* moduleAttribute = AZ::FindAttribute(AZ::Script::Attributes::Module, attributes);
|
||||
if (moduleAttribute)
|
||||
{
|
||||
const char* moduleName = nullptr;
|
||||
AZ::AttributeReader scopeAttributeReader(nullptr, moduleAttribute);
|
||||
scopeAttributeReader.Read<const char*>(moduleName);
|
||||
if (moduleName)
|
||||
{
|
||||
return { moduleName };
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
namespace Convert
|
||||
{
|
||||
// allocation pattern for BehaviorValueParameters being stored in the stack and needs to be cleaned at the end of a block
|
||||
using VariableDeleter = AZStd::function<void()>;
|
||||
|
||||
struct StackVariableAllocator final
|
||||
: public AZStd::static_buffer_allocator<256, 16>
|
||||
{
|
||||
public:
|
||||
~StackVariableAllocator();
|
||||
void StoreVariableDeleter(VariableDeleter&& deleter);
|
||||
|
||||
private:
|
||||
AZStd::vector<VariableDeleter> m_cleanUpItems;
|
||||
};
|
||||
|
||||
//! Converts a behavior value parameter to a Python object
|
||||
//! @param behaviorValue is a parameter that came from a result or some prepared behavior value
|
||||
//! @param stackVariableAllocator manages the allocated parameter while in scope
|
||||
//! @return a valid Python object or None if no conversion was possible
|
||||
pybind11::object BehaviorValueParameterToPython(AZ::BehaviorValueParameter& behaviorValue, Convert::StackVariableAllocator& stackVariableAllocator);
|
||||
|
||||
//! Converts Python object to a behavior value parameter using an existing behaviorArgument from a Behavior Method
|
||||
//! @param behaviorArgument the stored argument slot from a Behavior Method to match with the pyObj to covert in the parameter
|
||||
//! @param parameter is the output of the conversion from Python to a Behavior value
|
||||
//! @param stackVariableAllocator manages the allocated parameter while in scope
|
||||
//! @return true if the conversion happened
|
||||
bool PythonToBehaviorValueParameter(const AZ::BehaviorParameter& behaviorArgument, pybind11::object pyObj, AZ::BehaviorValueParameter& parameter, Convert::StackVariableAllocator& stackVariableAllocator);
|
||||
|
||||
//! Converts Python object to a PythonProxyObject, if possible
|
||||
//! @param behaviorArgument A stored PythonProxyObject in Python, returns FALSE if the Python object does not point to a PythonProxyObject
|
||||
//! @param parameter is the output of the conversion from Python to a Behavior value
|
||||
//! @return true if the conversion happened
|
||||
bool PythonProxyObjectToBehaviorValueParameter(const AZ::BehaviorParameter& behaviorArgument, pybind11::object pyObj, AZ::BehaviorValueParameter& parameter);
|
||||
|
||||
//! Gets a readable type name for the Python object; this will unwrap a PythonProxyObject to find its underlying type name
|
||||
//! @param pyObj any valid Python object value
|
||||
//! @return text form of the Python object value type
|
||||
AZStd::string GetPythonTypeName(pybind11::object pyObj);
|
||||
}
|
||||
|
||||
namespace Call
|
||||
{
|
||||
//! Calls a BehaviorMethod with a tuple of arguments for non-member functions
|
||||
pybind11::object StaticMethod(AZ::BehaviorMethod* behaviorMethod, pybind11::args args);
|
||||
|
||||
//! Calls a BehaviorMethod with a tuple of arguments for member class level functions
|
||||
pybind11::object ClassMethod(AZ::BehaviorMethod* behaviorMethod, AZ::BehaviorObject self, pybind11::args args);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user