Closed benknoble closed 2 years ago
This is a limitation of dyn-view
, and one of the reasons it's not documented yet/why I'm not sure it's a good idea. The problem is that while it can discover what dependencies the generated views have, it has no way to register those deps with the renderer. Probably the easiest fix would be to make dyn-view
add its own observers to dependencies reported by its children, but I'll have to sit down and think that through in case it would cause other problems. In the mean time, using obs-combine
is the right approach here, though you also have to make sure your monster group view uses the ability decks value from the combined observable and not the main one (if you don't, things might work out most of the time, but you're going to be depending on a race between the observables).
This is a limitation of
dyn-view
, and one of the reasons it's not documented yet/why I'm not sure it's a good idea. The problem is that while it can discover what dependencies the generated views have, it has no way to register those deps with the renderer. Probably the easiest fix would be to makedyn-view
add its own observers to dependencies reported by its children, but I'll have to sit down and think that through in case it would cause other problems.
That makes sense, and is kind of what I figured. It's unfortunate that cond-view
really won't work here without producing errors (though IIRC they don't prevent the application from running).
In the mean time, using
obs-combine
is the right approach here, though you also have to make sure your monster group view uses the ability decks value from the combined observable and not the main one (if you don't, things might work out most of the time, but you're going to be depending on a race between the observables).
That is unexpected, in some way, but also makes sense. Unfortunately the use of the @ability-decks
in make-monster-group-view
needs to be an observable—is your suggestion just to @
-wrap the correct value, then?
At any rate, this might give me the motivation I need to drop some of the cons
objects and put more things in struct
s (there's already a large number floating around, but no harm in adding more).
I just pushed a set of changes to track hidden deps in dyn-view
s and to make it so that if-view
(and derivatives like cond-view
and case-view
) short circuit. This should make it easier to create disjoint views like you're trying to do, though there are some caveats still:
This is wonderful! I haven't updated to try this out yet, but I plan to soon.
I completely follow the coerce
problem in your link, but I'm still trying to digest what's happening in https://github.com/Bogdanp/racket-gui-easy/commit/87580f802bd44f8651178b1535f72d41f43c44a3 where you say that referring to a static view is a problem: does this mean that static views should be considered something of an anti-pattern (i.e., all views should be returned by functions)?
Yes, I think they should generally be avoided. Ultimately, views are objects with internal mutable state so re-using the same view can be problematic.
In that case specifically, if-view
calls destroy
on its children when they get replaced, so the static view would get torn down after its first use and on subsequent uses it would appear empty. This would've been a problem before this set of commits too, but commit https://github.com/Bogdanp/racket-gui-easy/commit/bebc226c666f834c1a87ed047c89b6ab2bc962b2 makes it more apparent since it removes children from containers to avoid retaining them unnecessarily.
I want to expand on this some more, in case I forget some of these details by the next time I get to work on this stuff.
Initially, when I came up with view<%>
, I imagined view implementations would end up being stateless. That's why create
returns a native widget instance and why update
and destroy
get passed those instances. In those cases, you can pass the same instance of a view<%>
around multiple times and it won't cause any issues because the instances don't have any internal state. By the time I started implementing container views, I stopped worrying about retaining that property, because it made the implementation of those views much simpler. However, that breaks the promise that view<%>
s are purely representations of GUIs, which I'm not too happy about, but, in practice, it doesn't seem to cause many issues (of all the GUIs I've written with gui-easy so far, that example was the only one where I had reused a view like that).
I think for now I'm going to leave things as they are so we can see how much this trips people up. If it turns out to cause a lot of pain and confusion, what we can do is change those views that are stateful to parameterize their state per native widget instance. To give a concrete example, if-view
's then-view
and else-view
would change from being (or/c #f (is-a/c view<%>))
to (weak-hasheq/c any/c (or/c #f view<%>))
. Alternatively, we could wrap native widgets in a mixin that lets us store contextual data directly on those instances, simplifying some of the state & memory management.
Ok, that seems to make sense to me at the moment.
One last question: I saw a current-renderer
parameter at a glance; is that considered public/stable? I don't recall seeing an update to the docs mentioning it, but it is certainly something I'm interested in. Though, using (render (dialog …))
without a parent
argument hasn't caused any trouble for me so far…
No, it won't be public. I added it for internal use only and mostly as a way of getting around changing the view<%>
interface s.t. create
, update
and destroy
take an additional renderer param. I probably will eventually change the interface and get rid of it.
You can bind the top level window's renderer (or store it in your own param) and use it to make modal dialogs already (see "examples/modal.rkt"). What you can't currently do is bind the renderer for a dialog because rendering a dialog blocks until the dialog is closed. I haven't yet thought of a nice way to get around that.
Got it, thanks!
Here's an example of making your own parameter, for the record. I couldn't think of a thread-safe/re-entrant way to do it without a parameter containing a thunk:
#lang racket
(require racket/gui/easy)
(define cr (make-parameter (thunk #f)))
(define root
(parameterize ([cr (thunk root)])
(render
(window
(button
"Click me"
(lambda ()
(render
(dialog
(text "hello"))
((cr)))))))))
The view construction could then be placed in its own function(s) as long as it had access to the parameter. Thunk-ing is necessary because without it the value root
is evaluated in the first form of parameterize
before root
is bound to the value that parameterize
would return (i.e., it is undefined)!
Thank you for this; unless you feel strongly otherwise, I'm going to close this issue because I was successfully able to update to 0.5 and use a cond-view where I had this dyn-view before. 🎉 ❤️
I had a function that looked like this:
[Ignore the
(cons _
patterns; I am still deciding how best to restructure the data for that part.]The
make-creature-view
function is used in alist-view
over an observable@creatures
which holds items that are eitherplayer?
ormonster-group?
(again, ignoring the initialcons
bits).The
dyn-view
lets me do the job of anif-view
/cond-view
but without constructing the sub-views of all conditional branches when the data doesn't match what is needed (e.g., with acond-view
here, I can't avoid that themonster-group-view
gets aplayer?
or vice-versa, because the sub-views are constructed greedily). At least, I think this is why I am usingdyn-view
; I can't remember.But here's where things get interesting: the view returned by
make-player-view
has no dependencies other than@e
, which (conveniently) thedyn-view
also monitors and reacts to. On the other hand,make-monster-group-view
creates a view that depends on an observable not mentioned here. I update it in response to other events and was expecting the GUI to update with new information, but it doesn't (presumably becausedyn-view
doesn't know about it, so can't forward updates, so nothing is redrawn).There's a very silly hack to fix this (notice the
obs-combine
and the extracons
patterns):This works (!) but isn't scalable or maintainable: if more dependencies are added to
make-monster-group-view
, they need to balloon into theobs-combine
andmatch
, too. And if one is forgotten, there's a sort of "spooky action at a distance" bug like the lack of updates I described above.I actually first tried a slightly better version that used
(obs-combine (lambda (a e) e) @ability-decks @e)
, but that didn't work. It only improved thematch
, too; my points above still apply.Have you encountered something like this before? Do you have a suggestion to alleviate this? It's like I want the static parts of
if-view
but the dynamic parts ofdyn-view
(again, because of the issue I have with disjoint data).