commit f84c97686e50e2059cac31d4849faf7cc23c13c9
parent 1f15df9ca7b5764a28c26d34ada5183b5b66ead8
Author: William Casarin <jb55@jb55.com>
Date: Wed, 25 Feb 2026 14:08:11 -0800
nostrverse: quick refactors — parse_vec3, self_user, subscription dedup
- Extract parse_vec3() helper in nostr_events.rs, replacing duplicated
Vec3 parsing in parse_presence_position and parse_presence_velocity
- Add self_user()/self_user_mut() to NostrverseState, replacing 4
inline .users.iter().find(|u| u.is_self) patterns in lib.rs
- Unify RoomSubscription and PresenceSubscription via shared
KindSubscription base, deduplicating new() and poll() logic
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat:
4 files changed, 60 insertions(+), 48 deletions(-)
diff --git a/crates/notedeck_nostrverse/src/lib.rs b/crates/notedeck_nostrverse/src/lib.rs
@@ -274,9 +274,7 @@ impl NostrverseApp {
if let Some(renderer) = &self.renderer {
let self_pos = self
.state
- .users
- .iter()
- .find(|u| u.is_self)
+ .self_user()
.map(|u| u.position)
.unwrap_or(Vec3::ZERO);
let mut r = renderer.renderer.lock().unwrap();
@@ -497,9 +495,7 @@ impl NostrverseApp {
if let Some(kp) = ctx.accounts.selected_filled() {
let self_pos = self
.state
- .users
- .iter()
- .find(|u| u.is_self)
+ .self_user()
.map(|u| u.position)
.unwrap_or(Vec3::ZERO);
@@ -525,12 +521,7 @@ impl NostrverseApp {
// Assign avatar model to new users
if changed {
- let avatar_model = self
- .state
- .users
- .iter()
- .find(|u| u.is_self)
- .and_then(|u| u.model_handle);
+ let avatar_model = self.state.self_user().and_then(|u| u.model_handle);
if let Some(model) = avatar_model {
for user in &mut self.state.users {
if user.model_handle.is_none() {
@@ -602,7 +593,7 @@ impl NostrverseApp {
// Update self-user's position from the controller
if let Some(pos) = avatar_pos
- && let Some(self_user) = self.state.users.iter_mut().find(|u| u.is_self)
+ && let Some(self_user) = self.state.self_user_mut()
{
self_user.position = pos;
self_user.display_position = pos;
diff --git a/crates/notedeck_nostrverse/src/nostr_events.rs b/crates/notedeck_nostrverse/src/nostr_events.rs
@@ -103,27 +103,26 @@ pub fn build_presence_event<'a>(
.tag_str(&exp_str)
}
-/// Parse a presence event's position tag into a Vec3.
-pub fn parse_presence_position(note: &Note<'_>) -> Option<glam::Vec3> {
- let pos_str = get_tag_value(note, "position")?;
- let mut parts = pos_str.split_whitespace();
+/// Parse a whitespace-separated "x y z" string into a Vec3.
+fn parse_vec3(s: &str) -> Option<glam::Vec3> {
+ let mut parts = s.split_whitespace();
let x: f32 = parts.next()?.parse().ok()?;
let y: f32 = parts.next()?.parse().ok()?;
let z: f32 = parts.next()?.parse().ok()?;
Some(glam::Vec3::new(x, y, z))
}
+/// Parse a presence event's position tag into a Vec3.
+pub fn parse_presence_position(note: &Note<'_>) -> Option<glam::Vec3> {
+ parse_vec3(get_tag_value(note, "position")?)
+}
+
/// Parse a presence event's velocity tag into a Vec3.
/// Returns Vec3::ZERO if no velocity tag (backward compatible with old events).
pub fn parse_presence_velocity(note: &Note<'_>) -> glam::Vec3 {
- let Some(vel_str) = get_tag_value(note, "velocity") else {
- return glam::Vec3::ZERO;
- };
- let mut parts = vel_str.split_whitespace();
- let x: f32 = parts.next().and_then(|s| s.parse().ok()).unwrap_or(0.0);
- let y: f32 = parts.next().and_then(|s| s.parse().ok()).unwrap_or(0.0);
- let z: f32 = parts.next().and_then(|s| s.parse().ok()).unwrap_or(0.0);
- glam::Vec3::new(x, y, z)
+ get_tag_value(note, "velocity")
+ .and_then(parse_vec3)
+ .unwrap_or(glam::Vec3::ZERO)
}
/// Extract the "a" tag (space naddr) from a presence note.
diff --git a/crates/notedeck_nostrverse/src/room_state.rs b/crates/notedeck_nostrverse/src/room_state.rs
@@ -291,4 +291,14 @@ impl NostrverseState {
pub fn get_object_mut(&mut self, id: &str) -> Option<&mut RoomObject> {
self.objects.iter_mut().find(|o| o.id == id)
}
+
+ /// Get the local user
+ pub fn self_user(&self) -> Option<&RoomUser> {
+ self.users.iter().find(|u| u.is_self)
+ }
+
+ /// Get the local user mutably
+ pub fn self_user_mut(&mut self) -> Option<&mut RoomUser> {
+ self.users.iter_mut().find(|u| u.is_self)
+ }
}
diff --git a/crates/notedeck_nostrverse/src/subscriptions.rs b/crates/notedeck_nostrverse/src/subscriptions.rs
@@ -1,25 +1,43 @@
//! Local nostrdb subscription management for nostrverse rooms.
//!
-//! Subscribes to room events (kind 37555) in the local nostrdb and
-//! polls for updates each frame. No remote relay subscriptions — rooms
-//! are local-only for now.
+//! Subscribes to room events (kind 37555) and presence events (kind 10555)
+//! in the local nostrdb and polls for updates each frame.
use nostrdb::{Filter, Ndb, Note, Subscription, Transaction};
use crate::kinds;
+/// A local nostrdb subscription that polls for notes of a given kind.
+struct KindSubscription {
+ sub: Subscription,
+}
+
+impl KindSubscription {
+ fn new(ndb: &Ndb, kind: u16) -> Self {
+ let filter = Filter::new().kinds([kind as u64]).build();
+ let sub = ndb.subscribe(&[filter]).expect("kind subscription");
+ Self { sub }
+ }
+
+ fn poll<'a>(&self, ndb: &'a Ndb, txn: &'a Transaction) -> Vec<Note<'a>> {
+ ndb.poll_for_notes(self.sub, 50)
+ .into_iter()
+ .filter_map(|nk| ndb.get_note_by_key(txn, nk).ok())
+ .collect()
+ }
+}
+
/// Manages a local nostrdb subscription for room events.
pub struct RoomSubscription {
- /// Local nostrdb subscription handle
- sub: Subscription,
+ inner: KindSubscription,
}
impl RoomSubscription {
/// Subscribe to all room events (kind 37555) in the local nostrdb.
pub fn new(ndb: &Ndb) -> Self {
- let filter = Filter::new().kinds([kinds::ROOM as u64]).build();
- let sub = ndb.subscribe(&[filter]).expect("room subscription");
- Self { sub }
+ Self {
+ inner: KindSubscription::new(ndb, kinds::ROOM),
+ }
}
/// Subscribe to room events from a specific author.
@@ -30,16 +48,14 @@ impl RoomSubscription {
.authors([author])
.build();
let sub = ndb.subscribe(&[filter]).expect("room subscription");
- Self { sub }
+ Self {
+ inner: KindSubscription { sub },
+ }
}
/// Poll for new room events. Returns parsed notes.
pub fn poll<'a>(&self, ndb: &'a Ndb, txn: &'a Transaction) -> Vec<Note<'a>> {
- let note_keys = ndb.poll_for_notes(self.sub, 50);
- note_keys
- .into_iter()
- .filter_map(|nk| ndb.get_note_by_key(txn, nk).ok())
- .collect()
+ self.inner.poll(ndb, txn)
}
/// Query for existing room events (e.g. on startup).
@@ -55,23 +71,19 @@ impl RoomSubscription {
/// Manages a local nostrdb subscription for presence events (kind 10555).
pub struct PresenceSubscription {
- sub: Subscription,
+ inner: KindSubscription,
}
impl PresenceSubscription {
/// Subscribe to presence events in the local nostrdb.
pub fn new(ndb: &Ndb) -> Self {
- let filter = Filter::new().kinds([kinds::PRESENCE as u64]).build();
- let sub = ndb.subscribe(&[filter]).expect("presence subscription");
- Self { sub }
+ Self {
+ inner: KindSubscription::new(ndb, kinds::PRESENCE),
+ }
}
/// Poll for new presence events.
pub fn poll<'a>(&self, ndb: &'a Ndb, txn: &'a Transaction) -> Vec<Note<'a>> {
- let note_keys = ndb.poll_for_notes(self.sub, 50);
- note_keys
- .into_iter()
- .filter_map(|nk| ndb.get_note_by_key(txn, nk).ok())
- .collect()
+ self.inner.poll(ndb, txn)
}
}