[cells-devel] Handling not-to-be'd kids and how to do an input slot for kids

Peter Hildebrandt peter.hildebrandt at gmail.com
Sat Apr 12 19:42:24 UTC 2008


Ken,

thanks a lot for all the insight.  First things first, using c?n for
the kids works like a charm.  setf on a c? cell still produces an
error, suggesting to initialize the cell with c-in.  Anyway, that's
settled now.

As to the not-to-be issue,  everything can be reproduced using
test-gtk.  I can share that in the cells cvs, just let me know whether
it would be ok to restructure the cvs to match the old cells-gtk
directory structure (I'd rather keep it the way it is in the old
cells-gtk, so that I can commit it in there one day).

I did some narrowing it down and going through the back trace. So let
us look at an example:

root
  node 1     ---- observer 1
    node 1.1 ---- observer 1.1
  node 2     ---- observer 2

The basic code for the observer is

(defmodel family-observer (family)
  ;; we'll use the "value" slot for the observed
  ((row :reader row :initarg :row)
  (:default-initargs
      :kids (kids-list?
                  (bwhen (val (^value))
                      (mapcar #'(lambda (src) (mk-observer self src))
(kids val))))))))
      :row
       (c? (when-bind* ((parent (upper self)) (pos (position self
(kids parent))))
         (let ((new-row (tree-row-create (row parent) (id parent))))
           (when (tree-row-valid new-row)
              (tree-row-set-path new-row (row parent) pos)
                 new-row)))))

Where mk-observer is a method specializing on both parameters, so that
we can have different kinds of observers on the same type of targets.

The row is some gui object to be kept in sync.

Now we remove node1:

(with-integrity (:change 'tv-del-node)
  (setf (kids (upper node1)) (remove node1 (kids (upper node1)))))

Then first node2 and observer2 die, ok.  Then node1 dies, and so does
observer1.

The interesting part:

not-to-be :before on observer 1.1 is called -- and at this point
observer 1.1 itself is already :eternal-rest, in other words,
not-to-be is called on a dead object.  Now not-to-be of the observer
wishes to do something, so it accesses a (ruled) slot of the passed
object:

(defmethod not-to-be :before ((self cells-tree-node))
   (tree-row-destroy (row self))

The call to the accessor (row self) with the dead self triggers a
bunch of cells calls:

Backtrace:
  0: (CELLS::ENSURE-VALUE-IS-CURRENT NIL #<unused argument> #<unused argument>)
  1: ((LABELS CELLS::CHECK-REVERSED) (NIL))
  2: (CELLS::ENSURE-VALUE-IS-CURRENT (NIL .
<vld>)=492/OPTIMIZED-AWAY/ROW/DEAD!NODE-TREE-NODE3440] #<unused
argument> #<unused argument>)
  3: ((LAMBDA (CELLS::OPCODE CELLS::DEFER-INFO)) #<unused argument>
#<unused argument>)
  4: (CELLS::CALL-WITH-INTEGRITY NIL NIL #<CLOSURE (LAMBDA
(CELLS::OPCODE CELLS::DEFER-INFO)) {BBBD3DD}>)
  5: (CELLS::CELL-READ (NIL .
<vld>)=492/OPTIMIZED-AWAY/ROW/DEAD!NODE-TREE-NODE3440])
  6: (CELLS::MD-SLOT-VALUE DEAD!NODE-TREE-NODE3440 CELLS-GTK::ROW)

The last call is in

(defun ensure-value-is-current (c debug-id ensurer)
   (declare (ignorable debug-id ensurer))
  (count-it :ensure-value-is-current)
   (when (and (not (symbolp (c-model c)))(eq :eternal-rest (md-state
(c-model c))))
    (break "model ~a of cell ~a is dead" (c-model c) c))

.... in particular the form:

(c-model c)

which breaks with:

 The value NIL is not of type CELL.
   [Condition of type TYPE-ERROR]

To sum up, I believe the problem is that at a change of the kids list
- First the kids are declared dead
- Then not-to-be is called recursively
and thus not-to-be is passed a dead self.

However, I wish to do some cleanup work when kids are kicked out, and
for this I need to access a few slots.

What I'd like to have is
- not-to-be being called before the object is declared dead
or
- another method (last-will?) to be executed right before the kids die
or
- an interims state (:zombie?) in which cell slots are still
accessible with their last cached value

Or is the solution to have an observer on the kids slot instead of a
not-to-be-method, which does the cleanup work for the kids?

>  I was thinking while doing the dishes. I can image the observer class being
> interesting in its own right, and slots over there ending up dependent on
 > the same original kids-list in some way, as well of course as the value of
> the observer. Propagation would then try to update this slot and when it got
> to the value of the observer find a dead instance. Any rule that got to the
 > value of the observer by accessing the list of observers (say something
> iterating over them) would not encounter such an observer/value, but rules
> lower down that get at the value directly will.

This is an interesting idea, but I doubt that this is my problem here.
 It really seems to be the relation between ruled kids slots,
declaring cells dead, and not-to-be.

 >  Now normally this is not a problem because such lower down rules would tend
> not to depend also on the original list, and indeed the reason I have left
> this unaddressed is that in most cases I have seen a simple way to rewrite
 > my rules that was even better and which did not end up with these widespread
> dependencies (if you have followed me so far on that, and if I hasten to add
> all this guesswork is on the money). I like to hold out for Real Problems
 > before whacking away at the code, I think that is a slippery slope.

That is surely true, as this case proves.  I feel what we're looking
for here is something like family-finalizers which are specified to be
called everytime a kid dies.

>  btw, all that stuff in their that worries about dead instances is
> preemptive safeguard stuff -- I think if you disable that most things will
 > just work. The rules that are failing now will run harmlessly and in a few
> cycles everything gets cleaned up anyway. Cells ran for /years/ with this
> happening to no ill effect (until RoboCup, of all things).

Yep, which is why it broke after introducing cells3 :-)

OTOH, I see why it is good to have these safe guards.  I hacked a
solution today to the fm-other tree searches which were all over
cells-gtk -- now we have with-widget and with-widget-value which do
the right thing without kicking off tree searches (I introduced an
automatically maintained hashtable of active instances hashing by
md-name, like I did in cells-ode).

>  If you want to send me your whole project I will look to see how I would
> rewrite the rules if that is even possible, and if not take a look at
> solving this formally.

As I said, I like to try things out in test-gtk first, so that I can
isolate the error (and create a nice demo on the way).  I will work
through cvs and commit it tomorrow, I hope.  I just don't want to
force you to have to deal with my whole project -- and all the other
issues it has.

>  fyi, in the past I have done silly things like having Cells just return nil
 > on slot-value access to dead cells, but we may want to find something more
> elegant. :)

I have such code in my project, too:

(defun deadp (cell)
  (eql (slot-value cell 'cells::.md-state) :eternal-rest))

However, since I need the slot-value in my case, this does not help ;-)

Thanks again,
Peter



More information about the cells-devel mailing list