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