Skip to content

Commit 44f9581

Browse files
@brougkr fix(unreal): Replace FTickableGameObject with FTSTicker (#4835)
# Description of Changes This is a copy of #4006 with only the updates to the way Unreal handles ticks. As per @brougkr findings: - **FTickableGameObject initialization order bug** - Replaced with FTSTicker for reliable tick registration: - Removed FTickableGameObject inheritance - Added FTSTicker::FDelegateHandle for manual tick registration - Added destructor to clean up ticker registration - Added OnTickerTick() method ### Issue FTickableGameObject registers itself in its constructor BEFORE UDbConnectionBase's constructor body runs. Even with ETickableTickType::Never, UE's GENERATED_BODY() macro can interfere with base class initialization order, causing the default constructor to be called instead. # API and ABI breaking changes - Refactor of an underlying component of the SDK and non-breaking. # Expected complexity level and risk 2 - This changes the structure of how the database tooling can auto-tick, but is invisible to the developer # Testing As this changes a core feature I tested all aspects: - [x] Reproduced the bug and confirmed against `master` and this branch to see the fix working - [x] Ran full suite of Unreal tests - [x] Manually tested Unreal Blackholio
1 parent 70db721 commit 44f9581

2 files changed

Lines changed: 72 additions & 50 deletions

File tree

sdks/unreal/src/SpacetimeDbSdk/Source/SpacetimeDbSdk/Private/Connection/DbConnectionBase.cpp

Lines changed: 59 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
#include "Connection/DbConnectionBase.h"
2-
#include "Connection/DbConnectionBuilder.h"
2+
#include "Connection/DbConnectionBuilder.h"
33
#include "Connection/Credentials.h"
44
#include "Connection/LogCategory.h"
5+
#include "Containers/Ticker.h"
56
#include "ModuleBindings/Types/ClientMessageType.g.h"
67
#include "ModuleBindings/Types/SubscriptionErrorType.g.h"
78
#include "Misc/Compression.h"
@@ -65,13 +66,48 @@ static FString DecodeReducerErrorMessage(const TArray<uint8>& ErrorBytes)
6566
}
6667
}
6768

68-
UDbConnectionBase::UDbConnectionBase(const FObjectInitializer& ObjectInitializer)
69-
: Super(ObjectInitializer)
70-
{
71-
NextRequestId = 1;
72-
NextSubscriptionId = 1;
73-
ProcedureCallbacks = CreateDefaultSubobject<UProcedureCallbacks>(TEXT("ProcedureCallbacks"));
74-
}
69+
UDbConnectionBase::UDbConnectionBase(const FObjectInitializer& ObjectInitializer)
70+
: Super(ObjectInitializer)
71+
{
72+
NextRequestId = 1;
73+
NextSubscriptionId = 1;
74+
ProcedureCallbacks = CreateDefaultSubobject<UProcedureCallbacks>(TEXT("ProcedureCallbacks"));
75+
}
76+
77+
void UDbConnectionBase::SetAutoTicking(bool bAutoTick)
78+
{
79+
if (bIsAutoTicking == bAutoTick)
80+
{
81+
return;
82+
}
83+
84+
bIsAutoTicking = bAutoTick;
85+
86+
if (bIsAutoTicking)
87+
{
88+
if (!TickerHandle.IsValid())
89+
{
90+
TickerHandle = FTSTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateUObject(this, &UDbConnectionBase::OnTickerTick));
91+
}
92+
}
93+
else if (TickerHandle.IsValid())
94+
{
95+
FTSTicker::GetCoreTicker().RemoveTicker(TickerHandle);
96+
TickerHandle.Reset();
97+
}
98+
}
99+
100+
void UDbConnectionBase::BeginDestroy()
101+
{
102+
if (TickerHandle.IsValid())
103+
{
104+
FTSTicker::GetCoreTicker().RemoveTicker(TickerHandle);
105+
TickerHandle.Reset();
106+
}
107+
bIsAutoTicking = false;
108+
109+
Super::BeginDestroy();
110+
}
75111

76112
void UDbConnectionBase::Disconnect()
77113
{
@@ -216,8 +252,8 @@ void UDbConnectionBase::HandleWSBinaryMessage(const TArray<uint8>& Message)
216252
});
217253
}
218254

