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