はじめに
Unreal Engine (UE) Advent Calendar 2025 の11日目の記事です!
この記事では UMG Viewmodel の各 Execution Mode ごとの挙動の違いについて解説します。
Execution Mode とは View Bindings で追加した行ごと(バインディングごと)に設定できる、バインディングの実行タイミングのことです。
デフォルトではこの画像の部分のチェックを付けないと「Auto」の状態から変更できませんが、チェックを付けると Immediate と Delayed、Tick、Auto の4つから選択できるようになります。

これらの Execution Mode は MVVMExecutionMode.h で EMVVMExecutionMode として定義されています。
このコードを見るとわかるようにエディタで「Auto」と表示されている Execution Mode の実際の名称は「DelayedWhenSharedElseImmediate」です。
UENUM()
enum class EMVVMExecutionMode : uint8
{
/** Execute the binding as soon as the source value changes. */
Immediate = 0,
/** Execute the binding at the end of the frame before drawing when the source value changes. */
Delayed = 1,
/** Always execute the binding at the end of the frame. */
Tick = 2,
/** When the binding can be triggered from multiple fields, use Delayed. Else, uses Immediate. */
DelayedWhenSharedElseImmediate = 3 UMETA(DisplayName="Auto"),
};
このコードに書かれている各 Execution Mode のコメントの和訳は以下の通りです。
| Execution Mode 名 | コメントの和訳 |
|---|---|
| Immediate | ソース値が変更されたらすぐにバインディングを実行します。 |
| Delayed | ソース値が変更された時に描画前のフレームの最後でバインディングを実行します。 |
| Tick | バインディングを常にフレームの最後に実行します。 |
| Auto(DelayedWhenSharedElseImmediate) | バインディングが複数のフィールドからトリガーできる場合は Delayed を使用します。それ以外の場合は Immediate を使用します。 |
検証環境
この記事での環境は以下の通りです。
- Windows 11
- UE 5.7.0(ランチャー版)
- UMG Viewmodel 1.0(Beta)
Execution Mode
Immediate / Delayed
まずは Immediate と Delayed についてです。
Immediate は ViewModel の変更を検知したら毎回すぐに View へ反映させます。
Delayed はそのフレームの最後にまとめて実行します。
(同じバインディングが1フレームで複数回呼ばれた場合は最後のバインディングのみを一度だけ実行します。)
パフォーマンスの向上
Execution Mode を Immediate ではなく、Delayed にすることによってパフォーマンスが向上することがあるので、その例を見てみます。
とりあえず ViewModel に Integer 型の Field Notify 変数を1つ追加しました。

View では OnViewModelVariableChanged() というカスタムイベントを作成して、引数で受け取った Integer 型の値をログに表示するという処理を追加しました。

そして ViewModel の Field Notify 変数の値が変更されたらそのカスタムイベントを呼び出されるようにバインディングしました。

Model には Enter キーが押されたら ViewModel の Field Notify 変数の値を1万回変更するという処理を追加しました。

この状態でゲームを実行して Enter キーを押し、Unreal Insights で処理時間を確認してみました。
すると、Execution Mode を Immediate にしたときは OnViewModelVariableChanged() が1万回実行されるため、ログには1から10000までの数字が表示されました。

Unreal Insights を見ると、そのフレームの UWorld_Tick は 976.7ms もかかっています。

しかし、Execution Mode を Delayed にすると OnViewModelVariableChanged() はそのフレームの最後に一度のみ実行されるため、ログには10000という数字が一度だけ表示されました。

Unreal Insights を見ると、そのフレームの UWorld_Tick は 12.8ms になりました!!
かなり劇的な変化です。

このように1つのバインディングが1フレームで複数回実行されるような状況では Execution Mode を Delayed にすることによってパフォーマンスが向上することがあります。
競合の解決
次は Immediate と Delayed を組み合わせて使用することによって競合を解決することができるという例を見てみます。
ViewModel には「ViewModelVariable_1」と「ViewModelVariable_2」という Integer 型の Field Notify 変数を2つ追加しました。

Model では Enter キーが押されたら ViewModel の ViewModelVariable_1 に「1」を代入するという処理と、ViewModelVariable_2に「2」を代入するという処理を順番に行うようにしました。

View では SetViewText() というカスタムイベントを作成して、View の Hierarchy に追加してある Text に SetText() するという処理と、引数で受け取った値をログに表示するという処理を追加しました。

そして以下のように ViewModel の ViewModelVariable_1 と ViewModelVariable_2 を View の SetViewText() にバインディングしました。
まずはどちらも Immediate にしてあります。

この状態でゲームを実行して Enter キーを押すとログに「1」「2」と順番に表示され、View の Text は「2」となりました。
Model で行った順番通りに View にも反映されていることがわかります。


先ほどは ViewModelVariable_1 とのバインディングも、ViewModelVariable_2 とのバインディングも Immediate にしていましたが、次は ViewModelVariable_1 とのバインディングのみ Immediate から Delayed に変えてみます。

すると、Model の処理は何も変えていないにも関わらず、ログには「2」「1」と先ほどとは逆の順番で表示され、View の Text は「1」となりました。
Execution Mode を Immediate にしたバインディングが先に実行され、それを Delayed にしたバインディングはその後に実行されていることがわかります。


このように複数のバインディングによって View の1つの要素が更新されるという状況においては、Immediate と Delayed の組み合わせによって競合を解決することができます。
(今回の例では「View の Text は ViewModel の ViewModelVariable_1 によっても、ViewModelVariable_2 によっても変更される可能性があるけど、1フレームでこれら2つの Field Notify 変数によって Text が変更されそうになったときは ViewModelVariable_1 の変更を適用してほしいから ViewModelVariable_1 のバインディングは Delayed にしよう!」など)
Execution Mode を Delayed にすることによって競合を解決できるというのは、こちらの Unreal Fest Orlando 2025 のセッションでも触れられています。
(12分辺り)
Auto(DelayedWhenSharedElseImmediate)
次は Auto(DelayedWhenSharedElseImmediate)についてです。
バインディングを作成した際にデフォルトで設定されている Execution Mode はこの Auto です。
EMVVMExecutionMode::DelayedWhenSharedElseImmediate の定義部分には「バインディングが複数のフィールドからトリガーできる場合は Delayed を使用します。」というコメントが書かれています。
これは例えば、複数の Field Notify 変数が引数にバインドされている変換関数を使用している場合がそれに該当します。
とりあえず ViewModel と Model、View を先ほどと同じ以下の状態にしました。
(View の SetViewText() からは PrintString を削除しました。)



そして以下の関数を View に追加しました。
この関数では Integer 型の引数を2つ受け取り、その値をログに出力しています。
(「CF」は「Conversion Function」の略です。)

この関数を変換関数として使用したいのですが、関数が変換関数として使えるようにするための条件について公式ドキュメントには以下のように書かれていました。
関数はブループリントに表示され、1 つの入力引数と 1 つの戻り値を含める必要があります。 また、グローバルに定義されている場合は関数も静的である必要があり、 UserWidget に定義されている場合は pure で const である必要があります。
UMG ビューモデル
なので、この関数を選択して Details で「Pure」と「Const」にチェックを付けます。
これでこの関数を変換関数として使用できるようになりました。

View Bindings で CF_Sample() と SetViewText() をバインディングし、ViewModel の Field Notify 変数を各引数にバインドします。
Execution Mode は Auto にします。

この状態でゲームを実行して Enter キーを押すとログに「InValue_1: 1 InValue_2: 2」と一度だけ表示されました。
デバッガを繋げて MVVMView.cpp の737行目辺りでブレークすると、Delayed が選択されたことがわかります。

次は InValue_2 のバインドを外して ViewModel の Field Notify 変数が1つだけ変換関数にバインドされている状態にします。

この状態でゲームを実行して Enter キーを押すとログに「InValue_1: 1 InValue_2: 0」と一度だけ表示されました。
再びデバッガを繋げて MVVMView.cpp の737行目辺りでブレークすると、今回は Immediate が選択されたことがわかります。

このように Execution Mode を Auto にすると、状況に応じて Immediate と Delayed が自動で切り変わるようになります。
Tick
最後は Tick についてです。
Execution Mode を Tick にすると従来のバインディング方法のように値の変更に関わらず、バインディングを毎フレーム実行する『はず』です。
ところが、現時点で自分が試した限りではエディタから Execution Mode を Tick に変えると、ViewModel の Field Notify 変数の値を変更してもバインディングが実行されませんでした…

このプラグインのソースコードを読んでみると UMVVMView::InitializeSourceBindings() で FMVVMViewClass_Source::HasTickBindings() が true のときに UMVVMBindingSubsystem::AddViewWithTickBinding() が呼び出されて、その View が UMVVMBindingSubsystem::ViewsWithTickBindings に追加されるようでした。
(↓MVVMView.cpp の509行目辺り)
if (ClassSource.HasTickBindings())
{
++NumberOfSourceWithTickBinding;
if (NumberOfSourceWithTickBinding == 1 && !bHasDefaultTickBinding)
{
GEngine->GetEngineSubsystem<UMVVMBindingSubsystem>()->AddViewWithTickBinding(this);
}
}
ですが、ソースコードを読んだ限りでは FMVVMViewClass_Source::HasTickBindings() が true になることはないように見えます…
さらにソースコードを読んでみると UMVVMView::bHasDefaultTickBinding が true のとき、先ほどと同様に UMVVMBindingSubsystem::AddViewWithTickBinding() が呼び出されて、その View が UMVVMBindingSubsystem::ViewsWithTickBindings に追加されるようでした。
(↓MVVMView.cpp の386行目辺り)
if (NumberOfSourceWithTickBinding == 0 && bHasDefaultTickBinding)
{
GEngine->GetEngineSubsystem<UMVVMBindingSubsystem>()->AddViewWithTickBinding(this);
}
この UMVVMView::bHasDefaultTickBinding の値はそのコードのすぐ上で「MVVM.DefaultExecutionMode」というコンソール変数の値によって決まっています。
(↓MVVMView.cpp の382行目辺り)
static IConsoleVariable* CVarDefaultExecutionMode = IConsoleManager::Get().FindConsoleVariable(TEXT("MVVM.DefaultExecutionMode"));
bHasDefaultTickBinding = ensure(CVarDefaultExecutionMode) ? (EMVVMExecutionMode)CVarDefaultExecutionMode->GetInt() == EMVVMExecutionMode::Tick : false;
なので DefaultEngine.ini に以下の内容を追加して「MVVM.DefaultExecutionMode」の値を2に変更してみました。
(EMVVMExecutionMode::Tick は「2」です。)
[SystemSettings]
MVVM.DefaultExecutionMode=2
この状態でエディタを再起動してゲームを実行すると、ViewModel の Field Notify 変数の値が変わっていなくてもバインディングが毎フレーム実行されるようになりました!
ViewModel の Field Notify 変数の値を変更すると View に通知が飛ぶことも確認できました。




