LoginSignup
9
4

More than 3 years have passed since last update.

DYNAMIC_(MULTICAST_)DELEGATEにC++からバインドする

Last updated at Posted at 2020-11-30

小ネタです。
UE4のデリゲート(≒イベントディスパッチャー)は大きく分けて2種類あります。

ネイティブコードを主にバインドすることを前提としてる

DELEGATE
MULTICAST_DELEGATE

ブループリントの関数を主にバインドすることを前提としている

DYNAMIC_DELEGATE
DYNAMIC_MULTICAST_DELEGATE

です。前者をネイティブデリゲート、後者をダイナミックデリゲートと呼ぶことにします。
前者はラムダ式など柔軟かつ多彩な形で関数を登録できるのに対して、後者はBindUFunctionのようにUnrealEngineのリフレクションを経由したアクセスしかできません。

稀にダイナミックデリゲートに対してネイティブの関数をバインドしたくなる時があります。
今回の記事はこれに関する簡単なTipsです。

UFUNCTION宣言されていればネイティブ関数を登録できる

このような感じでUFUNCTIONとして宣言されている関数であれば、

    UFUNCTION()
    void OnLevelUnloaded();

以下のような形で関数名をFName型にしたもので登録することができます。


    FScriptDelegate Delegate;
    static const FName OnLevelUnloadedFunction(TEXT("OnLevelUnloaded"));
    Delegate.BindUFunction( this, OnLevelUnloadedFunction );
    UnloadLevel->OnLevelUnloaded.Add(Delegate);

ペイロード(追加のデータ)を乗せたい!

単純に関数を呼び出すだけであればこれで問題ないのですが、やはりデリゲートにたいして追加のペイロードを乗せたくなる時があると思います。
ネイティブデリゲートであればラムダのキャプチャなどによって超簡単にペイロードを乗せることができますが、
(キャプチャとは[]の中の部分です)

    FCoreDelegates::GetPakEncryptionKeyDelegate().BindLambda([DefaultKey](uint8 OutKey[32]) { FMemory::Memcpy(OutKey, DefaultKey.Key, sizeof(DefaultKey.Key)); });

ダイナミックデリゲートではそうはいきません。
対策として中間のオブジェクトを用意することで疑似ペイロードを再現します。

UCLASS()
class ULevelUnloadHelper : public UObject
{
    GENERATED_BODY()
public:
    UPROPERTY()
    ULevelStreaming* Level;
    UFUNCTION()
    void OnLevelUnloaded();
};

バインドする時は、中間のオブジェクトを作成してからパラメータをセットし、
その中間オブジェクトをデリゲートに登録します。
重要な点としては、BindUFunctionの第一引数で渡されるオブジェクトはTWeakObjectPtrで保持されることに注意する必要があります
登録してから呼び出すまでにGCに回収されないようにAddToRootを呼び出しています。

    ULevelUnloadHelper* helper = NewObject<ULevelUnloadHelper>();
    helper->Level = UnloadLevel;
    helper->AddToRoot();

    FScriptDelegate Delegate;
    Delegate.BindUFunction(helper, FName(TEXT("OnLevelUnloaded")));
    UnloadLevel->OnLevelUnloaded.Add(Delegate);

登録したデリゲートが読みだされたら、中間オブジェクトに対してRemoveFromRootを呼び出し、GCで回収できるようにしておきましょう。

void ULevelUnloadHelper::OnLevelUnloaded()
{
    RemoveFromRoot();
    Level->SetIsRequestingUnloadAndRemoval(true);
}

※もしデリゲートが呼び出されないケースがある場合、AddToRoot設定が残りメモリリークの可能性があります。
安全なタイミングでTObjectIteratorなどを回し、不運にも残ってしまったオブジェクトに対してRemoveFromRootを呼び出す必要があるかもしれません。

まとめ

  • ダイナミックデリゲートにはUFUNCTION定義すればネイティブの関数を名前でバインディングできる!
  • obj TRYGC でコンソールコマンドから即座にGCが呼び出せる。 詳しくはこちら→[UE4] Objコマンドによるオブジェクト解析
  • ダイナミックデリゲートに渡すUObjectは弱参照ということを覚えておく。(←これが言いたかっただけ)
9
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
4