๐Ÿ“ฆ Zeenobit / moonshine_view

๐Ÿ“„ lib.rs ยท 128 lines
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128#![doc = include_str!("../README.md")]
#![warn(missing_docs)]
#![allow(deprecated)] // TODO: Remove deprecated code

use bevy_app::prelude::*;
use bevy_ecs::prelude::*;
use bevy_ecs::relationship::Relationship;
use moonshine_kind::prelude::*;
use moonshine_save::load::Unload;

/// Common elements for the view system.
pub mod prelude {
    pub use super::{RegisterViewable, View, Viewable, ViewableKind};
}

#[cfg(test)]
mod tests;

/// Trait used to register a [`ViewableKind`] with an [`App`].
pub trait RegisterViewable {
    /// Adds a given [`Kind`] as viewable.
    fn register_viewable<T: ViewableKind>(&mut self) -> &mut Self;
}

impl RegisterViewable for App {
    fn register_viewable<T: ViewableKind>(&mut self) -> &mut Self {
        self.add_systems(PreUpdate, trigger_build_view::<T>);
        self
    }
}

/// A trait used to define a [`Kind`] as viewable.
pub trait ViewableKind: Kind {
    /// Returns the default view [`Bundle`] for this [`Kind`].
    ///
    /// # Usage
    /// By default, this returns an [`Unload`] component to ensure all views are despawned when the game is loaded.
    ///
    /// The output bundle is inserted into the [`View`] entity when it is spawned before [`Viewable<T>`] is inserted.
    /// This is useful for initializing the view entity before anything else can react to it.
    fn view_bundle() -> impl Bundle {
        Unload
    }
}

/// A [`Component`] which represents a view of an [`Entity`] of the given [`ViewableKind`].
///
/// A "view entity" is analogous to the View in the Model-View-Controller (MVC) pattern.
#[derive(Component)]
#[component(on_insert = <Self as Relationship>::on_insert)]
#[component(on_replace = <Self as Relationship>::on_replace)]
pub struct View<T: ViewableKind> {
    viewable: Instance<T>,
}

impl<T: ViewableKind> View<T> {
    /// Returns the associated viewable entity.
    pub fn viewable(&self) -> Instance<T> {
        self.viewable
    }
}

impl<T: ViewableKind> Relationship for View<T> {
    type RelationshipTarget = Viewable<T>;

    fn get(&self) -> Entity {
        self.viewable.entity()
    }

    fn from(entity: Entity) -> Self {
        Self {
            viewable: unsafe { Instance::from_entity_unchecked(entity) },
        }
    }

    fn set_risky(&mut self, entity: Entity) {
        unsafe {
            *self.viewable.as_entity_mut() = entity;
        }
    }
}

/// A [`Component`] which represents an [`Entity`] associated with a [`View`].
///
/// A "viewable entity" is analogous to the Model in the Model-View-Controller (MVC) pattern.
#[derive(Component, Debug)]
#[component(on_replace = <Self as RelationshipTarget>::on_replace)]
#[component(on_despawn = <Self as RelationshipTarget>::on_despawn)]
pub struct Viewable<T: ViewableKind> {
    view: Instance<View<T>>,
}

impl<T: ViewableKind> Viewable<T> {
    /// Returns the [`View`] [`Instance`] associated with this [`Viewable`].
    pub fn view(&self) -> Instance<View<T>> {
        self.view
    }
}

impl<T: ViewableKind> RelationshipTarget for Viewable<T> {
    const LINKED_SPAWN: bool = true;

    type Relationship = View<T>;

    type Collection = Instance<View<T>>;

    fn collection(&self) -> &Self::Collection {
        &self.view
    }

    fn collection_mut_risky(&mut self) -> &mut Self::Collection {
        &mut self.view
    }

    fn from_collection_risky(collection: Self::Collection) -> Self {
        Self { view: collection }
    }
}

fn trigger_build_view<T: ViewableKind>(
    query: Query<Instance<T>, Without<Viewable<T>>>,
    mut commands: Commands,
) {
    for viewable in query.iter() {
        commands.spawn((T::view_bundle(), View { viewable }));
    }
}