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;
+};