Get pings array results from region with best ping operation

I am trying to get the :
Common::JVector<Common::JVector<unsigned int> > mPingResponses;

from the LoadBalancing :: Client ,but it is a private member, how do you do that?

Or...how can I get a list of pings per region if even if I overload the client the Peer is private as well?

Thanks!

Comments

  • Hi @juaxix.

    This is not exposed in the API of class Client.

    However class Client simply uses the public pingServer API of class PhotonPeer to implement its best region feature.

    Your own code can do the same:
    Simply create a PhotonPeer instance and ping the regions yourself via that instance. Then pass your selected region to class Client with RegionSelectionMode::SELECT.

    For reference this is the current implementation of Client::pingBestRegion():
    		void Client::pingBestRegion(unsigned int pingsPerRegion)
    		{
    			EGLOG(DebugLevel::INFO, L"");
    			for(unsigned int i=0; i<mAvailableRegionServers.getSize(); ++i)
    			{
    				mpPeer->pingServer(mAvailableRegionServers[i], pingsPerRegion);
    				mPingResponses.addElement(JVector<unsigned int>(pingsPerRegion));
    			}
    			mPingsPerRegion = pingsPerRegion;
    		}
    
    As you can see, it simply iterates over the list of available regions and calls PhotonPeer::pingServer() for each entry in the list.
    Just do the same in your own code.

    The ping results arrive in PhotonListener::onPingResponse(), so don't forget to implement PhotonListener as well.
    Note that the callback will not be called just once per region, but once for every ping that got sent to that region. So i.e. if you pass '5' for pingsPerRegion for region 'eu', then it will be called 5 times for that region.

    For reference below is the current onPingResponse() implementation of class Client:
    		void Client::onPingResponse(const JString& address, unsigned int result)
    		{
    			bool receivedAllRequests = true;
    			for(unsigned int i=0; i<mAvailableRegionServers.getSize(); ++i)
    			{
    				if(address == mAvailableRegionServers[i])
    					mPingResponses[i].addElement(result);
    				if(mPingResponses[i].getSize() < mPingsPerRegion)
    					receivedAllRequests = false;
    			}
    			if(!receivedAllRequests)
    				return;
    			unsigned int bestPing = UINT_MAX;
    			unsigned int indexOfRegionWithBestPing = 0;
    			for(unsigned int i=0; i<mPingResponses.getSize(); ++i)
    			{
    				unsigned int ping = 0;
    				for(unsigned int j=0; j<mPingsPerRegion; ++j)
    					ping += mPingResponses[i][j];
    				ping /= mPingsPerRegion;
    				if(ping < bestPing)
    				{
    					bestPing = ping;
    					indexOfRegionWithBestPing = i;
    				}
    			}
    			mPingResponses.removeAllElements();
    			// replace the cluster with the best ping with a random cluster in the same region as the cluster with the best ping
    			int index = mAvailableRegions[indexOfRegionWithBestPing].indexOf(L'/');
    			mSelectedRegion = mRegionWithBestPing = (index==-1?mAvailableRegions[indexOfRegionWithBestPing]:mAvailableRegions[indexOfRegionWithBestPing].substring(0, index))+L"/*";
    			authenticate();
    		}
    

    You could use that as a starting point for your own implementation of that callback and then adjust your implementation according to your needs.
  • juaxix
    juaxix ✭✭
    edited November 2020
    Ok, I see what I have to do but I still need to know more about the Peer, can I have another Peer instance just for the pings and overload the LoadBalancing::Client using this to request the pings?

    Something like this:
    
    typedef TMap<FString, TArray<uint32> > TPingsResultMap;
    
    class PhotonCloudLoadBalancingClient: public ExitGames::LoadBalancing::Client
    {
    	bool m_Pinging = false;
    	int m_PingsPerServer = 5;
    	ExitGames::Common::JVector<ExitGames::Common::JString> m_Servers;
    
    public:
    	ExitGames::LoadBalancing::Peer* PingPeer;
    	TPingsResultMap PingResults;
    	FOnPingServersCompleted OnPingServersCompleted;
    
    	PhotonCloudLoadBalancingClient(ExitGames::LoadBalancing::Listener& listener, const ExitGames::Common::JString& applicationID, const ExitGames::Common::JString& appVersion, nByte connectionProtocol= ExitGames::Photon::ConnectionProtocol::DEFAULT, bool autoLobbyStats=false, nByte regionSelectionMode= ExitGames::LoadBalancing::RegionSelectionMode::DEFAULT, bool useAlternativePorts=false)
    		: Client(listener, applicationID, appVersion, connectionProtocol, autoLobbyStats, regionSelectionMode, useAlternativePorts),
    		  PingPeer(ExitGames::Common::MemoryManagement::allocate<ExitGames::LoadBalancing::Peer>(static_cast<PhotonListener&>(*this),ExitGames::Photon::ConnectionProtocol::UDP)),
    		  PingResults()
    	{
    
    	}
    
           ~PhotonCloudLoadBalancingClient()
            {
                   ExitGames::Common::MemoryManagement::deallocate(PingPeer);
            }
    
    	void PingServers(const ExitGames::Common::JVector<ExitGames::Common::JString>& Servers, int NumPings = 5)
    	{
    		if (m_Pinging || Servers.getSize() == 0)
    		{
    			return;
    		}
    
    		m_Pinging = true;
    
    		PingResults.Empty();
    
    		m_PingsPerServer = NumPings;
    		m_Servers = Servers;
    
    		for (unsigned i = 0; i<m_Servers.getSize(); i++)
    		{
    			PingPeer->pingServer(m_Servers[i], m_PingsPerServer);
    		}
    	}
    
    	void onPingResponse(const ExitGames::Common::JString& address, unsigned int result) override
    	{
    		FString AddressKey = address.UTF8Representation().cstr();
    		if (!PingResults.Contains(AddressKey))
    		{
    			PingResults.Add(AddressKey, TArray<uint32>());
    		}
    
    		TArray<uint32> *Pings = &PingResults[AddressKey];
    
    		if (Pings != nullptr)
    		{
    			Pings->Add(result);
    		}
    
    		bool completed = true;
    		for(auto& PingResult: PingResults)
    		{
    			if (PingResult.Value.Num() < m_PingsPerServer)
    			{
    				completed = false;
    			}
    		}
    
    		if (completed)
    		{
    			m_Pinging = false;
    			OnPingServersCompleted.Broadcast(PingResults);
    		}
    	}
    };
    
    
    
    

    this way I could just use this PingPeer but I think i need to move it to a background task to avoid blocking the game thread in unreal, i guess i can have a peer and a loadbalancing client without connect them ,only to do the pings , it needs PingPeer->service(dispatchIncomingCommands) though
  • Hi @juaxix.
    can I have another Peer instance just for the pings
    Absolutely.
    and overload the LoadBalancing::Client using this to request the pings?
    While you can do that, it might be preferable to let the class of which your Client instance is a member, just directly interact with that separate PhotonPeer instance to avoid potential issues when upgrading to newer versions of Photon which could potentially to changes to the Client class that might break your subclass.
    but I think i need to move it to a background task to avoid blocking the game thread in unreal
    No, this is not required as pingServer() is asynchronous and non-blocking (it spawns a new thread for each ping under the hood)
    i guess i can have a peer and a loadbalancing client without connect them ,only to do the pings
    I don't see a point in having an extra Client instance that you don't use at all.
    it needs PingPeer->service(dispatchIncomingCommands) though
    No, that is not required as the ping threads already do that themselves.
  • thanks, so what I did is something like this:
    class PhotonCloudLoadBalancingClient: public ExitGames::LoadBalancing::Client
    {
    	ExitGames::Common::JVector<ExitGames::Common::JString> m_Servers;
    
    public:
    	ExitGames::LoadBalancing::Peer* PingPeer;
    	TPingsResultMap PingResults;
    
    	PhotonCloudLoadBalancingClient(ExitGames::LoadBalancing::Listener& listener, const ExitGames::Common::JString& applicationID, const ExitGames::Common::JString& appVersion, nByte connectionProtocol= ExitGames::Photon::ConnectionProtocol::DEFAULT, bool autoLobbyStats=false, nByte regionSelectionMode= ExitGames::LoadBalancing::RegionSelectionMode::DEFAULT, bool useAlternativePorts=false)
    		: Client(listener, applicationID, appVersion, connectionProtocol, autoLobbyStats, regionSelectionMode, useAlternativePorts),
    		  PingPeer(ExitGames::Common::MemoryManagement::allocate<
    			 ExitGames::LoadBalancing::Peer>(
    				static_cast<PhotonListener&>(*this), connectionProtocol)),
    		  PingResults()
    	{
    	}
    
    	void serviceBasic() override
    	{
    		Client::serviceBasic();
    		if (m_Pinging)
    		{
    			PingPeer->serviceBasic();
    		}
    	}
    
    	~PhotonCloudLoadBalancingClient()
    	{
    		ExitGames::Common::MemoryManagement::deallocate(PingPeer);
    	}
    
    	void PingServers(const ExitGames::Common::JVector<ExitGames::Common::JString>& Servers, int NumPings = 5)
    	{
    		// use the PingPeer to ping the servers here
    	}
    
    	void onPingResponse(const ExitGames::Common::JString& address, unsigned int result) override
    	{
    		//broadcast the results
    	}
    };
    
    

    so i use this class as the listener class now