1use log::{debug, error, warn};
18use parking_lot::ReentrantMutex;
19use std::ffi::{CStr, OsString};
20use std::fmt::Debug;
21use std::path::PathBuf;
22use std::process::Child;
23use std::time::Duration;
24use std::{ffi::CString, path::Path, sync::Arc};
25
26pub use crate::{
27 asset::AssetLibrary,
28 errors::*,
29 ffi::{
30 enums::*, CompositorOptions, CookOptions, ImageFileFormat, SessionInfo, SessionSyncInfo,
31 ThriftServerOptions, TimelineOptions, Viewport,
32 },
33 node::{HoudiniNode, ManagerNode, ManagerType, NodeHandle, NodeType, Transform},
34 parameter::Parameter,
35 stringhandle::StringArray,
36};
37
38pub type SessionState = State;
39
40use crate::ffi::ImageInfo;
41use crate::stringhandle::StringHandle;
42use crate::{ffi::raw, utils};
43
44pub struct NodeBuilder<'s> {
46 session: &'s Session,
47 name: String,
48 label: Option<String>,
49 parent: Option<NodeHandle>,
50 cook: bool,
51}
52
53impl<'s> NodeBuilder<'s> {
54 pub fn with_label(mut self, label: impl Into<String>) -> Self {
56 self.label = Some(label.into());
57 self
58 }
59
60 pub fn with_parent<H: AsRef<NodeHandle>>(mut self, parent: H) -> Self {
62 self.parent.replace(*parent.as_ref());
63 self
64 }
65
66 pub fn cook(mut self, cook: bool) -> Self {
68 self.cook = cook;
69 self
70 }
71
72 pub fn create(self) -> Result<HoudiniNode> {
74 let NodeBuilder {
75 session,
76 name,
77 label,
78 parent,
79 cook,
80 } = self;
81 session.create_node_with(&name, parent, label.as_deref(), cook)
82 }
83}
84
85impl PartialEq for raw::HAPI_Session {
86 fn eq(&self, other: &Self) -> bool {
87 self.type_ == other.type_ && self.id == other.id
88 }
89}
90
91pub trait EnvVariable {
93 type Type: ?Sized + ToOwned + Debug;
94 fn get_value(session: &Session, key: impl AsRef<str>)
95 -> Result<<Self::Type as ToOwned>::Owned>;
96 fn set_value(session: &Session, key: impl AsRef<str>, val: &Self::Type) -> Result<()>;
97}
98
99impl EnvVariable for str {
100 type Type = str;
101
102 fn get_value(session: &Session, key: impl AsRef<str>) -> Result<String> {
103 let key = CString::new(key.as_ref())?;
104 let handle = crate::ffi::get_server_env_str(session, &key)?;
105 crate::stringhandle::get_string(handle, session)
106 }
107
108 fn set_value(session: &Session, key: impl AsRef<str>, val: &Self::Type) -> Result<()> {
109 let key = CString::new(key.as_ref())?;
110 let val = CString::new(val)?;
111 crate::ffi::set_server_env_str(session, &key, &val)
112 }
113}
114
115impl EnvVariable for Path {
116 type Type = Self;
117
118 fn get_value(session: &Session, key: impl AsRef<str>) -> Result<PathBuf> {
119 let key = CString::new(key.as_ref())?;
120 crate::stringhandle::get_string(crate::ffi::get_server_env_str(session, &key)?, session)
121 .map(PathBuf::from)
122 }
123
124 fn set_value(session: &Session, key: impl AsRef<str>, val: &Self::Type) -> Result<()> {
125 let key = CString::new(key.as_ref())?;
126 let val = utils::path_to_cstring(val)?;
127 crate::ffi::set_server_env_str(session, &key, &val)
128 }
129}
130
131impl EnvVariable for i32 {
132 type Type = Self;
133
134 fn get_value(session: &Session, key: impl AsRef<str>) -> Result<Self::Type> {
135 let key = CString::new(key.as_ref())?;
136 crate::ffi::get_server_env_int(session, &key)
137 }
138
139 fn set_value(session: &Session, key: impl AsRef<str>, val: &Self::Type) -> Result<()> {
140 let key = CString::new(key.as_ref())?;
141 crate::ffi::set_server_env_int(session, &key, *val)
142 }
143}
144
145#[derive(Debug, Clone, Eq, PartialEq)]
147pub enum CookResult {
148 Succeeded,
149 CookErrors(String),
151 FatalErrors(String),
153}
154
155#[derive(Debug, Clone, Eq, PartialEq)]
157pub enum ConnectionType {
158 ThriftPipe(OsString),
159 ThriftSocket(std::net::SocketAddrV4),
160 SharedMemory(String),
161 InProcess,
162 Custom,
163}
164
165#[derive(Debug)]
166pub(crate) struct SessionInner {
167 pub(crate) handle: raw::HAPI_Session,
168 pub(crate) options: SessionOptions,
169 pub(crate) connection: ConnectionType,
170 pub(crate) pid: Option<u32>,
171 pub(crate) lock: ReentrantMutex<()>,
172}
173
174#[derive(Debug, Clone)]
177pub struct Session {
178 pub(crate) inner: Arc<SessionInner>,
179}
180
181impl PartialEq for Session {
182 fn eq(&self, other: &Self) -> bool {
183 self.inner.handle.id == other.inner.handle.id
184 && self.inner.handle.type_ == other.inner.handle.type_
185 }
186}
187
188impl Session {
189 fn new(
190 handle: raw::HAPI_Session,
191 connection: ConnectionType,
192 options: SessionOptions,
193 pid: Option<u32>,
194 ) -> Session {
195 Session {
196 inner: Arc::new(SessionInner {
197 handle,
198 options,
199 connection,
200 lock: ReentrantMutex::new(()),
201 pid,
202 }),
203 }
204 }
205
206 pub fn session_type(&self) -> SessionType {
208 self.inner.handle.type_
209 }
210
211 pub fn connection_type(&self) -> &ConnectionType {
213 &self.inner.connection
214 }
215
216 pub fn server_pid(&self) -> Option<u32> {
217 self.inner.pid
218 }
219
220 #[inline(always)]
221 pub(crate) fn ptr(&self) -> *const raw::HAPI_Session {
222 &(self.inner.handle) as *const _
223 }
224
225 pub fn set_server_var<T: EnvVariable + ?Sized>(
227 &self,
228 key: &str,
229 value: &T::Type,
230 ) -> Result<()> {
231 debug_assert!(self.is_valid());
232 debug!("Setting server variable {key}={value:?}");
233 T::set_value(self, key, value)
234 }
235
236 pub fn get_server_var<T: EnvVariable + ?Sized>(
238 &self,
239 key: &str,
240 ) -> Result<<T::Type as ToOwned>::Owned> {
241 debug_assert!(self.is_valid());
242 T::get_value(self, key)
243 }
244
245 pub fn get_server_variables(&self) -> Result<StringArray> {
247 debug_assert!(self.is_valid());
248 let count = crate::ffi::get_server_env_var_count(self)?;
249 let handles = crate::ffi::get_server_env_var_list(self, count)?;
250 crate::stringhandle::get_string_array(&handles, self).context("Calling get_string_array")
251 }
252
253 pub fn get_string(&self, handle: StringHandle) -> Result<String> {
255 crate::stringhandle::get_string(handle, self)
256 }
257
258 pub fn get_string_batch(&self, handles: &[StringHandle]) -> Result<StringArray> {
260 crate::stringhandle::get_string_array(handles, self)
261 }
262
263 fn initialize(&self) -> Result<()> {
264 debug!("Initializing session");
265 debug_assert!(self.is_valid());
266 let res = crate::ffi::initialize_session(self, &self.inner.options);
267 match res {
268 Ok(_) => Ok(()),
269 Err(HapiError {
270 kind: Kind::Hapi(HapiResult::AlreadyInitialized),
271 ..
272 }) => {
273 warn!("Session already initialized, skipping");
274 Ok(())
275 }
276 Err(e) => Err(e),
277 }
278 }
279
280 pub fn cleanup(&self) -> Result<()> {
283 debug!("Cleaning session");
284 debug_assert!(self.is_valid());
285 crate::ffi::cleanup_session(self)
286 }
287
288 pub fn is_initialized(&self) -> bool {
290 debug_assert!(self.is_valid());
291 crate::ffi::is_session_initialized(self)
292 }
293
294 pub fn create_input_node(
296 &self,
297 name: &str,
298 parent: Option<NodeHandle>,
299 ) -> Result<crate::geometry::Geometry> {
300 debug!("Creating input node: {}", name);
301 debug_assert!(self.is_valid());
302 let name = CString::new(name)?;
303 let id = crate::ffi::create_input_node(self, &name, parent)?;
304 let node = HoudiniNode::new(self.clone(), NodeHandle(id), None)?;
305 let info = crate::geometry::GeoInfo::from_node(&node)?;
306 Ok(crate::geometry::Geometry { node, info })
307 }
308
309 pub fn create_input_curve_node(
311 &self,
312 name: &str,
313 parent: Option<NodeHandle>,
314 ) -> Result<crate::geometry::Geometry> {
315 debug!("Creating input curve node: {}", name);
316 debug_assert!(self.is_valid());
317 let name = CString::new(name)?;
318 let id = crate::ffi::create_input_curve_node(self, &name, parent)?;
319 let node = HoudiniNode::new(self.clone(), NodeHandle(id), None)?;
320 let info = crate::geometry::GeoInfo::from_node(&node)?;
321 Ok(crate::geometry::Geometry { node, info })
322 }
323
324 pub fn create_node(&self, name: impl AsRef<str>) -> Result<HoudiniNode> {
329 self.create_node_with(name.as_ref(), None, None, false)
330 }
331
332 pub fn node_builder(&self, node_name: impl Into<String>) -> NodeBuilder {
334 NodeBuilder {
335 session: self,
336 name: node_name.into(),
337 label: None,
338 parent: None,
339 cook: false,
340 }
341 }
342
343 pub(crate) fn create_node_with<P>(
345 &self,
346 name: &str,
347 parent: P,
348 label: Option<&str>,
349 cook: bool,
350 ) -> Result<HoudiniNode>
351 where
352 P: Into<Option<NodeHandle>>,
353 {
354 let parent = parent.into();
355 debug!("Creating node instance: {}", name);
356 debug_assert!(self.is_valid());
357 debug_assert!(
358 parent.is_some() || name.contains('/'),
359 "Node name must be fully qualified if parent is not specified"
360 );
361 debug_assert!(
362 !(parent.is_some() && name.contains('/')),
363 "Cannot use fully qualified node name with parent"
364 );
365 let name = CString::new(name)?;
366 let label = label.map(CString::new).transpose()?;
367 let id = crate::ffi::create_node(&name, label.as_deref(), self, parent, cook)?;
368 HoudiniNode::new(self.clone(), NodeHandle(id), None)
369 }
370
371 pub fn delete_node<H: Into<NodeHandle>>(&self, node: H) -> Result<()> {
373 crate::ffi::delete_node(node.into(), self)
374 }
375
376 pub fn get_node_from_path(
379 &self,
380 path: impl AsRef<str>,
381 parent: impl Into<Option<NodeHandle>>,
382 ) -> Result<Option<HoudiniNode>> {
383 debug_assert!(self.is_valid());
384 debug!("Searching node at path: {}", path.as_ref());
385 let path = CString::new(path.as_ref())?;
386 match crate::ffi::get_node_from_path(self, parent.into(), &path) {
387 Ok(handle) => Ok(NodeHandle(handle).to_node(self).ok()),
388 Err(HapiError {
389 kind: Kind::Hapi(HapiResult::InvalidArgument),
390 ..
391 }) => Ok(None),
392 Err(e) => Err(e),
393 }
394 }
395
396 pub fn find_parameter_from_path(
398 &self,
399 path: impl AsRef<str>,
400 start: impl Into<Option<NodeHandle>>,
401 ) -> Result<Option<Parameter>> {
402 debug_assert!(self.is_valid());
403 debug!("Searching parameter at path: {}", path.as_ref());
404 let Some((path, parm)) = path.as_ref().rsplit_once('/') else {
405 return Ok(None);
406 };
407 let Some(node) = self.get_node_from_path(path, start)? else {
408 debug!("Node {} not found", path);
409 return Ok(None);
410 };
411 Ok(node.parameter(parm).ok())
412 }
413
414 pub fn get_manager_node(&self, manager: ManagerType) -> Result<ManagerNode> {
416 debug_assert!(self.is_valid());
417 debug!("Getting Manager node of type: {:?}", manager);
418 let node_type = NodeType::from(manager);
419 let handle = crate::ffi::get_manager_node(self, node_type)?;
420 Ok(ManagerNode {
421 session: self.clone(),
422 handle: NodeHandle(handle),
423 node_type: manager,
424 })
425 }
426
427 pub fn get_composed_object_transform(
429 &self,
430 parent: impl AsRef<NodeHandle>,
431 rst_order: RSTOrder,
432 ) -> Result<Vec<Transform>> {
433 debug_assert!(self.is_valid());
434 crate::ffi::get_composed_object_transforms(self, *parent.as_ref(), rst_order)
435 .map(|transforms| transforms.into_iter().map(Transform).collect())
436 }
437
438 pub fn save_hip(&self, path: impl AsRef<Path>, lock_nodes: bool) -> Result<()> {
440 debug!("Saving hip file: {:?}", path.as_ref());
441 debug_assert!(self.is_valid());
442 let path = utils::path_to_cstring(path)?;
443 crate::ffi::save_hip(self, &path, lock_nodes)
444 }
445
446 pub fn load_hip(&self, path: impl AsRef<Path>, cook: bool) -> Result<()> {
448 debug!("Loading hip file: {:?}", path.as_ref());
449 debug_assert!(self.is_valid());
450 let path = utils::path_to_cstring(path)?;
451 crate::ffi::load_hip(self, &path, cook)
452 }
453
454 pub fn merge_hip(&self, name: &str, cook: bool) -> Result<i32> {
456 debug!("Merging hip file: {}", name);
457 debug_assert!(self.is_valid());
458 let name = CString::new(name)?;
459 crate::ffi::merge_hip(self, &name, cook)
460 }
461
462 pub fn get_hip_file_nodes(&self, hip_id: i32) -> Result<Vec<NodeHandle>> {
464 crate::ffi::get_hipfile_node_ids(self, hip_id)
465 .map(|handles| handles.into_iter().map(NodeHandle).collect())
466 }
467
468 pub fn load_asset_file(&self, file: impl AsRef<Path>) -> Result<AssetLibrary> {
470 debug_assert!(self.is_valid());
471 AssetLibrary::from_file(self.clone(), file)
472 }
473
474 pub fn get_loaded_asset_libraries(&self) -> Result<Vec<AssetLibrary>> {
476 debug_assert!(self.is_valid());
477
478 crate::ffi::get_asset_library_ids(self)?
479 .into_iter()
480 .map(|library_id| {
481 crate::ffi::get_asset_library_file_path(self, library_id).map(|lib_file| {
482 AssetLibrary {
483 lib_id: library_id,
484 session: self.clone(),
485 file: Some(PathBuf::from(lib_file)),
486 }
487 })
488 })
489 .collect::<Result<Vec<_>>>()
490 }
491
492 pub fn interrupt(&self) -> Result<()> {
494 debug_assert!(self.is_valid());
495 crate::ffi::interrupt(self)
496 }
497
498 pub fn get_status(&self, flag: StatusType) -> Result<SessionState> {
500 debug_assert!(self.is_valid());
501 crate::ffi::get_status(self, flag)
502 }
503
504 pub fn is_cooking(&self) -> Result<bool> {
506 debug_assert!(self.is_valid());
507 Ok(matches!(
508 self.get_status(StatusType::CookState)?,
509 SessionState::Cooking
510 ))
511 }
512
513 #[inline(always)]
515 pub fn is_valid(&self) -> bool {
516 crate::ffi::is_session_valid(self)
517 }
518
519 pub fn get_status_string(
521 &self,
522 status: StatusType,
523 verbosity: StatusVerbosity,
524 ) -> Result<String> {
525 debug_assert!(self.is_valid());
526 crate::ffi::get_status_string(self, status, verbosity)
527 }
528
529 pub fn get_cook_result_string(&self, verbosity: StatusVerbosity) -> Result<String> {
531 debug_assert!(self.is_valid());
532 self.get_status_string(StatusType::CookResult, verbosity)
533 }
534
535 pub fn cooking_total_count(&self) -> Result<i32> {
537 debug_assert!(self.is_valid());
538 crate::ffi::get_cooking_total_count(self)
539 }
540
541 pub fn cooking_current_count(&self) -> Result<i32> {
543 debug_assert!(self.is_valid());
544 crate::ffi::get_cooking_current_count(self)
545 }
546
547 pub fn cook(&self) -> Result<CookResult> {
550 debug_assert!(self.is_valid());
551 debug!("Cooking session..");
552 if self.inner.options.threaded {
553 loop {
554 match self.get_status(StatusType::CookState)? {
555 SessionState::Ready => break Ok(CookResult::Succeeded),
556 SessionState::ReadyWithFatalErrors => {
557 self.interrupt()?;
558 let err = self.get_cook_result_string(StatusVerbosity::Errors)?;
559 break Ok(CookResult::FatalErrors(err));
560 }
561 SessionState::ReadyWithCookErrors => {
562 let err = self.get_cook_result_string(StatusVerbosity::Errors)?;
563 break Ok(CookResult::CookErrors(err));
564 }
565 _ => {}
567 }
568 }
569 } else {
570 Ok(CookResult::Succeeded)
573 }
574 }
575
576 pub fn get_connection_error(&self, clear: bool) -> Result<String> {
578 debug_assert!(self.is_valid());
579 crate::ffi::get_connection_error(clear)
580 }
581
582 pub fn get_time(&self) -> Result<f32> {
584 debug_assert!(self.is_valid());
585 crate::ffi::get_time(self)
586 }
587
588 pub fn set_time(&self, time: f32) -> Result<()> {
590 debug_assert!(self.is_valid());
591 crate::ffi::set_time(self, time)
592 }
593
594 pub fn lock(&self) -> parking_lot::ReentrantMutexGuard<()> {
597 self.inner.lock.lock()
598 }
599
600 pub fn set_timeline_options(&self, options: TimelineOptions) -> Result<()> {
602 debug_assert!(self.is_valid());
603 crate::ffi::set_timeline_options(self, &options.0)
604 }
605
606 pub fn get_timeline_options(&self) -> Result<TimelineOptions> {
608 debug_assert!(self.is_valid());
609 crate::ffi::get_timeline_options(self).map(TimelineOptions)
610 }
611
612 pub fn set_use_houdini_time(&self, do_use: bool) -> Result<()> {
614 debug_assert!(self.is_valid());
615 crate::ffi::set_use_houdini_time(self, do_use)
616 }
617
618 pub fn get_use_houdini_time(&self) -> Result<bool> {
620 debug_assert!(self.is_valid());
621 crate::ffi::get_use_houdini_time(self)
622 }
623
624 pub fn get_viewport(&self) -> Result<Viewport> {
626 debug_assert!(self.is_valid());
627 crate::ffi::get_viewport(self).map(Viewport)
628 }
629
630 pub fn set_viewport(&self, viewport: &Viewport) -> Result<()> {
632 debug_assert!(self.is_valid());
633 crate::ffi::set_viewport(self, viewport)
634 }
635
636 pub fn set_sync(&self, enable: bool) -> Result<()> {
638 debug_assert!(self.is_valid());
639 crate::ffi::set_session_sync(self, enable)
640 }
641 pub fn get_sync_info(&self) -> Result<SessionSyncInfo> {
643 debug_assert!(self.is_valid());
644 crate::ffi::get_session_sync_info(self).map(SessionSyncInfo)
645 }
646
647 pub fn set_sync_info(&self, info: &SessionSyncInfo) -> Result<()> {
649 debug_assert!(self.is_valid());
650 crate::ffi::set_session_sync_info(self, &info.0)
651 }
652
653 pub fn get_license_type(&self) -> Result<License> {
655 debug_assert!(self.is_valid());
656 crate::ffi::session_get_license_type(self)
657 }
658
659 pub fn render_cop_to_image(
661 &self,
662 cop_node: impl Into<NodeHandle>,
663 image_planes: impl AsRef<str>,
664 path: impl AsRef<Path>,
665 ) -> Result<String> {
666 debug!("Start rendering COP to image.");
667 let cop_node = cop_node.into();
668 debug_assert!(cop_node.is_valid(self)?);
669 crate::ffi::render_cop_to_image(self, cop_node)?;
670 crate::material::extract_image_to_file(self, cop_node, image_planes, path)
671 }
672
673 pub fn render_texture_to_image(
674 &self,
675 node: impl Into<NodeHandle>,
676 parm_name: &str,
677 ) -> Result<()> {
678 debug_assert!(self.is_valid());
679 let name = CString::new(parm_name)?;
680 let node = node.into();
681 let id = crate::ffi::get_parm_id_from_name(&name, node, self)?;
682 crate::ffi::render_texture_to_image(&self, node, crate::parameter::ParmHandle(id))
683 }
684
685 pub fn extract_image_to_file(
686 &self,
687 node: impl Into<NodeHandle>,
688 image_planes: &str,
689 path: impl AsRef<Path>,
690 ) -> Result<String> {
691 crate::material::extract_image_to_file(self, node.into(), image_planes, path)
692 }
693
694 pub fn extract_image_to_memory(
695 &self,
696 node: impl Into<NodeHandle>,
697 buffer: &mut Vec<u8>,
698 image_planes: impl AsRef<str>,
699 format: impl AsRef<str>,
700 ) -> Result<()> {
701 debug_assert!(self.is_valid());
702 crate::material::extract_image_to_memory(self, node.into(), buffer, image_planes, format)
703 }
704
705 pub fn get_image_info(&self, node: impl Into<NodeHandle>) -> Result<ImageInfo> {
706 debug_assert!(self.is_valid());
707 crate::ffi::get_image_info(self, node.into()).map(ImageInfo)
708 }
709
710 pub fn render_cop_to_memory(
712 &self,
713 cop_node: impl Into<NodeHandle>,
714 buffer: &mut Vec<u8>,
715 image_planes: impl AsRef<str>,
716 format: impl AsRef<str>,
717 ) -> Result<()> {
718 debug!("Start rendering COP to memory.");
719 let cop_node = cop_node.into();
720 debug_assert!(cop_node.is_valid(self)?);
721 crate::ffi::render_cop_to_image(self, cop_node)?;
722 crate::material::extract_image_to_memory(self, cop_node, buffer, image_planes, format)
723 }
724
725 pub fn get_supported_image_formats(&self) -> Result<Vec<ImageFileFormat<'_>>> {
726 debug_assert!(self.is_valid());
727 crate::ffi::get_supported_image_file_formats(self).map(|v| {
728 v.into_iter()
729 .map(|inner| ImageFileFormat(inner, self.into()))
730 .collect()
731 })
732 }
733
734 pub fn get_active_cache_names(&self) -> Result<StringArray> {
735 debug_assert!(self.is_valid());
736 crate::ffi::get_active_cache_names(self)
737 }
738
739 pub fn get_cache_property_value(
740 &self,
741 cache_name: &str,
742 property: CacheProperty,
743 ) -> Result<i32> {
744 let cache_name = CString::new(cache_name)?;
745 crate::ffi::get_cache_property(self, &cache_name, property)
746 }
747
748 pub fn set_cache_property_value(
749 &self,
750 cache_name: &str,
751 property: CacheProperty,
752 value: i32,
753 ) -> Result<()> {
754 let cache_name = CString::new(cache_name)?;
755 crate::ffi::set_cache_property(self, &cache_name, property, value)
756 }
757
758 pub fn python_thread_interpreter_lock(&self, lock: bool) -> Result<()> {
759 debug_assert!(self.is_valid());
760 crate::ffi::python_thread_interpreter_lock(self, lock)
761 }
762 pub fn get_compositor_options(&self) -> Result<CompositorOptions> {
763 crate::ffi::get_compositor_options(self).map(CompositorOptions)
764 }
765
766 pub fn set_compositor_options(&self, options: &CompositorOptions) -> Result<()> {
767 crate::ffi::set_compositor_options(self, &options.0)
768 }
769
770 pub fn get_preset_names(&self, bytes: &[u8]) -> Result<Vec<String>> {
771 debug_assert!(self.is_valid());
772 let mut handles = vec![];
773 for handle in crate::ffi::get_preset_names(self, bytes)? {
774 let v = crate::stringhandle::get_string(handle, self)?;
775 handles.push(v);
776 }
777 Ok(handles)
778 }
779
780 pub fn start_performance_monitor_profile(&self, title: &str) -> Result<i32> {
781 let title = CString::new(title)?;
782 crate::ffi::start_performance_monitor_profile(self, &title)
783 }
784
785 pub fn stop_performance_monitor_profile(
786 &self,
787 profile_id: i32,
788 output_file: &str,
789 ) -> Result<()> {
790 let output_file = CString::new(output_file)?;
791 crate::ffi::stop_performance_monitor_profile(self, profile_id, &output_file)
792 }
793
794 pub fn get_job_status(&self, job_id: i32) -> Result<JobStatus> {
795 crate::ffi::get_job_status(self, job_id)
796 }
797}
798
799impl Drop for Session {
800 fn drop(&mut self) {
801 if Arc::strong_count(&self.inner) == 1 {
802 debug!("Dropping session pid: {:?}", self.server_pid());
803 if self.is_valid() {
804 if self.inner.options.cleanup {
805 if let Err(e) = self.cleanup() {
806 error!("Session cleanup failed in Drop: {}", e);
807 }
808 }
809 if let Err(e) = crate::ffi::shutdown_session(self) {
810 error!("Could not shutdown session in Drop: {}", e);
811 }
812 if let Err(e) = crate::ffi::close_session(self) {
813 error!("Closing session failed in Drop: {}", e);
814 }
815 } else {
816 debug!("Session was invalid in Drop!");
819 if let ConnectionType::ThriftPipe(f) = &self.inner.connection {
820 let _ = std::fs::remove_file(f);
821 }
822 }
823 }
824 }
825}
826
827pub fn connect_to_pipe(
833 pipe: impl AsRef<Path>,
834 options: Option<&SessionOptions>,
835 timeout: Option<Duration>,
836 pid: Option<u32>,
837) -> Result<Session> {
838 debug!("Connecting to Thrift session: {:?}", pipe.as_ref());
839 let c_str = utils::path_to_cstring(&pipe)?;
840 let pipe = pipe.as_ref().as_os_str().to_os_string();
841 let timeout = timeout.unwrap_or_default();
842 let options = options.cloned().unwrap_or_default();
843 let mut waited = Duration::from_secs(0);
844 let wait_ms = Duration::from_millis(100);
845 let handle = loop {
846 let mut last_error = None;
847 debug!("Trying to connect to pipe server");
848 match crate::ffi::new_thrift_piped_session(&c_str, &options.session_info.0) {
849 Ok(handle) => break handle,
850 Err(e) => {
851 last_error.replace(e);
852 std::thread::sleep(wait_ms);
853 waited += wait_ms;
854 }
855 }
856 if waited > timeout {
857 return Err(last_error.unwrap()).context("Connection timeout");
859 }
860 };
861 let connection = ConnectionType::ThriftPipe(pipe);
862 let session = Session::new(handle, connection, options, pid);
863 session.initialize()?;
864 Ok(session)
865}
866
867pub fn connect_to_memory_server(
868 memory_name: &str,
869 options: Option<&SessionOptions>,
870 pid: Option<u32>,
871) -> Result<Session> {
872 let mem_name = String::from(memory_name);
873 let mem_name_cstr = CString::new(memory_name)?;
874
875 let options = options.cloned().unwrap_or_default();
876 let handle =
877 crate::ffi::new_thrift_shared_memory_session(&mem_name_cstr, &options.session_info.0)?;
878
879 let connection = ConnectionType::SharedMemory(mem_name);
880 let session = Session::new(handle, connection, options, pid);
881 session.initialize()?;
882 Ok(session)
883}
884
885pub fn connect_to_socket(
887 addr: std::net::SocketAddrV4,
888 options: Option<&SessionOptions>,
889) -> Result<Session> {
890 debug!("Connecting to socket server: {:?}", addr);
891 let host = CString::new(addr.ip().to_string()).expect("SocketAddr->CString");
892 let options = options.cloned().unwrap_or_default();
893 let handle =
894 crate::ffi::new_thrift_socket_session(addr.port() as i32, &host, &options.session_info.0)?;
895 let connection = ConnectionType::ThriftSocket(addr);
896 let session = Session::new(handle, connection, options, None);
897 session.initialize()?;
898 Ok(session)
899}
900
901pub fn new_in_process(options: Option<&SessionOptions>) -> Result<Session> {
903 debug!("Creating new in-process session");
904 let options = options.cloned().unwrap_or_default();
905 let handle = crate::ffi::create_inprocess_session(&options.session_info.0)?;
906 let connection = ConnectionType::InProcess;
907 let session = Session::new(handle, connection, options, Some(std::process::id()));
908 session.initialize()?;
909 Ok(session)
910}
911
912#[derive(Clone, Debug)]
914pub struct SessionOptions {
915 pub cook_opt: CookOptions,
917 pub session_info: SessionInfo,
919 pub threaded: bool,
921 pub cleanup: bool,
923 pub log_file: Option<CString>,
924 pub ignore_already_init: bool,
926 pub env_files: Option<CString>,
927 pub env_variables: Option<Vec<(String, String)>>,
928 pub otl_path: Option<CString>,
929 pub dso_path: Option<CString>,
930 pub img_dso_path: Option<CString>,
931 pub aud_dso_path: Option<CString>,
932}
933
934impl Default for SessionOptions {
935 fn default() -> Self {
936 SessionOptions {
937 cook_opt: CookOptions::default(),
938 session_info: SessionInfo::default(),
939 threaded: false,
940 cleanup: false,
941 log_file: None,
942 ignore_already_init: true,
943 env_files: None,
944 env_variables: None,
945 otl_path: None,
946 dso_path: None,
947 img_dso_path: None,
948 aud_dso_path: None,
949 }
950 }
951}
952
953#[derive(Default)]
954pub struct SessionOptionsBuilder {
956 cook_opt: CookOptions,
957 session_info: SessionInfo,
958 threaded: bool,
959 cleanup: bool,
960 log_file: Option<CString>,
961 ignore_already_init: bool,
962 env_variables: Option<Vec<(String, String)>>,
963 env_files: Option<CString>,
964 otl_path: Option<CString>,
965 dso_path: Option<CString>,
966 img_dso_path: Option<CString>,
967 aud_dso_path: Option<CString>,
968}
969
970impl SessionOptionsBuilder {
971 pub fn houdini_env_files<I>(mut self, files: I) -> Self
973 where
974 I: IntoIterator,
975 I::Item: AsRef<str>,
976 {
977 let paths = utils::join_paths(files);
978 self.env_files
979 .replace(CString::new(paths).expect("Zero byte"));
980 self
981 }
982
983 pub fn env_variables<'a, I, K, V>(mut self, variables: I) -> Self
987 where
988 I: Iterator<Item = &'a (K, V)>,
989 K: ToString + 'a,
990 V: ToString + 'a,
991 {
992 self.env_variables.replace(
993 variables
994 .map(|(k, v)| (k.to_string(), v.to_string()))
995 .collect(),
996 );
997 self
998 }
999
1000 pub fn otl_search_paths<I>(mut self, paths: I) -> Self
1002 where
1003 I: IntoIterator,
1004 I::Item: AsRef<str>,
1005 {
1006 let paths = utils::join_paths(paths);
1007 self.otl_path
1008 .replace(CString::new(paths).expect("Zero byte"));
1009 self
1010 }
1011
1012 pub fn dso_search_paths<P>(mut self, paths: P) -> Self
1014 where
1015 P: IntoIterator,
1016 P::Item: AsRef<str>,
1017 {
1018 let paths = utils::join_paths(paths);
1019 self.dso_path
1020 .replace(CString::new(paths).expect("Zero byte"));
1021 self
1022 }
1023
1024 pub fn image_search_paths<P>(mut self, paths: P) -> Self
1026 where
1027 P: IntoIterator,
1028 P::Item: AsRef<str>,
1029 {
1030 let paths = utils::join_paths(paths);
1031 self.img_dso_path
1032 .replace(CString::new(paths).expect("Zero byte"));
1033 self
1034 }
1035
1036 pub fn audio_search_paths<P>(mut self, paths: P) -> Self
1038 where
1039 P: IntoIterator,
1040 P::Item: AsRef<str>,
1041 {
1042 let paths = utils::join_paths(paths);
1043 self.aud_dso_path
1044 .replace(CString::new(paths).expect("Zero byte"));
1045 self
1046 }
1047
1048 pub fn ignore_already_init(mut self, ignore: bool) -> Self {
1050 self.ignore_already_init = ignore;
1051 self
1052 }
1053
1054 pub fn cook_options(mut self, options: CookOptions) -> Self {
1056 self.cook_opt = options;
1057 self
1058 }
1059
1060 pub fn session_info(mut self, info: SessionInfo) -> Self {
1062 self.session_info = info;
1063 self
1064 }
1065
1066 pub fn threaded(mut self, threaded: bool) -> Self {
1068 self.threaded = threaded;
1069 self
1070 }
1071
1072 pub fn cleanup_on_close(mut self, cleanup: bool) -> Self {
1074 self.cleanup = cleanup;
1075 self
1076 }
1077
1078 pub fn log_file(mut self, file: impl AsRef<Path>) -> Self {
1079 self.log_file = Some(utils::path_to_cstring(file).unwrap());
1080 self
1081 }
1082
1083 pub fn build(mut self) -> SessionOptions {
1085 self.write_temp_env_file();
1086 SessionOptions {
1087 cook_opt: self.cook_opt,
1088 session_info: self.session_info,
1089 threaded: self.threaded,
1090 cleanup: self.cleanup,
1091 log_file: self.log_file,
1092 ignore_already_init: self.cleanup,
1093 env_files: self.env_files,
1094 env_variables: self.env_variables,
1095 otl_path: self.otl_path,
1096 dso_path: self.dso_path,
1097 img_dso_path: self.img_dso_path,
1098 aud_dso_path: self.aud_dso_path,
1099 }
1100 }
1101 fn write_temp_env_file(&mut self) {
1103 use std::io::Write;
1104
1105 if let Some(ref env) = self.env_variables {
1106 let mut file = tempfile::Builder::new()
1107 .suffix("_hars.env")
1108 .tempfile()
1109 .expect("tempfile");
1110 for (k, v) in env.iter() {
1111 writeln!(file, "{}={}", k, v).expect("write to .env file");
1112 }
1113 let (_, tmp_file) = file.keep().expect("persistent tempfile");
1114 debug!(
1115 "Creating temporary environment file: {}",
1116 tmp_file.to_string_lossy()
1117 );
1118 let tmp_file = CString::new(tmp_file.to_string_lossy().to_string()).expect("null byte");
1119
1120 if let Some(old) = &mut self.env_files {
1121 let mut bytes = old.as_bytes_with_nul().to_vec();
1122 bytes.extend(tmp_file.into_bytes_with_nul());
1123 self.env_files
1124 .replace(unsafe { CString::from_vec_with_nul_unchecked(bytes) });
1126 } else {
1127 self.env_files.replace(tmp_file);
1128 }
1129 }
1130 }
1131}
1132
1133impl SessionOptions {
1134 pub fn builder() -> SessionOptionsBuilder {
1136 SessionOptionsBuilder::default()
1137 }
1138}
1139
1140impl From<i32> for SessionState {
1141 fn from(s: i32) -> Self {
1142 match s {
1143 0 => SessionState::Ready,
1144 1 => SessionState::ReadyWithFatalErrors,
1145 2 => SessionState::ReadyWithCookErrors,
1146 3 => SessionState::StartingCook,
1147 4 => SessionState::Cooking,
1148 5 => SessionState::StartingLoad,
1149 6 => SessionState::Loading,
1150 7 => SessionState::Max,
1151 _ => panic!("Unmatched SessionState - {s}"),
1152 }
1153 }
1154}
1155
1156pub fn start_engine_pipe_server(
1158 path: impl AsRef<Path>,
1159 log_file: Option<&str>,
1160 options: &ThriftServerOptions,
1161) -> Result<u32> {
1162 debug!("Starting named pipe server: {:?}", path.as_ref());
1163 let log_file = log_file.map(CString::new).transpose()?;
1164 let c_str = utils::path_to_cstring(path)?;
1165 crate::ffi::clear_connection_error()?;
1166 crate::ffi::start_thrift_pipe_server(&c_str, &options.0, log_file.as_deref())
1167}
1168
1169pub fn start_engine_socket_server(
1171 port: u16,
1172 log_file: Option<&str>,
1173 options: &ThriftServerOptions,
1174) -> Result<u32> {
1175 debug!("Starting socket server on port: {}", port);
1176 let log_file = log_file.map(CString::new).transpose()?;
1177 crate::ffi::clear_connection_error()?;
1178 crate::ffi::start_thrift_socket_server(port as i32, &options.0, log_file.as_deref())
1179}
1180
1181pub fn start_houdini_server(
1183 pipe_name: impl AsRef<str>,
1184 houdini_executable: impl AsRef<Path>,
1185 fx_license: bool,
1186) -> Result<Child> {
1187 std::process::Command::new(houdini_executable.as_ref())
1188 .arg(format!("-hess=pipe:{}", pipe_name.as_ref()))
1189 .arg(if fx_license {
1190 "-force-fx-license"
1191 } else {
1192 "-core"
1193 })
1194 .stdin(std::process::Stdio::null())
1195 .stdout(std::process::Stdio::null())
1196 .stderr(std::process::Stdio::null())
1197 .spawn()
1198 .map_err(HapiError::from)
1199}
1200
1201pub fn start_shared_memory_server(
1203 memory_name: &str,
1204 options: &ThriftServerOptions,
1205 log_file: Option<&CStr>,
1206) -> Result<u32> {
1207 debug!("Starting shared memory server name: {memory_name}");
1208 let memory_name = CString::new(memory_name)?;
1209 crate::ffi::clear_connection_error()?;
1210 crate::ffi::start_thrift_shared_memory_server(&memory_name, &options.0, log_file.as_deref())
1211}
1212
1213pub fn quick_session(options: Option<&SessionOptions>) -> Result<Session> {
1216 let server_options = ThriftServerOptions::default()
1217 .with_auto_close(true)
1218 .with_timeout_ms(4000f32)
1219 .with_verbosity(StatusVerbosity::Statusverbosity0);
1220 let rand_memory_name = format!("shared-memory-{}", utils::random_string(16));
1221 let log_file = match &options {
1222 None => None,
1223 Some(opt) => opt.log_file.as_deref(),
1224 };
1225 let pid = start_shared_memory_server(&rand_memory_name, &server_options, log_file)?;
1226 connect_to_memory_server(&rand_memory_name, options, Some(pid))
1227}