Files
o3de/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Utils/BehaviorContextUtils.cpp
T

514 lines
20 KiB
C++

/*
* 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 "BehaviorContextUtils.h"
#include <AzCore/std/containers/map.h>
#include <ScriptCanvas/Core/GraphData.h>
#include <ScriptCanvas/Libraries/Core/Method.h>
namespace ScriptCanvas
{
void* BehaviorContextUtils::ConstructTuple(const AZ::TypeId& typeID)
{
return ConstructTupleGetContext(typeID).first;
}
AZStd::pair<void*, AZ::BehaviorContext*> BehaviorContextUtils::ConstructTupleGetContext(const AZ::TypeId& typeID)
{
using namespace AZ::ScriptCanvasAttributes;
AZStd::pair<const AZ::BehaviorClass*, AZ::BehaviorContext*> classAndContext = AZ::BehaviorContextHelper::GetClassAndContext(typeID);
if (const AZ::BehaviorClass* bcClass = classAndContext.first)
{
if (AZ::Attribute* attribute = AZ::FindAttribute(TupleConstructorFunction, bcClass->m_attributes))
{
TupleConstructorHolder holder;
AZ::AttributeReader attributeReader(nullptr, attribute);
attributeReader.Read<TupleConstructorHolder>(holder);
return { AZStd::invoke(holder.m_function), classAndContext.second };
}
}
return { nullptr, nullptr };
}
AZStd::vector<AZ::TypeId> BehaviorContextUtils::GetUnpackedTypes(const AZ::TypeId& typeID)
{
using namespace AZ::ScriptCanvasAttributes;
if (const AZ::BehaviorClass* bcClass = AZ::BehaviorContextHelper::GetClass(typeID))
{
if (AZ::Attribute* attribute = AZ::FindAttribute(ReturnValueTypesFunction, bcClass->m_attributes))
{
GetUnpackedReturnValueTypesHolder holder;
AZ::AttributeReader attributeReader(nullptr, attribute);
attributeReader.Read<GetUnpackedReturnValueTypesHolder>(holder);
return AZStd::invoke(holder.m_function);
}
}
return { typeID };
}
bool BehaviorContextUtils::FindClass(const AZ::BehaviorMethod*& outMethod, const AZ::BehaviorClass*& outClass, [[maybe_unused]] AZStd::string_view className, [[maybe_unused]] AZStd::string_view methodName, [[maybe_unused]] AZStd::string* outPrettyClassName, [[maybe_unused]] bool warnOnMissing)
{
AZ::BehaviorContext* behaviorContext(nullptr);
AZ::ComponentApplicationBus::BroadcastResult(behaviorContext, &AZ::ComponentApplicationRequests::GetBehaviorContext);
if (!behaviorContext)
{
AZ_Warning("Script Canvas", false, "BehaviorContext is required!");
return false;
}
auto classIter(behaviorContext->m_classes.find(className));
if (classIter == behaviorContext->m_classes.end())
{
AZ_Warning("Script Canvas", !warnOnMissing, "No class by name of %s found in the BehaviorContext!", className.data());
return false;
}
const AZ::BehaviorClass* behaviorClass(classIter->second);
AZ_Assert(behaviorClass, "BehaviorContext Class entry %s has no class pointer", className.data());
const auto methodIter(behaviorClass->m_methods.find(methodName.data()));
if (methodIter == behaviorClass->m_methods.end())
{
AZ_Warning("Script Canvas", !warnOnMissing, "No method by name of %s found in BehaviorContext class %s", methodName.data(), className.data());
return false;
}
// this argument is the first argument...so perhaps remove the distinction between class and member functions, since it probably won't follow polymorphism
// if it will, keep the distinction, and add the first argument separately
AZ::BehaviorMethod* method(methodIter->second);
if (!method)
{
AZ_Warning("Script Canvas", !warnOnMissing, "BehaviorContext Method entry %s has no method pointer", methodName.data());
return false;
}
if (outPrettyClassName)
{
*outPrettyClassName = className;
if (AZ::Attribute* prettyNameAttribute = AZ::FindAttribute(AZ::ScriptCanvasAttributes::PrettyName, behaviorClass->m_attributes))
{
AZ::AttributeReader(nullptr, prettyNameAttribute).Read<AZStd::string>(*outPrettyClassName, *behaviorContext);
}
}
outClass = behaviorClass;
outMethod = method;
return true;
}
bool BehaviorContextUtils::FindEBus(const AZ::BehaviorEBus*& outEBus, [[maybe_unused]] AZStd::string_view ebusName, [[maybe_unused]] bool warnOnMissing)
{
AZ::BehaviorContext* behaviorContext = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(behaviorContext, &AZ::ComponentApplicationRequests::GetBehaviorContext);
if (!behaviorContext)
{
AZ_Warning("Script Canvas", false, "BehaviorContext is required!");
return false;
}
const auto& ebusIterator = behaviorContext->m_ebuses.find(ebusName);
if (ebusIterator == behaviorContext->m_ebuses.end())
{
AZ_Warning("Script Canvas", !warnOnMissing, "No ebus by name of %s found in the BehaviorContext!", ebusName.data());
return false;
}
outEBus = ebusIterator->second;
AZ_Assert(outEBus, "ebus == nullptr in %s", ebusName.data());
return true;
}
bool BehaviorContextUtils::FindExplicitOverload(const AZ::BehaviorMethod*& outMethod, const AZ::BehaviorClass*& outClass, AZStd::string_view /*className*/, AZStd::string_view methodName, AZStd::string* /*outPrettyClassName*/)
{
AZ::BehaviorContext* behaviorContext = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(behaviorContext, &AZ::ComponentApplicationRequests::GetBehaviorContext);
AZ_Assert(behaviorContext, "Behavior Context is required");
if (behaviorContext)
{
AZ::ExplicitOverloadInfo explicitOverloadInfo;
explicitOverloadInfo.m_name = methodName;
auto iter = behaviorContext->m_explicitOverloads.find(explicitOverloadInfo);
if (iter != behaviorContext->m_explicitOverloads.end())
{
outMethod = iter->m_overloads[0].first;
outClass = iter->m_overloads[0].second;
return true;
}
}
return false;
}
AZStd::string BehaviorContextUtils::FindExposedMethodName(const AZ::BehaviorMethod& method, const AZ::BehaviorClass* behaviorClass)
{
if (behaviorClass)
{
for (auto candidate : behaviorClass->m_methods)
{
if (&method == candidate.second)
{
return candidate.first;
}
for (auto overload = candidate.second->m_overload; overload != nullptr; overload = overload->m_overload)
{
if (&method == overload)
{
return candidate.first;
}
}
}
}
return method.m_name;
}
bool BehaviorContextUtils::FindEvent(const AZ::BehaviorMethod*& outMethod, AZStd::string_view ebusName, AZStd::string_view eventName, EventType* outEventType, [[maybe_unused]] bool warnOnMissing)
{
const AZ::BehaviorEBus* ebus{};
if (!FindEBus(ebus, ebusName, warnOnMissing))
{
return false;
}
return FindEvent(outMethod, ebus, eventName, outEventType, warnOnMissing);
}
bool BehaviorContextUtils::FindEvent(const AZ::BehaviorMethod*& outMethod, const AZ::BehaviorEBus* const ebus, AZStd::string_view eventName, EventType* outEventType, [[maybe_unused]] bool warnOnMissing)
{
if (!ebus)
{
AZ_Warning("Script Canvas", !warnOnMissing, "event by name of %s found has no ebus to search in", eventName.data());
return false;
}
const auto& sender = ebus->m_events.find(eventName);
if (sender == ebus->m_events.end())
{
AZ_Warning("Script Canvas", !warnOnMissing, "No event by name of %s found in the ebus %s", eventName.data(), ebus->m_name.c_str());
return false;
}
AZ::BehaviorMethod* method = GetEventMethod(*ebus, sender->second);
if (!method)
{
AZ_Warning("Script Canvas", !warnOnMissing, "Queue function mismatch in %s-%s", eventName.data(), ebus->m_name.c_str());
return false;
}
if (outEventType)
{
*outEventType = GetEventType(*ebus);
}
outMethod = method;
return true;
}
bool BehaviorContextUtils::FindFree(const AZ::BehaviorMethod*& outMethod, AZStd::string_view methodName, [[maybe_unused]] bool warnOnMissing)
{
AZ::BehaviorContext* behaviorContext(nullptr);
AZ::ComponentApplicationBus::BroadcastResult(behaviorContext, &AZ::ComponentApplicationRequests::GetBehaviorContext);
if (!behaviorContext)
{
AZ_Warning("Script Canvas", false, "BehaviorContext is required!");
return false;
}
AZ::BehaviorMethod* method{};
const auto methodIter(behaviorContext->m_methods.find(methodName));
if (methodIter != behaviorContext->m_methods.end())
{
method = methodIter->second;
if (!method)
{
AZ_Warning("Script Canvas", !warnOnMissing, "BehaviorContext Method entry %.*s has no method pointer",
aznumeric_cast<int>(methodName.size()), methodName.data());
return false;
}
}
else
{
// The method is not in the behaviorContext Global Methods, so check the Global Properties
for (auto [propertyName, behaviorProperty] : behaviorContext->m_properties)
{
AZStd::string getterName = AZStd::string::format("%s::Getter", methodName.data());
AZStd::string setterName = AZStd::string::format("%s::Setter", methodName.data());
if (behaviorProperty->m_getter && (behaviorProperty->m_getter->m_name == methodName || behaviorProperty->m_getter->m_name == getterName))
{
method = behaviorProperty->m_getter;
break;
}
if (behaviorProperty->m_setter && (behaviorProperty->m_setter->m_name == methodName || behaviorProperty->m_setter->m_name == setterName))
{
method = behaviorProperty->m_setter;
break;
}
}
if (!method)
{
AZ_Warning("Script Canvas", !warnOnMissing, "No method by name of %.*s found in the BehaviorContext!",
aznumeric_cast<int>(methodName.size()), methodName.data());
return false;
}
}
outMethod = method;
return true;
}
size_t BehaviorContextUtils::GenerateFingerprintForBehaviorContext()
{
size_t fingerprint = 0;
AZ::BehaviorContext* behaviorContext(nullptr);
AZ::ComponentApplicationBus::BroadcastResult(behaviorContext, &AZ::ComponentApplicationRequests::GetBehaviorContext);
if (!behaviorContext)
{
AZ_Warning("Script Canvas", false, "BehaviorContext is required!");
return fingerprint;
}
if (behaviorContext)
{
HashCombineMethods(fingerprint, &(behaviorContext->m_methods));
HashCombineProperties(fingerprint, &(behaviorContext->m_properties));
HashCombineClasses(fingerprint, &(behaviorContext->m_classes));
HashCombineEBuses(fingerprint, &(behaviorContext->m_ebuses));
}
return fingerprint;
}
size_t BehaviorContextUtils::GenerateFingerprintForMethod(const MethodType& methodType, const AZStd::string& className, const AZStd::string& methodName)
{
size_t hash = 0;
const AZ::BehaviorMethod* dependentMethod;
switch (methodType)
{
case MethodType::Event:
if (methodName.empty())
{
const AZ::BehaviorEBus* dependentEBus;
if (ScriptCanvas::BehaviorContextUtils::FindEBus(dependentEBus, className))
{
ScriptCanvas::BehaviorContextUtils::HashCombineEvents(hash, dependentEBus);
}
}
else
{
if (ScriptCanvas::BehaviorContextUtils::FindEvent(dependentMethod, className, methodName))
{
ScriptCanvas::BehaviorContextUtils::HashCombineMethodSignature(hash, dependentMethod);
}
}
break;
case MethodType::Free:
if (ScriptCanvas::BehaviorContextUtils::FindFree(dependentMethod, methodName))
{
ScriptCanvas::BehaviorContextUtils::HashCombineMethodSignature(hash, dependentMethod);
}
break;
case MethodType::Member:
const AZ::BehaviorClass* dependentClass;
if (ScriptCanvas::BehaviorContextUtils::FindClass(dependentMethod, dependentClass, className, methodName))
{
ScriptCanvas::BehaviorContextUtils::HashCombineMethodSignature(hash, dependentMethod);
}
break;
}
return hash;
}
AZStd::pair<const AZ::BehaviorMethod*, const AZ::BehaviorClass*> BehaviorContextUtils::GetCheck(const AZ::BehaviorMethod& method)
{
AZ::BehaviorContext* behaviorContext(nullptr);
AZ::ComponentApplicationBus::BroadcastResult(behaviorContext, &AZ::ComponentApplicationRequests::GetBehaviorContext);
if (!behaviorContext)
{
AZ_Warning("Script Canvas", false, "BehaviorContext is required!");
return {};
}
auto iter = behaviorContext->m_checksByOperations.find(&method);
if (iter != behaviorContext->m_checksByOperations.end())
{
return iter->second;
}
else
{
return {};
}
}
AZ::EBusAddressPolicy BehaviorContextUtils::GetEBusAddressPolicy(const AZ::BehaviorEBus& ebus)
{
return GetEBusAddressPolicyByUuid(ebus.m_idParam.m_typeId);
}
AZ::EBusAddressPolicy BehaviorContextUtils::GetEBusAddressPolicyByUuid(AZ::Uuid addressTypeUuid)
{
return (addressTypeUuid.IsNull() || addressTypeUuid == AZ::AzTypeInfo<void>::Uuid())
? AZ::EBusAddressPolicy::Single : AZ::EBusAddressPolicy::ById;
}
AZ::BehaviorMethod* BehaviorContextUtils::GetEventMethod(const AZ::BehaviorEBus& ebus, const AZ::BehaviorEBusEventSender& ebusEventSender)
{
const auto addressPolicy = GetEBusAddressPolicy(ebus);
return ebus.m_queueFunction
? (addressPolicy == AZ::EBusAddressPolicy::ById ? ebusEventSender.m_queueEvent : ebusEventSender.m_queueBroadcast)
: (addressPolicy == AZ::EBusAddressPolicy::ById ? ebusEventSender.m_event : ebusEventSender.m_broadcast);
}
EventType BehaviorContextUtils::GetEventType(const AZ::BehaviorEBus& ebus)
{
const auto addressPolicy = GetEBusAddressPolicy(ebus);
return ebus.m_queueFunction
? (addressPolicy == AZ::EBusAddressPolicy::ById ? EventType::EventQueue : EventType::BroadcastQueue)
: (addressPolicy == AZ::EBusAddressPolicy::ById ? EventType::Event : EventType::Broadcast);
}
void BehaviorContextUtils::HashCombineClasses(size_t& outHash, const AZStd::unordered_map<AZStd::string, AZ::BehaviorClass*>* const unsortedClasses)
{
if (!unsortedClasses)
{
return;
}
const AZStd::map<AZStd::string, AZ::BehaviorClass*> sortedClasses(unsortedClasses->begin(), unsortedClasses->end());
for (auto classIter = sortedClasses.begin(); classIter != sortedClasses.end(); ++classIter)
{
if (!classIter->second)
{
continue;
}
AZStd::hash_combine(outHash, classIter->second->m_name);
AZStd::hash_combine(outHash, classIter->second->m_typeId);
HashCombineProperties(outHash, &(classIter->second->m_properties));
HashCombineMethods(outHash, &(classIter->second->m_methods));
}
}
void BehaviorContextUtils::HashCombineEBuses(size_t& outHash, const AZStd::unordered_map<AZStd::string, AZ::BehaviorEBus*>* const unsortedEBuses)
{
if (!unsortedEBuses)
{
return;
}
const AZStd::map<AZStd::string, AZ::BehaviorEBus*> sortedEbus(unsortedEBuses->begin(), unsortedEBuses->end());
for (auto ebusIter = sortedEbus.begin(); ebusIter != sortedEbus.end(); ++ebusIter)
{
if (!ebusIter->second)
{
continue;
}
AZStd::hash_combine(outHash, ebusIter->second->m_name);
HashCombineEvents(outHash, ebusIter->second);
}
}
void BehaviorContextUtils::HashCombineEvents(size_t& outHash, const AZ::BehaviorEBus* const ebus)
{
if (!ebus)
{
return;
}
const AZStd::map<AZStd::string, AZ::BehaviorEBusEventSender> sortedEvents(ebus->m_events.begin(), ebus->m_events.end());
for (auto eventIter = sortedEvents.begin(); eventIter != sortedEvents.end(); ++eventIter)
{
AZ::BehaviorMethod* method = GetEventMethod(*ebus, eventIter->second);
AZStd::hash_combine(outHash, eventIter->first);
HashCombineMethodSignature(outHash, method);
}
}
void BehaviorContextUtils::HashCombineMethods(size_t& outHash, const AZStd::unordered_map<AZStd::string, AZ::BehaviorMethod*>* const unsortedMethods)
{
if (!unsortedMethods)
{
return;
}
const AZStd::map<AZStd::string, AZ::BehaviorMethod*> sortedMethods(unsortedMethods->begin(), unsortedMethods->end());
for (auto methodIter = sortedMethods.begin(); methodIter != sortedMethods.end(); ++methodIter)
{
HashCombineMethodSignature(outHash, methodIter->second);
}
}
void BehaviorContextUtils::HashCombineMethodSignature(size_t& outHash, const AZ::BehaviorMethod* const behaviorMethod)
{
if (!behaviorMethod)
{
// Soft handle this case:
// 1. This case should fail much earlier when BC gets initialized
// 2. Hash value won't be affected even if this case is consistent
return;
}
// Hash return type
if (behaviorMethod->HasResult())
{
AZStd::hash_combine(outHash, behaviorMethod->GetResult()->m_typeId);
}
// Hash method name
AZStd::hash_combine(outHash, behaviorMethod->m_name);
// Hash arguments type
for (size_t i = 0; i < behaviorMethod->GetNumArguments(); ++i)
{
AZStd::hash_combine(outHash, behaviorMethod->GetArgument(i)->m_typeId);
}
}
void BehaviorContextUtils::HashCombineProperties(size_t& outHash, const AZStd::unordered_map<AZStd::string, AZ::BehaviorProperty*>* const behaviorProperties)
{
if (!behaviorProperties)
{
return;
}
const AZStd::map<AZStd::string, AZ::BehaviorProperty*> sortedProperties(behaviorProperties->begin(), behaviorProperties->end());
for (auto propertyIter = sortedProperties.begin(); propertyIter != sortedProperties.end(); ++propertyIter)
{
if (!propertyIter->second)
{
continue;
}
AZStd::hash_combine(outHash, propertyIter->second->m_name);
HashCombineMethodSignature(outHash, propertyIter->second->m_getter);
HashCombineMethodSignature(outHash, propertyIter->second->m_setter);
}
}
bool BehaviorContextUtils::IsSameDataType(const AZ::BehaviorParameter* const parameter, Data::Type dataType)
{
return parameter && Data::GetBehaviorParameterDataType(*parameter) == dataType;
}
}