Code Monkey home page Code Monkey logo

socketioclient-unreal's Introduction

SocketIOClient-Unreal

Socket.IO client plugin for the Unreal Engine.

GitHub release Github All Releases

Socket.IO is a performant real-time bi-directional communication library. There are two parts, the server written in node.js and the client typically javascript for the web. There are alternative client implementations and this repo uses the C++11 client library ported to Unreal.

Socket.IO Lib uses asio, rapidjson, and websocketpp. SIOJson is originally forked from ufna's VaRest

Unreal Forum Thread

Discord Server

Recommended socket.io server version: 3.0+

Tip: This is a sizeable readme, quickly find your topic with Ctrl+F and a search term e.g. namespaces

Contribute! Current Main Issues:

Current platform issues:

Current TLS/SSL issues:

  • Certificate verification is not implemented; setting bShouldVerifyTLSCertificate will always fail - see issue 303

Socket.IO Server Compatibility

Some features in later versions of this plugin are not supported by earlier versions of the Socket.IO server API. See the compatibility table below for more details

UE4 Socket.IO plugin version Socket.IO server version
1.x / 2.x 3.x / 4.x
v1.5.5 and earlier YES NO
v1.6.0 and later NO YES

Quick Install & Setup

Via Github Releases

  1. Download Latest Release
  2. Create new or choose project.
  3. Browse to your project folder (typically found at Documents/Unreal Project/{Your Project Root})
  4. Copy Plugins folder into your Project root.
  5. Plugin should be now ready to use.

Via Unreal Engine Marketplace (helps support plugin development and maintenance)

Available at this link: Socket.IO Client - Marketplace

Via Git clone

  1. Create new or choose project.
  2. Browse to your project folder (typically found at Documents/Unreal Project/{Your Project Root})
  3. Create a Plugins in your project root folder and use that path for step 4. command.
  4. Git clone. Repository uses submodules, so recommended command is:

git clone https://github.com/getnamo/SocketIOClient-Unreal.git --recurse-submodules

  1. Build your project (needs to be c++/mixed) and then the plugin will get built with it.

Example Project - Chat

For an example project check out https://github.com/getnamo/SocketIOClient-Unreal-example which contains both server and client parts required to try out socket.io based chat, from Unreal to any other client and vice versa.

How to use - BP Basics

Add the SocketIO Client Component to your blueprint actor of choice

IMG

By default the component will auto connect on begin play to your default address and port http://localhost:3000. You can change this default address to connect to your service instead as well as add any query/header or other URL parameters you need.

IMG

If you want to connect at your own time, you change the default variable Should Auto Connect to false and then call Connect with your address

Receiving an Event

There are three main ways to receive socket.io events.

Receive To Function

The recommended way is to bind an event directly to a function or custom event. E.g. receiving the event "chatMessage" with a String parameter.

IMG

Keep in mind that you can have this be a proper function instead of a custom event

IMG

For the receiving type, if it's known, you can specify the exact type (like String in the example above see https://github.com/getnamo/SocketIOClient-Unreal#emit-with-callback for supported signatures), or if you're not sure or it's a complex type (e.g. a struct) you set it to a SIOJsonValue and use functions to decode it (see https://github.com/getnamo/SocketIOClient-Unreal#decoding-responses for details)

IMG

Receive To Delegate

The other recommended way (available since v2.2.0) is to bind your event directly to a delegate.

IMG

See https://github.com/getnamo/SocketIOClient-Unreal#decoding-responses for data conversion nodes from SIOJsonValues.

Receive To Generic Event

You can also receive an event to a generic unreal event. First you bind the socket.io event to the generic event.

IMG

and then you receive it and filter the results by checking against the Event Name.

IMG

Sending data or Emitting Events to Server

If you want to send information to the server, emit events on the SocketIO Client Component, e.g. pressing M to emit a 'chat message' string

IMG

Note on Printing Json Value

A very common mistake is to drag from a SIOJsonValue to the print string for basic debug. By default the engine will pick Get Display Name, this is incorrect as it will only print out the container objects engine name and nothing about the actual value. Instead you want to use either Encode Json or As String(SIOJson Value), either of the two will re-encode the value as a json string. Encode Json is also available for SIOJsonObject types.

printing a value

Blueprint - Advanced

Simple Json

You can formulate any SIOJsonValue directly in blueprint. Apart from native basic types which are supported directly via conversion and e.g. Construct Json String Value, you can construct SIOJsonObjects and fill their fields.

IMG

Start with Construct Json Object then set any desired fields. In this instance we wanted to make the JSON {"myString":"Hello"} so we used Set String Field and then auto-convert the object into a message.

Complex Json

By combining arrays and objects you can form almost any data type, nest away!

IMG

Structs

The plugin supports making SIOJsonValues from any unreal structs you make, including ones defined purely in blueprints!

An easy example of a familiar struct is the Vector type

IMG

But you can make a custom type and emit it or nest it inside other SIOJsonValues which should make it easy to organize your data however you want it.

IMG

Binary

Socket.IO spec supports raw binary data types and these should be capable of being mixed in with other JSON types as usual. This plugin supports binaries as a first class citizen in blueprints and any arrays of bytes can be embedded and decoded in the chain as expected.

IMG

Since v1.2.6 binaries (byte arrays) are fully supported inside structs as well.

Binary to base64 string fallback (since v1.2.6)

If you encode a SIOJsonValue or SIOJsonObject to JSON string (i.e. not using socket.io protocol for transmission) then binaries will get encoded in base64. Conversely passing in a SIOJsonValue of string type for decoding into a binary target (e.g. Get Binary Field) will attempt base64 decoding of that string to allow for non-socket.io protocol fallback. Keep in mind that base64 encoding has a 33% overhead (6/8 useful bits).

Decoding Responses

There are many ways to decode your SIOJsonValue message, it all depends on your data setup. You can even decode your JsonObjects directly into structs, if the JSON structure has matching variable names.

IMG

Make sure your server is sending the correct type of data, you should not be encoding your data into json strings on the server side if you want to decode them directly into objects in the Unreal side, send the objects as is and the socket.io protocol will handle serialization on both ends.

Json Object to Struct Example

