Unreal Engine 4: Delegates, Async And Subsystems

Transcription

Unreal Engine 4:Delegates, Asyncand SubsystemsA follow up session onUE4’s async executionmodelMichele Mischitelli

Main topics of this meetupDelegatesAsynchronousexecutionSubsystemsData types thatreference and executemember functions onC objectsStrategies and classesthat allow devs to runasynchronous code usingthe UE4 frameworkAutomaticallyinstantiated classes withmanaged lifetimesMichele Mischitelli2

DelegatesType-safe dynamic binding of memberfunctionsMichele Mischitelli3

There are 4 2 types of delegates in UE4SingleMulticastA single function isbound to the delegateDelegates that can be boundto multiple functions andexecute them all at once Safe to copyDynamicDelegates that can beserialized and rely on reflection(instead of function pointers) Prefer passing by ref Declared using MACROs In global scope Inside a namespace Within a class declaration Support for signatures thatDynamic MulticastSparseEvents1-byte multicastimplementation. Even slowerthan dynamic multicastSimilar to multicast, but onlythe class that declares it canBroadcast Return a value Are const Have up to 8 arguments Have up to 4 additional payloadsMichele Mischitelli4

Single (or unicast) delegate typeDeclarationBindingUsagevoid Function()DECLARE DELEGATE( DelegateName )void Function( Param1 )DECLARE DELEGATE OneParam( DelegateName, Param1Type )void Function( Param1 , . )DECLARE DELEGATE Num Params( DelegateName, Param1Type, . ) RetVal Function()DECLARE DELEGATE RetVal( RetValType, DelegateName ) RetVal Function( Param1 )DECLARE DELEGATE RetVal OneParam( RetValType, DelegateName, Param1Type ) RetVal Function( Param1 , . )DECLARE DELEGATE RetVal Num Params( RetValType, DelegateName, Param1Type, . )Michele Mischitelli5

Single (or unicast) delegate typeDeclaration BindStatic(func, args ) Binds a raw C pointer global function delegate BindLambda(func, args ) Binds a C lambda delegate Technically this works for any functor types, butlambdas are the primary use case BindRaw(obj*, func, args ) Binds a raw C pointer delegate Raw pointer doesn't use any sort of reference, somay be unsafe to call if the object was deleted. Becareful when calling Execute()!BindingUsage BindSP(objPtr, func, args )BindThreadSafeSP( ) Shared pointer-based member function delegate BindUFunction(uObj*, funcName, args ) UFunction-based member function delegate BindUObject(uObj*, func, args ) UObject-based member function delegate BindWeakLambda(obj*, func, args ) Just like the non-weak variantThese keep a weak reference to your object. Youcan use ExecuteIfBound() to call themMichele Mischitelli6

Single (or unicast) delegate typeDeclarationBindingUsageDECLARE DELEGATE OneParam(FDataIsReadyDelegate, float, value)UCLASS()class TEST API UProducer : public UObject{public:FDataIsReadyDelegate OnDataIsReady;void Register() {auto funName GET FUNCTION NAME CHECKED(UProducer, Receive);OnDataIsReady.BindUFunction(this, funName, true);}void Invoke() const oid Receive(float arg1, bool payload1) { }};Michele Mischitelli7

Multicast delegate typevoid Function()DECLARE MULTICAST DELEGATE( DelegateName )void Function( Param1 )DECLARE MULTICAST DELEGATE OneParam( DelegateName, Param1Type )void Function( Param1 , . )DECLARE MULTICAST DELEGATE Num Params( DelegateName, Param1Type, . )Similar to unicast delegates, both in declaration and in usageCan register multiple functions, thus binding methods are more array-like insemanticsRegistered functions are stored in an invocation listThe order in which bound functions are called is not definedBroadcast() is always safe to callMichele Mischitelli8

Dynamic delegate variantsvoid Function()DECLARE DYNAMIC DELEGATE( DelegateName )void Function( Param1 )DECLARE DYNAMIC MULTICAST DELEGATE OneParam( DelegateName, Param1Type )void Function( Param1 , . )DECLARE DYNAMIC MULTICAST DELEGATE Num Params( DelegateName, Param1Type, . )Can be serializedFunctions can be found by name (reflection)Slower than regular delegates as functions are found via reflection compared toC functorsBinding via helper macros AddDynamic(obj*, &Class::Func), BindDynamic( ),RemoveDynamic( )Executed via Execute(), ExecuteIfBound(), IsBound()Michele Mischitelli9

