use log::{debug, error, warn};
use parking_lot::ReentrantMutex;
use std::ffi::OsString;
use std::fmt::Debug;
use std::path::PathBuf;
use std::process::Child;
use std::time::Duration;
use std::{ffi::CString, path::Path, sync::Arc};
pub use crate::{
asset::AssetLibrary,
errors::*,
ffi::{enums::*, CookOptions, ImageFileFormat, SessionSyncInfo, TimelineOptions, Viewport},
node::{HoudiniNode, ManagerNode, ManagerType, NodeHandle, NodeType, Transform},
parameter::Parameter,
stringhandle::StringArray,
};
pub type SessionState = State;
use crate::stringhandle::StringHandle;
use crate::{ffi::raw, utils};
pub struct NodeBuilder<'s> {
session: &'s Session,
name: String,
label: Option<String>,
parent: Option<NodeHandle>,
cook: bool,
}
impl<'s> NodeBuilder<'s> {
pub fn with_label(mut self, label: impl Into<String>) -> Self {
self.label = Some(label.into());
self
}
pub fn with_parent<H: AsRef<NodeHandle>>(mut self, parent: H) -> Self {
self.parent.replace(*parent.as_ref());
self
}
pub fn cook(mut self, cook: bool) -> Self {
self.cook = cook;
self
}
pub fn create(self) -> Result<HoudiniNode> {
let NodeBuilder {
session,
name,
label,
parent,
cook,
} = self;
session.create_node_with(&name, parent, label.as_deref(), cook)
}
}
impl PartialEq for raw::HAPI_Session {
fn eq(&self, other: &Self) -> bool {
self.type_ == other.type_ && self.id == other.id
}
}
pub trait EnvVariable {
type Type: ?Sized + ToOwned + Debug;
fn get_value(session: &Session, key: impl AsRef<str>)
-> Result<<Self::Type as ToOwned>::Owned>;
fn set_value(session: &Session, key: impl AsRef<str>, val: &Self::Type) -> Result<()>;
}
impl EnvVariable for str {
type Type = str;
fn get_value(session: &Session, key: impl AsRef<str>) -> Result<String> {
let key = CString::new(key.as_ref())?;
let handle = crate::ffi::get_server_env_str(session, &key)?;
crate::stringhandle::get_string(handle, session)
}
fn set_value(session: &Session, key: impl AsRef<str>, val: &Self::Type) -> Result<()> {
let key = CString::new(key.as_ref())?;
let val = CString::new(val)?;
crate::ffi::set_server_env_str(session, &key, &val)
}
}
impl EnvVariable for Path {
type Type = Self;
fn get_value(session: &Session, key: impl AsRef<str>) -> Result<PathBuf> {
let key = CString::new(key.as_ref())?;
crate::stringhandle::get_string(crate::ffi::get_server_env_str(session, &key)?, session)
.map(PathBuf::from)
}
fn set_value(session: &Session, key: impl AsRef<str>, val: &Self::Type) -> Result<()> {
let key = CString::new(key.as_ref())?;
let val = utils::path_to_cstring(val)?;
crate::ffi::set_server_env_str(session, &key, &val)
}
}
impl EnvVariable for i32 {
type Type = Self;
fn get_value(session: &Session, key: impl AsRef<str>) -> Result<Self::Type> {
let key = CString::new(key.as_ref())?;
crate::ffi::get_server_env_int(session, &key)
}
fn set_value(session: &Session, key: impl AsRef<str>, val: &Self::Type) -> Result<()> {
let key = CString::new(key.as_ref())?;
crate::ffi::set_server_env_int(session, &key, *val)
}
}
#[derive(Debug, Clone)]
pub enum CookResult {
Succeeded,
Warnings,
Errored(String),
}
#[derive(Debug, Clone)]
pub enum ConnectionType {
ThriftPipe(OsString),
ThriftSocket(std::net::SocketAddrV4),
InProcess,
Custom,
}
#[derive(Debug)]
pub(crate) struct SessionInner {
pub(crate) handle: raw::HAPI_Session,
pub(crate) options: SessionOptions,
pub(crate) connection: ConnectionType,
pub(crate) lock: ReentrantMutex<()>,
}
#[derive(Debug, Clone)]
pub struct Session {
pub(crate) inner: Arc<SessionInner>,
}
impl PartialEq for Session {
fn eq(&self, other: &Self) -> bool {
self.inner.handle.id == other.inner.handle.id
&& self.inner.handle.type_ == other.inner.handle.type_
}
}
impl Session {
fn new(
handle: raw::HAPI_Session,
connection: ConnectionType,
options: SessionOptions,
) -> Session {
Session {
inner: Arc::new(SessionInner {
handle,
options,
connection,
lock: ReentrantMutex::new(()),
}),
}
}
pub fn session_type(&self) -> SessionType {
self.inner.handle.type_
}
pub fn connection_type(&self) -> &ConnectionType {
&self.inner.connection
}
#[inline(always)]
pub(crate) fn ptr(&self) -> *const raw::HAPI_Session {
&(self.inner.handle) as *const _
}
pub fn set_server_var<T: EnvVariable + ?Sized>(
&self,
key: &str,
value: &T::Type,
) -> Result<()> {
debug_assert!(self.is_valid());
debug!("Setting server variable {key}={value:?}");
T::set_value(self, key, value)
}
pub fn get_server_var<T: EnvVariable + ?Sized>(
&self,
key: &str,
) -> Result<<T::Type as ToOwned>::Owned> {
debug_assert!(self.is_valid());
T::get_value(self, key)
}
pub fn get_server_variables(&self) -> Result<StringArray> {
debug_assert!(self.is_valid());
let count = crate::ffi::get_server_env_var_count(self)?;
let handles = crate::ffi::get_server_env_var_list(self, count)?;
crate::stringhandle::get_string_array(&handles, self)
}
pub fn get_string(&self, handle: StringHandle) -> Result<String> {
crate::stringhandle::get_string(handle, self)
}
fn initialize(&self) -> Result<()> {
debug!("Initializing session");
debug_assert!(self.is_valid());
let res = crate::ffi::initialize_session(self, &self.inner.options);
match res {
Ok(_) => Ok(()),
Err(HapiError {
kind: Kind::Hapi(HapiResult::AlreadyInitialized),
..
}) => {
warn!("Session already initialized, skipping");
Ok(())
}
Err(e) => Err(e),
}
}
pub fn cleanup(&self) -> Result<()> {
debug!("Cleaning session");
debug_assert!(self.is_valid());
crate::ffi::cleanup_session(self)
}
pub fn is_initialized(&self) -> bool {
debug_assert!(self.is_valid());
crate::ffi::is_session_initialized(self)
}
pub fn create_input_node(&self, name: &str) -> Result<crate::geometry::Geometry> {
debug!("Creating input node: {}", name);
debug_assert!(self.is_valid());
let name = CString::new(name)?;
let id = crate::ffi::create_input_node(self, &name)?;
let node = HoudiniNode::new(self.clone(), NodeHandle(id), None)?;
let info = crate::geometry::GeoInfo::from_node(&node)?;
Ok(crate::geometry::Geometry { node, info })
}
pub fn create_input_curve_node(&self, name: &str) -> Result<crate::geometry::Geometry> {
debug!("Creating input curve node: {}", name);
debug_assert!(self.is_valid());
let name = CString::new(name)?;
let id = crate::ffi::create_input_curve_node(self, &name)?;
let node = HoudiniNode::new(self.clone(), NodeHandle(id), None)?;
let info = crate::geometry::GeoInfo::from_node(&node)?;
Ok(crate::geometry::Geometry { node, info })
}
pub fn create_node(&self, name: impl AsRef<str>) -> Result<HoudiniNode> {
self.create_node_with(name.as_ref(), None, None, false)
}
pub fn node_builder(&self, node_name: impl Into<String>) -> NodeBuilder {
NodeBuilder {
session: self,
name: node_name.into(),
label: None,
parent: None,
cook: false,
}
}
pub(crate) fn create_node_with<P>(
&self,
name: &str,
parent: P,
label: Option<&str>,
cook: bool,
) -> Result<HoudiniNode>
where
P: Into<Option<NodeHandle>>,
{
let parent = parent.into();
debug!("Creating node instance: {}", name);
debug_assert!(self.is_valid());
debug_assert!(
parent.is_some() || name.contains('/'),
"Node name must be fully qualified if parent is not specified"
);
debug_assert!(
!(parent.is_some() && name.contains('/')),
"Cannot use fully qualified node name with parent"
);
let name = CString::new(name)?;
let label = label.map(CString::new).transpose()?;
let id = crate::ffi::create_node(&name, label.as_deref(), self, parent, cook)?;
HoudiniNode::new(self.clone(), NodeHandle(id), None)
}
pub fn delete_node<H: Into<NodeHandle>>(&self, node: H) -> Result<()> {
crate::ffi::delete_node(node.into(), self)
}
pub fn get_node_from_path(
&self,
path: impl AsRef<str>,
parent: impl Into<Option<NodeHandle>>,
) -> Result<Option<HoudiniNode>> {
debug_assert!(self.is_valid());
debug!("Searching node at path: {}", path.as_ref());
let path = CString::new(path.as_ref())?;
match crate::ffi::get_node_from_path(self, parent.into(), &path) {
Ok(handle) => Ok(NodeHandle(handle).to_node(self).ok()),
Err(HapiError {
kind: Kind::Hapi(HapiResult::InvalidArgument),
..
}) => Ok(None),
Err(e) => Err(e),
}
}
pub fn find_parameter_from_path(&self, path: impl AsRef<str>) -> Result<Option<Parameter>> {
debug_assert!(self.is_valid());
debug!("Searching parameter at path: {}", path.as_ref());
let Some((path, parm)) = path.as_ref().rsplit_once('/') else {
return Ok(None);
};
let Some(node) = self.get_node_from_path(path, None)? else {
debug!("Node {} not found", path);
return Ok(None);
};
Ok(node.parameter(parm).ok())
}
pub fn get_manager_node(&self, manager: ManagerType) -> Result<ManagerNode> {
debug_assert!(self.is_valid());
debug!("Getting Manager node of type: {:?}", manager);
let node_type = NodeType::from(manager);
let handle = crate::ffi::get_manager_node(self, node_type)?;
Ok(ManagerNode {
session: self.clone(),
handle: NodeHandle(handle),
node_type: manager,
})
}
pub fn get_composed_object_transform(
&self,
parent: impl AsRef<NodeHandle>,
rst_order: RSTOrder,
) -> Result<Vec<Transform>> {
debug_assert!(self.is_valid());
crate::ffi::get_composed_object_transforms(self, *parent.as_ref(), rst_order).map(
|transforms| {
transforms
.into_iter()
.map(|tr| Transform { inner: tr })
.collect()
},
)
}
pub fn save_hip(&self, path: impl AsRef<Path>, lock_nodes: bool) -> Result<()> {
debug!("Saving hip file: {:?}", path.as_ref());
debug_assert!(self.is_valid());
let path = utils::path_to_cstring(path)?;
crate::ffi::save_hip(self, &path, lock_nodes)
}
pub fn load_hip(&self, path: impl AsRef<Path>, cook: bool) -> Result<()> {
debug!("Loading hip file: {:?}", path.as_ref());
debug_assert!(self.is_valid());
let path = utils::path_to_cstring(path)?;
crate::ffi::load_hip(self, &path, cook)
}
pub fn merge_hip(&self, name: &str, cook: bool) -> Result<i32> {
debug!("Merging hip file: {}", name);
debug_assert!(self.is_valid());
let name = CString::new(name)?;
crate::ffi::merge_hip(self, &name, cook)
}
pub fn get_hip_file_nodes(&self, hip_id: i32) -> Result<Vec<NodeHandle>> {
crate::ffi::get_hipfile_node_ids(self, hip_id)
.map(|handles| handles.into_iter().map(NodeHandle).collect())
}
pub fn load_asset_file(&self, file: impl AsRef<Path>) -> Result<AssetLibrary> {
debug_assert!(self.is_valid());
AssetLibrary::from_file(self.clone(), file)
}
pub fn get_loaded_asset_libraries(&self) -> Result<Vec<AssetLibrary>> {
debug_assert!(self.is_valid());
crate::ffi::get_asset_library_ids(self)?
.into_iter()
.map(|library_id| {
crate::ffi::get_asset_library_file_path(self, library_id).map(|lib_file| {
AssetLibrary {
lib_id: library_id,
session: self.clone(),
file: Some(PathBuf::from(lib_file)),
}
})
})
.collect::<Result<Vec<_>>>()
}
pub fn interrupt(&self) -> Result<()> {
debug_assert!(self.is_valid());
crate::ffi::interrupt(self)
}
pub fn get_status(&self, flag: StatusType) -> Result<SessionState> {
debug_assert!(self.is_valid());
crate::ffi::get_status(self, flag)
}
pub fn is_cooking(&self) -> Result<bool> {
debug_assert!(self.is_valid());
Ok(matches!(
self.get_status(StatusType::CookState)?,
SessionState::Cooking
))
}
#[inline(always)]
pub fn is_valid(&self) -> bool {
crate::ffi::is_session_valid(self)
}
pub fn get_status_string(
&self,
status: StatusType,
verbosity: StatusVerbosity,
) -> Result<String> {
debug_assert!(self.is_valid());
crate::ffi::get_status_string(self, status, verbosity)
}
pub fn get_cook_result_string(&self, verbosity: StatusVerbosity) -> Result<String> {
debug_assert!(self.is_valid());
self.get_status_string(StatusType::CookResult, verbosity)
}
pub fn cooking_total_count(&self) -> Result<i32> {
debug_assert!(self.is_valid());
crate::ffi::get_cooking_total_count(self)
}
pub fn cooking_current_count(&self) -> Result<i32> {
debug_assert!(self.is_valid());
crate::ffi::get_cooking_current_count(self)
}
pub fn cook(&self) -> Result<CookResult> {
debug!("Cooking session..");
debug_assert!(self.is_valid());
if self.inner.options.threaded {
loop {
match self.get_status(StatusType::CookState)? {
SessionState::Ready => break Ok(CookResult::Succeeded),
SessionState::ReadyWithFatalErrors => {
self.interrupt()?;
let err = self.get_cook_result_string(StatusVerbosity::Errors)?;
break Ok(CookResult::Errored(err));
}
SessionState::ReadyWithCookErrors => break Ok(CookResult::Warnings),
_ => {}
}
}
} else {
Ok(CookResult::Succeeded)
}
}
pub fn get_connection_error(&self, clear: bool) -> Result<String> {
debug_assert!(self.is_valid());
crate::ffi::get_connection_error(clear)
}
pub fn get_time(&self) -> Result<f32> {
debug_assert!(self.is_valid());
crate::ffi::get_time(self)
}
pub fn set_time(&self, time: f32) -> Result<()> {
debug_assert!(self.is_valid());
crate::ffi::set_time(self, time)
}
pub fn lock(&self) -> parking_lot::ReentrantMutexGuard<()> {
self.inner.lock.lock()
}
pub fn set_timeline_options(&self, options: TimelineOptions) -> Result<()> {
debug_assert!(self.is_valid());
crate::ffi::set_timeline_options(self, &options.inner)
}
pub fn get_timeline_options(&self) -> Result<TimelineOptions> {
debug_assert!(self.is_valid());
crate::ffi::get_timeline_options(self).map(|opt| TimelineOptions { inner: opt })
}
pub fn set_use_houdini_time(&self, do_use: bool) -> Result<()> {
debug_assert!(self.is_valid());
crate::ffi::set_use_houdini_time(self, do_use)
}
pub fn get_use_houdini_time(&self) -> Result<bool> {
debug_assert!(self.is_valid());
crate::ffi::get_use_houdini_time(self)
}
pub fn get_viewport(&self) -> Result<Viewport> {
debug_assert!(self.is_valid());
crate::ffi::get_viewport(self).map(|inner| Viewport { inner })
}
pub fn set_viewport(&self, viewport: &Viewport) -> Result<()> {
debug_assert!(self.is_valid());
crate::ffi::set_viewport(self, viewport)
}
pub fn set_sync(&self, enable: bool) -> Result<()> {
debug_assert!(self.is_valid());
crate::ffi::set_session_sync(self, enable)
}
pub fn get_sync_info(&self) -> Result<SessionSyncInfo> {
debug_assert!(self.is_valid());
crate::ffi::get_session_sync_info(self).map(|inner| SessionSyncInfo { inner })
}
pub fn set_sync_info(&self, info: &SessionSyncInfo) -> Result<()> {
debug_assert!(self.is_valid());
crate::ffi::set_session_sync_info(self, &info.inner)
}
pub fn get_license_type(&self) -> Result<License> {
debug_assert!(self.is_valid());
crate::ffi::session_get_license_type(self)
}
pub fn render_cop_to_image(
&self,
cop_node: impl Into<NodeHandle>,
image_planes: impl AsRef<str>,
path: impl AsRef<Path>,
) -> Result<String> {
debug!("Start rendering COP to image.");
let cop_node = cop_node.into();
debug_assert!(cop_node.is_valid(self)?);
crate::ffi::render_cop_to_image(self, cop_node)?;
crate::material::extract_image_to_file(self, cop_node, image_planes, path)
}
pub fn render_cop_to_memory(
&self,
cop_node: impl Into<NodeHandle>,
buffer: &mut Vec<u8>,
image_planes: impl AsRef<str>,
format: impl AsRef<str>,
) -> Result<()> {
debug!("Start rendering COP to memory.");
let cop_node = cop_node.into();
debug_assert!(cop_node.is_valid(self)?);
crate::ffi::render_cop_to_image(self, cop_node)?;
crate::material::extract_image_to_memory(self, cop_node, buffer, image_planes, format)
}
pub fn get_supported_image_formats(&self) -> Result<Vec<ImageFileFormat<'_>>> {
debug_assert!(self.is_valid());
crate::ffi::get_supported_image_file_formats(self).map(|v| {
v.into_iter()
.map(|inner| ImageFileFormat {
inner,
session: self.into(),
})
.collect()
})
}
pub fn get_active_cache_names(&self) -> Result<StringArray> {
debug_assert!(self.is_valid());
crate::ffi::get_active_cache_names(self)
}
pub fn get_cache_property_value(
&self,
cache_name: &str,
property: CacheProperty,
) -> Result<i32> {
let cache_name = CString::new(cache_name)?;
crate::ffi::get_cache_property(self, &cache_name, property)
}
pub fn set_cache_property_value(
&self,
cache_name: &str,
property: CacheProperty,
value: i32,
) -> Result<()> {
let cache_name = CString::new(cache_name)?;
crate::ffi::set_cache_property(self, &cache_name, property, value)
}
}
impl Drop for Session {
fn drop(&mut self) {
if Arc::strong_count(&self.inner) == 1 {
debug!("Dropping session");
if self.is_valid() {
if self.inner.options.cleanup {
if let Err(e) = self.cleanup() {
error!("Session cleanup failed in Drop: {}", e);
}
}
if let Err(e) = crate::ffi::shutdown_session(self) {
error!("Could not shutdown session in Drop: {}", e);
}
if let Err(e) = crate::ffi::close_session(self) {
error!("Closing session failed in Drop: {}", e);
}
} else {
warn!("Session is invalid!");
if let ConnectionType::ThriftPipe(f) = &self.inner.connection {
let _ = std::fs::remove_file(f);
}
}
}
}
}
pub fn connect_to_pipe(
pipe: impl AsRef<Path>,
options: Option<&SessionOptions>,
timeout: Option<Duration>,
) -> Result<Session> {
debug!("Connecting to Thrift session: {:?}", pipe.as_ref());
let c_str = utils::path_to_cstring(&pipe)?;
let pipe = pipe.as_ref().as_os_str().to_os_string();
let timeout = timeout.unwrap_or_default();
let mut waited = Duration::from_secs(0);
let wait_ms = Duration::from_millis(100);
let handle = loop {
let mut last_error = None;
debug!("Trying to connect to pipe server");
match crate::ffi::new_thrift_piped_session(&c_str) {
Ok(handle) => break handle,
Err(e) => {
last_error.replace(e);
std::thread::sleep(wait_ms);
waited += wait_ms;
}
}
if waited > timeout {
return Err(last_error.unwrap()).context("Connection timeout");
}
};
let connection = ConnectionType::ThriftPipe(pipe);
let session = Session::new(handle, connection, options.cloned().unwrap_or_default());
session.initialize()?;
Ok(session)
}
pub fn connect_to_socket(
addr: std::net::SocketAddrV4,
options: Option<&SessionOptions>,
) -> Result<Session> {
debug!("Connecting to socket server: {:?}", addr);
let host = CString::new(addr.ip().to_string()).expect("SocketAddr->CString");
let handle = crate::ffi::new_thrift_socket_session(addr.port() as i32, &host)?;
let connection = ConnectionType::ThriftSocket(addr);
let session = Session::new(handle, connection, options.cloned().unwrap_or_default());
session.initialize()?;
Ok(session)
}
pub fn new_in_process(options: Option<&SessionOptions>) -> Result<Session> {
debug!("Creating new in-process session");
let handle = crate::ffi::create_inprocess_session()?;
let connection = ConnectionType::InProcess;
let session = Session::new(handle, connection, options.cloned().unwrap_or_default());
session.initialize()?;
Ok(session)
}
#[derive(Clone, Debug)]
pub struct SessionOptions {
pub cook_opt: CookOptions,
pub threaded: bool,
pub cleanup: bool,
pub ignore_already_init: bool,
pub env_files: Option<CString>,
pub env_variables: Option<Vec<(String, String)>>,
pub otl_path: Option<CString>,
pub dso_path: Option<CString>,
pub img_dso_path: Option<CString>,
pub aud_dso_path: Option<CString>,
}
impl Default for SessionOptions {
fn default() -> Self {
SessionOptions {
cook_opt: CookOptions::default(),
threaded: false,
cleanup: false,
ignore_already_init: true,
env_files: None,
env_variables: None,
otl_path: None,
dso_path: None,
img_dso_path: None,
aud_dso_path: None,
}
}
}
#[derive(Default)]
pub struct SessionOptionsBuilder {
cook_opt: CookOptions,
threaded: bool,
cleanup: bool,
ignore_already_init: bool,
env_variables: Option<Vec<(String, String)>>,
env_files: Option<CString>,
otl_path: Option<CString>,
dso_path: Option<CString>,
img_dso_path: Option<CString>,
aud_dso_path: Option<CString>,
}
impl SessionOptionsBuilder {
pub fn houdini_env_files<I>(mut self, files: I) -> Self
where
I: IntoIterator,
I::Item: AsRef<str>,
{
let paths = utils::join_paths(files);
self.env_files
.replace(CString::new(paths).expect("Zero byte"));
self
}
pub fn env_variables<I, K, V>(mut self, variables: I) -> Self
where
I: IntoIterator<Item = (K, V)>,
K: Into<String>,
V: Into<String>,
{
self.env_variables.replace(
variables
.into_iter()
.map(|(k, v)| (k.into(), v.into()))
.collect(),
);
self
}
pub fn otl_search_paths<I>(mut self, paths: I) -> Self
where
I: IntoIterator,
I::Item: AsRef<str>,
{
let paths = utils::join_paths(paths);
self.otl_path
.replace(CString::new(paths).expect("Zero byte"));
self
}
pub fn dso_search_paths<P>(mut self, paths: P) -> Self
where
P: IntoIterator,
P::Item: AsRef<str>,
{
let paths = utils::join_paths(paths);
self.dso_path
.replace(CString::new(paths).expect("Zero byte"));
self
}
pub fn image_search_paths<P>(mut self, paths: P) -> Self
where
P: IntoIterator,
P::Item: AsRef<str>,
{
let paths = utils::join_paths(paths);
self.img_dso_path
.replace(CString::new(paths).expect("Zero byte"));
self
}
pub fn audio_search_paths<P>(mut self, paths: P) -> Self
where
P: IntoIterator,
P::Item: AsRef<str>,
{
let paths = utils::join_paths(paths);
self.aud_dso_path
.replace(CString::new(paths).expect("Zero byte"));
self
}
pub fn ignore_already_init(mut self, ignore: bool) -> Self {
self.ignore_already_init = ignore;
self
}
pub fn cook_options(mut self, options: CookOptions) -> Self {
self.cook_opt = options;
self
}
pub fn threaded(mut self, threaded: bool) -> Self {
self.threaded = threaded;
self
}
pub fn cleanup_on_close(mut self, cleanup: bool) -> Self {
self.cleanup = cleanup;
self
}
pub fn build(mut self) -> SessionOptions {
self.write_temp_env_file();
SessionOptions {
cook_opt: self.cook_opt,
threaded: self.threaded,
cleanup: self.cleanup,
ignore_already_init: self.cleanup,
env_files: self.env_files,
env_variables: self.env_variables,
otl_path: self.otl_path,
dso_path: self.dso_path,
img_dso_path: self.img_dso_path,
aud_dso_path: self.aud_dso_path,
}
}
fn write_temp_env_file(&mut self) {
use std::io::Write;
if let Some(ref env) = self.env_variables {
let mut file = tempfile::Builder::new()
.suffix("_hars.env")
.tempfile()
.expect("tempfile");
for (k, v) in env.iter() {
writeln!(file, "{}={}", k, v).expect("write to .env file");
}
let (_, tmp_file) = file.keep().expect("persistent tempfile");
debug!(
"Creating temporary environment file: {}",
tmp_file.to_string_lossy()
);
let tmp_file = CString::new(tmp_file.to_string_lossy().to_string()).expect("null byte");
if let Some(old) = &mut self.env_files {
let mut bytes = old.as_bytes_with_nul().to_vec();
bytes.extend(tmp_file.into_bytes_with_nul());
self.env_files
.replace(unsafe { CString::from_vec_with_nul_unchecked(bytes) });
} else {
self.env_files.replace(tmp_file);
}
}
}
}
impl SessionOptions {
pub fn builder() -> SessionOptionsBuilder {
SessionOptionsBuilder::default()
}
}
impl From<i32> for SessionState {
fn from(s: i32) -> Self {
match s {
0 => SessionState::Ready,
1 => SessionState::ReadyWithFatalErrors,
2 => SessionState::ReadyWithCookErrors,
3 => SessionState::StartingCook,
4 => SessionState::Cooking,
5 => SessionState::StartingLoad,
6 => SessionState::Loading,
7 => SessionState::Max,
_ => unreachable!(),
}
}
}
pub fn start_engine_pipe_server(
path: impl AsRef<Path>,
auto_close: bool,
timeout: f32,
verbosity: StatusVerbosity,
log_file: Option<&str>,
) -> Result<u32> {
debug!("Starting named pipe server: {:?}", path.as_ref());
let opts = crate::ffi::raw::HAPI_ThriftServerOptions {
autoClose: auto_close as i8,
timeoutMs: timeout,
verbosity,
};
let log_file = log_file.map(CString::new).transpose()?;
let c_str = utils::path_to_cstring(path)?;
crate::ffi::clear_connection_error()?;
crate::ffi::start_thrift_pipe_server(&c_str, &opts, log_file.as_deref())
}
pub fn start_engine_socket_server(
port: u16,
auto_close: bool,
timeout: i32,
verbosity: StatusVerbosity,
log_file: Option<&str>,
) -> Result<u32> {
debug!("Starting socket server on port: {}", port);
let opts = crate::ffi::raw::HAPI_ThriftServerOptions {
autoClose: auto_close as i8,
timeoutMs: timeout as f32,
verbosity,
};
let log_file = log_file.map(CString::new).transpose()?;
crate::ffi::clear_connection_error()?;
crate::ffi::start_thrift_socket_server(port as i32, &opts, log_file.as_deref())
}
pub fn start_houdini_server(
pipe_name: impl AsRef<str>,
houdini_executable: impl AsRef<Path>,
fx_license: bool,
) -> Result<Child> {
std::process::Command::new(houdini_executable.as_ref())
.arg(format!("-hess=pipe:{}", pipe_name.as_ref()))
.arg(if fx_license {
"-force-fx-license"
} else {
"-core"
})
.stdin(std::process::Stdio::null())
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.spawn()
.map_err(HapiError::from)
}
pub fn quick_session(options: Option<&SessionOptions>) -> Result<Session> {
let file = tempfile::Builder::new()
.suffix("-hars.pipe")
.tempfile()
.expect("new temp file");
let (_, file) = file.keep().expect("persistent temp file");
start_engine_pipe_server(&file, true, 4000.0, StatusVerbosity::Statusverbosity1, None)?;
connect_to_pipe(file, options, None)
}
#[cfg(test)]
pub(crate) mod tests {
use crate::session::*;
use once_cell::sync::Lazy;
pub(crate) static SESSION: Lazy<Session> = Lazy::new(|| {
env_logger::init();
let session = quick_session(None).expect("Could not create test session");
session
.load_asset_file("otls/hapi_geo.hda")
.expect("load asset");
session
.load_asset_file("otls/hapi_vol.hda")
.expect("load asset");
session
.load_asset_file("otls/hapi_parms.hda")
.expect("load asset");
session
.load_asset_file("otls/sesi/SideFX_spaceship.hda")
.expect("load asset");
session
});
}