Keep in mind that you need to match your json object names (case doesn't matter) and if you are using objects of objects, the sub-objects will need their own structs to build the full main struct. For example if we have the following json object:

{
	"Title":
	{
		"Text":"Example",
		"Caption":"Used to guide developers"
	}
}
Defined Struct

Make a substruct for the Title object with two string variables, Text and Caption matching your json object format.

title substruct

and then make the main struct with the substruct and a member variable with the Title property name to match your json.

main struct

Alternative Struct

If you know that you will have a set of properties of the same type (blueprints only support maps of same type), then you can use a Map property. In this example our title sub-object only has String:String type properties so we could use a String:String map named Title instead of the sub-struct.

alt struct

If you wish to mix types in an object, you will need to use defined structs as before.

Arrays of array

An Array of arrays is not supported in Unreal structs, a workaround is to use an array of structs that contains an array of the data type you want. The server side will need to adapt what it sends or you can decode using json fields.

Conversion

Most primitive types have auto-conversion nodes to simplify your workflow. E.g. if you wanted to emit a float literal you can get a reference to your float and simply drag to the message parameter to auto-convert into a SIOJsonValue.

IMG

Supported auto-conversion

  • Float
  • Int
  • Bool
  • SIOJsonObject
  • String -technically supported but it will by default pick Get Display Name instead, use As String to get desired result

Emit with Callback

You can have a callback when, for example, you need an acknowledgement or if you're fetching data from the server. You can respond to this callback straight in your blueprint. Keep in mind that the server can only use the callback once per emit.

IMG

Instead of using Emit you use Emit With Callback and by default the target is the calling blueprint so you can leave that parameter blank and simply type your function name e.g. OnEcho function.

IMG

If the function is missing or named incorrectly, your output log will warn you.

IMG

Your function expects a SIOJsonValue reference signature. By default this contains your first response value from you callback parameter. If your callback uses more than one parameter, make a second SIOJsonValue Input parameter which contains an array of all the responses.

Since 0.6.8, if you know your data type you can use that signature directly in your function name. E.g. if you're sending a callback with a float value you can make a function with the matching name and only one float parameter as your signature.

Supported Signatures:

  • SIOJsonValue
  • SIOJsonObject
  • String
  • Float
  • Int
  • Bool
  • Byte Array

Emit With Graph Callback

Since v1.1.0 you can get results directly to your calling graph function. Use the Emit with Graph Callback method and simply drag off from the completed node to receive your result when your server uses the callback. This feature should be useful for fetching data from the server without breaking your graph flow.

graph callback

Limitations:

  • Can only be used in Event Graphs (BP functions don't support latent actions)

Emit with Callback node.js server example

If you're unsure how the callbacks look like on the server side, here is a basic example:

const io = require('socket.io')(http);

io.on('connection', (socket) => {
	
	//...

	socket.on('getData', (msg, callback) => {
		//let's say your data is an object
		let result = {};
	
		/* do something here to get your data */
		
		//callback with the results, this will call your bound function inside your blueprint
		callback(result);
	});
};

See https://socket.io/docs/server-api/#socket-on-eventName-callback for detailed API.

Binding Events to Functions

Instead of using the event graph and comparing strings, you can bind an event directly to a function. The format to make the function is the same as callbacks.

IMG

Receiving Events on non-game thread

Since v1.1.0 use Bind Event to Function and change the thread override option to Use Network Thread.

NB: You cannot make or destroy uobjects on non-gamethreads and be mindful of your access patterns across threads to ensure you don't get a race condition. See https://github.com/getnamo/SocketIOClient-Unreal#blueprint-multithreading for other types of threading utility.

Namespaces

Before v1.2.3 you can only join namespaces via using Emit and Bind Event with a namespace specified. This will auto-join your namespace of choice upon calling either function.

Example join emit

A good place to bind your namespace functions is using the dedicated namespace connected/disconnected events

namespace events

Below is an example of binding a namespace event when you've connected to it. Keep in mind you'd need to join the namespace for this event to get bound and you can bind it elsewhere e.g. on beginplay instead if preferred.

namespace bind event

Since v1.2.3 you can now also join and leave a namespace explicitly using Join Namespace and Leave Namespace functions.

join and leave namespaces

Rooms

The Rooms functionality is solely based on server implementation, see Socket.IO api for details: https://socket.io/docs/v4/rooms/.

Generally speaking you can have some kind of event to emit to your server specifying the unreal client wants to join or leave a room and then the server would handle that request for you. If you wanted to emit a message to a specific user in a room you'd need a way to get a list of possible users (e.g. get the list on joining the room or via a callback). Then selecting a user from the list and passing their id along with desired data in an emit call to the server which would forward the data to the user in the room you've joined.

See this basic tutorial for implementing rooms on the server.

Complex Connect

You can fill the optional Query and Headers parameters to pass in e.g. your own headers for authentication.

The input type for both fields is a SIOJsonObject with purely string fields or leaving it empty for default.

Here's an example of constructing a single header X-Forwarded-Host: qnova.io and then connecting.

connectwithheader

Since v2.3.0 you can also connect using a SIOConnectParams struct

connectwithparams

Plugin Scoped Connection

If you want your connection to survive level transitions, you can tick the class default option Plugin Scoped Connection. Then if another component has the same plugin scoped id, it will re-use the same connection. Note that if this option is enabled the connection will not auto-disconnect on End Play and you will need to either manually disconnect or the connection will finally disconnect when the program exits.

plugin scoped connection

This does mean that you may not receive events during times your actor does not have a world (such as a level transition without using a persistent parent map to which the socket.io component actor belongs). If this doesn't work for you consider switching to C++ and using FSocketIONative, which doesn't doesn't depend on using an actor component.

Statically Constructed SocketIOClient Component

Since v1.1.0 there is a BPFunctionLibrary method Construct SocketIOComponent which creates and correctly initializes the component in various execution contexts. This allows you to add and reference a SocketIOClient component inside a non-actor blueprint. Below is an example use pattern. It's important to save the result from the construct function into a member variable of your blueprint or the component will be garbage collected.

static example

Note on Auto-connect

Game modes do have actor owners and will correctly respect bShouldAutoConnect. The connection happens one tick after construction so you can disable the toggle and connect at own time.

Game Instances do not have actor owners and therefore cannot register and initialize the component. The only drawback is that you must manually connect. bShouldAutoConnect is disabled in this context.

Note on Emit with Graph Callback

Non actor-owners such as Game Instances cannot receive the graph callbacks due to invalid world context. This only affects this one callback method, other methods work as usual.

TLS / SSL

TLS is supported for both C++ and BP if your platform supports OpenSSL (see https://github.com/getnamo/SocketIOClient-Unreal/blob/master/Source/SocketIOLib/SocketIOLib.Build.cs#L64 for currently supported platforms). Simply use a https or wss URL for host target and it will use TLS by default. You can also force TLS on any URL by using bForceTLS set to true.

Gist with example node.js TLS server and instructions for self signing certificate for testing purposes: https://gist.github.com/getnamo/fe6c9574dc971066813fd291c363ee04

NB: Certificate verification is currently not implemented; bShouldVerifyTLSCertificate is set to false by default, setting it to true will currently cause connections to fail. See issue 303.

CoreUtility

Plugin contains the CoreUtility module with a variety of useful C++ and blueprint utilities.

CUFileComponent

Provides and easy way to save/load files to common project directories. Example usecase: Encode a received message to JSON and pass the bytes in to SaveBytesToFile to store your response.

See https://github.com/getnamo/SocketIOClient-Unreal/blob/master/Source/CoreUtility/Public/CUFileComponent.h for details.

CULambdaRunnable

A wrapper for simple multi-threading in C++. Used all over the plugin to handle threading with lambda captured scopes and simpler latent structs for bp calls. Below is an example of a call to a background thread and return to gamethread:

//Assuming you're in game thread
FCULambdaRunnable::RunLambdaOnBackGroundThread([]
{
	//Now you're in a background thread
	//... do some calculation and let's callback a result
	int SomeResult;
	FCULambdaRunnable::RunShortLambdaOnGameThread([SomeResult]
	{
		//You're back to the game thread
		//... display or do something with the result safely
	});
});

See https://github.com/getnamo/SocketIOClient-Unreal/blob/master/Source/CoreUtility/Public/CULambdaRunnable.h for full API.

For blueprint multi-threading see https://github.com/getnamo/SocketIOClient-Unreal#blueprint-multithreading.

CUMeasureTimer

Static string tagged measurement of durations. Useful for sprinkling your code with duration messages for optimization. CUBlueprintLibrary exposes this utility to blueprint.

See https://github.com/getnamo/SocketIOClient-Unreal/blob/master/Source/CoreUtility/Public/CUBlueprintLibrary.h for details.

CUBlueprintLibrary

Global blueprint utilities.

  • Conversions: String<->Bytes, Texture2D<->Bytes, Opus<->Wav, Wav<->Soundwave
  • Time string
  • Unique ID
  • Measurement timers based on CUMeasureTimer
  • Blueprint multithreading calls
Blueprint Multithreading

Enables easy calling of blueprint functions on background threads and returning back to the game thread to deal with the results. This enables long running operations to not block the game thread while they work.

Two variants are available as of v1.2.8: Call Function On Thread and Call Function on Thread Graph Return. Generally the latent variant is recommended for easier function chaining and it will always return on the game thread when finished.

In the example below we use the latent variant to call two background tasks in succession, return to read the result on the game thread and measure the overall time taken.

latent multithreading (click image to see video of performance)

The first task prepares an array with a million floats, the second sums them up. This should take ~1 sec to run, but from the video above you can see that it doesn't block the game thread at all. We use class member variables to pass data between threads. It's important to only have one function modifying the same data at any time and you cannot make or destroy UObjects on background threads so it may be necessary to callback to the gamethread to do allocations if you use UObjects.

CUOpusCoder

Standalone opus coder that doesn't depend on the online subsystem. Useful for custom VOIP solutions.

See https://github.com/getnamo/SocketIOClient-Unreal/blob/master/Source/CoreUtility/Public/CUOpusCoder.h for details.

How to use - C++

Setup

To use the C++ code from the plugin add SocketIOClient, SocketIOLib, and Json as dependency modules in your project build.cs. If you're using conversion methods you will also have to add SIOJson.

PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "SocketIOClient", "SocketIOLib", "Json", "SIOJson" });

This guide assumes you want to use the client component method. See the FSocketIONative section for non-actor based C++ details.

#include "SocketIOClientComponent.h" and add USocketIoClientComponent to your actor of choice via e.g. a UProperty

and CreateDefaultSubobject in your constructor

SIOClientComponent = CreateDefaultSubobject<USocketIOClientComponent>(TEXT("SocketIOClientComponent"));

or reference it from another component by getting it on begin play e.g.

SIOClientComponent = Cast<USocketIOClientComponent>(this->GetOwner()->GetComponentByClass(USocketIOClientComponent::StaticClass()));
if (!SIOClientComponent)
{
	UE_LOG(LogTemp, Warning, TEXT("No sister socket IO component found"));
	return;
}
else
{
	UE_LOG(LogTemp, Log, TEXT("Found SIOClientComponent: %s"), *SIOComponent->GetDesc());
}

Connect / Disconnect

To connect simply change your address, the component will auto-connect on component initialization.

USocketIOClientComponent* SIOClientComponent; //get a reference or add as subobject in your actor

//the component will autoconnect, but you may wish to change the url before it does that via
SIOClientComponent->AddressAndPort = TEXT("http://127.0.0.1:3000"); //change your address

You can also connect at your own time by disabling auto-connect and connecting either to the default address or a custom one

//you can also disable auto connect and connect it at your own time via
SIOClientComponent->ShouldAutoConnect = false;
SIOClientComponent->Connect(); 

//You can also easily disconnect at some point, reconnect to another address
SIOClientComponent->Disconnect();
SIOClientComponent->Connect(TEXT("http://127.0.0.1:3000"));

Receiving Events

To receive events call OnNativeEvent and pass in your expected event name and callback lambda or function with void(const FString&, const TSharedPtr<FJsonValue>&) signature. Optionally pass in another FString to specify namespace, omit if not using a namespace (default TEXT("/")).

SIOClientComponent->OnNativeEvent(TEXT("MyEvent"), [](const FString& Event, const TSharedPtr<FJsonValue>& Message)
{
	//Called when the event is received. We can e.g. log what we got
	UE_LOG(LogTemp, Log, TEXT("Received: %s"), *USIOJConvert::ToJsonString(Message));
});

Message parameter is a FJsonValue, if you have a complex message you'll most commonly want to decode it into a FJsonObject via AsObject().

Note that this is equivalent to the blueprint BindEventToFunction function and should be typically called once e.g. on beginplay.

Emitting Events

In C++ you can use EmitNative, EmitRaw, or EmitRawBinary. EmitNative is fully overloaded and expects all kinds of native Unreal data types and is the recommended method.

String

Emit an FString (or TEXT() macro).

SIOClientComponent->EmitNative(TEXT("nativeTest"), TEXT("hi"));

Number

Emit a double

SIOClientComponent->EmitNative(TEXT("nativeTest"), -3.5f);

Boolean

Emit a raw boolean

SIOClientComponent->EmitNative(TEXT("nativeTest"), true);

Binary or raw data

Emit raw data via a TArray

TArray<uint8> Buffer;	//null terminated 'Hi!'
Buffer.Add(0x48);
Buffer.Add(0x69);
Buffer.Add(0x21);
Buffer.Add(0x00);

SIOClientComponent->EmitNative(TEXT("nativeTest"), Buffer);

or

SIOComponent->EmitRawBinary(TEXT("myBinarySendEvent"), Buffer.GetData(), Buffer.Num());

FJsonObject - Simple

Option 1 - Shorthand

//Basic one field object e.g. {"myKey":"myValue"}
auto JsonObject = USIOJConvert::MakeJsonObject();								
JsonObject->SetStringField(TEXT("myKey"), TEXT("myValue"));

SIOClientComponent->EmitNative(TEXT("nativeTest"), JsonObject);

Option 2 - Standard

TSharedPtr<FJsonObject> JsonObject = MakeShareable(new FJsonObject);	

FJsonObject - Complex Example

A nested example using various methods

//All types, nested
TSharedPtr<FJsonObject> JsonObject = MakeShareable(new FJsonObject);						//make object option2
JsonObject->SetBoolField(TEXT("myBool"), false);
JsonObject->SetStringField(TEXT("myString"), TEXT("Socket.io is easy"));
JsonObject->SetNumberField(TEXT("myNumber"), 9001);

JsonObject->SetField(TEXT("myBinary1"), USIOJConvert::ToJsonValue(Buffer));				//binary option1 - shorthand
JsonObject->SetField(TEXT("myBinary2"), MakeShareable(new FJsonValueBinary(Buffer)));	//binary option2

JsonObject->SetArrayField(TEXT("myArray"), ArrayValue);
JsonObject->SetObjectField(TEXT("myNestedObject"), SmallObject);

SIOClientComponent->EmitNative(TEXT("nativeTest"), JsonObject);

Callback Example

Below is an example of emitting a simple object with the server using the passed in callback to return a response or acknowledgement.

//Make an object {"myKey":"myValue"}
TSharedPtr<FJsonObject> JsonObject = MakeShareable(new FJsonObject);
JsonObject->SetStringField(TEXT("myKey"), TEXT("myValue"));

//Show what we emitted
UE_LOG(LogTemp, Log, TEXT("1) Made a simple object and emitted: %s"), *USIOJConvert::ToJsonString(JsonObject));

//Emit event "callbackTest" expecting an echo callback with the object we sent
SIOClientComponent->EmitNative(TEXT("callbackTest"), JsonObject, [&](auto Response)
{
	//Response is an array of JsonValues, in our case we expect an object response, grab first element as an object.
	auto Message = Response[0]->AsObject();

	//Show what we received
	UE_LOG(LogTemp, Log, TEXT("2) Received a response: %s"), *USIOJConvert::ToJsonString(Message));
});

UStruct

Plugin supports automatic conversion to/from UStructs, below is an example of a struct roundtrip, being in Json format on the server side.

USTRUCT()
struct FTestCppStruct
{
	GENERATED_BODY()

	UPROPERTY()
	int32 Index;

	UPROPERTY()
	float SomeNumber;

	UPROPERTY()
	FString Name;
};
//Set your struct variables
FTestCppStruct TestStruct;
TestStruct.Name = TEXT("George");
TestStruct.Index = 5;
TestStruct.SomeNumber = 5.123f;

SIOClientComponent->EmitNative(TEXT("callbackTest"),  FTestCppStruct::StaticStruct(), &TestStruct, [&](auto Response)
{
	auto Message = Response[0]->AsObject();

	//Show what we received
	UE_LOG(LogTemp, Log, TEXT("Received a response: %s"), *USIOJConvert::ToJsonString(Message));

	//Set our second struct to the new values
	USIOJConvert::JsonObjectToUStruct(Message, FTestCppStruct::StaticStruct(), &MemberStruct);

	//Show that struct
	UE_LOG(LogTemp, Log, TEXT("Our received member name is now: %s"), *MemberStruct.Name);
});

C++ FSocketIONative

If you do not wish to use Unreal AActors or UObjects, you can use the native base class FSocketIONative. Please see the class header for API. It generally follows a similar pattern to USocketIOClientComponent with the exception of native callbacks which you can for example see in use here: https://github.com/getnamo/SocketIOClient-Unreal/blob/master/Source/SocketIOClient/Private/SocketIOClientComponent.cpp#L81

Example FSocketIONative Custom Game Instance

SIOTestGameInstance.h

#include "CoreMinimal.h"
#include "Engine/GameInstance.h"
#include "SocketIONative.h"
#include "SIOTestGameInstance.generated.h"
UCLASS()
class SIOCLIENT_API USIOTestGameInstance : public UGameInstance
{
	GENERATED_BODY()

	virtual void Init() override;
	virtual void Shutdown() override;
	
	TSharedPtr<FSocketIONative> Socket;
};

SIOTestGameInstance.cpp

#include "SIOTestGameInstance.h"
#include "SocketIOClient.h"
void USIOTestGameInstance::Init()
{
	Super::Init();

	Socket= ISocketIOClientModule::Get().NewValidNativePointer();
	
	Socket->Connect("http://localhost:3000", nullptr, nullptr);

	Socket->OnEvent(TEXT("MyEvent"), [this](const FString& Event, const TSharedPtr<FJsonValue>& Message)
		{
			UE_LOG(LogTemp, Log, TEXT("Received: %s"), *USIOJConvert::ToJsonString(Message));
		});

	Socket->Emit(TEXT("MyEmit"), TEXT("hi"));
}

void USIOTestGameInstance::Shutdown()
{
	Super::Shutdown();

	if (Socket.IsValid())
	{
		ISocketIOClientModule::Get().ReleaseNativePointer(Socket);
		Socket = nullptr;
	}
}

Alternative Raw C++ Complex message using sio::message

see sio::message for how to form a raw message. Generally it supports a lot of std:: variants e.g. std::string or more complex messages e.g. socket.io c++ emit readme. Note that there are static helper functions attached to the component class to convert from std::string to FString and the reverse.

static std::string USIOMessageConvert::StdString(FString UEString);

static FString USIOMessageConvert::FStringFromStd(std::string StdString);

and assuming

FSocketIONative* NativeClient;

e.g. emitting {type:"image"} object

//create object message
auto message = sio::object_message::create();

//set map property string
message->get_map()["type"] = sio::string_message::create(std::string("image"));

//emit message
NativeClient->EmitRaw(ShareResourceEventName, message);

with a callback

NativeClient->EmitRawWithCallback(FString("myRawMessageEventWithAck"), string_message::create(username), [&](message::list const& msg) {
	//got data, handle it here
});

Receiving Events

To receive events you can bind lambdas which makes things awesomely easy e.g.

Binary

NativeClient->OnBinaryEvent([&](const FString& Name, const TArray<uint8>& Buffer)
		{
			//Do something with your buffer
		}, FString(TEXT("myBinaryReceiveEvent")));

Complex message using sio::message

See sio::message or socket.io c++ readme for examples.

e.g. expecting a result {type:"some type"}

NativeClient->OnRawEvent([&](const FString& Name, const sio::message::ptr& Message)
		{
		        //if you expected an object e.g. {}
			if (Message->get_flag() != sio::message::flag_object)
			{
				UE_LOG(LogTemp, Warning, TEXT("Warning! event did not receive expected Object."));
				return;
			}
			auto MessageMap = Message->get_map();
			
			//use the map to decode an object key e.g. type string
			auto typeMessage = MessageMap["type"];
			if (typeMessage->get_flag() == typeMessage->flag_string)
			{
				FString TypeValue = USocketIOClientComponent::FStringFromStd(typeMessage->get_string());
				
				//do something with your received type value!
			}
			
		}, FString(TEXT("myArbitraryReceiveEvent")));

Http JSON requests

Using e.g. https://gist.github.com/getnamo/ced1e6fbee122b640169f5fb867ed540 server

You can post simple JSON requests using the SIOJRequest (this is the same architecture as VARest).

Sending a JSON post request

These request functions are available globally.

Packaging

C++

Works out of the box.

Blueprint

Marketplace

Works out of the box.

Github

If you're using this as a project plugin you will need to convert your blueprint only project to mixed (bp and C++). Just add a new C++ class via Tools-> New C++ class and select anything, e.g. None -> Next -> MyClass and hit CreateClass.

Converting project to C++

After it processes, your project is now Bp/C++ mixed and building or packaging the project will automatically include any code plugins in your project.

iOS

If you're using non-ssl connections (which as of 1.0 is all that is supported), then you need to enable Allow web connections to non-HTTPS websites

IOS platform setting

Its possible you may also need to convert your IP4 to IP6, see #136 (comment)

Android

Minimum/Target SDK 21 or higher is recommended, but not required.

License

license

socketioclient-unreal's People

Contributors

anadin avatar bapin93 avatar brittanft avatar deams51 avatar dobby5 avatar finger563 avatar getnamo avatar gmpreussner avatar iambeeblebrox avatar kenshihh avatar lehuan5062 avatar lordned avatar mikeseese avatar namse avatar rwinright avatar seon-geun avatar staskjs avatar tamaynard avatar tlightsky avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

socketioclient-unreal's Issues

When support 4.14?

This plugin is very usefull ,thanks. Can you tell us when support 4.14?

multiple connections

I updated the plugin to latest version 0.5.2 and that appears to have sorted out the ue4 editor freezing everytime I was coming out of the play editor. There is however another issue that has come up, whenever I launch the server, after my : listening on port message I am getting multiple client connections - up to 20 even though I have not lauched the play editor in ue4.

Editor crashes while sending data

The editor crashes while sending data from UE:
2017-11-06 16_06_21-firstpersoncharacter
I tried both, the "emit" function and the "emit with callback" function. I have a function in place with correct input signatures to receive the callback in case of "emit with callback".

I am receiving the data correctly on the server but I believe its the acknowledgement message handling, that is making the editor crash. I am acknowledging with :
"EventCallback",{"data":"sample text"}

Unreal Editor Crashes on Stop

When clicking on Stop after testing a blueprint the entire editor freezes. The only option is to kill the process with the task manager. This occurs most of the times - making it impossible to make any progress using the socket-io plugin.

I'm using Uneal 4.14.3, socket.IO 1.7

socket.io connection timeout hang

replicated when connecting to e.g. 192.168 range ips without auto-connect and there is no server running.

see #10 for guidance of past similar issue

When this plugin can support android?

hi,
Can you tell me when you plan to add android suport?I have used this plugin for my game,I test my project in win64,it works well,but i package my project to android apk,the plugin doesn't work.I have looked #21 ,but i can't compile it by myself.
Do you have time,can you help me solve it?
This plugin is really usefull,hope you can help me.

Component tries to connect to server even in editor

Every time when I open blueprint with SocketIOComponent in it, SocketIOComponent is trying to connect to server.
Moreover, SocketIOComponent events also will be called...
If you disable bShouldAutoConnect parameter, it will not connect but still create FSocketIONative object.

Connect AddressAndPort

TL;DR Line 30 should be changed to if (InAddressAndPort.IsEmpty()).

I've been playing around with the plugin and I found a pretty major (1 line) bug.
The default usage for the Connect function is to use http://localhost:3000.
To do this, you check if the InAddressAndPort is not empty. Problem is, on Line 30, you use the default AddressAndPort which is hardcoded to use the above address, which is never empty.
Therefore, the plugin only connects to the default address.

Dedicated Server Crash

Hello =)

I posted some stuff in the other thread about a bug and crashing but after a weekend and a few hours today trying to get to the bottom of it I think I finally nailed down my issue.

Whenever something is in the world that has a socket io component on the SERVER in a dedicated server setup is when I will eventually get a crash.

Examples:

Dedicated server, character has a socket IO component, login to the server and load the map when the character spawns in the first time he is fine but if I "open 127.0.0.1" or quit and start up the client again I will get a client or server crash depending on if this is PIE or a client build. Note in this case there is a has authority and a server switch set up before I connect to the socket IO server but this doesnt change anything for this example as there is a server version of my character with the component which is why it crashes

Same setup as before but I prevent the server from having the component, I don't put the component on the character and I attach a new one only on the client version of the character. No crashes

I remove the socket IO from the character and put it on another BP in the world and all this BP does is hold the socketIO component. I did this as I originally thought the crash was due to it being on a character. I then have my character talk to this BP and the component and do it's stuff and I get the same results as the first time (crashes due to the server spawning in a copy of this BP)

If I make it so the BP only spawns in on the clients, no server, then I get no crashes.

I thought the crashes were based on disconnecting and reconnecting the client but I actually let the clients sit for a bit today which is when I discovered this issue again. If I have a server blueprint with the component and then I disconnect SocketIO or destroy the BP I get the disconnect message in my node server as expected but if I just wait about 10 seconds or so I get a crash on the dedicated server. If I do the exact same thing but the blueprint is on the client only I get no issues.

I know, long read but maybe this can help figure out something?

iOS platform untested

I'm currently building for iOS and I get the following error:

LogPlayLevel: UnrealBuildTool: In file included from /mac/gamelocation/Plugins/socketio-client-ue4/Intermediate/Build/IOS/SemiFutureDuplex2/Development/SIOJson/Module.SIOJson.cpp:2:
LogPlayLevel: UnrealBuildTool:
LogPlayLevel: UnrealBuildTool: /pc/gamelocation/Plugins/socketio-client-ue4/Source/SIOJson/Private/SIOJConvert.cpp:75:9: error: unknown pragma ignored [-Werror,-Wunknown-pragmas]
LogPlayLevel: UnrealBuildTool:
LogPlayLevel: UnrealBuildTool: #pragma endregion ToJsonValue

Connect->Disconnect->Connect actions cause game to crash

Calling Connect action on SocketIOComponent while internal SocketIOClient is disconnecting, causes game/editor to crash with log below.

From epic crash log:

...
C++ EH exception - code e06d7363 (first/second chance not available)

KERNELBASE
VCRUNTIME140
msvcp140
UE4Editor_SocketIOClient!std::thread::join()
UE4Editor_SocketIOClient!sio::client_impl::connect()
UE4Editor_SocketIOClient!<lambda_48c5bfc20a28d488412b9ebe85cab230>::operator()() [d:\unrealprojects\loc416experiments\plugins\socketio-client-ue4.x\source\socketioclient\private\socketionative.cpp:112]
UE4Editor_SocketIOClient!FSIOLambdaRunnable::Run() [d:\unrealprojects\loc416experiments\plugins\socketio-client-ue4.x\source\socketioclient\private\siolambdarunnable.cpp:48]
UE4Editor_Core!FRunnableThreadWin::Run() [d:\build\++ue4+release-4.16+compile\sync\engine\source\runtime\core\private\windows\windowsrunnablethread.cpp:76]

PS1: If you remove line 29 in SocketIONative.cpp (and fix paren on 113):

...
ConnectionThread = FSIOLambdaRunnable::RunLambdaOnBackGroundThread([&, Query, Headers]
.,.

... the game will only hang in this situation but not crash.

PS2: There is typo in SIOLambdaRunnable.cpp:

FSIOLambdaRunnable::~FSIOLambdaRunnable()
{
	if (Thread == NULL) // <<< Should be Thread != NULL ?
	{
		delete Thread;
		Thread = NULL;
	}

	//Runnables.Remove(this);
}

But it's not an answer to subj :)

Packaged game open map with changing server status

While changing the map in a packaged game while your server is up or down works, one case where the server status changes after you've loaded the map will still potentially crash.

Do we add a reachability test or use a stack allocated pointer which won't trigger CanaryFail?

One workaround is to set https://github.com/getnamo/socketio-client-ue4/blob/master/Source/SocketIOClient/Public/SocketIOClientComponent.h#L54 to false, but this will significantly slow down your disconnection process.

Socket.io crash when loading new level

Hello everyone.
Not always, but frequently the game crash because of a thread while quitting and loading a new level. I think it's the same problem or one that is similar to this one. I have a hard time to track down this bug, it also crash sometimes when we Broadcast on namespace : #55.

I think therere is a conflict when creating a new Client while the previous one is not totally destroy but not sure ?

Here are the log :

2017.09.27-07.27.46:667][579]SocketIOLog: SocketIO disconnected from namespace: /
[2017.09.27-07.27.46:667][579]SocketIOLog: namespacedisconnectcallback
[2017.09.27-07.27.46:667][579]SocketIOLog: SocketIO disconnected from namespace: /unrealclient
[2017.09.27-07.27.46:667][579]SocketIOLog: namespacedisconnectcallback
[2017.09.27-07.27.46:667][579]LogThreadingWindows:Error: Runnable thread FSIOLambdaRunnable1 crashed.
[2017.09.27-07.27.46:667][579]LogWindows:Error: === Critical error: ===
[2017.09.27-07.27.46:667][579]LogWindows:Error:
[2017.09.27-07.27.46:667][579]LogWindows:Error: Fatal error!
[2017.09.27-07.27.46:667][579]LogWindows:Error:
[2017.09.27-07.27.46:667][579]LogWindows:Error: Unhandled Exception: EXCEPTION_ACCESS_VIOLATION writing address 0x152b7b92
[2017.09.27-07.27.46:667][579]LogWindows:Error:
[2017.09.27-07.27.46:667][579]LogWindows:Error: ERAM.exe!UE4Function_Private::TFunctionRefCaller<<lambda_0457f06d8d6d5b4ff8e33b8c797dae00>,void __cdecl(void)>::Call() [c:\users\user\desktop\unrealengine\engine\source\runtime\core\public\templates\function.h:244]
[2017.09.27-07.27.46:667][579]LogWindows:Error: HandleError re-entered.
[2017.09.27-07.27.46:667][579]LogWindows:Error: ERAM.exe!TGraphTask::ExecuteTask() [c:\users\user\desktop\unrealengine\engine\source\runtime\core\public\async\taskgraphinterfaces.h:884]
[2017.09.27-07.27.46:667][579]LogWindows:Error: ERAM.exe!FNamedTaskThread::ProcessTasksNamedThread() [c:\users\user\desktop\unrealengine\engine\source\runtime\core\private\async\taskgraph.cpp:954]
[2017.09.27-07.27.46:667][579]LogWindows:Error: ERAM.exe!FNamedTaskThread::ProcessTasksUntilQuit() [c:\users\user\desktop\unrealengine\engine\source\runtime\core\private\async\taskgraph.cpp:701]
[2017.09.27-07.27.46:667][579]LogWindows:Error: ERAM.exe!FTaskGraphImplementation::WaitUntilTasksComplete() [c:\users\user\desktop\unrealengine\engine\source\runtime\core\private\async\taskgraph.cpp:1809]
[2017.09.27-07.27.46:667][579]LogWindows:Error: ERAM.exe!FPhysScene::WaitPhysScenes() [c:\users\user\desktop\unrealengine\engine\source\runtime\engine\private\physicsengine\physscene.cpp:1088]
[2017.09.27-07.27.46:667][579]LogWindows:Error: ERAM.exe!FPhysScene::EnsureCollisionTreeIsBuilt() [c:\users\user\desktop\unrealengine\engine\source\runtime\engine\private\physicsengine\physscene.cpp:1615]
[2017.09.27-07.27.46:667][579]LogWindows:Error: ERAM.exe!UWorld::FlushLevelStreaming() [c:\users\user\desktop\unrealengine\engine\source\runtime\engine\private\world.cpp:2919]
[2017.09.27-07.27.46:667][579]LogWindows:Error: ERAM.exe!UEngine::LoadMap() [c:\users\user\desktop\unrealengine\engine\source\runtime\engine\private\unrealengine.cpp:10263]
[2017.09.27-07.27.46:667][579]LogWindows:Error: ERAM.exe!UEngine::Browse() [c:\users\user\desktop\unrealengine\engine\source\runtime\engine\private\unrealengine.cpp:9574]
[2017.09.27-07.27.46:667][579]LogWindows:Error: ERAM.exe!UEngine::TickWorldTravel() [c:\users\user\desktop\unrealengine\engine\source\runtime\engine\private\unrealengine.cpp:9763]
[2017.09.27-07.27.46:667][579]LogWindows:Error: ERAM.exe!UGameEngine::Tick() [c:\users\user\desktop\unrealengine\engine\source\runtime\engine\private\gameengine.cpp:1129]
[2017.09.27-07.27.46:667][579]LogWindows:Error: ERAM.exe!FEngineLoop::Tick() [c:\users\user\desktop\unrealengine\engine\source\runtime\launch\private\launchengineloop.cpp:3119]
[2017.09.27-07.27.46:667][579]LogWindows:Error: ERAM.exe!GuardedMain() [c:\users\user\desktop\unrealengine\engine\source\runtime\launch\private\launch.cpp:166]
[2017.09.27-07.27.46:667][579]LogWindows:Error: ERAM.exe!GuardedMainWrapper() [c:\users\user\desktop\unrealengine\engine\source\runtime\launch\private\windows\launchwindows.cpp:134]
[2017.09.27-07.27.46:667][579]LogWindows:Error: ERAM.exe!WinMain() [c:\users\user\desktop\unrealengine\engine\source\runtime\launch\private\windows\launchwindows.cpp:210]
[2017.09.27-07.27.46:667][579]LogWindows:Error: ERAM.exe!__scrt_common_main_seh() [f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl:253]
[2017.09.27-07.27.46:667][579]LogWindows:Error: KERNEL32.DLL!0x00000000E1B813D2
[2017.09.27-07.27.46:667][579]LogWindows:Error: ntdll.dll!0x00000000E23254F4
[2017.09.27-07.27.46:667][579]LogWindows:Error: ntdll.dll!0x00000000E23254F4
[2017.09.27-07.27.46:667][579]LogWindows:Error:
[2017.09.27-07.27.46:667][579]LogWindows:Error: Crash in runnable thread FSIOLambdaRunnable1
[2017.09.27-07.27.46:669][579]LogWindows: FPlatformMisc::RequestExit(1)
[2017.09.27-07.27.46:669][579]Log file closed, 09/27/17 09:27:46

OnFail is not called when no SocketIO Server is running on Address

Unreal 4.16.1 on Windows.

Using the following GameMode setup (copy text from gist, paste into a blank GameMode, ensure BeginPlay is hooked up and create a SocketIOClient variable and hook it up), when trying to connect to a port where the SocketIO server is not running, the OnFail and OnDisconnect messages are not called, so I can't know when it has failed to connect.

https://gist.github.com/LordNed/ace0d2492ae1e18b532536db98b41e1e

Even if you use an entirely invalid address like "fou5k3" none of these callbacks are called.

NGROK - Custom events aren't triggering (related to SSL)

onConnected, onDisconnected etc etc trigger as expected. However, binding to a custom event via blueprints doesn't appear to work (I'm guessing it's the bind operation that's failing rather than the event firing failing ?).

