View Issue Details

IDProjectCategoryView StatusLast Update
0002575NoesisGUIUnrealpublic2023-05-03 18:05
Reportervmaurer Assigned Tohcpizzi  
PrioritynormalSeverityfeatureReproducibilityalways
Status resolvedResolutionfixed 
Product Version3.2.0 
Fixed in Version3.2.1 
Summary0002575: Support for UE Delegates / Expose Delegates to DataEventTriggers
DescriptionCurrently it's not possible to hook up Unreal Engine Delegates (such as TBaseDynamicMulticastDelegate<>) with DataEventTriggers because they are not supported by the Noesis Type Class wrapper. When a DataEventTrigger is registered, it checks for a matching definition in the TypeClass::mEvents array, but it won't find any.

Steps To ReproducePlease see the forum topic for a full description / steps to reproduce: https://www.noesisengine.com/forums/viewtopic.php?t=2918
TagsC++, Unreal
PlatformAny

Activities

hcpizzi

hcpizzi

2023-05-03 18:02

developer   ~0008467

You can find a patch attached that enables this feature.

You can use it with natively declared dynamic multicast delegates (declared using the DECLARE_DYNAMIC_MULTICAST_DELEGATE family of macros), or with Blueprint declared Event Dispatchers.
Events.patch (6,615 bytes)   
Index: NoesisTypeClass.cpp
===================================================================
--- NoesisTypeClass.cpp	(revision 12417)
+++ NoesisTypeClass.cpp	(working copy)
@@ -369,6 +369,12 @@
 	typedef Noesis::FlowDirection NoesisType;
 	static Noesis::FlowDirection ToNoesis(const ETextFlowDirection& Value)
 	{
+		if (Value == ETextFlowDirection::Auto)
+		{
+			EFlowDirection FlowDirection = FLayoutLocalization::GetLocalizedLayoutDirection();
+			return (FlowDirection == EFlowDirection::RightToLeft) ? Noesis::FlowDirection_RightToLeft : Noesis::FlowDirection_LeftToRight;
+		}
+
 		return (Value == ETextFlowDirection::RightToLeft) ? Noesis::FlowDirection_RightToLeft : Noesis::FlowDirection_LeftToRight;
 	}
 	static ETextFlowDirection ToUnreal(const Noesis::FlowDirection& Value)
@@ -1673,7 +1679,6 @@
 NoesisTypeProperty::NoesisTypeProperty(Noesis::Symbol Name, const Noesis::Type* Type)
 	: Noesis::TypeProperty(Name, Type)
 {
-	check(Type != nullptr);
 }
 
 void* NoesisTypeProperty::GetContent(void* Ptr) const
@@ -1755,15 +1760,33 @@
 		mProperties.Insert(It, Property);
 	}
 
+	void AddEvent(NoesisTypeProperty* Event)
+	{
+		check(Event);
+
+		PropertyVector::Iterator It = LowerBound(mEvents.Begin(), mEvents.End(), Event,
+			[](Noesis::TypeProperty* elem1, Noesis::TypeProperty* elem2)
+			{
+				return elem1->GetName() < elem2->GetName();
+			});
+
+		check(It == mEvents.End() || (*It)->GetName() != Event->GetName());
+
+		mEvents.Insert(It, Event);
+	}
+
 	void ChangeName(Noesis::Symbol NewName)
 	{
 		PropertyVector Properties = mProperties;
 		mProperties.Clear();
+		PropertyVector Events = mEvents;
+		mEvents.Clear();
 		const Noesis::TypeClass* Base = mBase;
 		NoesisIsShuttingDown = true;
 		this->~NoesisTypeClass();
 		NoesisIsShuttingDown = false;
 		::new(this) NoesisTypeClass(NewName);
+		mEvents = Noesis::MoveArg(Events);
 		mProperties = Noesis::MoveArg(Properties);
 		mBase = Base;
 	}
@@ -1775,6 +1798,12 @@
 			NoesisTypeProperty* Property = (NoesisTypeProperty*)TypeProperty;
 			Property->Invalidate();
 		}
+
+		for (auto TypeProperty : mEvents)
+		{
+			NoesisTypeProperty* Property = (NoesisTypeProperty*)TypeProperty;
+			Property->Invalidate();
+		}
 	}
 
 	union
@@ -2008,6 +2037,7 @@
 	Noesis::PropertyChangedEventHandler PropertyChangedHandler;
 	UObject* Object;
 	TMap<UFunction*, Noesis::Ptr<class NoesisFunctionWrapper>> CommandMap;
+	TMap<FProperty*, Noesis::EventHandler> EventMap;
 };
 
 class NoesisFunctionWrapper : public Noesis::BaseCommand
@@ -2389,6 +2419,112 @@
 	return Property->HasAllPropertyFlags(CPF_BlueprintReadOnly);
 }
 
