Skip to content

Guidelines for Developing a New TTK Module

Julien Tierny edited this page Sep 15, 2022 · 38 revisions

Thanks for creating a new module for TTK!

Please take a few minutes to review our guidelines below, which will hopefully ease your work.

1. Module structure

As documented in TTK's companion paper, each TTK module is organized in 3 layers:

  • Base layer core/base/: This directory contains the implementations of the core algorithms.
  • VTK layer core/vtk: This directory provides wrappers for the VTK API.
  • ParaView layer paraview/: This directory provides XML specifications for ParaView plug-ins. We provide a set of scripts which will help you create automatically all the necessary files. To create a blank new module, enter the following command from the top of TTK's source tree: $ scripts/createTTKmodule.sh <Name, first letter in uppercase, no space. for example: HelloWorld>

Assuming your module name is HelloWorld, the above command will create the following directories:

  • core/base/helloWorld
  • core/vtk/ttkHelloWorld
  • paraview/HelloWorld
  • standalone/HelloWorld

The last directory (standalone/HelloWorld) contains 2 standalone programs using your new module (one in command line, the other one with a basic VTK-based GUI). If you do not wish to provide/maintain these standalone programs, simply remove the directory standalone/HelloWorld.

At this point, if you build and install TTK, your new module should be readily available and executable in ParaView.

If you wish to delete a module (for instance the above HelloWorld example), enter the following command: $ scripts/deleteTTKmodule.sh HelloWorld

You can also create a new module by cloning an already-existing module (which is particularly useful to copy a specific configuration of the VTK layer, in terms of inputs and outputs): scripts/cloneTTKmodule.sh

If you created your module with the createTTKmodule.sh script, you can start to adjust the module to your needs by typically updating the input and output specification at the VTK/ParaView layers. For this, follow the TODO comments, in increasing order, from the file core/vtk/ttkHelloWorld/ttkHelloWorld.h. Note that modifications made to the VTK layer will probably have to be reported to the ParaView and standalone components.

Once you have adjusted the inputs and outputs of your TTK module, you can proceed with the implementation of the core algorithm in the base layer. For this, follow the TODO comments, in increasing order, from the file core/base/helloWorld/HelloWorld.h. Note that this file contains many other comments providing useful recommendations (in particular regarding interactions with TTK's internal triangulation data structure).

Note that by default, new modules will use TTK's internal triangulation data structure for efficient mesh traversals. Please review this class documentation before using it, as it uses an original pre-conditioning mechanism.

Now, before diving in the implementation of your module, we invite you to read carefully the following recommendations.

2. Implementation recommendations

  • Base layer:

    • The base TTK classes (implementing the core of your module) should be implemented as templatized functors (as initially declared with the above scripts). This means that your base class should not store data. It should only be passed a pointer to an input buffer to read, and a pointer to an output buffer to fill. It is recommend to templatize the core functions of your class to be generic to the type of data considered on the input and the output (float, double, char, etc., as initially declared with the above scripts).
    • By default (as initially declared with the above scripts), each TTK base class inherits from the ttk::Debug class, which holds some information about:
      • the current debug level (debugLevel_, setup by the calling program with the function setDebugLevel())
      • the current thread number (threadNumber_, setup by the calling program with the function setThreadNumber(), default value: number of cores on the system).
      • a pointer to the calling wrapper (used by the VTK layer)
    • To print some messages on the standard outputs, please do not use std::cout or std::cerr. Please use the dMsg() function of your TTK class instead (see the doxygen documentation of the ttk::Debug class)
    • TTK provides a number of general-purpose classes that may make your life easier in the development of the base classes of your module:
      • To measure time in your code, please use the ttk::Timer class.
      • To perform some geometrical computation (dot products, cross products, barycentric coordinates, etc), please checkout the doxygen documentation of the ttk::Geometry class. It is likely that what you need is already implemented there. If it is not, this class is the right place to implement new, low-level, geometry related functions.
      • The ttk::CommandLineParser class provides a full featured command line parser (used by the standalone components of your module).
      • Several other classes from the base layer can be useful for the development of your own module (see for instance: ttk::Dijkstra class, ttk::DimensionReduction class, ttk::Laplacian class, ttk::UnionFind class). Please have a quick look at the list of available classes before implementing a new algorithm your module may need. Note that if your module needs a specific functionality, it may be useful to implement it as a separate base layer class (for instance the ttk::HarmonicField class uses the ttk::Laplacian class, which uses itself the ttk::Geometry class).
      • If you use an already-existing base layer class B in the base class A of your own module, please update A's CMakeLists.txt file accordingly to make sure that it links against B (see for instance core/base/scalarFieldCriticalPoints/CMakeLists.txt, where the class ttk::ScalarFieldCriticalPoints links against the ttk::UnionFind class in addition to the default ttk::Triangulation one).
    • We recommend to use OpenMP as soon as possible. Please wrap any OpenMP call around #ifdef TTK_ENABLE_OPENMP and #endif statements.
    • At the beginning of each function, wrap the check of the validity of its parameters around #ifdef TTK_ENABLE_KAMIKAZE and #endif statements (see core/base/blank/Blank.h for an example). When built in KAMIKAZE mode, TTK will skip these sanity checks for faster performances.
    • Function prototypes in the base layer are based on the following conventions:
      • Functions return an error code (0 if success).
      • Inputs are passed as function arguments with const references (const pointers if not possible otherwise).
      • Outputs are passed as function arguments with references (pointers if not possible otherwise).
  • Third-party dependencies:

    • Third-party dependencies should be handled at the base layer.
    • If a third-party dependency is not found, your module should still build and run. At run time, an error message can be reported about the missing dependency.
    • To add a third-party dependency in your code, follow the example of the inclusion of ZLIB:
  • TODO VTK layer:

    • pointer to VTK documentation
    • by default shallowcopy, deepcopy only for data which needs to be modified (smoother version of an input field)
    • GetVoidPointer
    • SetVoidPointer
  • TODO ParaView layer: make sure to assign your module to the right category.

  • TODO General recommendations:

    • Never use new (or related functions, such as VTK's ::New()).
    • Use performance profilers (perf+hotspot, 1 thread, with CMAKE_CXX_FLAGS="--ggdb -fno-omit-frame-pointer") and memory leak trackers (heaptrack)

3. Documentation

  • TTK uses Doxygen to generate online documentations.
  • Please provide a general documentation for each class you write.
  • Within each class, please provide a documentation for each function. See the ttk::Triangulation class for an example.

4. Authorship and references