Skip to main content

kvas_engine/
params.rs

1//! Typed script variables. Declare them once with [`script_params!`](crate::script_params): they get
2//! defaults, show up as Inspector fields on the attached script, are saved in the scene and can be read or
3//! changed by other scripts (`Ctx::var` / `Ctx::set_var`).
4//!
5//! ```
6//! use kvas_engine::prelude::*;
7//! script_params! {
8//!     /// Spins its object.
9//!     pub struct Spinner {
10//!         /// Degrees per second around each axis.
11//!         speed: Vec3 = Vec3::new(0.0, 90.0, 0.0),
12//!         enabled: bool = true,
13//!         target: EntityRef = EntityRef::default(),
14//!         tint: Color = Color([1.0, 0.5, 0.2, 1.0]),
15//!         ticks: Local<u32> = Local::default(),
16//!     }
17//! }
18//! let mut s = Spinner::default();
19//! assert_eq!(s.params().len(), 4); // `Local` values are not shown
20//! ```
21
22use crate::reflect::{AssetKind, Field};
23use glam::Vec3;
24use serde::{Deserialize, Serialize};
25
26/// A type that can be a script variable.
27pub trait Param {
28    /// The Inspector field for this value, or `None` for hidden state.
29    fn field<'a>(&'a mut self, name: &'static str) -> Option<Field<'a>>;
30}
31
32impl Param for f32 {
33    fn field<'a>(&'a mut self, name: &'static str) -> Option<Field<'a>> {
34        Some(Field::f32(name, self))
35    }
36}
37impl Param for i32 {
38    fn field<'a>(&'a mut self, name: &'static str) -> Option<Field<'a>> {
39        Some(Field::i32(name, self))
40    }
41}
42impl Param for u32 {
43    fn field<'a>(&'a mut self, name: &'static str) -> Option<Field<'a>> {
44        Some(Field::u32(name, self))
45    }
46}
47impl Param for bool {
48    fn field<'a>(&'a mut self, name: &'static str) -> Option<Field<'a>> {
49        Some(Field::bool(name, self))
50    }
51}
52impl Param for Vec3 {
53    fn field<'a>(&'a mut self, name: &'static str) -> Option<Field<'a>> {
54        Some(Field::vec3(name, self))
55    }
56}
57impl Param for String {
58    fn field<'a>(&'a mut self, name: &'static str) -> Option<Field<'a>> {
59        Some(Field::text(name, self))
60    }
61}
62impl<T: crate::reflect::Choice + 'static> Param for T {
63    fn field<'a>(&'a mut self, name: &'static str) -> Option<Field<'a>> {
64        Some(Field::choice(name, self))
65    }
66}
67
68/// An RGBA colour variable (shown with a colour picker).
69#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
70pub struct Color(pub [f32; 4]);
71
72impl Default for Color {
73    fn default() -> Self {
74        Color([1.0; 4])
75    }
76}
77impl Param for Color {
78    fn field<'a>(&'a mut self, name: &'static str) -> Option<Field<'a>> {
79        Some(Field::color(name, &mut self.0))
80    }
81}
82
83/// A reference to another object by name (shown as an object picker you can drop objects on).
84#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
85pub struct EntityRef(pub String);
86
87impl EntityRef {
88    /// The referenced object, if one with that name exists.
89    pub fn resolve(&self, ctx: &crate::script::Ctx) -> Option<kvas_ecs::Entity> {
90        (!self.0.is_empty()).then(|| ctx.find(&self.0)).flatten()
91    }
92}
93impl Param for EntityRef {
94    fn field<'a>(&'a mut self, name: &'static str) -> Option<Field<'a>> {
95        Some(Field::entity(name, &mut self.0))
96    }
97}
98
99/// A model path, picked from the project's models.
100#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
101pub struct MeshRef(pub String);
102
103impl Param for MeshRef {
104    fn field<'a>(&'a mut self, name: &'static str) -> Option<Field<'a>> {
105        Some(Field::asset(name, &mut self.0, AssetKind::Mesh))
106    }
107}
108
109/// A texture path, picked from the project's images.
110#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
111pub struct TextureRef(pub String);
112
113impl Param for TextureRef {
114    fn field<'a>(&'a mut self, name: &'static str) -> Option<Field<'a>> {
115        Some(Field::asset(name, &mut self.0, AssetKind::Texture))
116    }
117}
118
119/// A sound clip path, picked from the project's audio files.
120#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
121pub struct SoundRef(pub String);
122
123impl Param for SoundRef {
124    fn field<'a>(&'a mut self, name: &'static str) -> Option<Field<'a>> {
125        Some(Field::asset(name, &mut self.0, AssetKind::Audio))
126    }
127}
128
129/// Script state that is not shown in the Inspector or saved.
130#[derive(Clone, Copy, Debug, Default, PartialEq)]
131pub struct Local<T>(pub T);
132
133impl<T> Param for Local<T> {
134    fn field<'a>(&'a mut self, _name: &'static str) -> Option<Field<'a>> {
135        None
136    }
137}
138impl<T> std::ops::Deref for Local<T> {
139    type Target = T;
140    fn deref(&self) -> &T {
141        &self.0
142    }
143}
144impl<T> std::ops::DerefMut for Local<T> {
145    fn deref_mut(&mut self) -> &mut T {
146        &mut self.0
147    }
148}
149
150/// Declares a script struct with typed, defaulted variables. Generates `Default` and
151/// `params()`, which a script returns from `fields()`:
152///
153/// ```ignore
154/// script_params! { pub struct Door { speed: f32 = 2.0, open: bool = false } }
155/// impl Script for Door {
156///     fn update(&mut self, ctx: &mut Ctx) { /* self.speed, self.open */ }
157///     fn fields(&mut self) -> Vec<Field<'_>> { self.params() }
158/// }
159/// ```
160#[macro_export]
161macro_rules! script_params {
162    ($(#[$meta:meta])* $vis:vis struct $name:ident { $($(#[$fmeta:meta])* $field:ident : $ty:ty = $default:expr),* $(,)? }) => {
163        $(#[$meta])*
164        $vis struct $name { $($(#[$fmeta])* pub $field: $ty),* }
165        impl Default for $name {
166            fn default() -> Self { Self { $($field: $default),* } }
167        }
168        impl $name {
169            /// Inspector fields for every non-`Local` variable.
170            pub fn params(&mut self) -> Vec<$crate::reflect::Field<'_>> {
171                let mut out = Vec::new();
172                $(if let Some(f) = $crate::params::Param::field(&mut self.$field, stringify!($field)) { out.push(f); })*
173                out
174            }
175        }
176    };
177}
178
179/// Lists the Inspector fields of a hand-written script in one line.
180/// Every listed field must implement [`Param`] (numbers, `bool`, `Vec3`, `String`, [`Color`],
181/// asset and entity references, enums with `Choice`, and [`Local`] to hide a variable).
182///
183/// ```
184/// use kvas_engine::prelude::*;
185///
186/// #[derive(Default)]
187/// struct Door { speed: f32, open: bool, name: String }
188/// impl Script for Door {
189///     fn fields(&mut self) -> Vec<Field<'_>> { fields!(self; speed, open, name) }
190/// }
191/// let mut door = Door::default();
192/// assert_eq!(door.fields().len(), 3);
193/// ```
194#[macro_export]
195macro_rules! fields {
196    ($s:ident; $($f:ident),* $(,)?) => {{
197        #[allow(unused_mut)]
198        let mut out: Vec<$crate::reflect::Field<'_>> = Vec::new();
199        $(if let Some(field) = $crate::params::Param::field(&mut $s.$f, stringify!($f)) { out.push(field); })*
200        out
201    }};
202}
203
204/// Declares a whole script in one block: the variables with their defaults (shown in the Inspector
205/// and saved in the scene) and the callbacks. No `Default`, no `fields()`, no boilerplate.
206///
207/// Callbacks are `start`, `update` and `fixed_update`; each names `self` and the context.
208/// Wrap a variable in [`Local`](crate::params::Local) to keep it out of the Inspector.
209///
210/// ```
211/// use kvas_engine::prelude::*;
212///
213/// script! {
214///     /// Spins around Y and counts the frames it has been running.
215///     pub struct Spinner {
216///         speed: f32 = 90.0,
217///         axis: Vec3 = Vec3::Y,
218///         frames: Local<u32> = Local(0),
219///     }
220///
221///     start(self, ctx) {
222///         ctx.log(format!("spinner started at speed {}", self.speed));
223///     }
224///
225///     update(self, ctx) {
226///         ctx.rotate(self.axis * self.speed * ctx.dt());
227///         *self.frames += 1;
228///     }
229/// }
230///
231/// let mut spinner = Spinner::default();
232/// assert_eq!(spinner.speed, 90.0);
233/// assert_eq!(spinner.fields().len(), 2, "Local fields are hidden");
234/// ```
235#[macro_export]
236macro_rules! script {
237    (
238        $(#[$meta:meta])* $vis:vis struct $name:ident { $($body:tt)* }
239        $( $phase:ident ($s:ident, $c:ident) $blk:block )*
240    ) => {
241        $crate::script_params! { $(#[$meta])* $vis struct $name { $($body)* } }
242        impl $crate::script::Script for $name {
243            $( $crate::script!(@phase $phase $s $c $blk); )*
244            fn fields(&mut self) -> Vec<$crate::reflect::Field<'_>> { self.params() }
245        }
246    };
247    (@phase start $s:ident $c:ident $blk:block) => { fn start(&mut $s, $c: &mut $crate::script::Ctx) $blk };
248    (@phase update $s:ident $c:ident $blk:block) => { fn update(&mut $s, $c: &mut $crate::script::Ctx) $blk };
249    (@phase fixed_update $s:ident $c:ident $blk:block) => { fn fixed_update(&mut $s, $c: &mut $crate::script::Ctx) $blk };
250}