diff --git a/.gitignore b/.gitignore index ae97c4b..c4987a1 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ result *.db-shm *.ttf *.jpg +*.png *.db-wal package-lock.json *.sqlite diff --git a/assets/slides.md b/assets/slides.md index 729fc58..f630928 100644 --- a/assets/slides.md +++ b/assets/slides.md @@ -4,6 +4,14 @@ Making atproto actually easy with Rust --- +# @nonbinary.computer + +- Embedded (and everything else) developer, electronics designer +- Utopian in the Terra Ignota sense +- atproto hit me like a truck + +--- + ## Spite-driven development - Existing atproto libraries kinda sucked to work with @@ -30,23 +38,6 @@ Not just an atproto problem, but hurts more here, especially for those not wanti --- ## Why Rust? - -Only language where you can actually run it anywhere - -and it's not a stupid idea to. - ---- - -## Why Rust? - -- Rust has a reputation as a difficult and demanding language -- That reputation is sort of deserved - - ---- - -## Why Rust? - - The things Rust asks of you are things you often need to think about anyway - It just makes them explicit - We can make the sharp edges easier, if we care to @@ -59,14 +50,6 @@ and it's not a stupid idea to. # "Parse, don't validate" considered harmful -If serde_json can't parse something as the type you're asking of it, - -it will give you nothing except an error. - ---- - -# "Parse, don't validate" considered harmful - Jacquard has its `Data` type to handle some classes of this, to work with freeform data in useful ways, @@ -89,23 +72,17 @@ but not quite fits a lexicon. # Smart constraints -- Also a bad developer experience. -- All of a sudden you can't take advantage of all the nice types! -- `serde_json::Value` or `ipld_core::Ipld` offers literally nothing useful beyond "this thing is valid JSON/CBOR". +`Data` is **less** useless but still sucks donkey balls when you wanted your type. --- -# Smart constraints - -`Data` is **less useless** but still sucks donkey balls when you wanted your type. - ![](avasarala.jpg) --- # Smart constraints -And that is why jacquard's handle constructor has a specific carveout for "handle.invalid" +And that is why jacquard's handle constructor has a specific carveout for 'handle.invalid' Because there's a time to follow the spec @@ -120,6 +97,18 @@ And there's a time to refrain - Good defaults - Generated code you can just use, that's readable, easy to work with +--- + +## Why borrowing and lifetimes everywhere + + +- Lifetimes (Jacquard, `serde_json_borrow`) +- Unsafe (wrapped up, like `zerocopy` crate) +- Separate borrowed/owned type variants (what Rust does) +- `bytemuck` approach (`&'a Did` and `Box`) +- **Borrow or Share** (available on `bos-beta` branch on tangled) + + --- ## What you're seeing @@ -136,6 +125,8 @@ And there's a time to refrain - We can change that! +![](example-derive.png) + --- ## HOW? @@ -147,6 +138,6 @@ And there's a time to refrain --- -## Thank you! +## Thank you -Questions? +![Questions?](qr-code.png) diff --git a/src/event_card.rs b/src/event_card.rs index 206c76a..8f32768 100644 --- a/src/event_card.rs +++ b/src/event_card.rs @@ -969,14 +969,14 @@ fn render_slide_content(world: &mut World, slide: &crate::slides::Slide) { let wrapper = world .spawn(Node { align_self: AlignSelf::Center, + max_width: Val::Percent(60.0), + max_height: Val::Percent(40.0), margin: UiRect::axes(Val::Px(0.0), Val::Px(8.0)), ..default() }) .id(); - let img = world - .spawn(ImageNode::new(handle)) - .id(); + let img = world.spawn(ImageNode::new(handle)).id(); world.entity_mut(wrapper).add_child(img); world.entity_mut(content_entity).add_child(wrapper); @@ -1003,7 +1003,9 @@ fn render_slide_at_embed( let cached_text = { let cache = world.resource::(); cache.records.get(uri).map(|cached| { - let text = cached.data.get_at_path("text") + let text = cached + .data + .get_at_path("text") .and_then(|d| d.as_str()) .unwrap_or("[no text]") .to_string(); @@ -1013,8 +1015,8 @@ fn render_slide_at_embed( }; let border = UiRect::all(Val::Px(1.0)); - let (display_text, author_label) = cached_text - .unwrap_or_else(|| (format!("loading {uri}…"), String::new())); + let (display_text, author_label) = + cached_text.unwrap_or_else(|| (format!("loading {uri}…"), String::new())); let container = world .spawn(( @@ -1047,7 +1049,11 @@ fn render_slide_at_embed( let author_node = world .spawn(( Text::new(author_display), - TextFont { font: font.clone(), font_size: 28.0, ..default() }, + TextFont { + font: font.clone(), + font_size: 28.0, + ..default() + }, TextColor(dim), )) .id(); @@ -1057,7 +1063,11 @@ fn render_slide_at_embed( let text_node = world .spawn(( Text::new(display_text), - TextFont { font: font.clone(), font_size: 32.0, ..default() }, + TextFont { + font: font.clone(), + font_size: 32.0, + ..default() + }, TextColor(bright), TextLayout::new_with_linebreak(LineBreak::WordBoundary), )) @@ -1086,10 +1096,16 @@ fn render_slide_at_embed( let mut cache = world.resource_mut::(); cache.records_pending.remove(&uri_owned); if let Some((data, author_did)) = result { - cache.records.insert(uri_owned, CachedRecord { data, author_did }); + cache + .records + .insert(uri_owned, CachedRecord { data, author_did }); cache.bump(); + // Trigger slide re-render + let mut render = world.resource_mut::(); + render.slide_generation += 1; } - }); + }) + .await; }); } } @@ -1117,7 +1133,11 @@ fn spawn_styled_spans( return world .spawn(( Text::new(full_text), - TextFont { font: font.clone(), font_size, ..default() }, + TextFont { + font: font.clone(), + font_size, + ..default() + }, TextColor(bright), TextLayout::new_with_linebreak(LineBreak::WordBoundary), )) @@ -1145,7 +1165,11 @@ fn spawn_styled_spans( let child = world .spawn(( Text::new(span.text.clone()), - TextFont { font: span_font, font_size, ..default() }, + TextFont { + font: span_font, + font_size, + ..default() + }, TextColor(bright), )) .id(); @@ -1162,8 +1186,20 @@ async fn fetch_record_by_uri( use jacquard::types::string::Did; use smol_str::ToSmolStr; - let parsed = AtUri::new(uri.to_smolstr()).ok()?; - let output = client.fetch_record_slingshot(&parsed).await.ok()?; + let parsed = match AtUri::new(uri.to_smolstr()) { + Ok(u) => u, + Err(e) => { + warn!("fetch_record_by_uri: invalid AT-URI {uri}: {e:?}"); + return None; + } + }; + let output = match client.fetch_record_slingshot(&parsed).await { + Ok(o) => o, + Err(e) => { + warn!("fetch_record_by_uri: slingshot failed for {uri}: {e:?}"); + return None; + } + }; let author = Did::new(parsed.authority().as_str().to_smolstr()).ok()?; Some((output.value, author)) }