Cramming PhysX in there

More PhysX work

More PhysX work
This commit is contained in:
2025-07-06 23:58:40 +02:00
parent c3a1686325
commit 84350b85ab
3257 changed files with 587241 additions and 66 deletions

View File

@@ -0,0 +1,414 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "anyhow"
version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
[[package]]
name = "bitflags"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clang-ast"
version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5218f4ca4789f5665f2a29a08b3fd36a9862364ef29892a1255b7efcf91edc78"
dependencies = [
"rustc-hash",
"serde",
]
[[package]]
name = "console"
version = "0.15.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb"
dependencies = [
"encode_unicode",
"lazy_static",
"libc",
"windows-sys",
]
[[package]]
name = "encode_unicode"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
[[package]]
name = "env_logger"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580"
dependencies = [
"humantime",
"is-terminal",
"log",
"regex",
"termcolor",
]
[[package]]
name = "errno"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
dependencies = [
"libc",
"windows-sys",
]
[[package]]
name = "fastrand"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a"
[[package]]
name = "heck"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "hermit-abi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
[[package]]
name = "humantime"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "insta"
version = "1.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "810ae6042d48e2c9e9215043563a58a80b877bc863228a74cf10c49d4620a6f5"
dependencies = [
"console",
"lazy_static",
"linked-hash-map",
"similar",
]
[[package]]
name = "is-terminal"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b"
dependencies = [
"hermit-abi",
"libc",
"windows-sys",
]
[[package]]
name = "itoa"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.155"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
[[package]]
name = "linked-hash-map"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
[[package]]
name = "linux-raw-sys"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
[[package]]
name = "log"
version = "0.4.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
[[package]]
name = "memchr"
version = "2.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
[[package]]
name = "proc-macro2"
version = "1.0.85"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23"
dependencies = [
"unicode-ident",
]
[[package]]
name = "pxbind"
version = "0.1.0"
dependencies = [
"anyhow",
"clang-ast",
"env_logger",
"heck",
"insta",
"log",
"serde",
"serde_json",
"tempfile",
]
[[package]]
name = "quote"
version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
dependencies = [
"proc-macro2",
]
[[package]]
name = "regex"
version = "1.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustix"
version = "0.38.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys",
"windows-sys",
]
[[package]]
name = "ryu"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
[[package]]
name = "serde"
version = "1.0.203"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.203"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "similar"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa42c91313f1d05da9b26f267f931cf178d4aba455b4c4622dd7355eb80c6640"
[[package]]
name = "syn"
version = "2.0.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tempfile"
version = "3.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1"
dependencies = [
"cfg-if",
"fastrand",
"rustix",
"windows-sys",
]
[[package]]
name = "termcolor"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
dependencies = [
"winapi-util",
]
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "winapi-util"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b"
dependencies = [
"windows-sys",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
[[package]]
name = "windows_i686_gnu"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
[[package]]
name = "windows_i686_msvc"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"

View File

@@ -0,0 +1,19 @@
[package]
name = "pxbind"
version = "0.1.0"
edition = "2021"
license = "MIT OR Apache-2.0"
publish = false
[dependencies]
anyhow = "1.0"
clang-ast = "0.1"
env_logger = "0.10"
heck = "0.4"
log = "0.4"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
[dev-dependencies]
insta = "1.26"
tempfile = "3.6"

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,245 @@
use super::{Builtin, Comment, EnumDecl, Item, Node, Typedef};
use anyhow::Context as _;
pub struct EnumVariant<'ast> {
/// The name of the variant
pub name: &'ast str,
/// The constant value of the variant
pub value: i64,
/// Text comment on the enum constant
pub comment: Option<Comment<'ast>>,
}
pub struct EnumBinding<'ast> {
/// The repr() applied to the the Rust enum to get it the correct size
pub repr: Builtin,
/// The "friendly" name of the enum, eg `PxErrorCode`
pub name: &'ast str,
/// The qualified type of the enum, minus the physx:: namespace since all
/// the c/cpp code we compile is done within that namespace, eg. PxErrorCode::Enum
pub cxx_qt: &'ast str,
/// Text comment on the enum (or more usually, the wrapping struct)
pub comment: Option<Comment<'ast>>,
/// The list of constants
pub variants: Vec<EnumVariant<'ast>>,
}
pub struct FlagsBinding<'ast> {
/// The name of the typedef used in the public API
pub name: &'ast str,
/// The index in the AstConsumer of the enum binding
pub enums_index: usize,
/// The storage type used by the flags
pub storage_type: super::Builtin,
}
impl<'ast> super::AstConsumer<'ast> {
pub(super) fn consume_enum(
&mut self,
parent: &'ast Node,
enum_node: &'ast Node,
enum_decl: &'ast EnumDecl,
) -> anyhow::Result<()> {
let comment = match Self::get_comment(enum_node) {
Some(com) => Some(com),
None => {
// If the comment isn't directly on the enum itself, check if
// the parent is a struct since most physx enums follow the classic
// struct EnumName { enum Enum {}; }; pattern
if matches!(parent.kind, Item::CXXRecordDecl(_)) {
Self::get_comment(parent)
} else {
None
}
}
};
let name = if let Item::CXXRecordDecl(rec) = &parent.kind {
if let Some(rname) = rec.name.as_deref() {
// Check if the record is actually not just a wrapper (ie, has fields or methods)
// as that means we'll need to the name the enum with the inner name
// as that is _probably_ still unique
if parent
.inner
.iter()
.any(|inn| matches!(&inn.kind, Item::FieldDecl { .. }))
{
if let Some(ename) = enum_decl.name.as_deref() {
ename
} else {
log::debug!("Wrapper struct {rname} that will also be a POD contained an anonymous enum");
return Ok(());
}
} else {
rname
}
} else {
log::debug!(
"skipping enum {:?} with anonymous wrapper struct",
enum_decl.name
);
return Ok(());
}
} else if let Some(ename) = enum_decl.name.as_deref() {
ename
} else {
log::debug!("skipping anonymous enum");
return Ok(());
};
let mut repr = Builtin::Int;
let mut variants = Vec::new();
// Unfortunately the qualified type isn't present on the enum itself,
// but rather each and every variant. They _should_ all be the same
let mut cxx_qt = None;
fn get_value(node: &Node, current: i64, repr: &mut Builtin) -> anyhow::Result<i64> {
for inn in &node.inner {
match &inn.kind {
Item::ImplicitCastExpr { .. } => {
return get_value(inn, current, repr);
}
Item::ConstantExpr { value, kind } => {
// There are a couple of cases where clang will emit
// unsigned int for some variants and int for others,
// so we need to just ignore changes once it's not the default
if matches!(repr, Builtin::Int) {
if let Some(builtin) = super::AstConsumer::parse_builtin(kind) {
*repr = builtin;
}
}
return value.parse().context("failed to parse enum constant");
}
_ => continue,
}
}
Ok(current)
}
let mut current = 0;
for (varn, vard) in enum_node.inner.iter().filter_map(|inn| {
if let Item::EnumConstantDecl(cdecl) = &inn.kind {
Some((inn, cdecl))
} else {
None
}
}) {
if Self::is_deprecated(varn) {
log::debug!("ignoring deprecated variant {name}::{}", vard.name);
continue;
}
let name = &vard.name;
if let Some(cxx_qt) = cxx_qt {
anyhow::ensure!(
cxx_qt == vard.kind.qual_type,
"enum has mismatching qualified types current: '{cxx_qt}', new: '{}'",
vard.kind.qual_type
);
} else {
cxx_qt = Some(vard.kind.qual_type.as_str());
}
let comment = Self::get_comment(varn);
let value = get_value(varn, current, &mut repr)?;
current = value + 1;
variants.push(EnumVariant {
name,
comment,
value,
});
}
// All cpp code is used within the physx namespace, so strip that prefix
let cxx_qt =
cxx_qt.with_context(|| format!("enum '{name}' never declared a qualified typename"))?;
let cxx_qt = super::no_physx(cxx_qt);
self.enums.push(EnumBinding {
repr,
name,
cxx_qt,
comment,
variants,
});
self.enum_map.insert(cxx_qt, (repr, name));
Ok(())
}
pub(super) fn consume_flags(
&mut self,
_node: &'ast Node,
td: &'ast Typedef,
) -> anyhow::Result<()> {
// PhysX uses a PxFlags<> template typedef to create a bitfield type for
// a specific enum, we use this typedef to also generate an appropriate
// bitflags that can be transparently passed between the FFI boundary
let Some(flags) = super::no_physx(&td.kind.qual_type).strip_prefix("PxFlags<") else {
return Ok(());
};
// Get rid of `>`
let flags = &flags[..flags.len() - 1];
let mut iter = flags.split(',');
// PxFlags<PxDistanceJointFlag::Enum, physx::PxU16>
// First template parameter is the enum type being wrapped
let enum_type = iter
.next()
.with_context(|| format!("PxFlags typedef '{}' did not specify an enum type", td.name))?
.trim();
// Second is the storage type, which _should_ always be an unsigned integer
// type, so we assert on this to ensure shenanigans aren't happening
// Though the PxFlags template allows for defaulting to u32 for storage,
// this is not used AFAICT in PhysX, so we require that the storage
// type be specified as well, for now
let storage_type = iter
.next()
.with_context(|| {
format!(
"PxFlags typedef '{}' did not specify a storage type",
td.name
)
})?
.trim();
let storage_type = Self::parse_builtin(storage_type).with_context(|| {
format!(
"PxFlags typedef '{}' has storage type '{storage_type}' which is not a builtin",
td.name
)
})?;
// Find the enum binding, we use this later when generating the bitflags,
// we search in reverse order since the enum being wrapped almost always
// comes directly before the flags typedef
let enums_index = self
.enums
.iter()
.rposition(|eb| eb.cxx_qt == enum_type)
.with_context(|| {
format!(
"PxFlags typedef '{}' references unknown enum type '{enum_type}'",
td.name
)
})?;
self.flags.push(FlagsBinding {
name: &td.name,
enums_index,
storage_type,
});
self.flags_map.insert(&td.name, storage_type);
Ok(())
}
}

View File

