/* * Copyright (c) Contributors to the Open 3D Engine Project. * For complete copyright and license terms please see the LICENSE at the root of this distribution. * * SPDX-License-Identifier: Apache-2.0 OR MIT * */ #pragma once #include #include #include #include #include namespace ScriptCanvas { namespace Data { using GetterFunction = AZStd::function(const Datum&)>; struct GetterWrapper { AZ_CLASS_ALLOCATOR(GetterWrapper, AZ::SystemAllocator, 0); GetterFunction m_getterFunction; Data::Type m_propertyType; AZStd::string m_propertyName; AZStd::string m_displayName; }; using GetterContainer = AZStd::unordered_map; using SetterFunction = AZStd::function(Datum&, const Datum&)>; struct SetterWrapper { AZ_CLASS_ALLOCATOR(SetterWrapper, AZ::SystemAllocator, 0); SetterFunction m_setterFunction; Data::Type m_propertyType; AZStd::string m_propertyName; AZStd::string m_displayName; }; using SetterContainer = AZStd::unordered_map; struct PropertyMetadata { AZ_TYPE_INFO(PropertyMetadata, "{A4910EF1-0139-4A7A-878C-E60E18F3993A}"); AZ_CLASS_ALLOCATOR(PropertyMetadata, AZ::SystemAllocator, 0); static void Reflect(AZ::ReflectContext* reflectContext); SlotId m_propertySlotId; Data::Type m_propertyType; AZStd::string m_propertyName; Data::GetterFunction m_getterFunction; }; AZStd::vector GetBehaviorProperties(const Data::Type& type); GetterContainer ExplodeToGetters(const Data::Type& type); SetterContainer ExplodeToSetters(const Data::Type& type); AZ_INLINE AZStd::vector GetBehaviorProperties(const Data::Type& type) { AZ_Error("Script Canvas", type.GetType() == Data::eType::BehaviorContextObject, "GetBehaviorProperties cannot return properties for non-BehaviorContextObjexts"); AZStd::vector propertiesByType; const AZ::BehaviorClass* behaviorClass = AZ::BehaviorContextHelper::GetClass(type.GetAZType()); if (behaviorClass) { for (auto&& namePropertyPair : behaviorClass->m_properties) { propertiesByType.push_back(namePropertyPair.second); } } return propertiesByType; } namespace Internal { template struct WrapGetterHelper { static_assert(!AZStd::is_void::value, "Getter function must be either a member pointer that accepts 0 arguments or an invokable object that accepts 1 argument"); using PropertyType = AZStd::decay_t>; static_assert(!AZStd::is_void::value, "Getter function must return a non-void type"); static GetterWrapper Callback(AZStd::string_view propertyName, const FunctionType& propertyGetter, AZStd::string_view displayName) { GetterFunction getterWrapper = [propertyGetter](const Datum& thisDatum) -> AZ::Outcome { auto thisObject = thisDatum.GetAs(); if (!thisObject) { return AZ::Failure(AZStd::string::format("Unable to invoke getter. The getter parameter is nullptr")); } return AZ::Success(Datum(AZStd::invoke(propertyGetter, thisObject))); }; return { getterWrapper, Data::FromAZType(), propertyName, displayName }; } }; template struct WrapSetterHelper { static_assert(!AZStd::is_void::value, "Setter function must be either a member function pointer that accepts 1 arguments or an invokable object that accepts 2 argument"); static_assert(!AZStd::is_void::value, "Property being set must be a non-void type"); static SetterWrapper Callback(AZStd::string_view propertyName, const FunctionType& propertySetter, AZStd::string_view displayName) { SetterFunction setterWrapper = [propertySetter](Datum& thisDatum, const Datum& propertyDatum) -> AZ::Outcome { auto thisObject = thisDatum.ModAs(); auto propertyObject = propertyDatum.GetAs>(); if (!thisObject) { return AZ::Failure(AZStd::string::format("Unable to invoke setter. The setter parameter is nullptr")); } if (!propertyObject) { return AZ::Failure(AZStd::string::format("Unable to invoke setter. The property value parameter is nullptr")); } AZStd::invoke(propertySetter, thisObject, *propertyObject); return AZ::Success(); }; return { setterWrapper, Data::FromAZType(), propertyName, displayName }; } }; } template struct WrapGetter; template struct WrapGetter::value>> : Internal::WrapGetterHelper::class_type> {}; template struct WrapGetter::value && AZStd::function_traits::arity == 1>> : Internal::WrapGetterHelper>> {}; template struct WrapSetter; template struct WrapSetter::value && AZStd::function_traits::arity == 1>> : Internal::WrapSetterHelper::class_type, AZStd::function_traits_get_arg_t> {}; template struct WrapSetter::value && AZStd::function_traits::arity == 2>> : Internal::WrapSetterHelper, AZStd::function_traits_get_arg_t> {}; template struct PropertyTraits { static GetterContainer GetGetterWrappers([[maybe_unused]] const Data::Type& type) { return GetterContainer{}; } static SetterContainer GetSetterWrappers([[maybe_unused]] const Data::Type& type) { return SetterContainer{}; } }; template<> struct PropertyTraits { static GetterContainer GetGetterWrappers(const Data::Type&) { GetterContainer getterFunctions; getterFunctions.emplace("x", WrapGetter::Callback("x", &Data::QuaternionType::GetX, "X")); getterFunctions.emplace("y", WrapGetter::Callback("y", &Data::QuaternionType::GetY, "Y")); getterFunctions.emplace("z", WrapGetter::Callback("z", &Data::QuaternionType::GetZ, "Z")); getterFunctions.emplace("w", WrapGetter::Callback("w", &Data::QuaternionType::GetW, "W")); return getterFunctions; } static SetterContainer GetSetterWrappers(const Data::Type&) { SetterContainer setterFunctions; setterFunctions.emplace("x", WrapSetter::Callback("x", &Data::QuaternionType::SetX, "X")); setterFunctions.emplace("y", WrapSetter::Callback("y", &Data::QuaternionType::SetY, "Y")); setterFunctions.emplace("z", WrapSetter::Callback("z", &Data::QuaternionType::SetZ, "Z")); setterFunctions.emplace("w", WrapSetter::Callback("w", &Data::QuaternionType::SetW, "W")); return setterFunctions; } }; template<> struct PropertyTraits { static GetterContainer GetGetterWrappers(const Data::Type&) { GetterContainer getterFunctions; getterFunctions.emplace("x", WrapGetter::Callback("x", &Data::Vector2Type::GetX, "X")); getterFunctions.emplace("y", WrapGetter::Callback("y", &Data::Vector2Type::GetY, "Y")); return getterFunctions; } static SetterContainer GetSetterWrappers(const Data::Type&) { SetterContainer setterFunctions; setterFunctions.emplace("x", WrapSetter::Callback("x", &Data::Vector2Type::SetX, "X")); setterFunctions.emplace("y", WrapSetter::Callback("y", &Data::Vector2Type::SetY, "Y")); return setterFunctions; } }; template<> struct PropertyTraits { static GetterContainer GetGetterWrappers(const Data::Type&) { GetterContainer getterFunctions; getterFunctions.emplace("x", WrapGetter::Callback("x", &Data::Vector3Type::GetX, "X")); getterFunctions.emplace("y", WrapGetter::Callback("y", &Data::Vector3Type::GetY, "Y")); getterFunctions.emplace("z", WrapGetter::Callback("z", &Data::Vector3Type::GetZ, "Z")); return getterFunctions; } static SetterContainer GetSetterWrappers(const Data::Type&) { SetterContainer setterFunctions; setterFunctions.emplace("x", WrapSetter::Callback("x", &Data::Vector3Type::SetX, "X")); setterFunctions.emplace("y", WrapSetter::Callback("y", &Data::Vector3Type::SetY, "Y")); setterFunctions.emplace("z", WrapSetter::Callback("z", &Data::Vector3Type::SetZ, "Z")); return setterFunctions; } }; template<> struct PropertyTraits { static GetterContainer GetGetterWrappers(const Data::Type&) { GetterContainer getterFunctions; getterFunctions.emplace("x", WrapGetter::Callback("x", &Data::Vector4Type::GetX, "X")); getterFunctions.emplace("y", WrapGetter::Callback("y", &Data::Vector4Type::GetY, "Y")); getterFunctions.emplace("z", WrapGetter::Callback("z", &Data::Vector4Type::GetZ, "Z")); getterFunctions.emplace("w", WrapGetter::Callback("w", &Data::Vector4Type::GetW, "W")); return getterFunctions; } static SetterContainer GetSetterWrappers(const Data::Type&) { SetterContainer setterFunctions; setterFunctions.emplace("x", WrapSetter::Callback("x", &Data::Vector4Type::SetX, "X")); setterFunctions.emplace("y", WrapSetter::Callback("y", &Data::Vector4Type::SetY, "Y")); setterFunctions.emplace("z", WrapSetter::Callback("z", &Data::Vector4Type::SetZ, "Z")); setterFunctions.emplace("w", WrapSetter::Callback("w", &Data::Vector4Type::SetW, "W")); return setterFunctions; } }; template<> struct PropertyTraits { static GetterContainer GetGetterWrappers(const Data::Type&) { GetterContainer getterFunctions; getterFunctions.emplace("r", WrapGetter::Callback("r", &Data::ColorType::GetR, "Red")); getterFunctions.emplace("g", WrapGetter::Callback("g", &Data::ColorType::GetG, "Green")); getterFunctions.emplace("b", WrapGetter::Callback("b", &Data::ColorType::GetB, "Blue")); getterFunctions.emplace("a", WrapGetter::Callback("a", &Data::ColorType::GetA, "Alpha")); return getterFunctions; } static SetterContainer GetSetterWrappers(const Data::Type&) { SetterContainer setterFunctions; setterFunctions.emplace("r", WrapSetter::Callback("r", &Data::ColorType::SetR, "Red")); setterFunctions.emplace("g", WrapSetter::Callback("g", &Data::ColorType::SetG, "Green")); setterFunctions.emplace("b", WrapSetter::Callback("b", &Data::ColorType::SetB, "Blue")); setterFunctions.emplace("a", WrapSetter::Callback("a", &Data::ColorType::SetA, "Alpha")); return setterFunctions; } }; template<> struct PropertyTraits { static GetterContainer GetGetterWrappers(const Data::Type&) { GetterContainer getterFunctions; getterFunctions.emplace("mormal", WrapGetter::Callback("normal", &Data::PlaneType::GetNormal, "Normal")); getterFunctions.emplace("distance", WrapGetter::Callback("distance", &Data::PlaneType::GetDistance, "Distance")); return getterFunctions; } static SetterContainer GetSetterWrappers(const Data::Type&) { SetterContainer setterFunctions; setterFunctions.emplace("normal", WrapSetter::Callback("normal", &Data::PlaneType::SetNormal, "Normal")); setterFunctions.emplace("distance", WrapSetter::Callback("distance", &Data::PlaneType::SetDistance, "Distance")); return setterFunctions; } }; template<> struct PropertyTraits { static GetterContainer GetGetterWrappers(const Data::Type&) { GetterContainer getterFunctions; getterFunctions.emplace("basisX", WrapGetter::Callback("basisX", &Data::TransformType::GetBasisX, "X Axis")); getterFunctions.emplace("basisY", WrapGetter::Callback("basisY", &Data::TransformType::GetBasisY, "Y Axis")); getterFunctions.emplace("basisZ", WrapGetter::Callback("basisZ", &Data::TransformType::GetBasisZ, "Z Axis")); getterFunctions.emplace("translation", WrapGetter::Callback("translation", &Data::TransformType::GetTranslation, "Translation")); return getterFunctions; } static SetterContainer GetSetterWrappers(const Data::Type&) { SetterContainer setterFunctions; setterFunctions.emplace("translation", WrapSetter::Callback("translation", &Data::TransformType::SetTranslation, "Translation")); return setterFunctions; } }; template<> struct PropertyTraits { static GetterContainer GetGetterWrappers(const Data::Type&) { GetterContainer getterFunctions; getterFunctions.emplace("min", WrapGetter::Callback("min", &Data::AABBType::GetMin, "Minimum")); getterFunctions.emplace("max", WrapGetter::Callback("max", &Data::AABBType::GetMax, "Maximum")); return getterFunctions; } static SetterContainer GetSetterWrappers(const Data::Type&) { SetterContainer setterFunctions; setterFunctions.emplace("min", WrapSetter::Callback("min", &Data::AABBType::SetMin, "Minimum")); setterFunctions.emplace("max", WrapSetter::Callback("max", &Data::AABBType::SetMax, "Maximum")); return setterFunctions; } }; template<> struct PropertyTraits { static GetterContainer GetGetterWrappers(const Data::Type&) { GetterContainer getterFunctions; getterFunctions.emplace("axisX", WrapGetter::Callback("axisX", &Data::OBBType::GetAxisX, "X Axis")); getterFunctions.emplace("axisY", WrapGetter::Callback("axisY", &Data::OBBType::GetAxisY, "Y Axis")); getterFunctions.emplace("Z Axis", WrapGetter::Callback("axisZ", &Data::OBBType::GetAxisZ, "Z Axis")); getterFunctions.emplace("halfLengthX", WrapGetter::Callback("halfLengthX", &Data::OBBType::GetHalfLengthX, "Half Length X")); getterFunctions.emplace("halfLengthY", WrapGetter::Callback("halfLengthY", &Data::OBBType::GetHalfLengthY, "Half Length Y")); getterFunctions.emplace("halfLengthZ", WrapGetter::Callback("halfLengthZ", &Data::OBBType::GetHalfLengthZ, "Half Length Z")); getterFunctions.emplace("position", WrapGetter::Callback("position", &Data::OBBType::GetPosition, "Position")); return getterFunctions; } static SetterContainer GetSetterWrappers(const Data::Type&) { SetterContainer setterFunctions; setterFunctions.emplace("halfLengthX", WrapSetter::Callback("halfLengthX", &Data::OBBType::SetHalfLengthX, "Half Length X")); setterFunctions.emplace("halfLengthY", WrapSetter::Callback("halfLengthY", &Data::OBBType::SetHalfLengthY, "Half Length Y")); setterFunctions.emplace("halfLengthZ", WrapSetter::Callback("halfLengthZ", &Data::OBBType::SetHalfLengthZ, "Half Length Z")); setterFunctions.emplace("position", WrapSetter::Callback("position", &Data::OBBType::SetPosition, "Position")); return setterFunctions; } }; template<> struct PropertyTraits { static GetterContainer GetGetterWrappers(const Data::Type&) { GetterContainer getterFunctions; getterFunctions.emplace("basisX", WrapGetter::Callback("basisX", &Data::Matrix3x3Type::GetBasisX, "Position")); getterFunctions.emplace("basisY", WrapGetter::Callback("basisY", &Data::Matrix3x3Type::GetBasisY, "Position")); getterFunctions.emplace("basisZ", WrapGetter::Callback("basisZ", &Data::Matrix3x3Type::GetBasisZ, "Position")); return getterFunctions; } static SetterContainer GetSetterWrappers(const Data::Type&) { SetterContainer setterFunctions; setterFunctions.emplace("basisX", WrapSetter::Callback("basisX", &Data::Matrix3x3Type::SetBasisX, "X Axis")); setterFunctions.emplace("basisY", WrapSetter::Callback("basisY", &Data::Matrix3x3Type::SetBasisY, "Y Axis")); setterFunctions.emplace("basisZ", WrapSetter::Callback("basisZ", &Data::Matrix3x3Type::SetBasisZ, "Z Axis")); return setterFunctions; } }; template<> struct PropertyTraits { static GetterContainer GetGetterWrappers(const Data::Type&) { GetterContainer getterFunctions; getterFunctions.emplace("basisX", WrapGetter::Callback("basisX", &Data::Matrix4x4Type::GetBasisX, "X Axis")); getterFunctions.emplace("basisY", WrapGetter::Callback("basisY", &Data::Matrix4x4Type::GetBasisY, "Y Axis")); getterFunctions.emplace("basisZ", WrapGetter::Callback("basisZ", &Data::Matrix4x4Type::GetBasisZ, "Z Axis")); getterFunctions.emplace("Translation", WrapGetter::Callback("translation", &Data::Matrix4x4Type::GetTranslation, "Translation")); return getterFunctions; } static SetterContainer GetSetterWrappers(const Data::Type&) { SetterContainer setterFunctions; setterFunctions.emplace("basisX", WrapSetter::Callback("basisX", &Data::Matrix4x4Type::SetBasisX, "X Axis")); setterFunctions.emplace("basisY", WrapSetter::Callback("basisY", &Data::Matrix4x4Type::SetBasisY, "Y Axis")); setterFunctions.emplace("basisZ", WrapSetter::Callback("basisZ", &Data::Matrix4x4Type::SetBasisZ, "Z Axis")); setterFunctions.emplace("translation", WrapSetter::Callback("translation", &Data::Matrix4x4Type::SetTranslation, "Translation")); return setterFunctions; } }; template<> struct PropertyTraits { static GetterContainer GetGetterWrappers(const Data::Type& type) { GetterContainer getterFunctions; auto behaviorProperties = GetBehaviorProperties(type); for (auto&& behaviorProperty : behaviorProperties) { AZ::BehaviorMethod* getterMethod = behaviorProperty->m_getter; if (getterMethod && getterMethod->HasResult() && getterMethod->GetNumArguments() == 1) { GetterFunction getterFunction = [&behaviorProperty, getterMethod](const Datum& thisDatum) -> AZ::Outcome { const size_t maxGetterArguments = 1; AZStd::array getterParams; const AZ::BehaviorParameter* thisParam = getterMethod->GetArgument(0); AZ::Outcome thisObjectParam = thisDatum.ToBehaviorValueParameter(*thisParam); if (!thisObjectParam.IsSuccess()) { return AZ::Failure(AZStd::string::format("BehaviorContextObject %s couldn't be turned into a BehaviorValueParameter for getter: %s", Data::GetName(thisDatum.GetType()).data(), thisObjectParam.GetError().data())); } getterParams[0].Set(thisObjectParam.TakeValue()); auto behaviorMethodInvokeOutcome = Datum::CallBehaviorContextMethodResult(getterMethod, getterMethod->GetResult(), getterParams.begin(), static_cast(getterMethod->GetNumArguments()), behaviorProperty->m_name); if (!behaviorMethodInvokeOutcome) { return AZ::Failure(AZStd::string::format("Attempting to invoke getter method %s failed: %s", getterMethod->m_name.data(), behaviorMethodInvokeOutcome.GetError().data())); } return behaviorMethodInvokeOutcome; }; const AZ::BehaviorParameter* resultParam = getterMethod->GetResult(); Data::Type resultType = Data::FromAZType(resultParam->m_typeId); if (AZ::BehaviorContextHelper::IsStringParameter((*resultParam))) { resultType = Data::Type::String(); } getterFunctions.insert({ behaviorProperty->m_name, { AZStd::move(getterFunction), resultType, behaviorProperty->m_name } }); } } return getterFunctions; } static SetterContainer GetSetterWrappers(const Data::Type& type) { enum : size_t { thisIndex = 0U }; enum : size_t { propertyIndex = 1U }; SetterContainer setterFunctions; auto behaviorProperties = GetBehaviorProperties(type); for (auto&& behaviorProperty : behaviorProperties) { AZ::BehaviorMethod* setterMethod = behaviorProperty->m_setter; if (setterMethod && setterMethod->GetNumArguments() == 2) { SetterFunction setterFunction = [setterMethod](Datum& thisDatum, const Datum& propertyDatum) -> AZ::Outcome { const size_t maxSetterArguments = 2; AZStd::array setterParams; const AZ::BehaviorParameter* thisParam = setterMethod->GetArgument(thisIndex); AZ::Outcome thisObjectParam = thisDatum.ToBehaviorValueParameter(*thisParam); if (!thisObjectParam.IsSuccess()) { return AZ::Failure(AZStd::string::format("BehaviorContextObject %s couldn't be turned into a BehaviorValueParameter for setter: %s", Data::GetName(thisDatum.GetType()).data(), thisObjectParam.GetError().data())); } setterParams[thisIndex].Set(thisObjectParam.TakeValue()); const AZ::BehaviorParameter* propertyParam = setterMethod->GetArgument(propertyIndex); AZ::Outcome propertyObjectParam = propertyDatum.ToBehaviorValueParameter(*propertyParam); if (!propertyObjectParam.IsSuccess()) { return AZ::Failure(AZStd::string::format("Property type %s couldn't be turned into a BehaviorValueParameter for setter. BehaviorContextObject %s will not be set: %s.", Data::GetName(propertyDatum.GetType()).data(), Data::GetName(thisDatum.GetType()).data(), propertyObjectParam.GetError().data())); } setterParams[propertyIndex].Set(propertyObjectParam.GetValue()); if (!setterMethod->Call(setterParams.data(), static_cast(setterParams.size()))) { return AZ::Failure(AZStd::string::format("Attempting to invoke setter method %s failed", setterMethod->m_name.data())); } return AZ::Success(); }; const AZ::BehaviorParameter* argumentParam = setterMethod->GetArgument(propertyIndex); Data::Type argumentType = Data::FromAZType(argumentParam->m_typeId); if (AZ::BehaviorContextHelper::IsStringParameter((*argumentParam))) { argumentType = Data::Type::String(); } setterFunctions.insert({ behaviorProperty->m_name, { AZStd::move(setterFunction), argumentType, behaviorProperty->m_name } }); } } return setterFunctions; } }; namespace Properties { struct TypeErasedPropertyTraits { AZ_CLASS_ALLOCATOR(TypeErasedPropertyTraits, AZ::SystemAllocator, 0); TypeErasedPropertyTraits() = default; template explicit TypeErasedPropertyTraits(Traits) { m_getterFunctionCB = &Traits::GetGetterWrappers; m_setterFunctionCB = &Traits::GetSetterWrappers; } bool m_isTransient = false; GetterContainer GetGetterWrappers(const Data::Type& type) const { return m_getterFunctionCB ? m_getterFunctionCB(type) : GetterContainer{}; } SetterContainer GetSetterWrappers(const Data::Type& type) const { return m_setterFunctionCB ? m_setterFunctionCB(type) : SetterContainer{}; } using GetterContainerCreator = GetterContainer(*)(const Data::Type&); GetterContainerCreator m_getterFunctionCB = nullptr; using SetterContainerCreator = SetterContainer(*)(const Data::Type&); SetterContainerCreator m_setterFunctionCB = nullptr; }; template static TypeErasedPropertyTraits MakeTypeErasedPropertyTraits() { return TypeErasedPropertyTraits(PropertyTraits{}); } } } }