2 Signals
dxtr edited this page 2026-10-20 10:09:08 +02:00

Signals

A signal is how an object tells whoever listens that something happened, as Godot's signals are. Nodes, resources and other objects all have them: each is an emitter of pyssel/resource, and pyssel/scene exports the same functions, so sc:connect and res:connect are one. Here a game's rules, kept apart from what draws them, as a tower-defence game keeps its simulation, tell a label what to show:

(res:define-object rules ()
  ((gold :initarg :gold :initform 100 :accessor rules-gold))
  (:signals gold-changed))

(defun earn (rules amount)
  (incf (rules-gold rules) amount)
  (res:emit-signal rules 'gold-changed (rules-gold rules)))

(sc:define-node level (s2d:node-2d)
  ((rules :initform (make-instance 'rules) :reader level-rules)))

(defun show-gold (label gold)
  (setf (ui:label-text label) (format nil "Gold: ~D" gold)))

(defmethod sc:ready ((level level))
  (sc:connect (level-rules level) 'gold-changed (sc:find-node level "gold") 'show-gold))

With a label named gold under the level, (earn (level-rules level) 20) has it show "Gold: 120".

  • Declaring: a class names the signals its objects emit with (:signals name...), an option of define-node, define-resource and define-object, besides the signals of its superclasses. class-signals lists them, its own first. define-object defines a class of objects with signals that are neither nodes nor resources, as Godot's RefCounted objects with signals of their own; it takes slots and options as define-resource does. A value class has no signals.
  • Connecting: (connect source signal target function) links signal of source to function. With a target, an emitter, the function is called with the target first and then the signal's arguments, as show-gold above; given as a symbol, redefining it takes effect at once. With nil as the target, a function object or a symbol is called with the arguments alone, which suits code that connects at run time. :persist t has a saved scene keep a node's connection to a node in the scene, as the editor makes them; only nodes' connections persist. connected-p tells whether a connection exists, disconnect undoes one, signal-connections lists an emitter's, and disconnect-all undoes all of an emitter's and those to it. has-connections-p tells whether anything is connected to a signal.
  • Emitting: (emit-signal source signal argument...) calls the connected functions in the order they were connected, allocating nothing. A signal the class does not have is an error, for connect too, and so is connecting the same function and target twice. A signal whose argument costs something to make, as a double-float, which Lisp makes anew to pass, is emitted only while has-connections-p says someone hears it, as a range's value-changed is.
  • Nodes add rules of their own: a freed node is refused; a node's connections are made and undone on its tree's thread; a connection to a node that does not behave, such as one of a game's classes while its tree is edited, or a stopped one, is skipped; an error in a function connected to a node, or to a node's signal with no target, stops that node, with a node-stopped warning, as an error in a callback does, and the other connections still run; and freeing a node undoes its connections and those to it, so a plain object connected to a level's nodes lets them go with the level.
  • Resources and other objects: an error in a function connected to one, with no node to stop, goes on to whoever emitted, as any error does. Their connections are made and run on one thread at a time. disconnect-all lets go of an object whose part is over while the nodes it is connected to live on.
  • Systems of your own with rules of their own, as pyssel/scene adds its nodes', give methods of emitter-freed-p, check-emitter-thread, receives-signals-p, handle-connection-error and connections-persist-p.