/* * 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 #include #include namespace ScriptCanvas { void* BehaviorContextUtils::ConstructTuple(const AZ::TypeId& typeID) { return ConstructTupleGetContext(typeID).first; } AZStd::pair BehaviorContextUtils::ConstructTupleGetContext(const AZ::TypeId& typeID) { using namespace AZ::ScriptCanvasAttributes; AZStd::pair 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(holder); return { AZStd::invoke(holder.m_function), classAndContext.second }; } } return { nullptr, nullptr }; } AZStd::vector 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(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(*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(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(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 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::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* const unsortedClasses) { if (!unsortedClasses) { return; } const AZStd::map 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* const unsortedEBuses) { if (!unsortedEBuses) { return; } const AZStd::map 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 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* const unsortedMethods) { if (!unsortedMethods) { return; } const AZStd::map 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* const behaviorProperties) { if (!behaviorProperties) { return; } const AZStd::map 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; } }