When CAS fails, I want to get which key failed.

Options
I want to use CAS to implement who gets the item on the stage.
Based on various samples, I was able to implement it in the following way.
    public bool SetCustomPropertySafe(this Room room, string key, object newValue, WebFlags webFlags = null)
    {
        Hashtable newProps = new Hashtable(1) {{key, newValue}};
        Hashtable oldProps = new Hashtable(1) {{key,  PhotonNetwork.CurrentRoom.CustomProperties[key]}};
		return PhotonNetwork.CurrentRoom.SetCustomProperties(newProps, oldProps);
    }
	
    private void OnEnable()
    {
        PhotonNetwork.NetworkingClient.OpResponseReceived += NetworkingClientOnOpResponseReceived; 
    }

    private void NetworkingClientOnOpResponseReceived(OperationResponse opResponse)
    {
        if (opResponse.OperationCode == OperationCode.SetProperties &&
            opResponse.ReturnCode == ErrorCode.InvalidOperation)
        {
            // cas failure
            // TODO: Which key failed?
            Debug.Log(opResponse.DebugMessage);
            Debug.Log(opResponse.ToStringFull());
            Debug.Log(opResponse.Parameters.ToStringFull());
        }
    }

However, what I want to do is to determine which key failed when multiple keys (items) are registered at the same time using OpResponseReceived (OperationResponse opResponse).

However, I'm having trouble with opResponse not containing the value of the failed key.
DebugMessage contains the string "(CAS update failed: property='item1' has value='1')" which is the information of the failed key.
However, I am having trouble identifying the opResponse.Parameters because it does not contain any value.
How can I include the value of the failed key in Parameters?
Or how can I find out the value of the failed key in a different way?

Comments

  • JohnTube
    JohnTube ✭✭✭✭✭
    Options
    Hi @canvas,

    Thank you for choosing Photon!

    Unfortunately, currently there is no way to find out which SetProperties operation request failed or which properties caused the CAS failure from the operation response itself as it only contains the error code and error message.

    So if you really need this: cache the properties to be set locally on the client before calling SetProperties and use them inside the callback or operation response for extra checks then clear them. To be safe also make sure you call SetProperties only once at a time and wait for the server feedback to make the next call (so maybe queue calls to SetProperties?)...also don't forget to clear the cached properties sent to the server in case of disconnects or leaving the room etc.

    Let me know if this thelps or you need some code.
  • JohnTube
    JohnTube ✭✭✭✭✭
    Options
    Hi @canvas,

    made a simple first attempt to achieve this:

    ofc there is always room for improvement
    this wrapper assumes you call UpdateProps from a single Unity main thread
    also, this assumes you don't set roomOptions.BroadcastActorPropertiesToAll to false
    lmk if this works and if this is what you needed
    using System;
    using ExitGames.Client.Photon;
    using Photon.Pun;
    using Photon.Realtime;
    
    public class PropertiesUpdateWrapper : MonoBehaviourPunCallbacks
    {
        private Action<Hashtable> cachedSuccessCallback;
        private Action<Hashtable, string> cachedFailureCallback;
        private bool pending;
        // todo: make an SetPropertiesRequest class and add SetProperties params to it and queue it?
        private Hashtable propertiesSent;
        //private Hashtable expectedPropertiesSent;
    
        public override void OnEnable()
        {
            base.OnEnable();
            PhotonNetwork.NetworkingClient.EventReceived += OnEventReceived;
            PhotonNetwork.NetworkingClient.OpResponseReceived += OnOpResponseReceived;
        }
    
        public override void OnDisable()
        {
            base.OnDisable();
            PhotonNetwork.NetworkingClient.EventReceived -= OnEventReceived;
            PhotonNetwork.NetworkingClient.OpResponseReceived -= OnOpResponseReceived;
            this.Clear();
        }
    
        private void OnEventReceived(EventData photonEvent)
        {
            if (photonEvent.Code == EventCode.PropertiesChanged && photonEvent.Sender == PhotonNetwork.LocalPlayer.ActorNumber)
            {
                this.cachedSuccessCallback?.Invoke(this.propertiesSent);
                this.Clear();
            }
        }
    
        public bool UpdateRoomProperties(Hashtable properties, Hashtable expectedProperties, Action<Hashtable> success, Action<Hashtable, string> failure, WebFlags webFlags = null)
        {
            if (this.pending) // todo: make a queue of operations and send them in a coroutine loop or Update?
            {
                failure?.Invoke(properties, "Another operation is pending response");
                return false;
            }
            if (!PhotonNetwork.CurrentRoom.SetCustomProperties(properties, expectedProperties, webFlags))
            {
                failure?.Invoke(properties, "Operation not sent, see error logs for more info");
                return false;
            }
            this.propertiesSent = properties;
            //this.expectedPropertiesSent = expectedProperties;
            this.cachedSuccessCallback = success;
            this.cachedFailureCallback = failure;
            this.pending = true;
            return true;
        }
    
        public bool UpdateActorProperties(Player player, Hashtable properties, Hashtable expectedProperties, Action<Hashtable> success, Action<Hashtable, string> failure, WebFlags webFlags = null)
        {
            if (this.pending) // todo: make a queue of operations and send them in a coroutine loop or Update?
            {
                failure?.Invoke(properties, "Another operation is pending response");
                return false;
            }
            if (!player.SetCustomProperties(properties, expectedProperties, webFlags))
            {
                failure?.Invoke(properties, "Operation not sent, see error logs for more info");
                return false;
            }
            this.propertiesSent = properties;
            //this.expectedPropertiesSent = expectedProperties;
            this.cachedSuccessCallback = success;
            this.cachedFailureCallback = failure;
            this.pending = true;
            return true;
        }
    
        private void OnOpResponseReceived(OperationResponse opResponse)
        {
            if (opResponse.OperationCode == OperationCode.SetProperties && opResponse.ReturnCode != ErrorCode.Ok)
            {
                this.cachedFailureCallback?.Invoke(this.propertiesSent, opResponse.DebugMessage);
                this.Clear();
            }
        }
    
        private void Clear()
        {
            this.cachedFailureCallback = null;
            this.cachedSuccessCallback = null;
            this.pending = false;
        }
    
        public override void OnDisconnected(DisconnectCause cause)
        {
            if (this.pending)
            {
                this.cachedFailureCallback?.Invoke(this.propertiesSent, string.Format("client disconnected, cause: {0}", cause));
            }
            this.Clear();
        }
    }