[PEAK] Trellis on_commit and Performers

Sergey Schetinin maluke at gmail.com
Sun Oct 5 00:46:22 EDT 2008

> Hm.  Well, if you didn't need the EVT_SIZE subscription to be lazy, I'd have
> a solution.  Just use a @maintain rule for ``value``, and have your
> performer check the cell object's ``was_set`` attribute to know if it was
> written to (as opposed to computed).  You could then drop all the
> pending_write and listening attribute stuff.
> You might try that simple approach first, and see if you can get away with
> it.  The downside is that checking ``was_set`` creates a dependency, and
> thus the ``value`` rule could not be lazy.  So, it would always be
> subscribed to EVT_SIZE, but everything else would be simpler.
> When I was originally designing Sensor and Effector, I was thinking that
> Effector needed some kind of "on write" call, but ended up dropping it
> because it seemed that you could just use ``was_set``.  Now I see that using
> ``was_set`` keeps the Effector from being able to be lazy.
> What's really needed here is a lazy Effector with a ``write()`` callback.
>  However, since such a thing does not exist, I think the simplest thing is
> to wrap the value cell in a property whose write method schedules the
> wx-level update.  Something like:

This is exactly the impression I got from Effector implementation -- I
really wished .was_set was a separate cell, or effector had a .write.

>    @trellis.compute
>    def _value(self)
>        evt = getattr(self.ob, self.attr.event_name)
>        if evt is not None:
>            if self.attr.decode_event is not None:
>                return self.attr.decode_event(evt)
>        return self._get()
>    def get_value(self):
>        return self._value
>    def set_value(self, value):
>        if not trellis.ctrl.active:
>            return trellis.atomically(self.set_value, value)
>        self.__cells__['_value'].receive(value)  # hack!
>        trellis.on_commit(wxchanges.schedule, self.do_write, value)
>    value = property(get_value, set_value)

First thing: self.__cells__['_value'] expects that cell to exist
already, if we write it before reading, it doesn't, as it's optional.

        if '_size' in self.__cells__:

This wouldn't work either, because the dependency (for ex. layout)
could be created after setting the size, but before wx changes are
applied (because wx.CallAfter only start running in wx.App.MainLoop).

I just create LazyCell manually, without using @compute, so it's OK,
but generally, a helper function to get optional cells could be

> I think this does what you want.  But it's a bit of a hack, though, because
> the assumption underlying receive() is that it is being called from the
> external side, not the trellis side of things.
> This means that if you write ``value`` during the same transaction as
> anything that causes ``_value`` to be recalculated, then the recalculated
> value will take precedence over the written value...  I think.  IOW, any
> rules that depend on the value will get re-run, and the explicitly set value
> will be ignored in this transaction (but set in wx post-transaction).

This might be true for that order, but if the _value rule is
calculated first, then set, then it results in InputConflict.
For example, first, we create the wrapper for some panel and set
layout rules for it, so it reads the actual wx size. Then we add it to
another panel which also has a layout rule that changes the size of
this first panel. That triggers the .receive which in turn calls
Value.set_value which does the following check:

        if value!=self._value:
            if self._set_by is not ctrl.current_listener:
                # already set by someone else
                raise InputConflict(self._value, value, self._set_by,
ctrl.current_listener) # XXX

It fails, because _set_by is the size rule itself and current_listener
is the layout rule for the parent panel.

> However, if there is no outside event and you set a value, then that value
> will be seen by other rules in this transaction, and then copied to the
> outside world.
> I think that's what your original code was trying to do, though, so maybe
> this is exactly what you want.  However, if setting the value in wx doesn't
> cause an event to come back, you need to make sure that do_write() sets
> .value, too, in order to update things.

I'm not sure I get this. do_write writes the value to wx, if the event
is not fired, what else should it do and why? .value is property that
reads _value which is a compute which gets set with .receive before
the do_write is scheduled. Why would event not coming back matter?

> This whole scenario is making my head spin, though, so I'm starting to
> wonder if anything I'm saying makes sense at all.  :)

That's exactly my feelings for past few days. Trellis seemingly has
the potential to tame most of wx nonsense, but when it doesn't work it
hurts even more than without it.

Also, not really related, but another thing that was causing me
headache and wasn't obvious until I found out the reason was
interaction of List and @modifier. I had a @maintain rule that updated
a Set with a List, like this:

def update_set(self):

But Set.update is a modifier, and the rule itself doesn't touch the
list contents, so no dependency is created. This was really tricky to
debug, because there are more complex bits than that and it really
doesn't look like this code could have a bug.
I understand why this happened, but I think it's non-trivial and I
wonder what could be done to avoid such things, because it's really
hard to see that coming without knowing it from experience.

Also, regarding the the initial on_commit and Performers question. I
wonder what's different about resetting_to= rules which makes
Performers run twice, they use on_commit as well, and if not for them
I'd never get an idea of starting a new transaction in on_commit

Best Regards,
Sergey Schetinin

http://s3bk.com/ -- S3 Backup
http://word-to-html.com/ -- Word to HTML Converter

More information about the PEAK mailing list