Making sure PhotonNetwork.RaiseEvent reaches temporarily disconnected players

Hi!

I'm using PhotonNetwork.RaiseEvent to broadcast events to all players passing sendReliable = true as a parameter.

Some of the receiving players are temporarily disconnected at the time of my broadcasts (let's say due to a bad internet connection). I need to make sure that after those clients reconnect, they will receive all the events that were broadcasted while those clients were in disconnected state.

Is this possible to implement with Photon?

I don't know where to start from. I don't really understand how RaiseEvent works under the hood and what happens when I reliably RaiseEvent while some players are disconnected. How can I find out who was disconnected at the time and also which events he missed?

Thank you!

Best Answers

  • JohnTube
    JohnTube ✭✭✭✭✭
    Answer ✓
    Hi @1_Mad_Developer,

    You're welcome and don't worry.

    But there is a problem. Now when a player disconnects and then reconnects during the game, he receives all the events from the beginning of the match, including the events that he already received before.

    This should not be a problem and the behavior is expected and by design.
    What you should do, since you have 2 players only:
    if an event is received successfully by the other player delete it from cache so it won't be sent again as it is already "processed and consumed". It is the best option as other ways to avoid receiving duplicate or old events may be too complex for your game.
    In any case, receiving an event more than once is better than not receiving it at all! So you can just ignore those events by checking actor number.

    Regarding the heart beat coroutine to check internet connection, let's discuss that on the other forum thread.
  • JohnTube
    JohnTube ✭✭✭✭✭
    edited November 2016 Answer ✓
    Hey @1_Mad_Developer,

    I think you just need to fix the data filter by changing:
    broadcastData.Add("eid", new Hashtable() { { "eid", 999 } }); // mark my cached event with 999
    
    to
    broadcastData.Add("eid", 999); // mark my cached event with 999
  • JohnTube
    JohnTube ✭✭✭✭✭
    edited November 2016 Answer ✓
    Hi @1_Mad_Developer,

    I see the issue, the OnPlayerDisconnected callback is not being triggered when a player leaves temporarily.

    Until we release a fix in an upcoming version which could take a while, here is how to fix this manually by changing the code yourself:

    In "NetworkingPeer.cs" inside HandleEventLeave please replace:
    if (player.isInactive && !_isAlreadyInactive)
    		{
    			return;
    		}
    with
    if (player.isInactive && !_isAlreadyInactive)
    		{
                SendMonoMessage(PhotonNetworkingMessage.OnPhotonPlayerDisconnected, player);
                return;
    		}
    In the OnPlayerDisconnected(PhotonPlayer player) you can check if the player is coming back using player.isInactive.

Answers

  • JohnTube
    JohnTube ✭✭✭✭✭
    Hi @1_Mad_Developer,

    You probably need to cache those events.
    Read more about events caching here.
  • Thanks @JohnTube and sorry for the late responce!

    As you suggested I've enabled event caching for my room. But there is a problem. Now when a player disconnects and then reconnects during the game, he receives all the events from the beginning of the match, including the events that he already received before. I only want to process the new events (those that we raised while the player was disconnected). Can this be done with Photon?

    Also I'm not sure if it's relevant but I'm doing something unusual when a player disconnects. Maybe it influences room cache somehow. I have a corutine that in short intervals checks if internet can be reached. And as soon as internet is unavailable I call PhotonNetwork.Disconnect(). This way I don't need to wait for disconnectTimeout and it allows me to immediately call PhotonNetwork.ReconnectAndRejoin() as soon as the internet becomes available again.

    For your reference, here is how I'm creating a new room:
    RoomOptions roomOptions = new RoomOptions();
    roomOptions.IsOpen = true;
    roomOptions.IsVisible = true;
    roomOptions.MaxPlayers = 2;
    roomOptions.CleanupCacheOnLeave = false;
    roomOptions.EmptyRoomTtl = 120000;
    roomOptions.PlayerTtl = 120000;
    
    PhotonNetwork.CreateRoom(null, roomOptions, TypedLobby.Default);
    I look forward for hearing your answer!

    Thank you very much!
  • JohnTube
    JohnTube ✭✭✭✭✭
    Answer ✓
    Hi @1_Mad_Developer,

    You're welcome and don't worry.

    But there is a problem. Now when a player disconnects and then reconnects during the game, he receives all the events from the beginning of the match, including the events that he already received before.

    This should not be a problem and the behavior is expected and by design.
    What you should do, since you have 2 players only:
    if an event is received successfully by the other player delete it from cache so it won't be sent again as it is already "processed and consumed". It is the best option as other ways to avoid receiving duplicate or old events may be too complex for your game.
    In any case, receiving an event more than once is better than not receiving it at all! So you can just ignore those events by checking actor number.

    Regarding the heart beat coroutine to check internet connection, let's discuss that on the other forum thread.
  • 1_Mad_Developer
    edited November 2016
    Thanks for the advice @JohnTube !

    I'm having troubles deleting the cached events though. I'm not sure how to do it correctly and the documentation doesn't give any code examples.

    Right now my process of caching and deletion of events is as follows:

    1) Our player raises some event:
    RaiseEventOptions eventOptions = RaiseEventOptions.Default;
    eventOptions.CachingOption = EventCaching.AddToRoomCache;
    
    Hashtable broadcastData = new Hashtable();
    broadcastData.Add(0, "actual event content for my game");
    broadcastData.Add("eid", new Hashtable() { { "eid", 999 } }); // mark my cached event with 999
    
    PhotonNetwork.RaiseEvent(myEventCode, broadcastData, true, eventOptions);
    2) When enemy player receives the event, he raises another event which whole purpose is to delete the first event from room's cache:
    PhotonNetwork.RaiseEvent(myEventCode, new Hashtable() { { "eid", 999 } }, true, new RaiseEventOptions { CachingOption = EventCaching.RemoveFromRoomCache });
    And then the first event is still in room's cache and I still receive them all a second time after a reconnect. I've tried CachingOption = EventCaching.AddToRoomCacheGlobal but the result is the same.
    In any case, receiving an event more than once is better than not receiving it at all! So you can just ignore those events by checking actor number.

    Could you please explain how do I do that? :smile: I'm still new to Photon and this stuff is a little complicated for me.

    Also thank you for all your help so far! At least I'm digging in the right direction now.
  • JohnTube
    JohnTube ✭✭✭✭✭
    edited November 2016 Answer ✓
    Hey @1_Mad_Developer,

    I think you just need to fix the data filter by changing:
    broadcastData.Add("eid", new Hashtable() { { "eid", 999 } }); // mark my cached event with 999
    
    to
    broadcastData.Add("eid", 999); // mark my cached event with 999
  • Omg, you are ofcourse right!

    Such a stupid mistake from my side. Why would I put a Hashtable inside another Hashtable? I guess I just worked too much yesterday.

    Anyway now everything works fine and I finally can proceed with developing my game!

    Thank you very much for your help @JohnTube !
  • Hi @JohnTube ,

    I'm having one more issue regarding player disconnects. As you suggested I'm using cached events to make sure that raised events can reach temporarily disconnected players.

    I had to set
    roomOptions.PlayerTtl = 120 000
    to make sure that disconnected players can rejoin within 2 mins. This leads to another problem. When 1st player is in game and 2nd player disconnects, the 1st player only receives OnPhotonPlayerDisconnected after 2 mins (after PlayerTtl timeout). I need to immediately know when the 2nd player disconnects and for this I had to set
    roomOptions.PlayerTtl = 1000
    but now the players can't reconnect to the abandoned room at all. I guess this is because the room is not storing the actorID anymore.

    I did set
    roomOptions.EmptyRoomTtl = 120 000
    but it doesn't solve the issue.

    Could please point me to the right direction here? How can I resolve this?

    Thank you!
  • JohnTube
    JohnTube ✭✭✭✭✭
    edited November 2016 Answer ✓
    Hi @1_Mad_Developer,

    I see the issue, the OnPlayerDisconnected callback is not being triggered when a player leaves temporarily.

    Until we release a fix in an upcoming version which could take a while, here is how to fix this manually by changing the code yourself:

    In "NetworkingPeer.cs" inside HandleEventLeave please replace:
    if (player.isInactive && !_isAlreadyInactive)
    		{
    			return;
    		}
    with
    if (player.isInactive && !_isAlreadyInactive)
    		{
                SendMonoMessage(PhotonNetworkingMessage.OnPhotonPlayerDisconnected, player);
                return;
    		}
    In the OnPlayerDisconnected(PhotonPlayer player) you can check if the player is coming back using player.isInactive.
  • Wow, it worked like a charm!

    Thanks @JohnTube !
  • Hi, i'm new user
    sorry for my bad english, i've the same problem the difference is that mu game is a card game with 5 players, when a player disconnect and rejoin room events from the beginning of the match, including the events that he already received before. I only want to process the new events (those that we raised while the player was disconnected). How i do ?
  • JohnTube
    JohnTube ✭✭✭✭✭
    Hi @Mario1972,

    Thank you for choosing Photon!

    Well, you need to keep track of what events are received by client manually:

    1- locally cache "the progress" of the player on the client. when events arrive process only new ones.
    2- when a cached event is no longer needed delete it from cache.
  • Thank you.
    There is a index in photon or i must create my own index for discard event received.
  • JohnTube
    JohnTube ✭✭✭✭✭
    It depends on your game so yes you must create your own "index".
    You may have something like an event "counter" per game or per player.
    Ideally, you should remove events from the cache when you no longer need them.
  • Hi, i've new problem,
    when player raisevent when lose connection temporarily, other player don't receve event.
    How to test if player is connect before send event?
  • Hi my question is when player raise event ad he is disconect, how check befor post pessage
  • JohnTube
    JohnTube ✭✭✭✭✭
    edited January 2018
    Hi @Mario1972,

    I already posted here the approach I suggest if you want to make sure an event is successfully transmitted to its destination(s).

    Some ideas:

    Broadcast (because RaiseEvent does not have Operation Response by default).
    Add "event round-trip" timeout.
    ACK event reception also using events.
    Cache event locally on the client before sending.
    Add an event ACK timeout.
    Retry in case of failure or timeout or disconnect, etc.
    Add max. retry count.
    Sender adds events to room cache first when raised.
    Sender deletes events from room cache when ACKed.
  • Hi, i wont clear all cache event, how i do?
  • JohnTube
    JohnTube ✭✭✭✭✭
    Hi @Mario1972,

    Next time, start a new forum question as this one is not related to the original post.
    I'll answer here this time:

    There is this method:
            PhotonNetwork.networkingPeer.OpRemoveCompleteCache();
    
    If it does not work well for you (didn't test it myself), you can try the following:
    public void CleanUpEventsCache()
        {
            Dictionary<byte, object> parameters = new Dictionary<byte, object>();
            parameters.Add(ParameterCode.Cache, EventCaching.RemoveFromRoomCache);
            PhotonNetwork.networkingPeer.OpCustom(OperationCode.RaiseEvent, parameters, true);
        }