NVIDIA Index example code nvidia_logo_transpbg.gif Up
create_label_auto_width.cpp
Go to the documentation of this file.
1/******************************************************************************
2 * Copyright 2023 NVIDIA Corporation. All rights reserved.
3 *****************************************************************************/
6
7#include <mi/dice.h>
8
9// Include code shared by all examples
10#include "utility/example_shared.h"
11
12#include <nv/index/icamera.h>
13#include <nv/index/iconfig_settings.h>
14#include <nv/index/idepth_offset.h>
15#include <nv/index/idepth_test.h>
16#include <nv/index/iindex.h>
17#include <nv/index/ilabel.h>
18#include <nv/index/ilight.h>
19#include <nv/index/imaterial.h>
20#include <nv/index/iscene.h>
21#include <nv/index/iscene_group.h>
22#include <nv/index/isession.h>
23
24#include <nv/index/app/index_connect.h>
25#include <nv/index/app/string_dict.h>
26#include <nv/index/app/time_functions.h>
27
28#include "utility/app_rendering_context.h"
29#include "utility/canvas_utility.h"
30
31#include <algorithm>
32#include <iostream>
33#include <sstream>
34
35//----------------------------------------------------------------------
37 public nv::index::app::Index_connect
38{
39public:
41 :
42 Index_connect()
43 {
44 // INFO_LOG << "DEBUG: Create_label_auto_width() ctor";
45 }
46
48 {
49 // Note: Index_connect::~Index_connect() will be called after here.
50 // INFO_LOG << "DEBUG: ~Create_label_auto_width() dtor";
51 }
52
53 // launch application
54 mi::Sint32 launch();
55
56protected:
57 virtual bool evaluate_options(nv::index::app::String_dict& sdict) CPP11_OVERRIDE;
58 // override
60 mi::neuraylib::INetwork_configuration* network_configuration,
61 nv::index::app::String_dict& options) CPP11_OVERRIDE
62 {
63 check_success(network_configuration != 0);
64
65 check_success(options.is_defined("unittest"));
66 const bool is_unittest = nv::index::app::get_bool(options.get("unittest"));
67 if (is_unittest)
68 {
69 info_cout("NETWORK: disabled networking mode.", options);
70 network_configuration->set_mode(mi::neuraylib::INetwork_configuration::MODE_OFF);
71 return true;
72 }
73
74 return initialize_networking_as_default_udp(network_configuration, options);
75 }
76
77private:
78 void create_append_material_to_group(
79 nv::index::IScene* scene_edit,
80 nv::index::ITransformed_scene_group* group_node,
81 const mi::math::Color & ambient_color,
82 const mi::math::Color & diffuse_color,
83 const mi::math::Color & specular_color,
84 const mi::Float32 shiness,
85 mi::neuraylib::IDice_transaction* dice_transaction) const;
86
87 // depth offset attribute test: labels
88 //
89 // \param[in] scene_edit the editable IndeX scene
90 // \param[in] group_node parent group node
91 // \param[in] label_point label's corner position in the object space
92 // \param[in] label_str label contents string
93 // \param[in] label_height label height in the object space
94 // \param[in] label_width label width in the object space
95 // \param[in] fg_col label foreground color
96 // \param[in] bg_col label background color
97 // \param[in] dice_transaction db transaction
98 // \return created group node tag
99 void create_append_label_to_group(
100 nv::index::IScene* scene_edit,
101 nv::index::ITransformed_scene_group* group_node,
102 const mi::math::Vector<mi::Float32, 3>& label_point,
103 const std::string& label_str,
104 const mi::Float32 label_height,
105 const mi::Float32 label_width,
106 const mi::math::Color_struct& fg_col,
107 const mi::math::Color_struct& bg_col,
108 mi::neuraylib::IDice_transaction* dice_transaction) const;
109
110 // create test scene
111 //
112 // \param[in] scene_edit IScene for the scene edit.
113 // \param[in] dice_transaction dice transaction
114 void create_scene(
115 nv::index::IScene* scene_edit,
116 mi::neuraylib::IDice_transaction* dice_transaction) const;
117
118 // setup camera to see this example scene
119 //
120 // \param[in] cam a camera
121 void setup_camera(nv::index::IPerspective_camera* cam) const;
122
123 // render a frame
124 //
125 // \param[in] output_fname output rendering image filename
126 // \return performance values
127 nv::index::IFrame_results* render_frame(const std::string& output_fname) const;
128
129 // This session tag
130 mi::neuraylib::Tag m_session_tag;
131 // NVIDIA IndeX cluster configuration
132 mi::base::Handle<nv::index::ICluster_configuration> m_cluster_configuration;
133 // Application layer image file canvas (a render target)
134 mi::base::Handle<nv::index::app::canvas_infrastructure::IIndex_image_file_canvas> m_image_file_canvas;
135 // Create_label_auto_width
136 std::string m_outfname;
137 std::string m_font_fpath;
138 bool m_is_unittest;
139 std::string m_verify_image_fname;
140};
141
142//----------------------------------------------------------------------
144{
145 mi::Sint32 exit_code = 0;
146
147 // Get DiCE database components
148 {
149 m_cluster_configuration = get_index_interface()->get_api_component<nv::index::ICluster_configuration>();
150 check_success(m_cluster_configuration.is_valid_interface());
151
152 // create image canvas in application_layer
153 m_image_file_canvas = create_image_file_canvas(get_application_layer_interface());
154 check_success(m_image_file_canvas.is_valid_interface());
155
156 // Verifying that local host has joined
157 const int max_retry = 3;
158 for (int retry = 0; retry < max_retry; ++retry)
159 {
160 if (m_cluster_configuration->get_number_of_hosts() == 0)
161 {
162 INFO_LOG << "no host joined yet, retry "
163 << (retry + 1) << "/" << max_retry;
164 nv::index::app::util::time::sleep(0.3f);
165 }
166 }
167 check_success(m_cluster_configuration->get_number_of_hosts() != 0);
168
169 {
170 // Obtain a DiCE transaction
171 mi::base::Handle<mi::neuraylib::IDice_transaction> dice_transaction(
172 m_global_scope->create_transaction<mi::neuraylib::IDice_transaction>());
173 check_success(dice_transaction.is_valid_interface());
174 {
175 // Setup session information
176 m_session_tag =
177 m_index_session->create_session(dice_transaction.get());
178 check_success(m_session_tag.is_valid());
179
180 mi::base::Handle<const nv::index::ISession> session(
181 dice_transaction->access<nv::index::ISession>(
182 m_session_tag));
183 check_success(session.is_valid_interface());
184
185 mi::base::Handle< nv::index::IScene > scene_edit(
186 dice_transaction->edit<nv::index::IScene>(session->get_scene()));
187 check_success(scene_edit.is_valid_interface());
188
189 //----------------------------------------------------------------------
190 // Scene setup: add textured planes
191 //----------------------------------------------------------------------
192 create_scene(scene_edit.get(), dice_transaction.get());
193
194 mi::base::Handle< nv::index::IPerspective_camera > cam(
195 scene_edit->create_camera<nv::index::IPerspective_camera>());
196 check_success(cam.is_valid_interface());
197 setup_camera(cam.get());
198 const mi::neuraylib::Tag camera_tag = dice_transaction->store(cam.get());
199 check_success(camera_tag.is_valid());
200
201 const mi::math::Vector<mi::Uint32, 2> buffer_resolution(1024, 1024);
202 m_image_file_canvas->set_resolution(buffer_resolution);
203
204 // Set up the scene and define the region of interest
205 const mi::math::Bbox_struct<mi::Float32, 3> xyz_roi_st = {
206 { -500.0f, -500.0f, -500.0f, },
207 { 500.0f, 500.0f, 500.0f, },
208 };
209
210 // scope for a scene edit
211 {
212 mi::base::Handle< nv::index::IScene > scene(
213 dice_transaction->edit< nv::index::IScene >(session->get_scene()));
214 check_success(scene.is_valid_interface());
215
216 // set the region of interest
217 const mi::math::Bbox< mi::Float32, 3 > xyz_roi(xyz_roi_st);
218 check_success(xyz_roi.is_volume());
219 scene->set_clipped_bounding_box(xyz_roi_st);
220
221 // Set the scene global transformation matrix.
222 // only change the coordinate system
223 mi::math::Matrix<mi::Float32, 4, 4> transform_mat(
224 1.0f, 0.0f, 0.0f, 0.0f,
225 0.0f, 1.0f, 0.0f, 0.0f,
226 0.0f, 0.0f, -1.0f, 0.0f,
227 0.0f, 0.0f, 0.0f, 1.0f
228 );
229
230 scene->set_transform_matrix(transform_mat);
231
232 // Set the current camera to the scene.
233 check_success(camera_tag.is_valid());
234 scene->set_camera(camera_tag);
235 }
236 }
237 // Commit the transaction used for initialization
238 dice_transaction->commit();
239 }
240
241 // Rendering
242 {
243 // Render the frame to a file
244 const mi::Sint32 frame_idx = 0;
245 const std::string fname = get_output_file_name(m_outfname, frame_idx);
246 mi::base::Handle<nv::index::IFrame_results> frame_results(render_frame(fname));
247 const mi::base::Handle<nv::index::IError_set> err_set(frame_results->get_error_set());
248 if (err_set->any_errors())
249 {
250 std::ostringstream os;
251 const mi::Uint32 nb_err = err_set->get_nb_errors();
252 for (mi::Uint32 e = 0; e < nb_err; ++e)
253 {
254 if (e != 0) os << '\n';
255 const mi::base::Handle<nv::index::IError> err(err_set->get_error(e));
256 os << err->get_error_string();
257 }
258
259 ERROR_LOG << "IIndex_rendering rendering call failed with the following error(s): " << '\n'
260 << os.str();
261 exit_code = 1;
262 }
263
264
265 // verify the generated frame
266 if (!(verify_canvas_result(get_application_layer_interface(),
267 m_image_file_canvas.get(), m_verify_image_fname, get_options())))
268 {
269 exit_code = 1;
270 }
271 }
272 }
273
274 return exit_code;
275}
276
277
278//----------------------------------------------------------------------
279bool Create_label_auto_width::evaluate_options(nv::index::app::String_dict& sdict)
280{
281 const std::string com_name = sdict.get("command:", "<unknown_command>");
282 m_is_unittest = nv::index::app::get_bool(sdict.get("unittest", "false"));
283
284 if (m_is_unittest)
285 {
286 if (nv::index::app::get_bool(sdict.get("is_call_from_test", "false")))
287 {
288 sdict.insert("is_dump_comparison_image_when_failed", "0");
289 }
290 sdict.insert("outfname", ""); // turn off file output in the unit test mode
291 sdict.insert("dice::verbose", "2");
292 }
293
294 m_outfname = sdict.get("outfname", "");
295 m_font_fpath = sdict.get("font_fpath", "");
296 m_verify_image_fname = sdict.get("verify_image_fname", "");
297
298 info_cout(std::string("running ") + com_name, sdict);
299 info_cout("outfname = [" + m_outfname +
300 "], dice::verbose = " + sdict.get("dice::verbose"), sdict);
301
302 // print help and exit if -h
303 if (sdict.is_defined("h"))
304 {
305 std::cout
306 << "info: Usage: " << com_name << " [option]\n"
307 << "Option: [-h]\n"
308 << " printout this message\n"
309 << " [-dice::verbose severity_level]\n"
310 << " verbose severity level (3 is info.). (default: " + sdict.get("dice::verbose")
311 << ")\n"
312
313 << " [-font_fpath FONT_FILE_PATH]\n"
314 << " font file path. (default: " << m_font_fpath << ")\n"
315
316 << " [-outfname string]\n"
317 << " output ppm file base name. When empty, no output.\n"
318 << " A frame number and extension (.ppm) will be added.\n"
319 << " (default: [" << m_outfname << "])\n"
320 << " [-verify_image_fname [image_fname]]\n"
321 << " when image_fname exist, verify the rendering image. (default: ["
322 << m_verify_image_fname << "])\n"
323 << " [-unittest bool]\n"
324 << " when true, unit test mode. " << m_is_unittest
325 << std::endl;
326 exit(1);
327 }
328 return true;
329}
330
331//----------------------------------------------------------------------
332void Create_label_auto_width::create_append_material_to_group(
333 nv::index::IScene* scene_edit,
334 nv::index::ITransformed_scene_group* group_node,
335 const mi::math::Color & ambient_color,
336 const mi::math::Color & diffuse_color,
337 const mi::math::Color & specular_color,
338 const mi::Float32 shiness,
339 mi::neuraylib::IDice_transaction* dice_transaction) const
340{
341 check_success(scene_edit != 0);
342 check_success(group_node != 0);
343 check_success(dice_transaction != 0);
344
345 // Add a fully ambient material for the planes, so that lighting doesn't matter
346 mi::base::Handle<nv::index::IPhong_gl> phong_1(
347 scene_edit->create_attribute<nv::index::IPhong_gl>());
348
349 check_success(phong_1.is_valid_interface());
350 phong_1->set_ambient(ambient_color);
351 phong_1->set_diffuse(diffuse_color);
352 phong_1->set_specular(specular_color);
353 phong_1->set_shininess(shiness);
354
355 mi::neuraylib::Tag phong_1_tag = dice_transaction->store_for_reference_counting(phong_1.get());
356 check_success(phong_1_tag.is_valid());
357 group_node->append(phong_1_tag, dice_transaction);
358}
359
360
361//----------------------------------------------------------------------
362void Create_label_auto_width::create_append_label_to_group(
363 nv::index::IScene* scene_edit,
364 nv::index::ITransformed_scene_group* group_node,
365 const mi::math::Vector<mi::Float32, 3>& label_point,
366 const std::string& label_str,
367 const mi::Float32 label_height,
368 const mi::Float32 label_width,
369 const mi::math::Color_struct& fg_col,
370 const mi::math::Color_struct& bg_col,
371 mi::neuraylib::IDice_transaction* dice_transaction) const
372{
373 // Add font to the scene description
374 mi::base::Handle<nv::index::IFont> font(scene_edit->create_attribute<nv::index::IFont>());
375 check_success(font.is_valid_interface());
376 check_success(!m_font_fpath.empty());
377
378 if (font->set_file_name(m_font_fpath.c_str()))
379 {
380 INFO_LOG << "set the font path [" << m_font_fpath << "]";
381 }
382 else
383 {
384 ERROR_LOG << "Can not find the font path [" << m_font_fpath << "], "
385 << "the rendering result may not correct.";
386 }
387 font->set_font_resolution(64.0f);
388 const mi::neuraylib::Tag font_tag = dice_transaction->store_for_reference_counting(font.get());
389 check_success(font_tag.is_valid());
390 group_node->append(font_tag, dice_transaction);
391
392 // Add a label
393 {
394 mi::base::Handle<nv::index::ILabel_layout> label_layout(
395 scene_edit->create_attribute<nv::index::ILabel_layout>());
396 check_success(label_layout.is_valid_interface());
397 const mi::Float32 padding = 20.0f;
398 label_layout->set_padding(padding);
399 label_layout->set_color(fg_col, bg_col);
400 const mi::neuraylib::Tag label_layout_tag =
401 dice_transaction->store_for_reference_counting(label_layout.get());
402 check_success(label_layout_tag.is_valid());
403 group_node->append(label_layout_tag, dice_transaction);
404
405 mi::base::Handle<nv::index::ILabel_3D> label(scene_edit->create_shape<nv::index::ILabel_3D>());
406 check_success(label.is_valid_interface());
407 label->set_text(label_str.c_str());
408
409 const mi::math::Vector<mi::Float32, 3> right(1.0f, 0.0f, 0.0f);
410 const mi::math::Vector<mi::Float32, 3> up (0.0f, 1.0f, 0.0f);
411 label->set_geometry(label_point, right, up, label_height, label_width);
412 const mi::neuraylib::Tag label_tag = dice_transaction->store_for_reference_counting(label.get());
413 check_success(label_tag.is_valid());
414 group_node->append(label_tag, dice_transaction);
415 }
416}
417
418//----------------------------------------------------------------------
419void Create_label_auto_width::create_scene(
420 nv::index::IScene* scene_edit,
421 mi::neuraylib::IDice_transaction* dice_transaction) const
422{
423 check_success(scene_edit != 0);
424 check_success(dice_transaction != 0);
425
426 // Add a scene group where the shapes should be added
427 mi::base::Handle<nv::index::ITransformed_scene_group> main_group_node(
428 scene_edit->create_scene_group<nv::index::ITransformed_scene_group>());
429 check_success(main_group_node.is_valid_interface());
430
431 // Add a light and a material
432 {
433 // Add a light
434 mi::base::Handle<nv::index::IDirectional_headlight> headlight(
435 scene_edit->create_attribute<nv::index::IDirectional_headlight>());
436 check_success(headlight.is_valid_interface());
437 const mi::math::Color_struct color_intensity = { 1.0f, 1.0f, 1.0f, 1.0f, };
438 headlight->set_intensity(color_intensity);
439 headlight->set_direction(mi::math::Vector<mi::Float32, 3>(1.0f, -1.0f, -1.0f));
440 const mi::neuraylib::Tag headlight_tag = dice_transaction->store_for_reference_counting(headlight.get());
441 check_success(headlight_tag.is_valid());
442 main_group_node->append(headlight_tag, dice_transaction);
443
444 // Add a fully ambient material for the planes, so that lighting doesn't matter
445 create_append_material_to_group(scene_edit,
446 main_group_node.get(),
447 mi::math::Color(1.0f, 1.0f, 1.0f, 1.0f),
448 mi::math::Color(0.0f),
449 mi::math::Color(0.0f),
450 100.0f,
451 dice_transaction);
452 }
453
454 // create some labels with fixed width
455 {
456 mi::math::Color_struct fg_col; fg_col.r = 0.25f; fg_col.g = 0.95f; fg_col.b = 0.99f; fg_col.a = 1.0f;
457 mi::math::Color_struct bg_col; bg_col.r = 0.4f; bg_col.g = 0.5f; bg_col.b = 0.4f; bg_col.a = 0.5f;
458
459 mi::math::Vector<mi::Float32, 3> label_pos (-800.0f, 700.0f, 0.0f );
460 std::string str = "This label's size";
461 create_append_label_to_group(scene_edit, main_group_node.get(), label_pos, str,
462 100.0f, 700.0f, fg_col, bg_col, dice_transaction);
463
464 label_pos.y += -300.f;
465 str = "has to be";
466 create_append_label_to_group(scene_edit, main_group_node.get(), label_pos, str,
467 130.0f, 700.0f, fg_col, bg_col, dice_transaction);
468
469 label_pos.y += -300.f;
470 str = "manually adjusted";
471 create_append_label_to_group(scene_edit, main_group_node.get(), label_pos, str,
472 110.0f, 700.0f, fg_col, bg_col, dice_transaction);
473
474 label_pos.y += -300.f;
475 str = "to";
476 create_append_label_to_group(scene_edit, main_group_node.get(), label_pos, str,
477 165.0f, 700.0f, fg_col, bg_col, dice_transaction);
478
479 label_pos.y += -300.f;
480 str = "fit the text";
481 create_append_label_to_group(scene_edit, main_group_node.get(), label_pos, str,
482 135.0f, 700.0f, fg_col, bg_col, dice_transaction);
483
484 }
485
486 // create some other labels with auto width
487 {
488 mi::math::Color_struct fg_col; fg_col.r = 0.25f; fg_col.g = 0.95f; fg_col.b = 0.25f; fg_col.a = 1.0f;
489 mi::math::Color_struct bg_col; bg_col.r = 0.4f; bg_col.g = 0.5f; bg_col.b = 0.4f; bg_col.a = 0.5f;
490
491 mi::math::Vector<mi::Float32, 3> label_pos (0.0f, 700.0f, 0.0f );
492 std::string str = "This other labels instead";
493 create_append_label_to_group(scene_edit, main_group_node.get(), label_pos, str,
494 100.0f, -1.0f, fg_col, bg_col, dice_transaction);
495
496 label_pos.y += -300.f;
497 str = "can";
498 create_append_label_to_group(scene_edit, main_group_node.get(), label_pos, str,
499 130.0f, -1.0f, fg_col, bg_col, dice_transaction);
500
501 label_pos.y += -300.f;
502 str = "automatically adapt";
503 create_append_label_to_group(scene_edit, main_group_node.get(), label_pos, str,
504 110.0f, -1.0f, fg_col, bg_col, dice_transaction);
505
506 label_pos.y += -300.f;
507 str = "its size to";
508 create_append_label_to_group(scene_edit, main_group_node.get(), label_pos, str,
509 165.0f, -1.0f, fg_col, bg_col, dice_transaction);
510
511 label_pos.y += -300.f;
512 str = "fit the text";
513 create_append_label_to_group(scene_edit, main_group_node.get(), label_pos, str,
514 135.0f, -1.0f, fg_col, bg_col, dice_transaction);
515
516 }
517
518 // Finally append everything to the root of the hierachical scene description
519 const mi::neuraylib::Tag main_group_tag = dice_transaction->store_for_reference_counting(main_group_node.get());
520 check_success(main_group_tag.is_valid());
521 scene_edit->append(main_group_tag, dice_transaction);
522}
523
524//----------------------------------------------------------------------
525void Create_label_auto_width::setup_camera(nv::index::IPerspective_camera* cam) const
526{
527 check_success(cam != 0);
528
529 // Set the camera parameters to see the whole scene.
530 const mi::math::Vector<mi::Float32, 3> from(0.0f, 0.0f, 2000.0f);
531 const mi::math::Vector<mi::Float32, 3> to (0.0f, 0.0f, 0.0f);
532 const mi::math::Vector<mi::Float32, 3> up (0.0f, 1.0f, 0.0f);
533 mi::math::Vector<mi::Float32, 3> viewdir = to - from;
534 viewdir.normalize();
535
536 cam->set(from, viewdir, up);
537 cam->set_aperture(0.033f);
538 cam->set_aspect(1.0f);
539 cam->set_focal(0.03f);
540 cam->set_clip_min(80.0f);
541 cam->set_clip_max(5000.0f);
542}
543
544//----------------------------------------------------------------------
545nv::index::IFrame_results* Create_label_auto_width::render_frame(
546 const std::string& output_fname) const
547{
548 check_success(m_index_rendering.is_valid_interface());
549
550 // set output filename, empty string is valid
551 m_image_file_canvas->set_rgba_file_name(output_fname.c_str());
552
553 check_success(m_session_tag.is_valid());
554
555 mi::base::Handle<mi::neuraylib::IDice_transaction> dice_transaction(
556 m_global_scope->create_transaction<mi::neuraylib::IDice_transaction>());
557 check_success(dice_transaction.is_valid_interface());
558
559 m_index_session->update(m_session_tag, dice_transaction.get());
560
561 mi::base::Handle<nv::index::IFrame_results> frame_results(
562 m_index_rendering->render(
563 m_session_tag,
564 m_image_file_canvas.get(),
565 dice_transaction.get()));
566 check_success(frame_results.is_valid_interface());
567
568 dice_transaction->commit();
569
570 frame_results->retain();
571 return frame_results.get();
572}
573
574//----------------------------------------------------------------------
575// This example shows how to create label with automatic width setting
576int main(int argc, const char* argv[])
577{
578 nv::index::app::String_dict sdict;
579 sdict.insert("dice::verbose", "3"); // log level
580 sdict.insert("font_fpath", "/usr/share/fonts/dejavu/DejaVuSans.ttf"); // font path
581 sdict.insert("outfname", "frame_create_label_auto_width"); // output file base name
582 sdict.insert("verify_image_fname", ""); // for unit test
583 sdict.insert("unittest", "0"); // default mode
584 sdict.insert("is_dump_comparison_image_when_failed", "1"); // default: dump images when failed.
585 sdict.insert("is_call_from_test", "0"); // default: not call from make check.
586
587 // Load IndeX library via Index_connect
588 sdict.insert("dice::network::mode", "OFF");
589
590 // index setting
591 sdict.insert("index::config::set_monitor_performance_values", "true");
592 sdict.insert("index::service", "rendering_and_compositing");
593 sdict.insert("index::cuda_debug_checks", "false");
594
595 // application_layer component loading
596 sdict.insert("index::app::components::application_layer::component_name_list",
597 "canvas_infrastructure image io");
598
599 // Start IndeX via index_connect
600 Create_label_auto_width create_label_auto_width;
601 create_label_auto_width.initialize(argc, argv, sdict);
602 check_success(create_label_auto_width.is_initialized());
603
604 // launch the application. creating the scene and rendering.
605 const mi::Sint32 exit_code = create_label_auto_width.launch();
606 INFO_LOG << "Shutting down ...";
607
608 return exit_code;
609}
virtual bool initialize_networking(mi::neuraylib::INetwork_configuration *network_configuration, nv::index::app::String_dict &options) CPP11_OVERRIDE
virtual bool evaluate_options(nv::index::app::String_dict &sdict) CPP11_OVERRIDE
int main(int argc, const char *argv[])
#define check_success(expr)