Event delegate typevoid Function()DECLARE EVENT( OwningType, EventName )void Function( Param1 , . )DECLARE EVENT Num Params( OwningType, EventName, Param1Type, . )void Function( Param1 , . )DECLARE DERIVED EVENT( DerivedType, ParentType::PureEventName, OverriddenEventName )It’s a multicast delegateAny class can bind to events but only the one that declares it may invokeBroadcast(), IsBound() and Clear() functionsEvent objects can be exposed in a public interface without worrying aboutwho’s going to call these functionsUse case: callbacks in purely abstract classesBroadcast() is always safe to callMichele Mischitelli10

Sparse dynamic multicast delegate typevoid Function()DECLARE DYNAMIC MULTICAST SPARSE DELEGATE( DelegateClass, OwningType, DelegateName )void Function( Param1 , . )DECLARE DYNAMIC MULTICAST SPARSE DELEGATE Num Params( . )It works just like a (slower) dynamic multicast delegateStores just a bool in the owner, signalling whether it’s bound or notThere’s a global static manager that stores:Delegateowner astdelegate OwningType,DelegateName pairOffset to delegateMichele Mischitelli11

Asynchronous executionSynchronization primitives, containers andparallelizationMichele Mischitelli12

Synchronization primitivesAtomicsLocking FPlatformAtomicsSignallingWaiting What are atomics? InterlockedAdd Operations that allow lockless concurrent programming InterlockedCompare{Exchange,Pointer} Atomic operations are indivisible Interlocked{Decrement,Increment} Are also free of data races InterlockedExchange[Ptr] Interlocked{And,Or,Xor}class FThreadSafeCounter{volatile int32 m Counter;public:int32 Add(int32 value) {return FPlatformAtomics::InterlockedAdd(&m Counter, value);}};Michele Mischitelli13

