0

I'm currently designing a UI for a framework I've been given that plays connect 4. This framework is encapsulated in a class called GameSession. I will not describe the ins and outs of it's API and how it operates. I don't believe it's important.

I believe I may be misunderstanding dispatch queues and I am using them incorrectly. But, I have searched endlessly and have not found anything that hints to a solution to my problem.

Here is a a short explanation on what is happening. The method that controls the moves is playGame(botStarts: Bool, atColumn: Int). In this method, a collision boundary is added at the correct row and column using a method called addBoundary(atRow: Int, atColumn: Int). Then, a disc is created using a method called dropDisc(atColumn: Int, color: UIColor). This method creates a custom UIView, adds it to the view on screen and adds collision and gravity behaviours. It falls until it reaches the previously added boundary.

In playGame() I am dropping the discs onto the screen using DispatchMain.Queue.async{dropDisc()}. But, every time I call playGame() for the second time and beyond, the custom discs are drawn at the top of the screen but they fail to fall. On the first iteration, they are drawn and fall as expected.

Below are the functions that I've referenced above.

private func playGame(botStarts: Bool, dropPieceAt: Int) {
        DispatchQueue.global(qos: .userInitiated).async {
            if botStarts {
                if let move = gameSession.move {
                    let column = move.action % self.gameSession.boardLayout.columns
                    let row = move.action / self.gameSession.boardLayout.columns
                    self.addBoundary(atRow: row, atColumn: column)
                    DispatchQueue.main.async {
                        self.dropDisc(atColumn: column, color: move.color)
                    }
                }
            } else {
                let column = dropPieceAt
                if self.gameSession.userPlay(at: column) {
                    if let move = self.gameSession.move {
                        print(move)
                        let column = move.action % self.gameSession.boardLayout.columns
                        let row = move.action / self.gameSession.boardLayout.columns
                        self.addBoundary(atRow: row, atColumn: column)
                        DispatchQueue.main.async {
                            self.dropDisc(atColumn: column, color: move.color)
                        }
                    }
                    if let move = self.gameSession.move {
                        let column = move.action % self.gameSession.boardLayout.columns
                        let row = move.action / self.gameSession.boardLayout.columns
                        self.addBoundary(atRow: row, atColumn: column)
                        DispatchQueue.main.async {
                            self.dropDisc(atColumn: column, color: move.color)
                        }
                    }
                }
                if self.gameSession.done {
                    if let outcome = self.gameSession.outcome {
                        DispatchQueue.main.async {
                            self.gameLabel.text = outcome.message + "\n Winning pieces \(outcome.winningPieces)"
                        }
                    }
                }
            }
        }
    }


 private func dropDisc(atColumn: Int, color: UIColor) {
        var frame = CGRect()
        frame.origin = CGPoint.zero
        frame.size = Constants.bubbleSize
        let x = CGFloat(39) + CGFloat(47 * atColumn)
        frame.origin.x = x
        let bubbleView = DiscView(frame: frame, color: color)
        gameView.addSubview(bubbleView)
        collider.addItem(bubbleView)
        gravity.addItem(bubbleView)
    }



    // Adds a boundary using the row and column obtained from game session.
    private func addBoundary(atRow: Int, atColumn: Int) {
        let fromCoordX = CGFloat(16 + (boardView.initialX-boardView.radius)) + CGFloat(47 * atColumn)
        let toCoordX = fromCoordX + CGFloat(24)
        let coordY =  CGFloat(198.5 + (boardView.initialY+boardView.radius)) + CGFloat(45 * atRow)
        let fromPoint = CGPoint(x: fromCoordX, y: coordY+1)
        let toPoint = CGPoint(x: toCoordX, y: coordY+1)
        self.collider.addBoundary(withIdentifier: "boundary" as NSCopying, from: fromPoint, to: toPoint)
        self.drawLineFromPoint(start: fromPoint, toPoint: toPoint, ofColor: UIColor.red, inView: self.gameView)

    }

Here is a screenshot of my screen: https://i.stack.imgur.com/r2470.jpg.

On the bottom row you can see the users disc (yellow) and the bots disc (red). These were added on the first call to playGame(). But, on the top, you can see the two discs that were added on the second call to playGame(). These do not fall.

No matter what I've tried

Any feedback is much appreciated!

DylanR
  • 25
  • 1
  • 5
  • Why are you using dispatch queues here at all? Why not just remove all the dispatch queue calls and do everything on the main thread? Was there some issue caused by doing that? If so, what was it? If you do that, does this particular problem (by any chance) go away? – matt Apr 14 '19 at 18:01
  • That does in fact solve my problem . playGame() was a method I was provided and it was initially using them for the following: DispatchQueue.main.async { self.gameLabel.text = printBoard("AlphaC4", [move.action], move.color == UIColor.red ? "X" : "O") }. printBoard() was a closure that returned a string that represented the state of board. The text of the label in the view was set to represent the board as you can see. Is there any reason why these UI updates had to occur in dispatch queues and not on the main thread? If I remove them the update only occurs at the end. – DylanR Apr 14 '19 at 18:30
  • "Is there any reason why these UI updates had to occur in dispatch queues and not on the main thread?” I’m not sure what you are asking. _All_ UI updates _must_ occur on the main thread. The question is why you were doing something so minor as adding a collision boundary in a background thread. It seemed to me that you were just confusing yourself with your dispatch queues; they cause code to run out of order, which is what you seemed to be complaining about. – matt Apr 14 '19 at 19:11
  • So is removing all the DispatchQueue stuff the right answer or not? If it is, I’ll just give it as an answer. If it causes a new issue, maybe you should delete or edit the question to ask about that. – matt Apr 14 '19 at 19:13
  • Yes, Matt. That is the right answer. Thank you. – DylanR Apr 14 '19 at 19:23

1 Answers1

1

Background threading is hard, and should be used only when absolutely necessary (because it is forced upon you, or because you have a time-consuming activity to perform and you don’t want to freeze the interface, which is owned by the main thread). It doesn’t seem necessary here. You are not doing anything time-consuming. And it looks like use of DispatchQueue.global might be confusing you; a background dispatch queue causes your code to run out of order, and apparently you weren’t aware of this.

Solution: just get rid of all your DispatchQueue.global and DispatchQueue.main code. (In other words, eliminate those lines and matching right curly brace lines.) Everything will then just run on the main queue and there’s no reason why it shouldn’t.

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • And you might want to read my blog post http://www.programmingios.net/what-asynchronous-means/, as I think it might explain your issue. – matt Apr 14 '19 at 19:39