Page 1 of 2

[resolved] signal-slot concept of Qt => Cells

Posted: Tue Jan 19, 2010 2:26 pm
by hewih
Hello fellow Lispers!

i'm a newbie to Common Lisp and developing a small game engine where i want to implement the signal-slot concept of Qt. basically it means that you can specify "signals" and "slots" (not Lisp slots) for classes and connect them for some instances. everytime the signal is called the connected slots are also called. please could you give me some hints on how to do this. i would really appreciate it!

example:

Code: Select all

(defclass A ()
   ((foo :accessor foo))
   (:signals (setf foo)))

(defclass B ()
   ((bar :accessor bar))
   (:slots (bar)))

(connect a1 (setf foo) b1 (setf bar))
(connect a1 (setf foo) b2 (setf bar))

(setf (foo a1) 537) ; => b1.bar and b2.bar = 537

Re: signal-slot concept of Qt for Lisp classes

Posted: Tue Jan 19, 2010 3:15 pm
by ramarren
Unless I misunderstand the example (I don't know how Qt signals/slots work exactly), Cells (there is more documentation in cells-doc) gives you something similar, except perhaps better in some ways, since dependencies do not have to be established explicitly, just by accessing the values of other cells they are created automatically.

Re: signal-slot concept of Qt for Lisp classes

Posted: Tue Jan 19, 2010 4:00 pm
by drewc
Untested:

Code: Select all


(defclass a ()
  ((foo :accessor foo)))

(defclass b ()
  ((bar :accessor bar)))

(let ((a1 (make-instance 'a))
      (b1 (make-instance 'b))
      (b2 (make-instance 'b)))
 (defmethod (setf foo) :after (value (object (eql a1))) 
   (setf (bar b1) (setf (bar b2) value))))
You don't need a special mechanism for this in CL, a superset of the functionality is built in. However, doing it manually is error prone. Something like CELLS, FORMULATE, or COMPUTED-CLASS is probably a better bet depending on your actual needs.

Also note that, to do this properly, you have to come up with a way to make it work with garbage collection.... almost any of the simple ways to do this are going to hold references to your objects. Qt probably gets around this by not collecting garbage automatically.

Cheers,

drewc

Re: signal-slot concept of Qt for Lisp classes

Posted: Tue Jan 19, 2010 6:53 pm
by nuntius
Qt has a gc-like behavior; it essentially uses weak pointers to store the mappings, and deregisters a connection when either end is destroyed.

Re: signal-slot concept of Qt for Lisp classes

Posted: Wed Jan 20, 2010 4:29 am
by hewih
you guys are great, thank you! ^^

cells is exactly what i need and it looks pretty mature. they seem to call it "instance-specific formulas".

Code: Select all

(defmethod (setf foo) :after (value (object (eql a1)))
haven't thought of that, thanks. the values in my game engine may change 100 times per second, maybe i will fall back to this approach if cells turns out to be to slow.

Re: signal-slot concept of Qt for Lisp classes

Posted: Wed Jan 20, 2010 4:38 am
by dmitry_vk
The signal/slot concept in Qt appeared because of C++ limitations (lack of GC and lack of first-class functions).
In Common Lisp, it is more idiomatic to implement events (analogous to events in .Net and signals in Gtk+). Basically an event is a list (or array) of functions (or weak pointers to functions depending on your needs). To raise an event you call all functions in an event and you can add and remove event handlers to the event.
Here's an example:

Code: Select all

(defclass event ()
  ((handlers :initform (make-array 0 :adjustable t :fill-pointer t)
             :accessor event-handlers)))

(defun event-invoke (event &rest args)
  (map nil
       (lambda (handler) (apply handler args))
       (event-handlers event)))

(defun event-add-handler (event handler)
  (vector-push-extend handler (event-handlers event)))

(defun event-remove-handler (event handler)
  (setf (event-handlers event)
        (delete handler (event-handlers event))))

(defun subscribe (object event handler)
  (event-add-handler (slot-value object event) handler))

(defun unsubscribe (object event handler)
  (event-remove-handler (slot-value object event) handler))

(defun raise (object event &rest args)
  (apply #'event-invoke (slot-value object event) args))

;; Example

(defclass widget ()
  ((click-event :initform (make-instance 'event))))

(defun test ()
  (let ((w (make-instance 'widget)))
    (subscribe w 'click-event (lambda (x y) (format t "The widget was clicked at ~A,~A~%" x y)))
    (raise w 'click-event 10 15)))

(test)
==>The widget was clicked at 10,15

Re: signal-slot concept of Qt for Lisp classes

Posted: Wed Jan 20, 2010 4:45 am
by dmitry_vk
hewih wrote:you guys are great, thank you! ^^

cells is exactly what i need and it looks pretty mature. they seem to call it "instance-specific formulas".

Code: Select all

(defmethod (setf foo) :after (value (object (eql a1)))
haven't thought of that, thanks. the values in my game engine may change 100 times per second, maybe i will fall back to this approach if cells turns out to be to slow.
Adding methods to generic function is definitely not the way to do this. First is that it prevents your object from being garbage-collected. Second is that you can only define one method for one qualifier. Third is that defining a method is quite expensive operation.

Re: signal-slot concept of Qt for Lisp classes

Posted: Wed Jan 20, 2010 6:25 am
by hewih
events are not quite the thing i need. thanks for the example though.

i have a spacecraft sprite which depends on a position and is followed by 2 side-kicks (also sprites) which depend on an offset position relative to the spacecraft. the spacecraft position is changed by a mystical force, but every time it is changed, the new position should be propagated to the offsets, which in turn calculate the new absolute positions.

Code: Select all

GameObject Spacecraft
   Position
      Sprite0 -> draw Spacecraft image at Position.position
   Offset1 -> add predefined-offset (:x 10 :y 0) to Position.parent-position
      Sprite1 -> draw Sidekick image at Offset1.actual-position
   Offset2 -> add (:x -10 :y 0) to Position.parent-position
      Sprite2 -> draw Sidekick image at Offset2.actual-position
thus, every time (setf (position Position)) is called, the connected slots (Offset1,2.parent-position) should be updated, which update their actual-position to parent-position + predefined-offset, but only if they are changed.

i think this results in quite a nice architecture if it works how i imagined it. currently it should only support slot propagation, maybe i will need it for function-call propagation too.

Re: signal-slot concept of Qt for Lisp classes

Posted: Wed Jan 20, 2010 6:56 am
by dmitry_vk
Signal/slot is actually a limited implementation of events (signal handlers can only be specific methods of some objects).
In many frameworks, you just define events like 'position-changed' or 'foo-changed', 'bar-changed' that are raised when the value of 'position', 'foo' and 'bar' slot (the CLOS slot, not slot of signal/slot) is changed (those are 'signals' in signal/slot terminology). And you have handlers (the 'slots' in signal/slot terminology) that handle updating dependent objects.
Of course, this is much more error-prone and hard than using something like Cells. But much better that Qt's signal/slot.

Re: signal-slot concept of Qt for Lisp classes

Posted: Wed Jan 20, 2010 9:27 am
by hewih
does anybody know how to apply rules in Cells on class instances? i can only find examples for class definitions.