Production-Ready Architecture for Centralized Supabase Management
The USupabaseSubsystem is the heart of the Supabase UE5 Plugin’s production-ready architecture. Built as a UGameInstanceSubsystem, it provides centralized, singleton-based management of all Supabase operations with automatic lifecycle management, health monitoring, and thread-safe operations.
UCLASS(BlueprintType)
class SUPABASE_API USupabaseSubsystem : public UGameInstanceSubsystem
{
GENERATED_BODY()
public:
// USubsystem interface
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
virtual void Deinitialize() override;
// Core functionality...
};
| Principle | Implementation | Benefit |
|---|---|---|
| Singleton Pattern | GameInstance-scoped subsystem | Single source of truth for all Supabase state |
| Automatic Lifecycle | Initialize/Deinitialize hooks | Proper startup/cleanup without manual management |
| Centralized State | Single client instance with shared state | Consistent authentication and connection status |
| Thread Safety | Mutex-protected operations | Safe concurrent access from any thread |
| Health Monitoring | Automatic connection and token monitoring | Proactive maintenance of connection health |
UPROPERTY()
USupabaseConnection* ActiveConnection;
UFUNCTION(BlueprintCallable, Category = "Supabase|Connection")
bool InitializeConnection(USupabaseConnection* Connection);
UFUNCTION(BlueprintPure, Category = "Supabase|Connection")
USupabaseConnection* GetActiveConnection() const;
The subsystem maintains a single active connection that serves all operations. This eliminates the common issue of multiple connection instances causing state inconsistencies.
UFUNCTION(BlueprintPure, Category = "Supabase|Connection")
bool IsConnected() const { return bIsConnected; }
UFUNCTION(BlueprintPure, Category = "Supabase|Connection")
FString GetConnectionStatus() const;
Connection States:
UPROPERTY()
USupabaseClient* SupabaseClient;
UFUNCTION(BlueprintPure, Category = "Supabase|Connection")
USupabaseClient* GetSupabaseClient() const;
Benefits of Centralized Client:
UPROPERTY()
FUser CurrentUser;
UFUNCTION(BlueprintPure, Category = "Supabase|Authentication")
bool IsAuthenticated() const { return bIsAuthenticated; }
UFUNCTION(BlueprintPure, Category = "Supabase|Authentication")
FUser GetCurrentUser() const { return CurrentUser; }
UFUNCTION(BlueprintCallable, Category = "Supabase|Authentication")
void LoginWithEmail(const FString& Email, const FString& Password);
UFUNCTION(BlueprintCallable, Category = "Supabase|Authentication")
void RegisterWithEmail(const FString& Email, const FString& Password);
UFUNCTION(BlueprintCallable, Category = "Supabase|Authentication")
void Logout();
UFUNCTION(BlueprintCallable, Category = "Supabase|Authentication")
void RefreshToken();
The subsystem implements a comprehensive health monitoring system that runs automatically in the background:
// Connection management
FTimerHandle ConnectionTestTimer;
const float ConnectionTestInterval = 300.0f; // 5 minutes
void StartConnectionHealthChecks();
void OnConnectionHealthCheck();
Features:
// Token management
FTimerHandle TokenRefreshTimer;
const float TokenRefreshInterval = 3300.0f; // 55 minutes
void OnTokenRefreshCheck();
Token Management:
int32 ConnectionRetryCount;
const int32 MaxConnectionRetries = 3;
void RetryConnection();
void HandleConnectionFailure();
Retry Strategy:
The subsystem broadcasts events for all major state changes, allowing your game to respond appropriately:
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnConnectionStateChanged, bool, bIsConnected);
UPROPERTY(BlueprintAssignable, Category = "Supabase|Events")
FOnConnectionStateChanged OnConnectionStateChanged;
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnAuthenticationStateChanged,
bool, bIsAuthenticated,
const FUser&, User);
UPROPERTY(BlueprintAssignable, Category = "Supabase|Events")
FOnAuthenticationStateChanged OnAuthenticationStateChanged;
UPROPERTY(BlueprintAssignable, Category = "Supabase|Events")
FSupabaseSuccessDelegate OnLoginSuccessful;
UPROPERTY(BlueprintAssignable, Category = "Supabase|Events")
FSupabaseErrorEvent OnLoginFailed;
UPROPERTY(BlueprintAssignable, Category = "Supabase|Events")
FSupabaseStringDelegate OnLogoutSuccessful;
UPROPERTY(BlueprintAssignable, Category = "Supabase|Events")
FSupabaseErrorEvent OnLogoutFailed;
void AMyGameInstance::BeginPlay()
{
Super::BeginPlay();
USupabaseSubsystem* Subsystem = GetSubsystem<USupabaseSubsystem>();
if (Subsystem)
{
// Bind to connection state changes
Subsystem->OnConnectionStateChanged.AddDynamic(
this, &AMyGameInstance::OnSupabaseConnectionChanged);
// Bind to authentication changes
Subsystem->OnAuthenticationStateChanged.AddDynamic(
this, &AMyGameInstance::OnSupabaseAuthChanged);
// Bind to login events
Subsystem->OnLoginSuccessful.AddDynamic(
this, &AMyGameInstance::OnSupabaseLoginSuccess);
Subsystem->OnLoginFailed.AddDynamic(
this, &AMyGameInstance::OnSupabaseLoginFailed);
}
}
// In GameInstance Blueprint
Event BeginPlay -> Get Subsystem (Supabase Subsystem)
-> Bind Event to On Connection State Changed
-> Bind Event to On Authentication State Changed
The subsystem implements comprehensive thread safety measures:
private:
mutable FCriticalSection ConnectionStateMutex;
mutable FCriticalSection AuthStateMutex;
void SetConnectionState(bool bConnected);
void SetAuthenticationState(bool bAuthenticated, const FUser& User);
bool USupabaseSubsystem::IsConnected() const
{
FScopeLock Lock(&ConnectionStateMutex);
return bIsConnected;
}
bool USupabaseSubsystem::IsAuthenticated() const
{
FScopeLock Lock(&AuthStateMutex);
return bIsAuthenticated;
}
// All delegate broadcasts execute on game thread
void USupabaseSubsystem::HandleLoginSuccess(FString Response, const FTokenResponse& TokenResponse)
{
// Safe to call from any thread
AsyncTask(ENamedThreads::GameThread, [this, Response, TokenResponse]()
{
SetAuthenticationState(true, TokenResponse.User);
OnLoginSuccessful.Broadcast(Response, TokenResponse);
});
}
The subsystem automatically handles session save/restore:
void USupabaseSubsystem::HandleLoginSuccess(FString Response, const FTokenResponse& TokenResponse)
{
// Update authentication state
SetAuthenticationState(true, TokenResponse.User);
// Automatically save session
FSupabaseCredentials Credentials = ActiveConnection->SupabaseCredentials;
USupabaseUtils::SaveSupabaseSession(Credentials, TokenResponse.User);
// Broadcast success
OnLoginSuccessful.Broadcast(Response, TokenResponse);
}
bool USupabaseSubsystem::InitializeConnection(USupabaseConnection* Connection)
{
// ... connection setup ...
// Try to restore previous session
FSupabaseCredentials SavedCredentials;
FUser SavedUser;
if (USupabaseUtils::LoadSupabaseSession(SavedCredentials, SavedUser))
{
UE_LOG(LogTemp, Log, TEXT("Restored previous Supabase session"));
Connection->SupabaseCredentials.UserAuthenticatedKey = SavedCredentials.UserAuthenticatedKey;
Connection->SupabaseCredentials.RefreshToken = SavedCredentials.RefreshToken;
SetAuthenticationState(true, SavedUser);
StartConnectionHealthChecks();
}
return true;
}
void USupabaseSubsystem::Logout()
{
if (SupabaseClient)
{
SupabaseClient->Logout();
USupabaseUtils::ClearSupabaseSession(); // Clear saved session
ClearCache(); // Clear in-memory cache
}
}
The subsystem works seamlessly with USupabaseManager to provide easy static access:
// USupabaseManager provides static wrapper functions
class SUPABASE_API USupabaseManager : public UBlueprintFunctionLibrary
{
public:
UFUNCTION(BlueprintPure, Category = "Supabase|Manager")
static USupabaseSubsystem* GetSupabaseSubsystem(const UObject* WorldContext);
UFUNCTION(BlueprintCallable, Category = "Supabase|Manager")
static bool InitializeSupabase(const UObject* WorldContext, USupabaseConnection* Connection);
UFUNCTION(BlueprintCallable, Category = "Supabase|Manager")
static void LoginWithEmail(const UObject* WorldContext, const FString& Email, const FString& Password);
};
void USupabaseManager::LoginWithEmail(const UObject* WorldContext, const FString& Email, const FString& Password)
{
USupabaseSubsystem* Subsystem = GetSupabaseSubsystem(WorldContext);
if (Subsystem)
{
Subsystem->LoginWithEmail(Email, Password);
}
else
{
UE_LOG(LogTemp, Error, TEXT("Supabase subsystem not available"));
}
}
The subsystem provides comprehensive logging for debugging:
UFUNCTION(BlueprintCallable, Category = "Supabase|Debug")
void EnableDebugLogging(bool bEnable) { bDebugLogging = bEnable; }
private:
bool bDebugLogging = false;
void USupabaseSubsystem::LogConnectionInfo()
{
if (bDebugLogging)
{
UE_LOG(LogTemp, Log, TEXT("=== Supabase Connection Info ==="));
UE_LOG(LogTemp, Log, TEXT("Connection Name: %s"), *ActiveConnection->ConnectionName.ToString());
UE_LOG(LogTemp, Log, TEXT("Server URL: %s"), *ActiveConnection->SupabaseServerUrl);
UE_LOG(LogTemp, Log, TEXT("Is Connected: %s"), IsConnected() ? TEXT("Yes") : TEXT("No"));
UE_LOG(LogTemp, Log, TEXT("Is Authenticated: %s"), IsAuthenticated() ? TEXT("Yes") : TEXT("No"));
UE_LOG(LogTemp, Log, TEXT("Status: %s"), *GetConnectionStatus());
if (IsAuthenticated())
{
UE_LOG(LogTemp, Log, TEXT("Current User: %s"), *CurrentUser.Email);
}
}
}
void USupabaseSubsystem::OnConnectionHealthCheck()
{
if (bDebugLogging)
{
UE_LOG(LogTemp, Verbose, TEXT("Performing connection health check"));
}
TestConnection();
}
void USupabaseSubsystem::OnTokenRefreshCheck()
{
if (bIsAuthenticated && !ActiveConnection->SupabaseCredentials.RefreshToken.IsEmpty())
{
if (bDebugLogging)
{
UE_LOG(LogTemp, Verbose, TEXT("Performing token refresh"));
}
RefreshToken();
}
}
void AMyGameInstance::BeginPlay()
{
Super::BeginPlay();
// Get the subsystem
USupabaseSubsystem* Supabase = GetSubsystem<USupabaseSubsystem>();
// Create and configure connection
USupabaseConnection* Connection = NewObject<USupabaseConnection>();
Connection->SupabaseServerUrl = TEXT("https://your-project.supabase.co");
Connection->SupabaseCredentials.AnonymousKey = TEXT("your-anon-key");
Connection->ConnectionName = TEXT("Main");
// Initialize - handles everything automatically
bool bSuccess = Supabase->InitializeConnection(Connection);
if (bSuccess)
{
UE_LOG(LogTemp, Log, TEXT("Supabase initialized successfully"));
// Enable debug logging
Supabase->EnableDebugLogging(true);
// Bind to events
Supabase->OnConnectionStateChanged.AddDynamic(this, &AMyGameInstance::OnConnectionChanged);
Supabase->OnAuthenticationStateChanged.AddDynamic(this, &AMyGameInstance::OnAuthChanged);
}
}
Event BeginPlay
-> Get Subsystem (Supabase Subsystem)
-> Create Supabase Connection
-> Set Server URL: "https://your-project.supabase.co"
-> Set Anonymous Key: "your-anon-key"
-> Set Connection Name: "Main"
-> Initialize Connection
-> Branch (Success?)
-> True: Enable Debug Logging
-> Bind Event to On Connection State Changed
-> Bind Event to On Authentication State Changed
-> False: Log Error
void AMyPlayerController::PerformLogin(const FString& Email, const FString& Password)
{
USupabaseSubsystem* Supabase = GetGameInstance()->GetSubsystem<USupabaseSubsystem>();
if (Supabase && Supabase->IsConnected())
{
// Bind to login events
Supabase->OnLoginSuccessful.AddDynamic(this, &AMyPlayerController::OnLoginSuccess);
Supabase->OnLoginFailed.AddDynamic(this, &AMyPlayerController::OnLoginFailed);
// Attempt login
Supabase->LoginWithEmail(Email, Password);
}
else
{
UE_LOG(LogTemp, Error, TEXT("Supabase not connected"));
}
}
void AMyPlayerController::OnLoginSuccess(const FString& Response, const FTokenResponse& TokenResponse)
{
UE_LOG(LogTemp, Log, TEXT("Login successful for user: %s"), *TokenResponse.User.Email);
// User is now authenticated, session automatically saved
// Proceed with game logic
}
void AMyGameMode::OnConnectionChanged(bool bIsConnected)
{
if (bIsConnected)
{
UE_LOG(LogTemp, Log, TEXT("Supabase connected - enabling online features"));
EnableOnlineFeatures();
}
else
{
UE_LOG(LogTemp, Warning, TEXT("Supabase disconnected - switching to offline mode"));
EnableOfflineMode();
}
}
void AMyGameMode::OnAuthChanged(bool bIsAuthenticated, const FUser& User)
{
if (bIsAuthenticated)
{
UE_LOG(LogTemp, Log, TEXT("User authenticated: %s"), *User.Email);
ShowMainMenu();
}
else
{
UE_LOG(LogTemp, Log, TEXT("User logged out"));
ShowLoginScreen();
}
}
GameInstance::BeginPlay() for proper lifecycleIsConnected() before operationsThe USupabaseSubsystem represents the evolution from basic client usage to production-ready, enterprise-level architecture. It solves the fundamental issues of state management, resource efficiency, and operational reliability that are critical for shipped games.