Skip to content

Commit df3c4e9

Browse files
refactor: Add a *consuming* data handle that removes a key (acts-project#4135)
This PR adds a consuming data handle, which removes a key from the whiteboard. I need this, because for the EDM4hep/PODIO writing, I need to take a `podio::Frame` that's stored in the whiteboard and move it into the PODIO IO system, and I don't want to copy the frame. The handle is fully integrated with the dependency tracking system so it will trigger an error if a read handle tries to access a key that was removed.
1 parent d48b8a6 commit df3c4e9

14 files changed

Lines changed: 949 additions & 188 deletions

File tree

Examples/Framework/include/ActsExamples/Framework/DataHandle.hpp

Lines changed: 143 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,30 @@
1313

1414
#include <stdexcept>
1515
#include <typeinfo>
16+
#include <unordered_map>
17+
18+
namespace Acts {
19+
class Logger;
20+
}
1621

1722
namespace ActsExamples {
1823

24+
/// Base class for all data handles.
25+
///
26+
/// Provides common functionality for tracking the parent sequence element
27+
/// and key name. The key is optional until explicitly initialized.
1928
class DataHandleBase {
20-
protected:
21-
virtual ~DataHandleBase() = default;
29+
private:
30+
struct StringHash {
31+
using is_transparent = void; // Enables heterogeneous operations.
2232

33+
std::size_t operator()(std::string_view sv) const {
34+
std::hash<std::string_view> hasher;
35+
return hasher(sv);
36+
}
37+
};
38+
39+
protected:
2340
DataHandleBase(SequenceElement* parent, const std::string& name)
2441
: m_parent(parent), m_name(name) {}
2542

@@ -28,6 +45,8 @@ class DataHandleBase {
2845
DataHandleBase(DataHandleBase&&) = default;
2946

3047
public:
48+
virtual ~DataHandleBase() = default;
49+
3150
const std::string& key() const { return m_key.value(); }
3251

3352
virtual const std::type_info& typeInfo() const = 0;
@@ -36,50 +55,115 @@ class DataHandleBase {
3655

3756
const std::string& name() const { return m_name; }
3857

39-
void maybeInitialize(const std::string& key) {
40-
if (!key.empty()) {
41-
m_key = key;
42-
}
43-
}
58+
void maybeInitialize(std::string_view key);
4459

4560
virtual bool isCompatible(const DataHandleBase& other) const = 0;
4661

62+
using StateMapType = std::unordered_map<std::string, const DataHandleBase*,
63+
StringHash, std::equal_to<>>;
64+
65+
virtual void emulate(StateMapType& state, WhiteBoard::AliasMapType& aliases,
66+
const Acts::Logger& logger) const = 0;
67+
4768
std::string fullName() const { return m_parent->name() + "." + name(); }
4869

4970
protected:
71+
void registerAsWriteHandle();
72+
void registerAsReadHandle();
73+
74+
// Trampoline functions to avoid having the WhiteBoard as a friend
75+
template <typename T>
76+
void add(WhiteBoard& wb, T&& object) const {
77+
wb.add(m_key.value(), std::forward<T>(object));
78+
}
79+
80+
template <typename T>
81+
const T& get(const WhiteBoard& wb) const {
82+
return wb.get<T>(m_key.value());
83+
}
84+
85+
template <typename T>
86+
T pop(WhiteBoard& wb) const {
87+
return wb.pop<T>(m_key.value());
88+
}
89+
5090
SequenceElement* m_parent{nullptr};
5191
std::string m_name;
5292
std::optional<std::string> m_key{};
5393
};
5494

95+
/// Base class for write data handles.
96+
///
97+
/// Write handles are used to store data in the WhiteBoard. They ensure that:
98+
/// - Each key can only be written once
99+
/// - The key must be non-empty
100+
/// - The data type is consistent for each key
55101
class WriteDataHandleBase : public DataHandleBase {
56102
protected:
57103
WriteDataHandleBase(SequenceElement* parent, const std::string& name)
58104
: DataHandleBase{parent, name} {}
59105

60106
public:
61-
void initialize(const std::string& key);
107+
void initialize(std::string_view key);
62108

63109
bool isCompatible(const DataHandleBase& other) const final;
110+
111+
void emulate(StateMapType& state, WhiteBoard::AliasMapType& aliases,
112+
const Acts::Logger& logger) const final;
64113
};
65114

115+
/// Base class for read data handles.
116+
///
117+
/// Read handles are used to access data from the WhiteBoard. They ensure that:
118+
/// - The data exists before reading
119+
/// - The data type matches the expected type
120+
/// - The data can be read multiple times
66121
class ReadDataHandleBase : public DataHandleBase {
67122
protected:
68-
ReadDataHandleBase(SequenceElement* parent, const std::string& name)
69-
: DataHandleBase{parent, name} {}
123+
using DataHandleBase::DataHandleBase;
70124

71125
public:
72-
void initialize(const std::string& key);
126+
void initialize(std::string_view key);
73127

74128
bool isCompatible(const DataHandleBase& other) const final;
129+
130+
void emulate(StateMapType& state, WhiteBoard::AliasMapType& aliases,
131+
const Acts::Logger& logger) const override;
132+
};
133+
134+
/// Base class for consume data handles.
135+
///
136+
/// Consume handles are used to take ownership of data from the WhiteBoard.
137+
/// They ensure that:
138+
/// - The data exists before consuming
139+
/// - The data type matches the expected type
140+
/// - The data can only be consumed once
141+
/// - The data is removed from the WhiteBoard after consumption
142+
class ConsumeDataHandleBase : public ReadDataHandleBase {
143+
protected:
144+
using ReadDataHandleBase::ReadDataHandleBase;
145+
146+
public:
147+
void emulate(StateMapType& state, WhiteBoard::AliasMapType& aliases,
148+
const Acts::Logger& logger) const override;
75149
};
76150

151+
/// A write handle for storing data in the WhiteBoard.
152+
///
153+
/// @tparam T The type of data to store
154+
///
155+
/// Example usage:
156+
/// @code
157+
/// WriteDataHandle<int> handle(parent, "my_data");
158+
/// handle.initialize("my_key");
159+
/// handle(wb, 42); // Store value
160+
/// @endcode
77161
template <typename T>
78162
class WriteDataHandle final : public WriteDataHandleBase {
79163
public:
80164
WriteDataHandle(SequenceElement* parent, const std::string& name)
81165
: WriteDataHandleBase{parent, name} {
82-
m_parent->registerWriteHandle(*this);
166+
registerAsWriteHandle();
83167
}
84168

85169
void operator()(const AlgorithmContext& ctx, T&& value) const {
@@ -91,18 +175,28 @@ class WriteDataHandle final : public WriteDataHandleBase {
91175
throw std::runtime_error{"WriteDataHandle '" + fullName() +
92176
"' not initialized"};
93177
}
94-
wb.add(m_key.value(), std::move(value));
178+
add(wb, std::move(value));
95179
}
96180

97181
const std::type_info& typeInfo() const override { return typeid(T); };
98182
};
99183

184+
/// A read handle for accessing data from the WhiteBoard.
185+
///
186+
/// @tparam T The type of data to read
187+
///
188+
/// Example usage:
189+
/// @code
190+
/// ReadDataHandle<int> handle(parent, "my_data");
191+
/// handle.initialize("my_key");
192+
/// const auto& value = handle(wb); // Access value
193+
/// @endcode
100194
template <typename T>
101195
class ReadDataHandle final : public ReadDataHandleBase {
102196
public:
103197
ReadDataHandle(SequenceElement* parent, const std::string& name)
104198
: ReadDataHandleBase{parent, name} {
105-
m_parent->registerReadHandle(*this);
199+
registerAsReadHandle();
106200
}
107201

108202
const T& operator()(const AlgorithmContext& ctx) const {
@@ -114,7 +208,41 @@ class ReadDataHandle final : public ReadDataHandleBase {
114208
throw std::runtime_error{"ReadDataHandle '" + fullName() +
115209
"' not initialized"};
116210
}
117-
return wb.get<T>(m_key.value());
211+
return get<T>(wb);
212+
}
213+
214+
const std::type_info& typeInfo() const override { return typeid(T); };
215+
};
216+
217+
/// A consume handle for taking ownership of data from the WhiteBoard.
218+
///
219+
/// @tparam T The type of data to consume
220+
///
221+
/// Example usage:
222+
/// @code
223+
/// ConsumeDataHandle<int> handle(parent, "my_data");
224+
/// handle.initialize("my_key");
225+
/// auto value = handle(wb); // Take ownership of value
226+
/// // value is no longer in WhiteBoard
227+
/// @endcode
228+
template <typename T>
229+
class ConsumeDataHandle final : public ConsumeDataHandleBase {
230+
public:
231+
ConsumeDataHandle(SequenceElement* parent, const std::string& name)
232+
: ConsumeDataHandleBase{parent, name} {
233+
registerAsReadHandle();
234+
}
235+
236+
T operator()(const AlgorithmContext& ctx) const {
237+
return (*this)(ctx.eventStore);
238+
}
239+
240+
T operator()(WhiteBoard& wb) const {
241+
if (!isInitialized()) {
242+
throw std::runtime_error{"ConsumeDataHandle '" + fullName() +
243+
"' not initialized"};
244+
}
245+
return pop<T>(wb);
118246
}
119247

120248
const std::type_info& typeInfo() const override { return typeid(T); };

Examples/Framework/include/ActsExamples/Framework/IAlgorithm.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ class IAlgorithm : public SequenceElement {
5151
/// Finalize the algorithm
5252
ProcessCode finalize() override { return ProcessCode::SUCCESS; }
5353

54+
std::string_view typeName() const override { return "Algorithm"; }
55+
5456
protected:
5557
const Acts::Logger& logger() const { return *m_logger; }
5658

Examples/Framework/include/ActsExamples/Framework/IReader.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ class IReader : public SequenceElement {
4848

4949
/// Fulfill the algorithm interface
5050
ProcessCode finalize() override { return ProcessCode::SUCCESS; }
51+
52+
/// Return the type for debug output
53+
std::string_view typeName() const override { return "Reader"; }
5154
};
5255

5356
} // namespace ActsExamples

Examples/Framework/include/ActsExamples/Framework/IWriter.hpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,11 @@ class IWriter : public SequenceElement {
3232
return write(context);
3333
}
3434

35-
/// Fulfil the algorithm interface
35+
/// Fulfill the algorithm interface
3636
ProcessCode initialize() override { return ProcessCode::SUCCESS; }
37+
38+
/// Return the type for debug output
39+
std::string_view typeName() const override { return "Writer"; }
3740
};
3841

3942
} // namespace ActsExamples

Examples/Framework/include/ActsExamples/Framework/SequenceElement.hpp

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,12 @@ class SequenceElement {
2525
public:
2626
virtual ~SequenceElement() = default;
2727

28-
/// The algorithm name.
28+
/// The sequence element name.
2929
virtual std::string name() const = 0;
3030

31+
/// The sequence element type name, used for debug output
32+
virtual std::string_view typeName() const = 0;
33+
3134
/// Initialize the algorithm
3235
virtual ProcessCode initialize() = 0;
3336

@@ -45,11 +48,7 @@ class SequenceElement {
4548
void registerWriteHandle(const DataHandleBase& handle);
4649
void registerReadHandle(const DataHandleBase& handle);
4750

48-
template <typename T>
49-
friend class WriteDataHandle;
50-
51-
template <typename T>
52-
friend class ReadDataHandle;
51+
friend class DataHandleBase;
5352

5453
friend class BufferedReader;
5554

Examples/Framework/include/ActsExamples/Framework/Sequencer.hpp

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@
99
#pragma once
1010

1111
#include "Acts/Plugins/FpeMonitoring/FpeMonitor.hpp"
12+
#include "ActsExamples/Framework/DataHandle.hpp"
1213
#include "ActsExamples/Framework/IAlgorithm.hpp"
1314
#include "ActsExamples/Framework/IContextDecorator.hpp"
1415
#include "ActsExamples/Framework/IReader.hpp"
1516
#include "ActsExamples/Framework/IWriter.hpp"
1617
#include "ActsExamples/Framework/SequenceElement.hpp"
18+
#include "ActsExamples/Framework/WhiteBoard.hpp"
1719
#include "ActsExamples/Utilities/tbbWrap.hpp"
1820
#include <Acts/Utilities/Logger.hpp>
1921

@@ -22,14 +24,12 @@
2224
#include <optional>
2325
#include <stdexcept>
2426
#include <string>
25-
#include <unordered_map>
2627
#include <utility>
2728
#include <vector>
2829

2930
#include <tbb/enumerable_thread_specific.h>
3031

3132
namespace ActsExamples {
32-
class DataHandleBase;
3333
class IAlgorithm;
3434
class IContextDecorator;
3535
class IReader;
@@ -45,8 +45,8 @@ class FpeFailure : public std::runtime_error {
4545

4646
class SequenceConfigurationException : public std::runtime_error {
4747
public:
48-
SequenceConfigurationException()
49-
: std::runtime_error{"Sequence configuration error"} {}
48+
explicit SequenceConfigurationException(const std::string &message)
49+
: std::runtime_error{"Sequence configuration error: " + message} {}
5050
};
5151

5252
/// A simple algorithm sequencer for event processing.
@@ -177,9 +177,9 @@ class Sequencer {
177177
std::vector<SequenceElementWithFpeResult> m_sequenceElements;
178178
std::unique_ptr<const Acts::Logger> m_logger;
179179

180-
std::unordered_multimap<std::string, std::string> m_whiteboardObjectAliases;
180+
WhiteBoard::AliasMapType m_whiteboardObjectAliases;
181181

182-
std::unordered_map<std::string, const DataHandleBase *> m_whiteBoardState;
182+
DataHandleBase::StateMapType m_whiteBoardState;
183183

184184
std::atomic<std::size_t> m_nUnmaskedFpe = 0;
185185

0 commit comments

Comments
 (0)