I looked at the example and it's using a custom version of the plug-in with different functions.
I've also verified these same events on other platforms and they are fine (non-UE4).

Edit: Correction, it appears it fires the event once and once only.

Fantastic plug-in BTW (if I can get a solution to this).

Linux Untested

Linux build needs basic testing to confirm it works as intended.

OnDisconnect not getting fired and reconnect even with bShouldAutoConnect = false;

Hi, I have been using this plugin for a while and is awesome, but there are two things I cant get to work:
I successfully bind function with OnConnect:
SIOClientComponent->OnConnected.AddDynamic(this, &UfpsGameInstance::OnClientWsConnect);
where OnClientWsConnect is defined as: UFUNCTION() void OnServerWsConnect(FString SessionId);

but it doesnt work at all with OnDisconnect:
SIOClientComponent->OnDisconnected.AddDynamic(this, &UfpsGameInstance::OnClientWsDisconnect);
where OnClientWsDisconnect is defined as: UFUNCTION() void OnClientWsDisconnect(TEnumAsByte<ESIOConnectionCloseReason> Reason);

SIOClientComponent->OnSocketNamespaceDisconnected.AddDynamic(this, &UfpsGameInstance::OnClientWsDisconnect) doesnt work neither.

And no matter I set SIOClientComponent->bShouldAutoConnect = false; it keeps reconnecting when server is again available and then sends all the messages not delivered.

