IS there an example of CAS anywhere?

I tried this over on the Unity forums with no luck.

Does anyone have a code example for changing a RoomProperty in Photon using CAS, that includes the error handling?

I have a float[] as a custom property. The various players can change elements of the float array. However, because of concurrency, while I try to change one element, another player may try to change a different element.

Ultimately what I am looking for (psuedocode)

ChangeRoomProperties()
{
ReadRoomProperties() //casting them to an array[]
alter the desired element
recreate Hashtable
room.SetCustomProperties(prop, oldvalueProp);
if error == incorrect oldValueProp
{
ChangeRoomProperties()
}
}

Best Answer

Answers

  • JohnTube
    JohnTube ✭✭✭✭✭
    Hi @Spektre,

    Here is how to use CAS for room properties, it's an extension method you can directly use:
    
    using ExitGames.Client.Photon;
    using Photon.Realtime;
    
    public static class RoomExtensions 
    {
        public static bool SetCustomPropertySafe(this Room room, string key, object newValue, WebFlags webFlags = null)
        {
            if (room == null)
            {
                return false;
            }
            if (room.IsOffline)
            {
                return false;
            }
            if (!room.CustomProperties.ContainsKey(key))
            {
                return false;
            }
            Hashtable newProps = new Hashtable(1) {{key, newValue}};
            Hashtable oldProps = new Hashtable(1) {{key, room.CustomProperties[key]}};
            return room.LoadBalancingClient.OpSetCustomPropertiesOfRoom(newProps, oldProps, webFlags);
        }
    }
    Use as follows:

    PhotonNetwork.CurrentRoom.SetCustomPropertySafe(key, newValue);

    You should know that:

    - initializing (i.e. first time creating a new property) using CAS is not supported
    - there is no error callback for when SetProperties fails due to CAS
  • Thanks JohnTube.

    Is a flag set or anything to tell you the attempted CAS write failed?

    Per the docs.

    https://doc.photonengine.com/en-us/pun/current/gameplay/synchronization-and-state

    "SetCustomProperties has an optional expectedValues parameter, which can be used as condition. With expectedValues, the server will only update the properties, if its current key-values match the ones in expectedValues. Updates with outdated expectedValues will be ignored (the clients get an error as a result, others won't notice the failed update).
  • Can you get it from a PunBehavior?


    Error CS0117 'PhotonNetwork' does not contain a definition for 'NetworkingClient'
  • Looking at the documentaiton for PhotonNetwork, I do not see a member named NetworkingClient.
  • This is PUN Classic I am working with.
  • JohnTube
    JohnTube ✭✭✭✭✭
    We highly recommend migrating to PUN2 asap especially when starting a new project.
    PUN Classic code:
    using ExitGames.Client.Photon;
    
    public static class RoomExtensions 
    {
        public static bool SetCustomPropertySafe(this Room room, string key, object newValue, bool webForward)
        {
            if (room == null)
            {
                return false;
            }
            if (!room.CustomProperties.ContainsKey(key))
            {
                return false;
            }
            Hashtable newProps = new Hashtable(1) {{key, newValue}};
            Hashtable oldProps = new Hashtable(1) {{key, room.CustomProperties[key]}};
            return PhotonNetwork.networkingPeer.OpSetCustomPropertiesOfRoom(newProps, oldProps, webForward);
        }
    }
    Error response is not exposed in PUN1.
    You need to inject the code in "Assets\Photon Unity Networking\Plugins\PhotonNetwork\NetworkingPeer.cs" line 2015:
    case OperationCode.SetProperties:
                    // this.mListener.setPropertiesReturn(returnCode, debugMsg);
                    break;
    with:
    case OperationCode.SetProperties:
            if (opResponse.ReturnCode == ErrorCode.InvalidOperation)
            {
                // cas failure
            }
           break;
  • While the error is not exposed, the essential update is still available: When CAS fails, this is because the "expected properties" did not match the server's values. This case triggers an update event for the properties and so the client gets a property updated callback.

    We should maybe add this callback (it's not in PUN 2 either) or at least update the reference doc.

    If you need the error itself, then please follow JohnTube's suggestion. Is this something the client needs?
  • My use case is as such...

    I have a game that has rounds of play where people may join and leave freely. To be eligible for rankings and such, you must have been present at the start of the round. As such, having scores stored as a Player property are not desirable as they disappear when the player leaves (even though they may be eligible.) The scores, as they get updated, therefore need to be a room property as they persist.

    If a separate property were made for each player though, the room would soon become populated with many no longer present players scores from past rounds.

    Therefore, the method decided was to make the master client note who is present at round start and create an array with the player's ID, and another with the corresponding player's scores. Each player in turn can update their score in the array as needed.

    To do this, the array is read, the player updates their score, if they are a member of the array, and the array is written back to the room properties, generating a OnPhotonCustomRoomPropertiesChanged() to tell everyone to update their scoreboard.

    This method however allows for a concurrency error where 2 players at nearby times, read the previous array and try and modify their value, only to have the last player to write erase the first player's score. We therefore write the scores using the CAS method so the second concurrent player does not overwrite the first's score. Unfortunately, this leaves us without the second player's score getting updated.

    The "CAS callback" is needed for this case. If a CAS write fails, the user must reattempt the write.

  • The documentation reads...

    SetCustomProperties has an optional expectedValues parameter, which can be used as condition. With expectedValues, the server will only update the properties, if its current key-values match the ones in expectedValues. Updates with outdated expectedValues will be ignored (the clients get an error as a result, others won't notice the failed update).


    This reads a bit confusing as it says the "clients get an error as a result, others won't notice the failed update" I assumed "clients" in this context meant "the client or clients who attempted the failed update".

    As such, i was looking for this error so that I could resend the player's score.
  • Tobias said:

    While the error is not exposed, the essential update is still available: When CAS fails, this is because the "expected properties" did not match the server's values. This case triggers an update event for the properties and so the client gets a property updated callback.

    We should maybe add this callback (it's not in PUN 2 either) or at least update the reference doc.

    If you need the error itself, then please follow JohnTube's suggestion. Is this something the client needs?

    Are you stating in the case of a failed CAS update, only the client who attempted the update gets a OnPhotonCustomRoomPropertiesChanged() event raised?

    If so, the client would not know if this was raised in response to a failed update or another client's score actually changing. This would necessitate a scan of the array on every Update to see if the score you think should be there for you , is there for you, Getting an actual error would enable this to only happen on actual failed updates.
  • Spektre said:

    Tobias said:

    While the error is not exposed, the essential update is still available: When CAS fails, this is because the "expected properties" did not match the server's values. This case triggers an update event for the properties and so the client gets a property updated callback.

    We should maybe add this callback (it's not in PUN 2 either) or at least update the reference doc.

    If you need the error itself, then please follow JohnTube's suggestion. Is this something the client needs?

    Are you stating in the case of a failed CAS update, only the client who attempted the update gets a OnPhotonCustomRoomPropertiesChanged() event raised?

    If so, the client would not know if this was raised in response to a failed update or another client's score actually changing. This would necessitate a scan of the array on every Update to see if the score you think should be there for you , is there for you, Getting an actual error would enable this to only happen on actual failed updates.
    This never got an answer and was important again here. Does the calling client get an update event even when the CAS denied update fails? Do all clients?
  • JohnTube
    JohnTube ✭✭✭✭✭
    Does the calling client get an update event even when the CAS denied update fails? Do all clients?
    In case of SetProperties operation request error due to CAS failure, the calling client will receive an operation response with error code InvalidOperation (-2) and a debug message containing some info about the error. Other clients will not receive anything.
  • develax
    develax ✭✭
    edited September 2019
    Hi @JohnTube,

    Could you explain why did you choose to use the room.LoadBalancingClient.OpSetCustomPropertiesOfRoom() method instead of the regular room.SetCustomProperties()?

    I see that the room.SetCustomProperties() method internally calls room.LoadBalancingClient.LoadBalancingPeer.OpSetPropertiesOfRoom() and also it merges new properties to room.CustomProperties if expectedProperties is empty. Why is this condition employed? The room.LoadBalancingClient.OpSetCustomPropertiesOfRoom() method does call the same method internally. Could you clarify a bit what is going on there and for what?

  • JohnTube
    JohnTube ✭✭✭✭✭
    hi @develax,

    One returns void, the other returns bool.
    I always want to use the return value bool in LoadBalancing operations calls as it could tell you if the operation request was enqueued successfully for sending or not.
  • @JohnTube wouldn't it return false if CAS fails instead of using NetworkingClientOnOpResponseReceived() ?
  • JohnTube
    JohnTube ✭✭✭✭✭
    Hi @develax,

    All LoadBalancing operations are asynchronous.
    The client needs to wait for the operation response.
    Besides, in case of CAS, the server does the comparison/check.
  • Hi @JohnTube,
    JohnTube said:

    Hi @Spektre,
    You should know that:

    - initializing (i.e. first time creating a new property) using CAS is not supported

    In the case of a room, properties can be set when the room is initialized. What practice is used for the first time creating properties of the player? I think they cannot be initialized except through the player.SetCustomProperties() method.