Skip to main content

yog_event/
nanite.rs

1//! Nanites (`docs/design/nanites.md`).
2//!
3//! A host's mapping table ("Names"): the game's
4//! classes and members by Mojang's names, and what they're called at run
5//! time on that host (intermediary on Fabric before 26.2, SRG members on
6//! Forge). Only names that differ are kept. `yog mappings` makes it; the
7//! host reads it once to look up every name a nanite uses.
8
9use yog_data::{Schema, Value};
10use yog_ids as ids;
11
12actions! {
13    /// A member by Mojang's names (`class`: `net.minecraft.world.entity.LivingEntity`;
14    /// `desc`: a method's or constructor's (`<init>`) descriptor, or a
15    /// field's type) → its handle, for every call after; `None`: no such member.
16    Resolve { class: String, name: String, desc: String } -> Option<u32> = ids::service_nanite::RESOLVE, "nanite.YogReflect.resolve";
17    /// Method or constructor `member` called on `target` (null: a static one,
18    /// a constructor) with `args`; its result (`None`: it failed). An object
19    /// that isn't a number, text, a list or an entity comes back as a
20    /// reference (`yog_ids::nanite_ref::OBJECT`) the host holds until released.
21    Call { member: u32, target: Value, args: Vec<Value> } -> Option<Value> = ids::service_nanite::CALL, "nanite.YogReflect.call";
22    /// Field `member` of `target` (null: a static one).
23    Get { member: u32, target: Value } -> Option<Value> = ids::service_nanite::GET, "nanite.YogReflect.get";
24    /// Field `member` of `target` set to `value`.
25    Set { member: u32, target: Value, value: Value } -> bool = ids::service_nanite::SET, "nanite.YogReflect.set";
26    /// The objects referenced let go.
27    Release { objects: Vec<u64> } -> bool = ids::service_nanite::RELEASE, "nanite.YogReflect.release";
28}
29
30/// Every class a member or the class itself of is renamed.
31#[derive(Schema, Debug, Clone, Default, PartialEq, Eq)]
32pub struct Mappings {
33    /// The run-time namespace (`yog_ids::mapping_ns::*`).
34    pub runtime: String,
35    pub classes: Vec<ClassMap>,
36}
37
38/// A class: its names (internal form, `net/minecraft/world/entity/Entity`)
39/// and its renamed members.
40#[derive(Schema, Debug, Clone, Default, PartialEq, Eq)]
41pub struct ClassMap {
42    pub named: String,
43    pub runtime: String,
44    pub fields: Vec<MemberMap>,
45    pub methods: Vec<MemberMap>,
46}
47
48/// A member: its name, its descriptor in Mojang's names (a method's;
49/// empty for a field) and its run-time name.
50#[derive(Schema, Debug, Clone, Default, PartialEq, Eq)]
51pub struct MemberMap {
52    pub named: String,
53    pub desc: String,
54    pub runtime: String,
55}
56
57impl Mappings {
58    /// Drops what isn't renamed: members named alike, classes left with
59    /// nothing renamed.
60    pub fn prune(&mut self) {
61        for c in &mut self.classes {
62            c.fields.retain(|m| m.named != m.runtime);
63            c.methods.retain(|m| m.named != m.runtime);
64        }
65        self.classes
66            .retain(|c| c.named != c.runtime || !c.fields.is_empty() || !c.methods.is_empty());
67        self.classes.sort_by(|a, b| a.named.cmp(&b.named));
68    }
69}
70
71/// A nanite a mod declares (`#[inject]`, `#[redirect]`, `#[modify_arg]`…):
72/// where, by Mojang's names, and what it does there — what the host turns
73/// into the Mixin injector of its `kind` before the class loads. Mods at one
74/// point share it: its [`Inject::point`] is the event's key, their handlers
75/// ordered by the bus's priorities.
76#[derive(Schema, Debug, Clone, Default, PartialEq, Eq)]
77pub struct Inject {
78    /// The point's key ([`point`]).
79    pub point: String,
80    /// `yog_ids::nanite_kind`.
81    pub kind: String,
82    /// `net.minecraft.world.entity.LivingEntity`.
83    pub class: String,
84    pub method: String,
85    pub desc: String,
86    /// Mixin's `@At` value (`yog_ids::mixin_at`).
87    pub at: String,
88    /// What an `INVOKE` / `FIELD` / `NEW` at names (`Lowner;name(desc)`, Mojang's), else empty.
89    pub target: String,
90    /// Which occurrence (-1: every one).
91    pub ordinal: i32,
92    /// `yog_ids::mixin_shift`.
93    pub shift: String,
94    /// Whether handlers may cancel the method (or set its return value).
95    pub cancellable: bool,
96    /// A `modify_arg`'s argument, a `modify_variable`'s local (-1: by type).
97    pub index: i32,
98    /// A `modify_constant`'s constant: `<yog_ids::nanite_constant>:<value>`.
99    pub constant: String,
100    /// A `modify_variable`'s local's type (a descriptor, Mojang's).
101    pub value_desc: String,
102    /// A `FIELD` at's write (`PUTFIELD` / `PUTSTATIC`), else its read.
103    pub write: bool,
104    /// The locals an inject asks for, in order: `[&]<descriptor>[@<ordinal>]`
105    /// (`yog_ids::nanite_local`; Mojang's names).
106    pub locals: Vec<String>,
107}
108
109/// An injection point's key:
110/// `<kind>:<class>#<method><desc>@<at>[:<target>][:<qualifier>]` — the
111/// qualifier a `modify_arg`'s index, a `modify_variable`'s local, a
112/// `modify_constant`'s constant.
113pub fn point(i: &Inject) -> String {
114    use ids::nanite_point::{AT, MEMBER, TARGET};
115    let mut key = format!(
116        "{}{TARGET}{}{MEMBER}{}{}{AT}{}",
117        i.kind, i.class, i.method, i.desc, i.at
118    );
119    for part in [i.target.clone(), qualifier(i)] {
120        if !part.is_empty() {
121            key.push_str(TARGET);
122            key.push_str(&part);
123        }
124    }
125    key
126}
127
128/// What tells apart nanites of one kind at one place.
129fn qualifier(i: &Inject) -> String {
130    match i.kind.as_str() {
131        ids::nanite_kind::MODIFY_ARG => i.index.to_string(),
132        ids::nanite_kind::MODIFY_VARIABLE => format!("{}{}", i.index, i.value_desc),
133        ids::nanite_kind::MODIFY_CONSTANT => i.constant.clone(),
134        ids::nanite_kind::REDIRECT if i.write => ids::nanite_point::WRITE.to_owned(),
135        // Injects asking for other locals at one place are made apart.
136        ids::nanite_kind::INJECT => i.locals.join(ids::nanite_local::LIST),
137        _ => String::new(),
138    }
139}
140
141/// Every injection a mod declares (`yog_mod_nanites`).
142#[derive(Schema, Debug, Clone, Default, PartialEq, Eq)]
143pub struct Nanites {
144    pub injects: Vec<Inject>,
145}
146
147/// A nanite's point reached (keyed by its point): the object (null in a
148/// static method), the arguments (a redirected call's, the receiver first),
149/// the value at stake. Handlers change it: `cancelled` stops the method,
150/// `returned` makes `value` the answer.
151#[derive(Schema, Debug, Clone, Default, PartialEq)]
152pub struct Injected {
153    pub point: String,
154    pub this: Value,
155    pub args: Vec<Value>,
156    /// The value at stake: the return value, a `modify_*`'s value.
157    pub value: Value,
158    pub cancelled: bool,
159    /// `value` is the answer (a return value, a call's result, a modified value).
160    pub returned: bool,
161    /// The locals the inject asked for; a handler changes those it may write.
162    pub locals: Vec<Value>,
163    /// A redirect's original call, an object (`java.util.function.Function`
164    /// of its arguments) a handler may call instead of answering.
165    pub original: Value,
166}