Engine (or pure C++) calling TypeScript
Useful when Unreal Engine needs to execute certain functionality inside of TypeScript.
DYNAMIC_DELEGATE
TypeScript logic can be executed from within UObjects by binding to DYNAMIC_DELEGATE and DYNAMIC_MULTICAST_DELEGATE.
Example use-cases include:
- Exporting TypeScript functions to C++.
- UI actions (e.g OnPressed) and network messages that bind to TypeScript functionality.
C++
DECLARE_DYNAMIC_DELEGATE(FBasicNotify);
DECLARE_DYNAMIC_DELEGATE_OneParam(FNotifyWithRefString, FString&, InStringRef);
DECLARE_DYNAMIC_DELEGATE_RetVal_OneParam(FString, FNotifyWithStringRet, FString, InString);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FMulticastNotifyWithInt, int32, InInt);
UCLASS()
class PUERTS_UNREAL_DEMO_API AMyActor : public AActor
{
GENERATED_BODY()
protected:
virtual void BeginPlay() override {
// DECLARE_DYNAMIC_DELEGATE
BasicNotify.ExecuteIfBound();
// DECLARE_DYNAMIC_DELEGATE_OneParam
if (NotifyWithRefString.IsBound())
{
FString StringReference = "Input String";
NotifyWithRefString.Execute(StringReference);
UE_LOG(LogTemp, Warning, TEXT("StringReference new value: %s"), *StringReference);
}
// DECLARE_DYNAMIC_DELEGATE_RetVal_OneParam
if (NotifyWithStringRet.IsBound())
{
FString ReturnedString = NotifyWithStringRet.Execute("Some string");
UE_LOG(LogTemp, Warning, TEXT("ReturnedString: %s"), *ReturnedString); // "Some string" + " with some extra characters"
}
// DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam
MulticastNotifyWithInt.Broadcast(1337);
}
public:
UPROPERTY()
FBasicNotify BasicNotify;
UPROPERTY()
FNotifyWithRefString NotifyWithRefString;
UPROPERTY()
FNotifyWithStringRet NotifyWithStringRet;
UPROPERTY()
FMulticastNotifyWithInt MulticastNotifyWithInt;
UPROPERTY()
class UButton* ExampleButtonWidget;
};
Note: UPROPERTY()
exposes the delegate variable to puerts
TypeScript
// DECLARE_DYNAMIC_DELEGATE
function OnNotifyBasic(): void {
console.warn("BasicNotify callback executed");
}
Actor.BasicNotify.Bind(OnNotifyBasic);
// DECLARE_DYNAMIC_DELEGATE_OneParam
import {$unref, $set} from 'puerts';
Actor.NotifyWithRefString.Bind((InStringRef) => {
console.warn("NotifyWithRefString", $unref(InStringRef)); // Print the value
$set(InStringRef, "Some new string!"); // Update the value by reference
});
// DECLARE_DYNAMIC_DELEGATE_RetVal_OneParam
Actor.NotifyWithStringRet.Bind((InString) => {
return InString + " with some extra characters";
});
// DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam
function OnMulticast(InInt: number): void {
console.warn("Multicast Callback 1: ", InInt);
}
Actor.MulticastNotifyWithInt.Add(OnMulticast);
Actor.MulticastNotifyWithInt.Add((InInt: number) => {
console.warn("Multicast Callback 2:", InInt);
});
// Binding to button "OnPressed"
function OnButtonPressed(): void {
console.warn("Button Pressed!");
}
Actor.ExampleButtonWidget.OnPressed.Add(OnButtonPressed);
Defining delegates in typescript
Instead of directly binding to DYNAMIC_DELEGATE inside of a UObject, new delegates can be constructed from within typescript.
Example use-cases include:
- Passing delegates as parameters to C++
- Manual control over delegate handles and their lifetime
C++
DECLARE_DYNAMIC_DELEGATE(FBasicNotify);
void UMyObject::PassJsFunctionAsDelegate(FBasicNotify Callback)
{
auto ReturnValue = Callback.Execute("Some Example String...");
UE_LOG(LogTemp, Warning, TEXT("%d"), ReturnValue);
}
TypeScript
// Delegate handle owned by UObject
import {toDelegate} from 'puerts';
function GetStringLength(InString:string) : number {
return InString.length;
}
const NewDelegate = toDelegate(MyUObj, GetStringLength); // toDelegate(owOwnerner: UE.Object, Func:Function)
MyUObj.PassJsFunctionAsDelegate(NewDelegate);
// Delegate handle with no owner (Manual Cleanup)
import {toManualReleaseDelegate, releaseManualReleaseDelegate} from 'puerts';
function GetStringLength(InString:string) : number {
return InString.length;
}
const NewManualReleaseDelegate = toManualReleaseDelegate(GetStringLength); // toManualReleaseDelegate(Func:Function)
MyUObj.PassJsFunctionAsDelegate(NewManualReleaseDelegate);
releaseManualReleaseDelegate(GetStringLength); // Release to prevent memory leak
// Delegate function is a UFunction of UObject
import {toDelegate} from 'puerts';
const NewDelegate = toDelegate(MyUObj, "SomeUFunction"); // toDelegate(Owner: UE.Object, UFuncName: string)
MyUObj.PassJsFunctionAsDelegate(toDelegate(NewDelegate));
Binding to std::function
Inside of a standard C++ environment, std::function
is the preferred method for passing around function pointers. Puerts supports this as well.
C++
void MyClass::PassJsFunctionWithStd(std::function<int(int, int)> InFunction)
{
int ReturnValue = InFunction(88, 99);
UE_LOG(LogTemp, Warning, TEXT("%i"), ReturnValue);
}
TypeScript
MyObj.PassJsFunctionWithStd((A: number, B: number) => {
const Sum = A + B;
console.warn(A + '+' + B + "= " + Sum);
return Sum;
})