Jonah Begood
Jonah Begood

Reputation: 317

Multipeer connectivity data stream issue over Wifi

I'm using multipeer connectivity to synchronize between several devices connected through Wifi the movement of nodes in a 3D SceneKit environnement.

The node movement is calculated on the master device and sent to the slave devices which then set the node to the position received. The movement is sent via the renderer loop of the SceneView of the master, and through a data stream opened with each slave and managed with the MultipeerConnectivity framework.

The video below shows the result, and the issue which is the jittering of the ball on the slave (right) due to a regular pause every 0.6-0.7 second in the reception of the data through the stream. The counter on the upper left shows that no packet is lost. There is also no issue with the integrity of the data received.

enter image description here

This very regular pause on the slaves is not present in the simulator but only when it runs on real devices, and whatever the devices (iPhone or Ipad, old or recent).

Is there a way to find out what can cause this regular pause on the slaves devices ?

Would it make sense to have the input stream on the slaves executed on a dedicated thread/runloop instead of the runloop of the main thread ?

Below the implementation

Master Multipeer Connectivity initialization

    PeerID = MCPeerID(displayName: "Master (" + UIDevice.current.name + ")")
    MPCSession = MCSession(peer: PeerID, securityIdentity: nil, encryptionPreference: .none)
    MPCSession.delegate = self
    
    ServiceAdvertiser = MCNearbyServiceAdvertiser(peer: PeerID, discoveryInfo: nil, serviceType: "ARMvt")
    ServiceAdvertiser.delegate = self
    ServiceAdvertiser.startAdvertisingPeer()
    

Slave Multipeer Connectivity initialization

    PeerID = MCPeerID(displayName: "Slave (" + UIDevice.current.name + ")")
    MPCSession = MCSession(peer: PeerID, securityIdentity: nil, encryptionPreference: .none)
    MPCSession.delegate = self
    
    ServiceBrowser = MCNearbyServiceBrowser(peer: PeerID,  serviceType: "ARMvt")
    ServiceBrowser.delegate = self
    ServiceBrowser.startBrowsingForPeers()

Function that sends the data, called in the renderer function

func MPCSendData(VCRef: GameViewController, DataToSend: Dictionary<String, Any>, ViaStream: Bool = false)
{
    var DataFilledToSend = DataToSend
    var DataConverted = try! NSKeyedArchiver.archivedData(withRootObject: DataFilledToSend, requiringSecureCoding: true)
    var TailleData: Int = 0
    var NewTailleData: Int = 0
    
            
    if ViaStream    // Through the stream
    {
            // Filling data to have a constant size packet
            // kSizeDataPack is set to 2048. The bigger it is the worst is the jittering.
        VCRef.Compteur = VCRef.Compteur + 1
        VCRef.Message.SetText(Text: String(VCRef.Compteur))
        DataFilledToSend[eTypeData.Compteur.rawValue] = VCRef.Compteur
        DataFilledToSend[eTypeData.FillingData.rawValue] = "A"
        TailleData = DataConverted.count
        DataFilledToSend[eTypeData.FillingData.rawValue] = String(repeating: "A", count: kSizeDataPack - TailleData)
        DataConverted = try! NSKeyedArchiver.archivedData(withRootObject: DataFilledToSend, requiringSecureCoding: false)
        NewTailleData = DataConverted.count
        DataFilledToSend[eTypeData.FillingData.rawValue] = String(repeating: "A", count: kSizeDataPack - TailleData - (NewTailleData - kSizeDataPack))

        DataConverted = try! NSKeyedArchiver.archivedData(withRootObject: DataFilledToSend, requiringSecureCoding: false)
        
        if VCRef.OutStream!.hasSpaceAvailable
        {
            let bytesWritten = DataConverted.withUnsafeBytes { VCRef.OutStream!.write($0, maxLength: DataConverted.count) }

            if bytesWritten == -1 { print("Erreur send stream") }
        } else { print("No space in stream") }
    }
    else    // Not through the stream
    {
        let Peer = VCRef.MPCSession.connectedPeers.first!
        try! VCRef.MPCSession.send(DataConverted, toPeers: [Peer], with: .reliable)
    }
}

Function that is called when data is received through the stream on the slave

func stream(_ aStream: Stream, handle eventCode: Stream.Event)
{
    DispatchQueue.main.async
    {
        switch(eventCode)
        {
        case Stream.Event.hasBytesAvailable:
            let InputStream = aStream as! InputStream
            var Buffer = [UInt8](repeating: 0, count: kSizeDataPack)
            let NumberBytes = InputStream.read(&Buffer, maxLength: kSizeDataPack)
            let DataString = NSData(bytes: &Buffer, length: NumberBytes)
            if let _ = NSKeyedUnarchiver.unarchiveObject(with: DataString as Data) as? [String:Any] //deserializing the NSData
            {
                ProcessMPCDataReceived(VCRef: self, RawData: DataString as Data)
            }
            
        case Stream.Event.hasSpaceAvailable:
            break
            
        case Stream.Event.errorOccurred:
            print("ErrorOccurred: \(String(describing: aStream.streamError?.localizedDescription))")
            
        default:
            break
        }
    }
}

Function that processes the data received

func ProcessMPCDataReceived(VCRef: GameViewController, RawData: Data)
{
    let DataReceived: Dictionary = (try! NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(RawData) as! [String : Any])

    switch DataReceived[eTypeData.EventType.rawValue] as! String
    {
    case eTypeEvent.SetMovement.rawValue:
        VCRef.CurrentMovement = eTypeMovement(rawValue: DataReceived[eTypeData.Movement.rawValue] as! String)!
     
    case eTypeEvent.SetPosition.rawValue:
        VCRef.Ball.position = DataReceived[eTypeData.Position.rawValue] as! SCNVector3
            
    default:
        break
    }
}

Upvotes: 1

Views: 482

Answers (1)

Tiki McFee
Tiki McFee

Reputation: 228

Looks like you're dispatching work on the main thread. While I wouldn't expect unpacking data to really cause these regular pauses, it's possible that you're running in to a data processing bottleneck. In other words, the time it's taking you to receive, unpack, and reposition nodes (which also incurs implicit SceneKit transactions) may be just enough to make the device slow down. It seems like your code is compact enough to allow you to dispatch the entire thing to a queue. I recommend trying that to see if you get any new behavior. Try DispatchQueue.global(), or better, make your own with DispatchQueue(label:"StreamReceiver", qos: .userInteractive). I think the async dispatch is perfectly fine here.

EDIT: Actually, looking at more, I think this may be related to SceneKit transactions. It looks like you're not really 'pausing', but decelerating. I mentioned that implicit transaction - when you position the node, explicitly start, set the animation time for, and end a SCNTransaction. A snippit I've been using is:

func sceneTransaction(_ duration: Int? = nil,
                      _ operation: () -> Void) {
    SCNTransaction.begin()
    SCNTransaction.animationDuration =
        duration.map{ CFTimeInterval($0) }
            ?? SCNTransaction.animationDuration
    operation()
    SCNTransaction.commit()
}

Try calling this with your repositioning code, or just sticking the transaction start/animationTime/end around your block. Good luck!

EDIT 2: Ok, one more thing. If it makes sense for your use case, make sure you've stopped browsing and advertising for peers. It's an expensive bit of networking, and it may be bogging down the entire subsystem.

Upvotes: 0

Related Questions