Skip to content

Commit 3332b17

Browse files
committed
Add 'im' feature for supporting the im crate collections
This is an experiment; the idea is to encourage the use of these types over std::collections. There's currently an issue or two with im::Vector, so this includes a poor implementation of `Data` there, for the time being.
1 parent 7fab048 commit 3332b17

File tree

7 files changed

+166
-5
lines changed

7 files changed

+166
-5
lines changed

.github/workflows/ci.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ jobs:
6666
uses: actions-rs/cargo@v1
6767
with:
6868
command: clippy
69-
args: --manifest-path=druid/Cargo.toml --all-targets --features=svg,image -- -D warnings
69+
args: --manifest-path=druid/Cargo.toml --all-targets --features=svg,image,im -- -D warnings
7070

7171
- name: cargo clippy druid-derive
7272
uses: actions-rs/cargo@v1
@@ -91,7 +91,7 @@ jobs:
9191
uses: actions-rs/cargo@v1
9292
with:
9393
command: test
94-
args: --manifest-path=druid/Cargo.toml --features=svg,image
94+
args: --manifest-path=druid/Cargo.toml --features=svg,image,im
9595

9696
- name: cargo test druid-derive
9797
uses: actions-rs/cargo@v1
@@ -176,7 +176,7 @@ jobs:
176176
with:
177177
command: clippy
178178
# TODO: Add svg feature when it's no longer broken with wasm
179-
args: --manifest-path=druid/Cargo.toml --all-targets --features=image --target wasm32-unknown-unknown -- -D warnings
179+
args: --manifest-path=druid/Cargo.toml --all-targets --features=image,im --target wasm32-unknown-unknown -- -D warnings
180180

181181
- name: cargo clippy druid-derive
182182
uses: actions-rs/cargo@v1
@@ -203,7 +203,7 @@ jobs:
203203
with:
204204
command: test
205205
# TODO: Add svg feature when it's no longer broken with wasm
206-
args: --manifest-path=druid/Cargo.toml --features=image --no-run --target wasm32-unknown-unknown
206+
args: --manifest-path=druid/Cargo.toml --features=image,im --no-run --target wasm32-unknown-unknown
207207

208208
- name: cargo test compile druid-derive
209209
uses: actions-rs/cargo@v1
@@ -264,7 +264,7 @@ jobs:
264264
uses: actions-rs/cargo@v1
265265
with:
266266
command: test
267-
args: --manifest-path=druid/Cargo.toml --features=svg,image
267+
args: --manifest-path=druid/Cargo.toml --features=svg,image,im
268268

