Numbers on this page were captured, not estimated. A unit test (Test_DispatchStack, tagged TSTK, in
GMPTests.cpp) calls FPlatformStackWalk::CaptureStackBackTrace from inside the listener body and
counts the GMP frames sitting between the send call and the callback.
The same listener is hit two ways:
- by-name —
SendObjectMessage(MSGKEY(...), ...), the FName plusTMaplookup path - by-store —
MSGKEY_SLOT(...)plusSendObjectMessageDirect(...), the baked-key path
Reproduce:
dotnet Engine/Binaries/DotNET/UnrealBuildTool/UnrealBuildTool.dll \
<YourEditor> Win64 <DebugGame|Development> -Project=<Your.uproject> -Module=GMP -WaitMutex
Engine/Binaries/Win64/UnrealEditor[-Win64-DebugGame]-Cmd.exe \
<Your.uproject> -run=GMPUnitTest -unattended -nopause -nosplash -nullrhi -NoShaderCompile
| Build | by-name (FName + lookup) | by-store (baked key) |
|---|---|---|
| Unoptimized (DebugGame) | 9 | 7 |
| Optimized (Development) | 4 | 3 |
Frame counts exclude the listener callback itself.
Both rows are modular builds. GMP_WITH_INLINE_FIRE only takes effect when GMP_WITH_STATIC_STORE
is on, and that requires GMP_STATIC_STORE_MONOLITHIC (IS_MONOLITHIC). What it removes is the first two
of the three optimized frames: the cross-module GMP_API wrapper disappears, and the dispatch loop inlines
into the caller.
Going from 9 to 3 is two separate effects:
- key baking merges the three hub frames (
SendObjectMessageWrapperEx→NotifyMessageImpl→FireMsgBodyAdapt) into a singleNotifyMessageDirectRaw - the optimizer inlines away the type-erasure scaffolding: the dispatch lambda,
FlexBackendThunk,TGMPFunction::operator()and the unpack thunk
What survives optimization is the work that actually has to happen: find the store, walk the matched listeners, call them.
by-name — 9 frames
GMP::FMessageHub::SendObjectMessageWrapperEx<0,int>()GMP::FMessageHub::NotifyMessageImpl()GMP::FireMsgBodyAdapt()GMP::GMPFireWithSigSourceDirectRaw()GMP::FSignalUtils::FireWithSigSourceCore<0, ...FireWithSigSourceRaw<0>...<lambda_1> >()GMP::FSignalUtils::FireWithSigSourceRaw<0>::<lambda_1>::operator()()GMP::FlexSig::TFlexBackendThunk<GMP::TGMPFunction<void __cdecl(FGMPTypedAddr const*, GMP::FGMPExtra const*)>, FGMPTypedAddr const*, GMP::FGMPExtra const*>::FlexThunk()GMP::TGMPFunction<void __cdecl(FGMPTypedAddr const*, GMP::FGMPExtra const*)>::operator()()GMP::Hub::RawUnpackThunk<std::tuple<int>, ...Test_DispatchStack...<lambda_1> >()
by-store (baked key) — 7 frames
GMP::FMessageHub::NotifyMessageDirectRaw()GMP::GMPFireWithSigSourceDirectRaw()GMP::FSignalUtils::FireWithSigSourceCore<0, ...>()GMP::FSignalUtils::FireWithSigSourceRaw<0>::<lambda_1>::operator()()GMP::FlexSig::TFlexBackendThunk<...>::FlexThunk()GMP::TGMPFunction<...>::operator()()GMP::Hub::RawUnpackThunk<...>()
by-name — 4 frames
GMP::FMessageHub::SendObjectMessageWrapperEx<0,int>()GMP::FMessageHub::NotifyMessageImpl()GMP::GMPFireWithSigSourceDirectRaw()GMP::FSignalUtils::FireWithSigSourceCore<0, ...>()
by-store (baked key) — 3 frames
GMP::FMessageHub::NotifyMessageDirectRaw()GMP::GMPFireWithSigSourceDirectRaw()—GMP_API, the export the optimizer cannot inline throughGMP::FSignalUtils::FireWithSigSourceCore<0, ...>()— the matching loop
Symbols are as the platform stack walker printed them; template arguments are elided above where they only repeat the enclosing signature.