[PEAK] Trellis on_commit and Performers
Sergey Schetinin
maluke at gmail.com
Sun Oct 5 12:54:15 EDT 2008
> That rather sounds like the correct way to do it, as long as there's no way
> for the re-entry events to set conflicting values. From the trellis' POV,
> the rule that writes to wx and thereby triggers a callback that sets some
> other cell value, might as well be directly updating that other value. And
> that should work out correctly for putting everything into a single
> transaction.
>
> What's not entirely clear to me is how the freeze/thaw works with that... I
> think you'd have to set up a non-trellis flag somewhere for whether freeze
> has been called, and then have a performer that does the thaw and clears the
> flag.
Trellis managers + AddOns make it rather trivial. In .do_write I call
manage_freeze(wxobj) which is implemented like this:
class WxFreezeManager(addons.AddOn):
def __init__(self, ob):
self.ob = ob
def __enter__(self):
self.ob.Freeze()
def __exit__(self, *args):
self.ob.Thaw()
def manage_freeze(wxobj):
if isinstance(wxobj, wx.Window):
trellis.manage(WxFreezeManager(wxobj.TopLevelParent))
>> The other order (when we first set it to new value, then it's
>> recalculated due to an event) could happen if we write the size to wx
>> in the same transaction.
>
> This is actually the bit I don't understand about what you're doing. If you
> set it to a new value *and* it's recalculated due to an event in the same
> transaction, it seems like something is broken in the design.
>
> That is, if the event is firing due to user activity, then how is it that
> you're also programmatically setting the size? Conversely, if the event is
> firing due to you setting the size, then why not just ignore the nested
> event? After all, either the nested event will set the same size, or if
> it's a different size, you could just read back the value you sent to wx,
> and then update the cell with that new value.
This makes perfect sense. I didn't consider that the change is written
immediately (I believe it's true at least on Windows, but I'll have to
check again), so I assumed that the effect of writing to wx might not
be immediate. Also, event callbacks firing inside user code make me
suspicious about possibility that user-activity callbacks might fire
there too. Hopefully that's not the case, but if it is it's possible
to differentiate between events we expected to fire because of program
activity and ignore them and schedule the handling of the rest of them
for later.
Also, I remember there are some oddities with some writes not going
through for invisible windows. I recall that title changes for windows
that weren't shown yet were ignored (I can't reproduce that now
though, maybe it was something different), so I had to batch the
.shown and .title writes into the same performer in certain order. It
seems that writing values in @maintain rules will make this more
manageable if similar issues will resurface.
> That is, in the property.fset, write to wx (with undo) and with the event
> effectively disabled. Then, read the new value, and write *that* value to
> the cell.
This seems to be the perfect solution.
> ISTM that you could even get that to work lazily, using
> receive().
But I didn't get this. .receive() for which cell? the only lazy cell
is the event one.
> One simple way to disable all re-entrant event callbacks would be to have
> them check whether a transaction is currently in effect, thereby ensuring
> that only non-programmatic events will ever update the trellis. I'm not
> sure right off the top of my head whether that's something you'd want
> globally, but I'm wondering where in wx any programmatic events are
> non-local; i.e., where the event tells you something you don't already know.
Hopefully there are none, but I've learned to expect the strangest
things from wx.
> But certainly a size event that only tells you that you just changed the
> size is not necessary, as trellis propagation from the cell in question is
> sufficient to notify the rest of the system. So, in the general case, it's
> probably best to:
>
> 1. Prevent re-entrant events from setting trellis values
> 2. Handle sets by writing to wx first, reading back the value, and then
> storing it to a @maintain cell
>
> You could also make this "lazy" by using @.connector/@.disconnector methods
> on the @maintain rule. These methods run in a second recalc phase, after a
> connection or disconnection has occurred, and can manipulate trellis values.
> Thus you could do something like:
>
> watching = trellis.attr(False)
>
> @trellis.maintain
> def _value(self):
> if self.watching:
> # code that depends on event
> # code that doesn't
>
> @_value.connector
> def _watch(self, sensor):
> self.watching = True
>
> @_value.discconnector
> def _unwatch(self, sensor, key):
> self.watching = False
>
> def set_value(self, value):
> # write to wx, with undo
> self._value = # value read from wx
>
> value = property(lambda self: self._value, set_value)
>
> Of course, this approach assumes that the watched event is ignored when it
> occurs inside a transaction (i.e., programmatically). Also, the "write to
> wx" needs to handle the freeze/thaw stuff already mentioned above.
I completely missed the [dis]connectors of @maintain rules, this
really starts to look like a solution. Thanks a lot, your help is
invaluable. I'll report on how this works out.
If you will have a moment, could you please point to what makes
discrete rules work correctly with performers? It's more curiosity
that anything else. It seems like those cells just do this in their
_finish (which runs in on_commit):
change_attr(self, '_value', self._reset)
changed(self)
And it doesn't seem terribly different from setting a regular Value in
on_commit (given its _set_by was reset by that time).
--
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