diff --git a/SocketIOClient.uplugin b/SocketIOClient.uplugin index eb0d7e51..6d0c44ac 100644 --- a/SocketIOClient.uplugin +++ b/SocketIOClient.uplugin @@ -1,7 +1,7 @@ { "FileVersion": 3, "Version": 1, - "VersionName": "0.2.0", + "VersionName": "0.2.1", "FriendlyName": "SocketIOClient", "Description": "Socket IO C++ Client ported to UE4", "Category": "Networking", diff --git a/Source/SocketIOClient/Private/SocketIOClientComponent.cpp b/Source/SocketIOClient/Private/SocketIOClientComponent.cpp index 2ca8cd5c..c1b04cb2 100644 --- a/Source/SocketIOClient/Private/SocketIOClientComponent.cpp +++ b/Source/SocketIOClient/Private/SocketIOClientComponent.cpp @@ -11,11 +11,12 @@ USocketIOClientComponent::USocketIOClientComponent(const FObjectInitializer &ini bWantsInitializeComponent = true; bAutoActivate = true; AddressAndPort = FString(TEXT("http://localhost:3000")); //default to 127.0.0.1 + SessionId = FString(TEXT("invalid")); } std::string StdString(FString UEString) { - return std::string(TCHAR_TO_UTF8(*UEString)); + return std::string(TCHAR_TO_UTF8(*UEString)); //TCHAR_TO_ANSI try this string instead? } FString FStringFromStd(std::string StdString) { @@ -34,19 +35,41 @@ void USocketIOClientComponent::Connect(FString InAddressAndPort) //Connect to the server on a background thread FSIOLambdaRunnable::RunLambdaOnBackGroundThread([&] { - //Attach the specific events - BindLambdaToEvent([&] - { - UE_LOG(LogTemp, Log, TEXT("SocketIO Connected")); - OnConnected.Broadcast(); - }, FString(TEXT("connected")), FString(TEXT("/"))); //you need to emit this on the server to receive the event + //Attach the specific connection status events events + + PrivateClient.set_open_listener(sio::client::con_listener([&]() { + SessionId = FStringFromStd(PrivateClient.get_sessionid()); + UE_LOG(LogTemp, Log, TEXT("SocketIO Connected with session: %s"), *SessionId); + OnConnected.Broadcast(SessionId); + })); - BindLambdaToEvent([&] + PrivateClient.set_close_listener(sio::client::close_listener([&](sio::client::close_reason const& reason) { + SessionId = FString(TEXT("invalid")); UE_LOG(LogTemp, Log, TEXT("SocketIO Disconnected")); - OnDisconnected.Broadcast(); - }, FString(TEXT("disconnect")), FString(TEXT("/"))); //doesn't get called atm, unsure how to get it called + OnDisconnected.Broadcast((EConnectionCloseReason)reason); + })); + + PrivateClient.set_socket_open_listener(sio::client::socket_listener([&](std::string const& nsp) + { + FString Namespace = FStringFromStd(nsp); + UE_LOG(LogTemp, Log, TEXT("SocketIO connected to namespace: %s"), *Namespace); + OnSocketNamespaceConnected.Broadcast(Namespace); + })); + + PrivateClient.set_socket_close_listener(sio::client::socket_listener([&](std::string const& nsp) + { + FString Namespace = FStringFromStd(nsp); + UE_LOG(LogTemp, Log, TEXT("SocketIO disconnected from namespace: %s"), *Namespace); + OnSocketNamespaceDisconnected.Broadcast(FStringFromStd(nsp)); + })); + PrivateClient.set_fail_listener(sio::client::con_listener([&]() + { + UE_LOG(LogTemp, Log, TEXT("SocketIO failed to connect.")); + OnFail.Broadcast(); + })); + PrivateClient.connect(StdAddressString); }); } @@ -54,7 +77,12 @@ void USocketIOClientComponent::Connect(FString InAddressAndPort) void USocketIOClientComponent::Disconnect() { - PrivateClient.close(); + if (PrivateClient.opened()) + { + PrivateClient.socket()->off_all(); + PrivateClient.socket()->off_error(); + PrivateClient.close(); + } } void USocketIOClientComponent::Emit(FString Name, FString Data, FString Namespace /* = FString(TEXT("/"))*/) @@ -64,6 +92,11 @@ void USocketIOClientComponent::Emit(FString Name, FString Data, FString Namespac } +void USocketIOClientComponent::EmitBuffer(FString Name, uint8* Data, int32 DataLength, FString Namespace /*= FString(TEXT("/"))*/) +{ + PrivateClient.socket(StdString(Namespace))->emit(StdString(Name), std::make_shared((char*)Data, DataLength)); +} + void USocketIOClientComponent::BindEvent(FString Name, FString Namespace /*= FString(TEXT("/"))*/) { BindDataLambdaToEvent([&](const FString& EventName, const FString& EventData) @@ -128,6 +161,56 @@ void USocketIOClientComponent::BindDataLambdaToEvent( } +void USocketIOClientComponent::BindRawMessageLambdaToEvent(TFunction< void(const FString&, const sio::message::ptr&)> InFunction, FString Name, FString Namespace /*= FString(TEXT("/"))*/) +{ + const TFunction< void(const FString&, const sio::message::ptr&)> SafeFunction = InFunction; //copy the function so it remains in context + + PrivateClient.socket(StdString(Namespace))->on( + StdString(Name), + sio::socket::event_listener_aux( + [&, SafeFunction](std::string const& name, sio::message::ptr const& data, bool isAck, sio::message::list &ack_resp) + { + const FString SafeName = FStringFromStd(name); + + FFunctionGraphTask::CreateAndDispatchWhenReady([&, SafeFunction, SafeName, data] + { + SafeFunction(SafeName, data); + }, TStatId(), nullptr, ENamedThreads::GameThread); + })); +} + + +void USocketIOClientComponent::BindBinaryMessageLambdaToEvent(TFunction< void(const FString&, const TArray&)> InFunction, FString Name, FString Namespace /*= FString(TEXT("/"))*/) +{ + const TFunction< void(const FString&, const TArray&)> SafeFunction = InFunction; //copy the function so it remains in context + + PrivateClient.socket(StdString(Namespace))->on( + StdString(Name), + sio::socket::event_listener_aux( + [&, SafeFunction](std::string const& name, sio::message::ptr const& data, bool isAck, sio::message::list &ack_resp) + { + const FString SafeName = FStringFromStd(name); + + //Construct raw buffer + if (data->get_flag() == sio::message::flag_binary) + { + TArray Buffer; + int32 BufferSize = data->get_binary()->size(); + auto MessageBuffer = data->get_binary(); + Buffer.Append((uint8*)(MessageBuffer->data()), BufferSize); + + FFunctionGraphTask::CreateAndDispatchWhenReady([&, SafeFunction, SafeName, Buffer] + { + SafeFunction(SafeName, Buffer); + }, TStatId(), nullptr, ENamedThreads::GameThread); + } + else + { + UE_LOG(LogTemp, Warning, TEXT("Non-binary message received to binary message lambda, check server message data!")); + } + })); +} + //Todo: add object -> json conversion, or convert it to a UEJSON object that can stringify sio::message::ptr USocketIOClientComponent::getMessage(const std::string& json) diff --git a/Source/SocketIOClient/Private/SocketIOClientPrivatePCH.h b/Source/SocketIOClient/Private/SocketIOClientPrivatePCH.h index df9f0a4a..144f5861 100644 --- a/Source/SocketIOClient/Private/SocketIOClientPrivatePCH.h +++ b/Source/SocketIOClient/Private/SocketIOClientPrivatePCH.h @@ -4,6 +4,7 @@ #include "Core.h" #include "Engine.h" +#include "Object.h" // You should place include statements to your module's private header files here. You only need to // add includes for headers that are used in most of your module's source files though. \ No newline at end of file diff --git a/Source/SocketIOClient/Public/SocketIOClientComponent.h b/Source/SocketIOClient/Public/SocketIOClientComponent.h index 2202b048..5624fbd6 100644 --- a/Source/SocketIOClient/Public/SocketIOClientComponent.h +++ b/Source/SocketIOClient/Public/SocketIOClientComponent.h @@ -4,7 +4,43 @@ #include "Components/ActorComponent.h" #include "SocketIOClientComponent.generated.h" +UENUM(BlueprintType) +enum EMessageTypeFlag +{ + FLAG_INTEGER, + FLAG_DOUBLE, + FLAG_STRING, + FLAG_BINARY, + FLAG_ARRAY, + FLAG_OBJECT, + FLAG_BOOLEAN, + FLAG_NULL +}; + +//TODO: convert sio::message to UE struct for more flexible use +USTRUCT() +struct FSIOMessage +{ + GENERATED_USTRUCT_BODY() + + UPROPERTY(BlueprintReadWrite, Category = "SocketIO Message Properties") + TEnumAsByte MessageFlag; + + //Internal UE storage + FJsonObject Object; +}; + +UENUM(BlueprintType) +enum EConnectionCloseReason +{ + CLOSE_REASON_NORMAL, + CLOSE_REASON_DROP +}; + DECLARE_DYNAMIC_MULTICAST_DELEGATE(FSIOCEventSignature); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FSIOCSocketEventSignature, FString, Namespace); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FSIOCOpenEventSignature, FString, SessionId); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FSIOCCloseEventSignature, TEnumAsByte, Reason); DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FSIOCNameDataEventSignature, FString, Name, FString, Data); UCLASS(ClassGroup = "Networking", meta = (BlueprintSpawnableComponent)) @@ -15,31 +51,43 @@ class SOCKETIOCLIENT_API USocketIOClientComponent : public UActorComponent //Async events UPROPERTY(BlueprintAssignable, Category = "SocketIO Events") - FSIOCEventSignature OnConnected; + FSIOCOpenEventSignature OnConnected; + + UPROPERTY(BlueprintAssignable, Category = "SocketIO Events") + FSIOCCloseEventSignature OnDisconnected; + + UPROPERTY(BlueprintAssignable, Category = "SocketIO Events") + FSIOCSocketEventSignature OnSocketNamespaceConnected; + + UPROPERTY(BlueprintAssignable, Category = "SocketIO Events") + FSIOCSocketEventSignature OnSocketNamespaceDisconnected; UPROPERTY(BlueprintAssignable, Category = "SocketIO Events") - FSIOCEventSignature OnDisconnected; + FSIOCEventSignature OnFail; UPROPERTY(BlueprintAssignable, Category = "SocketIO Events") - FSIOCNameDataEventSignature On; + FSIOCNameDataEventSignature On; //Default properties UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = defaults) - FString AddressAndPort; + FString AddressAndPort; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = defaults) - bool ShouldAutoConnect; + bool ShouldAutoConnect; + + UPROPERTY(BlueprintReadWrite, Category = "SocketIO Properties") + FString SessionId; /** - * Connect to a socket.io server, optional if autoconnect is set + * Connect to a socket.io server, optional if auto-connect is set * * @param AddressAndPort the address in URL format with port */ UFUNCTION(BlueprintCallable, Category = "SocketIO Functions") - void Connect(FString InAddressAndPort); + void Connect(FString InAddressAndPort); UFUNCTION(BlueprintCallable, Category = "SocketIO Functions") - void Disconnect(); + void Disconnect(); /** * Emit a string event with a string action @@ -48,7 +96,10 @@ class SOCKETIOCLIENT_API USocketIOClientComponent : public UActorComponent * @param Data Data string */ UFUNCTION(BlueprintCallable, Category = "SocketIO Functions") - void Emit(FString Name, FString Data, FString Namespace = FString(TEXT("/"))); + void Emit(FString Name, FString Data, FString Namespace = FString(TEXT("/"))); + + //Binary data version, only available in C++ + void EmitBuffer(FString Name, uint8* Data, int32 DataLength, FString Namespace = FString(TEXT("/"))); /** * Emit a string event with a string action @@ -57,7 +108,7 @@ class SOCKETIOCLIENT_API USocketIOClientComponent : public UActorComponent * @param Namespace Optional namespace, defaults to default namespace */ UFUNCTION(BlueprintCallable, Category = "SocketIO Functions") - void BindEvent(FString Name, FString Namespace = FString(TEXT("/"))); + void BindEvent(FString Name, FString Namespace = FString(TEXT("/"))); virtual void InitializeComponent() override; @@ -69,6 +120,10 @@ class SOCKETIOCLIENT_API USocketIOClientComponent : public UActorComponent //When you care about the data you get void BindDataLambdaToEvent(TFunction< void(const FString&, const FString&)> InFunction, FString Name, FString Namespace = FString(TEXT("/"))); + //Typically for binary data events + void BindRawMessageLambdaToEvent(TFunction< void(const FString&, const sio::message::ptr&)> InFunction, FString Name, FString Namespace = FString(TEXT("/"))); + void BindBinaryMessageLambdaToEvent(TFunction< void(const FString&, const TArray&)> InFunction, FString Name, FString Namespace = FString(TEXT("/"))); + protected: sio::client PrivateClient; diff --git a/Source/SocketIOClient/SocketIOClient.Build.cs b/Source/SocketIOClient/SocketIOClient.Build.cs index 939ca729..536a79d6 100644 --- a/Source/SocketIOClient/SocketIOClient.Build.cs +++ b/Source/SocketIOClient/SocketIOClient.Build.cs @@ -67,6 +67,7 @@ public SocketIOClient(TargetInfo Target) new string[] { "Core", + "JSON", // ... add other public dependencies that you statically link with here ... } );