Synchronization

Hello all
I have somewhat difficult question for you :)
I have two players in a same room.
They are both trying to execute some form of combo button press (lets say players can do this in about a second)
I have to inform both players of who finished (sent an event) this first.

If player one sends this event and by the time it reaches player two, player two still haven't send his own finish event, then it is clear that player one wins.
But..if player two have just sent his own finish event and then receive finish event from player one how do I know with one finished first?

Local clocks are not synchronized. I don't know the lag for neither players. Is there a way to do this right?

Answers

  • Okay so many ways you can go about doing this.

    Lets take your case here:
    -1 master client
    -2 players in the room
    -We need to detect when each user sends an input
    -Once inputs are received, produce a win result for the first input received
    -Reset for a new use case

    With that rule set in line, we can move on to logic.

    We want to have 1 object in the scene to receive the player inputs and register the feedback back to the players. We will name this "GameController" and will give it a PhotonView component and have it observe itself. We will add a string to the script called "firstPlayer" and this will be our indicator for who acted first. We will also add a "bool" called "allowInput" and this will be our flag to stop inputs being sent in.

    Inside of GameController script we will add a PunRPC method called "ReceiveInput" with a parameter type of "string" named "actorId", this will set the firstPlayer var.

    From here, on each client you will look in the Update method for input, on input true we will get the GameController, get its photonView and send it an RPC with our local PhotonPlayer id.

    Inside of the Receive input RPC we will first create an if statement that checks for allowInput false and return out of that, then set allowInput to false and firstPlayer to the actorId parameter.

    From here we can now see that the first player that sends an input gets logged to the firstPlayer var and inputs are paused, preventing the secondary player from bugging out the system with a late call, etc case.

    Finally, we can create another RPC if needed to then reset the firstPlayer string to null and the allowInput bool to true and fire this method from the GameController itself when youre ready to move to the next round or case.

    Hopefully this helps, again this is just 1 way of going about this, i can think of 4 different ways off the top my head for photon, it really just depends on how you want to go about doing it and what is the most efficient for you, youre game and the rooms data IO.
  • JohnTube
    JohnTube ✭✭✭✭✭
    edited October 2019
    Hi @GLucas,

    Thank you for your contribution on the forum, much appreciated.

    I won't discuss your suggested approach in general or in details, however, I have one question:
    with a parameter type of "string" named "actorId"
    why "string"? PUN registers the Player class as custom type and you can send it in as RPC parameter and under the hood, only the ActorNumber which is an integer is exchanged. I think it's more efficient and intuitive to use ActorNumber and integer instead of string and UserId.
  • @JohnTube Ah, yes i agree i just tested and that would work out easier and would be more legible as far as a cohesive workflow goes.
  • JohnTube
    JohnTube ✭✭✭✭✭
    @denmla1000,

    Since this is very time sensitive you could send the network timestamp (PhotonNetwork.Time) from each player and then compare.
    The thing is who should decide.
    The best case is to have an authoritative server logic.
    Otherwise, you could go for the lightweight pseudo authoritative master client.
    In any case, always send via server.
    Meaning even if the master client sends to himself, forward to the server first to give some time for the other player's event to arrive then compare.
  • Thank you @GLucas for the answer and @JohnTube for followup.
    So far I used only RaiseEvent in my code. I red that in low level Photon do only this call, but I maybe I am wrong. I tough to make this sync with this calls but I guess the PhotonView + RPC is already setup system for this sync lock-unlock logic.
    My only concern is that the latency will be big. I tested master to other player:
    RaiseEvent -> OnEvetnt RaiseEvent -> OnEvent and it has 0.12 - 0.2 seconds latency.
    Is that normal/expected or it can be optimized somehow?
  • JohnTube
    JohnTube ✭✭✭✭✭
    Hi @denmla1000,

    Yes, correct.
    All PUN features are using RaiseEvent under the hood.
    RaiseEvent can be used in this case.

    Is that normal/expected or it can be optimized somehow?
    If you need a faster decision, I would recommend going for custom server plugins which would allow deciding on the server which has 2 benefits: half the time + authoritative logic. The plugins require self-hosting or enterprise cloud.
  • Tnx. We are yet to come there :)
    For now MVP of the game is all we need, and later custom solutions will be a must.
    Tnx for the answers
  • GLucas said:

    Okay so many ways you can go about doing this.

    Lets take your case here:
    -1 master client
    -2 players in the room
    -We need to detect when each user sends an input
    -Once inputs are received, produce a win result for the first input received
    -Reset for a new use case

    With that rule set in line, we can move on to logic.

    We want to have 1 object in the scene to receive the player inputs and register the feedback back to the players. We will name this "GameController" and will give it a PhotonView component and have it observe itself. We will add a string to the script called "firstPlayer" and this will be our indicator for who acted first. We will also add a "bool" called "allowInput" and this will be our flag to stop inputs being sent in.

    Inside of GameController script we will add a PunRPC method called "ReceiveInput" with a parameter type of "string" named "actorId", this will set the firstPlayer var.

    From here, on each client you will look in the Update method for input, on input true we will get the GameController, get its photonView and send it an RPC with our local PhotonPlayer id.

    Inside of the Receive input RPC we will first create an if statement that checks for allowInput false and return out of that, then set allowInput to false and firstPlayer to the actorId parameter.

    From here we can now see that the first player that sends an input gets logged to the firstPlayer var and inputs are paused, preventing the secondary player from bugging out the system with a late call, etc case.

    Finally, we can create another RPC if needed to then reset the firstPlayer string to null and the allowInput bool to true and fire this method from the GameController itself when youre ready to move to the next round or case.

    Hopefully this helps, again this is just 1 way of going about this, i can think of 4 different ways off the top my head for photon, it really just depends on how you want to go about doing it and what is the most efficient for you, youre game and the rooms data IO.

    @GLucas I tried this out and I think it is not a viable solution. I found that RPC calls are separating events so that local message (RPC call) goes only local and invokes it right away. This way one RPC call that is send to all players in a room will always be first executed locally.
    I thought that Photon cloud will have some timestamp mechanic for this but I was wrong. It seam that it is always only message/broker server.
    This means that I would have to come up with custom solution for this conflict (win/lose) resolution.

    I would have to compare timestamps on both clients and send confirmations and so on.
  • JohnTube
    JohnTube ✭✭✭✭✭
    Hi @denmla1000,

    I found that RPC calls are separating events so that local message (RPC call) goes only local and invokes it right away. This way one RPC call that is send to all players in a room will always be first executed locally.
    This is not true for RPC targets that end with "ViaServer", those will be executed at the clients after receiving an event from the server, even for the local client.
    I thought that Photon cloud will have some timestamp mechanic for this but I was wrong.
    RPCs have the timestamp of when they were sent. Add a last extra parameter of type PhotonMessageInfo which contains the server timestamp of when the client sent the RPC.

    I invite you to reread about RPCs here carefully.
  • @JohnTube Thank you very much!
    I somehow missed the "viaserver" part in that documentation and even worse I was looking into RPC call itself in Photon classes and there is a switch case for targets and I haven got to the part where cases "viaserver" are :disappointed:
    You just saved me days of work!