bevy_nostr 0.2.0

Bevy plugin for the Nostr protocol
Documentation
//! Database related systems

use bevy::prelude::*;
use bevy_async_task::TaskRunner;
use nostr_sdk::prelude::Event;
use std::task::Poll;

use crate::{
    NostrClient,
    components::{DatabaseFetchEvent, FetchingEvent, NostrEvent, NostrEventId},
};

#[derive(EntityEvent)]
pub struct DbFetchEvent {
    entity: Entity,
}

/// Fetch the nostr events from the database when the [`DatabaseFetchEvent`]
/// component is added to an entity that has a [`NostrEventId`]
///
/// We trigger [`DbFetchEvent`] for every entity that contains [`FetchingEvent`]
/// or had the [`DatabaseFetchEvent`] added to it. [`FetchingEvent`] remains in the
/// entity as long as we need to poll the db task.
pub fn fetch_events_from_db(
    mut commands: Commands,
    to_fetch: Query<
        (Entity, &NostrEventId),
        Or<(Added<DatabaseFetchEvent>, With<FetchingEvent>)>,
    >,
) {
    for (entity, _) in &to_fetch {
        commands.trigger(DbFetchEvent { entity });
    }
}

/// Handle [`DbFetchEvent`]
pub fn on_db_fetch(
    db_event: On<DbFetchEvent>,
    mut commands: Commands,
    mut task_runner: TaskRunner<'_, (Entity, Option<Event>)>,
    nostr_client: Res<NostrClient>,
    nostr_events: Query<(Entity, &NostrEventId)>,
) {
    let Ok((entity, event_id)) = nostr_events.get(db_event.entity) else {
        warn!("on_db_fetch: Cannot find event!");
        return;
    };

    // Insert FetchingEvent early, no matter the status of the task runner
    commands.entity(entity).insert(FetchingEvent);

    if task_runner.is_idle() {
        let client = nostr_client.client.clone();
        let id = event_id.0.clone();
        let entity_c = entity.clone();

        task_runner.start(async move {
            match client.database().event_by_id(&id).await {
                Ok(result) => (entity_c, result),
                Err(err) => {
                    warn!("Error fetching event with id {id:?}: {err}");
                    (entity_c, None)
                }
            }
        });
    }

    // When the event is ready, insert it as a component in the entity
    if let Poll::Ready((ev_entity, result)) = task_runner.poll() {
        match result {
            Some(event) => {
                commands
                    .entity(ev_entity)
                    .insert(NostrEvent(event))
                    .remove::<FetchingEvent>();
            }
            None => {
                // Could not find the event: make sure we remove FetchingEvent
                commands.entity(ev_entity).remove::<FetchingEvent>();
            }
        }
    }
}