Can you help me?

Get Session ID (Socket ID ?) seems always empty

I tried to print the Socket ID (named Session Id in the component via blueprints) as differents times in the game flow (construct => begin play => after emit...) and it seems to always be empty

Beside that, all is working great.

Is this a bug ? How can I get the Socket/Session ID ?

Thank you,

Json Object to Struct not work?

Question in title.

Steps:

  • Construct Json Object (new)
  • Decode Json from string or set string field
  • check fill by encode json - correct
  • Make empty Struct with correct fields
  • try Json Object to Struct (return true)
  • check by break struct or struct to json - values is empty

But Struct to Json works correctly

image

Any thoughts?

Can't package since upgrading my project to 4.15

Hello,

First, thank you very much for this incredible work. I was not used to work with sockets, and I think you made this very easy.

I upgraded my project to 4.15 yesterday (Tmaps, finally), but since then I can't package anymore.

At first I could not even launch it, until I added EdGraphUtilities.h as suggested in your last commit,

I think it comes from socketio, as my only errors are these :

Cannot open file include: 'EdGraphUtilities.h': No such file or directory
ERROR: UBT ERROR: Failed to produce item: [...]\Plugins\socketio-client-ue4\Binaries\Win64\UE4-SIOJson.lib

Is there something I'm missing ?