Synchronization primitivesAtomicsLockingSignallingWaiting Critical Sections FCriticalSection synchronization object (mutex) OS-independent: PThreads (Android, iOS, Mac,Unix), CRITICAL SECTION (Windows, HoloLens) FScopeLock(mutex*) for scope level locking The mutex is released in the scope lock’s destructor Very useful to prevent deadlocks Fast if the lock is not activatedclass FScopeLockTest{bool m Toggle false;FCriticalSection m Mutex;public:// Thread safe togglingvoid Toggle() {FScopeLock lock(m Mutex);m Toggle !m Toggle;}};Michele Mischitelli14

Synchronization primitivesAtomicsLocking FSemaphore Like mutex with signalling mechanism Only implemented for Windows and hardly used Don’t use FEvent is there for you!SignallingWaitingclass FSemaphore{std::mutex mtx;std::condition variable cv;unsigned int count;public:FSemaphore(unsigned int count);void Notify() {std::unique lock std::mutex Lk(mtx); count;cv.notify one();}void Wait(); // Block until counter 0bool TryWait(); // Non-blocking Wait()template class C, class D bool WaitUntil(const time point C,D & p);};Michele Mischitelli15

Synchronization primitivesAtomicsLockingSignallingWaiting FEvent Blocks a thread until triggered or timed out Frequently used to wake up worker threads FScopedEvent Wraps an FEvent that blocks on scope exitvoid SomeFunction{FScopedEvent Event;DoWorkOnAnotherThread(Event.Get());// stalls here until the other thread calls Event.Trigger();}Michele Mischitelli16

High level constructsContainers General thread-safety info Most containers (TArray, TMap, etc.) are not threadsafeUse synchronization primitives if neededHelpers ABA Problem (lock-free data structs) Process P1 reads value A from shared memory P1 is put on hold while P2 is allowed to run P2 modified the shared memory A to B and then backto A before P2 is put on hold P1 continues execution without knowing that thememory has changed TLockFreePointerList Lock free, stack based and ABA resistant Used by Task Graph system TQueue Lock vs contention Lock is one of the possible scenarios that causecontention Uses a linked list under the hood Lock and contention free for Single-Producer, SingleConsumer (SPSC) Contention can happen on lock-free resources aswell: two threads atomically accessing some variable Lock free for MPSC The result is that one thread runs slower than theother oneMichele Mischitelli17

High level constructsContainersHelpers FThreadSafe Counter, Counter64, Int32, Int64, Bool TThreadSingleton Creates only one instance for each thread FMemStack Fast, temporary per-thread memory allocation TLockFreeClassAllocator, TLockFreeFixedSizeAllocator Thread safe, lock free pooling allocator of memory for instances of T FThreadIdleStats Measures how often a thread is idleMichele Mischitelli18

ParallelizationThreadsTask Graph FRunnableProcessesMessaging Game Thread Platform-agnostic interface All game code, Blueprints and UI Override just 4 methods: Init, Run, Stop and Exit UObjects are not thread-safe Launch with FRunnableThread::Create() AsyncPool (Global) Execute a given function on the specified thread pool AsyncThread (Global) Render Thread Proxy objects for materials, primitives run in this one Stats Thread Engine performance countersExecute a given function using a separate threadMichele Mischitelli19

ParallelizationThreadsTask Graph Task based multithreading Small units of work are pushed to available workerthreads Tasks can have dependencies to each other Task Graph will figure out order of execution Used internally for a lot of things: Animations, message dispatch, object reachabilityanalysis in GC, render and physics subsystems ProcessesMessaging AsyncTask (Global) Execute a given function on the task graph ParallelFor General purpose parallel for that uses the task graphParallelFor(num, [](int32 idx){.}, bForceSingleThread);FConstructor taskCtor TGraphTask TAsyncGraphTask ResultType ady(args ); // This or mp(func), MoveTemp(future));// Or, for something a little bit ormalTask, [](){ . });Michele Mischitelli20

ParallelizationThreadsTask Graph FPlatformProcessProcessesMessaging FMonitoredProcess CreateProc() executes an external program Convenience class for keeping track of some process LaunchURL() launches the default program for a URL IsProcRunning() checks whether a process isrunningEven delegates for cancellation, competition andoutput And many more utils for process managementFMonitoredProcess Process(*Executable, *Arguments, true/*hidden*/, true/*piped out*/);Process.OnOutput().BindLambda([](){ . });Process.Launch();while(Process.Update()) {.}Michele Mischitelli21

ParallelizationThreadsTask Graph Unreal Message Bus (UMB)ProcessesMessaging IMessageTransport Zero configuration intra/inter-process communication Seamlessly connect processes across machines Request-Reply and Publish-Subscribe patterns Messages are simple UStructsCan use this interface to implement custom networkprotocols or API Notable classes: FMessageBus, FMessageRouter,FMessageEndpoint Implemented for TCP and UDP for the moment FGenericPlatformNamedPipe Yeah, named pipes.auto Endpoint hall(this, andling(FOnBusNotification::CreateRaw(this, &FMyEndpoint::OnNotify));Endpoint- Subscribe(MessageTypeFName, EMessageScope::Thread EMessageScope::Network);Endpoint- Send(.);Michele Mischitelli22

SubsystemsArchitectural pattern to better organize codeMichele Mischitelli23

Subsystems introAutomaticallyinstanced Instantiated,initialized anddestroyed by theengine No need to wireup systems tospawn and trackthis objectManagedlifetime Five differentones to choosefrom Multiple instancesof the sameobject if it makessense for thechosen lifetimeWHY ?! Architecturalpattern Improvedmodularity Especially usefulin plugins Save bothprogrammingtime AND lines ofcodeMichele Mischitelli24

Subsystem lifetimes / typesThe base class you derive from determines also the lifetime of your subsystem Game-centric Subsystems UGameInstanceSubsystem: lives before the world. Persists when changing levels (maps) in the game ULocalPlayerSubsystem: each player active on the current client is represented by an instance of ULocalPlayer UWorldSubsystem: a world can be a single persistent level with a list of streaming levels or composition of worlds Advanced Subsystems UEngineSubsystem lPlayerUEngine(Start)Michele Mischitelli25

Subsystem exampleMichele Mischitelli26

Thank itelli.github.ioMichele Mischitelli

Unreal Engine 4: Delegates, Async and Subsystems A follow up session on UE4’s async execution model. Michele Mischitelli 2 Main topics of this meetup Delegates Data types that reference and execute member functions on C objects Asynchronous execution Strategies and classes that allow devs to run asynchronous code using the UE4 framework Subsystems Automatically instantiated classes with .