Skip to main content

rusteal_codegen/
context.rs

1// Build context: type lookups, module mapping, FuncId assignment.
2
3use std::collections::{BTreeMap, HashMap, HashSet};
4
5use crate::config::CodegenConfig;
6use crate::schema::{
7    ClassInfo, EnumInfo, FunctionInfo, PropertyInfo, StructInfo, FUNC_BLUEPRINT_EVENT, FUNC_NATIVE,
8    FUNC_STATIC,
9};
10
11/// Central build context for the codegen pipeline.
12pub struct CodegenContext {
13    /// All classes by name.
14    pub classes: HashMap<String, ClassInfo>,
15    /// All structs by name.
16    pub structs: HashMap<String, StructInfo>,
17    /// All enums by name.
18    pub enums: HashMap<String, EnumInfo>,
19
20    /// Package name → Rust module name.
21    pub package_to_module: HashMap<String, String>,
22    /// Rust module name → Cargo feature name.
23    pub module_to_feature: HashMap<String, String>,
24    /// Set of enabled Rust module names (derived from features).
25    pub enabled_modules: HashSet<String>,
26
27    /// Classes grouped by module name. Sorted for determinism.
28    pub module_classes: BTreeMap<String, Vec<ClassInfo>>,
29    /// Structs grouped by module name.
30    pub module_structs: BTreeMap<String, Vec<StructInfo>>,
31    /// Enums grouped by module name.
32    pub module_enums: BTreeMap<String, Vec<EnumInfo>>,
33
34    /// All exportable functions sorted by (module, class, func) for FuncId assignment.
35    pub func_table: Vec<FuncEntry>,
36
37    /// Module name → set of other modules whose types it references.
38    /// Drives feature dependency emission in the generated `rusteal-bindings/Cargo.toml`.
39    pub module_deps: BTreeMap<String, std::collections::BTreeSet<String>>,
40}
41
42/// An entry in the global function table.
43#[derive(Clone)]
44pub struct FuncEntry {
45    pub func_id: u32,
46    pub module_name: String,
47    pub class_name: String,
48    pub func_name: String,
49    /// The actual resolved Rust function name (may include _1, _2 suffix for overloads).
50    pub rust_func_name: String,
51    pub func: FunctionInfo,
52    /// The C++ class name (with prefix, e.g. "AActor").
53    pub cpp_class_name: String,
54    /// The header file to include.
55    pub header: String,
56}
57
58impl CodegenContext {
59    pub fn new(
60        classes: Vec<ClassInfo>,
61        structs: Vec<StructInfo>,
62        enums: Vec<EnumInfo>,
63        config: &CodegenConfig,
64    ) -> Self {
65        // Build package→module and module→feature maps from config
66        let mut package_to_module = HashMap::new();
67        let mut module_to_feature_map = HashMap::new();
68
69        for (pkg, mapping) in &config.modules {
70            package_to_module.insert(pkg.clone(), mapping.module.clone());
71            module_to_feature_map.insert(mapping.module.clone(), mapping.feature.clone());
72        }
73
74        // Build enabled modules from features
75        let enabled_features: HashSet<&str> = config.features.iter().map(|s| s.as_str()).collect();
76        let mut enabled_modules = HashSet::new();
77        for (pkg, mapping) in &config.modules {
78            if enabled_features.contains(mapping.feature.as_str()) {
79                enabled_modules.insert(mapping.module.clone());
80                // Also ensure the package mapping exists for auto-derived packages
81                package_to_module.entry(pkg.clone()).or_insert_with(|| mapping.module.clone());
82            }
83        }
84
85        // Auto-derive module names for packages not in config
86        let mut all_packages = HashSet::new();
87        for c in &classes {
88            all_packages.insert(c.package.clone());
89        }
90        for s in &structs {
91            all_packages.insert(s.package.clone());
92        }
93        for e in &enums {
94            all_packages.insert(e.package.clone());
95        }
96
97        for pkg in &all_packages {
98            if !package_to_module.contains_key(pkg) {
99                let module = crate::naming::to_snake_case(pkg);
100                package_to_module.insert(pkg.clone(), module);
101            }
102        }
103
104        // Group types by module
105        let mut module_classes: BTreeMap<String, Vec<ClassInfo>> = BTreeMap::new();
106        let mut module_structs: BTreeMap<String, Vec<StructInfo>> = BTreeMap::new();
107        let mut module_enums: BTreeMap<String, Vec<EnumInfo>> = BTreeMap::new();
108
109        let mut classes_map = HashMap::new();
110        for c in classes {
111            if let Some(module) = package_to_module.get(&c.package)
112                && enabled_modules.contains(module)
113            {
114                classes_map.insert(c.name.clone(), c.clone());
115                module_classes.entry(module.clone()).or_default().push(c);
116            }
117        }
118
119        let mut structs_map = HashMap::new();
120        for s in structs {
121            if let Some(module) = package_to_module.get(&s.package)
122                && enabled_modules.contains(module)
123            {
124                structs_map.insert(s.name.clone(), s.clone());
125                module_structs.entry(module.clone()).or_default().push(s);
126            }
127        }
128
129        let mut enums_map = HashMap::new();
130        for e in enums {
131            if let Some(module) = package_to_module.get(&e.package)
132                && enabled_modules.contains(module)
133            {
134                enums_map.insert(e.name.clone(), e.clone());
135                module_enums.entry(module.clone()).or_default().push(e);
136            }
137        }
138
139        // Sort within each module for determinism
140        for classes in module_classes.values_mut() {
141            classes.sort_by(|a, b| a.name.cmp(&b.name));
142        }
143        for structs in module_structs.values_mut() {
144            structs.sort_by(|a, b| a.name.cmp(&b.name));
145        }
146        for enums in module_enums.values_mut() {
147            enums.sort_by(|a, b| a.name.cmp(&b.name));
148        }
149
150        add_interface_functions(&mut module_classes, &mut classes_map);
151
152        let mut ctx = CodegenContext {
153            classes: classes_map,
154            structs: structs_map,
155            enums: enums_map,
156            package_to_module,
157            module_to_feature: module_to_feature_map,
158            enabled_modules,
159            module_classes,
160            module_structs,
161            module_enums,
162            func_table: Vec::new(),
163            module_deps: BTreeMap::new(),
164        };
165        ctx.module_deps = ctx.compute_module_deps();
166        ctx
167    }
168
169    /// Walk all enabled types and record cross-module type references.
170    ///
171    /// For each (module M, type T in M), if T references a type owned by
172    /// another module M', record `M depends on M'`. The resulting graph
173    /// drives feature dependency emission in `rusteal-bindings/Cargo.toml`,
174    /// ensuring `cargo check` succeeds at any enabled feature combination.
175    fn compute_module_deps(&self) -> BTreeMap<String, std::collections::BTreeSet<String>> {
176        let mut deps: BTreeMap<String, std::collections::BTreeSet<String>> = BTreeMap::new();
177        for m in &self.enabled_modules {
178            deps.entry(m.clone()).or_default();
179        }
180
181        for (module, classes) in &self.module_classes {
182            for class in classes {
183                if let Some(parent) = &class.super_class
184                    && let Some(pc) = self.classes.get(parent.as_str())
185                {
186                    self.record_dep(module, &pc.package, &mut deps);
187                }
188                for iface in &class.interfaces {
189                    if let Some(ic) = self.classes.get(interface_class_name(iface)) {
190                        self.record_dep(module, &ic.package, &mut deps);
191                    }
192                }
193                for prop in &class.props {
194                    self.walk_prop_deps(prop, module, &mut deps);
195                }
196                for func in &class.funcs {
197                    for p in &func.params {
198                        self.walk_param_deps(p, module, &mut deps);
199                    }
200                }
201            }
202        }
203
204        for (module, structs) in &self.module_structs {
205            for s in structs {
206                if let Some(parent) = &s.super_struct
207                    && let Some(ps) = self.structs.get(parent.as_str())
208                {
209                    self.record_dep(module, &ps.package, &mut deps);
210                }
211                for prop in &s.props {
212                    self.walk_prop_deps(prop, module, &mut deps);
213                }
214            }
215        }
216
217        deps
218    }
219
220    fn record_dep(
221        &self,
222        current_module: &str,
223        target_package: &str,
224        deps: &mut BTreeMap<String, std::collections::BTreeSet<String>>,
225    ) {
226        if let Some(target_module) = self.package_to_module.get(target_package)
227            && target_module != current_module && self.enabled_modules.contains(target_module)
228        {
229            deps.entry(current_module.to_string())
230                .or_default()
231                .insert(target_module.clone());
232        }
233    }
234
235    fn walk_prop_deps(
236        &self,
237        prop: &PropertyInfo,
238        current_module: &str,
239        deps: &mut BTreeMap<String, std::collections::BTreeSet<String>>,
240    ) {
241        if let Some(en) = &prop.enum_name
242            && let Some(e) = self.enums.get(en.as_str())
243        {
244            self.record_dep(current_module, &e.package, deps);
245        }
246        if let Some(cn) = &prop.class_name
247            && let Some(c) = self.classes.get(cn.as_str())
248        {
249            self.record_dep(current_module, &c.package, deps);
250        }
251        if let Some(mc) = &prop.meta_class_name
252            && let Some(c) = self.classes.get(mc.as_str())
253        {
254            self.record_dep(current_module, &c.package, deps);
255        }
256        if let Some(sn) = &prop.struct_name
257            && let Some(s) = self.structs.get(sn.as_str())
258        {
259            self.record_dep(current_module, &s.package, deps);
260        }
261        if let Some(in_) = &prop.interface_name
262            && let Some(c) = self.classes.get(in_.as_str())
263        {
264            self.record_dep(current_module, &c.package, deps);
265        }
266        if let Some(fi) = &prop.func_info {
267            self.walk_delegate_func_info_deps(fi, current_module, deps);
268        }
269        if let Some(inner) = &prop.inner_prop {
270            self.walk_prop_deps(inner, current_module, deps);
271        }
272        if let Some(key) = &prop.key_prop {
273            self.walk_prop_deps(key, current_module, deps);
274        }
275        if let Some(value) = &prop.value_prop {
276            self.walk_prop_deps(value, current_module, deps);
277        }
278        if let Some(element) = &prop.element_prop {
279            self.walk_prop_deps(element, current_module, deps);
280        }
281    }
282
283    fn walk_param_deps(
284        &self,
285        param: &crate::schema::ParamInfo,
286        current_module: &str,
287        deps: &mut BTreeMap<String, std::collections::BTreeSet<String>>,
288    ) {
289        if let Some(en) = &param.enum_name
290            && let Some(e) = self.enums.get(en.as_str())
291        {
292            self.record_dep(current_module, &e.package, deps);
293        }
294        if let Some(cn) = &param.class_name
295            && let Some(c) = self.classes.get(cn.as_str())
296        {
297            self.record_dep(current_module, &c.package, deps);
298        }
299        if let Some(mc) = &param.meta_class_name
300            && let Some(c) = self.classes.get(mc.as_str())
301        {
302            self.record_dep(current_module, &c.package, deps);
303        }
304        if let Some(sn) = &param.struct_name
305            && let Some(s) = self.structs.get(sn.as_str())
306        {
307            self.record_dep(current_module, &s.package, deps);
308        }
309        if let Some(in_) = &param.interface_name
310            && let Some(c) = self.classes.get(in_.as_str())
311        {
312            self.record_dep(current_module, &c.package, deps);
313        }
314        if let Some(inner) = &param.inner_prop {
315            self.walk_prop_deps(inner, current_module, deps);
316        }
317        if let Some(key) = &param.key_prop {
318            self.walk_prop_deps(key, current_module, deps);
319        }
320        if let Some(value) = &param.value_prop {
321            self.walk_prop_deps(value, current_module, deps);
322        }
323        if let Some(element) = &param.element_prop {
324            self.walk_prop_deps(element, current_module, deps);
325        }
326    }
327
328    fn walk_delegate_func_info_deps(
329        &self,
330        fi: &serde_json::Value,
331        current_module: &str,
332        deps: &mut BTreeMap<String, std::collections::BTreeSet<String>>,
333    ) {
334        let Some(params) = fi.get("params").and_then(|p| p.as_array()) else { return };
335        for p in params {
336            if let Some(en) = p.get("enum_name").and_then(|v| v.as_str())
337                && let Some(e) = self.enums.get(en)
338            {
339                self.record_dep(current_module, &e.package, deps);
340            }
341            if let Some(cn) = p.get("class_name").and_then(|v| v.as_str())
342                && let Some(c) = self.classes.get(cn)
343            {
344                self.record_dep(current_module, &c.package, deps);
345            }
346            if let Some(mc) = p.get("meta_class_name").and_then(|v| v.as_str())
347                && let Some(c) = self.classes.get(mc)
348            {
349                self.record_dep(current_module, &c.package, deps);
350            }
351            if let Some(sn) = p.get("struct_name").and_then(|v| v.as_str())
352                && let Some(s) = self.structs.get(sn)
353            {
354                self.record_dep(current_module, &s.package, deps);
355            }
356            if let Some(in_) = p.get("interface_name").and_then(|v| v.as_str())
357                && let Some(c) = self.classes.get(in_)
358            {
359                self.record_dep(current_module, &c.package, deps);
360            }
361        }
362    }
363
364    /// Check if a type (class, struct, or enum) exists in an enabled module.
365    #[allow(dead_code)]
366    pub fn is_type_available(&self, type_name: &str) -> bool {
367        self.classes.contains_key(type_name)
368            || self.structs.contains_key(type_name)
369            || self.enums.contains_key(type_name)
370    }
371
372    /// Get the module name for a package.
373    #[allow(dead_code)]
374    pub fn module_for_package(&self, package: &str) -> Option<&str> {
375        self.package_to_module.get(package).map(|s| s.as_str())
376    }
377
378    /// Get the Cargo feature name for a module.
379    pub fn feature_for_module(&self, module: &str) -> Option<&str> {
380        self.module_to_feature.get(module).map(|s| s.as_str())
381    }
382
383    /// Get the actual Rust repr type for an enum, accounting for signed promotion.
384    /// This must match the logic in `rust_gen/enums.rs`.
385    pub fn enum_actual_repr(&self, enum_name: &str) -> Option<&'static str> {
386        let e = self.enums.get(enum_name)?;
387        let has_negative = e.pairs.iter().any(|(_, v)| *v < 0);
388        Some(if has_negative {
389            match e.underlying_type.as_str() {
390                "uint8" => "i8",
391                "int8" => "i8",
392                "uint16" => "i16",
393                "int16" => "i16",
394                "uint32" => "i32",
395                "int32" => "i32",
396                "uint64" => "i64",
397                "int64" => "i64",
398                _ => "i8",
399            }
400        } else {
401            match e.underlying_type.as_str() {
402                "uint8" => "u8",
403                "int8" => "i8",
404                "uint16" => "u16",
405                "int16" => "i16",
406                "uint32" => "u32",
407                "int32" => "i32",
408                "uint64" => "u64",
409                "int64" => "i64",
410                _ => "u8",
411            }
412        })
413    }
414}
415
416/// The reflected class name of a native interface listed in `ClassInfo::interfaces`:
417/// the exporter lists the `I`-prefixed native type (`IEnhancedInputSubsystemInterface`),
418/// the class is exported under its `UInterface` name (`EnhancedInputSubsystemInterface`).
419pub fn interface_class_name(native: &str) -> &str {
420    native.strip_prefix('I').unwrap_or(native)
421}
422
423/// Give each class the callable functions of the interfaces it implements.
424///
425/// Interfaces get no wrappers of their own, so a `UFUNCTION` declared only on an
426/// interface (`IEnhancedInputSubsystemInterface::AddMappingContext`) would be
427/// unreachable. It is copied to every class listing the interface, and the C++
428/// wrapper calls it through the interface type. Blueprint events are left out:
429/// on an interface they are only callable through the static `Execute_` thunks.
430/// Only interfaces in enabled modules are considered.
431fn add_interface_functions(
432    module_classes: &mut BTreeMap<String, Vec<ClassInfo>>,
433    classes_map: &mut HashMap<String, ClassInfo>,
434) {
435    let interfaces: HashMap<String, ClassInfo> = classes_map
436        .values()
437        .filter(|c| c.super_class.as_deref() == Some("Interface"))
438        .map(|c| (c.name.clone(), c.clone()))
439        .collect();
440
441    for classes in module_classes.values_mut() {
442        for class in classes.iter_mut() {
443            if class.super_class.as_deref() == Some("Interface") {
444                continue;
445            }
446            let mut added = Vec::new();
447            for native in &class.interfaces {
448                let Some(iface) = interfaces.get(interface_class_name(native)) else {
449                    continue;
450                };
451                for func in &iface.funcs {
452                    let callable = func.func_flags & FUNC_NATIVE != 0
453                        && func.func_flags & FUNC_BLUEPRINT_EVENT == 0
454                        && func.func_flags & FUNC_STATIC == 0
455                        && !func.is_static;
456                    let taken = class.funcs.iter().chain(&added).any(|f: &FunctionInfo| f.name == func.name);
457                    if callable && !taken {
458                        let mut func = func.clone();
459                        func.interface = Some(native.clone());
460                        added.push(func);
461                    }
462                }
463            }
464            if added.is_empty() {
465                continue;
466            }
467            class.funcs.extend(added);
468            if let Some(mapped) = classes_map.get_mut(&class.name) {
469                mapped.funcs = class.funcs.clone();
470            }
471        }
472    }
473}