219-
void UDbConnectionBase::FrameTick()
220-
{
255+
void UDbConnectionBase::FrameTick()
256+
{
221257
TArray<FServerMessageType> Local;
222258
{
223259
FScopeLock Lock(&PendingMessagesMutex);
@@ -236,31 +272,19 @@ void UDbConnectionBase::FrameTick()
236272
{
237273
//process the message, this will call DbUpdate or trigger subscription events as needed
238274
ProcessServerMessage(Msg);
239-
}
240-
}
241-
void UDbConnectionBase::Tick(float DeltaTime)
242-
{
243-
if (bIsAutoTicking)
244-
{
245-
FrameTick();
246-
}
247-
}
248-
249-
TStatId UDbConnectionBase::GetStatId() const
250-
{
251-
// This is used by the engine to track tickables, we return a unique stat ID for this class
252-
RETURN_QUICK_DECLARE_CYCLE_STAT(UMyTickableObject, STATGROUP_Tickables);
253-
}
254-
255-
bool UDbConnectionBase::IsTickable() const
256-
{
257-
return bIsAutoTicking;
258-
}
259-
260-
bool UDbConnectionBase::IsTickableInEditor() const
261-
{
262-
return bIsAutoTicking;
263-
}
275+
}
276+
}
277+
278+
bool UDbConnectionBase::OnTickerTick(float DeltaTime)
279+
{
280+
if (HasAnyFlags(RF_BeginDestroyed | RF_FinishDestroyed) || !bIsAutoTicking)
281+
{
282+
return false;
283+
}
284+
285+
FrameTick();
286+
return true;
287+
}
264288

265289

266290
void UDbConnectionBase::ProcessServerMessage(const FServerMessageType& Message)

sdks/unreal/src/SpacetimeDbSdk/Source/SpacetimeDbSdk/Public/Connection/DbConnectionBase.h

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
#pragma once
22

3-
#include "CoreMinimal.h"
4-
#include "UObject/NoExportTypes.h"
3+
#include "CoreMinimal.h"
4+
#include "Containers/Ticker.h"
5+
#include "UObject/NoExportTypes.h"
56
#include "Types/Builtins.h"
67
#include "Websocket.h"
78
#include "Subscription.h"
@@ -114,8 +115,8 @@ struct THasOnUpdateDelegate<T, std::void_t<decltype(&T::OnUpdate)>> : std::true_
114115
{
115116
};
116117

117-
UCLASS()
118-
class SPACETIMEDBSDK_API UDbConnectionBase : public UObject, public FTickableGameObject
118+
UCLASS()
119+
class SPACETIMEDBSDK_API UDbConnectionBase : public UObject
119120
{
120121
GENERATED_BODY()
121122

@@ -135,8 +136,8 @@ class SPACETIMEDBSDK_API UDbConnectionBase : public UObject, public FTickableGam
135136
UFUNCTION(BlueprintCallable, Category="SpacetimeDB")
136137
void FrameTick();
137138

138-
UFUNCTION(BlueprintCallable, Category="SpacetimeDB")
139-
void SetAutoTicking(bool bAutoTick) { bIsAutoTicking = bAutoTick; }
139+
UFUNCTION(BlueprintCallable, Category="SpacetimeDB")
140+
void SetAutoTicking(bool bAutoTick);
140141

141142
/** Send a raw JSON message to the server. */
142143
bool SendRawMessage(const FString& Message);
@@ -257,9 +258,11 @@ class SPACETIMEDBSDK_API UDbConnectionBase : public UObject, public FTickableGam
257258
}
258259

259260

260-
protected:
261+
protected:
261262

262-
friend class UDbConnectionBuilderBase;
263+
virtual void BeginDestroy() override;
264+
265+
friend class UDbConnectionBuilderBase;
263266
friend class UDbConnectionBuilder;
264267
friend class USubscriptionHandleBase;
265268
friend class USubscriptionBuilder;
@@ -276,13 +279,7 @@ class SPACETIMEDBSDK_API UDbConnectionBase : public UObject, public FTickableGam
276279
UFUNCTION()
277280
void HandleWSBinaryMessage(const TArray<uint8>& Message);
278281

279-
virtual void Tick(float DeltaTime) override;
280-
281-
virtual TStatId GetStatId() const override;
282-
283-
virtual bool IsTickable() const override;
284-
285-
virtual bool IsTickableInEditor() const override;
282+
bool OnTickerTick(float DeltaTime);
286283

287284
/** Internal handler that processes a single server message. */
288285
void ProcessServerMessage(const FServerMessageType& Message);
@@ -407,6 +404,7 @@ class SPACETIMEDBSDK_API UDbConnectionBase : public UObject, public FTickableGam
407404

408405
UPROPERTY()
409406
bool bIsAutoTicking = false;
407+
FTSTicker::FDelegateHandle TickerHandle;
410408
/** Guard to avoid repeatedly handling the same fatal protocol error. */
411409
FThreadSafeBool bProtocolViolationHandled = false;
412410

0 commit comments

Comments
 (0)