I hope I won't have to go back to 4.14...

Thanks again for your great work, I hope to hear from you soon.

Failing to find 'SIOJson'

I have installed socketio-client-ue4 to the plugins file of my project. I have nodejs installed, I have socket.io installed. I am still getting this error (Screencap) I have scoured the Github but could not find anything about dependencies.

I am running windows 10 x64
I am running Unreal Engine 4.16.2 (I have also tried 4.16.1)
I have the latest installs (as of the time of this post) of Nodejs and Socket.io as well as the socket.io plugin.

"As string" fails to serialize the message JSON object

Hello developers,

It seems the "As string" blueprint node is failing to convert the JSON message to string.

For all the combinations I have tried, I get the following in the output log:
LogJson: Error: Json Value of type 'Object' used as a 'String'.

Attaching the combinations that I tried and failed:
2017-11-02 13_44_05-firstpersoncharacter_
2017-11-02 13_45_36-firstpersoncharacter_

My approach was almost the same as the third image in the project's home page(https://github.com/getnamo/socketio-client-ue4):
687474703a2f2f692e696d6775722e636f6d2f76566c4e426c782e706e67

Apparently, the data format from the socket seems correct as the following blueprint works fine:
2017-11-02 13_49_50-firstpersoncharacter_

Response that I am sending from the server:

