UDocumentation UE5.7 10.02.2026 (Source)
API documentation for Unreal Engine 5.7
Threading.h
Go to the documentation of this file.
1// Copyright Epic Games, Inc. All Rights Reserved.
2
3#pragma once
4
6#include "CoreMinimal.h"
9#include "PhysicsCoreTypes.h"
10#include "ChaosLog.h"
12#include "Async/ParallelFor.h"
13#include <atomic>
14#include "HAL/CriticalSection.h"
15#include "AutoRTFM.h"
18
19#ifndef PHYSICS_THREAD_CONTEXT
20 #if (!UE_BUILD_SHIPPING && !UE_BUILD_TEST)
21 #define PHYSICS_THREAD_CONTEXT 1
22 #else
23 #define PHYSICS_THREAD_CONTEXT 0
24 #endif
25#endif
26
27
28
33#define CHAOS_SCENE_LOCK_SCENE_GUARD 0 // Unfair RW lock
34#define CHAOS_SCENE_LOCK_RWFIFO_SPINLOCK 1 // Fair RW spinlock, non yielding
35#define CHAOS_SCENE_LOCK_RWFIFO_CRITICALSECTION 2 // Fair RW spinlock, yielding
36#define CHAOS_SCENE_LOCK_FRWLOCK 3 // Recurrant RW lock based on FRwLock (uses platform sync primitives)
37#define CHAOS_SCENE_LOCK_SIMPLE_MUTEX 4 // Just a critical section (not an RWLock). Provided For profiling/debugging only. Not recommended
38
40#if WITH_EDITOR
41 #ifndef CHAOS_SCENE_LOCK_TYPE
42 #define CHAOS_SCENE_LOCK_TYPE CHAOS_SCENE_LOCK_RWFIFO_CRITICALSECTION
43 #endif
44#else
45 #ifndef CHAOS_SCENE_LOCK_TYPE
46 #define CHAOS_SCENE_LOCK_TYPE CHAOS_SCENE_LOCK_FRWLOCK
47 #endif
48#endif
49
50
57#ifndef CHAOS_SCENE_LOCK_CHECKS
58 #if (!UE_BUILD_SHIPPING && !UE_BUILD_TEST)
59 #define CHAOS_SCENE_LOCK_CHECKS 0
60 #else
61 #define CHAOS_SCENE_LOCK_CHECKS 0
62 #endif
63#endif
64
65#if PHYSICS_THREAD_CONTEXT
66namespace Chaos { class FPhysicsThreadContext; }
68#endif
69
70
71namespace Chaos
72{
73 // Not intended for external callers, provided here to allow the locks below to record depths
74 namespace ThreadingPrivate
75 {
76 // Control the current thread read/write depths
77 CHAOS_API void IncReadDepth(void* Instance);
78 CHAOS_API void IncWriteDepth(void* Instance);
79 CHAOS_API void DecReadDepth(void* Instance);
80 CHAOS_API void DecWriteDepth(void* Instance);
81
82 // Get the calling thread's current read depth
83 CHAOS_API uint32 GetThreadReadDepth(void* Instance);
84 }
85
86#if CHAOS_SCENE_LOCK_CHECKS
87
88 // Not intended for external callers, provided here to allow the below check macros to function
89 namespace ThreadingPrivate
90 {
91 // Checks assumptions for functions marked _AssumesLocked in the interface.
94 }
95
97 #define CHAOS_CHECK_READ_ASSUMPTION Chaos::ThreadingPrivate::CheckLockReadAssumption(ANSI_TO_TCHAR(__FUNCTION__))
98
100 #define CHAOS_CHECK_WRITE_ASSUMPTION Chaos::ThreadingPrivate::CheckLockWriteAssumption(ANSI_TO_TCHAR(__FUNCTION__))
101
109 #define CHAOS_CHECK_READ_ASSUMPTION_ACTOR(Actor) if(Actor && Actor->GetSolverBase()) {Chaos::ThreadingPrivate::CheckLockReadAssumption(ANSI_TO_TCHAR(__FUNCTION__));}
110
118 #define CHAOS_CHECK_WRITE_ASSUMPTION_ACTOR(Actor) if(Actor && Actor->GetSolverBase()) {Chaos::ThreadingPrivate::CheckLockWriteAssumption(ANSI_TO_TCHAR(__FUNCTION__));}
119
125 #define CHAOS_CHECK_READ_ASSUMPTION_CONSTRAINT(Handle) \
126 if(Handle.Constraint && \
127 ((Handle.Constraint->GetParticleProxies()[0] && Handle.Constraint->GetParticleProxies()[0]->GetSolverBase()) || \
128 (Handle.Constraint->GetParticleProxies()[1] && Handle.Constraint->GetParticleProxies()[1]->GetSolverBase()))) \
129 {Chaos::ThreadingPrivate::CheckLockReadAssumption(ANSI_TO_TCHAR(__FUNCTION__));}
130
136 #define CHAOS_CHECK_WRITE_ASSUMPTION_CONSTRAINT(Handle) \
137 if(Handle.Constraint && \
138 ((Handle.Constraint->GetParticleProxies()[0] && Handle.Constraint->GetParticleProxies()[0]->GetSolverBase()) || \
139 (Handle.Constraint->GetParticleProxies()[1] && Handle.Constraint->GetParticleProxies()[1]->GetSolverBase()))) \
140 {Chaos::ThreadingPrivate::CheckLockWriteAssumption(ANSI_TO_TCHAR(__FUNCTION__));}
141
142#else
143
144 // Empty when not compiled in
145 #define CHAOS_CHECK_READ_ASSUMPTION
146 #define CHAOS_CHECK_WRITE_ASSUMPTION
147 #define CHAOS_CHECK_READ_ASSUMPTION_ACTOR
148 #define CHAOS_CHECK_WRITE_ASSUMPTION_ACTOR
149 #define CHAOS_CHECK_READ_ASSUMPTION_CONSTRAINT
150 #define CHAOS_CHECK_WRITE_ASSUMPTION_CONSTRAINT
151
152#endif
153
155#define CHAOS_RECORD_ENTER_READ_LOCK Chaos::ThreadingPrivate::IncReadDepth(this);
156
158#define CHAOS_RECORD_ENTER_WRITE_LOCK Chaos::ThreadingPrivate::IncWriteDepth(this);
159
161#define CHAOS_RECORD_LEAVE_READ_LOCK Chaos::ThreadingPrivate::DecReadDepth(this);
162
164#define CHAOS_RECORD_LEAVE_WRITE_LOCK Chaos::ThreadingPrivate::DecWriteDepth(this);
165
166#if PHYSICS_THREAD_CONTEXT
167
169class FPhysicsThreadContext : public TThreadSingleton<FPhysicsThreadContext>
170{
171public:
173 {
174 return PhysicsSimContext > 0;
175 }
176
178 {
179 return (IsInGameThread() || GameThreadContext > 0) && !bFrozenGameThread;
180 }
181
183 {
184 ++PhysicsSimContext;
185 }
186
188 {
189 check(PhysicsSimContext > 0); //double delete?
190 --PhysicsSimContext;
191 }
192
194 {
195 ++GameThreadContext;
196 }
197
199 {
200 check(GameThreadContext > 0); //double delete?
201 --GameThreadContext;
202 }
203
205 {
206 ensure(!bFrozenGameThread);
207 bFrozenGameThread = true;
208 }
209
211 {
212 ensure(bFrozenGameThread);
213 bFrozenGameThread = false;
214 }
215
216private:
217 int32 PhysicsSimContext = 0;
218 int32 GameThreadContext = 0;
219 bool bFrozenGameThread = false;
220};
221
243
265
267{
269 {
270 FPhysicsThreadContext::Get().FreezeGameThreadContext();
271 }
272
274 {
275 FPhysicsThreadContext::Get().UnFreezeGameThreadContext();
276 }
277};
278
279
284
289
294
299#else
301{
302}
303
305{
306}
307#endif
308
309
311
324 {
325 public:
327 {
328 TlsSlot = FPlatformTLS::AllocTlsSlot();
329 CurrentWriterThreadId.Store(0);
330 }
331
333 {
335 {
336 // Validate the lock as it shuts down
337#if CHAOS_CHECKED
338 ensureMsgf(CurrentWriterThreadId.Load() == 0, TEXT("Shutting down a physics scene guard but thread %u still holds a write lock"), CurrentWriterThreadId.Load());
339#endif
341 }
342 }
343
348
349 void ReadLock()
350 {
351 const FSceneLockTls ThreadData = ModifyTls([](FSceneLockTls& ThreadDataInner) {ThreadDataInner.ReadDepth++; });
352
354
355 // If we're already writing then don't attempt the lock, we already have exclusive access
356 if(CurrentWriterThreadId.Load() != ThisThreadId && ThreadData.ReadDepth == 1)
357 {
358 InnerLock.ReadLock();
359 }
360
361#if PHYSICS_THREAD_CONTEXT
362 //read lock means we can access game thread data, so set the right context
363 FPhysicsThreadContext::Get().IncGameThreadContext();
364#endif
365 }
366
368 {
369 ModifyTls([](FSceneLockTls& ThreadDataInner) {ThreadDataInner.WriteDepth++; });
370
372
373 if(CurrentWriterThreadId.Load() != ThisThreadId)
374 {
375 InnerLock.WriteLock();
376 CurrentWriterThreadId.Store(ThisThreadId);
377 }
378
379#if PHYSICS_THREAD_CONTEXT
380 //write lock means we can access game thread data, so set the right context
381 FPhysicsThreadContext::Get().IncGameThreadContext();
382#endif
383 }
384
386 {
387 const FSceneLockTls ThreadData = ModifyTls([](FSceneLockTls& ThreadDataInner)
388 {
389 if(ThreadDataInner.ReadDepth > 0)
390 {
391 ThreadDataInner.ReadDepth--;
392 }
393 else
394 {
395#if CHAOS_CHECKED
396 ensureMsgf(false, TEXT("ReadUnlock called on physics scene guard when the thread does not hold the lock"));
397#else
398 UE_LOG(LogChaos, Warning, TEXT("ReadUnlock called on physics scene guard when the thread does not hold the lock"))
399#endif
400 }
401
402 });
403
405
406 if(CurrentWriterThreadId.Load() != ThisThreadId && ThreadData.ReadDepth == 0)
407 {
408 InnerLock.ReadUnlock();
409 }
410
411#if PHYSICS_THREAD_CONTEXT
412 //read lock is released, the gamethread context is gone
413 FPhysicsThreadContext::Get().DecGameThreadContext();
414#endif
415 }
416
418 {
420
421 if(CurrentWriterThreadId.Load() == ThisThreadId)
422 {
423 const FSceneLockTls ThreadData = ModifyTls([](FSceneLockTls& ThreadDataInner) {ThreadDataInner.WriteDepth--; });
424
425 if(ThreadData.WriteDepth == 0)
426 {
427 CurrentWriterThreadId.Store(0);
428 InnerLock.WriteUnlock();
429 }
430 }
431 else
432 {
433#if CHAOS_CHECKED
434 ensureMsgf(false, TEXT("WriteUnlock called on physics scene guard when the thread does not hold the lock"));
435#else
436 UE_LOG(LogChaos, Warning, TEXT("ReadUnlock called on physics scene guard when the thread does not hold the lock"))
437#endif
438 }
439
440#if PHYSICS_THREAD_CONTEXT
441 //write lock is released, the gamethread context is gone
442 FPhysicsThreadContext::Get().DecGameThreadContext();
443#endif
444 }
445
446 private:
447
448 // We use 32 bits to store our depths (16 read and 16 write) allowing a maximum
449 // recursive lock of depth 65,536. This unions to whatever the platform ptr size
450 // is so we can store this directly into TLS without allocating more storage
451 class FSceneLockTls
452 {
453 public:
454
455 FSceneLockTls()
456 : WriteDepth(0)
457 , ReadDepth(0)
458 {}
459
460 union
461 {
462 struct
463 {
464 uint16 WriteDepth;
465 uint16 ReadDepth;
466 };
467 void* PtrDummy;
468 };
469
470 };
471
472 // Helper for modifying the current TLS data
473 template<typename CallableType>
474 const FSceneLockTls ModifyTls(CallableType Callable)
475 {
477
478 void* ThreadData = FPlatformTLS::GetTlsValue(TlsSlot);
479
480 FSceneLockTls TlsData;
481 TlsData.PtrDummy = ThreadData;
482
483 Callable(TlsData);
484
485 FPlatformTLS::SetTlsValue(TlsSlot, TlsData.PtrDummy);
486
487 return TlsData;
488 }
489
490 uint32 TlsSlot;
491 TAtomic<uint32> CurrentWriterThreadId;
492 FRWLock InnerLock;
493 };
494
498 template<typename MutexType>
500 {
501 public:
503 : Mutex(InMutex)
504 {
505 Mutex.Lock();
506 }
507
509 {
510 Mutex.Unlock();
511 }
512
513 private:
514
515 // No default, copy or move construction
516 TMutexScopeLock() = delete;
517 TMutexScopeLock(const TMutexScopeLock&) = delete;
519 TMutexScopeLock& operator=(const TMutexScopeLock&) = delete;
520 TMutexScopeLock& operator=(TMutexScopeLock&&) = delete;
521
522 MutexType& Mutex;
523 };
524
535 template<typename MutexType>
537 {
538 public:
539
541 : NumReaders(0)
542 {
543 //ThreadingPrivate::CreateLockThreadData(this);
544 }
545
547 {
548 //ThreadingPrivate::DestroyLockThreadData(this);
549 }
550
551 void ReadLock()
552 {
553 TRACE_CHAOS_BEGIN_LOCK(Chaos::Insights::ELockEventType::RWLockReadLock);
554 if(ThreadingPrivate::GetThreadReadDepth(this) == 0)
555 {
557
558 // We lock for this increment to halt if there's a writer waiting to enter the lock
559 // In this case we will be forced to wait till the write completes
560 ++NumReaders;
561 }
562 else
563 {
564 // Only require a lock on the first acquisition. this allows recursive reads even while a
565 // writer is holding the lock waiting to enter. The writer will be allowed to proceed when
566 // all write scopes end
567 ++NumReaders;
568 }
569
570#if PHYSICS_THREAD_CONTEXT
571 // Read lock means we can access game thread data, so set the right context
572 FPhysicsThreadContext::Get().IncGameThreadContext();
573#endif
574
576
578 }
579
581 {
582 TRACE_CHAOS_BEGIN_LOCK(Chaos::Insights::ELockEventType::RWLockWriteLock);
583
584#if CHAOS_SCENE_LOCK_CHECKS
585 if(ThreadingPrivate::GetThreadReadDepth(this) > 0)
586 {
587 ensureMsgf(false, TEXT("A thread holding a read lock on the physics scene attempted to upgrade to a write lock - this is not supported, performing an unsafe write."));
588
589 // Still need to increment the context when we hit this case or we'll just crash later
590#if PHYSICS_THREAD_CONTEXT
591 // Write lock means we can access game thread data, so set the right context
592 FPhysicsThreadContext::Get().IncGameThreadContext();
593#endif
594
595 return;
596 }
597#endif
598
599 Mutex.Lock();
600
601 // Spin until all readers are finished
602 for(;;)
603 {
604 if(NumReaders.load() == 0)
605 {
606 // All readers now finished - writer can enter the lock properly (pass back to caller)
607 break;
608 }
609
610 // Issue pause instruction - architecture dependent instruction to better handle
611 // a spin lock not interfering with other threads on this core, this doesn't
612 // actually yield the thread
614 }
615
617
618#if PHYSICS_THREAD_CONTEXT
619 // Write lock means we can access game thread data, so set the right context
620 FPhysicsThreadContext::Get().IncGameThreadContext();
621#endif
623 }
624
626 {
628
629 // No locking here, just decrement atomic reader count
630 --NumReaders;
631
632#if PHYSICS_THREAD_CONTEXT
633 // Read lock is released, the gamethread context is gone
634 FPhysicsThreadContext::Get().DecGameThreadContext();
635#endif
636
638 }
639
641 {
643
644 Mutex.Unlock();
645#if PHYSICS_THREAD_CONTEXT
646 // Write lock is released, the gamethread context is gone
647 FPhysicsThreadContext::Get().DecGameThreadContext();
648#endif
649
651 }
652
653 private:
654 MutexType Mutex;
655 std::atomic<uint32> NumReaders;
656 };
657
666 {
667 public:
669 : Next(0)
670 , Current(0)
671 , WriterId(0)
672 , Count()
673 {}
674
675 void Lock()
676 {
677 // Support recursive locking
678 if(WriterId.load() == FPlatformTLS::GetCurrentThreadId())
679 {
680 Count++;
681 return;
682 }
683
684 // Get the wait value - acquire operation so Current.load can't be reordered before this
685 uint32 WaitFor = Next.fetch_add(1, std::memory_order_acquire);
686 for(;;)
687 {
688 if(WaitFor == Current.load())
689 {
690 break;
691 }
692
693 // Issue pause instruction - architecture dependent instruction to better handle
694 // a spin lock not interfering with other threads on this core, this doesn't
695 // actually yield the thread
697 }
698
699 // Lock acquired, store the thread ID for recursive locking
700 WriterId.store(FPlatformTLS::GetCurrentThreadId());
701 Count++;
702 }
703
704 void Unlock()
705 {
706 checkf(WriterId.load() == FPlatformTLS::GetCurrentThreadId(), TEXT("A thread unlocked without owning the lock (calling Lock first)"));
707 checkf(Count > 0, TEXT("A thread unlocked a lock that had no outstanding lock scopes"));
708
709 // Once all recursive locks are dropped, increment Current to allow the next thread in
710 if(--Count == 0)
711 {
712 // Clear the lock owner
713 WriterId.store(0);
714
715 // Release the next thread - this must be the last operation as immediately
716 // the next user of the lock will be allowed to take ownership
717 ++Current;
718 }
719 }
720
721 private:
722 std::atomic<uint32> Next;
723 std::atomic<uint32> Current;
724 std::atomic<uint32> WriterId;
725 uint32 Count;
726 };
727
728
733 {
734 struct FRwLockInfo
735 {
736 FRwLockInfo(void* TlsSlotValue)
737 {
738 ThreadReadDepth = uint32(uint64(TlsSlotValue));
739 ThreadWriteDepth = uint64(TlsSlotValue) >> 32;
740 }
741 void* GetTlsSlotValue()
742 {
743 uint64 ValueOut = uint64(ThreadReadDepth) | (uint64(ThreadWriteDepth) << 32);
744 return (void*)ValueOut;
745 }
746
747 uint32 ThreadReadDepth = 0;
748 uint32 ThreadWriteDepth = 0;
749 };
750
751 public:
752
758
764
765 void ReadLock()
766 {
767 TRACE_CHAOS_BEGIN_LOCK(Chaos::Insights::ELockEventType::RWLockReadLock)
768
769 FRwLockInfo ThreadInfo(FPlatformTLS::GetTlsValue(TlsSlot));
770 ThreadInfo.ThreadReadDepth++;
771 FPlatformTLS::SetTlsValue(TlsSlot, ThreadInfo.GetTlsSlotValue());
772
773 if (ThreadInfo.ThreadReadDepth + ThreadInfo.ThreadWriteDepth == 1)
774 {
775 RwLock.ReadLock();
776 }
777
778#if PHYSICS_THREAD_CONTEXT
779 // Read lock means we can access game thread data, so set the right context
780 FPhysicsThreadContext::Get().IncGameThreadContext();
781#endif
783 }
785 {
786 TRACE_CHAOS_BEGIN_LOCK(Chaos::Insights::ELockEventType::RWLockWriteLock)
787
788 FRwLockInfo ThreadInfo(FPlatformTLS::GetTlsValue(TlsSlot));
789 ThreadInfo.ThreadWriteDepth++;
790 FPlatformTLS::SetTlsValue(TlsSlot, ThreadInfo.GetTlsSlotValue());
791
792#if CHAOS_SCENE_LOCK_CHECKS
793 if (ThreadInfo.ThreadReadDepth > 0)
794 {
795 UE_LOG(LogChaos, Warning, TEXT("Attempt to upgrade a read lock to a write lock. This is not supported. Writes will be unsafe"))
796 }
797#endif
798 if (ThreadInfo.ThreadReadDepth + ThreadInfo.ThreadWriteDepth == 1)
799 {
800 RwLock.WriteLock();
801 }
802#if PHYSICS_THREAD_CONTEXT
803 // Write lock means we can access game thread data, so set the right context
804 FPhysicsThreadContext::Get().IncGameThreadContext();
805#endif
807 }
809 {
810 FRwLockInfo ThreadInfo(FPlatformTLS::GetTlsValue(TlsSlot));
811 ThreadInfo.ThreadReadDepth--;
812 FPlatformTLS::SetTlsValue(TlsSlot, ThreadInfo.GetTlsSlotValue());
813
814 if (ThreadInfo.ThreadWriteDepth + ThreadInfo.ThreadReadDepth == 0)
815 {
816 RwLock.ReadUnlock();
817 }
818
819#if PHYSICS_THREAD_CONTEXT
820 // Read lock is released, the gamethread context is gone
821 FPhysicsThreadContext::Get().DecGameThreadContext();
822#endif
824 }
826 {
827 FRwLockInfo ThreadInfo(FPlatformTLS::GetTlsValue(TlsSlot));
828 ThreadInfo.ThreadWriteDepth--;
829 FPlatformTLS::SetTlsValue(TlsSlot, ThreadInfo.GetTlsSlotValue());
830 if (ThreadInfo.ThreadWriteDepth + ThreadInfo.ThreadReadDepth == 0)
831 {
832 RwLock.WriteUnlock();
833 }
834
835#if PHYSICS_THREAD_CONTEXT
836 // Write lock is released, the gamethread context is gone
837 FPhysicsThreadContext::Get().DecGameThreadContext();
838#endif
840 }
841
842 private:
843 FRWLock RwLock;
844 uint32 TlsSlot;
845 };
846
847
852 {
853 public:
857
861
862 void ReadLock()
863 {
864 Cs.Lock();
865#if PHYSICS_THREAD_CONTEXT
866 // Read lock means we can access game thread data, so set the right context
867 FPhysicsThreadContext::Get().IncGameThreadContext();
868#endif
869 }
871 {
872 Cs.Lock();
873#if PHYSICS_THREAD_CONTEXT
874 // Write lock means we can access game thread data, so set the right context
875 FPhysicsThreadContext::Get().IncGameThreadContext();
876#endif
877 }
879 {
880 Cs.Unlock();
881
882#if PHYSICS_THREAD_CONTEXT
883 // Read lock is released, the gamethread context is gone
884 FPhysicsThreadContext::Get().DecGameThreadContext();
885#endif
886 }
888 {
889 Cs.Unlock();
890
891#if PHYSICS_THREAD_CONTEXT
892 // Write lock is released, the gamethread context is gone
893 FPhysicsThreadContext::Get().DecGameThreadContext();
894#endif
895 }
896
897 private:
899 };
900
901
905 template<typename MutexType>
907 {
908 public:
910 : Mutex(InMutex)
911 {
913 Mutex.WriteLock();
914 }
915
917 {
918 Mutex.WriteUnlock();
919 }
920
921 private:
927
928 MutexType& Mutex;
929 };
930
934 template<typename MutexType>
936 {
937 public:
939 : Mutex(InMutex)
940 {
942 Mutex.ReadLock();
943 }
944
946 {
947 Mutex.ReadUnlock();
948 }
949
950 private:
956
957 MutexType& Mutex;
958 };
959
960#if CHAOS_SCENE_LOCK_TYPE == CHAOS_SCENE_LOCK_SCENE_GUARD
961 using FPhysSceneLockNonTransactional = FPhysicsSceneGuard;
962#elif CHAOS_SCENE_LOCK_TYPE == CHAOS_SCENE_LOCK_RWFIFO_SPINLOCK
964#elif CHAOS_SCENE_LOCK_TYPE == CHAOS_SCENE_LOCK_RWFIFO_CRITICALSECTION
966#elif CHAOS_SCENE_LOCK_TYPE == CHAOS_SCENE_LOCK_FRWLOCK
968#elif CHAOS_SCENE_LOCK_TYPE == CHAOS_SCENE_LOCK_SIMPLE_MUTEX
970#endif
971
972#if UE_AUTORTFM
973 // A transactionally safe lock that works in the following novel ways:
974 // - In the open (non-transactional):
975 // - Take the lock like before. Simple!
976 // - Free the lock like before too.
977 // - In the closed (transactional):
978 // - During locking we query `TransactionalLockCount`:
979 // - 0 means we haven't taken the lock within our transaction nest and need to acquire the lock.
980 // - Otherwise we already have the lock (and are preventing non-transactional code seeing any
981 // modifications we've made while holding the lock), so just bump `TransactionalLockCount`.
982 // - We also register an on-abort handler to release the lock should we abort (but we need to
983 // query `TransactionalLockCount` even there because we could be aborting an inner transaction
984 // and the parent transaction still wants to have the lock held!).
985 // - During unlocking we defer doing the unlock until the transaction commits.
986 //
987 // Thus with this approach we will hold this lock for the *entirety* of the transactional nest should
988 // we take the lock during the transaction, thus preventing non-transactional code from seeing any
989 // modifications we should make.
990 //
991 // If we are within a transaction, we pessimise our read-lock to a write-lock. Note: that it should
992 // potentially be possible to have read-locks work correctly, but serious care will have to be taken to
993 // ensure that we don't have:
994 // Open Thread Closed Thread
995 // ----------- ReadLock
996 // ----------- ReadUnlock
997 // WriteLock -------------
998 // WriteUnlock -------------
999 // ----------- ReadLock <- Invalid because the transaction can potentially observe side
1000 // effects of the open-threads writes!
1002 {
1003 // Always open because the constructor arguments will create the underlying lock.
1006 {
1007 if (AutoRTFM::IsTransactional())
1008 {
1009 const AutoRTFM::EContextStatus Status = AutoRTFM::Close([this]
1010 {
1011 AutoRTFM::PushOnAbortHandler(this, [this]
1012 {
1014 });
1015 });
1016
1017 ensure(AutoRTFM::EContextStatus::OnTrack == Status);
1018 }
1019 }
1020
1022 {
1023 if (AutoRTFM::IsTransactional())
1024 {
1025 const AutoRTFM::EContextStatus Status = AutoRTFM::Close([this]
1026 {
1027 AutoRTFM::PopOnAbortHandler(this);
1028
1029 // We explicitly copy the state here for the case that `this` was stack
1030 // allocated and has already died before the on-commit is hit.
1031 AutoRTFM::OnCommit([State = AutoRTFM::TOpenWrapper{this->State}]
1032 {
1033 ensure(0 == State.Object->TransactionalLockCount);
1034 });
1035 });
1036
1037 ensure(AutoRTFM::EContextStatus::OnTrack == Status);
1038 }
1039
1040 // As the State was constructed in the open, it must be released in the open.
1041 AutoRTFM::Open([&] { State = nullptr; });
1042 }
1043
1044 void ReadLock()
1045 {
1046 if (AutoRTFM::IsTransactional() || AutoRTFM::IsCommittingOrAborting())
1047 {
1048 // Transactionally pessimise ReadLock -> WriteLock.
1049 WriteLock();
1050 }
1051 else
1052 {
1053 State->Lock.ReadLock();
1054 ensure(0 == State->TransactionalLockCount);
1055 }
1056 }
1057
1058 void ReadUnlock()
1059 {
1060 if (AutoRTFM::IsTransactional() || AutoRTFM::IsCommittingOrAborting())
1061 {
1062 // Transactionally pessimise ReadUnlock -> WriteUnlock.
1063 WriteUnlock();
1064 }
1065 else
1066 {
1067 ensure(0 == State->TransactionalLockCount);
1068 State->Lock.ReadUnlock();
1069 }
1070 }
1071
1072 void WriteLock()
1073 {
1074 if (AutoRTFM::IsTransactional() || AutoRTFM::IsCommittingOrAborting())
1075 {
1077 {
1078 // The transactional system which can increment TransactionalLockCount
1079 // is always single-threaded, thus this is safe to check without atomicity.
1080 if (0 == State->TransactionalLockCount)
1081 {
1082 State->Lock.WriteLock();
1083 }
1084
1085 State->TransactionalLockCount += 1;
1086 };
1087
1088 // We explicitly copy the state here for the case that `this` was stack
1089 // allocated and has already died before the on-abort is hit.
1090 AutoRTFM::OnAbort([State = AutoRTFM::TOpenWrapper{this->State}]
1091 {
1092 ensure(0 != State.Object->TransactionalLockCount);
1093
1094 State.Object->TransactionalLockCount -= 1;
1095
1096 if (0 == State.Object->TransactionalLockCount)
1097 {
1098 State.Object->Lock.WriteUnlock();
1099 }
1100 });
1101 }
1102 else
1103 {
1104 State->Lock.WriteLock();
1105 ensure(0 == State->TransactionalLockCount);
1106 }
1107 }
1108
1109 void WriteUnlock()
1110 {
1111 if (AutoRTFM::IsTransactional() || AutoRTFM::IsCommittingOrAborting())
1112 {
1113 // We explicitly copy the state here for the case that `this` was stack
1114 // allocated and has already died before the on-commit is hit.
1115 AutoRTFM::OnCommit([State = AutoRTFM::TOpenWrapper{this->State}]
1116 {
1117 ensure(0 != State.Object->TransactionalLockCount);
1118
1119 State.Object->TransactionalLockCount -= 1;
1120
1121 if (0 == State.Object->TransactionalLockCount)
1122 {
1123 State.Object->Lock.WriteUnlock();
1124 }
1125 });
1126 }
1127 else
1128 {
1129 ensure(0 == State->TransactionalLockCount);
1130 State->Lock.WriteUnlock();
1131 }
1132 }
1133
1134 private:
1136
1137 struct FState final
1138 {
1141 };
1142
1143 TSharedPtr<FState> State;
1144 };
1145
1146#if UE_WITH_REMOTE_OBJECT_HANDLE
1147 // With remote object support, we wrap the underlying FPhysSceneLockTransactionallySafe
1148 // in an additional layer and expose callbacks to register additional logic to execute
1149
1151
1153 {
1154 void (*ReadLock)(FPhysSceneLockRemoteObject*) = nullptr;
1155 void (*ReadUnlock)(FPhysSceneLockRemoteObject*) = nullptr;
1156 void (*WriteLock)(FPhysSceneLockRemoteObject*) = nullptr;
1157 void (*WriteUnlock)(FPhysSceneLockRemoteObject*) = nullptr;
1158 };
1159
1161
1163 {
1164 void ReadLock()
1165 {
1168 else
1169 UnderlyingLock.ReadLock();
1170 }
1171
1172 void ReadUnlock()
1173 {
1175 GPhysSceneLockRemoteObjectCallbacks.ReadUnlock(this);
1176 else
1177 UnderlyingLock.ReadUnlock();
1178 }
1179
1180 void WriteLock()
1181 {
1184 else
1185 UnderlyingLock.WriteLock();
1186 }
1187
1188 void WriteUnlock()
1189 {
1191 GPhysSceneLockRemoteObjectCallbacks.WriteUnlock(this);
1192 else
1193 UnderlyingLock.WriteUnlock();
1194 }
1195
1197 };
1198
1200#else
1202#endif
1203#else
1205#endif
1206
1207 // Stable types to use in calling code configured by the compiler switches above
1210}
OODEFFUNC typedef void(OODLE_CALLBACK t_fp_OodleCore_Plugin_Free)(void *ptr)
#define FORCEINLINE
Definition AndroidPlatform.h:140
#define checkSlow(expr)
Definition AssertionMacros.h:332
#define check(expr)
Definition AssertionMacros.h:314
#define ensureMsgf( InExpression, InFormat,...)
Definition AssertionMacros.h:465
#define ensure( InExpression)
Definition AssertionMacros.h:464
#define checkf(expr, format,...)
Definition AssertionMacros.h:315
#define UE_AUTORTFM_ALWAYS_OPEN
Definition AutoRTFMDefines.h:114
#define TRACE_CHAOS_ACQUIRE_LOCK(...)
Definition ChaosInsightsMacros.h:30
#define TRACE_CHAOS_BEGIN_LOCK(...)
Definition ChaosInsightsMacros.h:29
#define TRACE_CHAOS_END_LOCK(...)
Definition ChaosInsightsMacros.h:31
#define UE_NONCOPYABLE(TypeName)
Definition CoreMiscDefines.h:457
#define TEXT(x)
Definition Platform.h:1272
FPlatformTypes::TCHAR TCHAR
Either ANSICHAR or WIDECHAR, depending on whether the platform supports wide characters or the requir...
Definition Platform.h:1135
FPlatformTypes::int32 int32
A 32-bit signed integer.
Definition Platform.h:1125
FPlatformTypes::uint64 uint64
A 64-bit unsigned integer.
Definition Platform.h:1117
UE_FORCEINLINE_HINT TSharedRef< CastToType, Mode > StaticCastSharedRef(TSharedRef< CastFromType, Mode > const &InSharedRef)
Definition SharedPointer.h:127
UE::FPlatformRecursiveMutex FCriticalSection
Definition CriticalSection.h:53
#define CSV_SCOPED_TIMING_STAT(Category, StatName)
Definition CsvProfiler.h:150
#define CHAOS_RECORD_ENTER_WRITE_LOCK
Definition Threading.h:158
#define CHAOS_RECORD_ENTER_READ_LOCK
Definition Threading.h:155
#define CHAOS_RECORD_LEAVE_WRITE_LOCK
Definition Threading.h:164
#define CHAOS_RECORD_LEAVE_READ_LOCK
Definition Threading.h:161
#define UE_LOG(CategoryName, Verbosity, Format,...)
Definition LogMacros.h:270
EChaosThreadingMode
Definition PhysicsCoreTypes.h:21
#define UE_DECLARE_THREAD_SINGLETON_TLS(Type, Api)
Definition ThreadSingleton.h:35
CORE_API bool IsInGameThread()
Definition ThreadingBase.cpp:185
FRWLock Lock
Definition UnversionedPropertySerialization.cpp:921
uint16_t uint16
Definition binka_ue_file_header.h:7
uint32_t uint32
Definition binka_ue_file_header.h:6
Definition OpenWrapper.h:28
Definition Threading.h:666
FPhysSpinLock()
Definition Threading.h:668
void Unlock()
Definition Threading.h:704
void Lock()
Definition Threading.h:675
Definition Threading.h:733
void WriteUnlock()
Definition Threading.h:825
void ReadUnlock()
Definition Threading.h:808
void ReadLock()
Definition Threading.h:765
FPhysicsRwLock()
Definition Threading.h:753
~FPhysicsRwLock()
Definition Threading.h:759
void WriteLock()
Definition Threading.h:784
Definition Threading.h:324
void WriteLock()
Definition Threading.h:367
~FPhysicsSceneGuard()
Definition Threading.h:332
FPhysicsSceneGuard(FPhysicsSceneGuard &&InOther)=delete
void WriteUnlock()
Definition Threading.h:417
FPhysicsSceneGuard(const FPhysicsSceneGuard &InOther)=delete
FPhysicsSceneGuard & operator=(const FPhysicsSceneGuard &InOther)=delete
void ReadUnlock()
Definition Threading.h:385
FPhysicsSceneGuard & operator=(FPhysicsSceneGuard &&InOther)=delete
void ReadLock()
Definition Threading.h:349
FPhysicsSceneGuard()
Definition Threading.h:326
Definition Threading.h:852
void ReadUnlock()
Definition Threading.h:878
void WriteLock()
Definition Threading.h:870
FPhysicsSimpleMutexLock()
Definition Threading.h:854
void WriteUnlock()
Definition Threading.h:887
~FPhysicsSimpleMutexLock()
Definition Threading.h:858
void ReadLock()
Definition Threading.h:862
Definition Threading.h:170
void DecGameThreadContext()
Definition Threading.h:198
void FreezeGameThreadContext()
Definition Threading.h:204
bool IsInPhysicsSimContext() const
Definition Threading.h:172
void IncGameThreadContext()
Definition Threading.h:193
void IncPhysicsSimContext()
Definition Threading.h:182
bool IsInGameThreadContext() const
Definition Threading.h:177
void DecPhysicsSimContext()
Definition Threading.h:187
void UnFreezeGameThreadContext()
Definition Threading.h:210
Definition Threading.h:500
~TMutexScopeLock()
Definition Threading.h:508
TMutexScopeLock(MutexType &InMutex)
Definition Threading.h:502
Definition Threading.h:936
TPhysicsSceneGuardScopedRead(MutexType &InMutex)
Definition Threading.h:938
~TPhysicsSceneGuardScopedRead()
Definition Threading.h:945
Definition Threading.h:907
TPhysicsSceneGuardScopedWrite(MutexType &InMutex)
Definition Threading.h:909
~TPhysicsSceneGuardScopedWrite()
Definition Threading.h:916
Definition Threading.h:537
TRwFifoLock()
Definition Threading.h:540
void WriteUnlock()
Definition Threading.h:640
void WriteLock()
Definition Threading.h:580
~TRwFifoLock()
Definition Threading.h:546
void ReadLock()
Definition Threading.h:551
void ReadUnlock()
Definition Threading.h:625
Definition Atomic.h:538
Definition SharedPointer.h:692
Definition ThreadSingleton.h:44
static FORCEINLINE FPhysicsThreadContext & Get()
Definition ThreadSingleton.h:101
Definition CriticalSection.h:14
UE_REWRITE void WriteLock()
Definition CriticalSection.h:21
UE_REWRITE void ReadUnlock()
Definition CriticalSection.h:41
UE_REWRITE void WriteUnlock()
Definition CriticalSection.h:26
UE_REWRITE void ReadLock()
Definition CriticalSection.h:36
Definition SkeletalMeshComponent.h:307
FORCEINLINE bool IsInPhysicsThreadContext()
Definition Threading.h:280
FORCEINLINE void EnsureIsInPhysicsThreadContext()
Definition Threading.h:290
FORCEINLINE void EnsureIsInGameThreadContext()
Definition Threading.h:295
FPhysicsRwLock FPhysSceneLockNonTransactional
Definition Threading.h:967
FPhysSceneLockNonTransactional FPhysSceneLock
Definition Threading.h:1204
FORCEINLINE bool IsInGameThreadContext()
Definition Threading.h:285
Definition Threading.h:267
~FFrozenGameThreadContextScope()
Definition Threading.h:273
FFrozenGameThreadContextScope()
Definition Threading.h:268
Definition Threading.h:245
bool bParentIsGameThreadContext
Definition Threading.h:263
FGameThreadContextScope(bool InParentIsGameThreadContext)
Definition Threading.h:246
~FGameThreadContextScope()
Definition Threading.h:255
Definition Threading.h:223
FPhysicsThreadContextScope(bool InParentIsPhysicsSimContext)
Definition Threading.h:224
bool bParentIsPhysicsSimContext
Definition Threading.h:241
~FPhysicsThreadContextScope()
Definition Threading.h:233
static uint32 GetCurrentThreadId(void)
Definition AndroidPlatformTLS.h:20
static uint32 AllocTlsSlot(void)
Definition AndroidPlatformTLS.h:30
static UE_FORCEINLINE_HINT void FreeTlsSlot(uint32 SlotIndex)
Definition AndroidPlatformTLS.h:67
static UE_FORCEINLINE_HINT void * GetTlsValue(uint32 SlotIndex)
Definition AndroidPlatformTLS.h:57
static UE_FORCEINLINE_HINT void SetTlsValue(uint32 SlotIndex, void *Value)
Definition AndroidPlatformTLS.h:47
static void Yield()
Definition GenericPlatformProcess.h:950
static UE_FORCEINLINE_HINT bool IsValidTlsSlot(uint32 SlotIndex)
Definition GenericPlatformTLS.h:20