itisnajim / SocketIOUnity

A Wrapper for socket.io-client-csharp to work with Unity.
MIT License
417 stars 71 forks source link

Is it possible emitting 2 different events with ack callback at same time causes race condition issue? #94

Open KamilTheDev opened 1 month ago

KamilTheDev commented 1 month ago

I've been having a very strange problem, in socket.OnConnected event, I'm emitting 2 events: get-player and get-clan that have a callback that processes the result from the server.

Sometimes rarely, the get-clan event is actually receiving the player data (that would be for get-player), and the get-player callback is never called. Other times, get-player will get the player data, but the callback for get-clan is never called. I have server logs that indicate the server is always getting the right data for each event, and in the cases where client never gets the get-player or get-clan callback called, the server did receive the request.

Is there any potential ways to accidently create issues when emitting different events with ack callbacks at the same time? Perhaps I'm doing something that I shouldn't.

itisnajim commented 1 month ago

i have no code snippet of what u are trying to achieve,

but this is an example to how u can do it


    public async Task<(string playerData, string clanData)> GetPlayerAndClanAsync(object someObject)
    {
        var playerTaskCompletion = new TaskCompletionSource<string>();
        var clanTaskCompletion = new TaskCompletionSource<string>();

        // Emit get-player event and set the response to playerTaskCompletion
        socket.Emit("get-player", (response) =>
        {
            string playerData = response.GetValue<string>();
            playerTaskCompletion.TrySetResult(playerData);
        }, someObject);

        // Emit get-clan event and set the response to clanTaskCompletion
        socket.Emit("get-clan", (response) =>
        {
            string clanData = response.GetValue<string>();
            clanTaskCompletion.TrySetResult(clanData);
        }, someObject);

        // Await both tasks and return the tuple with both results
        string playerResult = await playerTaskCompletion.Task;
        string clanResult = await clanTaskCompletion.Task;

        return (playerResult, clanResult);
    }

usage:

var (playerData, clanData) = await socketManager.GetPlayerAndClanAsync(someObject);
Console.WriteLine($"Player Data: {playerData}");
Console.WriteLine($"Clan Data: {clanData}");

adjust the code to meet ur needs

KamilTheDev commented 1 month ago

I imagine it might be from having one socket.emit being executed in Unity thread, and the other socket.emit is not. I was playing around with different ways.

void Start()
{
     // Connect to the server
    socket.Connect();
    Debug.Log("socket.Connect is called.");

    // Handle connection event
    socket.OnConnected += (sender, e) =>
    {
        UnityThread.executeInUpdate(() => OnConnected?.Invoke());

        socket.Emit("player-data", (response) =>
        {
            UnityThread.executeInUpdate(() =>
            {
                Debug.Log("Player data response: " + response.GetValue<string>();
            });
        });
    };
}

public async void ExampleOnConnectedSubscriber()
{
    SocketIOResponse response = await EmitAsync("clan-data");
    Debug.Log("Clan data response: " + response.GetValue<string>());
}

public static async ValueTask<SocketIOResponse> EmitAsync(string eventName, object data = null)
{
    var tcs = new TaskCompletionSource<SocketIOResponse>();

    await Instance.socket.EmitAsync(eventName, (response) =>
    {
        tcs.SetResult(response);
    }, data);

    return await tcs.Task;
}

I think ensuring they are both executed in either socket or Unity thread fixes the issue. I was also testing the idea of being able to simply await the socket callback.

For example, I don't really like the syntax, especially if you need to make more emits inside:

socket.Emit("player-data", (response) =>
{
    UnityThread.executeInUpdate(() =>
    {
        playerText.text = response.GetValue<string>();

          socket.Emit("other-data", (response) =>
          {
              UnityThread.executeInUpdate(() =>
              {
                  otherText.text = response.GetValue<string>();
              });
          });
    });
});

I much rather:

SocketIOResponse playerResponse = await EmitAsync("player-data");
playerText.text = playerResponse.GetValue<string>();

SocketIOResponse otherResponse= await EmitAsync("other-data");
otherText.text = otherResponse.GetValue<string>();

Is the way I implemented EmitAsync the correct way to be able to directly await the callback?

itisnajim commented 1 month ago

try to not use unity thread when fetching ur needed data (get-player and get-clan), then after retrieving ur data run use unity thread

var (playerData, clanData) = await socketManager.GetPlayerAndClanAsync(someObject);
UnityThread.executeInUpdate(() =>
{
        // TODO
});

i have no code snippet of what u are trying to achieve,

but this is an example to how u can do it

    public async Task<(string playerData, string clanData)> GetPlayerAndClanAsync(object someObject)
    {
        var playerTaskCompletion = new TaskCompletionSource<string>();
        var clanTaskCompletion = new TaskCompletionSource<string>();

        // Emit get-player event and set the response to playerTaskCompletion
        socket.Emit("get-player", (response) =>
        {
            string playerData = response.GetValue<string>();
            playerTaskCompletion.TrySetResult(playerData);
        }, someObject);

        // Emit get-clan event and set the response to clanTaskCompletion
        socket.Emit("get-clan", (response) =>
        {
            string clanData = response.GetValue<string>();
            clanTaskCompletion.TrySetResult(clanData);
        }, someObject);

        // Await both tasks and return the tuple with both results
        string playerResult = await playerTaskCompletion.Task;
        string clanResult = await clanTaskCompletion.Task;

        return (playerResult, clanResult);
    }

usage:

var (playerData, clanData) = await socketManager.GetPlayerAndClanAsync(someObject);
Console.WriteLine($"Player Data: {playerData}");
Console.WriteLine($"Clan Data: {clanData}");

adjust the code to meet ur needs