emit('connection response', {'data': 'Connected'}) where,
'connection response' is the event and
'{'data': 'Connected'}' is the JSON message

UE version: 4.17.1

Thank you.

Seperate Branches for each unreal version

I recommend setting up separate branches, that allow for branch specific fixes to be implemented and for people to easily access the code for each branch if needed.

Destroying while a Connection is Pending Causes Crashes

This is an interesting one. The callstack effectively points to the engine running out of memory trying to allocate a huge amount of memory, caused by broadcasting a Multicast delegate. It seems to only happen if you start the game, attempt a connection to a websocket server, and then close the game before the webserver responds.

After many hours of searching, logging and breakpointing I think it comes down to this case:
The SocketIONative class starts a long-running background thread for connecting where it registers a couple of handlers (such as OnFailCallback). Somehow when running UninitializeComponent it ends up invoking the OnFailCallback. This OnFailCallback is listened to in the SocketIOClientComponent. By the time the callback gets invoked (which does look valid when invoked), the SocketIOClientComponent thinks the NativeClient pointer is null, ie when Line 195 is hit, NativeClient reports as null. Then, it starts a lambda on the game thread to broadcast the failure.

By the time the game thread lambda actually happens, OnFail now looks to be deconstructed or partially deconstructed. Broadcasting it causes an array reallocation with a huge amount of memory requested, and the engine crashes.

