autd3capi_wrapper_generator/
lib.rs

1/*
2 * File: lib.rs
3 * Project: src
4 * Created Date: 10/11/2022
5 * Author: Shun Suzuki
6 * -----
7 * Last Modified: 29/11/2023
8 * Modified By: Shun Suzuki (suzuki@hapis.k.u-tokyo.ac.jp)
9 * -----
10 * Copyright (c) 2023 Shun Suzuki. All rights reserved.
11 *
12 */
13
14mod parse;
15mod python;
16mod traits;
17mod types;
18
19use std::path::Path;
20
21use anyhow::Result;
22
23use cargo_metadata::MetadataCommand;
24use convert_case::{Case, Casing};
25use parse::{parse_const, parse_enum, parse_func, parse_struct};
26use python::PythonGenerator;
27use traits::Generator;
28
29fn gen<G: Generator, P1: AsRef<Path>, P2: AsRef<Path>>(
30    path: P1,
31    crate_path: P2,
32    use_single: bool,
33) -> Result<()> {
34    std::fs::create_dir_all(path.as_ref())?;
35
36    let metadata = MetadataCommand::new()
37        .manifest_path(crate_path.as_ref().join("Cargo.toml"))
38        .exec()?;
39
40    let crate_name = metadata.root_package().unwrap().name.as_str();
41
42    glob::glob(&format!(
43        "{}/**/*.rs",
44        crate_path.as_ref().join("src").display()
45    ))?
46    .try_fold(G::new(), |acc, path| -> Result<_> {
47        let path = path?;
48        Ok(acc
49            .register_func(parse_func(&path, use_single)?)
50            .register_const(parse_const(&path, use_single)?)
51            .register_enum(parse_enum(&path, use_single)?)
52            .register_struct(parse_struct(&path, use_single)?))
53    })?
54    .write(path, crate_name)
55}
56
57fn generate_cs<P1: AsRef<Path>, P2: AsRef<Path>>(
58    path: P1,
59    crate_path: P2,
60    use_single: bool,
61) -> Result<()> {
62    let sub_abbr =
63        |str: String| -> String { str.replace("Twincat", "TwinCAT").replace("Soem", "SOEM") };
64
65    let to_pascal = |name: &str| -> String {
66        let res = name.to_case(Case::Pascal);
67        sub_abbr(res)
68    };
69
70    let to_class_name = |name: &str| {
71        if name.split('-').count() == 1 {
72            return "Base".to_string();
73        }
74        to_pascal(&name.replace("autd3capi-", ""))
75    };
76
77    let crate_name = crate_path.as_ref().file_name().unwrap().to_str().unwrap();
78    let out_file = Path::new(path.as_ref()).join(format!("{}.cs", to_class_name(crate_name)));
79    let dll_name = crate_name.replace('-', "_");
80    let class_name = to_class_name(crate_name);
81
82    glob::glob(&format!(
83        "{}/**/*.rs",
84        crate_path.as_ref().join("src").display()
85    ))?
86    .try_fold(csbindgen::Builder::default(), |acc, path| -> Result<_> {
87        let path = path?;
88        Ok(acc.input_extern_file(path))
89    })?
90    .csharp_dll_name(dll_name)
91    .csharp_class_name(format!("NativeMethods{}", class_name))
92    .csharp_namespace("AUTD3Sharp.NativeMethods")
93    .csharp_generate_const_filter(|_| true)
94    .csharp_class_accessibility("public")
95    .generate_csharp_file(&out_file)
96    .map_err(|_| anyhow::anyhow!("failed to generate cs wrapper"))?;
97
98    let content = std::fs::read_to_string(&out_file)?;
99    let content = content.replace("@float", if use_single { "float" } else { "double" });
100    let content = content.replace("ConstPtr", "IntPtr");
101    let content = content.replace("void*", "IntPtr");
102    let content = content.replace("SamplingConfiguration", "SamplingConfigurationRaw");
103
104    let content = content.replace("Drive*", "DriveRaw*");
105
106    let content = if use_single {
107        let re = regex::Regex::new(r"public const float (.*) = (.*);").unwrap();
108        re.replace_all(&content, "public const float $1 = ${2}f;")
109            .to_string()
110    } else {
111        content
112    };
113
114    std::fs::write(&out_file, content)?;
115
116    Ok(())
117}
118
119pub fn gen_c<P1: AsRef<Path>, P2: AsRef<Path>>(crate_path: P1, dest_dir: P2) -> Result<()> {
120    let out_file = dest_dir.as_ref().join(format!(
121        "{}.h",
122        crate_path.as_ref().file_name().unwrap().to_str().unwrap()
123    ));
124    let mut config = cbindgen::Config::default();
125    config.language = cbindgen::Language::Cxx;
126    config.pragma_once = true;
127    config.autogen_warning = Some(
128        "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */"
129            .to_string(),
130    );
131    config.namespace = Some("autd3::internal::native_methods".to_string());
132    config.no_includes = true;
133    config.sys_includes = vec!["cstdint".to_string()];
134    config.sort_by = cbindgen::SortKey::None;
135    config.usize_is_size_t = true;
136    config.export = cbindgen::ExportConfig {
137        include: vec![
138            "TimerStrategy".to_string(),
139            "GainSTMMode".to_string(),
140            "ControllerPtr".to_string(),
141            "EmissionConstraintPtr".to_string(),
142            "FirmwareInfoListPtr".to_string(),
143            "GroupKVMapPtr".to_string(),
144            "CachePtr".to_string(),
145            "DevicePtr".to_string(),
146            "TransducerPtr".to_string(),
147            "GeometryPtr".to_string(),
148            "ModulationPtr".to_string(),
149            "GainPtr".to_string(),
150            "LinkPtr".to_string(),
151            "DatagramPtr".to_string(),
152            "DatagramSpecialPtr".to_string(),
153            "STMPropsPtr".to_string(),
154            "BackendPtr".to_string(),
155            "GroupGainMapPtr".to_string(),
156            "GainCalcDrivesMapPtr".to_string(),
157            "LinkBuilderPtr".to_string(),
158            "ResultI32".to_string(),
159            "ResultModulation".to_string(),
160            "ResultBackend".to_string(),
161            "ResultController".to_string(),
162            "ResultGainCalcDrivesMap".to_string(),
163            "ResultDatagram".to_string(),
164            "Drive".to_string(),
165        ],
166        exclude: vec!["ConstPtr".to_string()],
167        rename: vec![
168            ("float".to_string(), "double".to_string()),
169            ("ConstPtr".to_string(), "void*".to_string()),
170        ]
171        .into_iter()
172        .collect(),
173        ..Default::default()
174    };
175    config.function = cbindgen::FunctionConfig {
176        sort_by: None,
177        must_use: Some("[[nodiscard]]".to_string()),
178        ..Default::default()
179    };
180    config.constant = cbindgen::ConstantConfig {
181        allow_static_const: false,
182        allow_constexpr: true,
183        sort_by: Some(cbindgen::SortKey::None),
184    };
185
186    cbindgen::Builder::new()
187        .with_crate(crate_path)
188        .with_config(config)
189        .generate()?
190        .write_to_file(out_file);
191
192    Ok(())
193}
194
195pub fn gen_py<P1: AsRef<Path>, P2: AsRef<Path>>(crate_path: P1, dest_dir: P2) -> Result<()> {
196    gen::<PythonGenerator, _, _>(dest_dir, crate_path, false)
197}
198
199pub fn gen_cs<P1: AsRef<Path>, P2: AsRef<Path>>(crate_path: P1, dest_dir: P2) -> Result<()> {
200    generate_cs(dest_dir, crate_path, false)
201}
202
203pub fn gen_unity<P1: AsRef<Path>, P2: AsRef<Path>>(crate_path: P1, dest_dir: P2) -> Result<()> {
204    generate_cs(dest_dir, crate_path, true)
205}
206
207pub fn generate<P: AsRef<Path>>(crate_path: P) -> Result<()> {
208    gen_py(&crate_path, "../../python/pyautd3/native_methods")?;
209
210    gen_c(
211        &crate_path,
212        "../../cpp/include/autd3/internal/native_methods",
213    )?;
214    gen_cs(&crate_path, "../../dotnet/cs/src/NativeMethods")?;
215    gen_unity(
216        &crate_path,
217        "../../dotnet/unity/Assets/Scripts/NativeMethods",
218    )?;
219
220    Ok(())
221}