fastnoise2/
metadata.rs

1use std::{any::type_name, collections::HashMap, ffi::CStr, sync::LazyLock};
2
3use fastnoise2_sys::*;
4
5use crate::{FastNoiseError, Node};
6
7#[derive(Debug)]
8pub(crate) struct Metadata {
9    #[allow(dead_code)]
10    pub id: i32,
11    #[allow(dead_code)]
12    pub name: String,
13    pub members: HashMap<String, Member>,
14}
15
16#[derive(Debug, Clone)]
17pub struct Member {
18    pub name: String,
19    pub member_type: MemberType,
20    pub index: i32,
21    pub enum_names: HashMap<String, i32>,
22}
23
24/// Defines the type of value or reference a node can handle.
25#[derive(Clone, Copy, Debug)]
26pub enum MemberType {
27    /// A floating-point number ([`f32`]).
28    Float,
29    /// An integer ([`i32`]).
30    Int,
31    /// An enumerated value represented as a string ([`&str`]).
32    Enum,
33    /// A reference to a [`Node`] instance.
34    NodeLookup,
35    /// A member that can be either a floating-point value ([`f32`]) or a [`Node`] reference.
36    Hybrid,
37}
38
39impl std::fmt::Display for MemberType {
40    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41        match self {
42            Self::Float => f.write_str(type_name::<f32>()),
43            Self::Int => f.write_str(type_name::<i32>()),
44            Self::Enum => f.write_str(type_name::<&str>()),
45            Self::NodeLookup => f.write_str(type_name::<&Node>()),
46            Self::Hybrid => f.write_fmt(format_args!("{} or {}", Self::Float, Self::NodeLookup)),
47        }
48    }
49}
50
51pub(crate) static METADATA_NAME_LOOKUP: LazyLock<HashMap<String, i32>> = LazyLock::new(|| {
52    let metadata_count = unsafe { fnGetMetadataCount() };
53    let mut lookup = HashMap::new();
54
55    for id in 0..metadata_count {
56        let name =
57            format_lookup(&unsafe { CStr::from_ptr(fnGetMetadataName(id)) }.to_string_lossy());
58        lookup.insert(name, id);
59    }
60    lookup
61});
62
63pub(crate) static NODE_METADATA: LazyLock<Vec<Metadata>> = LazyLock::new(|| {
64    let metadata_count = unsafe { fnGetMetadataCount() };
65    let mut metadata_vec = Vec::with_capacity(metadata_count as usize);
66    for id in 0..metadata_count {
67        let name =
68            format_lookup(&unsafe { CStr::from_ptr(fnGetMetadataName(id)) }.to_string_lossy());
69        let mut members = HashMap::new();
70
71        let variable_count = unsafe { fnGetMetadataVariableCount(id) };
72        let node_lookup_count = unsafe { fnGetMetadataNodeLookupCount(id) };
73        let hybrid_count = unsafe { fnGetMetadataHybridCount(id) };
74
75        for variable_idx in 0..variable_count {
76            let member_type = match unsafe { fnGetMetadataVariableType(id, variable_idx) } {
77                0 => MemberType::Float,
78                1 => MemberType::Int,
79                2 => MemberType::Enum,
80                _ => MemberType::Hybrid,
81            };
82            let dimension_idx = unsafe { fnGetMetadataVariableDimensionIdx(id, variable_idx) };
83            let name = format_dimension_member(
84                &format_lookup(
85                    &unsafe { CStr::from_ptr(fnGetMetadataVariableName(id, variable_idx)) }
86                        .to_string_lossy(),
87                ),
88                dimension_idx,
89            );
90            let mut enum_names = HashMap::new();
91            if let MemberType::Enum = member_type {
92                let enum_count = unsafe { fnGetMetadataEnumCount(id, variable_idx) };
93                for enum_idx in 0..enum_count {
94                    let enum_name = format_lookup(
95                        &unsafe {
96                            CStr::from_ptr(fnGetMetadataEnumName(id, variable_idx, enum_idx))
97                        }
98                        .to_string_lossy(),
99                    );
100                    enum_names.insert(enum_name, enum_idx);
101                }
102            }
103            members.insert(
104                name.clone(),
105                Member {
106                    name,
107                    member_type,
108                    index: variable_idx,
109                    enum_names,
110                },
111            );
112        }
113
114        for node_lookup_idx in 0..node_lookup_count {
115            let dimension_idx = unsafe { fnGetMetadataNodeLookupDimensionIdx(id, node_lookup_idx) };
116            let name = format_dimension_member(
117                &format_lookup(
118                    &unsafe { CStr::from_ptr(fnGetMetadataNodeLookupName(id, node_lookup_idx)) }
119                        .to_string_lossy(),
120                ),
121                dimension_idx,
122            );
123            members.insert(
124                name.clone(),
125                Member {
126                    name,
127                    member_type: MemberType::NodeLookup,
128                    index: node_lookup_idx,
129                    enum_names: HashMap::new(),
130                },
131            );
132        }
133
134        for hybrid_idx in 0..hybrid_count {
135            let dimension_idx = unsafe { fnGetMetadataHybridDimensionIdx(id, hybrid_idx) };
136            let name = format_dimension_member(
137                &format_lookup(
138                    &unsafe { CStr::from_ptr(fnGetMetadataHybridName(id, hybrid_idx)) }
139                        .to_string_lossy(),
140                ),
141                dimension_idx,
142            );
143            members.insert(
144                name.clone(),
145                Member {
146                    name,
147                    member_type: MemberType::Hybrid,
148                    index: hybrid_idx,
149                    enum_names: HashMap::new(),
150                },
151            );
152        }
153
154        metadata_vec.push(Metadata { id, name, members });
155    }
156    metadata_vec
157});
158
159pub(crate) fn format_lookup(name: &str) -> String {
160    name.replace(" ", "").to_lowercase()
161}
162
163fn format_dimension_member(name: &str, dim_idx: i32) -> String {
164    if dim_idx >= 0 {
165        let dim_suffix = ['x', 'y', 'z', 'w'];
166        let suffix = dim_suffix[dim_idx as usize];
167        format!("{name}{suffix}")
168    } else {
169        name.to_string()
170    }
171}
172
173pub trait MemberValue {
174    const TYPE: MemberType;
175
176    fn apply(&self, node: &mut Node, member: &Member) -> Result<(), FastNoiseError>;
177
178    fn invalid_member_type_error(member: &Member) -> FastNoiseError {
179        FastNoiseError::InvalidMemberType {
180            member_name: member.name.clone(),
181            expected: member.member_type,
182            found: Self::TYPE,
183        }
184    }
185}
186
187impl MemberValue for f32 {
188    const TYPE: MemberType = MemberType::Float;
189
190    fn apply(&self, node: &mut Node, member: &Member) -> Result<(), FastNoiseError> {
191        match member.member_type {
192            MemberType::Float => {
193                if !unsafe { fnSetVariableFloat(node.handle, member.index, *self) } {
194                    return Err(FastNoiseError::SetFloatFailed);
195                }
196            }
197            MemberType::Hybrid => {
198                if !unsafe { fnSetHybridFloat(node.handle, member.index, *self) } {
199                    return Err(FastNoiseError::SetHybridFloatFailed);
200                }
201            }
202            _ => return Err(Self::invalid_member_type_error(member)),
203        }
204        Ok(())
205    }
206}
207
208impl MemberValue for i32 {
209    const TYPE: MemberType = MemberType::Int;
210
211    fn apply(&self, node: &mut Node, member: &Member) -> Result<(), FastNoiseError> {
212        match member.member_type {
213            MemberType::Int => {
214                if !unsafe { fnSetVariableIntEnum(node.handle, member.index, *self) } {
215                    return Err(FastNoiseError::SetIntFailed);
216                }
217            }
218            _ => return Err(Self::invalid_member_type_error(member)),
219        }
220        Ok(())
221    }
222}
223
224impl MemberValue for &str {
225    const TYPE: MemberType = MemberType::Enum;
226
227    fn apply(&self, node: &mut Node, member: &Member) -> Result<(), FastNoiseError> {
228        match member.member_type {
229            MemberType::Enum => {
230                let enum_idx = member.enum_names.get(&format_lookup(self)).ok_or_else(|| {
231                    FastNoiseError::EnumValueNotFound {
232                        expected: member.enum_names.keys().cloned().collect(),
233                        found: self.to_string(),
234                    }
235                })?;
236                if !unsafe { fnSetVariableIntEnum(node.handle, member.index, *enum_idx) } {
237                    return Err(FastNoiseError::SetEnumFailed);
238                }
239            }
240            _ => return Err(Self::invalid_member_type_error(member)),
241        }
242        Ok(())
243    }
244}
245
246impl MemberValue for &Node {
247    const TYPE: MemberType = MemberType::NodeLookup;
248
249    fn apply(&self, node: &mut Node, member: &Member) -> Result<(), FastNoiseError> {
250        match member.member_type {
251            MemberType::NodeLookup => {
252                // C API expects pointer to SmartNode-like structure (pointer at offset 0)
253                // Pass address of handle so C can dereference to get the Generator*
254                if !unsafe {
255                    fnSetNodeLookup(
256                        node.handle,
257                        member.index,
258                        &self.handle as *const _ as *const _,
259                    )
260                } {
261                    return Err(FastNoiseError::SetNodeLookupFailed);
262                }
263            }
264            MemberType::Hybrid => {
265                // Same fix for hybrid node lookups
266                if !unsafe {
267                    fnSetHybridNodeLookup(
268                        node.handle,
269                        member.index,
270                        &self.handle as *const _ as *const _,
271                    )
272                } {
273                    return Err(FastNoiseError::SetHybridNodeLookupFailed);
274                }
275            }
276            _ => return Err(Self::invalid_member_type_error(member)),
277        }
278        Ok(())
279    }
280}