Odbxx: Difference between revisions
Stefan Ritt (talk | contribs) |
|||
(11 intermediate revisions by 3 users not shown) | |||
Line 1: | Line 1: | ||
A C++11 object-oriented interface to the [[ODB|ODB (online database)]] was introduced in May 2020. You can think of it like a "magic" map/dictionary that automatically sends changes you make to the ODB, and receives updates that others have made. | A C++11 object-oriented interface to the [[ODB|ODB (online database)]] was introduced in May 2020. You can think of it like a "magic" map/dictionary that automatically sends changes you make to the ODB, and receives updates that others have made. | ||
The header for this odbxx interface is at [https://bitbucket.org/tmidas/midas/src/develop/include/odbxx.h odbxx.h] and example usage in [https://bitbucket.org/tmidas/midas/src/develop/ | The header for this odbxx interface is at [https://bitbucket.org/tmidas/midas/src/develop/include/odbxx.h odbxx.h] and example usage in [https://bitbucket.org/tmidas/midas/src/develop/examples/odbxx/odbxx_test.cxx odbxx_test.cxx]. | ||
You can find more details about the ODB on the [[ODB_Access_and_Use|ODB Access and Use]] page, which includes links to the command-line, javascript, python, and non-object C++ interfaces. | You can find more details about the ODB on the [[ODB_Access_and_Use|ODB Access and Use]] page, which includes links to the command-line, javascript, python, and non-object C++ interfaces. | ||
Line 27: | Line 27: | ||
<pre>int curr_timeout = exp["Transition timeout"];</pre> | <pre>int curr_timeout = exp["Transition timeout"];</pre> | ||
'''Note''': The ODB directory you connect to ("/Experiment" in the above example), has to start with a "/" to tell the midas::odb object | |||
to connect directly to the ODB. Otherwise, a simple local midas::odb string object gets created without any connection to the ODB. | |||
== Automatic refreshing == | == Automatic refreshing == | ||
Line 106: | Line 109: | ||
<pre>// Define the ODB structure | <pre>// Define the ODB structure | ||
midas::odb | midas::odb o = { | ||
{"Int32 Key", 42}, | {"Int32 Key", 42}, | ||
{"Bool Key", true}, | {"Bool Key", true}, | ||
Line 119: | Line 122: | ||
}; | }; | ||
// Then sync the structure. | // Then sync the structure. This function | ||
// that | // - keeps the existing value of any keys that are in the ODB and your code | ||
// - creates any keys that are in your code but not yet in the ODB | |||
o.connect("/Test/Settings"); | o.connect("/Test/Settings"); | ||
// | // If you make the `write_defaults` argument true, then the function | ||
// | // - overwrites the value of any keys that are in the ODB with the value in your code | ||
// - creates any keys that are in your code but not yet in the ODB | |||
o.connect("/Test/Settings", true); | o.connect("/Test/Settings", true); | ||
// The `connect_and_fix_structure()` method acts like the old db_check_record() function, and | |||
// - keeps the existing value of any keys that are in the ODB and your code | |||
// - creates any keys that are in your code but not yet in the ODB | |||
// - deletes any keys that are in the ODB but not your code | |||
// - updates the order of keys in the ODB to match your code | |||
o.connect_and_fix_structure("/Test/Settings"); | |||
</pre> | </pre> | ||
Note that the ODB path in teh odb::connect() call must start with a '/'. The ODB root path like <code>o.connect("/");</code> is not allowed in this call. | |||
If you want to add new keys to existing ODB subdirectories, you can also just use the [] operator: | If you want to add new keys to existing ODB subdirectories, you can also just use the [] operator: | ||
Line 160: | Line 174: | ||
You can check whether a subkey exists using <code>odb::is_subkey()</code>. | You can check whether a subkey exists using <code>odb::is_subkey()</code>. | ||
== Callback functions == | == Callback functions == | ||
Line 176: | Line 182: | ||
is <code>cm_yield()</code> itself that calls your callback function. | is <code>cm_yield()</code> itself that calls your callback function. | ||
The callback functions can either be a "normal" function or a C++ | The callback functions can either be a "normal" function, a C++ lambda, or a member function of a C++ class. | ||
In | In all cases it should accept one argument - a <code>midas::odb</code> object (passed | ||
by reference) that contains the new state. | by reference) that contains the new state. | ||
Line 195: | Line 201: | ||
to_watch.watch(my_function); | to_watch.watch(my_function); | ||
</pre> | </pre> | ||
<pre>// Example with a member function of a class: | |||
#include <functional> | |||
void MyClass::my_function(midas::odb& arg) { | |||
std::cout << "Value of key \"" + arg.get_full_path() + "\" changed to " << arg << std::endl; | |||
} | |||
void MyClass::some_initialisation_code() { | |||
// Arguments to std::bind() are: "member function to call", "object to call that function on", "placeholder for midas::odb arg" | |||
std::function<void(midas::odb&)> callback = std::bind(&MyClass::my_function, this, std::placeholders::_1); | |||
midas::odb to_watch("/Experiment"); | |||
to_watch.watch(callback); | |||
} | |||
</pre> | |||
== Utility functions == | |||
There are various utility functions which can be used: | |||
==== void odb::create(const char *name, int type) ==== | |||
Simple wrapper around db_create_key() to create a single key in the ODB. <code>type</code> is one of TID_xxx. | |||
==== void odb::delete_key() ==== | |||
This member function of a midas::odb object deletes that object from the ODB: | |||
<pre>midas::odb o("/Some/ODB/Path"); | |||
o.delete_key(); | |||
</pre> | |||
==== int odb::delete_key(const std::string &name) ==== | |||
This function deletes a key or a subtree in the ODB passed by its path in the <code>name</code> argument. It is a simple wrapper around the C function db_delete_key() and returns the status of that function. | |||
==== bool odb::exists(const std::string *name) ==== | |||
This boolean function checks if a key given by its name exists in the ODB. | |||
==== void odb::set_debug(bool flag) / bool odb::get_debug() ==== | |||
These functions set and retrieve the debug flag. If the debug flag is <code>true</code> all communication with the ODB is printed to the screen. This can be helpful in debugging some problems. | |||
== Example code == | == Example code == | ||
A full working example exploring most of the features can be found in | A full working example exploring most of the features can be found in | ||
<code> | <code>odbxx/odbxx_test.cxx</code>. The test executable will be compiled as | ||
<code>build/ | <code>build/odbxx/odbxx_test</code> (it is not installed in the `bin` directory). |
Latest revision as of 05:48, 18 July 2024
A C++11 object-oriented interface to the ODB (online database) was introduced in May 2020. You can think of it like a "magic" map/dictionary that automatically sends changes you make to the ODB, and receives updates that others have made.
The header for this odbxx interface is at odbxx.h and example usage in odbxx_test.cxx.
You can find more details about the ODB on the ODB Access and Use page, which includes links to the command-line, javascript, python, and non-object C++ interfaces.
Basic usage
The simplest usage is like:
// Grab a bit of the ODB midas::odb exp("/Experiment"); // Simple read std::cout << "The current transition timeout is " << exp["Transition timeout"] << std::endl; // Make a change. The new value is automatically sent to the ODB. // Most C++ operators are supported (++, += etc), or you can do a simple // re-assignment like `exp["Transition timeout"] = 12345;`. exp["Transition timeout"] += 100; // Read the new value std::cout << "The transition timeout is now " << exp["Transition timeout"] << std::endl;
You can automatically cast to regular data types (int, double) etc if you want a copy of the value to work with:
int curr_timeout = exp["Transition timeout"];
Note: The ODB directory you connect to ("/Experiment" in the above example), has to start with a "/" to tell the midas::odb object to connect directly to the ODB. Otherwise, a simple local midas::odb string object gets created without any connection to the ODB.
Automatic refreshing
You may temporarily disable the automatic updating to/from the ODB
using odb::set_auto_refresh_write(false)
and odb::set_auto_refresh_read(false)
.
If auto-refresh is enabled (the default), your new values are sent to
the ODB as soon as you touch the value in the midas::odb
object. The ODB
is queried for new values whenever you access the value. In the above
example, the ODB is queried 4 times (during construction of exp
, and
each time exp["Transition timeout"]
is mentioned), and written to 1
time (when exp["Transition timeout"]
is assigned to).
See the #Callback functions section below for details on how to have a function called when a value changes.
Arrays/vectors
ODB arrays are represented by std vectors.
You can access/edit individual elements using []:
odb["Example"][1] = 1.2;
You can completely re-assign content using a std::vector or std::array:
std::vector<float> vec = std::vector<float>(10); odb["Example"] = vec;
You can resize arrays using odb::resize()
. If the existing array is longer,
it will be truncated; if shorter it will be extended with default values
(0 or an empty string).
odb["Example"].resize(5); // Now is 5 elements long
Note that arithmetic operators are supported for arrays, and will apply the operation to ALL ELEMENTS IN THE ARRAY:
// Create the vector std::vector<float> vec = std::vector<float>(2); vec[0] = 3; vec[1] = 5; // Assign in ODB odb["Example"] = vec; // Multiply ALL elements by 2 odb["Example"] *= 2; // odb["Example"] now contains {6, 10}.
You can directly iterate over arrays/vectors:
// Iterating using standard begin/end. for (auto it = o["Int Array"].begin(); it != o["Int Array"].end(); it++) { int val = *it; std::cout << val << std::endl; }
// Iterating using C++11 range-based for loop. for (int val : o["Int Array"]) { std::cout << val << std::endl; }
Strings
Strings in the ODB are returned as std::string (unlike the midas.h db_get_value()
family of functions, where strings are returned as char*). You may have vectors of strings.
Creating new bits of the ODB
You can automatically create bits of the ODB by passing a struct to the
midas::odb
constructor, then calling odb::connect()
, like:
// Define the ODB structure midas::odb o = { {"Int32 Key", 42}, {"Bool Key", true}, {"Subdir", { {"Float key", 1.2f}, // floats must be explicitly specified }}, {"Int Array", {1, 2, 3}}, {"Double Array", {1.2, 2.3, 3.4}}, {"String Array", {"Hello1", "Hello2", "Hello3"}}, {"Large Array", std::array<int, 10>{} }, // array with explicit size {"Large String", std::string(63, '\0') }, // string with explicit size }; // Then sync the structure. This function // - keeps the existing value of any keys that are in the ODB and your code // - creates any keys that are in your code but not yet in the ODB o.connect("/Test/Settings"); // If you make the `write_defaults` argument true, then the function // - overwrites the value of any keys that are in the ODB with the value in your code // - creates any keys that are in your code but not yet in the ODB o.connect("/Test/Settings", true); // The `connect_and_fix_structure()` method acts like the old db_check_record() function, and // - keeps the existing value of any keys that are in the ODB and your code // - creates any keys that are in your code but not yet in the ODB // - deletes any keys that are in the ODB but not your code // - updates the order of keys in the ODB to match your code o.connect_and_fix_structure("/Test/Settings");
Note that the ODB path in teh odb::connect() call must start with a '/'. The ODB root path like o.connect("/");
is not allowed in this call.
If you want to add new keys to existing ODB subdirectories, you can also just use the [] operator:
midas::odb existing_key("/MyExistingKey"); existing_key["MyNewSubKey"] = 1.23;
You can also create new keys by providing a default value when reading a value. If the key doesn't already exist, the default value will be used.
midas::odb existing_key("/MyExistingKey"); double val = existing_key["MyNewSubKey"](1.23);
Iterating over subkeys
You can iterate over subkeys using normal iterator functions.
// Iterating using standard begin/end. midas::odb exp("/Experiment"); for (auto it = exp.begin(); it != exp.end(); it++) { midas::odb& subkey = *it; std::cout << subkey.get_name() << " = " << subkey << std::endl; }
// Iterating using C++11 range-based for loop. for (midas::odb& subkey : exp) { std::cout << subkey.get_name() << " = " << subkey << std::endl; }
You can check whether a subkey exists using odb::is_subkey()
.
Callback functions
You may also set up callback functions that are called whenever a value
changes, using the odb::watch()
function. Note that you must call
cm_yield()
(from midas.h) periodically for this to work - deep down it
is cm_yield()
itself that calls your callback function.
The callback functions can either be a "normal" function, a C++ lambda, or a member function of a C++ class.
In all cases it should accept one argument - a midas::odb
object (passed
by reference) that contains the new state.
// Example with a lambda: midas::odb to_watch("/Experiment"); to_watch.watch([](midas::odb &arg) { std::cout << "Value of key \"" + arg.get_full_path() + "\" changed to " << arg << std::endl; });
// Example with a "normal" function: void my_function(midas::odb &arg) { std::cout << "Value of key \"" + arg.get_full_path() + "\" changed to " << arg << std::endl; } midas::odb to_watch("/Experiment"); to_watch.watch(my_function);
// Example with a member function of a class: #include <functional> void MyClass::my_function(midas::odb& arg) { std::cout << "Value of key \"" + arg.get_full_path() + "\" changed to " << arg << std::endl; } void MyClass::some_initialisation_code() { // Arguments to std::bind() are: "member function to call", "object to call that function on", "placeholder for midas::odb arg" std::function<void(midas::odb&)> callback = std::bind(&MyClass::my_function, this, std::placeholders::_1); midas::odb to_watch("/Experiment"); to_watch.watch(callback); }
Utility functions
There are various utility functions which can be used:
void odb::create(const char *name, int type)
Simple wrapper around db_create_key() to create a single key in the ODB. type
is one of TID_xxx.
void odb::delete_key()
This member function of a midas::odb object deletes that object from the ODB:
midas::odb o("/Some/ODB/Path"); o.delete_key();
int odb::delete_key(const std::string &name)
This function deletes a key or a subtree in the ODB passed by its path in the name
argument. It is a simple wrapper around the C function db_delete_key() and returns the status of that function.
bool odb::exists(const std::string *name)
This boolean function checks if a key given by its name exists in the ODB.
void odb::set_debug(bool flag) / bool odb::get_debug()
These functions set and retrieve the debug flag. If the debug flag is true
all communication with the ODB is printed to the screen. This can be helpful in debugging some problems.
Example code
A full working example exploring most of the features can be found in
odbxx/odbxx_test.cxx
. The test executable will be compiled as
build/odbxx/odbxx_test
(it is not installed in the `bin` directory).