Skip to main content

yog_api/reach/
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
56yog_data::kind!(HeldObject = nanite_ref::OBJECT: u64);
57
58impl Handle {
59    /// The entity `uuid`.
60    pub fn entity(uuid: impl Into<String>) -> Handle {
61        Handle::Entity(uuid.into())
62    }
63
64    /// As the bus carries it.
65    pub fn to_value(&self) -> Value {
66        match self {
67            Handle::Object(held) => Value::Ref(Ref::of::<HeldObject>(&held.0)),
68            Handle::Entity(uuid) => Value::Ref(Ref::of::<yog_data::kinds::Entity>(uuid)),
69        }
70    }
71
72    /// Read off the bus: a held object or an entity.
73    pub fn from_value(v: Value) -> Option<Handle> {
74        let Value::Ref(r) = v else { return None };
75        match r.kind.as_str() {
76            nanite_ref::OBJECT => {
77                r.id.as_str()
78                    .parse()
79                    .ok()
80                    .map(|n| Handle::Object(Arc::new(Held(n))))
81            }
82            kind::ENTITY => Some(Handle::Entity(r.id.as_str().to_owned())),
83            _ => None,
84        }
85    }
86}
87
88/// A Java class a mod reaches (`#[nanite("…")]` implements it on a struct
89/// holding its [`Handle`]).
90pub trait Nanite: Sized {
91    /// Its name, Mojang's (`net.minecraft.world.entity.LivingEntity`).
92    const CLASS: &'static str;
93    fn from_handle(handle: Handle) -> Self;
94    fn handle(&self) -> &Handle;
95
96    /// The entity `entity` as this class (it should be one).
97    fn of(entity: &yog_event::Entity) -> Self {
98        Self::from_handle(Handle::entity(entity.uuid.clone()))
99    }
100}
101
102/// A member of a class, resolved the first time it's used.
103pub struct Member {
104    class: &'static str,
105    name: &'static str,
106    /// Its descriptor, made from its Rust types the first time.
107    desc: fn() -> String,
108    id: OnceLock<Option<u32>>,
109}
110
111/// A constructor's name.
112pub const CONSTRUCTOR: &str = "<init>";
113
114impl Member {
115    /// Member `name` of `class`, `desc` its descriptor (a method's
116    /// `(…)R`, a constructor's `(…)V` named [`CONSTRUCTOR`], a field's type).
117    pub const fn new(class: &'static str, name: &'static str, desc: fn() -> String) -> Member {
118        Member {
119            class,
120            name,
121            desc,
122            id: OnceLock::new(),
123        }
124    }
125
126    /// Its class, Mojang's name.
127    pub fn class(&self) -> &'static str {
128        self.class
129    }
130
131    /// Its name and descriptor, as resolved.
132    pub fn signature(&self) -> (&'static str, String) {
133        (self.name, (self.desc)())
134    }
135
136    /// Its handle; `None` when the host has no such member (logged there).
137    pub fn id(&self) -> Option<u32> {
138        *self.id.get_or_init(|| {
139            crate::call(&Resolve {
140                class: self.class.to_owned(),
141                name: self.name.to_owned(),
142                desc: (self.desc)(),
143            })
144            .flatten()
145        })
146    }
147
148    /// Called on `target` (null: static, a constructor) with `args`.
149    pub fn call(&self, target: Value, args: Vec<Value>) -> Option<Value> {
150        let member = self.id()?;
151        crate::call(&Call {
152            member,
153            target,
154            args,
155        })
156        .flatten()
157    }
158
159    /// The field of `target` (null: static).
160    pub fn get(&self, target: Value) -> Option<Value> {
161        let member = self.id()?;
162        crate::call(&Get { member, target }).flatten()
163    }
164
165    /// The field of `target` set to `value`.
166    pub fn set(&self, target: Value, value: Value) -> bool {
167        let Some(member) = self.id() else {
168            return false;
169        };
170        crate::call(&Set {
171            member,
172            target,
173            value,
174        })
175        .unwrap_or(false)
176    }
177}
178
179/// `(params)ret`, a method's descriptor.
180pub fn method_desc(params: &[String], ret: &str) -> String {
181    format!("({}){ret}", params.concat())
182}
183
184#[cfg(test)]
185mod tests;