migueldeicaza / SwiftGodot

New Godot bindings for Swift
https://migueldeicaza.github.io/SwiftGodotDocs/tutorials/swiftgodot-tutorials/
MIT License
1.19k stars 77 forks source link

Treat Signals Declared In Swift Like Signals Declared In Godot #586

Open samdeane opened 1 month ago

samdeane commented 1 month ago

Imported Signals

When we import a signal from Godot, we generate a helper class and a computed property, which allows us to connect to it in a very Swift-like manner:

func setupBot (robot: Node) {
    robot.ready.connect {
        print ("The robot's node is ready")
    }
}

If the signal has parameters, these are surfaced correctly in the closure:

obj.signalWithParams.connect { param1, param2 in 
  ...
}

Signals Defined In Swift

However, when we define a signal in Swift using the #signal macro, we create a static property on the Swift class.

This requires a different, less intuitive, less idiomatic syntax for connected to or emitting signals:

@Godot public class SBActor: Object {
  #signal("weapons_changed")

  func changeWeapon(_ weapon: SBWeapon) {
    emit(signal: SBActor.weaponsChanged)
  }
}

@Godot public class SBHud: Object {
  func connectToActor(_ actor: SBActor) {
    actor.connect(signal: SBActor.weaponsChanged, to: self, method: "updateWeapons")
  }

  @Callable func updateWeapons() {
    // update the HUD UI here...
  }
}

It should be possible to re-write the signal support so that Swift-side signals are implemented using the same helper classes as imported ones. This should result in a more uniform interface, and less code.

This could be a breaking change (if we alter the #signal macro), but I think it could probably be done instead as an additive change, using a different macro (possibly @Signal). Any implementation would probably be built on top of #42.

samdeane commented 1 month ago

A solution should result in syntax more like this:


@Godot public class SBActor: Object {
  @Signal var weaponsChanged   // signal will be exposed to Godot as "weapons_changed"

  func changeWeapon(_ weapon: SBWeapon) {
    weaponsChanged.emit()
  }
}

@Godot public class SBHud: Object {
  func connectToActor(_ actor: SBActor) {
    actor.weaponsChanged.connect {
      // update the HUD UI here...
  }
}
samdeane commented 1 month ago

It should also support signal parameters properly:

@Godot public class SBActor: Object {
  @Signal(args: [Int.self]) var healthChanged   // signal will be exposed to Godot as "health_changed, <new-health>"

  func hit(_ amount: Int) {
    health -= amount
    healthChanged.emit(health)
  }
}

@Godot public class SBHud: Object {
  func connectToActor(_ actor: SBActor) {
    actor.healthChanged.connect { health in
      // update the HUD UI here...
  }
}