Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot intercept callbacks during loading map.. #3

Closed
aornano opened this issue Dec 16, 2016 · 10 comments
Closed

Cannot intercept callbacks during loading map.. #3

aornano opened this issue Dec 16, 2016 · 10 comments
Assignees

Comments

@aornano
Copy link

aornano commented Dec 16, 2016

How can I intercept the end of the map loading without completions or delegate if my code follow your home instructions? How can I use callbacks?

// Load TileMap
guard let tilemap = SKTilemap.load(fromFile: currentTMXfilename) else {
            fatalError("Failed to load tilemap.")
}

after this line if I try to get a SKLayer, is always nil (I suppose because the map don't finish to load all components..) ..
Thank you in advance.

@mfessenden
Copy link
Owner

I've implemented a delegate in the next release to handle completions, I'll try and get the delegate working and pushed this weekend.

@aornano
Copy link
Author

aornano commented Dec 16, 2016

Thank you sir, very appreciated..

@aornano
Copy link
Author

aornano commented Dec 16, 2016

I was thinking to something about (just if this can help..):

...
protocol SKTiledmapDelegate: class {
    func didFinishLoading(success:Bool)
}

    
/**
 The `SKTilemap` class represents a container which manages layers, tiles (sprites), vector objects & images.
 
 - size:         tile map size in tiles.
 - tileSize:     tile map tile size in pixels.
 - sizeInPoints: tile map size in points.
 
 Tile data is stored in `SKTileset` tile sets.
 */
open class SKTilemap: SKNode, SKTiledObject {
    
    open var filename: String!                                    // tilemap filename
    weak var tilemapDelegate:SKTiledmapDelegate?
...

and below:

open func didFinishParsing(timeStarted: Date, verbose: Bool=true, completion: (() -> ())? = nil) {
...
        // This should be replicated also to all the parts where success = false or where it fails
        tilemapDelegate?.didFinishLoading(success: true)
        
        // run completion handler
        if completion != nil { completion!() }
 }

So, in my project I can intercept this callback:

class GameScene: SKScene, SKTiledmapDelegate {
     var tilemap: SKTilemap!
     func didMove(to view: SKView) {
          ...
          guard let tilemap = SKTilemap.load(fromFile: "myfile.tmx") else {
            fatalError("Failed to load tilemap.")
          }
          self.tilemap.tilemapDelegate = self
     }
     
     func didFinishLoading(success: Bool) {
        print("- tilemap has finished to load..")
        let walls = self.tilemap.getLayer(named: "walls") as! SKTileLayer
        print(walls.getTilesWithProperty("wall", "" as AnyObject))
        print(walls)
    }
}

@mfessenden
Copy link
Owner

mfessenden commented Dec 16, 2016

That's pretty cool! I just pushed up a revision with a similar setup:

public protocol SKTilemapDelegate: class {
    func didRenderMap(_ tilemap: SKTilemap, completion: (() -> ())? )
}

The new callback is called after parsing is complete and all of the layers have finished rendering asynchronously. If you're using the included SKTiledSceneDelegate protocol you'll have access to the delegate. It'll allow access to the rendered SKTilemap object as well as an optional completion handler.

You can use it like this (here the scene is the delegate):

if let tilemap = SKTilemap.load(fromFile: filename, delegate: self, completion: nil) {
    // do stuff with the tilemap here
}

Let me know if this works for you, or if you have any other requests.

@aornano
Copy link
Author

aornano commented Dec 17, 2016

Hello sir, I'm here. First of all I like your way to write code, you think very well in Swift 3.

As you see my protocol contain class word and my var weak to avoid to create strong references

I think there is a problem with completion , the callback is fired so the delegate work, the tilemap var is full but the completion is equal to nil

I've investigate to your sources and completion result nil always inside source. Why?

Seems the problem start when you allow the user to do:

guard let tilemap = SKTilemap.load(fromFile: "myfile.tmx") else {
            fatalError("Failed to load tilemap.")
          }

Pay attention, in my honest opinion that's great! but in your sources you say that if user don't explicit
a completion, the completion is equal to nil (your code) so when you propagate completion through your code, you propagate essentially nil:

open class func load(fromFile filename: String,
                         delegate: SKTilemapDelegate? = nil,
                         completion: (() -> ())? = nil) -> SKTilemap? {
        if let tilemap = SKTilemapParser().load(fromFile: filename, delegate: delegate, completion: completion) {
            return tilemap
        }
        return nil
    }

I've think about this solution below where, if user put a completion I use it, but if the user don't care about completion I create it to use and propagate it to the rest of the code so callbacks have completion not equal to nil since the first launch, and not less important, without touching the other code...:

open class func load(fromFile filename: String,
                         delegate: SKTilemapDelegate? = nil,
                         completion: (() -> ())? = nil) -> SKTilemap? {
        var mutableCompletion = completion
        if let _ = mutableCompletion {
        } else {
            mutableCompletion = {() -> () in}
        }
        if let tilemap = SKTilemapParser().load(fromFile: filename, delegate: delegate, completion: mutableCompletion) {
            return tilemap
        }
        return nil
    }

That code to better understand what happened but I think you could correct it in a short form as:

open class func load(fromFile filename: String,
                         delegate: SKTilemapDelegate? = nil,
                         completion: (() -> ())? = {() -> () in}) -> SKTilemap? {
        if let tilemap = SKTilemapParser().load(fromFile: filename, delegate: delegate, completion: completion) {
            return tilemap
        }
        return nil
    }

BUT there is another problem if an user decide to use completion since the first load as:

self.tilemap = SKTilemap.load(fromFile: "myfile.tmx", completion:{
 ...
}

because even after my correction this completion is fired before the async queue

Maybe you have a better idea or we can use this one. You choose. Ah, great project, again congratulations. I use it for my new game.

@mfessenden mfessenden self-assigned this Dec 20, 2016
@mfessenden
Copy link
Owner

mfessenden commented Dec 20, 2016

Hi Alessandro,

Thanks for the sample code. It's kind of a busy week here in the US, but I am taking a look at this.

I've investigate to your sources and completion result nil always inside source. Why?

The SKTilemapDelegate API is still being specced out, so the documentation hasn't been updated to reflect the newest syntax. I gave the completion argument a default of nil just as a convenience so that the user doesn't need to pass it, but I'm happy to change it to something that works better.

When I put in a simple test closure, it gets called at the end of the didRenderMap callback, is that not the case for you?

Here's a modified & simplified version of the SKTiledScene.didMove(to view:) method:

override open func didMove(to view: SKView) {
    let myClosure = {
        if let tilemap = self.tilemap {
            print("-> tile map loaded with \(tilemap.layerCount) layers")
        }
    }

    if let tilemap = loadTilemap(fromFile: self.tmxFilename, completion: myClosure ) {
        // do stuff here
    }
}

When I run this, the last thing displayed in the console is:

-> tile map loaded with 9 layers

As I said, I'm happy to take it out or change it to something that works.

@aornano
Copy link
Author

aornano commented Dec 21, 2016

I don't use SKTiledScene. Say that, as you can understand, I want to start with a simple clean SKScene. In this scene I've simply loaded a working map (when I say working it is because Im sure it is a compatible TMX map also with your project after I've made many checks and controls). As explained in my last part of the last answer ( take a look to how I call completion instead of your last code, the return map is in concurrency with completion..) , as if you declare a completion during the load map, the completion in your sources is not propagated to the other subroutines that required completion, in fact it exit with the map before it loaded the layers. Take present I've solved and use your project with the first proposal about delegate so these discussions are maded only to improve, help, find some wrong approaches etc..so you can test what I say simply by start a blank Spritekit project (helloworld template is good for these kind of tests) and launch the load map with a completion without SKTiledScene using my latest code syntax in my last answer . Take also present you know better than me your sources so I could make some mistakes of evalutation or syntax..

@mfessenden
Copy link
Owner

Ah yes, you are absolutely correct. Thanks for bringing this to my attention.

The problem is that in the current code the completion is unwrapped and executed in the in my sample scene, but that won't happen if you are building your own SKScene. It should really be executed in the parser when the dispatch queue is completed.

Do you even foresee a need to have a completion closure in the callback? I've been using it primarily for debugging purposes, but the delegate would be cleaner if I simply removed the completion and let users do all of the post-processing in their delegate.

@aornano
Copy link
Author

aornano commented Dec 21, 2016

I agree with you. You could remove the completion of the load function and use only the delegate to close every reasonable doubt. I do not know but I think that to be able to leave this completion you should rebuild the loading flow for using to a raw SKScene: it might not be worth it.

I'd say something more useful to your project might be instead to include a Swift sprite activity indicator kit that says "Loading .." simple and a little customizable, because in a game that loads the map layers, controls (joystick, buttons ...) go off until the map is not fully charged then it could be just a value added to your project. But this is only my personal opinion, you may look to a large scale.

@aornano
Copy link
Author

aornano commented Dec 27, 2016

Oh I forgot, for me this issue is closed because you've solved adding delegate for callbacks so for me that's enough, thank you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants