Skip to main content

yog_api/
nanite.rs

1//! Nanites (`docs/design/nanites.md`): a Yog mod reaching into the game's
2//! (or a Java mod's) classes by Mojang's names — any member, private ones
3//! too. Usually through `#[nanite]`:
4//!
5//! ```ignore
6//! #[nanite("net.minecraft.world.entity.LivingEntity")]
7//! pub struct LivingEntity;
8//!
9//! #[nanite]
10//! impl LivingEntity {
11//!     fn hurt(&self, source: &DamageSource, amount: f32) -> bool;
12//!     #[field] fn health(&self) -> f32;
13//! }
14//!
15//! LivingEntity::of(&entity).hurt(&source, 4.0);
16//! ```
17//!
18//! A member is resolved once (its run-time name looked up by the host), then
19//! called by its handle; objects that cross are held by the host for the
20//! mod until their [`Handle`]'s last copy drops.
21
22use yog_data::{Ref, Value};
23use yog_event::nanite::{Call, Get, Release, Resolve, Set};
24use yog_ids::{kind, nanite_ref};
25use yog_sync::{Arc, OnceLock};
26
27mod added;
28mod inject;
29mod java;
30pub use added::Added;
31pub use inject::{__declare, __yog_nanite_registry, __yog_nanites, Hook, InjectEntry};
32pub use java::{Java, Reply};
33
34#[doc(hidden)]
35pub use inject::subscribe as __subscribe;
36
37/// An object the host holds for this mod, or an entity (by uuid).
38#[derive(Clone, Debug)]
39pub enum Handle {
40    Object(Arc<Held>),
41    Entity(String),
42}
43
44/// An object held: let go when the last handle to it drops.
45#[derive(Debug)]
46pub struct Held(u64);
47
48impl Drop for Held {
49    fn drop(&mut self) {
50        crate::call(&Release {
51            objects: vec![self.0],
52        });
53    }
54}
55
56impl Handle {
57    /// The entity `uuid`.
58    pub fn entity(uuid: impl Into<String>) -> Handle {
59        Handle::Entity(uuid.into())
60    }
61
62    /// As the bus carries it.
63    pub fn to_value(&self) -> Value {
64        match self {
65            Handle::Object(held) => Value::Ref(Ref::new(nanite_ref::OBJECT, held.0.to_string())),
66            Handle::Entity(uuid) => Value::Ref(Ref::new(kind::ENTITY, uuid.clone())),
67        }
68    }
69
70    /// Read off the bus: a held object or an entity.
71    pub fn from_value(v: Value) -> Option<Handle> {
72        let Value::Ref(r) = v else { return None };
73        match r.kind.as_str() {
74            nanite_ref::OBJECT => r.id.parse().ok().map(|n| Handle::Object(Arc::new(Held(n)))),
75            kind::ENTITY => Some(Handle::Entity(r.id)),
76            _ => None,
77        }
78    }
79}
80
81/// A Java class a mod reaches (`#[nanite("…")]` implements it on a struct
82/// holding its [`Handle`]).
83pub trait Nanite: Sized {
84    /// Its name, Mojang's (`net.minecraft.world.entity.LivingEntity`).
85    const CLASS: &'static str;
86    fn from_handle(handle: Handle) -> Self;
87    fn handle(&self) -> &Handle;
88
89    /// The entity `entity` as this class (it should be one).
90    fn of(entity: &yog_event::Entity) -> Self {
91        Self::from_handle(Handle::entity(entity.uuid.clone()))
92    }
93}
94
95/// A member of a class, resolved the first time it's used.
96pub struct Member {
97    class: &'static str,
98    name: &'static str,
99    /// Its descriptor, made from its Rust types the first time.
100    desc: fn() -> String,
101    id: OnceLock<Option<u32>>,
102}
103
104/// A constructor's name.
105pub const CONSTRUCTOR: &str = "<init>";
106
107impl Member {
108    /// Member `name` of `class`, `desc` its descriptor (a method's
109    /// `(…)R`, a constructor's `(…)V` named [`CONSTRUCTOR`], a field's type).
110    pub const fn new(class: &'static str, name: &'static str, desc: fn() -> String) -> Member {
111        Member {
112            class,
113            name,
114            desc,
115            id: OnceLock::new(),
116        }
117    }
118
119    /// Its class, Mojang's name.
120    pub fn class(&self) -> &'static str {
121        self.class
122    }
123
124    /// Its name and descriptor, as resolved.
125    pub fn signature(&self) -> (&'static str, String) {
126        (self.name, (self.desc)())
127    }
128
129    /// Its handle; `None` when the host has no such member (logged there).
130    pub fn id(&self) -> Option<u32> {
131        *self.id.get_or_init(|| {
132            crate::call(&Resolve {
133                class: self.class.to_owned(),
134                name: self.name.to_owned(),
135                desc: (self.desc)(),
136            })
137            .flatten()
138        })
139    }
140
141    /// Called on `target` (null: static, a constructor) with `args`.
142    pub fn call(&self, target: Value, args: Vec<Value>) -> Option<Value> {
143        let member = self.id()?;
144        crate::call(&Call {
145            member,
146            target,
147            args,
148        })
149        .flatten()
150    }
151
152    /// The field of `target` (null: static).
153    pub fn get(&self, target: Value) -> Option<Value> {
154        let member = self.id()?;
155        crate::call(&Get { member, target }).flatten()
156    }
157
158    /// The field of `target` set to `value`.
159    pub fn set(&self, target: Value, value: Value) -> bool {
160        let Some(member) = self.id() else {
161            return false;
162        };
163        crate::call(&Set {
164            member,
165            target,
166            value,
167        })
168        .unwrap_or(false)
169    }
170}
171
172/// `(params)ret`, a method's descriptor.
173pub fn method_desc(params: &[String], ret: &str) -> String {
174    format!("({}){ret}", params.concat())
175}
176
177#[cfg(test)]
178mod tests;