| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // Copyright Contributors to the OpenVDB Project | ||
| 2 | // SPDX-License-Identifier: MPL-2.0 | ||
| 3 | |||
| 4 | /// @file Queue.h | ||
| 5 | /// @author Peter Cucka | ||
| 6 | |||
| 7 | #ifndef OPENVDB_IO_QUEUE_HAS_BEEN_INCLUDED | ||
| 8 | #define OPENVDB_IO_QUEUE_HAS_BEEN_INCLUDED | ||
| 9 | |||
| 10 | #include <openvdb/Types.h> | ||
| 11 | #include <openvdb/Grid.h> | ||
| 12 | #include <algorithm> // for std::copy | ||
| 13 | #include <functional> | ||
| 14 | #include <iterator> // for std::back_inserter | ||
| 15 | #include <memory> | ||
| 16 | |||
| 17 | |||
| 18 | namespace openvdb { | ||
| 19 | OPENVDB_USE_VERSION_NAMESPACE | ||
| 20 | namespace OPENVDB_VERSION_NAME { | ||
| 21 | namespace io { | ||
| 22 | |||
| 23 | class Archive; | ||
| 24 | |||
| 25 | /// @brief Queue for asynchronous output of grids to files or streams | ||
| 26 | /// | ||
| 27 | /// @warning The queue holds shared pointers to grids. It is not safe | ||
| 28 | /// to modify a grid that has been placed in the queue. Instead, | ||
| 29 | /// make a deep copy of the grid (Grid::deepCopy()). | ||
| 30 | /// | ||
| 31 | /// @par Example: | ||
| 32 | /// @code | ||
| 33 | /// #include <openvdb/openvdb.h> | ||
| 34 | /// #include <openvdb/io/Queue.h> | ||
| 35 | /// #include <tbb/concurrent_hash_map.h> | ||
| 36 | /// #include <functional> | ||
| 37 | /// | ||
| 38 | /// using openvdb::io::Queue; | ||
| 39 | /// | ||
| 40 | /// struct MyNotifier | ||
| 41 | /// { | ||
| 42 | /// // Use a concurrent container, because queue callback functions | ||
| 43 | /// // must be thread-safe. | ||
| 44 | /// using FilenameMap = tbb::concurrent_hash_map<Queue::Id, std::string>; | ||
| 45 | /// FilenameMap filenames; | ||
| 46 | /// | ||
| 47 | /// // Callback function that prints the status of a completed task. | ||
| 48 | /// void callback(Queue::Id id, Queue::Status status) | ||
| 49 | /// { | ||
| 50 | /// const bool ok = (status == Queue::SUCCEEDED); | ||
| 51 | /// FilenameMap::accessor acc; | ||
| 52 | /// if (filenames.find(acc, id)) { | ||
| 53 | /// std::cout << (ok ? "wrote " : "failed to write ") | ||
| 54 | /// << acc->second << std::endl; | ||
| 55 | /// filenames.erase(acc); | ||
| 56 | /// } | ||
| 57 | /// } | ||
| 58 | /// }; | ||
| 59 | /// | ||
| 60 | /// int main() | ||
| 61 | /// { | ||
| 62 | /// // Construct an object to receive notifications from the queue. | ||
| 63 | /// // The object's lifetime must exceed the queue's. | ||
| 64 | /// MyNotifier notifier; | ||
| 65 | /// | ||
| 66 | /// Queue queue; | ||
| 67 | /// | ||
| 68 | /// // Register the callback() method of the MyNotifier object | ||
| 69 | /// // to receive notifications of completed tasks. | ||
| 70 | /// queue.addNotifier(std::bind(&MyNotifier::callback, ¬ifier, | ||
| 71 | /// std::placeholders::_1, std::placeholders::_2)); | ||
| 72 | /// | ||
| 73 | /// // Queue grids for output (e.g., for each step of a simulation). | ||
| 74 | /// for (int step = 1; step <= 10; ++step) { | ||
| 75 | /// openvdb::FloatGrid::Ptr grid = ...; | ||
| 76 | /// | ||
| 77 | /// std::ostringstream os; | ||
| 78 | /// os << "mygrid." << step << ".vdb"; | ||
| 79 | /// const std::string filename = os.str(); | ||
| 80 | /// | ||
| 81 | /// Queue::Id id = queue.writeGrid(grid, openvdb::io::File(filename)); | ||
| 82 | /// | ||
| 83 | /// // Associate the filename with the ID of the queued task. | ||
| 84 | /// MyNotifier::FilenameMap::accessor acc; | ||
| 85 | /// notifier.filenames.insert(acc, id); | ||
| 86 | /// acc->second = filename; | ||
| 87 | /// } | ||
| 88 | /// } | ||
| 89 | /// @endcode | ||
| 90 | /// Output: | ||
| 91 | /// @code | ||
| 92 | /// wrote mygrid.1.vdb | ||
| 93 | /// wrote mygrid.2.vdb | ||
| 94 | /// wrote mygrid.4.vdb | ||
| 95 | /// wrote mygrid.3.vdb | ||
| 96 | /// ... | ||
| 97 | /// wrote mygrid.10.vdb | ||
| 98 | /// @endcode | ||
| 99 | /// Note that tasks do not necessarily complete in the order in which they were queued. | ||
| 100 | class OPENVDB_API Queue | ||
| 101 | { | ||
| 102 | public: | ||
| 103 | /// Default maximum queue length (see setCapacity()) | ||
| 104 | static const Index32 DEFAULT_CAPACITY = 100; | ||
| 105 | /// @brief Default maximum time in seconds to wait to queue a task | ||
| 106 | /// when the queue is full (see setTimeout()) | ||
| 107 | static const Index32 DEFAULT_TIMEOUT = 120; // seconds | ||
| 108 | |||
| 109 | /// ID number of a queued task or of a registered notification callback | ||
| 110 | using Id = Index32; | ||
| 111 | |||
| 112 | /// Status of a queued task | ||
| 113 | enum Status { UNKNOWN, PENDING, SUCCEEDED, FAILED }; | ||
| 114 | |||
| 115 | |||
| 116 | /// Construct a queue with the given capacity. | ||
| 117 | explicit Queue(Index32 capacity = DEFAULT_CAPACITY); | ||
| 118 | /// Block until all queued tasks complete (successfully or unsuccessfully). | ||
| 119 | ~Queue(); | ||
| 120 | |||
| 121 | /// @brief Return @c true if the queue is empty. | ||
| 122 | bool empty() const; | ||
| 123 | /// @brief Return the number of tasks currently in the queue. | ||
| 124 | Index32 size() const; | ||
| 125 | |||
| 126 | /// @brief Return the maximum number of tasks allowed in the queue. | ||
| 127 | /// @details Once the queue has reached its maximum size, adding | ||
| 128 | /// a new task will block until an existing task has executed. | ||
| 129 | Index32 capacity() const; | ||
| 130 | /// Set the maximum number of tasks allowed in the queue. | ||
| 131 | void setCapacity(Index32); | ||
| 132 | |||
| 133 | /// Return the maximum number of seconds to wait to queue a task when the queue is full. | ||
| 134 | Index32 timeout() const; | ||
| 135 | /// Set the maximum number of seconds to wait to queue a task when the queue is full. | ||
| 136 | void setTimeout(Index32 seconds = DEFAULT_TIMEOUT); | ||
| 137 | |||
| 138 | /// @brief Return the status of the task with the given ID. | ||
| 139 | /// @note Querying the status of a task that has already completed | ||
| 140 | /// (whether successfully or not) removes the task from the status registry. | ||
| 141 | /// Subsequent queries of its status will return UNKNOWN. | ||
| 142 | Status status(Id) const; | ||
| 143 | |||
| 144 | using Notifier = std::function<void (Id, Status)>; | ||
| 145 | /// @brief Register a function that will be called with a task's ID | ||
| 146 | /// and status when that task completes, whether successfully or not. | ||
| 147 | /// @return an ID that can be passed to removeNotifier() to deregister the function | ||
| 148 | /// @details When multiple notifiers are registered, they are called | ||
| 149 | /// in the order in which they were registered. | ||
| 150 | /// @warning Notifiers are called from worker threads, so they must be thread-safe | ||
| 151 | /// and their lifetimes must exceed that of the queue. They must also not call, | ||
| 152 | /// directly or indirectly, addNotifier(), removeNotifier() or clearNotifiers(), | ||
| 153 | /// as that can result in a deadlock. | ||
| 154 | Id addNotifier(Notifier); | ||
| 155 | /// Deregister the notifier with the given ID. | ||
| 156 | void removeNotifier(Id); | ||
| 157 | /// Deregister all notifiers. | ||
| 158 | void clearNotifiers(); | ||
| 159 | |||
| 160 | /// @brief Queue a single grid for output to a file or stream. | ||
| 161 | /// @param grid the grid to be serialized | ||
| 162 | /// @param archive the io::File or io::Stream to which to output the grid | ||
| 163 | /// @param fileMetadata optional file-level metadata | ||
| 164 | /// @return an ID with which the status of the queued task can be queried | ||
| 165 | /// @throw RuntimeError if the task cannot be queued within the time limit | ||
| 166 | /// (see setTimeout()) because the queue is full | ||
| 167 | /// @par Example: | ||
| 168 | /// @code | ||
| 169 | /// openvdb::FloatGrid::Ptr grid = ...; | ||
| 170 | /// | ||
| 171 | /// openvdb::io::Queue queue; | ||
| 172 | /// | ||
| 173 | /// // Write the grid to the file mygrid.vdb. | ||
| 174 | /// queue.writeGrid(grid, openvdb::io::File("mygrid.vdb")); | ||
| 175 | /// | ||
| 176 | /// // Stream the grid to a binary string. | ||
| 177 | /// std::ostringstream ostr(std::ios_base::binary); | ||
| 178 | /// queue.writeGrid(grid, openvdb::io::Stream(ostr)); | ||
| 179 | /// @endcode | ||
| 180 | Id writeGrid(GridBase::ConstPtr grid, const Archive& archive, | ||
| 181 | const MetaMap& fileMetadata = MetaMap()); | ||
| 182 | |||
| 183 | /// @brief Queue a container of grids for output to a file. | ||
| 184 | /// @param grids any iterable container of grid pointers | ||
| 185 | /// (e.g., a GridPtrVec or GridPtrSet) | ||
| 186 | /// @param archive the io::File or io::Stream to which to output the grids | ||
| 187 | /// @param fileMetadata optional file-level metadata | ||
| 188 | /// @return an ID with which the status of the queued task can be queried | ||
| 189 | /// @throw RuntimeError if the task cannot be queued within the time limit | ||
| 190 | /// (see setTimeout()) because the queue is full | ||
| 191 | /// @par Example: | ||
| 192 | /// @code | ||
| 193 | /// openvdb::FloatGrid::Ptr floatGrid = ...; | ||
| 194 | /// openvdb::BoolGrid::Ptr boolGrid = ...; | ||
| 195 | /// openvdb::GridPtrVec grids; | ||
| 196 | /// grids.push_back(floatGrid); | ||
| 197 | /// grids.push_back(boolGrid); | ||
| 198 | /// | ||
| 199 | /// openvdb::io::Queue queue; | ||
| 200 | /// | ||
| 201 | /// // Write the grids to the file mygrid.vdb. | ||
| 202 | /// queue.write(grids, openvdb::io::File("mygrid.vdb")); | ||
| 203 | /// | ||
| 204 | /// // Stream the grids to a (binary) string. | ||
| 205 | /// std::ostringstream ostr(std::ios_base::binary); | ||
| 206 | /// queue.write(grids, openvdb::io::Stream(ostr)); | ||
| 207 | /// @endcode | ||
| 208 | template<typename GridPtrContainer> | ||
| 209 | Id write(const GridPtrContainer& grids, const Archive& archive, | ||
| 210 | const MetaMap& fileMetadata = MetaMap()); | ||
| 211 | |||
| 212 | private: | ||
| 213 | // Disallow copying of instances of this class. | ||
| 214 | Queue(const Queue&); | ||
| 215 | Queue& operator=(const Queue&); | ||
| 216 | |||
| 217 | Id writeGridVec(const GridCPtrVec&, const Archive&, const MetaMap&); | ||
| 218 | |||
| 219 | struct Impl; | ||
| 220 | std::unique_ptr<Impl> mImpl; | ||
| 221 | }; // class Queue | ||
| 222 | |||
| 223 | |||
| 224 | template<typename GridPtrContainer> | ||
| 225 | inline Queue::Id | ||
| 226 |
1/2✓ Branch 1 taken 20 times.
✗ Branch 2 not taken.
|
20 | Queue::write(const GridPtrContainer& container, |
| 227 | const Archive& archive, const MetaMap& metadata) | ||
| 228 | { | ||
| 229 | 20 | GridCPtrVec grids; | |
| 230 |
1/2✓ Branch 1 taken 20 times.
✗ Branch 2 not taken.
|
20 | std::copy(container.begin(), container.end(), std::back_inserter(grids)); |
| 231 |
2/2✓ Branch 1 taken 19 times.
✓ Branch 2 taken 1 times.
|
39 | return this->writeGridVec(grids, archive, metadata); |
| 232 | } | ||
| 233 | |||
| 234 | // Specialization for vectors of const Grid pointers; no copying necessary | ||
| 235 | template<> | ||
| 236 | inline Queue::Id | ||
| 237 | Queue::write<GridCPtrVec>(const GridCPtrVec& grids, | ||
| 238 | const Archive& archive, const MetaMap& metadata) | ||
| 239 | { | ||
| 240 | return this->writeGridVec(grids, archive, metadata); | ||
| 241 | } | ||
| 242 | |||
| 243 | } // namespace io | ||
| 244 | } // namespace OPENVDB_VERSION_NAME | ||
| 245 | } // namespace openvdb | ||
| 246 | |||
| 247 | #endif // OPENVDB_IO_QUEUE_HAS_BEEN_INCLUDED | ||
| 248 |