Skip to content

Commit

Permalink
Dev work (#21)
Browse files Browse the repository at this point in the history
- Generate with 19.5.716
- Add parameter tag API
- Re-export bindings as ffi::raw
- Other missing APIs
- Add HAPI_GetOutputNodeId
- Get/Set cache properties API
---------

Co-authored-by: Aleksei Rusev <hou.alexx@gmail.com>
  • Loading branch information
alexxbb authored Dec 19, 2023
1 parent 70d9d19 commit 416c8dc
Show file tree
Hide file tree
Showing 29 changed files with 1,559 additions and 142 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ build
backup
otls/backup
cmake-build-*
bindgen/hapi-bindgen/bindings.rs
env.ps1
**/*target
_build
*.hip*
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# hapi-rs changelog

## [0.9.3]
- Bump Houdini version to `19.5.716`.
- Add API for working with parameter tags.
- String parameters of type Node can take `NodeHandle` values.
- More PDG WorkItem APIs.
- Expose cache APIs.

## [0.9.2]
### New
- Add `NumericAttribute::read_into()` method for reusing a buffer when reading attribute data.
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ repository = "https://github.com/alexxbb/hapi-rs/"
keywords = ["vfx", "graphics", "gamedev", "houdini"]
categories = ["graphics", "game-development"]
readme = "README.md"
version = "0.9.2"
version = "0.9.3"
authors = ["Aleksei Rusev <hou.alexx@gmail.com>"]
edition = "2021"
license = "MIT"
Expand Down
2 changes: 1 addition & 1 deletion apps/render_cop/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ edition = "2021"

[dependencies]
hapi-rs = {path="../.."}
iced = {version = "0.6.0", features = ["image"]}
iced = {version = "0.10.0", features = ["image"]}
anyhow = "1.0.66"
resolve-path = "0.1.0"
2 changes: 1 addition & 1 deletion apps/render_cop/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ impl Sandbox for App {
fn view(&self) -> Element<'_, Self::Message> {
let slider = slider(0.0..=1.0, self.input, Message::InputChanged)
.step(0.05)
.width(Length::Units(300));
.width(Length::Fixed(300.0));
let noise = pick_list(
&[Noise::Alligator, Noise::Voronoi][..],
self.noise,
Expand Down
9 changes: 9 additions & 0 deletions bindgen/coverage/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[package]
name = "coverage"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
regex-lite = "0.1.0"
74 changes: 74 additions & 0 deletions bindgen/coverage/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#![allow(dead_code, unused)]
use regex_lite::{Regex, RegexBuilder};
use std::collections::HashSet;
use std::hash::{Hash, Hasher};
use std::path::Path;

fn raw_hapi_function_names() -> HashSet<Item> {
const IGNORE_SUFFIX: &[&str] = &[
"_IsString",
"_IsFloat",
"_IsInt",
"_AreEqual",
"_IsPath",
"_IsNode",
"_Create",
"_Init",
];
let raw = Path::new("../../src/ffi/bindings.rs");
let text = std::fs::read_to_string(&raw).expect("bindings.rs");
let rx = regex_lite::Regex::new(r#"pub fn (HAPI\w+)\("#).unwrap();
let matches: HashSet<_> = rx
.captures_iter(&text)
.filter_map(|m| {
let name = &m[1];
let skip = IGNORE_SUFFIX.iter().any(|suf| name.ends_with(suf));
if skip {
None
} else {
Some(Item(name.to_string()))
}
})
.collect();
matches
}

#[derive(Debug, Eq)]
struct Item(String);

impl PartialEq for Item {
fn eq(&self, other: &Item) -> bool {
self.0.to_lowercase() == other.0.to_lowercase()
}
}

impl Hash for Item {
fn hash<H: Hasher>(&self, hasher: &mut H) {
self.0.to_lowercase().hash(hasher)
}
}

fn wrapped_rs_function_names() -> HashSet<Item> {
let rx1 = Regex::new(r#"raw::(HAPI\w+)\(?"#).unwrap();
let rx2 = Regex::new(r#"\[(HAPI\w+)\]"#).unwrap();
let rx3 = Regex::new(r#".*raw::(HAPI\w+)\("#).unwrap();

let text = std::fs::read_to_string("../../src/ffi/functions.rs").expect("functions.rs");
let it1 = rx1.captures_iter(&text).map(|c| Item(c[1].to_string()));

let text = std::fs::read_to_string("../../src/attribute/bindings.rs").expect("functions.rs");
let it2 = rx2.captures_iter(&text).map(|c| Item(c[1].to_string()));

let it3 = rx3.captures_iter(&text).map(|c| Item(c[1].to_string()));
HashSet::from_iter(it1.chain(it2).chain(it3))
}

fn main() {
let raw = raw_hapi_function_names();
let rs = wrapped_rs_function_names();
for r in raw.iter() {
if !rs.contains(r) {
println!("Missing {r:?}");
}
}
}
13 changes: 13 additions & 0 deletions bindgen/hapi-bindgen/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "hapi-bindgen"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
heck = "0.4.1"
bindgen = "0.66.1"
once_cell = "1.18.0"
argh = "0.1.10"
anyhow = "1.0.71"
234 changes: 234 additions & 0 deletions bindgen/hapi-bindgen/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
#![allow(dead_code)]
#![allow(unused)]

use anyhow::Context;
use argh::FromArgs;
use std::cell::RefCell;
use std::collections::HashMap;
use std::env::var;
use std::path::{Path, PathBuf};

use bindgen::callbacks::{EnumVariantValue, ParseCallbacks};
use once_cell::sync::Lazy;

#[derive(Debug, Copy, Clone)]
pub enum StripMode {
/// Strip N items at front, e.g N=1: FOO_BAR_ZOO => BAR_ZOO
StripFront(u8),
/// Keeps N items at tail, e.g N=1: FOO_BAR_ZOO => ZOO
KeepTail(u8),
}

impl StripMode {
pub fn new(m: i32) -> Self {
if m < 0 {
StripMode::KeepTail(m.abs() as u8)
} else {
StripMode::StripFront(m as u8)
}
}

pub fn strip_long_name<'a>(&self, name: &'a str) -> &'a str {
let mut iter = name.match_indices('_');
let elem = match self {
StripMode::KeepTail(i) => iter.nth_back((i - 1) as usize),
StripMode::StripFront(i) => iter.nth((i - 1) as usize),
};
let new_name = match elem {
Some((idx, _)) => &name[idx + 1..name.len()],
None => {
eprintln!("{} Not enough length: {}", line!(), name);
name
}
};
match new_name.chars().take(1).next() {
None => {
eprintln!("{} Empty string {}", line!(), name);
name
}
// If after first pass the name starts with a digit (illegal name) do another pass
Some(c) if c.is_digit(10) => match self {
StripMode::StripFront(v) => StripMode::StripFront(v + 1),
StripMode::KeepTail(v) => StripMode::KeepTail(v + 1),
}
.strip_long_name(name),
Some(_) => new_name,
}
}
}

static ENUMS: Lazy<HashMap<&str, (&str, i32)>> = Lazy::new(|| {
// -N translates to StripMode::StripFront(N)
// N translates to StripMode::KeepFront(N)
let mut map = HashMap::new();
map.insert("HAPI_License", ("auto", -2));
map.insert("HAPI_Result", ("HapiResult", 2));
map.insert("HAPI_StatusType", ("auto", -2));
map.insert("HAPI_State", ("auto", 2));
map.insert("HAPI_PDG_WorkItemState", ("PdgWorkItemState", -1));
map.insert("HAPI_PDG_EventType", ("PdgEventType", -3));
map.insert("HAPI_PDG_State", ("PdgState", -1));
map.insert("HAPI_CacheProperty", ("auto", -2));
map.insert("HAPI_EnvIntType", ("auto", -2));
map.insert("HAPI_PrmScriptType", ("auto", -2));
map.insert("HAPI_Permissions", ("auto", -2));
map.insert("HAPI_ParmType", ("auto", 2));

map.insert("HAPI_PartType", ("auto", -1));
map.insert("HAPI_StatusVerbosity", ("auto", -1));
map.insert("HAPI_SessionType", ("auto", -1));
map.insert("HAPI_PackedPrimInstancingMode", ("auto", -1));
map.insert("HAPI_RampType", ("auto", -1));
map.insert("HAPI_ErrorCode", ("auto", -3));
map.insert("HAPI_NodeFlags", ("auto", -1));
map.insert("HAPI_NodeType", ("auto", -1));
map.insert("HAPI_HeightFieldSampling", ("auto", -1));
map.insert("HAPI_SessionEnvIntType", ("auto", -1));
map.insert("HAPI_ImagePacking", ("auto", -1));
map.insert("HAPI_ImageDataFormat", ("auto", -1));
map.insert("HAPI_XYZOrder", ("auto", -1));
map.insert("HAPI_RSTOrder", ("auto", -1));
map.insert("HAPI_TransformComponent", ("auto", -1));
map.insert("HAPI_CurveOrders", ("auto", -1));
map.insert("HAPI_InputType", ("auto", -1));
map.insert("HAPI_GeoType", ("auto", -1));
map.insert("HAPI_AttributeTypeInfo", ("auto", -1));
map.insert("HAPI_StorageType", ("auto", -1));
map.insert("HAPI_VolumeVisualType", ("auto", -1));
map.insert("HAPI_VolumeType", ("auto", -1));
map.insert("HAPI_CurveType", ("auto", -1));
map.insert("HAPI_AttributeOwner", ("auto", -1));
map.insert("HAPI_GroupType", ("auto", -1));
map.insert("HAPI_PresetType", ("auto", -1));
map.insert("HAPI_ChoiceListType", ("auto", -1));
map.insert("HAPI_InputCurveMethod", ("auto", -1));
map.insert("HAPI_InputCurveParameterization", ("auto", -1));
map
});

#[derive(Debug)]
struct Rustifier {
visited: RefCell<HashMap<String, Vec<String>>>,
}

impl ParseCallbacks for Rustifier {
fn enum_variant_name(
&self,
_enum_name: Option<&str>,
_variant_name: &str,
_variant_value: EnumVariantValue,
) -> Option<String> {
if _enum_name.is_none() {
return None;
};
let name = _enum_name
.unwrap()
.strip_prefix("enum ")
.expect("Not enum?");
self.visited
.borrow_mut()
.entry(name.to_string())
.and_modify(|variants| variants.push(_variant_name.to_string()))
.or_default();
let (_, _mode) = ENUMS.get(name).expect(&format!("Missing enum: {}", name));
let mode = StripMode::new(*_mode);
let mut striped = mode.strip_long_name(_variant_name);
// Two stripped variant names can collide with each other. We take a dumb approach by
// attempting to strip one more time with increased step
if let Some(vars) = self.visited.borrow_mut().get_mut(name) {
let _stripped = striped.to_string();
if vars.contains(&_stripped) {
let mode = StripMode::new(*_mode - 1);
striped = mode.strip_long_name(_variant_name);
} else {
vars.push(_stripped);
}
}
Some(heck::AsUpperCamelCase(striped).to_string())
}

fn item_name(&self, _item_name: &str) -> Option<String> {
if let Some((rename, _)) = ENUMS.get(_item_name) {
let new_name = match *rename {
"auto" => _item_name
.strip_prefix("HAPI_")
.expect(&format!("{} - not a HAPI enum?", rename)),
n => n,
};
return Some(new_name.to_string());
}
None
}
}

#[derive(FromArgs, Debug)]
/// Houdini engine raw bindings generator.
struct Args {
/// absolute path to Houdini install
#[argh(option)]
hfs: String,
/// directory to output the bindings file. Default to CWD.
#[argh(option)]
outdir: Option<String>,

/// rust style naming converntion
#[argh(option, default = "true")]
rustify: bool,
}

fn main() -> anyhow::Result<()> {
let args: Args = argh::from_env();
let hfs = Path::new(&args.hfs);
if !hfs.is_dir() {
anyhow::bail!("Invalid HFS directory")
}
let out_path = match args.outdir.as_ref() {
Some(dir) => PathBuf::from(dir),
None => std::env::current_dir()?,
}
.join("bindings.rs");

let include_dir = hfs.join("toolkit/include/HAPI");

let builder = bindgen::Builder::default()
.header("wrapper.h")
.clang_arg(format!("-I{}", include_dir.to_string_lossy()))
.detect_include_paths(true)
.default_enum_style("rust_non_exhaustive".parse().unwrap())
.bitfield_enum("NodeType")
.bitfield_enum("NodeFlags")
.bitfield_enum("ErrorCode")
.prepend_enum_name(false)
.generate_comments(false)
.derive_copy(true)
.derive_debug(true)
.derive_hash(false)
.derive_eq(false)
.derive_partialeq(false)
.disable_name_namespacing()
// .rustfmt_bindings(true)
.layout_tests(false)
.raw_line(format!(
"// Houdini version {}",
hfs.file_name().unwrap().to_string_lossy()
))
.raw_line(format!(
"// hapi-sys version {}",
var("CARGO_PKG_VERSION").unwrap()
));
let builder = if args.rustify {
let callbacks = Box::new(Rustifier {
visited: Default::default(),
});
builder.parse_callbacks(callbacks)
} else {
builder
};
builder
.generate()
.context("bindgen failed")?
.write_to_file(out_path.clone())
.context("Could not write bindings to file")?;
println!("Generated: {}", out_path.to_string_lossy());
Ok(())
}
10 changes: 10 additions & 0 deletions bindgen/hapi-bindgen/wrapper.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#ifndef HAPI_RS_WRAPPER_H
#define HAPI_RS_WRAPPER_H

#include "HAPI.h"
#include "HAPI_Common.h"
#include "HAPI_Version.h"
#include "HAPI_Helpers.h"
#include "HAPI_API.h"

#endif //HAPI_RS_WRAPPER_H
2 changes: 2 additions & 0 deletions env.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
$env:HFS = "C:\Houdini\19.5.716";
$env:PATH += ";${env:HFS}\bin"
Loading

0 comments on commit 416c8dc

Please sign in to comment.