+class NoesisTypePropertyObjectWrapperEvent : public NoesisTypeProperty
+{
+public:
+	NoesisTypePropertyObjectWrapperEvent(Noesis::Symbol Name, FProperty* InProperty);
+
+	/// From TypeProperty
+	//@{
+	void* GetContent(const void* Ptr) const override;
+	const void* Get(const void* Ptr) const override;
+	//@}
+
+	virtual void Invalidate() override;
+
+protected:
+	/// From TypeProperty
+	//@{
+	bool IsReadOnly() const override;
+	//@}
+
+private:
+	FProperty* Property;
+};
+
+NoesisTypePropertyObjectWrapperEvent::NoesisTypePropertyObjectWrapperEvent(Noesis::Symbol Name, FProperty* InProperty)
+	: NoesisTypeProperty(Name, nullptr), Property(InProperty)
+{
+}
+
+DEFINE_FUNCTION(execNoesisDelegate)
+{
+	P_FINISH;
+	P_NATIVE_BEGIN;
+
+	UFunction* Delegate = Stack.Node;
+	FString EventName = Delegate->GetName().RightChop(15); // Remove the NoesisDelegate_ prefix
+	auto Wrapper = NoesisFindComponentForUObject(Context);
+	if (Wrapper != nullptr)
+	{
+		auto TypeClass = (NoesisTypeClass*)Wrapper->GetClassType();
+		Noesis::TypeClassEvent ClassEvent = Noesis::FindEvent(TypeClass, Noesis::Symbol(TCHAR_TO_UTF8(*EventName)));
+		auto TypeProperty = (NoesisTypePropertyObjectWrapperEvent*)ClassEvent.event;
+		if (TypeProperty != nullptr)
+		{
+			auto EventHandler = (Noesis::EventHandler*)TypeProperty->GetContent(Wrapper);
+			Noesis::EventArgs Args;
+			EventHandler->Invoke(Wrapper, Args);
+		}
+	}
+
+	P_NATIVE_END;
+}
+
+void* NoesisTypePropertyObjectWrapperEvent::GetContent(const void* Ptr) const
+{
+#if WITH_EDITOR
+	if (Property == nullptr)
+	{
+		NS_LOG("A property of a deleted class is still referenced");
+		return nullptr;
+	}
+#endif
+
+	NoesisObjectWrapper* Wrapper = (NoesisObjectWrapper*)Ptr;
+	if (Wrapper->Object == nullptr)
+	{
+		NS_LOG("A wrapper of a deleted object is still referenced");
+		return nullptr;
+	}
+
+	auto Found = Wrapper->EventMap.Find(Property);
+	if (Found == nullptr)
+	{
+		// We add the fuction names to the UObject class so FScriptDelegate can find them later.
+		// We only need to do this once per event name.
+		const auto DelegateName = WriteToString<128>("NoesisDelegate_", *Property->GetName());
+		UClass* ObjectClass = UObject::StaticClass();
+		if (ObjectClass->FindFunctionByName(*DelegateName) == nullptr)
+		{
+			auto NoesisDelegateFunc = NewObject<UFunction>(ObjectClass, *DelegateName);
+			NoesisDelegateFunc->SetNativeFunc(&execNoesisDelegate);
+			NoesisDelegateFunc->FunctionFlags |= FUNC_Native;
+			ObjectClass->AddFunctionToFunctionMap(NoesisDelegateFunc, *DelegateName);
+		}
+
+		FScriptDelegate Delegate;
+		Delegate.BindUFunction(Wrapper->Object, *DelegateName);
+		FMulticastDelegateProperty* DelegateProperty = (FMulticastDelegateProperty*)Property;
+		DelegateProperty->AddDelegate(Delegate, Wrapper->Object);
+	}
+	return &Wrapper->EventMap.FindOrAdd(Property);
+}
+
+const void* NoesisTypePropertyObjectWrapperEvent::Get(const void* Ptr) const
+{
+	return GetContent(Ptr);
+}
+
+void NoesisTypePropertyObjectWrapperEvent::Invalidate()
+{
+}
+
+bool NoesisTypePropertyObjectWrapperEvent::IsReadOnly() const
+{
+	return true;
+}
+
 class NoesisTypePropertyObjectWrapperGetterSetter : public NoesisTypeProperty
 {
 public:
@@ -2680,6 +2816,12 @@
 				TypeClass->AddProperty(TypeProperty);
 			}
 		}
+
+		if (Property->GetClass()->IsChildOf(FMulticastDelegateProperty::StaticClass()))
+		{
+			NoesisTypeProperty* TypeProperty = new NoesisTypePropertyObjectWrapperEvent(PropertyId, Property);
+			TypeClass->AddEvent(TypeProperty);
+		}
 	}
 }
 
@@ -3450,6 +3592,11 @@
 			Wrapper = *TextureSource;
 		}
 	}
+	else if (Class->IsChildOf(UNoesisXaml::StaticClass()))
+	{
+		auto NoesisXaml = (UNoesisXaml*)Object;
+		Wrapper = NoesisXaml->LoadXaml();
+	}
 	else
 	{
 		Wrapper = *new NoesisObjectWrapper(Object);
Events.patch (6,615 bytes)   

Issue History

Date Modified Username Field Change
2023-04-20 13:26 vmaurer New Issue
2023-04-20 13:26 vmaurer Tag Attached: C++
2023-04-20 13:26 vmaurer Tag Attached: Unreal
2023-04-20 17:50 sfernandez Assigned To => hcpizzi
2023-04-20 17:50 sfernandez Status new => assigned
2023-05-03 18:02 hcpizzi Note Added: 0008467
2023-05-03 18:02 hcpizzi File Added: Events.patch
2023-05-03 18:05 hcpizzi Status assigned => resolved
2023-05-03 18:05 hcpizzi Resolution open => fixed
2023-05-03 18:05 hcpizzi Fixed in Version => 3.2.1