1use log::{debug, error};
20use parking_lot::ReentrantMutex;
21use std::fmt::Debug;
22use std::path::PathBuf;
23use std::{ffi::CString, path::Path, sync::Arc};
24
25pub use crate::{
26 asset::AssetLibrary,
27 errors::*,
28 ffi::{
29 CompositorOptions, CookOptions, ImageFileFormat, SessionInfo, SessionSyncInfo,
30 ThriftServerOptions, TimelineOptions, Viewport, enums::*,
31 },
32 node::{HoudiniNode, ManagerNode, ManagerType, NodeHandle, NodeType, Transform},
33 parameter::Parameter,
34 server::ServerOptions,
35 stringhandle::StringArray,
36};
37
38pub type SessionState = State;
40pub type LicenseType = raw::License;
41
42use crate::cop::CopImageDescription;
43use crate::ffi::ImageInfo;
44use crate::stringhandle::StringHandle;
45use crate::{ffi::raw, utils};
46
47pub struct NodeBuilder<'s> {
49 session: &'s Session,
50 name: String,
51 label: Option<String>,
52 parent: Option<NodeHandle>,
53 cook: bool,
54}
55
56impl NodeBuilder<'_> {
57 #[must_use]
59 pub fn with_label(mut self, label: impl Into<String>) -> Self {
60 self.label = Some(label.into());
61 self
62 }
63
64 #[must_use]
66 pub fn with_parent<H: AsRef<NodeHandle>>(mut self, parent: H) -> Self {
67 self.parent.replace(*parent.as_ref());
68 self
69 }
70
71 #[must_use]
73 pub fn cook(mut self, cook: bool) -> Self {
74 self.cook = cook;
75 self
76 }
77
78 pub fn create(self) -> Result<HoudiniNode> {
80 let NodeBuilder {
81 session,
82 name,
83 label,
84 parent,
85 cook,
86 } = self;
87 session.create_node_with(&name, parent, label.as_deref(), cook)
88 }
89}
90
91impl PartialEq for raw::HAPI_Session {
92 fn eq(&self, other: &Self) -> bool {
93 self.type_ == other.type_ && self.id == other.id
94 }
95}
96
97pub trait EnvVariable {
99 type Type: ?Sized + ToOwned + Debug;
100 fn get_value(session: &Session, key: impl AsRef<str>)
101 -> Result<<Self::Type as ToOwned>::Owned>;
102 fn set_value(session: &Session, key: impl AsRef<str>, val: &Self::Type) -> Result<()>;
103}
104
105impl EnvVariable for str {
106 type Type = str;
107
108 fn get_value(session: &Session, key: impl AsRef<str>) -> Result<String> {
109 let key = CString::new(key.as_ref())?;
110 let handle = crate::ffi::get_server_env_str(session, &key)?;
111 crate::stringhandle::get_string(handle, session)
112 }
113
114 fn set_value(session: &Session, key: impl AsRef<str>, val: &Self::Type) -> Result<()> {
115 let key = CString::new(key.as_ref())?;
116 let val = CString::new(val)?;
117 crate::ffi::set_server_env_str(session, &key, &val)
118 }
119}
120
121impl EnvVariable for Path {
122 type Type = Self;
123
124 fn get_value(session: &Session, key: impl AsRef<str>) -> Result<PathBuf> {
125 let key = CString::new(key.as_ref())?;
126 crate::stringhandle::get_string(crate::ffi::get_server_env_str(session, &key)?, session)
127 .map(PathBuf::from)
128 }
129
130 fn set_value(session: &Session, key: impl AsRef<str>, val: &Self::Type) -> Result<()> {
131 let key = CString::new(key.as_ref())?;
132 let val = utils::path_to_cstring(val)?;
133 crate::ffi::set_server_env_str(session, &key, &val)
134 }
135}
136
137impl EnvVariable for i32 {
138 type Type = Self;
139
140 fn get_value(session: &Session, key: impl AsRef<str>) -> Result<Self::Type> {
141 let key = CString::new(key.as_ref())?;
142 crate::ffi::get_server_env_int(session, &key)
143 }
144
145 fn set_value(session: &Session, key: impl AsRef<str>, val: &Self::Type) -> Result<()> {
146 let key = CString::new(key.as_ref())?;
147 crate::ffi::set_server_env_int(session, &key, *val)
148 }
149}
150
151#[derive(Debug, Clone, Eq, PartialEq)]
153pub enum CookResult {
154 Succeeded,
155 CookErrors(String),
157 FatalErrors(String),
159}
160
161impl CookResult {
162 #[must_use]
164 pub fn message(&self) -> Option<&str> {
165 match self {
166 Self::Succeeded => None,
167 Self::CookErrors(msg) | Self::FatalErrors(msg) => Some(msg.as_str()),
168 }
169 }
170}
171
172#[derive(Debug)]
174pub(crate) struct SessionInner {
175 pub(crate) handle: raw::HAPI_Session,
176 pub(crate) options: SessionOptions,
177 pub(crate) server_options: Option<ServerOptions>,
179 pub(crate) lock: ReentrantMutex<()>,
180 pub(crate) server_pid: Option<u32>,
181}
182
183#[derive(Debug, Clone)]
186pub struct Session {
187 pub(crate) inner: Arc<SessionInner>,
188}
189
190impl PartialEq for Session {
191 fn eq(&self, other: &Self) -> bool {
192 self.inner.handle.id == other.inner.handle.id
193 && self.inner.handle.type_ == other.inner.handle.type_
194 }
195}
196
197#[derive(Debug)]
198pub struct UninitializedSession {
199 pub(crate) session_handle: raw::HAPI_Session,
200 pub(crate) server_options: Option<ServerOptions>,
201 pub(crate) server_pid: Option<u32>,
202}
203
204impl UninitializedSession {
205 pub fn initialize(self, session_options: SessionOptions) -> Result<Session> {
206 debug!("Initializing session");
207 crate::ffi::initialize_session(self.session_handle, &session_options)
208 .map(|()| Session {
209 inner: Arc::new(SessionInner {
210 handle: self.session_handle,
211 options: session_options,
212 lock: ReentrantMutex::new(()),
213 server_options: self.server_options,
214 server_pid: self.server_pid,
215 }),
216 })
217 .with_context(|| "Calling initialize_session")
218 }
219}
220
221impl Session {
222 #[must_use]
224 pub fn session_type(&self) -> SessionType {
225 self.inner.handle.type_
226 }
227
228 #[must_use]
230 pub fn server_pid(&self) -> Option<u32> {
231 self.inner.server_pid
232 }
233
234 #[inline]
235 pub(crate) fn ptr(&self) -> *const raw::HAPI_Session {
236 &raw const self.inner.handle
237 }
238
239 pub fn set_server_var<T: EnvVariable + ?Sized>(
242 &self,
243 key: &str,
244 value: &T::Type,
245 ) -> Result<()> {
246 debug_assert!(self.is_valid());
247 debug!("Setting server variable {key}={value:?}");
248 T::set_value(self, key, value)
249 }
250
251 pub fn get_server_var<T: EnvVariable + ?Sized>(
253 &self,
254 key: &str,
255 ) -> Result<<T::Type as ToOwned>::Owned> {
256 debug_assert!(self.is_valid());
257 debug!("Querying server variable {key}");
258 T::get_value(self, key)
259 }
260
261 pub fn get_server_variables(&self) -> Result<StringArray> {
264 debug_assert!(self.is_valid());
265 debug!("Querying all server variables");
266 let count = crate::ffi::get_server_env_var_count(self)?;
267 let handles = crate::ffi::get_server_env_var_list(self, count)?;
268 crate::stringhandle::get_string_array(&handles, self).context("Calling get_string_array")
269 }
270
271 pub fn get_string(&self, handle: StringHandle) -> Result<String> {
273 crate::stringhandle::get_string(handle, self)
274 }
275
276 pub fn get_string_batch(&self, handles: &[StringHandle]) -> Result<StringArray> {
278 crate::stringhandle::get_string_array(handles, self)
279 }
280
281 pub fn set_custom_string(&self, string: impl AsRef<str>) -> Result<StringHandle> {
283 debug_assert!(self.is_valid());
284 debug!("Setting custom string: {}", string.as_ref());
285 let string = CString::new(string.as_ref())?;
286 crate::ffi::set_custom_string(self, &string)
287 }
288
289 pub fn remove_custom_string(&self, handle: StringHandle) -> Result<()> {
291 debug_assert!(self.is_valid());
292 debug!("Removing custom string: {handle:?}");
293 crate::ffi::remove_custom_string(self, handle)
294 }
295
296 pub fn cleanup(self) -> Result<()> {
298 debug!("Cleaning up session");
299 debug_assert!(self.is_valid());
300 crate::ffi::cleanup_session(&self)
301 }
302
303 pub fn create_input_node(
305 &self,
306 name: &str,
307 parent: Option<NodeHandle>,
308 ) -> Result<crate::geometry::Geometry> {
309 debug!("Creating input node: {name}");
310 debug_assert!(self.is_valid());
311 let name = CString::new(name)?;
312 let id = crate::ffi::create_input_node(self, &name, parent)?;
313 let node = HoudiniNode::new(self.clone(), NodeHandle(id), None)?;
314 crate::ffi::cook_node(&node, None).with_context(|| "Cooking input node")?;
315 let info =
316 crate::geometry::GeoInfo::from_node(&node).with_context(|| "Getting geometry info")?;
317 Ok(crate::geometry::Geometry { node, info })
318 }
319
320 pub fn create_input_curve_node(
322 &self,
323 name: &str,
324 parent: Option<NodeHandle>,
325 ) -> Result<crate::geometry::Geometry> {
326 debug!("Creating input curve node: {name}");
327 debug_assert!(self.is_valid());
328 let name = CString::new(name)?;
329 let id = crate::ffi::create_input_curve_node(self, &name, parent)?;
330 let node = HoudiniNode::new(self.clone(), NodeHandle(id), None)?;
331 let info = crate::geometry::GeoInfo::from_node(&node)?;
332 Ok(crate::geometry::Geometry { node, info })
333 }
334
335 pub fn create_node(&self, name: impl AsRef<str>) -> Result<HoudiniNode> {
340 self.create_node_with(name.as_ref(), None, None, false)
341 }
342
343 pub fn node_builder(&self, node_name: impl Into<String>) -> NodeBuilder<'_> {
345 NodeBuilder {
346 session: self,
347 name: node_name.into(),
348 label: None,
349 parent: None,
350 cook: false,
351 }
352 }
353
354 pub(crate) fn create_node_with<P>(
356 &self,
357 name: &str,
358 parent: P,
359 label: Option<&str>,
360 cook: bool,
361 ) -> Result<HoudiniNode>
362 where
363 P: Into<Option<NodeHandle>>,
364 {
365 let parent = parent.into();
366 debug!("Creating node instance for op: {name}, with parent: {parent:?}");
367 debug_assert!(self.is_valid());
368 debug_assert!(
369 parent.is_some() || name.contains('/'),
370 "Node name must be fully qualified if parent node is not specified"
371 );
372 debug_assert!(
373 !(parent.is_some() && name.contains('/')),
374 "Cannot use fully qualified node name with parent node"
375 );
376 let name = CString::new(name)?;
377 let label = label.map(CString::new).transpose()?;
378 let node_id = crate::ffi::create_node(&name, label.as_deref(), self, parent, cook)?;
379 if self.inner.options.threaded {
380 if let CookResult::FatalErrors(message) = self.cook()? {
382 return Err(HapiError::Hapi {
383 result_code: HapiResultCode(HapiResult::Failure),
384 server_message: Some(message),
385 contexts: Vec::new(),
386 });
387 }
388 }
389 HoudiniNode::new(self.clone(), NodeHandle(node_id), None)
390 }
391
392 pub fn delete_node<H: Into<NodeHandle>>(&self, node: H) -> Result<()> {
394 let node = node.into();
395 debug!(
396 "Deleting node {}",
397 node.path(self)
398 .unwrap_or_else(|_| "Could not get path".to_owned())
399 );
400 crate::ffi::delete_node(node, self)
401 }
402
403 pub fn get_node_from_path(
406 &self,
407 path: impl AsRef<str>,
408 parent: impl Into<Option<NodeHandle>>,
409 ) -> Result<Option<HoudiniNode>> {
410 debug_assert!(self.is_valid());
411 debug!("Searching node at path: {}", path.as_ref());
412 let path = CString::new(path.as_ref())?;
413 match crate::ffi::get_node_from_path(self, parent.into(), &path) {
414 Ok(handle) => Ok(NodeHandle(handle).to_node(self).ok()),
415 Err(HapiError::Hapi { result_code, .. })
416 if matches!(result_code.0, HapiResult::InvalidArgument) =>
417 {
418 Ok(None)
419 }
420 Err(e) => Err(e),
421 }
422 }
423
424 pub fn find_parameter_from_path(
426 &self,
427 path: impl AsRef<str>,
428 start: impl Into<Option<NodeHandle>>,
429 ) -> Result<Option<Parameter>> {
430 debug_assert!(self.is_valid());
431 debug!("Searching parameter at path: {}", path.as_ref());
432 let Some((path, parm)) = path.as_ref().rsplit_once('/') else {
433 return Ok(None);
434 };
435 let Some(node) = self.get_node_from_path(path, start)? else {
436 debug!("Node {path} not found");
437 return Ok(None);
438 };
439 Ok(node.parameter(parm).ok())
440 }
441
442 pub fn get_manager_node(&self, manager: ManagerType) -> Result<ManagerNode> {
444 debug_assert!(self.is_valid());
445 debug!("Getting Manager node of type: {manager:?}");
446 let node_type = NodeType::from(manager);
447 let handle = crate::ffi::get_manager_node(self, node_type)?;
448 Ok(ManagerNode {
449 session: self.clone(),
450 handle: NodeHandle(handle),
451 node_type: manager,
452 })
453 }
454
455 pub fn get_composed_object_transform(
457 &self,
458 parent: impl AsRef<NodeHandle>,
459 rst_order: RSTOrder,
460 ) -> Result<Vec<Transform>> {
461 debug_assert!(self.is_valid());
462 crate::ffi::get_composed_object_transforms(self, *parent.as_ref(), rst_order)
463 .map(|transforms| transforms.into_iter().map(Transform).collect())
464 }
465
466 pub fn save_hip(&self, path: impl AsRef<Path>, lock_nodes: bool) -> Result<()> {
468 debug!("Saving hip file: {}", path.as_ref().display());
469 debug_assert!(self.is_valid());
470 let path = utils::path_to_cstring(path)?;
471 crate::ffi::save_hip(self, &path, lock_nodes)
472 }
473
474 pub fn load_hip(&self, path: impl AsRef<Path>, cook: bool) -> Result<()> {
476 debug!("Loading hip file: {}", path.as_ref().display());
477 debug_assert!(self.is_valid());
478 let path = utils::path_to_cstring(path)?;
479 crate::ffi::load_hip(self, &path, cook)
480 }
481
482 pub fn merge_hip(&self, name: &str, cook: bool) -> Result<i32> {
484 debug!("Merging hip file: {name}");
485 debug_assert!(self.is_valid());
486 let name = CString::new(name)?;
487 crate::ffi::merge_hip(self, &name, cook)
488 }
489
490 pub fn get_hip_file_nodes(&self, hip_id: i32) -> Result<Vec<NodeHandle>> {
492 crate::ffi::get_hipfile_node_ids(self, hip_id)
493 .map(|handles| handles.into_iter().map(NodeHandle).collect())
494 }
495
496 pub fn load_asset_file(&self, file: impl AsRef<Path>) -> Result<AssetLibrary> {
498 debug_assert!(self.is_valid());
499 AssetLibrary::from_file(self.clone(), file)
500 }
501
502 pub fn get_loaded_asset_libraries(&self) -> Result<Vec<AssetLibrary>> {
504 debug_assert!(self.is_valid());
505
506 crate::ffi::get_asset_library_ids(self)?
507 .into_iter()
508 .map(|library_id| {
509 crate::ffi::get_asset_library_file_path(self, library_id).map(|path| AssetLibrary {
510 lib_id: library_id,
511 session: self.clone(),
512 file: Some(PathBuf::from(path)),
513 })
514 })
515 .collect()
516 }
517
518 pub fn interrupt(&self) -> Result<()> {
520 debug_assert!(self.is_valid());
521 debug!("Interrupting session cooking");
522 crate::ffi::interrupt(self)
523 }
524
525 #[doc(hidden)]
527 #[allow(unused)]
528 pub(crate) fn get_call_result_status(&self) -> Result<HapiResult> {
529 debug_assert!(self.is_valid());
530 let status = crate::ffi::get_status_code(self, StatusType::CallResult)?;
531 Ok(unsafe { std::mem::transmute::<i32, HapiResult>(status) })
532 }
533
534 pub fn get_cook_state_status(&self) -> Result<SessionState> {
536 debug_assert!(self.is_valid());
537 crate::ffi::get_cook_state_status(self)
538 }
539
540 pub fn is_cooking(&self) -> Result<bool> {
542 debug_assert!(self.is_valid());
543 Ok(matches!(
544 self.get_cook_state_status()?,
545 SessionState::Cooking
546 ))
547 }
548
549 #[inline]
551 #[must_use]
552 pub fn is_valid(&self) -> bool {
553 crate::ffi::is_session_valid(self)
554 }
555
556 pub fn get_status_string(
558 &self,
559 status: StatusType,
560 verbosity: StatusVerbosity,
561 ) -> Result<String> {
562 debug_assert!(self.is_valid());
563 crate::ffi::get_status_string(self, status, verbosity)
564 }
565
566 pub fn get_cook_result_string(&self, verbosity: StatusVerbosity) -> Result<String> {
568 debug_assert!(self.is_valid());
569 self.get_status_string(StatusType::CookResult, verbosity)
570 }
571
572 pub fn cooking_total_count(&self) -> Result<i32> {
574 debug_assert!(self.is_valid());
575 crate::ffi::get_cooking_total_count(self)
576 }
577
578 pub fn cooking_current_count(&self) -> Result<i32> {
580 debug_assert!(self.is_valid());
581 crate::ffi::get_cooking_current_count(self)
582 }
583
584 pub fn cook(&self) -> Result<CookResult> {
587 debug_assert!(self.is_valid());
588 debug!("Cooking session..");
589 if self.inner.options.threaded {
590 loop {
591 match self.get_cook_state_status()? {
592 SessionState::Ready => break Ok(CookResult::Succeeded),
593 SessionState::ReadyWithFatalErrors => {
594 self.interrupt()?;
595 let err = self.get_cook_result_string(StatusVerbosity::Errors)?;
596 break Ok(CookResult::FatalErrors(err));
597 }
598 SessionState::ReadyWithCookErrors => {
599 let err = self.get_cook_result_string(StatusVerbosity::Errors)?;
600 break Ok(CookResult::CookErrors(err));
601 }
602 _ => {}
604 }
605 }
606 } else {
607 Ok(CookResult::Succeeded)
610 }
611 }
612
613 pub fn get_connection_error(&self, clear: bool) -> Result<String> {
615 debug_assert!(self.is_valid());
616 crate::ffi::get_connection_error(clear)
617 }
618
619 pub fn get_time(&self) -> Result<f64> {
621 debug_assert!(self.is_valid());
622 crate::ffi::get_time(self)
623 }
624
625 pub fn set_time(&self, time: f64) -> Result<()> {
627 debug_assert!(self.is_valid());
628 crate::ffi::set_time(self, time)
629 }
630
631 pub fn lock(&self) -> parking_lot::ReentrantMutexGuard<'_, ()> {
634 self.inner.lock.lock()
635 }
636
637 pub fn set_timeline_options(&self, options: &TimelineOptions) -> Result<()> {
639 debug_assert!(self.is_valid());
640 crate::ffi::set_timeline_options(self, &options.0)
641 }
642
643 pub fn get_timeline_options(&self) -> Result<TimelineOptions> {
645 debug_assert!(self.is_valid());
646 crate::ffi::get_timeline_options(self).map(TimelineOptions)
647 }
648
649 pub fn set_use_houdini_time(&self, do_use: bool) -> Result<()> {
651 debug_assert!(self.is_valid());
652 crate::ffi::set_use_houdini_time(self, do_use)
653 }
654
655 pub fn get_use_houdini_time(&self) -> Result<bool> {
657 debug_assert!(self.is_valid());
658 crate::ffi::get_use_houdini_time(self)
659 }
660
661 pub fn get_viewport(&self) -> Result<Viewport> {
663 debug_assert!(self.is_valid());
664 crate::ffi::get_viewport(self).map(Viewport)
665 }
666
667 pub fn set_viewport(&self, viewport: &Viewport) -> Result<()> {
669 debug_assert!(self.is_valid());
670 crate::ffi::set_viewport(self, viewport)
671 }
672
673 pub fn set_sync(&self, enable: bool) -> Result<()> {
675 debug_assert!(self.is_valid());
676 crate::ffi::set_session_sync(self, enable)
677 }
678 pub fn get_sync_info(&self) -> Result<SessionSyncInfo> {
680 debug_assert!(self.is_valid());
681 crate::ffi::get_session_sync_info(self).map(SessionSyncInfo)
682 }
683
684 pub fn set_sync_info(&self, info: &SessionSyncInfo) -> Result<()> {
686 debug_assert!(self.is_valid());
687 crate::ffi::set_session_sync_info(self, &info.0)
688 }
689
690 pub fn get_license_type(&self) -> Result<LicenseType> {
692 debug_assert!(self.is_valid());
693 crate::ffi::session_get_license_type(self)
694 }
695
696 pub fn render_cop_to_image(
698 &self,
699 cop_node: impl Into<NodeHandle>,
700 output_name: Option<&str>,
701 image_planes: impl AsRef<str>,
702 out_image: impl AsRef<Path>,
703 ) -> Result<String> {
704 let cop_node = cop_node.into();
705 let out_image = out_image.as_ref();
706 debug!("Start rendering COP to image file {}", out_image.display());
707 debug_assert!(cop_node.is_valid(self)?);
708 if let Some(output_name) = output_name {
709 let output_name = CString::new(output_name)?;
710 crate::ffi::render_cop_output_to_image(self, cop_node, &output_name)?;
711 } else {
712 crate::ffi::render_cop_to_image(self, cop_node)?;
713 }
714 crate::material::extract_image_to_file(self, cop_node, image_planes, out_image)
715 }
716
717 pub fn create_cop_image(
720 &self,
721 description: CopImageDescription,
722 parent_node: Option<NodeHandle>,
723 ) -> Result<()> {
724 crate::ffi::create_cop_image(
725 self,
726 parent_node,
727 description.width,
728 description.height,
729 description.packing,
730 description.flip_x,
731 description.flip_y,
732 description.image_data,
733 )
734 }
735
736 pub fn render_texture_to_image(
738 &self,
739 node: impl Into<NodeHandle>,
740 parm_name: &str,
741 ) -> Result<()> {
742 debug_assert!(self.is_valid());
743 let name = CString::new(parm_name)?;
744 let node = node.into();
745 let id = crate::ffi::get_parm_id_from_name(&name, node, self)?;
746 crate::ffi::render_texture_to_image(self, node, crate::parameter::ParmHandle(id))
747 }
748
749 pub fn extract_image_to_file(
751 &self,
752 node: impl Into<NodeHandle>,
753 image_planes: &str,
754 path: impl AsRef<Path>,
755 ) -> Result<String> {
756 crate::material::extract_image_to_file(self, node.into(), image_planes, path)
757 }
758
759 pub fn extract_image_to_memory(
761 &self,
762 node: impl Into<NodeHandle>,
763 buffer: &mut Vec<u8>,
764 image_planes: impl AsRef<str>,
765 format: impl AsRef<str>,
766 ) -> Result<()> {
767 debug_assert!(self.is_valid());
768 crate::material::extract_image_to_memory(self, node.into(), buffer, image_planes, format)
769 }
770
771 pub fn get_image_info(&self, node: impl Into<NodeHandle>) -> Result<ImageInfo> {
773 debug_assert!(self.is_valid());
774 crate::ffi::get_image_info(self, node.into()).map(ImageInfo)
775 }
776
777 pub fn render_cop_to_memory(
780 &self,
781 cop_node: impl Into<NodeHandle>,
782 buffer: &mut Vec<u8>,
783 image_planes: impl AsRef<str>,
784 format: impl AsRef<str>,
785 ) -> Result<()> {
786 debug!("Start rendering COP to memory.");
787 let cop_node = cop_node.into();
788 debug_assert!(cop_node.is_valid(self)?);
789 crate::ffi::render_cop_to_image(self, cop_node)?;
790 crate::material::extract_image_to_memory(self, cop_node, buffer, image_planes, format)
791 }
792
793 pub fn get_supported_image_formats(&self) -> Result<Vec<ImageFileFormat<'_>>> {
794 debug_assert!(self.is_valid());
795 crate::ffi::get_supported_image_file_formats(self).map(|v| {
796 v.into_iter()
797 .map(|inner| ImageFileFormat(inner, self.into()))
798 .collect()
799 })
800 }
801
802 pub fn get_active_cache_names(&self) -> Result<StringArray> {
803 debug_assert!(self.is_valid());
804 crate::ffi::get_active_cache_names(self)
805 }
806
807 pub fn get_cache_property_value(
808 &self,
809 cache_name: &str,
810 property: CacheProperty,
811 ) -> Result<i32> {
812 let cache_name = CString::new(cache_name)?;
813 crate::ffi::get_cache_property(self, &cache_name, property)
814 }
815
816 pub fn set_cache_property_value(
817 &self,
818 cache_name: &str,
819 property: CacheProperty,
820 value: i32,
821 ) -> Result<()> {
822 let cache_name = CString::new(cache_name)?;
823 crate::ffi::set_cache_property(self, &cache_name, property, value)
824 }
825
826 pub fn python_thread_interpreter_lock(&self, lock: bool) -> Result<()> {
827 debug_assert!(self.is_valid());
828 crate::ffi::python_thread_interpreter_lock(self, lock)
829 }
830 pub fn get_compositor_options(&self) -> Result<CompositorOptions> {
831 crate::ffi::get_compositor_options(self).map(CompositorOptions)
832 }
833
834 pub fn set_compositor_options(&self, options: &CompositorOptions) -> Result<()> {
835 crate::ffi::set_compositor_options(self, &options.0)
836 }
837
838 pub fn get_preset_names(&self, bytes: &[u8]) -> Result<Vec<String>> {
839 debug_assert!(self.is_valid());
840 let mut handles = vec![];
841 for handle in crate::ffi::get_preset_names(self, bytes)? {
842 let v = crate::stringhandle::get_string(handle, self)?;
843 handles.push(v);
844 }
845 Ok(handles)
846 }
847
848 pub fn start_performance_monitor_profile(&self, title: &str) -> Result<i32> {
849 let title = CString::new(title)?;
850 crate::ffi::start_performance_monitor_profile(self, &title)
851 }
852
853 pub fn stop_performance_monitor_profile(
854 &self,
855 profile_id: i32,
856 output_file: &str,
857 ) -> Result<()> {
858 let output_file = CString::new(output_file)?;
859 crate::ffi::stop_performance_monitor_profile(self, profile_id, &output_file)
860 }
861
862 #[cfg(feature = "async-cooking")]
863 pub fn get_job_status(&self, job_id: i32) -> Result<JobStatus> {
864 crate::ffi::get_job_status(self, job_id)
865 }
866}
867
868impl Drop for Session {
869 fn drop(&mut self) {
870 if Arc::strong_count(&self.inner) == 1 {
871 debug!("Dropping session pid: {:?}", self.server_pid());
872 if self.is_valid() {
873 if self.inner.options.cleanup
874 && let Err(e) = crate::ffi::cleanup_session(self)
875 {
876 error!("Session cleanup failed in Drop: {e}");
877 }
878 if let Err(e) = crate::ffi::shutdown_session(self) {
879 error!("Could not shutdown session in Drop: {e}");
880 }
881 if let Err(e) = crate::ffi::close_session(self) {
882 error!("Closing session failed in Drop: {e}");
883 }
884 } else {
885 debug!("Session was invalid in Drop!");
888 if let Some(server_options) = &self.inner.server_options
889 && let crate::server::ThriftTransport::Pipe(transport) =
890 &server_options.thrift_transport
891 {
892 let _ = std::fs::remove_file(&transport.pipe_path);
893 }
894 }
895 }
896 }
897}
898
899#[derive(Default, Clone, Debug)]
901pub struct SessionOptions {
902 pub cook_opt: CookOptions,
904 pub threaded: bool,
906 pub cleanup: bool,
908 pub env_files: Option<CString>,
909 pub otl_path: Option<CString>,
910 pub dso_path: Option<CString>,
911 pub img_dso_path: Option<CString>,
912 pub aud_dso_path: Option<CString>,
913}
914
915impl SessionOptions {
916 #[must_use]
921 pub fn houdini_env_files<I>(mut self, files: I) -> Self
922 where
923 I: IntoIterator,
924 I::Item: AsRef<str>,
925 {
926 let paths = utils::join_paths(files);
927 self.env_files
928 .replace(CString::new(paths).expect("Zero byte"));
929 self
930 }
931
932 #[must_use]
937 pub fn otl_search_paths<I>(mut self, paths: I) -> Self
938 where
939 I: IntoIterator,
940 I::Item: AsRef<str>,
941 {
942 let paths = utils::join_paths(paths);
943 self.otl_path
944 .replace(CString::new(paths).expect("Zero byte"));
945 self
946 }
947
948 #[must_use]
953 pub fn dso_search_paths<P>(mut self, paths: P) -> Self
954 where
955 P: IntoIterator,
956 P::Item: AsRef<str>,
957 {
958 let paths = utils::join_paths(paths);
959 self.dso_path
960 .replace(CString::new(paths).expect("Zero byte"));
961 self
962 }
963
964 #[must_use]
969 pub fn image_search_paths<P>(mut self, paths: P) -> Self
970 where
971 P: IntoIterator,
972 P::Item: AsRef<str>,
973 {
974 let paths = utils::join_paths(paths);
975 self.img_dso_path
976 .replace(CString::new(paths).expect("Zero byte"));
977 self
978 }
979
980 #[must_use]
985 pub fn audio_search_paths<P>(mut self, paths: P) -> Self
986 where
987 P: IntoIterator,
988 P::Item: AsRef<str>,
989 {
990 let paths = utils::join_paths(paths);
991 self.aud_dso_path
992 .replace(CString::new(paths).expect("Zero byte"));
993 self
994 }
995
996 #[must_use]
998 pub fn cook_options(mut self, options: CookOptions) -> Self {
999 self.cook_opt = options;
1000 self
1001 }
1002
1003 #[must_use]
1005 pub fn threaded(mut self, threaded: bool) -> Self {
1006 self.threaded = threaded;
1007 self
1008 }
1009
1010 #[must_use]
1012 pub fn cleanup(mut self, cleanup: bool) -> Self {
1013 self.cleanup = cleanup;
1014 self
1015 }
1016}
1017
1018pub fn new_in_process_session(options: Option<SessionOptions>) -> Result<Session> {
1022 debug!("Creating new in-process session");
1023 let session_options = options.unwrap_or_default();
1024 let session_info = SessionInfo::default();
1025 let handle = crate::ffi::create_inprocess_session(&session_info.0)?;
1026 let session = UninitializedSession {
1027 session_handle: handle,
1028 server_options: None,
1029 server_pid: Some(std::process::id()),
1030 }
1031 .initialize(session_options)?;
1032 Ok(session)
1033}
1034
1035pub fn new_thrift_session(
1037 session_options: SessionOptions,
1038 server_options: ServerOptions,
1039) -> Result<Session> {
1040 match server_options.thrift_transport {
1041 crate::server::ThriftTransport::SharedMemory(_) => {
1042 let pid = crate::server::start_engine_server(&server_options)?;
1043 crate::server::connect_to_memory_server(server_options, Some(pid))
1044 .context("Could not connect to shared memory server")?
1045 .initialize(session_options)
1046 }
1047 crate::server::ThriftTransport::Pipe(_) => {
1048 let pid = crate::server::start_engine_server(&server_options)?;
1049 crate::server::connect_to_pipe_server(server_options, Some(pid))
1050 .context("Could not connect to pipe server")?
1051 .initialize(session_options)
1052 }
1053 crate::server::ThriftTransport::Socket(_) => {
1054 let pid = crate::server::start_engine_server(&server_options)?;
1055 crate::server::connect_to_socket_server(server_options, Some(pid))
1056 .context("Could not connect to socket server")?
1057 .initialize(session_options)
1058 .context("Could not connect to socket server")
1059 }
1060 }
1061}
1062
1063pub fn simple_session() -> Result<Session> {
1065 new_thrift_session(
1066 SessionOptions::default(),
1067 ServerOptions::shared_memory_with_defaults(),
1068 )
1069}