@@ -0,0 +1,242 @@
use super::{Comment, Item, Method, QualType, TemplateArg};
use crate::Node;
use anyhow::Context as _;
use std::borrow::Cow;
#[derive(serde::Deserialize, Debug)]
pub struct Function {
pub name: String,
#[serde(rename = "type")]
pub kind: super::Type,
pub range: Option<clang_ast::SourceRange>,
}
#[derive(Debug)]
pub struct Param<'ast> {
pub name: Cow<'ast, str>,
pub kind: QualType<'ast>,
}
impl<'ast> Param<'ast> {
#[inline]
pub fn self_pod(rec_type: QualType<'ast>, is_const: bool) -> Self {
Self {
name: Cow::Borrowed("self_"),
kind: QualType::Pointer {
is_const: false,
is_pointee_const: is_const,
pointee: Box::new(rec_type),
},
}
}
}
pub enum PhysxInvoke<'ast> {
/// Normal function call. Not many of these in PhysX, it's pretty OO
Func { func_name: &'ast str, is_c: bool },
/// Method call
Method {
func_name: &'ast str,
class_name: &'ast str,
is_static: bool,
},
/// Constructor call
Ctor(&'ast str),
/// New
New(&'ast str),
}
pub enum FuncBindingExt<'ast> {
IsDelete(&'ast str),
None(PhysxInvoke<'ast>),
HasSelf(PhysxInvoke<'ast>),
}
pub struct FuncBinding<'ast> {
pub name: String,
pub comment: Option<Comment<'ast>>,
pub ext: FuncBindingExt<'ast>,
pub params: Vec<Param<'ast>>,
pub ret: Option<QualType<'ast>>,
}
impl<'ast> FuncBinding<'ast> {
pub fn owning_class(&self) -> Option<&'ast str> {
match &self.ext {
FuncBindingExt::None(pi) | FuncBindingExt::HasSelf(pi) => match pi {
PhysxInvoke::Ctor(name)
| PhysxInvoke::New(name)
| PhysxInvoke::Method {
class_name: name, ..
} => Some(name),
PhysxInvoke::Func { .. } => None,
},
FuncBindingExt::IsDelete(class_name) => Some(class_name),
}
}
}
impl<'ast> super::AstConsumer<'ast> {
/// Retrieves a unique name for the function to ensure that overloads have
/// different names. It also appends `phys_` before the name to ensure that
/// the C++ code compiles by avoiding errors due to functions that only
/// differ by return type being ambiguous
fn get_func_name(&mut self, name: &'ast str) -> String {
if let Some(count) = self.func_map.get_mut(name) {
*count += 1;
format!("phys_{name}_{count}")
} else {
self.func_map.insert(name, 0);
format!("phys_{name}")
}
}
pub fn consume_function(
&mut self,
node: &'ast Node,
func: &'ast Function,
template_types: &[(&str, &TemplateArg<'ast>)],
is_c: bool,
) -> anyhow::Result<()> {
if func.name.starts_with("operator") {
return Ok(());
}
// Check the source location for the function
//
// - PxMath - trivial functions that we can just do in Rust if needed
// without crossing the FFI boundary
// - PxString - internal-only string funtions we don't care about
// - PxUtilities - useless internal-only functions
// - PxAtomic - internal-only functions
// - PxHash - internal-only functions
let ignored = [
"PxMath.h",
"PxString.h",
"PxUtilities.h",
"PxAtomic.h",
"PxHash.h",
];
if func
.range
.as_ref()
.and_then(|r| r.begin.expansion_loc.as_ref())
.map_or(false, |el| {
el.file
.rfind('/')
.map_or(false, |sep| ignored.contains(&&el.file[sep + 1..]))
})
{
log::debug!("skipping PxMath.h function {}", func.name);
return Ok(());
}
let comment = Self::get_comment(node);
let mut fb = FuncBinding {
name: self.get_func_name(&func.name),
comment,
ext: FuncBindingExt::None(PhysxInvoke::Func {
func_name: &func.name,
is_c,
}),
ret: None,
params: Vec::new(),
};
self.consume_return(&func.name, &func.kind.qual_type, template_types, &mut fb)?;
self.consume_params(&func.name, node, template_types, &mut fb)?;
self.funcs.push(fb);
Ok(())
}
pub fn consume_method(
&mut self,
node: &'ast Node,
meth: &'ast Method,
template_types: &[(&str, &TemplateArg<'ast>)],
mut func: FuncBinding<'ast>,
) -> anyhow::Result<()> {
if meth.kind.qual_type.contains('<') {
log::debug!(
"ignoring method '{}' which has 1 or more templated parameters",
meth.name
);
}
self.consume_return(&meth.name, &meth.kind.qual_type, template_types, &mut func)?;
self.consume_params(&meth.name, node, template_types, &mut func)?;
self.funcs.push(func);
Ok(())
}
#[inline]
fn consume_params(
&self,
name: &'ast str,
node: &'ast Node,
template_types: &[(&str, &TemplateArg<'ast>)],
func: &mut FuncBinding<'ast>,
) -> anyhow::Result<()> {
for (i, param) in node
.inner
.iter()
.filter_map(|inn| {
if let Item::ParmVarDecl(param) = &inn.kind {
Some(param)
} else {
None
}
})
.enumerate()
{
let pname = param
.name
.as_deref()
.map_or_else(|| format!("anon_param{i}").into(), Cow::Borrowed);
let kind = self
.parse_type(&param.kind, template_types)
.with_context(|| {
format!(
"failed to parse parameter '{pname} ({})' for function '{name}'",
param.kind.qual_type,
)
})?;
func.params.push(Param { name: pname, kind });
}
Ok(())
}
/// Unfortunately `CXXMethodDecl`'s don't have a node for the return type,
/// so we need to manually parse it from the function signature...
#[inline]
fn consume_return(
&self,
name: &str,
sig: &'ast str,
template_types: &[(&str, &TemplateArg<'ast>)],
func: &mut FuncBinding<'ast>,
) -> anyhow::Result<()> {
// Ignore functions that already have a return type, notably constructors
if func.ret.is_none() {
let open_ind = sig
.find('(')
.with_context(|| format!("function signature for '{name}' doesn't have a '('"))?;
let ret = sig[..open_ind].trim();
if ret != "void" {
func.ret = Some(
self.parse_type(ret, template_types)
.with_context(|| format!("failed to parse return type for '{name}'"))?,
);
}
}
Ok(())
}
}

View File

@@ -0,0 +1,740 @@
use std::fmt;
use super::{functions, Builtin, ClassDef, Id, Item, QualType, Type, Typedef};
use crate::Node;
use anyhow::Context as _;
use functions::*;
use serde::Deserialize;
#[derive(Copy, Clone, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub enum Access {
Public,
Protected,
Private,
}
fn storage_class<'de, D>(deserializer: D) -> Result<bool, D::Error>
where
D: serde::de::Deserializer<'de>,
{
let s: &str = serde::de::Deserialize::deserialize(deserializer)?;
Ok(s == "static")
}
#[derive(Deserialize, Debug)]
pub struct Method {
pub name: String,
#[serde(rename = "type")]
pub kind: Type,
#[serde(default, rename = "storageClass", deserialize_with = "storage_class")]
pub is_static: bool,
#[serde(default, rename = "virtual")]
pub is_virtual: bool,
}
impl Method {
#[inline]
fn is_const(&self) -> bool {
self.is_static || self.kind.qual_type.ends_with(") const")
}
}
#[derive(Deserialize, Debug)]
pub struct Constructor {
#[serde(flatten)]
pub(super) inner: Method,
}
impl Constructor {
/// Determines whether a constructor is a [copy constructor](https://en.cppreference.com/w/cpp/language/copy_constructor)
#[inline]
fn is_copy_or_move_constructor(&self, node: &Node) -> bool {
let mut iter = node.inner.iter().filter_map(|inn| {
if let Item::ParmVarDecl(param) = &inn.kind {
Some(&param.kind.qual_type)
} else {
None
}
});
let Some(first) = iter.next() else {
return false;
};
if first.ends_with(" &&") {
return true;
}
let maybe_copy = first
.strip_suffix(" &")
.map_or(false, |is_ref| is_ref.ends_with(&self.inner.name));
maybe_copy && iter.next().is_none()
}
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
struct Dtor {
#[serde(default)]
irrelevant: bool,
#[serde(default)]
simple: bool,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct DefinitionData {
// #[serde(default, rename = "isPOD")]
// is_pod: bool,
dtor: Dtor,
#[serde(default)]
is_abstract: bool,
#[serde(default)]
is_polymorphic: bool,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Base {
//access: String,
#[serde(rename = "type")]
kind: Type,
//written_access: String,
}
#[derive(Copy, Clone, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub enum Tag {
Struct,
Class,
Union,
}
impl fmt::Display for Tag {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Struct => f.write_str("struct"),
Self::Class => f.write_str("class"),
Self::Union => f.write_str("union"),
}
}
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Record {
pub id: Option<Id>,
pub name: Option<String>,
pub tag_used: Option<Tag>,
pub definition_data: Option<DefinitionData>,
#[serde(default)]
pub bases: Vec<Base>,
}
impl Record {
#[inline]
fn is_abstract(&self) -> bool {
self.definition_data
.as_ref()
.map_or(false, |dd| dd.is_abstract)
}
#[inline]
fn is_polymorphic(&self) -> bool {
self.definition_data
.as_ref()
.map_or(false, |dd| dd.is_polymorphic)
}
#[inline]
fn has_irrelevant_destructor(&self) -> bool {
self.definition_data
.as_ref()
.map_or(false, |dd| dd.dtor.irrelevant)
}
#[inline]
fn has_simple_destructor(&self) -> bool {
self.definition_data
.as_ref()
.map_or(false, |dd| dd.dtor.simple)
}
}
#[derive(Debug)]
pub struct RecBindingDef<'ast> {
pub name: &'ast str,
pub has_vtable: bool,
pub ast: &'ast Record,
pub fields: Vec<FieldBinding<'ast>>,
pub calc_layout: bool,
}
#[derive(Debug)]
pub struct RecBindingForward<'ast> {
pub name: &'ast str,
}
#[derive(Debug)]
pub enum RecBinding<'ast> {
Forward(RecBindingForward<'ast>),
Def(RecBindingDef<'ast>),
}
impl<'ast> RecBinding<'ast> {
pub fn name(&self) -> &'ast str {
match self {
Self::Def(def) => def.name,
Self::Forward(fwd) => fwd.name,
}
}
}
impl<'ast> PartialEq<str> for RecBinding<'ast> {
fn eq(&self, other: &str) -> bool {
match self {
Self::Def(def) => def.name == other,
Self::Forward(fwd) => fwd.name == other,
}
}
}
#[derive(Debug, Clone)]
pub struct FieldBinding<'ast> {
pub name: &'ast str,
pub kind: QualType<'ast>,
pub is_public: bool,
pub is_reference: bool,
}
impl<'ast> super::AstConsumer<'ast> {
fn has_release_method(&self, node: &'ast Node, rec: &'ast Record) -> anyhow::Result<bool> {
if node.inner.iter().any(|inn| {
if let Item::CXXMethodDecl(method) = &inn.kind {
method.name == "release"
} else {
false
}
}) {
return Ok(true);
}
for base in self.iter_bases(rec) {
let (cdef, _) = base?;
if self.has_release_method(cdef.node, cdef.rec)? {
return Ok(true);
}
}
Ok(false)
}
#[inline]
pub(super) fn is_template_we_care_about(
node: &'ast Node,
td: &'ast Typedef,
) -> Option<&'ast str> {
let tds = [
"PxBitAndByte",
"PxRaycastCallback",
"PxOverlapCallback",
"PxSweepCallback",
"PxRaycastBuffer",
"PxOverlapBuffer",
"PxSweepBuffer",
"PxBitMap",
];
if !tds.contains(&td.name.as_str()) {
return None;
}
node.inner.iter().find_map(|inn| {
if let Item::TemplateSpecializationType { template_name } = &inn.kind {
Some(template_name.as_str())
} else {
None
}
})
}
pub(super) fn consume_template_typedef(
&mut self,
_node: &'ast Node,
td: &'ast Typedef,
template_name: &'ast str,
root: &'ast Node,
) -> anyhow::Result<()> {
if self.classes.contains_key(td.name.as_str()) {
return Ok(());
}
let mut fields = Vec::new();
match td.name.as_str() {
"PxBitAndByte" => {
fields.push(FieldBinding {
name: "mData",
kind: QualType::Builtin(Builtin::UChar),
is_public: false,
is_reference: false,
});
}
"PxRaycastCallback" | "PxOverlapCallback" | "PxSweepCallback" | "PxRaycastBuffer"
| "PxOverlapBuffer" | "PxSweepBuffer" => {
let hit_type = match td.name.as_str() {
"PxRaycastCallback" | "PxRaycastBuffer" => "PxRaycastHit",
"PxOverlapCallback" | "PxOverlapBuffer" => "PxOverlapHit",
"PxSweepCallback" | "PxSweepBuffer" => "PxSweepHit",
_ => unreachable!(),
};
fields.push(FieldBinding {
name: "block",
kind: QualType::Record { name: hit_type },
is_public: true,
is_reference: false,
});
fields.push(FieldBinding {
name: "hasBlock",
kind: QualType::Builtin(Builtin::Bool),
is_public: true,
is_reference: false,
});
fields.push(FieldBinding {
name: "touches",
kind: QualType::Pointer {
is_const: false,
is_pointee_const: false,
pointee: Box::new(QualType::Record { name: hit_type }),
},
is_public: true,
is_reference: false,
});
fields.push(FieldBinding {
name: "maxNbTouches",
kind: QualType::Builtin(Builtin::UInt),
is_public: true,
is_reference: false,
});
fields.push(FieldBinding {
name: "nbTouches",
kind: QualType::Builtin(Builtin::UInt),
is_public: true,
is_reference: false,
});
}
"PxBitMap" => {
fields.push(FieldBinding {
name: "mMap",
kind: QualType::Pointer {
is_const: false,
is_pointee_const: false,
pointee: Box::new(QualType::Builtin(Builtin::UInt)),
},
is_public: false,
is_reference: false,
});
fields.push(FieldBinding {
name: "mWordCount",
kind: QualType::Builtin(Builtin::UInt),
is_public: false,
is_reference: false,
});
}
_ => {
self.consume_template_instance(&td.kind.qual_type, Some(&td.name))?;
}
}
let name = &td.name;
let (node, ast) = super::search(root, &|node: &Node| match &node.kind {
Item::ClassTemplateSpecializationDecl(rec) | Item::CXXRecordDecl(rec) => {
(rec.name.as_deref() == Some(template_name)).then_some(rec)
}
_ => None,
})
.with_context(|| format!("failed to locate template specialization for {name}"))?;
self.classes.insert(
name,
Some(super::ClassDef {
index: self.recs.len(),
node,
rec: ast,
}),
);
self.recs.push(RecBinding::Def(RecBindingDef {
name,
has_vtable: ast.is_polymorphic(),
fields,
ast,
calc_layout: true,
}));
Ok(())
}
fn iter_bases(
&self,
rec: &'ast Record,
) -> impl Iterator<Item = anyhow::Result<(&ClassDef<'ast>, &RecBindingDef<'ast>)>> {
rec.bases.iter().filter_map(|base| {
let Some(base_name) = base.kind.qual_type.strip_prefix("physx::") else {
log::debug!("skipping non-physx base class '{}'", base.kind.qual_type);
return None;
};
let get = || {
let base_rec = self.classes.get(base_name).with_context(|| {
format!(
"failed to find base '{}' for '{}'",
base.kind.qual_type,
rec.name.as_deref().unwrap(),
)
})?;
let base_rec = base_rec.as_ref().with_context(|| {
format!(
"Definition for base class {} has not been consumed",
base.kind.qual_type
)
})?;
if let RecBinding::Def(base_binding) = &self.recs[base_rec.index] {
anyhow::ensure!(
base_binding.name == base_name,
"Retrieved incorrect binding for base class"
);
Ok((base_rec, base_binding))
} else {
anyhow::bail!("Found a forward declaration instead of the base definition");
}
};
Some(get())
})
}
pub(super) fn consume_record(
&mut self,
node: &'ast Node,
rec: &'ast Record,
) -> anyhow::Result<()> {
// Do a quick check of the inner nodes, if we have an enumdecl, but no fields
// or methods, this is just a wrapper around an enum and we can just emit the enum
// and exit
let mut had_enums = false;
let mut had_more = false;
for inn in &node.inner {
match &inn.kind {
Item::FieldDecl { .. } => had_more = true,
kind if kind.as_method().is_some() => had_more = true,
Item::EnumDecl(decl) => {
self.consume_enum(node, inn, decl)?;
had_enums = true;
}
_ => continue,
}
}
if had_enums && !had_more {
return Ok(());
}
let Some(rname) = rec.name.as_deref() else {
return Ok(());
};
anyhow::ensure!(
rec.definition_data.is_some(),
"can't consume a record without a definition"
);
let has_vtable = rec.is_polymorphic();
let mut is_public = !matches!(rec.tag_used, Some(Tag::Class));
let mut fields = Vec::new();
for base_binding in self.iter_bases(rec) {
let (_, base_binding) = base_binding?;
fields.extend(base_binding.fields.iter().cloned());
}
self.get_fields(node, rec, &[], &mut fields)?;
// Keep a record of each method that we are binding, to account for
// overloading, particularly constructors, we need to append a counter
// to keep the binding functions unique
let mut meth_map = std::collections::BTreeMap::<String, u8>::new();
let mut get_name = |req: String| -> String {
if let Some(count) = meth_map.get_mut(&req) {
*count += 1;
format!("{req}_{count}")
} else {
meth_map.insert(req.clone(), 0);
req
}
};
for inn in &node.inner {
// Ignore any method that isn't public, it's not part of the API we care about
if let Some(method) = inn.kind.as_method() {
if !is_public {
continue;
} else if method.kind.qual_type.contains('<') {
log::debug!(
"skipping `{rname}::{}` as it contains a templated parameter",
method.name
);
continue;
} else if Self::is_deprecated(inn) {
log::debug!("skipping deprecated method {rname}::{}", method.name);
continue;
}
}
let comment = Self::get_comment(inn);
let (mut func, method, has_self) = match &inn.kind {
Item::AccessSpecDecl { access } => {
is_public = matches!(access, Access::Public);
continue;
}
Item::CXXConstructorDecl(method) => {
// If the class is abstract we can't construct it directly so don't need to bind constructors
// We don't care about copy or move constructors because they don't make sense in a C API
if rec.is_abstract() || method.is_copy_or_move_constructor(inn) {
continue;
}
let func_binding = if rec.is_polymorphic() || !rec.has_simple_destructor() {
FuncBinding {
name: get_name(format!("{rname}_new_alloc")),
ret: Some(QualType::Pointer {
is_const: false,
is_pointee_const: false,
pointee: Box::new(QualType::Record { name: rname }),
}),
comment,
ext: FuncBindingExt::None(PhysxInvoke::New(rname)),
params: Vec::new(),
}
} else {
FuncBinding {
name: get_name(format!("{rname}_new")),
ret: Some(QualType::Record { name: rname }),
comment,
ext: FuncBindingExt::None(PhysxInvoke::Ctor(rname)),
params: Vec::new(),
}
};
(func_binding, &method.inner, false)
}
Item::CXXMethodDecl(method) => {
// We don't care about operator overloads
if method.name.starts_with("operator") {
continue;
}
let func_binding = FuncBinding {
name: get_name(format!(
"{rname}_{}{}",
method.name,
if method.is_const() { "" } else { "" }
)),
ret: None,
comment,
ext: if method.is_static {
FuncBindingExt::None(PhysxInvoke::Method {
func_name: &method.name,
class_name: rname,
is_static: method.is_static,
})
} else {
FuncBindingExt::HasSelf(PhysxInvoke::Method {
func_name: &method.name,
class_name: rname,
is_static: method.is_static,
})
},
params: Vec::new(),
};
(func_binding, method, !method.is_static)
}
Item::CXXDestructorDecl(method) => {
// Determine whether this class has a destructor which has no semantic effect.
//
// PhysX uses reference counting for many things, in which
// case it should be deleted with a `release` method instead
// of explicitly deleting it, but we need to account for that
// `release` method possibly being on a base class, so we
// need to walk up the base class chain until we hit the root
// or a base that has a `release` method
// https://nvidia-omniverse.github.io/PhysX/physx/5.1.2/docs/API.html#reference-counting
if rec.has_irrelevant_destructor() || self.has_release_method(node, rec)? {
continue;
}
(
FuncBinding {
name: format!("{rname}_delete"),
comment,
ext: FuncBindingExt::IsDelete(rname),
params: Vec::new(),
ret: None,
},
method,
true,
)
}
_ => continue,
};
if has_self {
func.params.push(Param::self_pod(
QualType::Record { name: rname },
method.is_const(),
));
}
self.consume_method(inn, method, &[], func)?;
}
// Check the fields to see if any records need to be forward declared
// Note this doesn't apply to function parameters since functions are
// emitted after all Pod types
for field in &fields {
if let QualType::Pointer { pointee, .. } | QualType::Reference { pointee, .. } =
&field.kind
{
if let QualType::Record { name } = &**pointee {
// Special case for PxTempAllocatorChunk which is an internal
// linked list
if *name != rname && !self.classes.contains_key(name) {
self.recs
.push(RecBinding::Forward(RecBindingForward { name }));
self.classes.insert(name, None);
}
}
}
}
// If there are no fields, we need to add a dummy field since C++ doesn't have
// zero-sized types. This is fine in practice since these types are only
// ever passed by pointer
let is_empty = fields.is_empty() && !has_vtable;
if is_empty {
fields.push(FieldBinding {
name: "pxbind_dummy",
kind: QualType::Builtin(super::Builtin::Char),
is_public: false,
is_reference: false,
});
}
// Decide whether we should use "structgen" to calculate the exact layout of
// this C++ struct.
//
// Ideally we would do this for all types, but we must be able to name them,
// which is not feasible for anonymous types, or types which the generator
// doesn't support yet (their cppTypeName will be empty).
//
// Note that empty types are only refered to by pointers and references in
// PhysX, so we can generate dummy contents for them.
let calc_layout = (!matches!(rec.tag_used, Some(crate::consumer::Tag::Union))
&& !fields.is_empty())
|| rname == "PxBroadcastingErrorCallback";
let record = RecBindingDef {
name: rname,
has_vtable,
fields,
ast: rec,
calc_layout,
};
self.classes.insert(
rname,
Some(super::ClassDef {
index: self.recs.len(),
node,
rec,
}),
);
self.recs.push(RecBinding::Def(record));
Ok(())
}
pub fn get_fields(
&self,
node: &'ast Node,
rec: &'ast Record,
template_types: &[(&str, &super::TemplateArg<'ast>)],
fields: &mut Vec<FieldBinding<'ast>>,
) -> anyhow::Result<()> {
let Some(rname) = rec.name.as_deref() else {
return Ok(());
};
let mut is_public = !matches!(rec.tag_used, Some(Tag::Class));
for inn in &node.inner {
// We _could_ get comments for fields here, but due to the rust
// declarations being emitted by structgen, it becomes a bit noisy
match &inn.kind {
Item::AccessSpecDecl { access } => {
is_public = matches!(access, Access::Public);
}
Item::FieldDecl { name, kind } => {
// Skip anonymous fields, they aren't really accessible
if let Some(name) = name.as_deref() {
// PhysX uses PxPadding<BYTES> in some struct, but this
// is uninteresting so we can just skip it, they'll be
// accounted for in our own padding calculations regardless
if kind.qual_type.starts_with("PxPadding<") {
log::debug!("skipping padding field");
continue;
}
if kind.qual_type.contains('<') {
log::debug!("skipping templated field {rname}::{name}");
continue;
}
// We've made modifications to the C++ code to deprecate
// fields that are using deprecated types
if Self::is_deprecated(inn) {
continue;
}
let kind = self
.parse_type(kind, template_types)
.with_context(|| format!("failed to parse type for {rname}::{name}"))?;
// if matches!(&kind, QualType::FunctionPointer) {
// continue;
// }
let is_reference = matches!(kind, QualType::Reference { .. });
fields.push(FieldBinding {
name,
kind,
is_public,
is_reference,
});
}
}
_ => {}
}
}
Ok(())
}
}

View File

@@ -0,0 +1,135 @@
#![allow(unused)]
use crate::Node;
use super::{AstConsumer, Builtin, QualType, Record};
use anyhow::Context as _;
use std::borrow::Cow;
#[derive(PartialEq)]
pub enum TemplateArg<'ast> {
Type(QualType<'ast>),
Value(u32),
}
struct TemplateParam<'ast> {
name: &'ast str,
builtin: Option<Builtin>,
}
enum TemplateItemKind<'ast> {
Concrete(QualType<'ast>),
Parameterized(&'ast str),
}
struct TemplateItem<'ast> {
name: &'ast str,
kind: TemplateItemKind<'ast>,
}
struct TemplateMethod<'ast> {
name: String,
params: Vec<TemplateItem<'ast>>,
}
struct TemplateStamp<'ast> {
name: Cow<'ast, str>,
args: Vec<TemplateArg<'ast>>,
}
pub struct Template<'ast> {
params: Vec<TemplateParam<'ast>>,
instanced: Vec<TemplateStamp<'ast>>,
/// This is the
top_record: &'ast Record,
def_record: &'ast Node,
}
impl<'ast> Template<'ast> {
fn parse_template_args(
&self,
ast: &AstConsumer<'ast>,
qt: &'ast str,
) -> anyhow::Result<Vec<TemplateArg<'ast>>> {
let begin = qt.find('<').context("not a template")?;
let end = qt.rfind('>').context("not a template")?;
let mut targs = Vec::new();
for (arg, param) in qt[begin + 1..end].split(',').zip(self.params.iter()) {
// We don't really do error checking here, if the templates are messed
// up PhysX won't compile
if param.builtin.is_some() {
targs.push(TemplateArg::Value(arg.parse()?));
} else {
targs.push(TemplateArg::Type(ast.parse_type(arg, &[])?));
}
}
Ok(targs)
}
#[inline]
fn get_stamped(&self, args: &[TemplateArg<'ast>]) -> Option<Cow<'ast, str>> {
self.instanced
.iter()
.find_map(|i| (i.args == args).then(|| i.name.clone()))
}
}
impl<'ast> AstConsumer<'ast> {
pub(super) fn consume_template_instance(
&mut self,
qual_type: &'ast str,
name: Option<&'ast str>,
) -> anyhow::Result<Cow<'ast, str>> {
let qual_type = if let Some(stripped) = qual_type.strip_prefix("const ") {
stripped
} else {
qual_type
};
let begin = qual_type.find('<').context("this isn't a template")?;
let template_name = &qual_type[..begin];
let tdecl = self
.templates
.get(template_name)
.with_context(|| format!("template decl '{template_name}' has not been consumed"))?;
let targs = tdecl.parse_template_args(self, qual_type)?;
// If we've already stamped out this template just return the name it was given
if let Some(stamped) = tdecl.get_stamped(&targs) {
return Ok(stamped);
}
let instance_name = name.map_or_else(
|| Cow::Owned(format!("{template_name}_T{}", tdecl.instanced.len())),
Cow::Borrowed,
);
let mut mappings: Vec<_> = targs
.iter()
.zip(tdecl.params.iter())
.map(|(ta, tp)| (tp.name, ta))
.collect();
let mut fields = Vec::new();
self.get_fields(tdecl.def_record, tdecl.top_record, &mappings, &mut fields)?;
Ok(instance_name)
}
pub(super) fn consume_template_decl(
&mut self,
node: &'ast Node,
name: &'ast str,
) -> anyhow::Result<()> {
anyhow::ensure!(
!self.templates.contains_key(name),
"template '{name}' has already been consumed"
);
unreachable!();
}
}

View File

@@ -0,0 +1,88 @@
use crate::Node;
use anyhow::Context as _;
pub fn get_repo_root() -> anyhow::Result<String> {
let mut git = std::process::Command::new("git");
git.args(["rev-parse", "--show-toplevel"]);
git.stdout(std::process::Stdio::piped());
let captured = git
.output()
.context("failed to run git to find repo root")?;
let mut rr = String::from_utf8(captured.stdout).context("git output was non-utf8")?;
// Removing trailing newline
rr.pop();
Ok(rr)
}
#[inline]
pub fn get_include_dir() -> anyhow::Result<String> {
// Acquire the repo root so we don't need to care about where we are executing
// this from (eg root, tests, wherever)
let repo_root = get_repo_root()?;
Ok(format!("{repo_root}/physx/physx-sys/physx/physx/include"))
}
pub fn get_ast(header: impl AsRef<std::path::Path>) -> anyhow::Result<Vec<u8>> {
let mut cmd = std::process::Command::new("clang++");
let include_dir = get_include_dir()?;
cmd.args([
"-Xclang",
// Requests the AST dump
"-ast-dump=json",
// We aren't actually compiling, just gathering type info
"-fsyntax-only",
// clang will complain about all physx headers when we are in C++
// mode because it treats .h as "c headers", this is useless
"-xc++-header",
// Define PX_DEPRECATED so that the attribute is emitted into the AST
"-DPX_DEPRECATED=__attribute__((deprecated()))",
// Ignore all warnings, we don't care about C++ shenanigans
"-w",
// We don't want this
"-DDISABLE_CUDA_PHYSX",
"-fcolor-diagnostics",
// Add the root include directory so that clang knows how to find
// all of the includes
"-I",
&include_dir,
// Sigh, physx asserts that this is defined :p
"-DNDEBUG",
]);
cmd.arg(header.as_ref());
// note that this is _terribly_ slow but hopefully fixed in 1.69?
// https://github.com/rust-lang/rust/issues/108223
cmd.stdout(std::process::Stdio::piped());
cmd.stderr(std::process::Stdio::piped());
let captured = cmd
.output()
.context("failed to run clang++ to gather AST")?;
anyhow::ensure!(
captured.status.success(),
"clang++ failed to gather AST {:?}\n{}",
captured.status,
String::from_utf8(captured.stderr).unwrap_or_default(),
);
Ok(captured.stdout)
}
/// Dump the AST of a header and all of its includes and parses it into a [`Node`]
pub fn get_parsed_ast(header: impl AsRef<std::path::Path>) -> anyhow::Result<(Node, Vec<u8>)> {
log::info!("Gathering AST via clang...");
let t = std::time::Instant::now();
let ast = get_ast(header)?;
log::info!("Gathered AST in {}ms", t.elapsed().as_millis());
log::info!("Parsing AST...");
let t = std::time::Instant::now();
let root_node: Node = serde_json::from_slice(&ast).context("failed to parse AST")?;
log::info!("Parsed AST in {}ms", t.elapsed().as_millis());
Ok((root_node, ast))
}

View File

@@ -0,0 +1,324 @@
#[macro_export]
macro_rules! writes {
($s:expr, $f:expr $(,)?) => {{
use std::fmt::Write;
write!($s, $f).unwrap();
}};
($s:expr, $f:expr, $($arg:tt)*) => {{
use std::fmt::Write;
write!($s, $f, $($arg)*).unwrap();
}};
}
#[macro_export]
macro_rules! writesln {
($s:expr) => {{
use std::fmt::Write;
writeln!($s).unwrap();
}};
($s:expr, $f:expr $(,)?) => {{
use std::fmt::Write;
writeln!($s, $f).unwrap();
}};
($s:expr, $f:expr, $($arg:tt)*) => {{
use std::fmt::Write;
writeln!($s, $f, $($arg)*).unwrap();
}};
}
mod comment;
mod enums;
mod functions;
mod record;
use crate::consumer::{AstConsumer, Builtin, EnumBinding, FuncBinding, RecBinding};
use std::{fmt, io::Write};
/// The variable name of `PodStructGen` in the structgen program
const SG: &str = "sg";
/// The name of the macro used to calculate a field's offset in the structgen program
const UOF: &str = "unsafe_offsetof";
/// It's impossible (I believe) with Rust's format strings to have the width
/// of the alignment be dynamic, so we just uhhh...be lame
struct Indent(u32);
impl fmt::Display for Indent {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for _ in 0..self.0 {
f.write_str(" ")?;
}
Ok(())
}
}
pub struct Generator {
pub record_filter: Box<dyn Fn(&RecBinding<'_>) -> bool>,
pub enum_filter: Box<dyn Fn(&EnumBinding<'_>) -> bool>,
pub func_filter: Box<dyn Fn(&FuncBinding<'_>) -> bool>,
}
impl Default for Generator {
fn default() -> Self {
Self {
record_filter: Box::new(|_rb| true),
enum_filter: Box::new(|_eb| true),
func_filter: Box::new(|_fb| true),
}
}
}
impl Generator {
pub fn generate_all(
&self,
ast: &AstConsumer<'_>,
structgen: &mut impl Write,
cpp: &mut impl Write,
rust: &mut impl Write,
) -> anyhow::Result<()> {
self.generate_structgen(ast, structgen)?;
self.generate_cpp(ast, cpp)?;
self.generate_rust(ast, rust)?;
Ok(())
}
/// Generates the structgen `main` function, which is used to generate
/// the the POD types for C and Rust and guarantee their fields are
/// appropriately sized and aligned so they can be interchanged
pub fn generate_structgen(
&self,
ast: &AstConsumer<'_>,
out: &mut impl Write,
) -> anyhow::Result<()> {
// Preamble
{
// Get access to all of the PhysX types we're retrieving the layout for
writeln!(out, "// Automatically generated by pxbind")?;
writeln!(out, r#"#include "PxPhysicsAPI.h""#)?;
writeln!(out, "\nusing namespace physx;")?;
// Macro used to get the offset of each public field that the PODs
// expose
writeln!(
out,
"\n#define {UOF}(st, m) ((size_t) ( (char *)&((st *)(0))->m - (char *)0 ))"
)?;
// The header that contains the implementation of PodStructGen
writeln!(out, r#"#include "structgen.hpp""#)?;
writeln!(out, "\nint main() {{")?;
}
let indent = Indent(1);
writeln!(out, "{indent}PodStructGen {SG};")?;
let mut acc = String::new();
for rec in ast.recs.iter().filter(|rb| (self.record_filter)(rb)) {
acc.clear();
match rec {
RecBinding::Def(def) => def.emit_structgen(&mut acc, 1),
RecBinding::Forward(forward) => forward.emit_structgen(&mut acc, 1),
}
writeln!(out, "{acc}")?;
}
writeln!(out, "{indent}{SG}.finish();\n}}")?;
Ok(())
}
pub fn generate_cpp(&self, ast: &AstConsumer<'_>, out: &mut impl Write) -> anyhow::Result<()> {
self.generate_size_asserts(ast, out)?;
self.generate_cpp_functions(ast, out, 0)?;
Ok(())
}
/// Generates the static assert code used to verify that every structgen
/// POD type is the same size as the C++ type it is wrapping
pub fn generate_size_asserts(
&self,
ast: &AstConsumer<'_>,
out: &mut impl Write,
) -> anyhow::Result<()> {
writeln!(
out,
"using namespace physx;\n#include \"structgen_out.hpp\"\n"
)?;
for rec in ast.recs.iter().filter_map(|rb| {
if let RecBinding::Def(def) = rb {
if (self.record_filter)(rb) {
return Some(def);
}
}
None
}) {
let name = rec.name;
writeln!(out, "static_assert(sizeof(physx::{name}) == sizeof(physx_{name}), \"POD wrapper for `physx::{name}` has incorrect size\");")?;
}
writeln!(out)?;
Ok(())
}
/// Generates the C functions used to convert between the C bridge types
/// and calls into the C++ code
pub fn generate_cpp_functions(
&self,
ast: &AstConsumer<'_>,
out: &mut impl Write,
level: u32,
) -> anyhow::Result<()> {
let indent = Indent(level);
writeln!(out, "{indent}extern \"C\" {{")?;
let mut acc = String::new();
for func in ast.funcs.iter().filter(|fb| (self.func_filter)(fb)) {
acc.clear();
func.emit_cpp(&mut acc, level + 1)?;
writeln!(out, "{acc}")?;
}
writeln!(out, "{indent}}}")?;
Ok(())
}
pub fn generate_rust(&self, ast: &AstConsumer<'_>, w: &mut impl Write) -> anyhow::Result<()> {
let level = 0;
self.generate_rust_enums(ast, w, level)?;
self.generate_rust_records(ast, w)?;
self.generate_rust_functions(ast, w, level)?;
Ok(())
}
pub fn generate_rust_enums(
&self,
ast: &AstConsumer<'_>,
writer: &mut impl Write,
level: u32,
) -> anyhow::Result<u32> {
let mut fiter = ast.flags.iter().peekable();
let mut acc = String::new();
const INT_ENUMS: &[(&str, Builtin, &str)] = &[
("PxConcreteType", Builtin::UShort, "Undefined"),
("PxD6Drive", Builtin::USize, "Count"),
];
for (enum_binding, flags_binding) in ast.enums.iter().enumerate().filter_map(|(i, eb)| {
let fb = if fiter.peek().map_or(false, |f| f.enums_index == i) {
fiter.next()
} else {
None
};
if (self.enum_filter)(eb) {
Some((eb, fb))
} else {
None
}
}) {
if !acc.is_empty() {
acc.clear();
writesln!(acc);
}
enum_binding.emit_rust(&mut acc, level);
if let Some((builtin, default)) = INT_ENUMS.iter().find_map(|(name, bi, def)| {
if *name == enum_binding.name {
Some((*bi, *def))
} else {
None
}
}) {
writesln!(acc);
enum_binding.emit_rust_conversion(&mut acc, level, builtin, default);
}
if let Some(flags) = flags_binding {
writesln!(acc);
flags.emit_rust(enum_binding, &mut acc, level);
}
write!(writer, "{acc}")?;
}
Ok((ast.enums.len() + ast.flags.len()) as u32)
}
pub fn generate_rust_records(
&self,
ast: &AstConsumer<'_>,
writer: &mut impl Write,
) -> anyhow::Result<u32> {
let mut num = 0;
let mut acc = String::new();
for rec in ast.recs.iter().filter(|rb| (self.record_filter)(rb)) {
acc.clear();
writesln!(acc);
match rec {
RecBinding::Def(def) => {
if def.emit_rust(&mut acc, 0) {
num += 1;
write!(writer, "{acc}")?;
}
}
RecBinding::Forward(forward) => {
if matches!(ast.classes.get(forward.name), Some(None)) {
forward.emit_rust(&mut acc, 0);
write!(writer, "{acc}")?;
num += 1;
}
}
}
}
Ok(num)
}
pub fn generate_rust_functions(
&self,
ast: &AstConsumer<'_>,
w: &mut impl Write,
level: u32,
) -> anyhow::Result<u32> {
let indent = Indent(level);
writeln!(w, "{indent}extern \"C\" {{")?;
let mut acc = String::new();
for func in ast.funcs.iter().filter(|fb| (self.func_filter)(fb)) {
acc.clear();
func.emit_rust(&mut acc, level + 1);
writeln!(w, "{acc}")?;
}
writeln!(w, "{indent}}}")?;
Ok(0)
}
}
struct RustIdent<'ast>(&'ast str);
impl<'ast> fmt::Display for RustIdent<'ast> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
static KEYWORDS: &[&str] = &["box", "type", "ref"];
f.write_str(self.0)?;
if KEYWORDS.contains(&self.0) {
f.write_str("_")?;
}
Ok(())
}
}

View File

@@ -0,0 +1,40 @@
use super::Indent;
impl<'ast> crate::consumer::Comment<'ast> {
pub(super) fn emit_rust(&self, writer: &mut String, level: u32) {
let indent = Indent(level);
let emit_lines = |w: &mut String, lines: &[&str]| {
for line in lines {
if line.is_empty() {
writesln!(w, "{indent}///");
// PhysX _tends_ to use `#type::field/variant` style intralinks
// so attempt to convert them to proper rustdoc style intralinks
} else if let Some(ind) = line.find('#') {
writes!(w, "{indent}/// {}", &line[..ind]);
writes!(w, "[`");
match line[ind + 1..]
.find(|c: char| !c.is_alphanumeric() && c != ':' && c != '_')
{
Some(end) => {
writes!(w, "{}`]", &line[ind + 1..ind + end + 1]);
writesln!(w, "{}", &line[ind + end + 1..]);
}
None => {
writesln!(w, "{}`]", &line[ind + 1..]);
}
};
} else {
writesln!(w, "{indent}/// {line}");
}
}
};
emit_lines(writer, self.summary.as_slice());
if !self.additional.is_empty() {
writesln!(writer, "{indent}///");
emit_lines(writer, self.additional.as_slice());
}
}
}

View File

@@ -0,0 +1,144 @@
use crate::consumer::{Builtin, EnumBinding};
use super::Indent;
/// Fixes enum variant names from the ugly C++ style of `eWHY_ARE_YOU_SHOUTING`
/// to `WhyAreYouShouting`
fn fix_variant_name(s: &str) -> String {
let no_e = s
.strip_prefix('e')
.filter(|s| s.chars().next().unwrap().is_ascii_alphabetic())
.unwrap_or(s);
use heck::ToUpperCamelCase;
no_e.to_upper_camel_case()
}
impl<'ast> EnumBinding<'ast> {
pub fn emit_rust(&self, w: &mut String, level: u32) {
if let Some(com) = &self.comment {
com.emit_rust(w, level);
}
let indent = Indent(level);
let indent1 = Indent(level + 1);
writesln!(w, "{indent}#[derive(Debug, Clone, Copy, PartialEq, Eq)]");
writesln!(w, "{indent}#[repr({})]", self.repr.rust_type());
writesln!(w, "{indent}pub enum {} {{", self.name);
for var in &self.variants {
if let Some(com) = &var.comment {
com.emit_rust(w, level + 1);
}
writesln!(
w,
"{indent1}{} = {},",
fix_variant_name(var.name),
var.value
);
}
writesln!(w, "{indent}}}");
}
pub fn emit_rust_conversion(&self, w: &mut String, level: u32, from: Builtin, default: &str) {
let indent = Indent(level);
let indent1 = Indent(level + 1);
let indent2 = Indent(level + 2);
let int_type = from.rust_type();
writesln!(w, "{indent}impl From<{int_type}> for {} {{", self.name);
writesln!(w, "{indent1}fn from(val: {int_type}) -> Self {{");
writesln!(w, "{indent2}#[allow(clippy::match_same_arms)]");
writesln!(w, "{indent2}match val {{");
let indentm = Indent(level + 3);
for var in &self.variants {
writesln!(
w,
"{indentm}{} => Self::{},",
var.value,
fix_variant_name(var.name)
);
}
writesln!(w, "{indentm}_ => Self::{default},");
writesln!(w, "{indent2}}}");
writesln!(w, "{indent1}}}");
writesln!(w, "{indent}}}");
}
}
impl<'ast> crate::consumer::FlagsBinding<'ast> {
pub fn emit_rust(&self, enum_binding: &EnumBinding<'ast>, w: &mut String, level: u32) {
let indent = Indent(level);
let indent1 = Indent(level + 1);
let indent2 = Indent(level + 2);
writesln!(w, "{indent}bitflags::bitflags! {{");
writesln!(w, "{indent1}/// Flags for [`{}`]", enum_binding.name);
writesln!(w, "{indent1}#[derive(Default)]");
writesln!(w, "{indent1}#[repr(transparent)]");
writesln!(
w,
"{indent1}pub struct {}: {} {{",
self.name,
self.storage_type.rust_type()
);
for var in &enum_binding.variants {
// If used as flags, ignore emitting any zero value, see
// https://docs.rs/bitflags/1.3.2/bitflags/#zero-flags
if var.value == 0 {
continue;
}
// if let Some(com) = &var.comment {
// com.emit_rust(writer, level + 2)?;
// }
writes!(w, "{indent2}const {} = ", fix_variant_name(var.name));
// Since bitflags are made up of power of 2 values that can
// be combined, and the PhysX API sometimes defines named
// combinations of flags, reconstruct the bitflags to be
// easier to read
let val = var.value as u64;
if val & (val - 1) == 0 {
writes!(w, "1 << {}", val.ilog2());
} else {
let mut is_combo = false;
// If we're not a power of 2, we're a combination of flags,
// find which ones and emit them in a friendly way
for (i, which) in enum_binding
.variants
.iter()
.filter_map(|var| {
let prev = var.value as u64;
(prev & (prev - 1) == 0 && (prev & val) != 0).then_some(var.name)
})
.enumerate()
{
is_combo = true;
if i > 0 {
writes!(w, " | ");
}
writes!(w, "Self::{}.bits", fix_variant_name(which));
}
// There are a couple of cases where they're not combos, so just
// emit the raw value
if !is_combo {
writes!(w, "0x{val:08x}");
}
}
writesln!(w, ";");
}
writesln!(w, "{indent1}}}\n{indent}}}");
}
}

View File

@@ -0,0 +1,291 @@
use super::Indent;
use crate::consumer::{functions::*, QualType};
const RET: &str = "return_val";
const RET_POD: &str = "return_val_pod";
impl<'ast> Param<'ast> {
fn write_c_to_cpp(&self, out: &mut String, level: u32) {
let name = &self.name;
let indent = Indent(level);
match &self.kind {
QualType::Builtin(bi) => {
// This means we got a pod type by value, so we need to copy
// from the C pod to the C++ type
writesln!(out, "{indent}{} {name};", bi.cpp_type());
writesln!(out, "{indent}memcpy(&{name}, &{name}_pod, sizeof({name}));");
}
QualType::Pointer { .. } => {
let ty = self.kind.cpp_type();
writesln!(
out,
"{indent}{ty} {name} = reinterpret_cast<{ty}>({name}_pod);",
);
}
QualType::Reference { pointee, .. } => {
if let QualType::Builtin(bi) = pointee.as_ref() {
if !bi.is_pod() {
writesln!(
out,
"{indent}{} {name} = *{name}_pod;",
self.kind.cpp_type()
);
return;
}
}
let ty = self.kind.cpp_type();
writesln!(
out,
"{indent}{ty} {name} = reinterpret_cast<{ty}>(*{name}_pod);",
);
}
QualType::Enum { .. } => {
writesln!(
out,
"{indent}auto {name} = static_cast<{}>({name}_pod);",
self.kind.cpp_type(),
);
}
QualType::Flags { .. } => {
writesln!(
out,
"{indent}auto {name} = {}({name}_pod);",
self.kind.cpp_type()
);
}
QualType::Record { .. } => {
writesln!(out, "{indent}{} {name};", self.kind.cpp_type());
writesln!(out, "{indent}memcpy(&{name}, &{name}_pod, sizeof({name}));");
}
_ => panic!(
"parameter '{}' kind '{:?}' is not supported ",
self.name, self.kind
),
}
}
}
impl<'ast> FuncBinding<'ast> {
pub(super) fn emit_cpp(&self, acc: &mut String, level: u32) -> anyhow::Result<()> {
// Emit the function signature, this uses the C pod types that we've
// generated to wrap the underlying physx C++ types
{
let indent = Indent(level);
writes!(acc, "{indent}");
if let Some(ret) = &self.ret {
writes!(acc, "{}", ret.c_type());
} else {
acc.push_str("void");
}
writes!(acc, " {}(", self.name);
for (i, param) in self.params.iter().enumerate() {
let sep = if i > 0 { ", " } else { "" };
writes!(
acc,
"{sep}{} {}{}",
param.kind.c_type(),
param.name,
if param.kind.is_pod() { "_pod" } else { "" }
);
}
writesln!(acc, ") {{");
}
let indent = Indent(level + 1);
// Emit the code that converts each argument to an appropriately typed/named
// c++ variable
if !self.params.is_empty() {
for param in self.params.iter().filter(|p| p.kind.is_pod()) {
param.write_c_to_cpp(acc, level + 1);
}
}
let (invoke, arg_skip) = match &self.ext {
FuncBindingExt::IsDelete(_) => {
writesln!(acc, "{indent}delete self_;\n{}}}", Indent(level));
return Ok(());
}
FuncBindingExt::None(inv) => (inv, 0),
FuncBindingExt::HasSelf(inv) => (inv, 1),
};
// Emit the code that actually calls into physx
let args = self
.params
.iter()
.skip(arg_skip)
.map(|param| param.name.as_ref());
invoke.emit(args, self.ret.as_ref(), acc, level + 1);
if let Some(ret) = &self.ret {
if !ret.is_pod() {
writesln!(acc, "{indent}return {RET};");
} else {
// If we're a pod type we need to generate the code to convert
// from the C++ type to the C wrapper
match ret {
QualType::Builtin(bi) => {
writesln!(acc, "{indent}{} {RET_POD};", bi.c_type());
writesln!(
acc,
"{indent}memcpy(&{RET_POD}, &{RET}, sizeof({RET_POD}));"
);
}
QualType::Record { name } => {
writesln!(acc, "{indent}physx_{name} {RET_POD};");
writesln!(
acc,
"{indent}memcpy(&{RET_POD}, &{RET}, sizeof({RET_POD}));"
);
}
QualType::Enum { repr, .. } | QualType::Flags { repr, .. } => {
writesln!(acc, "{indent}{} {RET_POD};", repr.c_type());
writesln!(
acc,
"{indent}memcpy(&{RET_POD}, &{RET}, sizeof({RET_POD}));"
);
}
QualType::Pointer { .. } => {
writesln!(
acc,
"{indent}auto {RET_POD} = reinterpret_cast<{}>({RET});",
ret.c_type()
);
}
QualType::Reference { .. } => {
writesln!(
acc,
"{indent}auto {RET_POD} = reinterpret_cast<{}>(&{RET});",
ret.c_type()
);
}
#[allow(clippy::unimplemented)]
_ => {
unimplemented!("return type is not supported {:?}", ret);
}
}
writesln!(acc, "{indent}return {RET_POD};");
}
}
writesln!(acc, "{}}}", Indent(level));
Ok(())
}
pub(super) fn emit_rust(&self, writer: &mut String, level: u32) {
if let Some(com) = &self.comment {
com.emit_rust(writer, level);
}
let indent = Indent(level);
let mut acc = String::new();
writes!(acc, "{indent}pub fn {}(", self.name);
for (i, param) in self.params.iter().enumerate() {
// While Rust allows trailing commas in function signatures, it's
// kind of ugly
let sep = if i > 0 { ", " } else { "" };
writes!(
acc,
"{sep}{}: {}",
super::RustIdent(&param.name),
param.kind.rust_type()
);
}
if let Some(ret) = &self.ret {
writes!(acc, ") -> {};", ret.rust_type());
} else {
writes!(acc, ");");
}
writesln!(writer, "{acc}");
}
}
impl<'ast> PhysxInvoke<'ast> {
fn emit(
&self,
args: impl Iterator<Item = &'ast str>,
return_type: Option<&QualType<'ast>>,
out: &mut String,
level: u32,
) {
let mut args = args.peekable();
let has_args = args.peek().is_some();
let indent = Indent(level);
// Would be nice with https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.intersperse
// but it's not stable yet
let emit_args = |out: &mut String| {
for (i, arg) in args.enumerate() {
let sep = if i > 0 { ", " } else { "" };
writes!(out, "{sep}{arg}");
}
};
match self {
Self::Func { func_name, .. } => {
writes!(out, "{indent}");
if let Some(ret) = return_type {
writes!(out, "{} {RET} = ", ret.cpp_type());
}
writes!(out, "{func_name}(");
emit_args(out);
writes!(out, ");\n");
}
Self::Method {
func_name,
class_name,
is_static,
} => {
writes!(out, "{indent}");
if let Some(ret) = return_type {
writes!(out, "{} {RET} = ", ret.cpp_type());
}
if *is_static {
writes!(out, "{class_name}::{func_name}(");
} else {
writes!(out, "self_->{func_name}(");
}
emit_args(out);
writes!(out, ");\n");
}
Self::Ctor(class) => {
writes!(out, "{indent}{class} {RET}");
// Deal with Most Vexing Parse, thanks C++
if has_args {
writes!(out, "(");
emit_args(out);
writes!(out, ");\n");
} else {
writes!(out, ";\n");
}
}
Self::New(class) => {
writes!(out, "{indent}auto {RET} = new physx::{}(", class);
emit_args(out);
writes!(out, ");\n");
}
}
}
}

View File

@@ -0,0 +1,189 @@
use super::{Indent, SG, UOF};
use crate::consumer::QualType;
impl<'ast> crate::consumer::RecBindingDef<'ast> {
pub(super) fn emit_structgen(&self, writer: &mut String, level: u32) {
if self.calc_layout {
self.emit_structgen_calc(writer, level);
} else {
self.emit_structgen_passthrough(writer, level);
}
}
fn emit_structgen_calc(&self, w: &mut String, level: u32) {
let indent = Indent(level);
let indent1 = Indent(level + 1);
let indent2 = Indent(level + 2);
let name = self.name;
writesln!(
w,
"{indent}struct physx_{name}: public physx::{name} {{"
);
writesln!(w, "{indent1}static void dump_layout(PodStructGen& {SG}) {{");
writesln!(
w,
r#"{indent2}{SG}.begin_struct("physx_{name}", "{name}");"#
);
for field in &self.fields {
if !field.is_public || field.is_reference {
continue;
}
let fname = field.name;
let cpp_type = field.kind.cpp_type();
let rust_type = field.kind.rust_type();
writes!(w, "{indent2}{SG}.add_field(\"");
// We need to handle arrays specially since they break the pattern of literally every other
// C type since the element and array lengths are split by the identifier. Sigh.
if let QualType::Array { element, len } = &field.kind {
// There are a couple of cases of 2-dimensional arrays :p
if let QualType::Array {
element: inner,
len: len1,
} = &**element
{
writes!(w, "{} {fname}[{len1}]", inner.c_type());
} else {
writes!(w, "{} {fname}", element.c_type());
}
writes!(w, "[{len}]");
} else {
let c_type = field.kind.c_type();
writes!(w, "{c_type} {fname}");
}
writesln!(
w,
r#"", "{}", "{rust_type}", sizeof({cpp_type}), {UOF}(physx_{name}, {fname}));"#,
super::RustIdent(fname),
);
}
writesln!(w, "{indent2}{SG}.end_struct(sizeof(physx::{name}));");
writesln!(w, "{indent1}}}\n{indent}}};");
writesln!(w, "{indent}physx_{name}::dump_layout({SG});");
}
fn emit_structgen_passthrough(&self, w: &mut String, level: u32) {
let indent = Indent(level);
let cindent = Indent(1);
writes!(
w,
"{indent}{SG}.pass_thru(\"{} physx_{}",
if !matches!(self.ast.tag_used, Some(crate::consumer::Tag::Union)) {
"struct"
} else {
"union"
},
self.name
);
if self.ast.definition_data.is_none() {
writesln!(w, ";\\n\");");
return;
}
writes!(w, " {{\\n");
if self.has_vtable {
writes!(w, "{cindent}void* vtable_;\\n");
}
for field in &self.fields {
if let QualType::Array { element, len } = &field.kind {
writes!(w, "{cindent}{} {}[{len}];\\n", element.c_type(), field.name);
} else {
writes!(w, "{cindent}{} {};\\n", field.kind.c_type(), field.name);
}
}
writes!(w, "}};\\n\");");
}
pub fn emit_rust(&self, w: &mut String, level: u32) -> bool {
if self.calc_layout {
return false;
}
let indent = Indent(level);
let indent1 = Indent(level + 1);
let is_union = matches!(self.ast.tag_used, Some(crate::consumer::Tag::Union));
writesln!(w, "{indent}#[derive(Clone, Copy)]");
if !is_union {
writesln!(
w,
"{indent}#[cfg_attr(feature = \"debug-structs\", derive(Debug))]"
);
}
writesln!(w, "{indent}#[repr(C)]");
writesln!(
w,
"{indent}pub {} {} {{",
if is_union { "union" } else { "struct" },
self.name
);
if self.has_vtable {
writesln!(w, "{indent1}vtable_: *const std::ffi::c_void,");
}
for field in &self.fields {
if field.is_public {
writes!(w, "{indent1}pub ");
}
writesln!(w, "{}: {},", field.name, field.kind.rust_type());
}
writesln!(w, "{indent}}}");
// Unions can't have derived Debug impls so we make our own
if is_union {
let indent2 = Indent(level + 2);
writesln!(w, "{indent}#[cfg(feature = \"debug-structs\")]");
writesln!(w, "{indent}impl std::fmt::Debug for {} {{", self.name);
writesln!(
w,
"{indent1}fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {{"
);
writesln!(w, "{indent2}f.write_str(\"{}\")", self.name);
writesln!(w, "{indent1}}}");
writesln!(w, "{indent}}}");
}
true
}
}
impl<'ast> crate::consumer::RecBindingForward<'ast> {
pub fn emit_structgen(&self, w: &mut String, level: u32) {
let indent = Indent(level);
writes!(
w,
"{indent}{SG}.pass_thru(\"struct physx_{};\\n\");",
self.name
);
}
pub fn emit_rust(&self, w: &mut String, level: u32) {
let indent = Indent(level);
let indent1 = Indent(level + 1);
writesln!(w, "{indent}#[derive(Copy, Clone)]");
writesln!(w, "{indent}#[repr(C)]");
writesln!(w, "{indent}pub struct {} {{", self.name);
writesln!(w, "{indent1}_unused: [u8; 0],");
writesln!(w, "}}");
}
}

View File

@@ -0,0 +1,7 @@
pub mod consumer;
mod dump;
pub mod generator;
pub use dump::*;
pub type Node = clang_ast::Node<consumer::Item>;

View File

@@ -0,0 +1,35 @@
use anyhow::Context as _;
fn main() -> anyhow::Result<()> {
env_logger::init();
// It takes clang++ around 30 seconds to dump the JSON, so just keep a file
// around to reduce iteration times
let root = if let Ok(json) = std::fs::read("ast-dump.json") {
serde_json::from_slice(&json).context("failed to parse ast-dump.json")?
} else {
// This is the root API include that includes all the other public APIs
let api_h = format!("{}/PxPhysicsAPI.h", pxbind::get_include_dir()?);
let (root, raw) = pxbind::get_parsed_ast(api_h)?;
std::fs::write("ast-dump.json", raw).context("failed to write ast-dump.json")?;
root
};
let mut ast = pxbind::consumer::AstConsumer::default();
ast.consume(&root)?;
let rr: std::path::PathBuf = pxbind::get_repo_root()?.into();
use std::fs::File;
let mut structgen = File::create(rr.join("physx-sys/src/structgen/structgen.cpp"))?;
let mut cpp = File::create(rr.join("physx-sys/src/physx_generated.hpp"))?;
let mut rust = File::create(rr.join("physx-sys/src/physx_generated.rs"))?;
let generator = pxbind::generator::Generator::default();
generator.generate_all(&ast, &mut structgen, &mut cpp, &mut rust)?;
Ok(())
}

View File

@@ -0,0 +1 @@
{"rustc_fingerprint":1020393412623493685,"outputs":{"15729799797837862367":{"success":true,"status":"","code":0,"stdout":"___.exe\nlib___.rlib\n___.dll\n___.dll\n___.lib\n___.dll\nC:\\Users\\Kuju\\.rustup\\toolchains\\stable-x86_64-pc-windows-msvc\npacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"msvc\"\ntarget_family=\"windows\"\ntarget_feature=\"cmpxchg16b\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_feature=\"sse3\"\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"windows\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"pc\"\nwindows\n","stderr":""},"4614504638168534921":{"success":true,"status":"","code":0,"stdout":"rustc 1.78.0 (9b00956e5 2024-04-29)\nbinary: rustc\ncommit-hash: 9b00956e56009bab2aa15d7bff10916599e3d6d6\ncommit-date: 2024-04-29\nhost: x86_64-pc-windows-msvc\nrelease: 1.78.0\nLLVM version: 18.1.2\n","stderr":""}},"successes":{}}

View File

@@ -0,0 +1,3 @@
Signature: 8a477f597d28d172789f06886806bc55
# This file is a cache directory tag created by cargo.
# For information about cache directory tags see https://bford.info/cachedir/

View File

@@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@@ -0,0 +1 @@
{"rustc":208723055495880444,"features":"[\"perf-literal\", \"std\"]","declared_features":"","target":12812136000324506373,"profile":12206360443249279867,"path":514390540837453068,"deps":[[15818844694086178958,"memchr",false,3696284910854410102]],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\aho-corasick-7325719205e84efb\\dep-lib-aho_corasick"}}],"rustflags":[],"metadata":13904389431191498124,"config":2202906307356721367,"compile_kind":0}

View File

@@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@@ -0,0 +1 @@
{"rustc":208723055495880444,"features":"[\"default\", \"std\"]","declared_features":"","target":18338613112069040866,"profile":12206360443249279867,"path":3398623636968603478,"deps":[[16045357212464686133,"build_script_build",false,4921420730370980292]],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\anyhow-33087781d5b7a33f\\dep-lib-anyhow"}}],"rustflags":[],"metadata":17154292783084528516,"config":2202906307356721367,"compile_kind":0}

View File

@@ -0,0 +1 @@
{"rustc":208723055495880444,"features":"[\"default\", \"std\"]","declared_features":"","target":2297296889237502566,"profile":13232757476167777671,"path":13951969448738311833,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\anyhow-ab7c18cf3a7b5f66\\dep-build-script-build-script-build"}}],"rustflags":[],"metadata":17154292783084528516,"config":2202906307356721367,"compile_kind":0}

View File

@@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@@ -0,0 +1 @@
{"rustc":208723055495880444,"features":"","declared_features":"","target":0,"profile":0,"path":0,"deps":[[16045357212464686133,"build_script_build",false,766602063617872809]],"local":[{"RerunIfChanged":{"output":"debug\\build\\anyhow-eda7ba776fa5260e\\output","paths":["build/probe.rs"]}},{"RerunIfEnvChanged":{"var":"RUSTC_BOOTSTRAP","val":null}}],"rustflags":[],"metadata":0,"config":0,"compile_kind":0}

View File

@@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@@ -0,0 +1 @@
{"rustc":208723055495880444,"features":"[]","declared_features":"","target":11726655330667564086,"profile":12206360443249279867,"path":18095504893307942540,"deps":[[1098045598771442027,"rustc_hash",false,10046108987582848972],[2511671586729554969,"serde",false,3009812188610348422]],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\clang-ast-d481a288a7b0d16c\\dep-lib-clang_ast"}}],"rustflags":[],"metadata":15365661418732589432,"config":2202906307356721367,"compile_kind":0}

View File

@@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@@ -0,0 +1 @@
{"rustc":208723055495880444,"features":"[\"auto-color\", \"color\", \"default\", \"humantime\", \"regex\"]","declared_features":"","target":17553807293545275602,"profile":12206360443249279867,"path":6357041016660639553,"deps":[[2500285171997094844,"termcolor",false,1632694092393299734],[5523453112889398513,"is_terminal",false,8855430130372177659],[10187828652899488954,"log",false,8060913817175892080],[13547796294171082677,"humantime",false,4574831503798447096],[14982752537185592049,"regex",false,15571578597231113558]],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\env_logger-56e99d5d442b8f7b\\dep-lib-env_logger"}}],"rustflags":[],"metadata":16604235976610830136,"config":2202906307356721367,"compile_kind":0}

View File

@@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@@ -0,0 +1 @@
{"rustc":208723055495880444,"features":"[\"default\"]","declared_features":"","target":11271119367433188140,"profile":12206360443249279867,"path":12172711469631106695,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\heck-02b7e56dd051b17e\\dep-lib-heck"}}],"rustflags":[],"metadata":4968006677088137060,"config":2202906307356721367,"compile_kind":0}

View File

@@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@@ -0,0 +1 @@
{"rustc":208723055495880444,"features":"[]","declared_features":"","target":10444203147181933371,"profile":12206360443249279867,"path":4372706294445872892,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\humantime-4629a5ab77780d49\\dep-lib-humantime"}}],"rustflags":[],"metadata":16972751450777833143,"config":2202906307356721367,"compile_kind":0}

View File

@@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@@ -0,0 +1 @@
{"rustc":208723055495880444,"features":"[]","declared_features":"","target":8847024640214747843,"profile":12206360443249279867,"path":4377015741861967496,"deps":[[11426986729031771186,"windows_sys",false,2836039361729776079]],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\is-terminal-0a7ad9ee6b95796f\\dep-lib-is-terminal"}}],"rustflags":[],"metadata":10282796769989993602,"config":2202906307356721367,"compile_kind":0}

View File

@@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@@ -0,0 +1 @@
{"rustc":208723055495880444,"features":"[]","declared_features":"","target":17114873591667335244,"profile":12206360443249279867,"path":5271807057032177272,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\itoa-efe7cef573b48b40\\dep-lib-itoa"}}],"rustflags":[],"metadata":851671291587502216,"config":2202906307356721367,"compile_kind":0}

View File

@@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@@ -0,0 +1 @@
{"rustc":208723055495880444,"features":"[\"std\"]","declared_features":"","target":10943587141627988751,"profile":12206360443249279867,"path":9668473457380251647,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\log-f3a5f9f964d58f6f\\dep-lib-log"}}],"rustflags":[],"metadata":179143468214550567,"config":2202906307356721367,"compile_kind":0}

View File

@@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@@ -0,0 +1 @@
{"rustc":208723055495880444,"features":"[\"alloc\", \"std\"]","declared_features":"","target":13876443730220172507,"profile":12206360443249279867,"path":16869016088127395208,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\memchr-994f69aff981d39f\\dep-lib-memchr"}}],"rustflags":[],"metadata":7513296495906230968,"config":2202906307356721367,"compile_kind":0}

View File

@@ -0,0 +1 @@
{"rustc":208723055495880444,"features":"","declared_features":"","target":0,"profile":0,"path":0,"deps":[[18024560886749334364,"build_script_build",false,4758730596670865345]],"local":[{"RerunIfChanged":{"output":"debug\\build\\proc-macro2-54f73788dbe949be\\output","paths":["build/probe.rs"]}},{"RerunIfEnvChanged":{"var":"RUSTC_BOOTSTRAP","val":null}}],"rustflags":[],"metadata":0,"config":0,"compile_kind":0}

View File

@@ -0,0 +1 @@
{"rustc":208723055495880444,"features":"[\"proc-macro\"]","declared_features":"","target":427768481117760528,"profile":13232757476167777671,"path":13607430533783183389,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\proc-macro2-b8d17f71a68360db\\dep-build-script-build-script-build"}}],"rustflags":[],"metadata":7635439851376710101,"config":2202906307356721367,"compile_kind":0}

View File

@@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@@ -0,0 +1 @@
{"rustc":208723055495880444,"features":"[\"proc-macro\"]","declared_features":"","target":2187471303096632034,"profile":13232757476167777671,"path":3452992316074397663,"deps":[[10045147784146067611,"unicode_ident",false,16873762277770283682],[18024560886749334364,"build_script_build",false,14099534650456690116]],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\proc-macro2-c511da7308677aa7\\dep-lib-proc_macro2"}}],"rustflags":[],"metadata":7635439851376710101,"config":2202906307356721367,"compile_kind":0}

View File

@@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@@ -0,0 +1 @@
{"rustc":208723055495880444,"features":"[]","declared_features":"","target":14407593714901199159,"profile":11597332650809196192,"path":17523903030608720598,"deps":[[235571525104734574,"env_logger",false,6452457866812819064],[1287732937133714181,"clang_ast",false,9673769248703814250],[2511671586729554969,"serde",false,3009812188610348422],[7898347539352098708,"serde_json",false,4381860357230674670],[10187828652899488954,"log",false,8060913817175892080],[11709930968028960932,"heck",false,7224161933702600197],[16045357212464686133,"anyhow",false,7294044523911409589]],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\pxbind-7b5c5eabd80e626f\\dep-lib-pxbind"}}],"rustflags":[],"metadata":7797948686568424061,"config":2202906307356721367,"compile_kind":0}

View File

@@ -0,0 +1 @@
{"rustc":208723055495880444,"features":"[]","declared_features":"","target":625391877448926851,"profile":11597332650809196192,"path":1684066648322511884,"deps":[[235571525104734574,"env_logger",false,6452457866812819064],[1287732937133714181,"clang_ast",false,9673769248703814250],[2511671586729554969,"serde",false,3009812188610348422],[7898347539352098708,"serde_json",false,4381860357230674670],[10187828652899488954,"log",false,8060913817175892080],[11709930968028960932,"heck",false,7224161933702600197],[16045357212464686133,"anyhow",false,7294044523911409589],[18151506772420311404,"pxbind",false,16741452645108434703]],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\pxbind-8860ffe5ff381603\\dep-bin-pxbind"}}],"rustflags":[],"metadata":7797948686568424061,"config":2202906307356721367,"compile_kind":0}

View File

@@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@@ -0,0 +1 @@
{"rustc":208723055495880444,"features":"[\"proc-macro\"]","declared_features":"","target":10824007166531090010,"profile":13232757476167777671,"path":8278568829579911698,"deps":[[18024560886749334364,"proc_macro2",false,1246332558987601219]],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\quote-6572dbe8242ebcbd\\dep-lib-quote"}}],"rustflags":[],"metadata":2717943770976187624,"config":2202906307356721367,"compile_kind":0}

View File

@@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@@ -0,0 +1 @@
{"rustc":208723055495880444,"features":"[\"perf\", \"perf-backtrack\", \"perf-cache\", \"perf-dfa\", \"perf-inline\", \"perf-literal\", \"perf-onepass\", \"std\"]","declared_features":"","target":16142358731464406428,"profile":12206360443249279867,"path":14124468467888855413,"deps":[[7325384046744447800,"aho_corasick",false,12640726259290341141],[12099698704509148049,"regex_syntax",false,3045103758781574214],[14373384308191128698,"regex_automata",false,11585689692279149770],[15818844694086178958,"memchr",false,3696284910854410102]],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\regex-ae8b68fa9aa6fefa\\dep-lib-regex"}}],"rustflags":[],"metadata":3256615787768725874,"config":2202906307356721367,"compile_kind":0}

View File

@@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@@ -0,0 +1 @@
{"rustc":208723055495880444,"features":"[\"alloc\", \"dfa-onepass\", \"hybrid\", \"meta\", \"nfa-backtrack\", \"nfa-pikevm\", \"nfa-thompson\", \"perf-inline\", \"perf-literal\", \"perf-literal-multisubstring\", \"perf-literal-substring\", \"std\", \"syntax\"]","declared_features":"","target":448595855551789158,"profile":12206360443249279867,"path":11649580546086723798,"deps":[[7325384046744447800,"aho_corasick",false,12640726259290341141],[12099698704509148049,"regex_syntax",false,3045103758781574214],[15818844694086178958,"memchr",false,3696284910854410102]],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\regex-automata-448c8a222c61f315\\dep-lib-regex_automata"}}],"rustflags":[],"metadata":8878122455581797878,"config":2202906307356721367,"compile_kind":0}

View File

@@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@@ -0,0 +1 @@
{"rustc":208723055495880444,"features":"[\"std\"]","declared_features":"","target":4790163008085533209,"profile":12206360443249279867,"path":11373887691066036009,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\regex-syntax-6cf651013ee141d8\\dep-lib-regex_syntax"}}],"rustflags":[],"metadata":17586400164587752172,"config":2202906307356721367,"compile_kind":0}

View File

@@ -0,0 +1 @@
This file has an mtime of when this was started.

Some files were not shown because too many files have changed in this diff Show More