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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
#![doc = include_str!("../README.md")]

use bevy_asset::{Asset, AssetPath, Handle};
use bevy_ecs::{bundle::Bundle, entity::Entity};
pub use default_constructor::InferInto;
use scoped_tls_hkt::scoped_thread_local;
use std::{cell::Cell, ptr::null_mut};

mod spawnable;
pub use spawnable::*;

#[doc(hidden)]
pub use bevy_asset::AssetServer;
#[doc(hidden)]
pub use bevy_ecs::system::{Commands, Res};
pub use bevy_spawn_fn_derive::*;
#[doc(hidden)]
pub use default_constructor::infer_construct;

/// Convert an item to a handle by registering using [`AssetServer::add`].
#[doc(hidden)]
pub fn asset<T: Asset>(a: T) -> Handle<T> {
    ASSET_SERVER.with(|s| s.add(a))
}

/// Convert a [`AssetPath`] to a handle by loading using [`AssetServer::load`].
#[doc(hidden)]
pub fn load<T: Asset>(a: AssetPath<'static>) -> Handle<T> {
    ASSET_SERVER.with(|s| s.load(a))
}

// A reference to the spawner scope.
thread_local! {static SPAWNER: Cell<*mut Spawner<'static, 'static, 'static>> = const { Cell::new(null_mut()) } }
scoped_thread_local!(static ASSET_SERVER: AssetServer);

/// Spawn a [`IntoSpawnable`] using a thread local spawner, returns [`Entity`].
///
/// This can be manually created via [`spawner_scope`] or used inside an system or function annotated with
/// [`spawner_fn`] or [`spawner_system`].
///
/// # Syntax
///
/// See [`infer_construct!`] and module level documentation of [`default_constructor`].
#[macro_export]
macro_rules! spawn {
    ($($tt: tt)*) => {
        {
            #[allow(unused_imports)]
            use $crate::{asset, load};
            $crate::spawn($crate::infer_construct! {
                $($tt)*
            })
        }
    };
}

struct Reset(*mut Spawner<'static, 'static, 'static>);

impl Drop for Reset {
    fn drop(&mut self) {
        SPAWNER.set(self.0);
    }
}

/// Push a [`Spawner`] onto thread local storage in a scope.
pub fn spawner_scope<'a, 'b: 'a, 'c: 'a, T>(
    spawner: &'a mut impl AsSpawner<'a, 'b, 'c>,
    f: impl FnOnce() -> T,
) -> T {
    let mut spawner = spawner.as_spawner();
    let prev = SPAWNER.replace((&mut spawner as *mut Spawner).cast());
    // for panic safety, this will reset the spawner during unwinding.
    let _reset = Reset(prev);
    f()
}

/// Push a [`AssetServer`] onto thread local storage in a scope.
pub fn asset_server_scope<T>(asset_server: &AssetServer, f: impl FnOnce() -> T) -> T {
    ASSET_SERVER.set(asset_server, f)
}

/// Spawn a [`IntoSpawnable`] using the current thread local [`spawner_scope`].
pub fn spawn(spawned: impl IntoSpawnable) -> Entity {
    let ptr = SPAWNER.replace(null_mut());
    // for panic safety, this will reset the spawner during unwinding.
    let __reset = Reset(ptr);
    // Safety: `SPAWNER` is only set by `spawner_scope` and
    // exclusively accessed in `spawn`.
    let spawner = unsafe { ptr.as_mut().expect("Must be called in a spawner scope.") };
    spawner.spawn(spawned)
}

/// A type that can be converted into a [`Bundle`].
pub trait IntoBundle {
    /// Convert to a [`Bundle`].
    fn into_bundle(self) -> impl Bundle;
}

/// A type that can be spawned as an entity.
pub trait Spawnable {
    /// Collects a static bundle of a concrete type.
    fn into_bundle(self) -> impl Bundle;
    /// Collect heterogenous components or bundles from a mutable reference of self.
    ///
    /// A common thing this might do is [`Option::take`] optional bundles and insert them.
    fn spawn_mut<'t>(&mut self, spawner: &'t mut Spawner) -> EntityMutSpawner<'t> {
        spawner.spawn_empty()
    }
    /// Spawn children.
    #[allow(unused_variables)]
    fn spawn_children(&mut self, spawner: &mut Spawner) {}
}

/// A type that can be converted to a [`Spawnable`].
pub trait IntoSpawnable {
    /// Convert to a [`Spawnable`].
    fn into_spawnable(self) -> impl Spawnable;
}

impl<T> IntoBundle for T
where
    T: Bundle,
{
    fn into_bundle(self) -> impl Bundle {
        self
    }
}

impl<T> Spawnable for T
where
    T: IntoBundle,
{
    fn into_bundle(self) -> impl Bundle {
        IntoBundle::into_bundle(self)
    }
}

impl<T> IntoSpawnable for T
where
    T: Spawnable,
{
    fn into_spawnable(self) -> impl Spawnable {
        self
    }
}

#[cfg(test)]
mod test {
    use bevy::app::App;
    use bevy_asset::AssetPlugin;
    use bevy_ecs::{bundle::Bundle, component::Component, system::RunSystemOnce, world::World};
    use bevy_hierarchy::WorldChildBuilder;
    use bevy_spawn_fn_derive::{spawner_fn, spawner_system};

    use crate::IntoBundle;

    #[derive(Component)]
    pub struct A;
    #[derive(Component)]
    pub struct B;

    #[derive(Component)]
    pub struct C;

    #[derive(Bundle)]
    pub struct Abc {
        a: A,
        b: B,
        c: C,
    }

    #[derive(Debug, Default)]
    pub struct IntoAbc {
        a: f32,
        b: String,
        c: char,
    }

    impl IntoBundle for IntoAbc {
        fn into_bundle(self) -> impl Bundle {
            Abc { a: A, b: B, c: C }
        }
    }

    #[spawner_fn]
    fn test1(spawner: &mut World) {
        spawn!(IntoAbc {
            a: 4,
            b: "Ferris",
            c: '\0'
        });
    }

    #[spawner_fn]
    fn test2(spawner: &mut WorldChildBuilder) {
        spawn!(IntoAbc {
            a: 4,
            b: "Ferris",
            c: '\0'
        });
    }

    #[spawner_system]
    fn test3() {
        spawn!(IntoAbc {
            a: 4,
            b: "Ferris",
            c: '\0'
        });
    }

    #[test]
    fn miri_test() {
        let mut world = App::new();
        world.add_plugins(AssetPlugin::default());
        world.world_mut().run_system_once(test3);
    }
}