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.
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
}
}
