Reference for the cvc::app context object — the data, properties,
threads, and named-mutex hub used throughout libcvc.
- Overview
- Quick Start
- Constructing an App
- Data Management
- Property Management
- Thread Management
- Mutex Management
- Type Registration
- Signals and Observers
- Utilities
- Design Patterns
- Thread Safety
- Complete Examples
- Best Practices
cvc::app is a constructible context object that bundles the
state libcvc components share among themselves:
- Data objects — type-safe storage via
boost::any - Properties — string-keyed configuration values with
lexical_castshortcuts - Threads — lifecycle management with progress reporting and an optional priority pool
- Named mutexes — process-wide resource locks identified by string name
- Signals —
boost::signals2change notifications on the four maps above
Construct as many cvc::app instances as you need — typically one
per logical application or per test fixture — and pass references
into the components that should share state.
cvc::app app; // default-constructed: registers libcvc's built-in types
// ... or, for a stripped-down instance (e.g. tests):
cvc::app bare(cvc::no_init_t{});History. Earlier versions of libcvc exposed
cvc::appas a process-wide singleton accessed viaapp::instance()and thecvcappmacro. Both have been removed;cvc::appis now exclusively constructed by callers and passed ascvc::app& ctx. The RAII helpersthread_info,thread_feedback, andscoped_lockall take a leadingapp& ctxparameter for this reason.
Key characteristics:
- Thread-safe with fine-grained locking
- Type-agnostic data storage via
boost::any - Optional human-readable type name registration
- Change notification via
boost::signals2 - Progress tracking and an optional priority thread pool
- Named-mutex system for cross-thread resource locks
#include <cvc/app.h>
cvc::app app; // your application's context object
// Store data
app.data("volume", myVolume);
app.data("geometry", myGeometry);
// Retrieve data
auto vol = app.data<cvc::volume>("volume");
auto geom = app.data<cvc::geometry>("geometry");
// Set properties (lexical_cast shortcut for non-string types)
app.properties("window.width", 1920);
app.properties("window.height", 1080);
app.properties("render.quality", "high");
// Get properties
int width = app.properties<int>("window.width");
std::string quality = app.properties("render.quality");
// Start a background thread; pass `app` to the RAII helper so it
// reports into this app's thread map.
app.startThread("loader", [&app]() {
cvc::thread_feedback feedback(app, "Loading data...");
app.threadProgress(0.5);
// ... more work ...
app.threadProgress(1.0);
});
// Named mutex for file access
{
cvc::scoped_lock lock(app, "output.dat", "Writing results");
writeFile("output.dat", results);
}Throughout the rest of this document, app denotes a cvc::app
instance you have constructed (as in the snippet above).
namespace cvc {
struct no_init_t {}; // tag type for the bare overload
class app {
public:
app(); // registers libcvc's built-in data types
explicit app(no_init_t); // skips type registration
~app();
// ... (rest of the API documented below)
};
}cvc::app app;Registers libcvc's built-in C++ → human-readable type-name
mappings (cvc::volume, cvc::geometry, the scalar types, etc.)
and installs the default file-format handlers.
cvc::app bare(cvc::no_init_t{});Skips type registration and default-handler installation. Useful in unit tests that exercise just the data map or property store and don't want the global IO handlers fired up.
cvc::app is move-only-ish (the copy constructor is private).
Construct one at the scope that owns it (main, a test fixture, a
cluster-node cvcsrv instance) and pass references downward.
The data map stores arbitrary typed objects using boost::any, providing type-safe storage and retrieval.
data_map data();Returns a copy of the entire data map.
data_map allData = app.data();
for (const auto& pair : allData) {
std::cout << pair.first << std::endl;
}boost::any data(const std::string& key);Returns the raw boost::any value for the given key.
boost::any rawValue = app.data("volume");void data(const std::string& key, const boost::any& value);Stores a value in the data map. Fires dataChanged signal.
app.data("volume", myVolume);
app.data("count", 42);
app.data("name", std::string("MyApp"));void data(const data_map& map);Merges all entries from the input map into the data map.
data_map batch;
batch["volume1"] = vol1;
batch["volume2"] = vol2;
app.data(batch);template<class T>
T data(const std::string& key);Returns the value cast to type T. Throws boost::bad_any_cast if type doesn't match.
auto vol = app.data<cvc::volume>("myVolume");
int count = app.data<int>("iteration");
std::string name = app.data<std::string>("projectName");template<class T>
bool isData(const std::string& key);Returns true if the key exists and can be cast to type T.
if (app.isData<cvc::volume>("volume")) {
auto vol = app.data<cvc::volume>("volume");
processVolume(vol);
}template<class T>
std::vector<std::string> data();Returns all keys that contain data of type T.
// Find all volumes in the data map
std::vector<std::string> volumeKeys = app.data<cvc::volume>();
for (const auto& key : volumeKeys) {
auto vol = app.data<cvc::volume>(key);
std::cout << "Volume: " << key << " - "
<< vol.XDim() << "x" << vol.YDim() << "x" << vol.ZDim()
<< std::endl;
}template<class T>
std::vector<T> data(const std::vector<std::string>& keys);Returns a vector of objects corresponding to the given keys.
std::vector<std::string> keys = {"vol1", "vol2", "vol3"};
std::vector<cvc::volume> volumes = app.data<cvc::volume>(keys);template<class Object>
void data(const std::vector<std::string>& keys,
const std::vector<Object>& v);Sets multiple data entries from parallel arrays.
std::vector<std::string> keys = {"result1", "result2", "result3"};
std::vector<cvc::volume> results = computeResults();
app.data(keys, results);template<class T>
void data(const std::vector<std::string>& keys, const T& value);Sets the same value for all specified keys.
std::vector<std::string> layers = {"layer1", "layer2", "layer3"};
app.data(layers, defaultVolume);template<class T>
std::vector<T> listData(const std::string& keylist);Parses a comma-separated list of keys and returns corresponding objects.
// Property contains: "volume1, volume2, volume3"
app.properties("input.volumes", "volume1, volume2, volume3");
// Retrieve all volumes in one call
auto volumes = app.listData<cvc::volume>(
app.properties("input.volumes")
);List Separators: Configured via system.list_separators property (default: ,)
Properties are string key-value pairs for configuration and settings.
property_map properties();Returns a copy of all properties.
property_map props = app.properties();
for (const auto& pair : props) {
std::cout << pair.first << " = " << pair.second << std::endl;
}std::string properties(const std::string& key);Returns the property value as a string.
std::string quality = app.properties("render.quality");
std::string outputPath = app.properties("output.path");void properties(const std::string& key, const std::string& val);Sets a property value. Fires propertiesChanged signal.
app.properties("render.quality", "high");
app.properties("output.format", "rawiv");void properties(const property_map& map);Replaces all properties with the given map.
property_map config;
config["window.width"] = "1920";
config["window.height"] = "1080";
app.properties(config);void addProperties(const property_map& map);Merges properties without clearing existing ones.
property_map additional;
additional["plugin.path"] = "/usr/local/plugins";
app.addProperties(additional);bool hasProperty(const std::string& key);Returns true if the property exists.
if (app.hasProperty("output.path")) {
std::string path = app.properties("output.path");
}template<class T>
void properties(const std::string& key, const T& val);Converts value to string using boost::lexical_cast before storing.
app.properties("window.width", 1920);
app.properties("threshold", 0.5);
app.properties("iterations", 100);
app.properties("enabled", true);template<class T>
T properties(const std::string& key);Converts string property to type T using boost::lexical_cast. Returns default-constructed T() if property doesn't exist.
int width = app.properties<int>("window.width");
double threshold = app.properties<double>("threshold");
bool enabled = app.properties<bool>("enabled");std::vector<std::string> listProperty(const std::string& key,
bool uniqueElements = false);Parses a comma-separated property value into a vector of strings.
// Property: "input.files" = "data1.rawiv, data2.rawiv, data3.rawiv"
auto files = app.listProperty("input.files");
// files = {"data1.rawiv", "data2.rawiv", "data3.rawiv"}
// With unique elements
app.properties("layers", "layer1, layer2, layer1, layer3");
auto uniqueLayers = app.listProperty("layers", true);
// uniqueLayers = {"layer1", "layer2", "layer3"}template<class T>
std::vector<T> listProperty(const std::string& key,
bool uniqueElements = false);Parses and converts each element to type T.
// Property: "thresholds" = "0.1, 0.5, 0.9"
auto thresholds = app.listProperty<double>("thresholds");
// thresholds = {0.1, 0.5, 0.9}
// Property: "resolutions" = "64, 128, 256, 512"
auto sizes = app.listProperty<int>("resolutions");
// sizes = {64, 128, 256, 512}void listPropertyAppend(const std::string& key, const std::string& val);Appends a value to a comma-separated list property.
app.properties("recent.files", "file1.dat");
app.listPropertyAppend("recent.files", "file2.dat");
app.listPropertyAppend("recent.files", "file3.dat");
// "recent.files" = "file1.dat, file2.dat, file3.dat"void listPropertyRemove(const std::string& key, const std::string& val);Removes a value from a comma-separated list property.
app.listPropertyRemove("recent.files", "file2.dat");
// "recent.files" = "file1.dat, file3.dat"template<class T>
std::vector<T> propertyData(const std::string& propKey,
bool uniqueElements = false);Reads a comma-separated list from a property, treats each item as a data key, and returns the corresponding objects.
// Store volumes in data map
app.data("input1", volume1);
app.data("input2", volume2);
app.data("input3", volume3);
// Configure which volumes to process via property
app.properties("pipeline.inputs", "input1, input2, input3");
// Retrieve all input volumes in one call
auto inputs = app.propertyData<cvc::volume>("pipeline.inputs");
// inputs = {volume1, volume2, volume3}
// Process them
for (auto& vol : inputs) {
processVolume(vol);
}void readPropertyMap(const std::string& path);Loads properties from a file (INI or JSON format via Boost.PropertyTree).
app.readPropertyMap("config.ini");
app.readPropertyMap("settings.json");void writePropertyMap(const std::string& path);Saves all properties to a file.
app.writePropertyMap("config.ini");
app.writePropertyMap("settings.json");The app class provides built-in thread management with progress tracking and lifecycle helpers.
template<class T>
void startThread(const std::string& key, const T& t, bool wait = true);Starts a new thread running the given functor.
Parameters:
key- Unique identifier for this threadt- Functor withoperator()(lambda, function object, etc.)wait- Iftrueand a thread with this key exists, wait for it to finish before starting new one
// Simple thread
app.startThread("worker", []() {
std::cout << "Working..." << std::endl;
});
// Thread with captured data
auto processData = [&volume]() {
thread_feedback feedback("Processing volume...");
// Long operation
app.threadProgress(0.5);
// More work
app.threadProgress(1.0);
};
app.startThread("processor", processData);
// Don't wait for existing thread (generates unique key)
app.startThread("task", task, false); // Creates "task.1", "task.2", etc.double threadProgress(const std::string& key = std::string());Returns progress (0.0 to 1.0) for the specified thread, or current thread if key is empty.
double progress = app.threadProgress("loader");
std::cout << "Loading: " << (progress * 100) << "%" << std::endl;void threadProgress(double progress); // 0.0 - 1.0
void threadProgress(const std::string& key, double progress);Sets progress for current thread or specified thread.
// In a thread
for (int i = 0; i < 100; i++) {
processItem(i);
app.threadProgress(i / 100.0);
}void finishThreadProgress(const std::string& key = std::string());Sets progress to 1.0 for the thread.
app.finishThreadProgress(); // Current threadvoid threadInfo(const std::string& key, const std::string& infostr);
std::string threadInfo(const std::string& key = std::string());Associates a status string with a thread for monitoring.
app.threadInfo("loader", "Loading file 1 of 10");
// Later
app.threadInfo("loader", "Parsing data...");
// Query status
std::string status = app.threadInfo("loader");void thisThreadInfo(const std::string& infostr);
std::string thisThreadInfo();Shorthand for current thread.
app.thisThreadInfo("Initializing...");class thread_feedback {
public:
thread_feedback(app& ctx, const std::string& key = "");
~thread_feedback();
};Automatically manages thread lifecycle:
- Constructor: sets progress to 0.0 in the bound app's thread map.
- Destructor: marks the thread "completed" and finishes its progress entry.
void workerThread(cvc::app& app) {
cvc::thread_feedback feedback(app, "Processing data");
for (int i = 0; i < 100; i++) {
processItem(i);
app.threadProgress(i / 100.0);
app.thisThreadInfo("Processing item " + std::to_string(i));
}
// Automatic cleanup on scope exit.
}
app.startThread("worker", [&app]{ workerThread(app); });class thread_info {
public:
thread_info(app& ctx, const std::string& info = "running");
~thread_info();
};Saves and restores thread info/progress when entering/exiting scopes.
void outerFunction(cvc::app& app) {
cvc::thread_info info(app, "Outer function");
app.threadProgress(0.2);
{
cvc::thread_info innerInfo(app, "Inner function");
app.threadProgress(0.5);
// Do work.
} // Progress and info restored to 0.2, "Outer function"
app.threadProgress(0.8);
}void wait(); // Non-static convenience wrapper
static void wait_for_threads(); // Static versionBlocks until all threads complete. Useful for cleanup or shutdown.
// Start multiple threads
app.startThread("worker1", task1);
app.startThread("worker2", task2);
app.startThread("worker3", task3);
// Wait for all to complete
app.wait();
std::cout << "All workers finished" << std::endl;thread_map threads();Returns map of all active threads.
thread_map active = app.threads();
std::cout << "Active threads: " << active.size() << std::endl;bool hasThread(const std::string& key);Returns true if thread exists.
if (app.hasThread("loader")) {
std::cout << "Loader is running" << std::endl;
}void removeThread(const std::string& key);Removes thread from tracking (doesn't stop the thread).
std::string uniqueThreadKey(const std::string& hint = std::string());Generates a unique thread key based on a hint.
std::string key = app.uniqueThreadKey("worker");
// Returns "worker.1", "worker.2", etc.Named mutex system for coordinating access to resources.
mutex_ptr mutex(const std::string& name);Returns a shared pointer to the named mutex, creating it if necessary.
auto fileMutex = app.mutex("output.dat");
boost::mutex::scoped_lock lock(*fileMutex);
// Exclusive access
writeToFile("output.dat", data);class scoped_lock {
public:
scoped_lock(app& ctx,
const std::string& name,
const std::string& info = std::string());
~scoped_lock();
};Acquires the named mutex from ctx.mutex(name) on construction and
releases it on destruction. The current thread key is automatically
prepended to the info string registered via mutexInfo, so the
mutex's debug record identifies the holder.
// Simple usage
{
cvc::scoped_lock lock(app, "database");
updateDatabase(); // exclusive access
} // lock released
// With description for debugging
{
cvc::scoped_lock lock(app, "output.vti", "Writing volume");
volume.write("output.vti");
}Typedef (in namespace cvc):
typedef app::scoped_lock scoped_lock;void mutexInfo(const std::string& name, const std::string& in);
std::string mutexInfo(const std::string& name);Associates debug information with a mutex to track who holds it.
// Set manually
app.mutexInfo("file.dat", "Thread 5: Writing results");
// Query (useful for debugging deadlocks)
std::string holder = app.mutexInfo("file.dat");
std::cout << "Lock held by: " << holder << std::endl;Note: scoped_lock automatically sets this to "<threadKey>: <info>"
Register human-readable names and enums for C++ types.
template<class T>
void registerDataType(const std::string& datatypename);Associates a friendly name with a C++ type.
app.registerDataType<cvc::volume>("Volume");
app.registerDataType<cvc::geometry>("Geometry");
app.registerDataType<std::vector<double>>("DoubleVector");Macro Shorthand:
#define registerDataType(type) registerDataType<type>(#type)
// Usage
app.registerDataType(volume); // Registers as "volume"
app.registerDataType(geometry); // Registers as "geometry"std::string dataTypeName(const std::string& key);Returns the registered name for the type of data at the key.
app.data("myVol", volume);
std::string type = app.dataTypeName("myVol");
// type = "Volume" (if registered)template<class T>
std::string dataTypeName();Returns the registered name for type T.
std::string volTypeName = app.dataTypeName<cvc::volume>();
// volTypeName = "Volume"std::string dataTypeName(const boost::any& d);Returns the registered name for a boost::any value.
boost::any data = myVolume;
std::string name = app.dataTypeName(data);template<class T>
void registerDataType(data_type dt);Associates a data_type enum value with a C++ type.
app.registerDataType<cvc::volume>(cvc::VolumeData);
app.registerDataType<cvc::geometry>(cvc::GeometryData);data_type dataType(const std::string& key);Returns the enum for the type of data at the key.
data_type type = app.dataType("myVolume");
if (type == cvc::VolumeData) {
// Handle volume
}template<class T>
data_type dataType();Returns the registered enum for type T.
data_type volType = app.dataType<cvc::volume>();
// volType = cvc::VolumeDataMonitor changes to the app state using Boost.Signals2.
map_change_signal dataChanged; // Fired when data map changes
map_change_signal propertiesChanged; // Fired when properties change
map_change_signal threadsChanged; // Fired when thread map changes
map_change_signal mutexesChanged; // Fired when mutex map changes// Monitor data changes
app.dataChanged.connect([](const std::string& key) {
std::cout << "Data changed: " << key << std::endl;
});
// Monitor property changes
app.propertiesChanged.connect([](const std::string& key) {
std::cout << "Property changed: " << key << std::endl;
});
// Monitor thread lifecycle
app.threadsChanged.connect([](const std::string& key) {
if (app.hasThread(key)) {
double progress = app.threadProgress(key);
std::cout << key << ": " << (progress * 100) << "%" << std::endl;
}
});std::vector<std::string> listify(const std::string& keylist);Parses a comma-separated string into a vector.
auto items = app.listify("item1, item2, item3");
// items = {"item1", "item2", "item3"}std::string listify(const std::vector<std::string>& keys);Joins a vector into a comma-separated string.
std::vector<std::string> items = {"a", "b", "c"};
std::string list = app.listify(items);
// list = "a, b, c"void sleep(double ms);Sleeps for the specified milliseconds.
app.sleep(100); // Sleep 100msvoid log(unsigned int level, const std::string& buf);Outputs a log message at the specified level.
app.log(0, "Info: Application started");
app.log(1, "Warning: Low memory");
app.log(2, "Error: Failed to load file");// Load configuration
app.readPropertyMap("config.ini");
// Access throughout application
int width = app.properties<int>("window.width");
std::string theme = app.properties("ui.theme");
// Save modified configuration
app.writePropertyMap("config.ini");// Stage 1: Load data
app.data("input", loadVolume("data.rawiv"));
// Stage 2: Process
auto input = app.data<cvc::volume>("input");
auto processed = applyFilter(input);
app.data("filtered", processed);
// Stage 3: Save results
auto result = app.data<cvc::volume>("filtered");
result.write("output.rawiv");// GUI thread
void updateProgressBar() {
if (app.hasThread("processor")) {
double progress = app.threadProgress("processor");
std::string info = app.threadInfo("processor");
progressBar->setValue(progress * 100);
statusLabel->setText(info);
}
}
// Worker thread
app.startThread("processor", []() {
thread_feedback feedback("Processing...");
for (int i = 0; i < 100; i++) {
app.thisThreadInfo("Processing item " + std::to_string(i));
app.threadProgress(i / 100.0);
processItem(i);
}
});// Multiple threads accessing same file
void writeResults(const std::string& filename, const Results& r) {
scoped_lock lock(filename, "Writing results");
std::ofstream out(filename, std::ios::app);
out << r.toString() << std::endl;
}
// Called from multiple threads safely
app.startThread("worker1", [&]() {
writeResults("output.txt", results1);
});
app.startThread("worker2", [&]() {
writeResults("output.txt", results2);
});// Setup observer
app.dataChanged.connect([](const std::string& key) {
if (key == "volume") {
auto vol = app.data<cvc::volume>(key);
updateVisualization(vol);
}
});
// Any code that modifies data triggers update
app.data("volume", newVolume); // Observer fires automatically✅ All public methods are thread-safe with fine-grained locking:
// Safe from multiple threads
app.data("key1", value1); // Thread 1
app.data("key2", value2); // Thread 2
app.properties("prop", val); // Thread 3All operations check for thread interruption:
app.startThread("worker", []() {
try {
for (int i = 0; i < 1000; i++) {
boost::this_thread::interruption_point();
// Work
}
} catch (boost::thread_interrupted&) {
// Clean shutdown
}
});
// Later, from another thread
auto thread = app.threads("worker");
thread->interrupt(); // Cooperative cancellationSignals are fired outside of lock scopes to prevent deadlocks when observers access the app.
// Safe: Observer can access app without deadlock
app.dataChanged.connect([](const std::string& key) {
// Can safely call app methods here
auto val = app.data(key);
app.properties("last.modified", key);
});The examples below assume an app of type cvc::app& is reachable
from the surrounding scope (a function parameter, a class member,
or a local variable created with cvc::app app;). Any thread
function or lambda that uses it should capture it by reference
(e.g. [&app]).
#include <cvc/app.h>
#include <cvc/volume.h>
void processingPipeline() {
// Register types
app.registerDataType<cvc::volume>("Volume");
// Load configuration
app.readPropertyMap("pipeline.ini");
// Get input files from config
auto inputFiles = app.listProperty("pipeline.inputs");
// Load all volumes
for (size_t i = 0; i < inputFiles.size(); i++) {
auto key = "input" + std::to_string(i);
app.data(key, cvc::volume(inputFiles[i]));
}
// Process each volume in parallel
auto volumeKeys = app.data<cvc::volume>();
for (const auto& key : volumeKeys) {
app.startThread("process_" + key, [key]() {
thread_feedback feedback("Processing " + key);
auto vol = app.data<cvc::volume>(key);
app.thisThreadInfo("Applying bilateral filter...");
vol.bilateralFilter(2.0, 0.1);
app.threadProgress(0.5);
app.thisThreadInfo("Saving result...");
vol.write("processed_" + key + ".rawiv");
app.threadProgress(1.0);
});
}
// Wait for all processing to complete
app.wait();
std::cout << "Pipeline complete" << std::endl;
}void parallelProcessing() {
// Prepare data
std::vector<cvc::volume> inputs = loadInputs();
// Store in app
for (size_t i = 0; i < inputs.size(); i++) {
app.data("input" + std::to_string(i), inputs[i]);
}
// Process in parallel
for (size_t i = 0; i < inputs.size(); i++) {
std::string key = "input" + std::to_string(i);
app.startThread("worker" + std::to_string(i), [key, i]() {
thread_feedback feedback;
auto vol = app.data<cvc::volume>(key);
for (int step = 0; step < 10; step++) {
app.thisThreadInfo("Step " + std::to_string(step));
app.threadProgress(step / 10.0);
processStep(vol, step);
app.sleep(100); // Simulate work
}
app.data("output" + std::to_string(i), vol);
});
}
// Monitor progress
while (app.threads().size() > 0) {
std::cout << "Active threads: " << app.threads().size() << std::endl;
app.sleep(500);
}
// Collect results
auto outputs = app.data<cvc::volume>();
std::cout << "Processed " << outputs.size() << " volumes" << std::endl;
}class MyApplication {
public:
void initialize() {
// Register types
app.registerDataType<cvc::volume>("Volume");
app.registerDataType<cvc::geometry>("Geometry");
// Load configuration
app.readPropertyMap("myapp.ini");
// Setup from configuration
int width = app.properties<int>("window.width");
int height = app.properties<int>("window.height");
std::string theme = app.properties("ui.theme");
createWindow(width, height, theme);
// Load recent files
auto recentFiles = app.listProperty("recent.files");
populateRecentMenu(recentFiles);
// Monitor configuration changes
app.propertiesChanged.connect([this](const std::string& key) {
if (key == "ui.theme") {
applyTheme(app.properties("ui.theme"));
}
});
}
void loadFile(const std::string& path) {
app.startThread("loader", [this, path]() {
thread_feedback feedback("Loading file");
app.thisThreadInfo("Reading file: " + path);
auto vol = cvc::volume(path);
app.threadProgress(0.5);
app.thisThreadInfo("Updating display");
app.data("current.volume", vol);
app.threadProgress(1.0);
// Update recent files
app.listPropertyAppend("recent.files", path);
});
}
void shutdown() {
// Wait for pending operations
app.wait();
// Save configuration
app.writePropertyMap("myapp.ini");
}
};void multiThreadedFileWriter() {
std::vector<std::string> workers = {"A", "B", "C", "D"};
for (const auto& name : workers) {
app.startThread("writer_" + name, [name]() {
thread_feedback feedback("Writer " + name);
for (int i = 0; i < 10; i++) {
// Acquire lock before writing
{
scoped_lock lock("output.log",
"Writer " + name + " iteration " + std::to_string(i));
std::ofstream out("output.log", std::ios::app);
out << "Writer " << name << " - Iteration " << i << std::endl;
}
app.threadProgress(i / 10.0);
app.sleep(100);
}
});
}
app.wait();
std::cout << "All writes complete" << std::endl;
}// Good
app.startThread("worker", []() {
thread_feedback feedback("Working");
// Automatic progress initialization and cleanup
});
// Avoid
app.startThread("worker", []() {
// Manual progress management (error-prone)
});// Good
app.data("user.preferences.volume1", vol);
app.properties("render.quality.high", "true");
// Avoid
app.data("v1", vol);
app.properties("q", "true");// Register early in main()
app.registerDataType<cvc::volume>("Volume");
app.registerDataType<cvc::geometry>("Geometry");
// Now get nice names
std::string type = app.dataTypeName("myVolume");
// type = "Volume" instead of mangled C++ name// Good - easy to modify without recompiling
app.properties("iterations", 100);
app.writePropertyMap("config.ini");
// Avoid - hardcoded
const int ITERATIONS = 100;// Good
app.startThread("processor", []() {
thread_feedback feedback("Processing");
for (int i = 0; i < 100; i++) {
app.threadProgress(i / 100.0);
app.thisThreadInfo("Processing item " + std::to_string(i));
// work
}
});
// Avoid - no progress feedback
app.startThread("processor", []() {
// User has no idea what's happening
});// Good - automatic lock management
{
scoped_lock lock("resource", "Operation X");
accessResource();
}
// Avoid - manual lock management
auto m = app.mutex("resource");
m->lock();
accessResource();
m->unlock(); // Easy to forget!// Good
app.startThread("worker", []() {
try {
for (int i = 0; i < 1000; i++) {
boost::this_thread::interruption_point();
// work
}
} catch (boost::thread_interrupted&) {
cleanup();
}
});
// Can safely interrupt
app.threads("worker")->interrupt();// Good - Observer pattern
app.dataChanged.connect([](const std::string& key) {
if (key.find("volume.") == 0) {
updateVisualization();
}
});
// Avoid - Tight coupling
void setVolume(const cvc::volume& vol) {
app.data("volume", vol);
updateVisualization(); // Hard-coded dependency
}Full Documentation: