GLTFSceneKit icon indicating copy to clipboard operation
GLTFSceneKit copied to clipboard

Async loading of resources (images, buffers)

Open sgara opened this issue 7 years ago • 5 comments

Hi there! Thanks for the great work here! I've been doing some test with the loader and I ended up loading distant files with http protocol, and it actually works. Using Data(contentsOf:) is synchronous and it doesn't give any room for customization (headers, authentication, ...) at least none that I know of. I was wondering if we could make some changes to make resource loading async. This would allow on-demand fetching of glTF files over http, and we could even create an interface to give customization for requests.

sgara avatar Dec 04 '17 22:12 sgara

I agree with you. It should be nice to have async functions to load resources for this web friendly format.

But I also want to keep the synchronous loading because the original API (SCNSceneSource) is also synchronous and async functions make a program kind of unpredictable. So I think it's better to add options or APIs to choose sync/async loading.

Do you have any ideas for the interface?

magicien avatar Dec 05 '17 00:12 magicien

Got your point, it totally make sense. In this case we should start with splitting loading functions into 2: load{Resource}(index:), that will be used as cache (actual behaviour) and load{Resource}(from:) that would actually convert from glTF data struct to SceneKit components. Maybe those 2 functions could end up into 2 distinct classes in the end. Btw, is there any async API in SceneKit we could bind to?

The http downloader interface can be quite simple, just a fetchFile with basic callback (data, error) or (filepath, error) . All customization would be in client implementation. We could also give a default implementation, using URLSessionDownloadTask or even more basic, keeping Data(contentsOf:)

What do you think?

sgara avatar Dec 05 '17 00:12 sgara

I think that making an async loader function for each resource is the last step.

For the first step, I'd like to try parallel loading of Buffers (and wait until all the buffers are loaded). And for the API, just wrap GLTFSceneSource.scene(options:) with DispatchQueue.

SCNSceneSource has scene(options:statusHandler:) function which calls the given handler periodically. This is not asynchronous but it can improve the user experience by showing the loading status. I think it's worth implementing this function.

I agree with you for the http downloader. The interface could be delegate. SCNScene has write(to:options:delegate:progressHandler:) function. The delegate (SCNSceneExportDelegate) will receive data and a scn file url and an image url. This is only for exporting, but I think we can use the similar interface.

I wish Swift had Promise and async/await functions...

magicien avatar Dec 05 '17 23:12 magicien

How to load 3d models from the external url? do { let sceneSource = try GLTFSceneSource(url: URL(string: "http://35.154.127.183/responsive")!) scene = try sceneSource.scene() } catch { print("(error.localizedDescription)") return } While I'am trying to do like above it is throwing error optional while unwrapping

tanalavijay avatar Jun 15 '20 05:06 tanalavijay

@magicien Any update on this? We're experiencing the scene never loading (or showing?) when wrapping Data(contentsOf:) in a DispatchQueue. And if we don't wrap it in DispatchQueue, it blocks the entire thread and Xcode gives warnings:

Screenshot 2023-11-11 at 16 22 18

func loadAnimationScene(urlString: String) {
        DispatchQueue.global(qos: .background).async { [self] in
            do {
                var scene: SCNScene
                let sceneSource = GLTFSceneSource(url: URL(string: urlString)!)
                scene = try sceneSource.scene()
                
                DispatchQueue.main.async { [self] in
                    sceneView.allowsCameraControl = true
                    sceneView.scene = scene
                    sceneView.autoenablesDefaultLighting = true
                    
                    sceneView.backgroundColor = Colors.backgroundColor
                }
            } catch {
                print(error.localizedDescription)
            }
        }
    }

Now that swift supports concurrency, we could maybe wrap .scene(options: ) in:

Task {
  scene = try await sceneSource.scene()
}

JohanAlbrectsen avatar Nov 11 '23 15:11 JohanAlbrectsen