本文主要是介绍翻译HoudiniEngine官方文档:Sessions,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
官方文档:《Houdini Engine 3.6: Sessions》
介绍
session的概念是在 Houdini Engine 2.x 中引入的。 在先前版本中,Houdini Engine 的动态链接库,也就是libHAPI
,会直接链接到Houdini的核心组件,而宿主程序通常又链接到libHAPI
。 这意味着 Houdini Engine 的实现是在宿主进程中运行的。
这一设计有两个主要问题:
- 因为
libHAPI
在宿主进程中加载了核心的Houdini库,而这些库又有多个第三方依赖项。因此它们很可能与宿主应用程序自身的依赖项发生冲突; - 每一个进程只能有一个Houdini场景。
在 Houdini Engine 3.x 中,我们仍支持这种“进程内”的形式——这对于简单的宿主程序可能仍旧是一个不错的选择。然而,我们还提供了一种新的 IPC(inter-process communication:进程间通信) 机制。这样,宿主应用程序可以运行多个session——在它自己的进程中或单独的进程中——以并行的方式或是在其他机器上(如果需要的话)。
每一个这样的session都用 HAPI_Session 来表示,大多数HAPI
中的函数都需要它作为参数。
进程外(Out-of-process)
另一方面,如果你想利用 “IPC实现” 的优势,或者说你想以较低的开销轻松地在 “IPC实现” 与 “进程内实现” 做切换,你应该将你的应用链接到libHAPIL
。libHAPIL
代表 Houdini Engine API Loader,它是一个充当 “楔子”(shim) 的库,它可以加载libHAPI
、libHARC
(我们的IPC客户端实现)、或者甚至是一个用户提供的动态库。
libHAPIL
导出在HAPI.h中声明的函数。这些函数中的大多数都被重定向到一个动态链接实现的库(例如libHAPI
或libHARC
),而其他函数是用于加载库和创建session的特殊函数,并在libHAPIL
本身中实现。
如果你将应用链接到libHAPIL
,那么你必须在调用任何使用session作为参数的HAPI
函数(包括HAPI_Initialize())之前,显式地创建一个session。
比如说,HAPI_CreateInProcessSession() 动态加载libHAPI
并在其输出参数中返回一个 “进程内session” 的单例。 请注意,在调用此函数之前,不会有任何Houdini库和依赖项在宿主进程中加载。 这可能有助于解决依赖关系问题。
你可以在调用HAPI_CreateInProcessSession()时传入 NULL 表示被创建的 “进程内session” 的单例。
如果你想使用我们的 “基于Thrift的IPC实现”,那么在创建session时,你可以:
- 使用HAPI_CreateThriftSocketSession()(使用“TCP sockets”作为传输层)
- 或者HAPI_CreateThriftNamedPipeSession()(在Windows系统上使用“named pipes”,在类Unix系统上使用“domain sockets”)
首次创建Thrift的session时,libHAPIL
会加载libHARC
,即客户端的动态库,为HAPI
调用实现Thrift绑定。
你甚至可以实现自己定义的IPC客户端库,使用HAPI_BindCustomImplementation()将其绑定到HAPI_SessionType枚举中的自定义插槽之一,并使用HAPI_CreateCustomSession()创建基于此实现的session,并将创建session所需的参数作为原始指针传递。
最后,当session的工作结束了之后,可以使用HAPI_CloseSession()将其关闭。 在libHARC
实现中,这将关闭客户端与服务器的连接。 如果在客户端的生存期内未使用HAPI_CloseSession()显式地关闭session,则在客户端进程终止时将自动关闭该session。
使用“自动关闭”这个选项创建session时,关联的服务器进程将在最后一个session关闭时终止。 请注意,在非Windows系统上,必须在宿主应用程序中处理“SIGCHLD event”,以使服务器进程完全终止并返回 license。
请注意,session可以打开多个,实现的库也可以同时加载多个。
这是个简单的示例,展示了如何启动和连接一个 “进程外session”,以及如何初始化Houdini Engine API。
// HARS(Houdini-Engine API Remote Server)选项
HAPI_ThriftServerOptions serverOptions{ 0 };
serverOptions.autoClose = true;
serverOptions.timeoutMs = 3000.0f;
// 使用"hapi"名字的 Named Pipe 来创建我们的HARS服务器
// 如果你之前已经手动启动了 HARS,则可以忽略这个调用
if (HAPI_RESULT_SUCCESS != HAPI_StartThriftNamedPipeServer(&serverOptions, "hapi", nullptr))return false;
// 创建一个新的 HAPI session,它会使用刚才那个服务器
HAPI_Session session;
if (HAPI_RESULT_SUCCESS != HAPI_CreateThriftNamedPipeSession(&session, "hapi"))return false;
// 初始化 HAPI
HAPI_CookOptions cookOptions = HAPI_CookOptions_Create();
if (HAPI_RESULT_SUCCESS != HAPI_Initialize(&session, // session:你正在与之交互的 session。如果是 NULL则表示使用默认的 “进程内session”&cookOptions, // cook options:一个全局的 cook option,后续的cook默认都会用它。// 每次cook都可以覆盖此设置,但是如果你选择在cook_on_load设置为true的情况下实例化 asset,则将使用这个选项。true, // use_cooking_thread:是否使用一个不同的线程来cook,这允许异步cook和更大的栈尺寸。-1, // cooking_thread_stack_size:设置cook线程的栈的尺寸,单位为 byte。“-1”代表使用 Houdini的默认值。"", // houdini_environment_files:一串路径列表(在Windows上以“;”作为分隔符,在Linux和Mac上以“:”作为分隔符)。// 这些文件后缀为 .env 的文件遵循Houdini用户偏好文件夹中的 houdini.env 文件的格式。// 它们将会在默认的 houdini.env 文件之后应用,并会覆盖掉进程的环境变量的值// 你可以使用它来在运行HoudiniEngine时强制为一个更严格的环境。详见:http://www.sidefx.com/docs/houdini/basics/config_envnullptr, // otl_search_path:OTL 的搜索路径。nullptr, // dso_search_path:通用的 DSO(自定义插件)的搜索路径。nullptr, // image_dso_search_path:图像的 DSO(自定义插件)的搜索路径。nullptr )) // audio_dso_search_path:音频的 DSO(自定义插件)的搜索路径。
{return false
}
进程内(In-Process)
如果你对使用IPC不感兴趣,只想在进程内运行 Houdini Engine,则可以将宿主应用程序直接链接到libHAPI
,并且在成功调用HAPI_Initialize()之前不需要调用任何其他函数。
libHAPI
包含 Houdini Engine 功能实际的实现,以及与Houdini核心组件直接进行交互的接口,但不包含IPC机制的实现。
如果是从更早的 HAPI 1.x 代码库中移植,则只需向所有HAPI调用添加HAPI_Session指针参数。 使用“进程内”的方式时,这个额外的session指针参数将被忽略,因此你需要将其设置为 NULL 以获得一致性。
HARS (Houdini-Engine API Remote Server)
在客户端上使用libHARC
(基于Thrift的IPC库)时,你需要启动服务器的可执行文件HARS
,该文件在Houdini安装目录中。 HARS是具有简单命令行参数的控制台应用程序:
$ HARS -h
Allowed options:-h [ --help ] 显示此帮助信息-s [ --socket-port ] arg 服务器端口(如果使用TCP Socket Session)-n [ --named-pipe ] arg pipe的名字(如果使用named pipe session)-a [ --auto-close ] 当所有客户端被关闭时,自动关闭服务器-r [ --ready-handle ] arg 服务器准备就绪时将设置的 Win32 Event 的句柄(Windows)服务器准备就绪时将发布的 POSIX named semaphore 的名称(Linux / macOS)(用于服务器自动启动)
HARS
直接链接到libHAPI
、核心的Houdini库、以及它们的依赖项。 因为基于 Thrift 的 IPC 是跨平台的,所以可以在不同平台上构建并运行主进程(使用libHAPIL
和libHARC
)和服务器进程(HARS
)。
你能通过“命令行”或者“管线脚本”以所需的选项来启动一个HARS
进程。然后使用相应的session创建函数,在应用程序的C ++代码中建立与服务器的客户端连接。或者,你也可以在创建session之前,方便地使用函数HAPI_StartThriftSocketServer() 或HAPI_StartThriftNamedPipeServer() 从C++代码启动服务器。这些函数都会将程序阻塞,直到服务器发出准备好服务的信号为止。也就是说,一旦这些函数成功地返回了,就说明已经可以安全地创建session了。
请注意,HARS
服务器当前仅支持单个客户端连接。 如果服务器已经与一个客户端连接了,那么当第二个客户端尝试连接时,第二个客户端就会阻塞,直到第一个连接关闭。
Houdini Engine Debugger
(译者注:在新版18.5中,此功能已经被 Houdini Engine SessionSync 功能替换)
多线程
使用libHARC
基于 Thrift 的 RPC 实现时,一个宿主应用程序进程可以与多个HAPI
的session进行交互,每个session都以其各自的Houdini状态代表其自己的HARS
进程。 这为使用Houdini Engine的应用程序打开了新的多线程可能性。 当使用来自多个线程的多个session时,libHARC
被设计为“线程安全”的。
对每个session的访问都受到互斥锁的保护,因此多线程可以安全地访问一个session。但事实上,仅使用一个session是无法真正实现“并行化”的。 在操作顺序有要求的情况下,客户端代码可能还需要在单个session上执行同步操作。 例如,在处理几何体时,设置几何体属性的顺序并不重要,重要的是HAPI_CommitGeo()需要在设置所有几何体属性之后调用。
另一方面,对于多个session,并行化成为可能,因为它们的Houdini上下文是完全独立的。 以下代码示例说明了如何通过独立的session进行操作,从而在异步任务之间并行地读写每个Tile的体数据。
class testharcSession
{
public:testharcSession(){mySession.type = HAPI_SESSION_MAX;mySession.id = 0;}virtual ~testharcSession(){if (mySession.type != HAPI_SESSION_MAX){HARC_TEST_SUCCESS(HAPI_IsSessionValid(&mySession));HARC_TEST_SUCCESS(HAPI_CloseSession(&mySession));HARC_TEST_ASSERT(HAPI_IsSessionValid(&mySession) == HAPI_RESULT_INVALID_SESSION);}}virtual void open() = 0;const HAPI_Session* get(){return &mySession;}
protected:HAPI_Session mySession;
};
class testharcSimpleAutoSession : public testharcSession
{
public:testharcSimpleAutoSession(const char* session_name_suffix,size_t task_id,const HAPI_ThriftServerOptions& server_options){std::ostringstream pipe_name_os;pipe_name_os << "/tmp/testharc_threading_"<< session_name_suffix << task_id << '_'
#ifdef WIN32<< GetCurrentProcessId();
#else<< getpid();
#endifmyPipeName = pipe_name_os.str();myServerOptions = server_options;myServerOptions.autoClose = true;}virtual ~testharcSimpleAutoSession(){if (mySession.type != HAPI_SESSION_MAX){HARC_TEST_SUCCESS(HAPI_IsInitialized(&mySession));HARC_TEST_SUCCESS(HAPI_Cleanup(&mySession));
#ifndef WIN32::unlink(myPipeName.c_str());
#endif}}virtual void open(){HARC_TEST_SUCCESS(HAPI_StartThriftNamedPipeServer(&myServerOptions, myPipeName.c_str(), nullptr));HARC_TEST_SUCCESS(HAPI_CreateThriftNamedPipeSession(&mySession,myPipeName.c_str()));HAPI_CookOptions cook_options = HAPI_CookOptions_Create();cook_options.splitGeosByGroup = false;HARC_TEST_SUCCESS(HAPI_Initialize(&mySession, &cook_options, true, -1,nullptr, nullptr, nullptr, nullptr, nullptr));}
private:HAPI_ThriftServerOptions myServerOptions;std::string myPipeName;
};
class testharcCopyTileValues
{
public:testharcCopyTileValues(const HAPI_Session* dst_session,const HAPI_Session* src_session,HAPI_NodeId node_id,HAPI_PartId part_id,HAPI_NodeId input_node_id,const HAPI_VolumeTileInfo& tile_info,int tile_value_count): myDstSession(dst_session), mySrcSession(src_session), myNodeId(node_id), myPartId(part_id), myInputNodeId(input_node_id), myTileInfo(tile_info), myTileValueCount(tile_value_count){}void operator()(){// 分配 Tile 数据的 Bufferstd::vector<float> tile_values(static_cast<size_t>(myTileValueCount), -5.8f);// 获得颜色数据HARC_TEST_SUCCESS(HAPI_GetVolumeTileFloatData(mySrcSession,myNodeId, myPartId,-8.8f,&myTileInfo, &tile_values.front(), myTileValueCount));// 设置输入体数据上的颜色数据HARC_TEST_SUCCESS(HAPI_SetVolumeTileFloatData(myDstSession,myInputNodeId,0,&myTileInfo, &tile_values.front(), myTileValueCount));}
private:const HAPI_Session * myDstSession;const HAPI_Session * mySrcSession;HAPI_NodeId myNodeId;HAPI_PartId myPartId;HAPI_NodeId myInputNodeId;HAPI_VolumeTileInfo myTileInfo;int myTileValueCount;
};
void
testharcCopyVolume(std::shared_ptr<testharcSession> src_session,std::shared_ptr<testharcSession> dst_session
)
{src_session->open();// 从文件加载资产库HAPI_AssetLibraryId library_id = -1;HARC_TEST_SUCCESS(HAPI_LoadAssetLibraryFromFile(src_session->get(), "HAPI_Test_Volumes_HoudiniFogColor.otl",false, &library_id));HARC_TEST_ASSERT(library_id >= 0);// 实例化资产HAPI_NodeId node_id = -1;HARC_TEST_SUCCESS(HAPI_CreateNode(src_session->get(), -1, "Object/HAPI_Test_Volumes_HoudiniFogColor",nullptr, true, &node_id));HAPI_GeoInfo geo_info;HARC_TEST_SUCCESS(HAPI_GetDisplayGeoInfo(src_session->get(), node_id, &geo_info));// 获得 part 信息,第二个(也就是说索引号是1)part应该是红色体数据const HAPI_PartId part_id = 1;// 获得 part 信息HAPI_PartInfo part_info;HARC_TEST_SUCCESS(HAPI_GetPartInfo(src_session->get(), geo_info.nodeId, part_id, &part_info));// 获得 volume 信息HAPI_VolumeInfo volume_info;HARC_TEST_SUCCESS(HAPI_GetVolumeInfo(src_session->get(), geo_info.nodeId, part_id, &volume_info));HARC_TEST_ASSERT(volume_info.tupleSize == 1);HARC_TEST_ASSERT(!volume_info.hasTaper);dst_session->open();// 创建输入节点来接收体数据HAPI_NodeId input_node_id = -1;HARC_TEST_SUCCESS(HAPI_CreateInputNode(dst_session->get(), &input_node_id, "Input_Volume"));// 设置 part 信息HARC_TEST_SUCCESS(HAPI_SetPartInfo(dst_session->get(), input_node_id, 0, &part_info));// 设置 volume 信息HARC_TEST_SUCCESS(HAPI_SetVolumeInfo(dst_session->get(), input_node_id, 0, &volume_info));// 获得第一个体数据 TileHAPI_VolumeTileInfo tile_info;HARC_TEST_SUCCESS(HAPI_GetFirstVolumeTile(src_session->get(), geo_info.nodeId, part_id, &tile_info));HARC_TEST_ASSERT(tile_info.isValid);std::vector< std::future< void > > futures;const int tile_value_count =volume_info.tileSize *volume_info.tileSize *volume_info.tileSize *volume_info.tupleSize;while (tile_info.isValid){futures.push_back(std::async(std::launch::async,testharcCopyTileValues(dst_session->get(), src_session->get(),geo_info.nodeId, part_id, input_node_id,tile_info, tile_value_count)));// 获得下一个TileHARC_TEST_SUCCESS(HAPI_GetNextVolumeTile(src_session->get(),geo_info.nodeId, part_id,&tile_info));}for (auto & future : futures)future.get();// 提交体数据输入HARC_TEST_SUCCESS(HAPI_CommitGeo(dst_session->get(), input_node_id));// 对节点进行 cookHARC_TEST_SUCCESS(HAPI_CookNode(dst_session->get(), input_node_id, nullptr));HARC_TEST_ASSERT(testharcVerifyInputVolume_HoudiniVolume(dst_session->get(), input_node_id, 0));
}
最后,用于管理session的函数(session创建函数,HAPI_IsSessionValid()和HAPI_CloseSession())共享一个互斥锁。但是它们不会干扰并发调用的常规HAPI函数(除了HAPI_CloseSession(),它会使session无效,如果随后将其传递给HAPI函数将导致未定义的行为)。
你始终可以安全地调用HAPI_IsSessionValid()来确定session是否有效。
这篇关于翻译HoudiniEngine官方文档:Sessions的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!