Repository navigation
Expand file tree
/
Copy pathbt_file_logger_v2.cpp
More file actions
164 lines (133 loc) · 4.51 KB
/
Copy pathbt_file_logger_v2.cpp
File metadata and controls
164 lines (133 loc) · 4.51 KB
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
#include "behaviortree_cpp/loggers/bt_file_logger_v2.h"
#include "behaviortree_cpp/xml_parsing.h"
#include <array>
#include <condition_variable>
#include <cstring>
#include <deque>
#include <fstream>
#include <mutex>
#include <thread>
#include "flatbuffers/base.h"
namespace BT
{
namespace
{
int64_t ToUsec(Duration ts)
{
return std::chrono::duration_cast<std::chrono::microseconds>(ts).count();
}
} // namespace
// Define the private implementation struct
struct FileLogger2::Pimpl
{
std::ofstream file_stream;
std::mutex file_mutex; // Protects file_stream access from multiple threads
Duration first_timestamp = {};
std::deque<FileLogger2::Transition> transitions_queue;
std::condition_variable queue_cv;
std::mutex queue_mutex;
std::thread writer_thread;
std::atomic_bool loop = true;
std::atomic_bool writer_ready = false; // Signals writer thread is in wait loop
};
FileLogger2::FileLogger2(const BT::Tree& tree, std::filesystem::path const& filepath)
: StatusChangeLogger() // Deferred subscription
, _p(std::make_unique<Pimpl>())
{
if(filepath.filename().extension() != ".btlog")
{
throw RuntimeError("FileLogger2: the file extension must be [.btlog]");
}
enableTransitionToIdle(true);
//-------------------------------------
_p->file_stream.open(filepath, std::ofstream::binary | std::ofstream::out);
if(!_p->file_stream.is_open())
{
throw RuntimeError("problem opening file in FileLogger2");
}
_p->file_stream << "BTCPP4-FileLogger2";
const uint8_t protocol = 1;
_p->file_stream << protocol;
std::string const xml = WriteTreeToXML(tree, true, true);
// serialize the length of the buffer in the first 4 bytes
std::array<char, 8> write_buffer{};
flatbuffers::WriteScalar(write_buffer.data(), static_cast<int32_t>(xml.size()));
_p->file_stream.write(write_buffer.data(), 4);
// write the XML definition
_p->file_stream.write(xml.data(), static_cast<std::streamsize>(xml.size()));
_p->first_timestamp = std::chrono::system_clock::now().time_since_epoch();
// save the first timestamp in the next 8 bytes (microseconds)
const int64_t timestamp_usec = ToUsec(_p->first_timestamp);
flatbuffers::WriteScalar(write_buffer.data(), timestamp_usec);
_p->file_stream.write(write_buffer.data(), 8);
_p->writer_thread = std::thread(&FileLogger2::writerLoop, this);
// Wait for writer thread to signal readiness (under mutex for proper synchronization)
{
std::unique_lock lock(_p->queue_mutex);
_p->queue_cv.wait(lock, [this]() { return _p->writer_ready.load(); });
}
subscribeToTreeChanges(tree.rootNode());
}
FileLogger2::~FileLogger2()
{
_p->loop = false;
_p->queue_cv.notify_one();
_p->writer_thread.join();
_p->file_stream.close();
}
void FileLogger2::callback(Duration timestamp, const TreeNode& node,
NodeStatus /*prev_status*/, NodeStatus status)
{
Transition trans{};
trans.timestamp_usec = uint64_t(ToUsec(timestamp - _p->first_timestamp));
trans.node_uid = node.UID();
trans.status = static_cast<uint64_t>(status);
{
const std::scoped_lock lock(_p->queue_mutex);
_p->transitions_queue.push_back(trans);
}
_p->queue_cv.notify_one();
}
void FileLogger2::flush()
{
const std::scoped_lock lock(_p->file_mutex);
_p->file_stream.flush();
}
void FileLogger2::writerLoop()
{
// local buffer in this thread
std::deque<Transition> transitions;
// Signal readiness while holding the lock (establishes happens-before with constructor)
{
std::scoped_lock lock(_p->queue_mutex);
_p->writer_ready.store(true, std::memory_order_release);
}
_p->queue_cv.notify_one();
while(_p->loop)
{
transitions.clear();
{
std::unique_lock lock(_p->queue_mutex);
_p->queue_cv.wait_for(lock, std::chrono::milliseconds(10), [this]() {
return !_p->transitions_queue.empty() || !_p->loop;
});
// simple way to pop all the transitions from _p->transitions_queue into transitions
std::swap(transitions, _p->transitions_queue);
}
{
const std::scoped_lock file_lock(_p->file_mutex);
while(!transitions.empty())
{
const auto trans = transitions.front();
std::array<char, 9> write_buffer{};
std::memcpy(write_buffer.data(), &trans.timestamp_usec, 6);
std::memcpy(write_buffer.data() + 6, &trans.node_uid, 2);
std::memcpy(write_buffer.data() + 8, &trans.status, 1);
_p->file_stream.write(write_buffer.data(), 9);
transitions.pop_front();
}
_p->file_stream.flush();
}
}
}
} // namespace BT