The point of opening this ticket is to help determine the best course of action to fix this. On one hand, it looks like I could slap a if(NativeClient == nullptr) { return; } in before we start the RunShortLambdaOnGameThread, skipping the callback if the object is destroyed. This is untested, but would presumably fix the issue.

The other option is we discover how the OnFailCallback is being called when NativeClient reports nullptr. It might be reporting nullptr because of how MakeShareable/TSharedPtr is implemented. It also looks like when the SocketIONative client is deconstructed (ie: assigned to nullptr in UnintializeComponent) it doesn't run the ClearCallbacks() function, which may be why the OnFailCallback is getting called. Perhaps clearing the list of callbacks when the object is deconstructed is the right course of action?

Unfortunately both of these changes mean that you won't receive potentially expected callbacks (such as OnDisconnected, OnFail) from a native client which is being destroyed, but I figure it's better as a known issue (you can hook the OnDestroyed event to know when the component is about to be destroyed instead, and use that as a "hey we got disconnected" message).

The most reliable way to repo this is put an actor in your scene, ask it to connect to something in BeginPlay (no async disconnect, no auto-connect), hit begin play and then immediately hit escape. Sometimes it doesn't crash but doing it repeatedly will cause it to crash. That being said, you can also cause it to crash late in the game when the actor is destroyed, ie 2-3 minutes later if it still hasn't connected to a websocket server.

This is becoming a blocker for our project so I may apply one of the above patches myself to see if they fix it, but I'd like to know the ideal course of action here so I can make it a PR and fix it for everyone.

Strange behavior with SocketIOComponent::UninitializeComponent()

I found some strange behavior with SocketIOComponent deinitialization process.
I logged calls of InitializeComponent() and UninitializeComponent() of SocketIOComponent and almost everytime on one Init call there were two Uninit calls.

In my project sometimes it leads to crash game with "Fatal error" exception message box.

In function below bIsConnected is always true (with successfull connection with server) in both Uninit calls so Super::UninitializeComponent() is never being called.

I think second call is from GC, coz Component wasn't unitialized properly.

SocketIOClientComponent.cpp

void USocketIOClientComponent::UninitializeComponent()
{
	//This may lock up so run it on a background thread
	if (bAsyncQuitDisconnect)
	{
		if (bIsConnected)
		{
			FSIOLambdaRunnable::RunLambdaOnBackGroundThread([&]
			{
				FScopeLock lock(&AllocationSection);
				NativeClient = nullptr;
			});
			return;
		}
	}
	else
	{
		FScopeLock lock(&AllocationSection);
		NativeClient = nullptr;;
	}

	Super::UninitializeComponent();
}

Get Bool field always returns false.

image

If value is passed as
{ "isConnecting" : true }

GetBoolField returns false.

If the value is passed as a string instead
{ "isConnecting" : "true" }

then GetStringField returns "true" as expected.

Invalid module name while checking availability

Why you need to check for LeapMotionC if your module called SocketIOClient?

SocketIOClient.h

...
	static inline ISocketIOClientModule& Get()
	{
		return FModuleManager::LoadModuleChecked< ISocketIOClientModule >("SocketIOClient");
	}

	static inline bool IsAvailable()
	{
		return FModuleManager::Get().IsModuleLoaded("LeapMotionC");
	}
...

An fatal error occurs after packaging

There is nothing wrong with playing in the engine. But an fatal error occurs after packaging like this.

Fatal error:
[File:D:\Build\++UE4+Release-4.17+Compile\Sync\Engine\Source\Runtime\CoreUObject\Private\Serialization\AsyncLoading.cpp][Line:3004]
Missing Dependency, request for SocketIOClientComponent but it hasn't been created yet.

I was only trying to connect in a clean project. 4.16 and 4.17.
So I put the plugin in a packaged folder.
Then I saw this.

Plugin 'SocketIOClient' faild to load because module 'SIOJson' could not be found.
Please ensure the plugin is properly installed, otherwise consider disabling the plugin for this project.

Could you tell me the solution?

Race Condition Crash in FSIOLambdaRunnable

This was a fun one to track down. It only happens in builds and it was very sporadic. I'd add a space and recompile and it'd crash constantly every time the Connect was called. I'd remove the space and compile again and it'd go away.

I finally tracked it down to FSIOLambdaRunnable (one of which is created upon calling the Connect function). The issue is that FSIOLambdaRunnable calls "delete this" in the Exit() function.

