mirror of
https://github.com/storytold/EventCenterPlugin.git
synced 2026-10-09 00:09:42 +00:00
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:
@@ -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()
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user