diff --git a/README.md b/README.md index 95f982d..b86274a 100644 --- a/README.md +++ b/README.md @@ -20,3 +20,30 @@ I stand up a websocket+pubsub system like so: ``` PS C:\Users\echel\OneDrive\Documents\Unreal Projects\RedisTestProject\Plugins\EventCenterPlugin> .\python\Scripts\Activate.ps1 ``` + +## Notes + +A few collected notes, since I'm still new to Unreal engine and I'm cleaning up my debug code. + +To log to the game screen, + +``` +GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Red, "Message goes here"); +``` + +Time based handling in Tick(), + +``` +void AEventSourceActor::Tick(float DeltaTime) +{ + Super::Tick(DeltaTime); + + float time = GetGameTimeSinceCreation(); + + if (time - timerLastTime > 15.0) { + timerLastTime = time; + // Do the thing + } +} +``` + diff --git a/Source/EventCenterPlugin/EventCenterPlugin.Build.cs b/Source/EventCenterPlugin/EventCenterPlugin.Build.cs index bdd6c4a..762ed7e 100644 --- a/Source/EventCenterPlugin/EventCenterPlugin.Build.cs +++ b/Source/EventCenterPlugin/EventCenterPlugin.Build.cs @@ -14,12 +14,6 @@ public class EventCenterPlugin : ModuleRules new string[] { // ... add public include paths required here ... - // NB(bt): Using the commercial Redis Plugin by GameSeed (AKA "SDRedis") - // Add this in the Unreal Engine Plugins menu. - // If not present, use the Epic Games Launcher's Unreal Library Marketplace. - "RedisPlugin/Public", - "RedisPlugin/Classes", - // NB(bt): Using the commercial BlueprintWebSocket plugin described in README.md // Add this in the Unreal Engine Plugins menu. // If not present, use the Epic Games Launcher's Unreal Library Marketplace. @@ -42,11 +36,6 @@ public class EventCenterPlugin : ModuleRules "Core", // ... add other public dependencies that you statically link with here ... - // NB(bt): Using the commercial Redis Plugin by GameSeed (AKA "SDRedis") - // Add this in the Unreal Engine Plugins menu. - // If not present, use the Epic Games Launcher's Unreal Library Marketplace. - "RedisPlugin", - // NB(bt): Using the commercial BlueprintWebSocket plugin described in README.md // Add this in the Unreal Engine Plugins menu. // If not present, use the Epic Games Launcher's Unreal Library Marketplace. diff --git a/Source/EventCenterPlugin/Private/EventSourceActor.cpp b/Source/EventCenterPlugin/Private/EventSourceActor.cpp index b3dc4cd..3723aab 100644 --- a/Source/EventCenterPlugin/Private/EventSourceActor.cpp +++ b/Source/EventCenterPlugin/Private/EventSourceActor.cpp @@ -5,49 +5,20 @@ DEFINE_LOG_CATEGORY(EventSourceLog); AEventSourceActor::AEventSourceActor() : - redis(nullptr), WebSocket(nullptr) { - UE_LOG(EventSourceLog, Log, TEXT("CTOR()")); - // Set this actor to call Tick() every frame. // You can turn this off to improve performance if you don't need it. PrimaryActorTick.bCanEverTick = true; - - //redis = NewObject(this, TEXT("RedisObject")); - //redis = CreateDefaultSubobject(TEXT("RedisObject")); - - //RootComponent = redis; - } AEventSourceActor::~AEventSourceActor() { - // TODO: Memory leak (somehow placing a BP derivation into the editor causes the below lines to segfault) - //delete redis; - //redis = nullptr; - /*if (redis != nullptr) { - delete redis; - redis = nullptr; - }*/ } void AEventSourceActor::BeginPlay() { Super::BeginPlay(); - - GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Red, "BeginPlay() - 2"); - UE_LOG(EventSourceLog, Log, TEXT("BeginPlay()")); - - if (redis == nullptr) { - UE_LOG(EventSourceLog, Log, TEXT("BeginPlay(): Creating redis instance")); - redis = NewObject(this, TEXT("RedisObject")); - } - - UE_LOG(EventSourceLog, Log, TEXT("BeginPlay(): Redis is null? ? %d"), redis == nullptr); - //UE_LOG(EventSourceLog, Log, TEXT("BeginPlay(): Redis host: %s"), redisHost); - UE_LOG(EventSourceLog, Log, TEXT("BeginPlay(): Redis port: %d"), redisPort); - //UE_LOG(EventSourceLog, Log, TEXT("BeginPlay(): Redis password (no secrets): %s"), redisPassword); } void AEventSourceActor::BeginDestroy() @@ -58,82 +29,8 @@ void AEventSourceActor::BeginDestroy() void AEventSourceActor::Tick(float DeltaTime) { Super::Tick(DeltaTime); - - float time = GetGameTimeSinceCreation(); - if (time - timerLastTime > 15.0) { - timerLastTime = time; - //GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Red, "seconds elapsed"); - //redis->Unsubscribe(TEXT("second")); - //redis->Subscribe(TEXT("second")); - } - - - // NB: Handled by RedisObject internal delegate - //redis->Tick(DeltaTime); -} - -void AEventSourceActor::ConstructClient() -{ - if (redis != nullptr) { - UE_LOG(EventSourceLog, Log, TEXT("Construct(): Redis is NOT null")); - return; - } - - UE_LOG(EventSourceLog, Log, TEXT("construct(): redis = new Redis()")); - redis = NewObject(this, TEXT("RedisObject")); -} - - -void AEventSourceActor::Init() -{ - if (redis == nullptr) { - UE_LOG(EventSourceLog, Log, TEXT("Init(): Redis is null")); - return; - } - - //UE_LOG(EventSourceLog, Log, TEXT("construct(): redis = new Redis()")); - //redis = NewObject(this, TEXT("RedisObject")); - UE_LOG(EventSourceLog, Log, TEXT("Init(): redis->Init()")); - redis->Init(redisHost, redisPort, redisPassword); -} - -void AEventSourceActor::Subscribe(const FString& Channel) -{ - if (redis == nullptr) { - UE_LOG(EventSourceLog, Log, TEXT("Subscribe(): Redis is null")); - return; - } - - UE_LOG(EventSourceLog, Log, TEXT("Subscribe(): redis->Subscribe()")); - redis->Subscribe(Channel); - - if (!redis->SubscribeReply.IsBound()) { - UE_LOG(EventSourceLog, Log, TEXT("Subscribe(): binding event")); - redis->SubscribeReply.AddDynamic(this, &AEventSourceActor::InternalSubscribeCallback); - } -} - -void AEventSourceActor::Unsubscribe(const FString& Channel) -{ - if (redis == nullptr) { - UE_LOG(EventSourceLog, Log, TEXT("Unsubscribe(): Redis is null")); - return; - } - - UE_LOG(EventSourceLog, Log, TEXT("Unsubscribe(): redis->Unsubscribe()")); - redis->Unsubscribe(Channel); -} - -void AEventSourceActor::InternalSubscribeCallback(FString Channel, FString Message) -{ - //UE_LOG(EventSourceLog, Log, TEXT("SubscribeCallback(): %s %s"), Channel, Message); - UE_LOG(EventSourceLog, Log, TEXT("SubscribeCallback(): called")); - - if (MyDelegate.IsBound()) { - MyDelegate.Broadcast(Channel, Message); - - } + // TODO(bt): If our callbacks aren't enough to restore broken connections, add some logic here. } // ======================================= web sockets ============================================= @@ -142,33 +39,24 @@ void AEventSourceActor::OnConnected() { GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Red, "OnConnected"); UE_LOG(EventSourceLog, Log, TEXT("OnConnected")); - - //WebSocket->SendMessage(TEXT("Hello Server!")); } void AEventSourceActor::OnConnectionError(const FString& Error) { GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Red, "OnConnectionError"); - UE_LOG(EventSourceLog, Log, TEXT("OnConnectionError")); - - // Connection failed. - UE_LOG(EventSourceLog, Error, TEXT("Failed to connect: %s."), *Error); + UE_LOG(EventSourceLog, Error, TEXT("OnConnectionError. Failed to connect: %s."), *Error); } void AEventSourceActor::OnClosed(int64 StatusCode, const FString& Reason, bool bWasClean) { GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Red, "OnClosed"); - UE_LOG(EventSourceLog, Log, TEXT("OnClosed")); - - UE_LOG(EventSourceLog, Warning, TEXT("Connection closed: %d:%s. Clean: %d"), StatusCode, *Reason, bWasClean); + UE_LOG(EventSourceLog, Warning, TEXT("OnClosed. Connection closed: %d:%s. Clean: %d"), StatusCode, *Reason, bWasClean); } void AEventSourceActor::OnMessage(const FString& Message) { GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Red, "OnMessage"); - UE_LOG(EventSourceLog, Log, TEXT("OnMessage")); - - UE_LOG(EventSourceLog, Log, TEXT("New message: %s"), *Message); + UE_LOG(EventSourceLog, Log, TEXT("OnMessage. New message: %s"), *Message); FString channel, payload; bool result = Message.Split(TEXT("|"), &channel, &payload); @@ -194,16 +82,13 @@ void AEventSourceActor::OnMessage(const FString& Message) void AEventSourceActor::OnRawMessage(const TArray& Data, int32 BytesRemaining) { GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Red, "OnRawMessage"); - UE_LOG(EventSourceLog, Log, TEXT("OnRawMessage")); - - UE_LOG(EventSourceLog, Log, TEXT("New binary message: %d bytes and %d bytes remaining."), Data.Num(), BytesRemaining); + UE_LOG(EventSourceLog, Log, TEXT("OnRawMessage. New binary message: %d bytes and %d bytes remaining."), Data.Num(), BytesRemaining); } void AEventSourceActor::OnMessageSent(const FString& Message) { GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Red, "OnMessageSent"); - UE_LOG(EventSourceLog, Log, TEXT("OnMessageSent")); - UE_LOG(EventSourceLog, Log, TEXT("We just sent %s to the server."), *Message); + UE_LOG(EventSourceLog, Log, TEXT("OnMessageSent. We just sent %s to the server."), *Message); } void AEventSourceActor::SetupWebSockets() @@ -225,34 +110,6 @@ void AEventSourceActor::SetupWebSockets() WebSocket->Connect(TEXT("ws://localhost:8765/"), TEXT("ws")); } -/*void AEventSourceActor::AssignEvent(FString& eventName, TScriptInterface handler) -{ - GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Red, "AssignEvent"); - UE_LOG(EventSourceLog, Log, TEXT("AssignEvent")); - - //TScriptDelegate ourDelegate = TScriptDelegate(); - - //ourDelegate.BindUFunction(handler.GetObject(), &IEventHandlerInterface::HandleEvent); - - IEventHandlerInterface* iface = Cast(handler.GetObject()); - - - //MyDelegate2.AddDynamic(iface, &IEventHandlerInterface::HandleEvent2); - //MyDelegate2.AddDynamic((UObject*)iface, &IEventHandlerInterface::HandleEvent2); - - //MyDelegate2.AddDynamic(handler.GetObject(), &IEventHandlerInterface::HandleEvent2); - //MyDelegate2.AddDynamic(handler.GetObject(), "HandleEvent2"); - - UObject* uobject = Cast(handler.GetObject()); - - //MyDelegate2.AddDynamic(handler->_getUObject(), &IEventHandlerInterface::HandleEvent2); -}*/ - -/*void AEventSourceActor::AssignEvent2(FString& eventName, UObject& object, FMySubscriberRefDelegate& theDelegate) -{ - -}*/ - void AEventSourceActor::BindSingleDelegateForChannel(const FString& channel, const FMySubscriberRefDelegate& delegateDef) { delegateSubscriptions.Add(channel, delegateDef); diff --git a/Source/EventCenterPlugin/Public/EventSourceActor.h b/Source/EventCenterPlugin/Public/EventSourceActor.h index c1cff3e..8de304f 100644 --- a/Source/EventCenterPlugin/Public/EventSourceActor.h +++ b/Source/EventCenterPlugin/Public/EventSourceActor.h @@ -4,9 +4,6 @@ #include "CoreMinimal.h" #include "UObject/NoExportTypes.h" -// RedisPlugin -#include "RedisObject.h" - // Event system #include "EventHandlerInterface.h" @@ -21,16 +18,16 @@ DECLARE_LOG_CATEGORY_EXTERN(EventSourceLog, Log, All); // TODO(bt): Make this not an actor. Consider UObject. Also consider a singleton. +// NB(bt): We can't use `DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams` multicast +// due to an Unreal Engine limitation +DECLARE_DYNAMIC_DELEGATE_TwoParams(FMySubscriberRefDelegate, const FString&, Channel, const FString&, Message); + /** * AEventSourceActor * * Manages an underlying Redis pubsub subscription and coordinates dispatch to other actors. */ -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FMySubscriberDelegate, FString, Channel, FString, Message); - -DECLARE_DYNAMIC_DELEGATE_TwoParams(FMySubscriberRefDelegate, const FString&, Channel, const FString&, Message); - // NB(bt): "Blueprintable" seems to remove the ability to add BP event nodes. UCLASS() class EVENTCENTERPLUGIN_API AEventSourceActor : public AActor @@ -42,58 +39,20 @@ public: AEventSourceActor(); ~AEventSourceActor(); - /** Redis Hostname */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "EventCenter") - FString redisHost; - - /** Redis Port */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "EventCenter") - int32 redisPort = 6379; - - /** Redis Password */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "EventCenter") - FString redisPassword; - - /** Construct Redis Object.*/ - UFUNCTION(BlueprintCallable, Category = "EventCenter") - void ConstructClient(); - - /** Connect to Redis.*/ - UFUNCTION(BlueprintCallable, Category = "EventCenter") - void Init(); - - /** Subscribe to a named Redis PubSub channel. */ - UFUNCTION(BlueprintCallable, Category = "EventCenter") - void Subscribe(const FString& Channel); - - /** Unsubscribe from a named Redis PubSub channel. */ - UFUNCTION(BlueprintCallable, Category = "EventCenter") - void Unsubscribe(const FString& Channel); - - /** This allows event binding from blueprint. */ - UPROPERTY(BlueprintAssignable) - FMySubscriberDelegate MyDelegate; - - /** - * Bind a delegate to a channel. Note that we can't multicast. - */ - UFUNCTION(BlueprintCallable, Category = "EventCenter") - void BindSingleDelegateForChannel(const FString& channel, const FMySubscriberRefDelegate& delegateDef); - /** Set up websockets.*/ UFUNCTION(BlueprintCallable, Category = "EventCenter") void SetupWebSockets(); - /** This is an experiment */ - //UFUNCTION(BlueprintCallable, Category = "EventCenter") - //void AssignEvent(FString& eventName, TScriptInterface handler); - - - // This won't work because we don't know what the function is - //void AssignEvent2(FString& eventName, UObject& object, FMySubscriberRefDelegate& theDelegate); - - //FMySubscriberRefDelegate MyDelegate2; - + /** + * Bind a delegate to a channel. + * + * Note that we can't multicast due to Unreal limitations. As a workaround, bind + * different channels for other events,or have the events delegates themselves + * perform the multicasting downstream of us. + */ + UFUNCTION(BlueprintCallable, Category = "EventCenter") + void BindSingleDelegateForChannel(const FString& channel, + const FMySubscriberRefDelegate& delegateDef); protected: // Called when the game starts or when spawned @@ -110,26 +69,14 @@ protected: UFUNCTION() void OnMessageSent(const FString& Message); private: - URedisObject* redis; - UPROPERTY() UBlueprintWebSocket* WebSocket; - // TODO(bt): Value type can be wrapped in a class to add additional routing. // Multiple delegation signatures, "pause/muting", etc. TMap delegateSubscriptions; - // Handle callbacks. - // NB(bt): Declared 'UFUNCTION()' so reflection enables as a delegate callback. - UFUNCTION() - void InternalSubscribeCallback(FString Channel, FString Message); - - // For a timer calculation. - float timerLastTime = 0.0f; - public: // Called every frame virtual void Tick(float DeltaTime) override; - };