/** * @file * * @author Mamadou Babaei * * @section LICENSE * * (The MIT License) * * Copyright (c) 2020 - 2024 SenseGlove * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * * @section DESCRIPTION * * */ #include "SGTracking/SGGloveTracker.h" #if WITH_EDITOR #include "Editor.h" #endif /* WITH_EDITOR */ #include "Engine/Engine.h" #include "Engine/TimerHandle.h" #include "Engine/World.h" #if WITH_EDITOR #include "Subsystems/UnrealEditorSubsystem.h" #endif /* WITH_EDITOR */ #include "TimerManager.h" #include "SGBackend/SGBackend.h" #include "SGBuildHacks/SGPlatform.h" #include "SGCore/SGHapticGlove.h" #include "SGLog/SGLog.h" #include "SGSettings/SGSettings.h" #include "SGSettings/SGTrackingSettings.h" struct USGGloveTracker::FImpl { /************************ * Static methods ************************/ static UWorld* GetWorld(); FORCEINLINE static float GetGloveConnectivityCheckInterval() { const USGSettings* Settings{USGSettings::GetInstance()}; ensureAlwaysMsgf(Settings, TEXT("The settings subsystem has not been initialized!")); return Settings->GetTrackingSettings().GloveTrackingSettings.GloveConnectivityCheckInterval; } /************************ * Member variables ************************/ FTimerHandle GloveConnectivityCheckTimer; /************************ * Owner object ************************/ USGGloveTracker* Owner; /************************ * Constructor / Destructor ************************/ explicit FImpl(USGGloveTracker* InOwner); ~FImpl(); /************************ * Default copy constructor & copy assignment operator ************************/ FImpl(const FImpl& Rhs) = default; FImpl& operator=(const FImpl& Rhs) = default; /************************ * Methods ************************/ void UpdateGloves() const; }; UWorld* USGGloveTracker::FImpl::GetWorld() { UWorld* World{nullptr}; #if WITH_EDITOR if (GIsEditor) { if (IsValid(GEditor)) { const bool bIsPIE = GPlayInEditorID != -1; if (!bIsPIE) { const FWorldContext* WorldContext{GEditor->GetPIEWorldContext(1)}; if (WorldContext) { World = WorldContext->World(); } } else { const FWorldContext* WorldContext{GEditor->GetPIEWorldContext(GPlayInEditorID)}; if (WorldContext) { World = WorldContext->World(); } } /// NOTE /// IsInGameThread() check is to avoid EngineScriptHelpers::CheckIfInEditorAndPIE emitting: /// "You are not on the main thread." /// !(GEditor->PlayWorld || GIsPlayInEditorWorld) check is to avoid /// EngineScriptHelpers::CheckIfInEditorAndPIE emitting: /// The Editor is currently in a play mode. if (!IsValid(World) && IsInGameThread() && !(GEditor->PlayWorld || GIsPlayInEditorWorld)) { UUnrealEditorSubsystem* UnrealEditorSubsystem{GEditor->GetEditorSubsystem()}; if (IsValid(UnrealEditorSubsystem)) { World = UnrealEditorSubsystem->GetEditorWorld(); } } } if (!IsValid(World) && IsValid(GEngine)) { const UGameViewportClient* Viewport{GEngine->GameViewport}; if (IsValid(Viewport)) { World = Viewport->GetWorld(); } } } else { if (IsValid(GEngine)) { World = GEngine->GetCurrentPlayWorld(nullptr); } } #else /* WITH_EDITOR */ if (IsValid(GEngine)) { World = GEngine->GetCurrentPlayWorld(nullptr); } #endif /* WITH_EDITOR */ if (!IsValid(World) && IsValid(GEngine)) { UEngineSubsystem* EngineSubsystem{GEngine->GetEngineSubsystem()}; if (IsValid(EngineSubsystem)) { World = EngineSubsystem->GetWorld(); } } return World; } USGGloveTracker* USGGloveTracker::GetInstance() { UWorld* World{FImpl::GetWorld()}; USGGloveTracker* Instance{IsValid(World) ? World->GetSubsystem() : nullptr}; return Instance; } USGGloveTracker::USGGloveTracker() : Pimpl(TUniquePtr(new FImpl{this}, PimplDeleter)), LeftGlove(nullptr), RightGlove(nullptr) { } bool USGGloveTracker::ShouldCreateSubsystem(UObject* Outer) const { return true; } void USGGloveTracker::Initialize(FSubsystemCollectionBase& Collection) { SGLOG("Initializing the SenseGlove tracking singleton with the world lifetime..."); Super::Initialize(Collection); const UWorld* World{GetWorld()}; if (ensureAlwaysMsgf(World, TEXT("Invalid world!"))) { SGLOG("Starting the glove connectivity check timer..."); const float GloveConnectivityCheckInterval = FImpl::GetGloveConnectivityCheckInterval(); FTimerDelegate GloveConnectivityCheckHandler; GloveConnectivityCheckHandler.BindLambda([= SG_CAPTURE_THIS]()-> void { Pimpl->UpdateGloves(); }); FTimerManager& TimerManager = World->GetTimerManager(); TimerManager.SetTimer( Pimpl->GloveConnectivityCheckTimer, GloveConnectivityCheckHandler, GloveConnectivityCheckInterval, true, -1.0f); SGLOG("The glove connectivity check timer has been started successfully!"); } SGLOG("The SenseGlove tracking singleton has been successfully initialized with the world lifetime!"); } void USGGloveTracker::Deinitialize() { SGLOG("De-initializing the SenseGlove tracking singleton due to the world lifetime expiration..."); Super::Deinitialize(); const UWorld* World{GetWorld()}; if (ensureAlwaysMsgf(World, TEXT("Invalid world!"))) { SGLOG("Stopping the glove connectivity check timer..."); FTimerManager& TimerManager = World->GetTimerManager(); TimerManager.ClearTimer(Pimpl->GloveConnectivityCheckTimer); SGLOG("The glove connectivity check timer has been stopped successfully!"); } (void) Pimpl.Release(); SGLOG("The SenseGlove tracking singleton has been successfully de-initialized!"); } USGGloveTracker::FImpl::FImpl(USGGloveTracker* InOwner) : Owner(InOwner) { } USGGloveTracker::FImpl::~FImpl() = default; void USGGloveTracker::FImpl::UpdateGloves() const { if (!USGBackend::GetInstance()->IsBackendInitialized()) { const bool bSucceeded = USGBackend::GetInstance()->InitializeBackend(); if (!bSucceeded) { return; } } const bool bIsLeftGloveConnected = USGHapticGlove::GetGlove(Owner, false, Owner->LeftGlove); if (!bIsLeftGloveConnected) { Owner->LeftGlove = nullptr; } const bool bIsRightGloveConnected = USGHapticGlove::GetGlove(Owner, true, Owner->RightGlove); if (!bIsRightGloveConnected) { Owner->RightGlove = nullptr; } } void USGGloveTracker::FImplDeleter::operator()(const FImpl* P) const { delete P; }