Reinhard Männer
Reinhard Männer

Reputation: 15247

Don't understand SCNSceneRenderer delegate data race warning

My app needs a reference to the SCNSceneRenderer for hit testing. I thus use SCNSceneRenderer delegate to get and store the reference as property in my ViewModel:

@MainActor
@Observable
final class ViewModel: NSObject, @preconcurrency SCNSceneRendererDelegate {
    private(set) weak var renderer: (any SCNSceneRenderer)?
//…
    
    func renderer(_ renderer: any SCNSceneRenderer, updateAtTime time: TimeInterval) {
        self.renderer = renderer
    }
}  

With this code, I get in func renderer the warning:

warning: data race detected: @MainActor function at …/ViewModel.swift:10 was not called on the main thread

As a bug fix, I changed func renderer to:

func renderer(_ renderer: any SCNSceneRenderer, updateAtTime time: TimeInterval) {
    Task {
        await MainActor.run {
            self.renderer = renderer
        }
    }
}

Now, the warning within func renderer is gone. However, the Issue Navigator shows the following error:

warning: data race detected: @MainActor function at …/ViewModel.swift:1023 was not called on the main thread

Line 1023 is the line where func renderer is defined.

My question is:
How can there be a data race, when the ViewModel is @MainActor, and the body of the function is executed on the MainActor? And how could I fix the bug?

Upvotes: 0

Views: 75

Answers (1)

Reinhard Männer
Reinhard Männer

Reputation: 15247

I still don't understand the problem, but I found a workaround.
I think this workaround is terrible, and there should be a better solution, but I did not find any up to now.

In the viewModel, I use now a set function for var renderer:

weak var renderer: (any SCNSceneRenderer)?
func setRenderer(_ renderer: (any SCNSceneRenderer)?) {
    self.renderer = renderer
}

Then, I created a new class SceneRendererDelegate with a local var renderer. This local var is set by the SCNSceneRendererDelegate function. When it was set, it starts a Task that sets var renderer in the viewModel:

class SceneRendererDelegate: NSObject, SCNSceneRendererDelegate, @unchecked Sendable {
    let viewModel: ViewModel
    var renderer: (any SCNSceneRenderer)? {
        didSet {
            Task { @MainActor in
                viewModel.renderer = renderer
            }
        }
    }
    
    init(viewModel: ViewModel) {
        self.viewModel = viewModel
    }
    
    func renderer(_ renderer: any SCNSceneRenderer, updateAtTime time: TimeInterval) {
        self.renderer = renderer
    }
    
}

Upvotes: 0

Related Questions