269269
- name: cargo test druid-derive
270270
uses: actions-rs/cargo@v1

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ While some features like the clipboard, menus or file dialogs are not yet availa
3434
- `UpdateCtx::request_timer` and `UpdateCtx::request_anim_frame`. ([#898] by [@finnerale])
3535
- `UpdateCtx::size` and `LifeCycleCtx::size`. ([#917] by [@jneem])
3636
- `WidgetExt::debug_widget_id`, for displaying widget ids on hover. ([#876] by [@cmyr])
37+
- `im` feature, with `Data` support for the [`im` crate](https://docs.rs/im/) collections. ([#924])
3738

3839
### Changed
3940

@@ -165,6 +166,7 @@ While some features like the clipboard, menus or file dialogs are not yet availa
165166
[#909]: https://github.com/xi-editor/druid/pull/909
166167
[#917]: https://github.com/xi-editor/druid/pull/917
167168
[#920]: https://github.com/xi-editor/druid/pull/920
169+
[#924]: https://github.com/xi-editor/druid/pull/924
168170

169171
## [0.5.0] - 2020-04-01
170172

Cargo.lock

Lines changed: 55 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/src/data.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,17 @@ Here is an example of using `Data` to implement a simple data model.
3131
```rust
3232
{{#include ../book_examples/src/data_md.rs:derive}}
3333
```
34+
35+
#### Collections
36+
37+
`Data` is expected to be cheap to clone and cheap to compare, which can cause
38+
issues with collection types. For this reason, `Data` is not implemented for
39+
`std` types like `Vec` or `HashMap`. This is not a huge issue, however; you can
40+
always put these types inside an `Rc` or an `Arc`, or if you're dealing with
41+
larger collections you can build druid with the `im` feature, which brings in
42+
the [`im crate`], and adds a `Data` impl for the collections there. The [`im`
43+
crate] is a collection of immutable data structures that act a lot like the `std`
44+
collections, but can be cloned efficiently.
45+
46+
47+
[`im` crate]: https://docs.rs/im

druid/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ fnv = "1.0.3"
3232
xi-unicode = "0.2.0"
3333
image = {version = "0.23.2", optional = true}
3434
instant = { version = "0.1", features = [ "wasm-bindgen" ] }
35+
im = { version = "14.0", optional = true }
3536

3637
[dependencies.simple_logger]
3738
version = "1.6.0"

druid/src/data.rs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,17 @@ pub use druid_derive::Data;
6565
/// This function must have a signature in the form, `fn<T>(&T, &T) -> bool`,
6666
/// where `T` is the type of the field.
6767
///
68+
/// ## Collection types
69+
///
70+
/// `Data` is not implemented for `std` collection types, because comparing them
71+
/// can be expensive. To use collection types with druid, there are two easy options:
72+
/// either wrap the collection in an `Arc`, or build druid with the `im` feature,
73+
/// which adds `Data` implementations to the collections from the [`im` crate],
74+
/// a set of immutable data structures that fit nicely with druid.
75+
///
76+
/// If the `im` feature is used, the `im` crate is reexported from the root
77+
/// of the druid crate.
78+
///
6879
/// ### Example:
6980
///
7081
/// ```
@@ -90,6 +101,7 @@ pub use druid_derive::Data;
90101
/// checks for equality. Therefore, such types must also implement `PartialEq`.
91102
///
92103
/// [`Data::same`]: trait.Data.html#tymethod.same
104+
/// [`im` crate]: https://docs.rs/im
93105
pub trait Data: Clone + 'static {
94106
//// ANCHOR: same_fn
95107
/// Determine whether two values are the same.
@@ -375,6 +387,58 @@ impl Data for piet::Color {
375387
}
376388
}
377389

390+
//FIXME Vector::ptr_eq is not currently reliable; this is a temporary impl?
391+
#[cfg(feature = "im")]
392+
impl<T: Data> Data for im::Vector<T> {
393+
fn same(&self, other: &Self) -> bool {
394+
// for reasons outlined in https://github.com/bodil/im-rs/issues/129,
395+
// ptr_eq always returns false for small collections. This heuristic
396+
// falls back to using equality for collections below some threshold.
397+
// There may be a possibility of this returning false negatives, but
398+
// not false positives; that's an acceptable outcome.
399+
400+
/* this is the impl I expected to use
401+
const INLINE_LEN: usize = 48; // bytes available before first allocation;
402+
let inline_capacity: usize = INLINE_LEN / std::mem::size_of::<T>();
403+
if self.len() == other.len() && self.len() <= inline_capacity {
404+
self.iter().zip(other.iter()).all(|(a, b)| a.same(b))
405+
} else {
406+
self.ptr_eq(other)
407+
}
408+
*/
409+
410+
self.len() == other.len() && self.iter().zip(other.iter()).all(|(a, b)| a.same(b))
411+
}
412+
}
413+
414+
#[cfg(feature = "im")]
415+
impl<K: Clone + 'static, V: Data> Data for im::HashMap<K, V> {
416+
fn same(&self, other: &Self) -> bool {
417+
self.ptr_eq(other)
418+
}
419+
}
420+
421+
#[cfg(feature = "im")]
422+
impl<T: Data> Data for im::HashSet<T> {
423+
fn same(&self, other: &Self) -> bool {
424+
self.ptr_eq(other)
425+
}
426+
}
427+
428+
#[cfg(feature = "im")]
429+
impl<K: Clone + 'static, V: Data> Data for im::OrdMap<K, V> {
430+
fn same(&self, other: &Self) -> bool {
431+
self.ptr_eq(other)
432+
}
433+
}
434+
435+
#[cfg(feature = "im")]
436+
impl<T: Data> Data for im::OrdSet<T> {
437+
fn same(&self, other: &Self) -> bool {
438+
self.ptr_eq(other)
439+
}
440+
}
441+
378442
macro_rules! impl_data_for_array {
379443
() => {};
380444
($this:tt $($rest:tt)*) => {
@@ -406,4 +470,24 @@ mod test {
406470
assert!(input.same(&[1u8, 0, 0, 1, 0]));
407471
assert!(!input.same(&[1u8, 1, 0, 1, 0]));
408472
}
473+
474+
#[test]
475+
#[cfg(feature = "im")]
476+
fn im_data() {
477+
for len in 8..256 {
478+
let input = std::iter::repeat(0_u8).take(len).collect::<im::Vector<_>>();
479+
let mut inp2 = input.clone();
480+
assert!(input.same(&inp2));
481+
inp2.set(len - 1, 98);
482+
assert!(!input.same(&inp2));
483+
}
484+
}
485+
486+
#[test]
487+
#[cfg(feature = "im")]
488+
fn im_vec_different_length() {
489+
let one = std::iter::repeat(0_u8).take(9).collect::<im::Vector<_>>();
490+
let two = std::iter::repeat(0_u8).take(10).collect::<im::Vector<_>>();
491+
assert!(!one.same(&two));
492+
}
409493
}

druid/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,11 @@ use druid_shell as shell;
110110
#[doc(inline)]
111111
pub use druid_shell::{kurbo, piet};
112112

113+
// the im crate provides immutable data structures that play well with druid
114+
#[cfg(feature = "im")]
115+
#[doc(inline)]
116+
pub use im;
117+
113118
mod app;
114119
mod app_delegate;
115120
mod bloom;

0 commit comments

Comments
 (0)