0
votes

I am rendering a SKScene as a mere grid of node where I project a data structure made by a greater grid of journals.

While asked to render a node at (i,j) position in the grid, the code looks for media (texture) availability, and in that case, adds a SKSpriteNode. But if not immediately avail, requests the texture from a remote server (in a thread of course). As a consequence, because it is not immediately available, the code creates a fallback SKShapeNode in order to make it visible by the user while downloading the texture.

When and if the texture is received, the replaces the fallback SKShapeNode with the SKSpriteNode with a newly created SKSpriteNode made of the received texture.

Each node is named against its position in the grid, aka. "((i),(j))".

Apart of that, I handle a pinch gesture that is responsible for rearrange nodes position depending on the scale educated by the pinch (nut not applying a scale to the scene).

All is ok on the paper, but unfortunately, I didn't find a way to avoid mutating the sprite node children list while enumerating.

What should I do?

    for i in 0..<tilesh {
        for j in 0..<tilesw {
            let row = i
            let col = j + band * tilesw

            if let journal = Journal.journalForLevel(level, row: row, col: col) {

                // --- ask for the journal cover, but if not straght availble, fetch from cache or remote server
                let _ = journal.getCover {

                    // If completion block is invoked, that mean the cover has been fetch from a cache level
                    (journal: Journal) in

                    // If cover is now available, we simply creat the tile as a SKNode
                    createteOrUpdateJournalNode(panel, journal: journal, row: i, col: j)
                }

                // --- Draw journal at each tile if cover already avail, just draw it
                createteOrUpdateJournalNode(panel, journal: journal, row: i, col: j)
            }
            // --- No journal available
            else {
                createteOrUpdateJournalNode(panel, journal: nil, row: i, col: j)
                print("No journal avail for band=\(band) at row=\(row), col=\(col)")
            }
        }   // each j
    }   // each i






   func createteOrUpdateJournalNode(parent: Panel, journal: Journal?, row: Int, col: Int) {

        var node : SKNode

        let alpha:CGFloat = 1 //0.5 + 0.5 * CGFloat((band+1)/panels.count)

        if let journal = journal {

            if let _ = journal.cover {

                let cover   = prepareCover(journal)
                let texture = SKTexture(image: cover)
                let n       = SKSpriteNode(texture: texture)
                node = n

                n.anchorPoint   = CGPoint(x: 0, y: 0)
            }
            else {
                let n       = SKShapeNode(rect: CGRect(x: 0, y: 0, width: w, height: h))
                node = n

                let c = 0.5 + CGFloat(arc4random() % 127) / 127
                n.fillColor     = UIColor(red: c, green: c, blue: 1, alpha: alpha)
                n.strokeColor   = UIColor.blueColor()
                n.lineWidth     = 4
            }
        }
        else {
            let n       = SKShapeNode(rect: CGRect(x: 0, y: 0, width: w, height: h))
            node = n

            let c = 0.5 + CGFloat(arc4random() % 127) / 127
            n.fillColor     = UIColor(red: 1, green: c, blue: c, alpha: alpha)
            n.strokeColor   = UIColor.redColor()
            n.lineWidth     = 4
        }

        let name = "\(band)-\(row)-\(col)"
        node.name = name
        node.position       = CGPoint(x: CGFloat(col) * w * timeScale, y: CGFloat(row) * h)

        if let n = parent.childNodeWithName(name) {
            //n.runAction(SKAction.removeFromParent())
            print ("---- should prune node from band \(band) at \(name)")
        }

        print(">>>> Creating node \(name)")
        parent.addChild(node)
        print("<<<<")
    }
2
I haven't read all of the question but this is a classic error that can occur with most collection classes and most languages. The solution is normally to keep separate, temporary, arrays of what you want added/removed while you enumerate and to then make the changes once the enumeration is complete. Hope that gets you most of the way there...trojanfoe
@trojanfoe Agreed, but my question is where should I swap my kind of "double buffering" datastructure. I mean if I maintain a new list of mutable nodes, when and where should I apply all this in order to avoid the issue?Stéphane de Luca
OK, so I read the question, but I don't know how to answer your question. I don't see any code that mutates the child nodes, however?trojanfoe
createteOrUpdateJournalNode() mutate the children of the node parent, which I added to the question.Stéphane de Luca
I can't see anywhere in the code shown where you are enumerating a collection and mutating it. Which line gives you an error? Another approach would be to either use an SKTextureNode as your placeholder (with an appropriate placeholder texture) and then you can just swap out the texture on the existing node or you could use your own SKNode subclass that had both a shape node and a texture node as children and you simply hide the appropriate child nodePaulw11

2 Answers

1
votes

Perhaps you could avoid the mutation problem altogether by using a SKSpriteNode as the temporary placeholder and only assigning a new texture when you receive the data. that way, the same object/instance is used and your enumeration will not be affected

0
votes

I solved the issue by founding that one can safely mutate the node list in the SpriteKit update() func as follows:

   // SpriteKits gameloop function
    override func update(currentTime: CFTimeInterval) {

        // Mutate list
        for p in panels {
            p.addNodes()
        }
    }

p refers to an array of Panel (see the Panel class later in the answer).

I prepare the additional nodes from the rendering loop in my slightly modified version of createOrUpdateJounralNode() as follows:

    let node = createOrUpdateJournalNode(nil, row: i, col: j)
    panel.addNode(node)

Where panel.addNode() simply refers to the code snippet as follows:

class Panel {
    var addNodesList = [SKNode]()

    func addNode(node: SKNode) {
        addNodesList.append(node)
    }

    func addNodes() {

        if addNodesList.count > 0 {
            for n in addNodesList {
                if let name = n.name {
                    if let n = childNodeWithName(name) {
                        n.removeFromParent()
                        print("---- removing node \(name)")
                    }
                }
                addChild(n)
                print("++++ adding node \(name)")
            }

            addNodesList = [SKNode]()
        }
    }
}

The slightly modified version of createOrUpdateJournalNode() is as follows:

    func createOrUpdateJournalNode(journal: Journal?, row: Int, col: Int) -> SKNode {
       // same code than before except we don't add child to parent, but simply return the node

       return node
    }