1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239
|
---
description: Sight coding guidelines
---
This is Sight, the Surgical Image Guidance and Healthcare Toolkit.
Sight is based on a component architecture composed of C++ libraries. It uses an object-service concept approach.
Object and services communicates using signals and slots.
It is mainly written in C++20. Components and applications are described with XML files.
The documentation is available at https://sight.pages.ircad.fr/sight-doc/.
All code and comments are in english.
## File hierarchy
The codebase is organized as follows:
- /app: applications written in XML
- /cmake: build files with CMake
- /config: dynamic components containing only shared configurations in XML
- /example: more elaborated code than tutorials
- /lib: shared code
- /module: dynamic components that can be assigned to an application. Most services are implemented inside modules.
- /tutorial: simple samples that demonstrate basic features with an increasing difficulty
- /util: standalone executables
## Includes
Includes headers should always be minimal.
For header guards, we use the `#pragma once` directive.
## Namespaces
We use namespaces everywhere, and they follow the file tree layout from the root folders.
In C++, we specify namespaces on a single line
| Folder | Namespace | Example folder | Example namespace |
| --- | --- | --- | --- |
| app | sight::app_name | app/viewer | sight::viewer |
| config | sight::config::config_name | config/filter/image | sight::filter::image |
| lib | sight::lib_name | lib/filter/image | sight::filter::image |
| module | sight::module::module_name | lib/filter/image | sight::module::filter::image |
Creating a new module is described in https://sight.pages.ircad.fr/sight-doc/HowTos/ModuleCreation.html
Creating a new service is described in https://sight.pages.ircad.fr/sight-doc/HowTos/ServiceCreation.html#howtosservicecreation
## Testing
Unit-tests are present in each CMake target with C++ code. They lie in the target under the `test/ut` folder.
We use Doctest for unit testing. Each cpp file contains one or more TEST_SUITE(), named after the C++ class for instance
it tests (i.e. "sight::data::image"). The test name is free but must be comprehensive and not hold a redundant "test"
prefix or suffix.
## How-tos
### Create a new service
[//]: #cspell:ignore noexcept
To create a new service in a module, you must:
1. write a .hpp file with the following skeleton:
```cpp
namespace sight::module::my_module
{
class my_service final : public sight::service::base
{
public:
SIGHT_DECLARE_SERVICE(my_service, sight::service::base);
// Service constructor
my_service() noexcept = default;
// Service destructor.
~my_service() noexcept final = default;
protected:
// To configure the service
void configuring(const config_t& _config) final;
// To start the service
void starting() final;
// To stop the service
void stopping() final;
// To update the service
void updating() final;
};
} // namespace sight::module::my_module
```
A new service can be created by taking input from various data types, such as those found in the `lib/__/data`
directory. These data are always smart-pointed and registered with key name using `sight::data::ptr` for single data
or `sight::data::ptr_vector` for multiple data, for instance for `sight::data::image` :
```cpp
sight::data::ptr<sight::data::image> m_image { this, "image" };
```
See https://sight.pages.ircad.fr/sight-doc/SAD/src/ObjService.html for more details.
2. A corresponding .cpp file that implements the above methods.
3. Register the service in the `rc/plugin.xml` file, for example:
```xml
<extension implements="::sight::service::extension::factory">
<service>sight::module::my_module</service>
<type>sight::service::base</type>
<object>sight::data::image</object>
</extension>
```
4. Create the associated unit-test in the `test/ut` folder.
### Create a unit-test for a service
Every service must-be unit-tested. It is recommended to use a test fixture to initialize and destroy the service, this
way it is properly unregistered if the test fails, allowing other tests to be launched. Here is an example with a
service that works on an image.
```cpp
#include <data/image.hpp>
#include <service/op.hpp>
#include <boost/property_tree/xml_parser.hpp>
#include <doctest/doctest.h>
//------------------------------------------------------------------------------
TEST_SUITE("sight::module::my_service")
{
class service_fixture
{
public:
service_fixture()
{
service = sight::service::add("sight::module::my_service");
REQUIRE(service);
REQUIRE(service->is_a("sight::module::my_service"));
image = std::make_shared<sight::data::image>();
service->set_input(image, "image");
}
~service_fixture()
{
if(service->started())
{
service->stop().get();
}
sight::service::remove(service);
}
sight::service::base::sptr service;
sight::data::image::sptr image;
};
TEST_CASE_FIXTURE(service_fixture, "test_1")
{
sight::service::config_t config;
std::stringstream config_string;
config_string
<< "<properties prop1=\"90\" prop2=\"30\"/>";
boost::property_tree::read_xml(config_string, config);
service->set_config(config);
service->configure();
service->start().get();
service->update().get();
CHECK(...);
}
TEST_CASE_FIXTURE(service_fixture, "test_2")
{
sight::service::config_t config;
std::stringstream config_string;
config_string
<< "<properties prop1=\"90\" prop2=\"30\"/>";
boost::property_tree::read_xml(config_string, config);
service->set_config(config);
service->configure();
service->start().get();
service->update().get();
CHECK(...);
}
}
```
The filename must be `test/ut/my_service_test.cpp`.
In tests, services must be instantiated using the factory:
```cpp
auto srv = sight::service::add("sight::module::my_service");
```
and removed using:
```cpp
sight::service::remove(srv);
```
Services data (input/inout) must be setup as usual following the instructions in
https://sight.pages.ircad.fr/sight-doc/SAD/src/ObjService.html#object-service-registration.
Sight services always protect their member variables so they can't be tested. Instead we usually monitor the input/inout
data. We can also bind local slots in the test to services signals.
Here is an example to test for instance to test the internal state of a service, assuming the `updating()` method of
the service triggers the `sample_signal`.
```cpp
bool call_slot = false;
auto slot_called = sight::core::com::new_slot(
[&call_slot]()
{
call_slot = true;
});
slot_called->set_worker(sight::core::thread::get_default_worker());
srv->signal("sample_signal")->connect(slot_called);
srv->update().get();
SIGHT_TEST_WAIT(call_slot == true);
```
|