This was quite a fight; at least it compiles now... (no idea if works)

Ugh, the type system in C++ is bad. It's hard to fit these things
together.

I'll have a ton of cleanup to do when done.
This commit is contained in:
Brandon Thomas
2021-02-19 05:48:16 -05:00
parent 33a2230323
commit 9c5b8fe57f
5 changed files with 130 additions and 7 deletions
+5 -3
View File
@@ -6,10 +6,12 @@ I tried Redis PubSub first, but the plugin I found was buggy. I'm trying WebSock
## Installation ## Installation
Here are the plugins I use: Install the [BlueprintWebSocket](https://www.unrealengine.com/marketplace/en-US/product/blueprintwebsocket)
plugin (paid/commercial) from the Epic/Unreal Marketplace. It's not expensive and has great documentation.
* [SDRedis](https://www.unrealengine.com/marketplace/en-US/product/sdredis) (paid/commercial) - this is flaky and disconnects. Attempts to reconnect segfault. ## Usage
* [BlueprintWebSocket](https://www.unrealengine.com/marketplace/en-US/product/blueprintwebsocket) (paid/commercial) - much better docs. Untested.
It's expected to wire events up in the `Level Blueprint`.
## Python testing ## Python testing
@@ -183,8 +183,22 @@ void AEventSourceActor::OnMessageSent(const FString& Message)
{ {
GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Red, "OnMessageSent"); GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Red, "OnMessageSent");
UE_LOG(EventSourceLog, Log, TEXT("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("We just sent %s to the server."), *Message);
FString channel, payload;
bool result = Message.Split(TEXT("|"), &channel, &payload);
if (!result) {
return;
}
if (!delegateSubscriptions.Contains(channel)) {
return;
}
delegateSubscriptions[channel].Execute(channel, payload);
} }
void AEventSourceActor::SetupWebSockets() void AEventSourceActor::SetupWebSockets()
@@ -206,3 +220,35 @@ void AEventSourceActor::SetupWebSockets()
WebSocket->Connect(TEXT("ws://localhost:8765/"), TEXT("ws")); WebSocket->Connect(TEXT("ws://localhost:8765/"), TEXT("ws"));
} }
/*void AEventSourceActor::AssignEvent(FString& eventName, TScriptInterface<IEventHandlerInterface> handler)
{
GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Red, "AssignEvent");
UE_LOG(EventSourceLog, Log, TEXT("AssignEvent"));
//TScriptDelegate<TWeakPtr> ourDelegate = TScriptDelegate<TWeakPtr>();
//ourDelegate.BindUFunction(handler.GetObject(), &IEventHandlerInterface::HandleEvent);
IEventHandlerInterface* iface = Cast<IEventHandlerInterface>(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<UObject>(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);
}
@@ -0,0 +1,46 @@
#pragma once
#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "UObject/Interface.h"
#include "EventHandlerInterface.generated.h"
/**
* Interface for all objects that can handle events.
*
* My C++ foo isn't strong enough to accomplish this with templates, lambdas,
* or reuse of unreal's existing delegation system.
*
* Assuming this works, see:
* https://docs.unrealengine.com/en-US/ProgrammingAndScripting/GameplayArchitecture/Interfaces/index.html
*/
UINTERFACE(BlueprintType)
class EVENTCENTERPLUGIN_API UEventHandlerInterface : public UInterface
{
GENERATED_BODY()
/*public:
UEventHandlerInterface() {}
virtual ~UEventHandlerInterface() {}*/
};
class EVENTCENTERPLUGIN_API IEventHandlerInterface
{
GENERATED_BODY()
public:
// Virtual functions cannot have UFUNCTION specifiers.
//virtual void HandleEvent(FString channel, FString message) = 0;
// ^ NB(bt): Designation of "= 0" makes this a pure virtual method,
// and it also makes the class abstract.
// Event callback for events dispatched with EventCenterPlugin.
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Event Callback")
void HandleEvent2(const FString& channel, const FString& message);
};
@@ -7,6 +7,9 @@
// RedisPlugin // RedisPlugin
#include "RedisObject.h" #include "RedisObject.h"
// Event system
#include "EventHandlerInterface.h"
// BlueprintWebSocketPlugin // BlueprintWebSocketPlugin
// Docs: https://github.com/Pandoa/BlueprintWebSocket // Docs: https://github.com/Pandoa/BlueprintWebSocket
#include "BlueprintWebSocketWrapper.h" #include "BlueprintWebSocketWrapper.h"
@@ -16,6 +19,8 @@
DECLARE_LOG_CATEGORY_EXTERN(EventSourceLog, Log, All); DECLARE_LOG_CATEGORY_EXTERN(EventSourceLog, Log, All);
// TODO(bt): Make this not an actor. Consider UObject. Also consider a singleton.
/** /**
* AEventSourceActor * AEventSourceActor
* *
@@ -24,6 +29,8 @@ DECLARE_LOG_CATEGORY_EXTERN(EventSourceLog, Log, All);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FMySubscriberDelegate, FString, Channel, FString, Message); 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. // NB(bt): "Blueprintable" seems to remove the ability to add BP event nodes.
UCLASS() UCLASS()
class EVENTCENTERPLUGIN_API AEventSourceActor : public AActor class EVENTCENTERPLUGIN_API AEventSourceActor : public AActor
@@ -63,14 +70,31 @@ public:
UFUNCTION(BlueprintCallable, Category = "EventCenter") UFUNCTION(BlueprintCallable, Category = "EventCenter")
void Unsubscribe(const FString& Channel); void Unsubscribe(const FString& Channel);
/** Again, what do. */ /** This allows event binding from blueprint. */
UPROPERTY(BlueprintAssignable) UPROPERTY(BlueprintAssignable)
FMySubscriberDelegate MyDelegate; 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.*/ /** Set up websockets.*/
UFUNCTION(BlueprintCallable, Category = "EventCenter") UFUNCTION(BlueprintCallable, Category = "EventCenter")
void SetupWebSockets(); void SetupWebSockets();
/** This is an experiment */
//UFUNCTION(BlueprintCallable, Category = "EventCenter")
//void AssignEvent(FString& eventName, TScriptInterface<IEventHandlerInterface> handler);
// This won't work because we don't know what the function is
//void AssignEvent2(FString& eventName, UObject& object, FMySubscriberRefDelegate& theDelegate);
//FMySubscriberRefDelegate MyDelegate2;
protected: protected:
// Called when the game starts or when spawned // Called when the game starts or when spawned
virtual void BeginPlay() override; virtual void BeginPlay() override;
@@ -91,6 +115,11 @@ private:
UPROPERTY() UPROPERTY()
UBlueprintWebSocket* WebSocket; UBlueprintWebSocket* WebSocket;
// TODO(bt): Value type can be wrapped in a class to add additional routing.
// Multiple delegation signatures, "pause/muting", etc.
TMap<FString, FMySubscriberRefDelegate> delegateSubscriptions;
// Handle callbacks. // Handle callbacks.
// NB(bt): Declared 'UFUNCTION()' so reflection enables as a delegate callback. // NB(bt): Declared 'UFUNCTION()' so reflection enables as a delegate callback.
UFUNCTION() UFUNCTION()
+2 -2
View File
@@ -76,7 +76,7 @@ async def server(websocket, path):
#await websocket.send("Got message: {}".format(msg)) #await websocket.send("Got message: {}".format(msg))
print('enqueuing websocket message', flush=True) print('enqueuing websocket message', flush=True)
queue.put_nowait(msg) queue.put_nowait("{}|{}".format(channel, msg))
asyncio.ensure_future(reader(mpsc)) asyncio.ensure_future(reader(mpsc))
@@ -98,7 +98,7 @@ async def server(websocket, path):
await websocket.send("Websocket ping") await websocket.send("Websocket ping")
if not queue.empty(): if not queue.empty():
msg = queue.get_nowait() # TODO THROWS EXCEPTION msg = queue.get_nowait() # TODO THROWS EXCEPTION IF EMPTY (data race?)
print('sending pubsub msg over websocket: {}'.format(msg), flush=True) print('sending pubsub msg over websocket: {}'.format(msg), flush=True)
await websocket.send(msg) await websocket.send(msg)