1use crate::context::CodegenContext;
11use crate::naming::to_snake_case;
12use crate::schema::PropertyInfo;
13use crate::type_map::{self, ConversionKind};
14
15pub struct DelegateInfo<'a> {
17 pub prop: &'a PropertyInfo,
18 pub class_name: &'a str,
19 pub rust_name: String,
21 pub struct_name: String,
23 pub is_multicast: bool,
25 pub params: Vec<DelegateParam>,
27}
28
29pub struct DelegateParam {
31 pub name: String,
32 pub rust_type: String,
33 pub conversion: ParamConversion,
34}
35
36pub enum ParamConversion {
38 Primitive(String),
40 ObjectRef(String),
42 Enum { rust_type: String, repr: String },
44 FName,
46 String,
48}
49
50pub fn collect_delegate_props<'a>(
52 props: &'a [PropertyInfo],
53 class_name: &'a str,
54 ctx: &CodegenContext,
55) -> Vec<DelegateInfo<'a>> {
56 let mut result = Vec::new();
57
58 for prop in props {
59 let mapped = type_map::map_property_type(
60 &prop.prop_type,
61 prop.class_name.as_deref(),
62 prop.struct_name.as_deref(),
63 prop.enum_name.as_deref(),
64 prop.enum_underlying_type.as_deref(),
65 prop.meta_class_name.as_deref(),
66 prop.interface_name.as_deref(),
67 );
68 if !matches!(
69 mapped.rust_to_ffi,
70 ConversionKind::Delegate | ConversionKind::MulticastDelegate
71 ) {
72 continue;
73 }
74
75 let is_multicast = matches!(mapped.rust_to_ffi, ConversionKind::MulticastDelegate);
76
77 let func_info = match &prop.func_info {
79 Some(fi) => fi,
80 None => continue,
81 };
82 let params_json = match func_info.get("params").and_then(|p| p.as_array()) {
83 Some(params) => params,
84 None => &Vec::new() as &Vec<serde_json::Value>,
85 };
86
87 let mut params = Vec::new();
88 let mut all_supported = true;
89
90 for param_value in params_json {
91 let param_name = param_value
92 .get("name")
93 .and_then(|n| n.as_str())
94 .unwrap_or("unknown");
95 let param_type = match param_value.get("type").and_then(|t| t.as_str()) {
96 Some(t) => t,
97 None => {
98 all_supported = false;
99 break;
100 }
101 };
102
103 match resolve_delegate_param(param_name, param_type, param_value, ctx) {
104 Some(dp) => params.push(dp),
105 None => {
106 all_supported = false;
107 break;
108 }
109 }
110 }
111
112 if !all_supported {
113 continue;
114 }
115
116 let rust_name = to_snake_case(&prop.name);
117 let struct_name = format!("{}{}Delegate", class_name, prop.name);
118
119 result.push(DelegateInfo {
120 prop,
121 class_name,
122 rust_name,
123 struct_name,
124 is_multicast,
125 params,
126 });
127 }
128
129 result
130}
131
132fn resolve_delegate_param(
133 name: &str,
134 prop_type: &str,
135 value: &serde_json::Value,
136 ctx: &CodegenContext,
137) -> Option<DelegateParam> {
138 let param_name = to_snake_case(name);
139
140 match prop_type {
141 "BoolProperty" => Some(DelegateParam {
142 name: param_name,
143 rust_type: "bool".into(),
144 conversion: ParamConversion::Primitive("bool".into()),
145 }),
146 "Int8Property" => Some(DelegateParam {
147 name: param_name,
148 rust_type: "i8".into(),
149 conversion: ParamConversion::Primitive("i8".into()),
150 }),
151 "ByteProperty" => {
152 if let Some(en) = value.get("enum_name").and_then(|v| v.as_str()) {
153 if ctx.enums.contains_key(en) {
154 Some(DelegateParam {
155 name: param_name,
156 rust_type: en.to_string(),
157 conversion: ParamConversion::Enum {
158 rust_type: en.to_string(),
159 repr: ctx
160 .enum_actual_repr(en)
161 .unwrap_or("u8")
162 .to_string(),
163 },
164 })
165 } else {
166 None
167 }
168 } else {
169 Some(DelegateParam {
170 name: param_name,
171 rust_type: "u8".into(),
172 conversion: ParamConversion::Primitive("u8".into()),
173 })
174 }
175 }
176 "Int16Property" => prim_param(param_name, "i16"),
177 "UInt16Property" => prim_param(param_name, "u16"),
178 "IntProperty" => prim_param(param_name, "i32"),
179 "UInt32Property" => prim_param(param_name, "u32"),
180 "Int64Property" => prim_param(param_name, "i64"),
181 "UInt64Property" => prim_param(param_name, "u64"),
182 "FloatProperty" => prim_param(param_name, "f32"),
183 "DoubleProperty" => prim_param(param_name, "f64"),
184 "NameProperty" => Some(DelegateParam {
185 name: param_name,
186 rust_type: "uika_runtime::FNameHandle".into(),
187 conversion: ParamConversion::FName,
188 }),
189 "StrProperty" | "TextProperty" => Some(DelegateParam {
190 name: param_name,
191 rust_type: "String".into(),
192 conversion: ParamConversion::String,
193 }),
194 "ObjectProperty" | "ClassProperty" => {
195 let cls = value.get("class_name").and_then(|v| v.as_str());
196 if let Some(cls) = cls {
197 if ctx.classes.contains_key(cls) {
198 Some(DelegateParam {
199 name: param_name,
200 rust_type: format!("uika_runtime::UObjectRef<{cls}>"),
201 conversion: ParamConversion::ObjectRef(cls.to_string()),
202 })
203 } else {
204 None
205 }
206 } else {
207 Some(DelegateParam {
208 name: param_name,
209 rust_type: "uika_runtime::UObjectHandle".into(),
210 conversion: ParamConversion::Primitive("uika_runtime::UObjectHandle".into()),
211 })
212 }
213 }
214 "EnumProperty" => {
215 let en = value.get("enum_name").and_then(|v| v.as_str())?;
216 if !ctx.enums.contains_key(en) {
217 return None;
218 }
219 Some(DelegateParam {
220 name: param_name,
221 rust_type: en.to_string(),
222 conversion: ParamConversion::Enum {
223 rust_type: en.to_string(),
224 repr: ctx.enum_actual_repr(en).unwrap_or("u8").to_string(),
225 },
226 })
227 }
228 "StructProperty" => {
229 None
232 }
233 _ => None,
234 }
235}
236
237fn prim_param(name: String, ty: &str) -> Option<DelegateParam> {
238 Some(DelegateParam {
239 name,
240 rust_type: ty.into(),
241 conversion: ParamConversion::Primitive(ty.into()),
242 })
243}
244
245pub fn generate_delegate_impls(
251 out: &mut String,
252 delegates: &[DelegateInfo],
253) {
254 for d in delegates {
255 let rust_name = &d.rust_name;
256 let struct_name = &d.struct_name;
257 let class_name = d.class_name;
258 let prop_name = &d.prop.name;
259 let prop_name_len = prop_name.len();
260 let byte_lit = format!("b\"{}\\0\"", prop_name);
261
262 out.push_str(&format!(
263 " fn {rust_name}(&self) -> {struct_name} {{\n\
264 \x20 static PROP: std::sync::OnceLock<uika_runtime::FPropertyHandle> = std::sync::OnceLock::new();\n\
265 \x20 let prop = *PROP.get_or_init(|| unsafe {{\n\
266 \x20 ((*uika_runtime::api().reflection).find_property)(\n\
267 \x20 {class_name}::static_class(), {byte_lit}.as_ptr(), {prop_name_len}\n\
268 \x20 )\n\
269 \x20 }});\n\
270 \x20 {struct_name} {{ owner: self.handle(), prop }}\n\
271 \x20 }}\n\n"
272 ));
273 }
274}
275
276pub fn generate_delegate_structs(
279 out: &mut String,
280 delegates: &[DelegateInfo],
281 class_name: &str,
282) {
283 for d in delegates {
284 let struct_name = &d.struct_name;
285 let is_multicast = d.is_multicast;
286
287 out.push_str(&format!(
288 "pub struct {struct_name} {{\n\
289 \x20 pub owner: uika_runtime::UObjectHandle,\n\
290 \x20 pub prop: uika_runtime::FPropertyHandle,\n\
291 }}\n\n"
292 ));
293
294 let sig_name = d.prop.func_info.as_ref()
296 .and_then(|fi| fi.get("name"))
297 .and_then(|n| n.as_str())
298 .unwrap_or(&d.prop.name);
299 let sig_name_len = sig_name.len();
300 let sig_byte_lit = format!("b\"{}\\0\"", sig_name);
301
302 let callback_params: Vec<String> = d.params.iter().map(|p| p.rust_type.clone()).collect();
304 let callback_sig = callback_params.join(", ");
305
306 let method_name = if is_multicast { "add" } else { "bind" };
307 let api_fn = if is_multicast { "bind_multicast" } else { "bind_unicast" };
308
309 out.push_str(&format!(
310 "impl {struct_name} {{\n"
311 ));
312
313 out.push_str(&format!(
315 " pub fn {method_name}(&self, mut callback: impl FnMut({callback_sig}) + Send + 'static) -> uika_runtime::UikaResult<uika_runtime::DelegateBinding> {{\n"
316 ));
317
318 if !d.params.is_empty() {
320 let n_params = d.params.len();
321 out.push_str(&format!(
322 " static OFFSETS: std::sync::OnceLock<[u32; {n_params}]> = std::sync::OnceLock::new();\n\
323 \x20 let offsets = OFFSETS.get_or_init(|| unsafe {{\n\
324 \x20 let sig_func = ((*uika_runtime::api().reflection).find_function_by_class)(\n\
325 \x20 {class_name}::static_class(),\n\
326 \x20 {sig_byte_lit}.as_ptr(), {sig_name_len});\n\
327 \x20 [\n"
328 ));
329
330 for p in &d.params {
331 let param_ue_name = &d.prop.func_info.as_ref()
332 .and_then(|fi| fi.get("params"))
333 .and_then(|ps| ps.as_array())
334 .and_then(|arr| arr.iter().find(|v| {
335 v.get("name").and_then(|n| n.as_str()).map(|n| to_snake_case(n)) == Some(p.name.clone())
336 }))
337 .and_then(|v| v.get("name"))
338 .and_then(|n| n.as_str())
339 .unwrap_or(&p.name);
340 let pname_len = param_ue_name.len();
341 let pname_lit = format!("b\"{}\\0\"", param_ue_name);
342 out.push_str(&format!(
343 " {{\n\
344 \x20 let param_prop = ((*uika_runtime::api().reflection).get_function_param)(\n\
345 \x20 sig_func, {pname_lit}.as_ptr(), {pname_len});\n\
346 \x20 ((*uika_runtime::api().reflection).get_property_offset)(param_prop)\n\
347 \x20 }},\n"
348 ));
349 }
350
351 out.push_str(
352 " ]\n\
353 \x20 });\n\
354 \x20 #[allow(unused_variables)] let offsets = offsets;\n"
355 );
356 }
357
358 if d.params.is_empty() {
360 out.push_str(&format!(
361 " let owner = self.owner;\n\
362 \x20 let prop = self.prop;\n\
363 \x20 uika_runtime::delegate_registry::{api_fn}(owner, prop, move |_params: *mut u8| {{\n\
364 \x20 callback();\n\
365 \x20 }})\n\
366 \x20 }}\n"
367 ));
368 out.push_str("}\n\n");
369 continue;
370 }
371
372 let needs_unsafe = d.params.iter().any(|p| !matches!(p.conversion, ParamConversion::String));
374 let params_var = if needs_unsafe { "params" } else { "_params" };
375
376 out.push_str(&format!(
377 " let owner = self.owner;\n\
378 \x20 let prop = self.prop;\n\
379 \x20 uika_runtime::delegate_registry::{api_fn}(owner, prop, move |{params_var}: *mut u8| {{\n"
380 ));
381
382 if needs_unsafe {
383 out.push_str(" unsafe {\n");
384 }
385
386 for (i, p) in d.params.iter().enumerate() {
388 let var_name = &p.name;
389 match &p.conversion {
390 ParamConversion::Primitive(ty) => {
391 out.push_str(&format!(
392 " let {var_name} = *(params.add(offsets[{i}] as usize) as *const {ty});\n"
393 ));
394 }
395 ParamConversion::ObjectRef(_cls) => {
396 out.push_str(&format!(
397 " let {var_name} = uika_runtime::UObjectRef::from_raw(\n\
398 \x20 *(params.add(offsets[{i}] as usize) as *const uika_runtime::UObjectHandle)\n\
399 \x20 );\n"
400 ));
401 }
402 ParamConversion::Enum { rust_type, repr } => {
403 out.push_str(&format!(
407 " let __raw_{var_name} = *(params.add(offsets[{i}] as usize) as *const {repr});\n\
408 \x20 let {var_name} = {rust_type}::from_value(__raw_{var_name}).unwrap_or_else(|| std::mem::transmute(__raw_{var_name}));\n"
409 ));
410 }
411 ParamConversion::FName => {
412 out.push_str(&format!(
413 " let {var_name} = *(params.add(offsets[{i}] as usize) as *const uika_runtime::FNameHandle);\n"
414 ));
415 }
416 ParamConversion::String => {
417 out.push_str(&format!(
421 " let {var_name} = String::new(); // TODO: string param extraction\n"
422 ));
423 }
424 }
425 }
426
427 let param_names: Vec<&str> = d.params.iter().map(|p| p.name.as_str()).collect();
429 let call_args = param_names.join(", ");
430 if needs_unsafe {
431 out.push_str(&format!(
432 " callback({call_args});\n\
433 \x20 }}\n\
434 \x20 }})\n\
435 \x20 }}\n"
436 ));
437 } else {
438 out.push_str(&format!(
439 " callback({call_args});\n\
440 \x20 }})\n\
441 \x20 }}\n"
442 ));
443 }
444
445 out.push_str("}\n\n");
446 }
447}