简体   繁体   中英

Possible Bug in Swift 3 with Implicit Bridging of multidimensional arrays?

I am currently working on a SpriteKit game written in Swift 3.

When I tried generating SKShapeNodes from points stored in a multidimensional array, using the init(splinePoints: UnsafeMutablePointer, count: Int) initialiser, I found that I just was not able to use implicit bridging feature of Swift, namely by adding a "&" in front of the var in question, to hand it to the function as an UnsafeMutablePointer.

Here is an image of the reproduction of the bug, including the error, in Xcode Playgrounds:

在Playground中复制bug

The error reads

Cannot convert value of type '[CGPoint]' to expected argument type 'CGPoint'

, although the initialiser reference explicitly states that it wants a pointer to the CGPoint array. Even in the Apple provided example , section

Creating Shape Nodes from Points , it is done exactly that way:

var points = [CGPoint(x: 0, y: 0),               
              CGPoint(x: 100, y: 100),               
              CGPoint(x: 200, y: -50),               
              CGPoint(x: 300, y: 30),               
              CGPoint(x: 400, y: 20)]         
let linearShapeNode = SKShapeNode(points: &points,                                   
                                  count: points.count)          
let splineShapeNode = SKShapeNode(splinePoints: &points,                                   
                                  count: points.count)

So my question is why it is not working, whether it is a compiler bug, and if not, how I can fix it.

Please note, however, that creating a copy of the array inside the function, like this...

func createPressurePointShape(arm: Int, finger: Int) {
    var tempPoints = pointsArray[arm][finger]
    let shapeNode = SKShapeNode.init(splinePoints: &tempPoints, count: pointsArray[arm][finger].count)
}

...is not a solution for me, because in my actual application the variable would only exist for 1 frame, and either be gone or create a memory leak afterwards. Just the fact that this lets the error go away though hints towards a bug in Swift or Xcode rather than my app, but I am unsure about it.

Thank you for your help in advance. Here's the code example, if somebody would like to play around with it:

//: Playground - noun: a place where people can play

import UIKit
import SpriteKit

let armCount = 4
let fingersPerArm = 4
let pressurePointsPerFinger = 3

private var pointsArray = [[[CGPoint]]](repeating: [], count: Int(armCount))

for armIndex in 0...Int(armCount - 1) {

    for fingerIndex in 0...(fingersPerArm - 1) {

        pointsArray[armIndex].append([])
        for innerIndex in 0..<pressurePointsPerFinger {
            pointsArray[armIndex][fingerIndex].append(CGPoint(x: 0.5, y: 0.5))
        }
    }
}

let exampleArm = 1
let exampleFinger = 1

func createPressurePointShape(arm: Int, finger: Int) {
    let shapeNode = SKShapeNode.init(splinePoints: &pointsArray[arm][finger], count: pointsArray[arm][finger].count)
}

createPressurePointShape(arm: exampleArm, finger: exampleFinger)

You're quite right, this does indeed look like a bug. I have filed a report here . A more minimal example would be be:

func foo(_ ptr: UnsafeMutablePointer<Int>) {}

var arr = Array(repeating: Array(0 ..< 5), count: 5)

let i = 0
foo(&arr[i]) // Cannot convert value of type '[Int]' to expected argument type 'Int'

I would however be wary of the suggestion to pass &pointsArray[arm][finger][0] to SKShapeNode 's initialiser – it happens to work because the compiler is just passing the address of the first element in the array, but I don't believe that's guaranteed. It would be equally valid for the compiler to copy the first element into a temporary, pass the address of that temporary, and then write-back to the array afterwards. That would invoke undefined behaviour in the initialiser.

Instead, I would just create a convenience initialiser on SKShapeNode in order to both side-step the bug in the first place and work with a nicer API:

extension SKShapeNode {

    convenience init(splinePoints: inout [CGPoint]) {
        self.init(splinePoints: &splinePoints, count: splinePoints.count)
    }
}

func createPressurePointShape(arm: Int, finger: Int) {
    let shapeNode = SKShapeNode(splinePoints: &pointsArray[arm][finger])
}

The inout still isn't fully satisfactory though as I don't believe the passed memory is ever mutated (really that's a bug with SKShapeNode 's API). However, if inout was taken away and a temporary mutable variable was passed instead to init(splinePoints:count:) , the array's buffer would always be copied first (as it needs to be uniquely referenced), which may not be desireable.

Try pointing to the first element of the array like this.

var finger = [CGPoint(x: 0.5, y: 0.5), CGPoint(x: 0.5, y: 0.5), CGPoint(x: 0.5, y: 0.5)]
var arm = [finger, finger, finger, finger]
var pointsArray = [arm, arm, arm, arm]

let exampleArm = 1
let exampleFinger = 1

func createPressurePointShape(arm: Int, finger: Int) {
  let shapeNode = SKShapeNode.init(splinePoints: &pointsArray[arm][finger][0], count: pointsArray[arm][finger].count)
}

createPressurePointShape(arm: exampleArm, finger: exampleFinger)

You are probably missing a pressure count ( per finger), here is a pseudo code:

let examplePressuePointsPerFinger = 1

func createPressurePointShape(arm: Int, finger: Int, pressurePoint: Int) {
    let shapeNode = SKShapeNode.init(splinePoints: &pointsArray[arm][finger][pressurePoint], count: pointsArray[arm][finger].count)
}

createPressurePointShape(arm: exampleArm, finger: exampleFinger, pressurePoint: examplePressuePointsPerFinger)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM