diff --git a/Lyra.sln.DotSettings.user b/Lyra.sln.DotSettings.user index 1bd7888a..7288a647 100644 --- a/Lyra.sln.DotSettings.user +++ b/Lyra.sln.DotSettings.user @@ -1,4 +1,8 @@  + ForceIncluded + ForceIncluded + ForceIncluded + ForceIncluded <SessionState ContinuousTestingMode="0" IsActive="True" Name="Session" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> <Nothing /> </SessionState> \ No newline at end of file diff --git a/Source/LyraGame/Workshop/PerWorldServiceLocator.cpp b/Source/LyraGame/Workshop/PerWorldServiceLocator.cpp new file mode 100644 index 00000000..6f9cac63 --- /dev/null +++ b/Source/LyraGame/Workshop/PerWorldServiceLocator.cpp @@ -0,0 +1,21 @@ +#include "PerWorldServiceLocator.h" + +#include "Engine/Engine.h" + +FServiceLocator* UPerWorldServiceLocator::Get(UObject* WorldContextObject) +{ + UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::ReturnNull); + if(!World) + return nullptr; + + UPerWorldServiceLocator* PerWorldServiceLocator = World->GetSubsystem(); + if(!PerWorldServiceLocator) + return nullptr; + + return &PerWorldServiceLocator->GetServiceLocator(); +} + +FServiceLocator& UPerWorldServiceLocator::GetServiceLocator() +{ + return ServiceLocator; +} diff --git a/Source/LyraGame/Workshop/PerWorldServiceLocator.h b/Source/LyraGame/Workshop/PerWorldServiceLocator.h new file mode 100644 index 00000000..5410f097 --- /dev/null +++ b/Source/LyraGame/Workshop/PerWorldServiceLocator.h @@ -0,0 +1,20 @@ +#pragma once + +#include "CoreMinimal.h" +#include "ServiceLocator.h" +#include "Subsystems/WorldSubsystem.h" + +#include "PerWorldServiceLocator.generated.h" + +UCLASS() +class LYRAGAME_API UPerWorldServiceLocator : public UWorldSubsystem +{ + GENERATED_BODY() +public: + static FServiceLocator* Get(UObject* WorldContextObject); + + FServiceLocator& GetServiceLocator(); + +protected: + FServiceLocator ServiceLocator; +}; diff --git a/Source/LyraGame/Workshop/ServiceLocator.cpp b/Source/LyraGame/Workshop/ServiceLocator.cpp new file mode 100644 index 00000000..5c2f4423 --- /dev/null +++ b/Source/LyraGame/Workshop/ServiceLocator.cpp @@ -0,0 +1,87 @@ +// Aesir Interactive GmbH, (c) 2019 + +#include "ServiceLocator.h" + + + +#if WITH_DEV_AUTOMATION_TESTS + +#include "Components/ActorComponent.h" +#include "Components/SceneComponent.h" + +static_assert(TModels::Value, "Actor component should have a static uclass"); +static_assert(TModels::Value); +static_assert(!TModels::Value); + +static_assert(TModels::Value); +static_assert(TIsUInterface::Value); +static_assert(TModels::Value); +static_assert(!TModels::Value); +static_assert(!TIsUInterface::Value); +static_assert(!TIsUInterface::Value); + + +class FServiceUser +{ +public: + USceneComponent* SceneComponent = nullptr; + UActorComponent* ActorComponent = nullptr; +}; + +BEGIN_DEFINE_SPEC(TServiceLocatorSpec, "Workshop.ServiceLocator", EAutomationTestFlags::ProductFilter | EAutomationTestFlags::ApplicationContextMask) + FServiceUser ServiceUser; + FServiceLocator ServiceLocator; + USceneComponent* SceneComponent = nullptr; + UActorComponent* ActorComponent = nullptr; +END_DEFINE_SPEC(TServiceLocatorSpec) + + +void TServiceLocatorSpec::Define() +{ + BeforeEach([this]() + { + SceneComponent = NewObject(); + ActorComponent = NewObject(); + }); + + Describe("ProvideService", [this]() + { + It("Should work for UObjects", [this]() + { + TestNull("GetService()", ServiceLocator.GetService()); + ServiceLocator.ProvideService(ActorComponent); + + TestEqual("GetService()", ServiceLocator.GetService(), ActorComponent); + }); + + It("Should work for UInterfaces", [this]() + { + TestNull("GetService()", ServiceLocator.GetService().GetInterface()); + ServiceLocator.ProvideService(ActorComponent); + + TestEqual("GetService()", + ServiceLocator.GetService().GetInterface(), + static_cast(ActorComponent)); + }); + }); + + Describe("GetServices", [this]() + { + BeforeEach([this]() + { + ServiceLocator.ProvideService(ActorComponent); + ServiceLocator.ProvideService(SceneComponent); + ServiceLocator.ProvideService(ActorComponent); + }); + It("Should get all the services", [this]() + { + auto [InjectedActorComponent, InjectedSceneComponent, InjectedInterface] = ServiceLocator.GetServices(); + TestEqual(TEXT("InjectedActorComponent"), InjectedActorComponent, ActorComponent); + TestEqual(TEXT("InjectedSceneComponent"), InjectedSceneComponent, SceneComponent); + TestEqual(TEXT("InjectedInterface"), InjectedInterface.GetInterface(), static_cast(ActorComponent)); + }); + }); + +} + +#endif \ No newline at end of file diff --git a/Source/LyraGame/Workshop/ServiceLocator.h b/Source/LyraGame/Workshop/ServiceLocator.h new file mode 100644 index 00000000..4a1442fb --- /dev/null +++ b/Source/LyraGame/Workshop/ServiceLocator.h @@ -0,0 +1,204 @@ +// Aesir Interactive GmbH, (c) 2019 + +#pragma once + +#include "CoreMinimal.h" +#include "Templates/Casts.h" + + +/* +namespace TIsUBaseInterfacePrivate +{ + template + struct TIsUBaseInterfaceImpl; + template<> + struct TIsUBaseInterfaceImpl { enum { Value = false };}; + template<> + struct TIsUBaseInterfaceImpl { enum { Value = true };}; +} +template +struct TIsUBaseInterface { enum { Value = TIsUBaseInterfacePrivate::TIsUBaseInterfaceImpl::Value}; }; +*/ + +struct CHasStaticUClass +{ + template + auto Requires() -> decltype( + ObjectClass::StaticClass() + ); +}; + +struct CHasInterfaceUClass +{ + template + auto Requires() -> decltype( + InterfaceClass::UClassType::StaticClass() + ); +}; + + +template +struct TIsUInterface +{ + enum + { + Value = !TModels::Value && TModels::Value + }; +}; + +/** + * General purpose service locator that is available from the world settings + */ +class LYRAGAME_API FServiceLocator +{ +public: + DECLARE_EVENT_OneParam(FServiceLocator, FServiceRegisteredEvent, FServiceLocator&); + + /** + * @return the registered service cast to ServiceClass or nullptr if the service is not registered or the cast failed + */ + template + bool IsServiceProvided() const + { + const UClass* ServiceUClass = GetServiceUClass(); + return Services.Contains(ServiceUClass); + } + + /** + * @return the registered service cast to ServiceClass or nullptr if the service is not registered or the cast failed + */ + template + typename TEnableIf::Value, ServiceType*>::Type + GetService() const + { + const UClass* ServiceUClass = GetServiceUClass(); + UObject* const* ServiceInstance = Services.Find(ServiceUClass); + if (!ServiceInstance) + return nullptr; + + return Cast(*ServiceInstance); + } + + /** + * @return the registered service as TScriptInterface or nullptr if the service is not registered or the cast failed + */ + template + typename TEnableIf::Value, TScriptInterface>::Type + GetService() const + { + const UClass* ServiceUClass = GetServiceUClass(); + UObject* const* ServiceInstance = Services.Find(ServiceUClass); + if (!ServiceInstance) + return nullptr; + + return TScriptInterface(&**ServiceInstance); + } + + /** + * @return the registered service cast to ServiceClass. Crashes if the service is not available. + */ + template + typename TEnableIf::Value, ServiceClass*>::Type + GetServiceChecked() const + { + const UClass* ServiceUClass = GetServiceUClass(); + + UObject *const* ServiceObject = Services.Find(ServiceUClass); + checkf(ServiceObject, TEXT("No object was found for service %s"), *ServiceUClass->GetName()); + UObject *const ServiceCast = Cast(*ServiceObject); + checkf(IsValid(ServiceCast), TEXT("Object %s is not of type %s"), *((*ServiceObject)->GetName()), *ServiceUClass->GetName()); + return Cast(*ServiceObject); + } + + /** + * @return the registered service cast to ServiceClass. Crashes if the service is not available. + */ + template + typename TEnableIf::Value, TScriptInterface>::Type + GetServiceChecked() const + { + const UClass* ServiceUClass = GetServiceUClass(); + + UObject *const* ServiceObject = Services.Find(ServiceUClass); + checkf(ServiceObject, TEXT("No object was found for service %s"), *ServiceUClass->GetName()); + checkf(Cast(ServiceObject), TEXT("Object %s does not implement %s"), *(*ServiceObject)->GetName(), *ServiceUClass->GetName()); + return TScriptInterface(*ServiceObject); + } + + + + /** + * Registers the given object interface with the service locator. + * Will refuse to work if the type is not actually derived from the interface. + * Note: you should always explicitly provide the template list to make sure you register the service as the intended type + * @return void + */ + template + typename TEnableIf::Value, void>::Type + ProvideService(ChildType* ServiceInstance) + { + const UClass* ServiceUClass = GetServiceUClass(); + Services.Emplace(ServiceUClass, ServiceInstance); + NotifyServiceProvided(ServiceUClass); + } + + /** + * Unregisters the given object with the service locator + * Note: you should always explicitly provide the template list to make sure you unregister the service as the intended type + */ + template + void WithdrawService(ServiceClass* ServiceInstance) + { + const UClass* ServiceClassObject = GetServiceUClass(); + ensureMsgf(GetService() == ServiceInstance, TEXT("The service you are trying to unregister is not the one that has been provided!")); + Services.Remove(ServiceClassObject); + } + + /** Waits for the service class to be registered or calls the callback immediately if the service is already registerd */ + template + void WaitForService(FServiceRegisteredEvent::FDelegate Callback) + { + const UClass* ServiceUClass = GetServiceUClass(); + ServiceRegisteredCallbacks.FindOrAdd(ServiceUClass).Add(Callback); + } + + template + struct ServiceTypeMapper + { + using MappedType = decltype(DeclVal().GetService()); + }; + + template + auto GetServices() -> decltype(auto) + { + return MakeTuple::MappedType...>(GetService()...); + } + +private: + void NotifyServiceProvided(const UClass* ServiceUClass) + { + FServiceRegisteredEvent& ServiceRegisteredEvent = ServiceRegisteredCallbacks.FindOrAdd(ServiceUClass); + ServiceRegisteredEvent.Broadcast(*this); + ServiceRegisteredEvent.Clear(); + } + + /** This statically gets the UClass from any given IInterface-Type */ + template + static typename TEnableIf::Value,const UClass*>::Type + GetServiceUClass() + { + return ServiceInterface::UClassType::StaticClass(); + } + + /** This statically gets the UClass from any given UObject-Type */ + template + static typename TEnableIf::Value,const UClass*>::Type + GetServiceUClass() + { + return ServiceClass::StaticClass(); + } + + TMap ServiceRegisteredCallbacks; + + TMap Services; +};