The issue is that calling Thread = FRunnableThread::Create(this, *threadStatGroup, 0, TPri_BelowNormal); (source) can lead to a race condition whereif the runnable thread is created, executes the lambda and returns before the main thread finishes the call to ::Create, you have now deleted the FSIOLambdaRunnable while... still inside of it. This finishes the function call on released memory and then the engine crashes a moment later with random differing callstack as it tries to use the memory that you helpfully overwrote.

I came up with a temporary solution so we could ship, but I can't submit it as a PR without some additional discussion as I think it leads to an additional edge case. The idea is that you remove "delete this" from the exit function (so that if the lambda finishes before the main thread completes, we don't delete the FSIOLambdarUnnable)

void FSIOLambdaRunnable::Exit()
{
	Finished = true;
        //delete this; <- Don't delete FSIOLambdaRunnable on thread exit as thread can exit before constructor finishes on main thread.
}

This means we now are responsible for the lifecycle of FSIOLambdaRunnable. It is inadvisable that FSIOLambdaRunnable controls the lifecycle of both itself and its FRunnableThread. Then, when you use the FSIOLambdaRunnable you need to store the pointer to the runnable and delete it in the deconstructor, and before you use it again. (Source)

Example:

FSocketIONative::~FSocketIONative()
{
	ClearCallbacks();
	delete ConnectionThread;
}
void FSocketIONative::Connect(const FString& InAddressAndPort, const TSharedPtr<FJsonObject>& Query /*= nullptr*/, const TSharedPtr<FJsonObject>& Headers /*= nullptr*/)
{
        delete ConnectionThread;
        ConnectionThread = FSIOLambdaRunnable::RunLambdaOnBackGroundThread([&,Query, Headers]() {});
}

This technically lets the Runnable exist in memory for longer than strictly needed but will be cleaned up when FSocketIONative is removed. However, this leads to a second issue - you need to delete ConnectionThread before you re-assign it otherwise you leak memory, thus the second delete call.

This however leads to another issue - if you delete ConnectionThread from the main thread while the FSIOLambdaRunnable is still executing something you crash again! I think the fix for this would be effectively:

void FSocketIONative::Connect(const FString& InAddressAndPort, const TSharedPtr<FJsonObject>& Query /*= nullptr*/, const TSharedPtr<FJsonObject>& Headers /*= nullptr*/)
{
        if(ConnectionThread)
        {
                ConnectionThread->WaitForCompletion();
                delete ConnectionThread;
        }
        ConnectionThread = FSIOLambdaRunnable::RunLambdaOnBackGroundThread([&,Query, Headers]() {});
}

This has the disadvantage of stalling the main thread until your background thread completes which kind of defeats the purpose of a background thread, but it'd only happen if you called the function twice in a row before the existing one completed. The only other way I can think of is finding a way to safely delete a thread while it's in the middle of executing a function, but I imagine that can still leave things in a bad state.

The other solution I could think of would be to safeguard inside the code that uses FSIOLambdaRunnable, ie: It sets a "IsConnecting" bool to true and the next time you try to call Connect if it's already connecting it just early outs and warns, and then a callback on the exit of the Runnable sets IsConnecting to false once the function actually completes the first time. Then it'd be safe to just delete ConnectionThread before assigning it, since it'd never get to that code if the current ConnectionThread was still in use.

I'm opening this as an issue and not a PR to start a discussion about the best way to fix it, because I'm honestly not sure. ¯\_(ツ)_/¯

TrimKey not called on struct of Map

ue4 4.16.3
sio 0.6.2
environment: Blueprint

This JSON is what I receive on the server:

{
  "actions": {
    "my_key": {
      "value_5_0C62405F4F8CD0DE2EFE1C84058E954D": 123,
      "vector_8_CBC3A5A248B0C7EFE10514AF9D644328": {
        "x": 1,
        "y": 1,
        "z": 1
      }
    }
  }
}

when I Emit this Actions struct:
struct Actions, field 'actions' which is a map of String -> Action
struct Action, fields 'value' = Float, 'vector' = Vector

The two fields value and vector seem not properly trimmed

The BP nodes I'm using are:

variable get Actions -> Struct to JSON Object -> ToJSONValue -> Emit

Android Static Libs

Which versions of Android does socketio-client-ue4 support?

I can't find this in the socketio-client-ue4 GitHub pages.

io.emit

needs confirmation if io.sockets.emit() works as expected or not

JSON string

sio::message->get_string() doesn't convert object into json string for parsing downstream using native JSON parsers.

Additional headers on connect

It would be great if you could pass additional HTTP headers when connecting - for example, passing OAUTH-tokens to authenticate on the server.

socket.io namespace

Hi,
I'm not sure, but I think the plugin is locked to the socket.io namespace, looking through the source code, I think this line is the culprit.
This could be by design, so i'd like your input.

Unreal Crashes After Several Start/Stops when SocketIOComponent is on GameMode

Hi all,

We've added the SocketIOComponent to the GameMode as we want to ensure it attempts the connection each time the GameMode is started. Unfortunately after 3 or 4 start/stop plays Unreal Engine crashes with the callstack posted below. This is fairly easy to test by rapidly starting/stopping Simulate.

This uses Unreal 4.16.1. (Windows) I have created a gist with our setup inside the GameMode which should let you replicate it. You will be missing a node for how we get our address/port but they should be easy. Add a SocketIOClient component and disable Auto-connect and then paste the gist's contents into the GameMode and hook up the connect functions to BeginPlay.

https://gist.github.com/LordNed/ace0d2492ae1e18b532536db98b41e1e

Access violation - code c0000005 (first/second chance not available)

UE4Editor_SocketIOClient!TMulticastScriptDelegate<FWeakObjectPtr>::ProcessMulticastDelegate<UObject>() [e:\program files\epic games\ue_4.16\engine\source\runtime\core\public\uobject\scriptdelegates.h:457]
UE4Editor_SocketIOClient!TGraphTask<FFunctionGraphTask>::ExecuteTask() [e:\program files\epic games\ue_4.16\engine\source\runtime\core\public\async\taskgraphinterfaces.h:884]
UE4Editor_Core!FNamedTaskThread::ProcessTasksNamedThread() [d:\build\++ue4+release-4.16+compile\sync\engine\source\runtime\core\private\async\taskgraph.cpp:954]
UE4Editor_Core!FNamedTaskThread::ProcessTasksUntilIdle() [d:\build\++ue4+release-4.16+compile\sync\engine\source\runtime\core\private\async\taskgraph.cpp:716]
UE4Editor_Engine!FFrameEndSync::Sync() [d:\build\++ue4+release-4.16+compile\sync\engine\source\runtime\engine\private\unrealengine.cpp:8502]
UE4Editor!FEngineLoop::Tick() [d:\build\++ue4+release-4.16+compile\sync\engine\source\runtime\launch\private\launchengineloop.cpp:3247]
UE4Editor!GuardedMain() [d:\build\++ue4+release-4.16+compile\sync\engine\source\runtime\launch\private\launch.cpp:166]
UE4Editor!GuardedMainWrapper() [d:\build\++ue4+release-4.16+compile\sync\engine\source\runtime\launch\private\windows\launchwindows.cpp:134]
UE4Editor!WinMain() [d:\build\++ue4+release-4.16+compile\sync\engine\source\runtime\launch\private\windows\launchwindows.cpp:210]
UE4Editor!__scrt_common_main_seh() [f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl:253]
kernel32
ntdll

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.