Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow custom kinds of bindables and stores #154

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open

Allow custom kinds of bindables and stores #154

wants to merge 11 commits into from

Conversation

rhelmot
Copy link
Collaborator

@rhelmot rhelmot commented May 15, 2022

No description provided.

L: Lens,
<L as Lens>::Target: Data,
L: Bindable,
<L as Bindable>::Output: Data,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if this bound is required or not now. Previously the output of a lens needed to implement data because the store which kept a copy of the output would need it to compare for change. Now that a Bindable creates its own store which decides whether data has changed this bound may not be needed anymore.

Comment on lines +63 to +65
for store_req in bindable.requests() {
// walk the ancestry chain child-to-parent...
for entity in new_ancestors.iter() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think maybe these loops should be swapped becuase when we iterate up the tree to find the relevant stores we can break out early if they are all found.


let model = model_data.downcast_ref::<L::Source>().unwrap();
fn requests(&self) -> Vec<StoreId>;
fn make_store(&self, source: ModelOrView) -> Option<Box<dyn StoreHandler>>;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could it be worth using an associated type for the returned store?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I considered this, but I'd like to keep the flexibility of a single bindable being able to bind to multiple kinds of stores!

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But how would a single bindable choose what store to use?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The thing I was envisioning is that there would be a MultiBindable which contains two child bindables. It will then dispatch its method calls to BOTH of the children, and take the first result that succeeds.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh okay, that's an interesting way to do it. I was thinking that this would be done with just the MultiBindable type which would have an associated store that holds on to two lenses and two pieces of state but one list of observers. Then the output of the Bindable would be a tuple of the output types for the lenses.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that approach would require that the two lenses both be on models which are built on the same entity, which is not desirable

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is why I was thinking that stores should be separated from models. Though your idea of having child bindables is probably a better one, I just need to get my head around what that would look like in terms of stores and whether we end up with redundant observers.

};

// ...so that we can register the observer with the appropriate store.
bindable.add_to_store(store.as_mut(), id);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this a method on bindable rather than on store? Would it not make more sense to have store.add_observer(id)?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the problem is that Store can need more than just an id in order to do registration - this is the main motivation behind bindable, that is, being able to store data which the store can use in order to more selectively dispatch a rebuild. As a result, there's no one concrete type the store would be able to take - we would need to do a dynamic dispatch to store.add_observer and then it would need to downcast the bindable in order to extract that information.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I see! But is the observer not always an entity?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The observer will always be an entity, but the observer's metadata (e.g. the list index being bound to) can be of arbitrary type.

model_data_store.lenses_dup.push(state);
}
}
fn requests(&self) -> Vec<StoreId> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit confused how this works because the make_store method seems to only be able to create a single store but this method suggests that a bindable can link to many stores.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make_store will be called once for each request!

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah okay, so the requests vector defines what stores the binable links to and then the make_store functions creates the store for each item?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct

fn element(&self) -> Option<&'static str> {
Some("binding")
#[derive(Clone, Debug)]
pub struct ConstantBindable<T> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this for?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is StaticLens but cleaner. It doesn't cover all the use cases that StaticLens does, but it will be more efficient since it will create no observers.

let top = ((data.child_y - data.parent_y) * data.scroll_y).round();
handle.left(Units::Pixels(-left)).top(Units::Pixels(-top));
})
.left(data.clone().map_shallow(|data| {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So does this mean that map_shallow replaces the need for .bind() when using it to set multiple properties?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants