Files
allwpilib/datalog/src/main/native/cpp/DataLog.cpp
2025-11-07 23:09:21 -08:00

865 lines
26 KiB
C++

// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "wpi/datalog/DataLog.hpp"
#include <algorithm>
#include <bit>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <utility>
#include <vector>
#include "wpi/util/Endian.hpp"
#include "wpi/util/Logger.hpp"
#include "wpi/util/SmallString.hpp"
#include "wpi/util/print.hpp"
#include "wpi/util/timestamp.h"
using namespace wpi::log;
static constexpr size_t kRecordMaxHeaderSize = 17;
static void DefaultLog(unsigned int level, const char* file, unsigned int line,
const char* msg) {
if (level > wpi::util::WPI_LOG_INFO) {
wpi::util::print(stderr, "DataLog: {}\n", msg);
} else if (level == wpi::util::WPI_LOG_INFO) {
wpi::util::print("DataLog: {}\n", msg);
}
}
wpi::util::Logger DataLog::s_defaultMessageLog{DefaultLog};
template <typename T>
static unsigned int WriteVarInt(uint8_t* buf, T val) {
unsigned int len = 0;
do {
*buf++ = static_cast<unsigned int>(val) & 0xff;
++len;
val >>= 8;
} while (val != 0);
return len;
}
// min size: 4, max size: 17
static unsigned int WriteRecordHeader(uint8_t* buf, uint32_t entry,
uint64_t timestamp,
uint32_t payloadSize) {
uint8_t* origbuf = buf++;
unsigned int entryLen = WriteVarInt(buf, entry);
buf += entryLen;
unsigned int payloadLen = WriteVarInt(buf, payloadSize);
buf += payloadLen;
unsigned int timestampLen =
WriteVarInt(buf, timestamp == 0 ? wpi::util::Now() : timestamp);
buf += timestampLen;
*origbuf =
((timestampLen - 1) << 4) | ((payloadLen - 1) << 2) | (entryLen - 1);
return buf - origbuf;
}
void DataLog::StartFile() {
std::scoped_lock lock{m_mutex};
if (m_active) {
return;
}
// Grab previously pending writes
std::vector<Buffer> bufs;
bufs.swap(m_outgoing);
m_outgoing.reserve(bufs.size() + 1);
// File header (version 1.0)
uint8_t* buf = Reserve(m_extraHeader.size() + 12);
static const uint8_t header[] = {'W', 'P', 'I', 'L', 'O', 'G', 0, 1};
std::memcpy(buf, header, 8);
wpi::util::support::endian::write32le(buf + 8, m_extraHeader.size());
std::memcpy(buf + 12, m_extraHeader.data(), m_extraHeader.size());
// Existing start and schema data records
for (auto&& entryInfo : m_entries) {
AppendStartRecord(entryInfo.second.id, entryInfo.first,
entryInfo.second.type,
m_entryIds[entryInfo.second.id].metadata, 0);
}
// Existing schema data records
for (auto&& schemaInfo : m_schemas) {
if (schemaInfo.second.id != 0) {
StartRecord(schemaInfo.second.id, 0, schemaInfo.second.data.size(), 0);
AppendImpl(schemaInfo.second.data);
}
}
// Append previously pending writes
for (auto&& buf : bufs) {
m_outgoing.emplace_back(std::move(buf));
}
m_active = true;
}
void DataLog::FlushBufs(std::vector<Buffer>* writeBufs) {
std::scoped_lock lock{m_mutex};
writeBufs->swap(m_outgoing);
DoReleaseBufs(&m_outgoing);
}
void DataLog::ReleaseBufs(std::vector<Buffer>* bufs) {
std::scoped_lock lock{m_mutex};
DoReleaseBufs(bufs);
}
void DataLog::Pause() {
std::scoped_lock lock{m_mutex};
m_paused = true;
}
void DataLog::Resume() {
std::scoped_lock lock{m_mutex};
m_paused = false;
}
void DataLog::Stop() {
std::scoped_lock lock{m_mutex};
m_active = false;
}
void DataLog::BufferHalfFull() {}
bool DataLog::HasSchema(std::string_view name) const {
std::scoped_lock lock{m_mutex};
return m_schemas.contains(name);
}
void DataLog::AddSchema(std::string_view name, std::string_view type,
std::span<const uint8_t> schema, int64_t timestamp) {
std::scoped_lock lock{m_mutex};
auto& schemaInfo = m_schemas[name];
if (schemaInfo.id != 0) {
return; // don't add duplicates
}
schemaInfo.data.assign(schema.begin(), schema.end());
wpi::util::SmallString<128> fullName{"/.schema/"};
fullName += name;
int entry = StartImpl(fullName, type, {}, timestamp);
// inline AppendRaw() without releasing lock
if (entry <= 0) {
[[unlikely]] return; // should never happen, but check anyway
}
schemaInfo.id = entry;
if (!m_active) {
[[unlikely]] return;
}
StartRecord(entry, timestamp, schema.size(), 0);
AppendImpl(schema);
}
// Control records use the following format:
// 1-byte type
// 4-byte entry
// rest of data (depending on type)
int DataLog::Start(std::string_view name, std::string_view type,
std::string_view metadata, int64_t timestamp) {
std::scoped_lock lock{m_mutex};
return StartImpl(name, type, metadata, timestamp);
}
int DataLog::StartImpl(std::string_view name, std::string_view type,
std::string_view metadata, int64_t timestamp) {
auto& entryInfo = m_entries[name];
if (entryInfo.id == 0) {
entryInfo.id = ++m_lastId;
}
auto& entryInfo2 = m_entryIds[entryInfo.id];
++entryInfo2.count;
if (entryInfo2.count > 1) {
if (entryInfo.type != type) {
WPI_ERROR(m_msglog,
"type mismatch for '{}': was '{}', requested '{}'; ignoring",
name, entryInfo.type, type);
return 0;
}
return entryInfo.id;
}
entryInfo.type = type;
entryInfo2.metadata = metadata;
if (!m_active) {
[[unlikely]] return entryInfo.id;
}
AppendStartRecord(entryInfo.id, name, type, metadata, timestamp);
return entryInfo.id;
}
void DataLog::AppendStartRecord(int id, std::string_view name,
std::string_view type,
std::string_view metadata, int64_t timestamp) {
size_t strsize = name.size() + type.size() + metadata.size();
uint8_t* buf = StartRecord(0, timestamp, 5 + 12 + strsize, 5);
*buf++ = impl::kControlStart;
wpi::util::support::endian::write32le(buf, id);
AppendStringImpl(name);
AppendStringImpl(type);
AppendStringImpl(metadata);
}
void DataLog::DoReleaseBufs(std::vector<Buffer>* bufs) {
for (auto&& buf : *bufs) {
buf.Clear();
if (m_free.size() < kMaxFreeCount) {
[[likely]] m_free.emplace_back(std::move(buf));
}
}
bufs->resize(0);
}
void DataLog::Finish(int entry, int64_t timestamp) {
if (entry <= 0) {
return;
}
std::scoped_lock lock{m_mutex};
auto& entryInfo2 = m_entryIds[entry];
if (entryInfo2.count == 0) {
return;
}
--entryInfo2.count;
if (entryInfo2.count != 0) {
return;
}
m_entryIds.erase(entry);
if (!m_active) {
[[unlikely]] return;
}
uint8_t* buf = StartRecord(0, timestamp, 5, 5);
*buf++ = impl::kControlFinish;
wpi::util::support::endian::write32le(buf, entry);
}
void DataLog::SetMetadata(int entry, std::string_view metadata,
int64_t timestamp) {
if (entry <= 0) {
return;
}
std::scoped_lock lock{m_mutex};
m_entryIds[entry].metadata = metadata;
if (!m_active) {
[[unlikely]] return;
}
uint8_t* buf = StartRecord(0, timestamp, 5 + 4 + metadata.size(), 5);
*buf++ = impl::kControlSetMetadata;
wpi::util::support::endian::write32le(buf, entry);
AppendStringImpl(metadata);
}
uint8_t* DataLog::Reserve(size_t size) {
assert(size <= kBlockSize);
if (m_outgoing.empty() || size > m_outgoing.back().GetRemaining()) {
if (m_outgoing.size() == kMaxBufferCount / 2) {
[[unlikely]] BufferHalfFull();
}
if (m_free.empty()) {
if (m_outgoing.size() >= kMaxBufferCount) {
[[unlikely]]
if (BufferFull()) {
m_paused = true;
}
}
m_outgoing.emplace_back();
} else {
m_outgoing.emplace_back(std::move(m_free.back()));
m_free.pop_back();
}
}
return m_outgoing.back().Reserve(size);
}
uint8_t* DataLog::StartRecord(uint32_t entry, uint64_t timestamp,
uint32_t payloadSize, size_t reserveSize) {
uint8_t* buf = Reserve(kRecordMaxHeaderSize + reserveSize);
auto headerLen = WriteRecordHeader(buf, entry, timestamp, payloadSize);
m_outgoing.back().Unreserve(kRecordMaxHeaderSize - headerLen);
buf += headerLen;
return buf;
}
void DataLog::AppendImpl(std::span<const uint8_t> data) {
while (data.size() > kBlockSize) {
uint8_t* buf = Reserve(kBlockSize);
std::memcpy(buf, data.data(), kBlockSize);
data = data.subspan(kBlockSize);
}
if (!data.empty()) {
uint8_t* buf = Reserve(data.size());
std::memcpy(buf, data.data(), data.size());
}
}
void DataLog::AppendStringImpl(std::string_view str) {
uint8_t* buf = Reserve(4);
wpi::util::support::endian::write32le(buf, str.size());
AppendImpl({reinterpret_cast<const uint8_t*>(str.data()), str.size()});
}
void DataLog::AppendRaw(int entry, std::span<const uint8_t> data,
int64_t timestamp) {
if (entry <= 0) {
return;
}
std::scoped_lock lock{m_mutex};
if (m_paused) {
[[unlikely]] return;
}
StartRecord(entry, timestamp, data.size(), 0);
AppendImpl(data);
}
void DataLog::AppendRaw2(int entry,
std::span<const std::span<const uint8_t>> data,
int64_t timestamp) {
if (entry <= 0) {
return;
}
std::scoped_lock lock{m_mutex};
if (m_paused) {
[[unlikely]] return;
}
size_t size = 0;
for (auto&& chunk : data) {
size += chunk.size();
}
StartRecord(entry, timestamp, size, 0);
for (auto chunk : data) {
AppendImpl(chunk);
}
}
void DataLog::AppendBoolean(int entry, bool value, int64_t timestamp) {
if (entry <= 0) {
return;
}
std::scoped_lock lock{m_mutex};
if (m_paused) {
[[unlikely]] return;
}
uint8_t* buf = StartRecord(entry, timestamp, 1, 1);
buf[0] = value ? 1 : 0;
}
void DataLog::AppendInteger(int entry, int64_t value, int64_t timestamp) {
if (entry <= 0) {
return;
}
std::scoped_lock lock{m_mutex};
if (m_paused) {
[[unlikely]] return;
}
uint8_t* buf = StartRecord(entry, timestamp, 8, 8);
wpi::util::support::endian::write64le(buf, value);
}
void DataLog::AppendFloat(int entry, float value, int64_t timestamp) {
if (entry <= 0) {
return;
}
std::scoped_lock lock{m_mutex};
if (m_paused) {
[[unlikely]] return;
}
uint8_t* buf = StartRecord(entry, timestamp, 4, 4);
if constexpr (std::endian::native == std::endian::little) {
std::memcpy(buf, &value, 4);
} else {
wpi::util::support::endian::write32le(buf, std::bit_cast<uint32_t>(value));
}
}
void DataLog::AppendDouble(int entry, double value, int64_t timestamp) {
if (entry <= 0) {
return;
}
std::scoped_lock lock{m_mutex};
if (m_paused) {
[[unlikely]] return;
}
uint8_t* buf = StartRecord(entry, timestamp, 8, 8);
if constexpr (std::endian::native == std::endian::little) {
std::memcpy(buf, &value, 8);
} else {
wpi::util::support::endian::write64le(buf, std::bit_cast<uint64_t>(value));
}
}
void DataLog::AppendString(int entry, std::string_view value,
int64_t timestamp) {
AppendRaw(entry,
{reinterpret_cast<const uint8_t*>(value.data()), value.size()},
timestamp);
}
void DataLog::AppendBooleanArray(int entry, std::span<const bool> arr,
int64_t timestamp) {
if (entry <= 0) {
return;
}
std::scoped_lock lock{m_mutex};
if (m_paused) {
[[unlikely]] return;
}
StartRecord(entry, timestamp, arr.size(), 0);
uint8_t* buf;
while (arr.size() > kBlockSize) {
buf = Reserve(kBlockSize);
for (auto val : arr.subspan(0, kBlockSize)) {
*buf++ = val ? 1 : 0;
}
arr = arr.subspan(kBlockSize);
}
buf = Reserve(arr.size());
for (auto val : arr) {
*buf++ = val ? 1 : 0;
}
}
void DataLog::AppendBooleanArray(int entry, std::span<const int> arr,
int64_t timestamp) {
if (entry <= 0) {
return;
}
std::scoped_lock lock{m_mutex};
if (m_paused) {
[[unlikely]] return;
}
StartRecord(entry, timestamp, arr.size(), 0);
uint8_t* buf;
while (arr.size() > kBlockSize) {
buf = Reserve(kBlockSize);
for (auto val : arr.subspan(0, kBlockSize)) {
*buf++ = val & 1;
}
arr = arr.subspan(kBlockSize);
}
buf = Reserve(arr.size());
for (auto val : arr) {
*buf++ = val & 1;
}
}
void DataLog::AppendBooleanArray(int entry, std::span<const uint8_t> arr,
int64_t timestamp) {
AppendRaw(entry, arr, timestamp);
}
void DataLog::AppendIntegerArray(int entry, std::span<const int64_t> arr,
int64_t timestamp) {
if constexpr (std::endian::native == std::endian::little) {
AppendRaw(entry,
{reinterpret_cast<const uint8_t*>(arr.data()), arr.size() * 8},
timestamp);
} else {
if (entry <= 0) {
return;
}
std::scoped_lock lock{m_mutex};
if (m_paused) {
[[unlikely]] return;
}
StartRecord(entry, timestamp, arr.size() * 8, 0);
uint8_t* buf;
while ((arr.size() * 8) > kBlockSize) {
buf = Reserve(kBlockSize);
for (auto val : arr.subspan(0, kBlockSize / 8)) {
wpi::util::support::endian::write64le(buf, val);
buf += 8;
}
arr = arr.subspan(kBlockSize / 8);
}
buf = Reserve(arr.size() * 8);
for (auto val : arr) {
wpi::util::support::endian::write64le(buf, val);
buf += 8;
}
}
}
void DataLog::AppendFloatArray(int entry, std::span<const float> arr,
int64_t timestamp) {
if constexpr (std::endian::native == std::endian::little) {
AppendRaw(entry,
{reinterpret_cast<const uint8_t*>(arr.data()), arr.size() * 4},
timestamp);
} else {
if (entry <= 0) {
return;
}
std::scoped_lock lock{m_mutex};
if (m_paused) {
[[unlikely]] return;
}
StartRecord(entry, timestamp, arr.size() * 4, 0);
uint8_t* buf;
while ((arr.size() * 4) > kBlockSize) {
buf = Reserve(kBlockSize);
for (auto val : arr.subspan(0, kBlockSize / 4)) {
wpi::util::support::endian::write32le(buf,
std::bit_cast<uint32_t>(val));
buf += 4;
}
arr = arr.subspan(kBlockSize / 4);
}
buf = Reserve(arr.size() * 4);
for (auto val : arr) {
wpi::util::support::endian::write32le(buf, std::bit_cast<uint32_t>(val));
buf += 4;
}
}
}
void DataLog::AppendDoubleArray(int entry, std::span<const double> arr,
int64_t timestamp) {
if constexpr (std::endian::native == std::endian::little) {
AppendRaw(entry,
{reinterpret_cast<const uint8_t*>(arr.data()), arr.size() * 8},
timestamp);
} else {
if (entry <= 0) {
return;
}
std::scoped_lock lock{m_mutex};
if (m_paused) {
[[unlikely]] return;
}
StartRecord(entry, timestamp, arr.size() * 8, 0);
uint8_t* buf;
while ((arr.size() * 8) > kBlockSize) {
buf = Reserve(kBlockSize);
for (auto val : arr.subspan(0, kBlockSize / 8)) {
wpi::util::support::endian::write64le(buf,
std::bit_cast<uint64_t>(val));
buf += 8;
}
arr = arr.subspan(kBlockSize / 8);
}
buf = Reserve(arr.size() * 8);
for (auto val : arr) {
wpi::util::support::endian::write64le(buf, std::bit_cast<uint64_t>(val));
buf += 8;
}
}
}
void DataLog::AppendStringArray(int entry, std::span<const std::string> arr,
int64_t timestamp) {
if (entry <= 0) {
return;
}
// storage: 4-byte array length, each string prefixed by 4-byte length
// calculate total size
size_t size = 4;
for (auto&& str : arr) {
size += 4 + str.size();
}
std::scoped_lock lock{m_mutex};
if (m_paused) {
[[unlikely]] return;
}
uint8_t* buf = StartRecord(entry, timestamp, size, 4);
wpi::util::support::endian::write32le(buf, arr.size());
for (auto&& str : arr) {
AppendStringImpl(str);
}
}
void DataLog::AppendStringArray(int entry,
std::span<const std::string_view> arr,
int64_t timestamp) {
if (entry <= 0) {
return;
}
// storage: 4-byte array length, each string prefixed by 4-byte length
// calculate total size
size_t size = 4;
for (auto&& str : arr) {
size += 4 + str.size();
}
std::scoped_lock lock{m_mutex};
if (m_paused) {
[[unlikely]] return;
}
uint8_t* buf = StartRecord(entry, timestamp, size, 4);
wpi::util::support::endian::write32le(buf, arr.size());
for (auto&& sv : arr) {
AppendStringImpl(sv);
}
}
void DataLog::AppendStringArray(int entry,
std::span<const struct WPI_String> arr,
int64_t timestamp) {
if (entry <= 0) {
return;
}
// storage: 4-byte array length, each string prefixed by 4-byte length
// calculate total size
size_t size = 4;
for (auto&& str : arr) {
size += 4 + str.len;
}
std::scoped_lock lock{m_mutex};
if (m_paused) {
[[unlikely]] return;
}
uint8_t* buf = StartRecord(entry, timestamp, size, 4);
wpi::util::support::endian::write32le(buf, arr.size());
for (auto&& sv : arr) {
AppendStringImpl(sv.str);
}
}
template <typename V1, typename V2>
inline bool UpdateImpl(std::optional<std::vector<V1>>& lastValue,
std::span<const V2> data) {
if (!lastValue || !std::equal(data.begin(), data.end(), lastValue->begin(),
lastValue->end())) {
if (lastValue) {
lastValue->assign(data.begin(), data.end());
} else {
lastValue = std::vector<V1>{data.begin(), data.end()};
}
return true;
}
return false;
}
template <typename V1>
inline bool UpdateImpl(std::optional<std::vector<V1>>& lastValue,
std::span<const bool> data) {
if (!lastValue || !std::equal(data.begin(), data.end(), lastValue->begin(),
lastValue->end(), [](auto a, auto b) {
return a == static_cast<bool>(b);
})) {
if (lastValue) {
lastValue->assign(data.begin(), data.end());
} else {
lastValue = std::vector<V1>{data.begin(), data.end()};
}
return true;
}
return false;
}
void RawLogEntry::Update(std::span<const uint8_t> data, int64_t timestamp) {
std::scoped_lock lock{m_mutex};
if (UpdateImpl(m_lastValue, data)) {
Append(data, timestamp);
}
}
void BooleanArrayLogEntry::Update(std::span<const bool> arr,
int64_t timestamp) {
std::scoped_lock lock{m_mutex};
if (UpdateImpl(m_lastValue, arr)) {
Append(arr, timestamp);
}
}
void BooleanArrayLogEntry::Update(std::span<const int> arr, int64_t timestamp) {
std::scoped_lock lock{m_mutex};
if (UpdateImpl(m_lastValue, arr)) {
Append(arr, timestamp);
}
}
void BooleanArrayLogEntry::Update(std::span<const uint8_t> arr,
int64_t timestamp) {
std::scoped_lock lock{m_mutex};
if (UpdateImpl(m_lastValue, arr)) {
Append(arr, timestamp);
}
}
void IntegerArrayLogEntry::Update(std::span<const int64_t> arr,
int64_t timestamp) {
std::scoped_lock lock{m_mutex};
if (UpdateImpl(m_lastValue, arr)) {
Append(arr, timestamp);
}
}
void FloatArrayLogEntry::Update(std::span<const float> arr, int64_t timestamp) {
std::scoped_lock lock{m_mutex};
if (UpdateImpl(m_lastValue, arr)) {
Append(arr, timestamp);
}
}
void DoubleArrayLogEntry::Update(std::span<const double> arr,
int64_t timestamp) {
std::scoped_lock lock{m_mutex};
if (UpdateImpl(m_lastValue, arr)) {
Append(arr, timestamp);
}
}
void StringArrayLogEntry::Update(std::span<const std::string> arr,
int64_t timestamp) {
std::scoped_lock lock{m_mutex};
if (UpdateImpl(m_lastValue, arr)) {
Append(arr, timestamp);
}
}
void StringArrayLogEntry::Update(std::span<const std::string_view> arr,
int64_t timestamp) {
std::scoped_lock lock{m_mutex};
if (UpdateImpl(m_lastValue, arr)) {
Append(arr, timestamp);
}
}
extern "C" {
void WPI_DataLog_Release(struct WPI_DataLog* datalog) {
delete reinterpret_cast<DataLog*>(datalog);
}
void WPI_DataLog_Flush(struct WPI_DataLog* datalog) {
reinterpret_cast<DataLog*>(datalog)->Flush();
}
void WPI_DataLog_Pause(struct WPI_DataLog* datalog) {
reinterpret_cast<DataLog*>(datalog)->Pause();
}
void WPI_DataLog_Resume(struct WPI_DataLog* datalog) {
reinterpret_cast<DataLog*>(datalog)->Resume();
}
void WPI_DataLog_Stop(struct WPI_DataLog* datalog) {
reinterpret_cast<DataLog*>(datalog)->Stop();
}
int WPI_DataLog_Start(struct WPI_DataLog* datalog,
const struct WPI_String* name,
const struct WPI_String* type,
const struct WPI_String* metadata, int64_t timestamp) {
return reinterpret_cast<DataLog*>(datalog)->Start(
wpi::util::to_string_view(name), wpi::util::to_string_view(type),
wpi::util::to_string_view(metadata), timestamp);
}
void WPI_DataLog_Finish(struct WPI_DataLog* datalog, int entry,
int64_t timestamp) {
reinterpret_cast<DataLog*>(datalog)->Finish(entry, timestamp);
}
void WPI_DataLog_SetMetadata(struct WPI_DataLog* datalog, int entry,
const struct WPI_String* metadata,
int64_t timestamp) {
reinterpret_cast<DataLog*>(datalog)->SetMetadata(
entry, wpi::util::to_string_view(metadata), timestamp);
}
void WPI_DataLog_AppendRaw(struct WPI_DataLog* datalog, int entry,
const uint8_t* data, size_t len, int64_t timestamp) {
reinterpret_cast<DataLog*>(datalog)->AppendRaw(entry, {data, len}, timestamp);
}
void WPI_DataLog_AppendBoolean(struct WPI_DataLog* datalog, int entry,
int value, int64_t timestamp) {
reinterpret_cast<DataLog*>(datalog)->AppendBoolean(entry, value, timestamp);
}
void WPI_DataLog_AppendInteger(struct WPI_DataLog* datalog, int entry,
int64_t value, int64_t timestamp) {
reinterpret_cast<DataLog*>(datalog)->AppendInteger(entry, value, timestamp);
}
void WPI_DataLog_AppendFloat(struct WPI_DataLog* datalog, int entry,
float value, int64_t timestamp) {
reinterpret_cast<DataLog*>(datalog)->AppendFloat(entry, value, timestamp);
}
void WPI_DataLog_AppendDouble(struct WPI_DataLog* datalog, int entry,
double value, int64_t timestamp) {
reinterpret_cast<DataLog*>(datalog)->AppendDouble(entry, value, timestamp);
}
void WPI_DataLog_AppendString(struct WPI_DataLog* datalog, int entry,
const struct WPI_String* value,
int64_t timestamp) {
reinterpret_cast<DataLog*>(datalog)->AppendString(
entry, {value->str, value->len}, timestamp);
}
void WPI_DataLog_AppendBooleanArray(struct WPI_DataLog* datalog, int entry,
const int* arr, size_t len,
int64_t timestamp) {
reinterpret_cast<DataLog*>(datalog)->AppendBooleanArray(entry, {arr, len},
timestamp);
}
void WPI_DataLog_AppendBooleanArrayByte(struct WPI_DataLog* datalog, int entry,
const uint8_t* arr, size_t len,
int64_t timestamp) {
reinterpret_cast<DataLog*>(datalog)->AppendBooleanArray(entry, {arr, len},
timestamp);
}
void WPI_DataLog_AppendIntegerArray(struct WPI_DataLog* datalog, int entry,
const int64_t* arr, size_t len,
int64_t timestamp) {
reinterpret_cast<DataLog*>(datalog)->AppendIntegerArray(entry, {arr, len},
timestamp);
}
void WPI_DataLog_AppendFloatArray(struct WPI_DataLog* datalog, int entry,
const float* arr, size_t len,
int64_t timestamp) {
reinterpret_cast<DataLog*>(datalog)->AppendFloatArray(entry, {arr, len},
timestamp);
}
void WPI_DataLog_AppendDoubleArray(struct WPI_DataLog* datalog, int entry,
const double* arr, size_t len,
int64_t timestamp) {
reinterpret_cast<DataLog*>(datalog)->AppendDoubleArray(entry, {arr, len},
timestamp);
}
void WPI_DataLog_AppendStringArray(struct WPI_DataLog* datalog, int entry,
const struct WPI_String* arr, size_t len,
int64_t timestamp) {
reinterpret_cast<DataLog*>(datalog)->AppendStringArray(entry, {arr, len},
timestamp);
}
void WPI_DataLog_AddSchemaString(struct WPI_DataLog* datalog,
const struct WPI_String* name,
const struct WPI_String* type,
const struct WPI_String* schema,
int64_t timestamp) {
reinterpret_cast<DataLog*>(datalog)->AddSchema(
wpi::util::to_string_view(name), wpi::util::to_string_view(type),
wpi::util::to_string_view(schema), timestamp);
}
void WPI_DataLog_AddSchema(struct WPI_DataLog* datalog,
const struct WPI_String* name,
const struct WPI_String* type, const uint8_t* schema,
size_t schema_len, int64_t timestamp) {
reinterpret_cast<DataLog*>(datalog)->AddSchema(
wpi::util::to_string_view(name), wpi::util::to_string_view(type),
std::span<const uint8_t>{schema, schema_len}, timestamp);
}
} // extern "C"