mirror of
https://github.com/slsdetectorgroup/aare.git
synced 2025-06-08 21:40:43 +02:00
read write cluster file (#60)
* Read and write cluster files (save work) * add reading test * use define for examples env variable and fix ci * read and write cluster files (working) * fix cluster CI
This commit is contained in:
parent
9dfd388927
commit
28d7e8c07a
14
.clang-tidy
14
.clang-tidy
@ -11,6 +11,15 @@ Checks: '
|
|||||||
# - cppcoreguidelines-owning-memory
|
# - cppcoreguidelines-owning-memory
|
||||||
# - bugprone-easily-swappable-parameters
|
# - bugprone-easily-swappable-parameters
|
||||||
# - cppcoreguidelines-non-private-member-variables-in-classes
|
# - cppcoreguidelines-non-private-member-variables-in-classes
|
||||||
|
|
||||||
|
# cppcoreguidelines-special-member-functions
|
||||||
|
# hicpp-special-member-functions
|
||||||
|
|
||||||
|
# cert-err33-c # too much with c file api
|
||||||
|
|
||||||
|
# modernize-pass-by-value # contradicts with other checks
|
||||||
|
|
||||||
|
|
||||||
# abseil-cleanup-ctad
|
# abseil-cleanup-ctad
|
||||||
# abseil-duration-addition
|
# abseil-duration-addition
|
||||||
# abseil-duration-comparison
|
# abseil-duration-comparison
|
||||||
@ -31,8 +40,7 @@ Checks: '
|
|||||||
# abseil-time-subtraction
|
# abseil-time-subtraction
|
||||||
# abseil-upgrade-duration-conversions
|
# abseil-upgrade-duration-conversions
|
||||||
android-cloexec-accept
|
android-cloexec-accept
|
||||||
cppcoreguidelines-special-member-functions
|
|
||||||
hicpp-special-member-functions
|
|
||||||
android-cloexec-accept4
|
android-cloexec-accept4
|
||||||
android-cloexec-creat
|
android-cloexec-creat
|
||||||
android-cloexec-dup
|
android-cloexec-dup
|
||||||
@ -131,7 +139,6 @@ Checks: '
|
|||||||
cert-dcl59-cpp
|
cert-dcl59-cpp
|
||||||
cert-env33-c
|
cert-env33-c
|
||||||
cert-err09-cpp
|
cert-err09-cpp
|
||||||
cert-err33-c
|
|
||||||
cert-err34-c
|
cert-err34-c
|
||||||
cert-err52-cpp
|
cert-err52-cpp
|
||||||
cert-err58-cpp
|
cert-err58-cpp
|
||||||
@ -384,7 +391,6 @@ Checks: '
|
|||||||
modernize-macro-to-enum
|
modernize-macro-to-enum
|
||||||
modernize-make-shared
|
modernize-make-shared
|
||||||
modernize-make-unique
|
modernize-make-unique
|
||||||
modernize-pass-by-value
|
|
||||||
modernize-raw-string-literal
|
modernize-raw-string-literal
|
||||||
modernize-redundant-void-arg
|
modernize-redundant-void-arg
|
||||||
modernize-replace-auto-ptr
|
modernize-replace-auto-ptr
|
||||||
|
2
.github/workflows/common-workflow.yml
vendored
2
.github/workflows/common-workflow.yml
vendored
@ -43,7 +43,7 @@ jobs:
|
|||||||
# find all examples in build/examples and run them
|
# find all examples in build/examples and run them
|
||||||
run: |
|
run: |
|
||||||
pwd
|
pwd
|
||||||
export PROJECT_ROOT_DIR="."
|
export AARE_ROOT_DIR="$PWD"
|
||||||
ls build/examples/*_example
|
ls build/examples/*_example
|
||||||
find build/examples -name "*_example" -not -name "zmq_*" | xargs -I {} -n 1 -t bash -c {}
|
find build/examples -name "*_example" -not -name "zmq_*" | xargs -I {} -n 1 -t bash -c {}
|
||||||
|
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
|
|
||||||
cmake_minimum_required(VERSION 3.12)
|
cmake_minimum_required(VERSION 3.12)
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 17) #TODO! Global or per target?
|
set(CMAKE_CXX_STANDARD 17) #TODO! Global or per target?
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||||
|
|
||||||
project(aare
|
project(aare
|
||||||
VERSION 0.1
|
VERSION 0.1
|
||||||
DESCRIPTION "Data processing library for PSI detectors"
|
DESCRIPTION "Data processing library for PSI detectors"
|
||||||
@ -206,10 +211,9 @@ add_custom_target(
|
|||||||
COMMENT "Formatting with clang-format"
|
COMMENT "Formatting with clang-format"
|
||||||
VERBATIM
|
VERBATIM
|
||||||
)
|
)
|
||||||
|
|
||||||
add_custom_target(
|
add_custom_target(
|
||||||
clang-tidy
|
clang-tidy
|
||||||
COMMAND find \( -path "./core/*" -o -path "./file_io/*" -path "./network_io/*" -path "./utils/*" \) \( -name "*.cpp" -o -name "*.hpp" \) -not -path "./python/*" -not -name "*.test.cpp" -not -name "CircularFifo.hpp" -not -name "ProducerConsumerQueue.hpp" -not -name "VariableSizeClusterFinder.hpp" | xargs -I {} -n 1 -P 10 bash -c "clang-tidy --config-file=.clang-tidy -p build {}"
|
COMMAND find \( -path "./core/*" -o -path "./file_io/*" -o -path "./network_io/*" -o -path "./utils/*" \) \( -name "*.cpp" \) -not -path "./python/*" -not -name "*.test.cpp" -not -name "CircularFifo.hpp" -not -name "ProducerConsumerQueue.hpp" -not -name "VariableSizeClusterFinder.hpp" | xargs -I {} -n 1 -P 10 bash -c "clang-tidy --config-file=.clang-tidy -p build {}"
|
||||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
COMMENT "linting with clang-tidy"
|
COMMENT "linting with clang-tidy"
|
||||||
VERBATIM
|
VERBATIM
|
||||||
|
@ -11,6 +11,20 @@
|
|||||||
|
|
||||||
namespace aare {
|
namespace aare {
|
||||||
|
|
||||||
|
struct Cluster {
|
||||||
|
int16_t x;
|
||||||
|
int16_t y;
|
||||||
|
std::array<int32_t, 9> data;
|
||||||
|
std::string to_string() const {
|
||||||
|
std::string s = "x: " + std::to_string(x) + " y: " + std::to_string(y) + "\ndata: [";
|
||||||
|
for (auto d : data) {
|
||||||
|
s += std::to_string(d) + " ";
|
||||||
|
}
|
||||||
|
s += "]";
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief header contained in parts of frames
|
* @brief header contained in parts of frames
|
||||||
*/
|
*/
|
||||||
|
BIN
data/clusters/beam_En700eV_-40deg_300V_10us_d0_f0_100.clust
Executable file
BIN
data/clusters/beam_En700eV_-40deg_300V_10us_d0_f0_100.clust
Executable file
Binary file not shown.
BIN
data/clusters/single_frame_97_clustrers.clust
Executable file
BIN
data/clusters/single_frame_97_clustrers.clust
Executable file
Binary file not shown.
BIN
data/clusters/test_writing.clust
Normal file
BIN
data/clusters/test_writing.clust
Normal file
Binary file not shown.
@ -1,8 +1,10 @@
|
|||||||
|
|
||||||
set(EXAMPLE_LIST "json_example;logger_example;numpy_read_example;multiport_example;raw_example;zmq_restream_example")
|
set(EXAMPLE_LIST "json_example;logger_example;numpy_read_example;multiport_example;raw_example;zmq_restream_example")
|
||||||
set(EXAMPLE_LIST "${EXAMPLE_LIST};mythen_example;numpy_write_example;zmq_receiver_example;zmq_sender_example;")
|
set(EXAMPLE_LIST "${EXAMPLE_LIST};mythen_example;numpy_write_example;zmq_receiver_example;zmq_sender_example;")
|
||||||
|
set(EXAMPLE_LIST "${EXAMPLE_LIST};cluster_example")
|
||||||
foreach(example ${EXAMPLE_LIST})
|
foreach(example ${EXAMPLE_LIST})
|
||||||
add_executable(${example} ${example}.cpp)
|
add_executable(${example} ${example}.cpp)
|
||||||
|
target_include_directories(${example} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||||
target_link_libraries(${example} PUBLIC aare PRIVATE aare_compiler_flags)
|
target_link_libraries(${example} PUBLIC aare PRIVATE aare_compiler_flags)
|
||||||
endforeach()
|
endforeach()
|
||||||
|
|
||||||
|
53
examples/cluster_example.cpp
Normal file
53
examples/cluster_example.cpp
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
#include "aare/core/defs.hpp"
|
||||||
|
#include "aare/file_io/ClusterFile.hpp"
|
||||||
|
#include <cassert>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
using namespace aare;
|
||||||
|
int main() {
|
||||||
|
auto PROJECT_ROOT_DIR = std::filesystem::path(getenv("AARE_ROOT_DIR"));
|
||||||
|
std::filesystem::path const fpath(PROJECT_ROOT_DIR / "data" / "clusters" / "single_frame_97_clustrers.clust");
|
||||||
|
|
||||||
|
// reading a file
|
||||||
|
aare::ClusterFile cf(fpath, "r");
|
||||||
|
std::cout << "file opened " << '\n';
|
||||||
|
std::cout << "n_clusters " << cf.count() << '\n';
|
||||||
|
std::cout << "frame_number " << cf.frame() << '\n';
|
||||||
|
std::cout << "file size: " << cf.count() << '\n';
|
||||||
|
cf.seek(0); // seek to the beginning of the file (this is the default behavior of the constructor)
|
||||||
|
|
||||||
|
auto cluster = cf.read(97);
|
||||||
|
|
||||||
|
std::cout << "read 10 clusters" << '\n';
|
||||||
|
int offset = 0;
|
||||||
|
int data_offset = 0;
|
||||||
|
for (auto c : cluster) {
|
||||||
|
assert(c.y == offset + 200);
|
||||||
|
for (int i = 0; i < 9; i++) {
|
||||||
|
assert(c.data[i] == data_offset + i);
|
||||||
|
}
|
||||||
|
|
||||||
|
offset++;
|
||||||
|
data_offset += 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
// writing a file
|
||||||
|
std::filesystem::path const fpath_out("/tmp/cluster_example_file.clust");
|
||||||
|
aare::ClusterFile cf_out(fpath_out, "w", ClusterFileConfig(1, 44));
|
||||||
|
std::cout << "file opened for writing" << '\n';
|
||||||
|
std::vector<Cluster> clusters;
|
||||||
|
for (int i = 0; i < 1084; i++) {
|
||||||
|
Cluster c;
|
||||||
|
c.x = i;
|
||||||
|
c.y = i + 200;
|
||||||
|
for (int j = 0; j < 9; j++) {
|
||||||
|
c.data[j] = j;
|
||||||
|
}
|
||||||
|
clusters.push_back(c);
|
||||||
|
}
|
||||||
|
cf_out.write(clusters);
|
||||||
|
std::cout << "wrote 10 clusters" << '\n';
|
||||||
|
cf_out.update_header();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
2
examples/include/aare/examples/defs.hpp
Normal file
2
examples/include/aare/examples/defs.hpp
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
#pragma once
|
||||||
|
#define AARE_ROOT_DIR "AARE_ROOT_DIR"
|
@ -1,10 +1,9 @@
|
|||||||
// Your First C++ Program
|
// Your First C++ Program
|
||||||
|
#include "aare/examples/defs.hpp"
|
||||||
#include "aare/file_io/File.hpp"
|
#include "aare/file_io/File.hpp"
|
||||||
#include "aare/utils/logger.hpp"
|
#include "aare/utils/logger.hpp"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
#define AARE_ROOT_DIR_VAR "PROJECT_ROOT_DIR"
|
|
||||||
|
|
||||||
using aare::File;
|
using aare::File;
|
||||||
using aare::Frame;
|
using aare::Frame;
|
||||||
|
|
||||||
@ -18,7 +17,7 @@ void test(File &f, int frame_number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
auto PROJECT_ROOT_DIR = std::filesystem::path(getenv(AARE_ROOT_DIR_VAR));
|
auto PROJECT_ROOT_DIR = std::filesystem::path(getenv(AARE_ROOT_DIR));
|
||||||
std::filesystem::path const fpath(PROJECT_ROOT_DIR / "data" / "jungfrau" / "jungfrau_single_master_0.json");
|
std::filesystem::path const fpath(PROJECT_ROOT_DIR / "data" / "jungfrau" / "jungfrau_single_master_0.json");
|
||||||
std::cout << fpath << '\n';
|
std::cout << fpath << '\n';
|
||||||
|
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
|
#include "aare/examples/defs.hpp"
|
||||||
#include "aare/utils/logger.hpp"
|
#include "aare/utils/logger.hpp"
|
||||||
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
@ -11,7 +13,7 @@ int main() {
|
|||||||
|
|
||||||
// writing to file
|
// writing to file
|
||||||
std::ofstream textfile;
|
std::ofstream textfile;
|
||||||
textfile.open("Test.txt");
|
textfile.open("/tmp/Test.txt");
|
||||||
aare::logger::set_streams(textfile.rdbuf());
|
aare::logger::set_streams(textfile.rdbuf());
|
||||||
aare::logger::info(LOCATION, "info printed to file");
|
aare::logger::info(LOCATION, "info printed to file");
|
||||||
|
|
||||||
@ -26,7 +28,7 @@ int main() {
|
|||||||
|
|
||||||
// setting file output by path
|
// setting file output by path
|
||||||
// user doesn't have to close file
|
// user doesn't have to close file
|
||||||
aare::logger::set_output_file("Test2.txt");
|
aare::logger::set_output_file("/tmp/Test2.txt");
|
||||||
aare::logger::info(LOCATION, "info printed to Test2.txt");
|
aare::logger::info(LOCATION, "info printed to Test2.txt");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
@ -1,9 +1,9 @@
|
|||||||
// Your First C++ Program
|
// Your First C++ Program
|
||||||
|
#include "aare/examples/defs.hpp"
|
||||||
#include "aare/file_io/File.hpp"
|
#include "aare/file_io/File.hpp"
|
||||||
#include "aare/utils/logger.hpp"
|
#include "aare/utils/logger.hpp"
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
#define AARE_ROOT_DIR_VAR "PROJECT_ROOT_DIR"
|
#include <iostream>
|
||||||
|
|
||||||
using aare::File;
|
using aare::File;
|
||||||
using aare::Frame;
|
using aare::Frame;
|
||||||
@ -19,7 +19,7 @@ void test(File &f, int frame_number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
auto PROJECT_ROOT_DIR = std::filesystem::path(getenv(AARE_ROOT_DIR_VAR));
|
auto PROJECT_ROOT_DIR = std::filesystem::path(getenv(AARE_ROOT_DIR));
|
||||||
std::filesystem::path const fpath(PROJECT_ROOT_DIR / "data" / "jungfrau" / "jungfrau_double_master_0.json");
|
std::filesystem::path const fpath(PROJECT_ROOT_DIR / "data" / "jungfrau" / "jungfrau_double_master_0.json");
|
||||||
std::cout << fpath << '\n';
|
std::cout << fpath << '\n';
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
// Your First C++ Program
|
// Your First C++ Program
|
||||||
|
#include "aare/examples/defs.hpp"
|
||||||
#include "aare/file_io/File.hpp"
|
#include "aare/file_io/File.hpp"
|
||||||
#include "aare/utils/logger.hpp"
|
#include "aare/utils/logger.hpp"
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
#define AARE_ROOT_DIR_VAR "PROJECT_ROOT_DIR"
|
#include <iostream>
|
||||||
|
|
||||||
using aare::File;
|
using aare::File;
|
||||||
using aare::Frame;
|
using aare::Frame;
|
||||||
@ -32,7 +32,7 @@ void test2(File &f, int frame_number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
auto PROJECT_ROOT_DIR = std::filesystem::path(getenv(AARE_ROOT_DIR_VAR));
|
auto PROJECT_ROOT_DIR = std::filesystem::path(getenv(AARE_ROOT_DIR));
|
||||||
if (PROJECT_ROOT_DIR.empty()) {
|
if (PROJECT_ROOT_DIR.empty()) {
|
||||||
throw std::runtime_error("environment variable PROJECT_ROOT_DIR is not set");
|
throw std::runtime_error("environment variable PROJECT_ROOT_DIR is not set");
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
// Your First C++ Program
|
// Your First C++ Program
|
||||||
|
#include "aare/examples/defs.hpp"
|
||||||
#include "aare/file_io/File.hpp"
|
#include "aare/file_io/File.hpp"
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
#define AARE_ROOT_DIR_VAR "PROJECT_ROOT_DIR"
|
#include <iostream>
|
||||||
|
|
||||||
using aare::File;
|
using aare::File;
|
||||||
using aare::Frame;
|
using aare::Frame;
|
||||||
@ -18,7 +18,7 @@ void test(File &f, int frame_number) {
|
|||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
|
|
||||||
auto PROJECT_ROOT_DIR = std::filesystem::path(getenv(AARE_ROOT_DIR_VAR));
|
auto PROJECT_ROOT_DIR = std::filesystem::path(getenv(AARE_ROOT_DIR));
|
||||||
std::filesystem::path const fpath(PROJECT_ROOT_DIR / "data" / "numpy" / "test_numpy_file.npy");
|
std::filesystem::path const fpath(PROJECT_ROOT_DIR / "data" / "numpy" / "test_numpy_file.npy");
|
||||||
std::cout << fpath << '\n';
|
std::cout << fpath << '\n';
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
// Your First C++ Program
|
// Your First C++ Program
|
||||||
#include "aare/core/Frame.hpp"
|
#include "aare/core/Frame.hpp"
|
||||||
|
#include "aare/examples/defs.hpp"
|
||||||
#include "aare/file_io/File.hpp"
|
#include "aare/file_io/File.hpp"
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
#define AARE_ROOT_DIR_VAR "PROJECT_ROOT_DIR"
|
#include <iostream>
|
||||||
|
|
||||||
using aare::File;
|
using aare::File;
|
||||||
using aare::FileConfig;
|
using aare::FileConfig;
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
// Your First C++ Program
|
// Your First C++ Program
|
||||||
|
#include "aare/examples/defs.hpp"
|
||||||
#include "aare/file_io/File.hpp"
|
#include "aare/file_io/File.hpp"
|
||||||
#include "aare/utils/logger.hpp"
|
#include "aare/utils/logger.hpp"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
#define AARE_ROOT_DIR_VAR "PROJECT_ROOT_DIR"
|
|
||||||
|
|
||||||
using aare::File;
|
using aare::File;
|
||||||
using aare::Frame;
|
using aare::Frame;
|
||||||
|
|
||||||
@ -17,7 +16,7 @@ void test(File &f, int frame_number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
auto PROJECT_ROOT_DIR = std::filesystem::path(getenv(AARE_ROOT_DIR_VAR));
|
auto PROJECT_ROOT_DIR = std::filesystem::path(getenv(AARE_ROOT_DIR));
|
||||||
if (PROJECT_ROOT_DIR.empty()) {
|
if (PROJECT_ROOT_DIR.empty()) {
|
||||||
throw std::runtime_error("environment variable PROJECT_ROOT_DIR is not set");
|
throw std::runtime_error("environment variable PROJECT_ROOT_DIR is not set");
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
#include "aare/examples/defs.hpp"
|
||||||
#include "aare/network_io/ZmqSocketReceiver.hpp"
|
#include "aare/network_io/ZmqSocketReceiver.hpp"
|
||||||
#include "aare/network_io/defs.hpp"
|
#include "aare/network_io/defs.hpp"
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
#include "aare/examples/defs.hpp"
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
#include "aare/core/Frame.hpp"
|
#include "aare/core/Frame.hpp"
|
||||||
|
#include "aare/examples/defs.hpp"
|
||||||
#include "aare/network_io/ZmqHeader.hpp"
|
#include "aare/network_io/ZmqHeader.hpp"
|
||||||
#include "aare/network_io/ZmqSocketSender.hpp"
|
#include "aare/network_io/ZmqSocketSender.hpp"
|
||||||
#include "aare/network_io/defs.hpp"
|
#include "aare/network_io/defs.hpp"
|
||||||
|
@ -15,6 +15,7 @@ set(SourceFiles
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/SubFile.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/SubFile.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/NumpyFile.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/NumpyFile.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/NumpyHelpers.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/NumpyHelpers.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/ClusterFile.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
add_library(file_io STATIC ${SourceFiles})
|
add_library(file_io STATIC ${SourceFiles})
|
||||||
@ -31,6 +32,7 @@ if(AARE_TESTS)
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/test/NumpyFile.test.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/test/NumpyFile.test.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/test/NumpyHelpers.test.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/test/NumpyHelpers.test.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/test/RawFile.test.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/test/RawFile.test.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/test/ClusterFile.test.cpp
|
||||||
)
|
)
|
||||||
target_sources(tests PRIVATE ${TestSources} )
|
target_sources(tests PRIVATE ${TestSources} )
|
||||||
target_link_libraries(tests PRIVATE core file_io)
|
target_link_libraries(tests PRIVATE core file_io)
|
||||||
|
70
file_io/include/aare/file_io/ClusterFile.hpp
Normal file
70
file_io/include/aare/file_io/ClusterFile.hpp
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "aare/core/defs.hpp"
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* cluster file format:
|
||||||
|
* header: [int32 frame_number][int32 n_clusters]
|
||||||
|
* data: [clusters....]
|
||||||
|
* where each cluster is of the form:
|
||||||
|
* typedef struct {
|
||||||
|
* int16_t x;
|
||||||
|
* int16_t y;
|
||||||
|
* int32_t data[9];
|
||||||
|
*} Cluster ;
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace aare {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Configuration of the ClusterFile
|
||||||
|
* can be use as the header of the cluster file
|
||||||
|
*/
|
||||||
|
struct ClusterFileConfig {
|
||||||
|
int32_t frame_number;
|
||||||
|
int32_t n_clusters;
|
||||||
|
ClusterFileConfig(int32_t frame_number_, int32_t n_clusters_)
|
||||||
|
: frame_number(frame_number_), n_clusters(n_clusters_) {}
|
||||||
|
ClusterFileConfig() : frame_number(0), n_clusters(0) {}
|
||||||
|
bool operator==(const ClusterFileConfig &other) const {
|
||||||
|
return frame_number == other.frame_number && n_clusters == other.n_clusters;
|
||||||
|
}
|
||||||
|
bool operator!=(const ClusterFileConfig &other) const { return !(*this == other); }
|
||||||
|
std::string to_string() const {
|
||||||
|
return "frame_number: " + std::to_string(frame_number) + " n_clusters: " + std::to_string(n_clusters) + "\n";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Class to read and write clusters to a file
|
||||||
|
*/
|
||||||
|
class ClusterFile {
|
||||||
|
|
||||||
|
public:
|
||||||
|
ClusterFile(const std::filesystem::path &fname, const std::string &mode, ClusterFileConfig config = {});
|
||||||
|
void write(std::vector<Cluster> &clusters);
|
||||||
|
void write(Cluster &cluster);
|
||||||
|
Cluster read();
|
||||||
|
Cluster iread(size_t cluster_number);
|
||||||
|
std::vector<Cluster> read(size_t n_clusters);
|
||||||
|
void seek(size_t cluster_number);
|
||||||
|
size_t tell() const;
|
||||||
|
size_t count() noexcept;
|
||||||
|
int32_t frame() const;
|
||||||
|
void update_header() /* throws */;
|
||||||
|
~ClusterFile() noexcept;
|
||||||
|
|
||||||
|
private:
|
||||||
|
FILE *fp = nullptr;
|
||||||
|
// size_t current_cluster{};
|
||||||
|
std::filesystem::path fname{};
|
||||||
|
std::string mode{};
|
||||||
|
int32_t frame_number{};
|
||||||
|
int32_t n_clusters{};
|
||||||
|
static const int HEADER_BYTES = 8;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace aare
|
@ -24,7 +24,7 @@ class RawFile : public FileInterface {
|
|||||||
* @brief write function is not implemented for RawFile
|
* @brief write function is not implemented for RawFile
|
||||||
* @param frame frame to write
|
* @param frame frame to write
|
||||||
*/
|
*/
|
||||||
void write(Frame & /*frame*/) override { throw std::runtime_error("Not implemented"); };
|
void write([[maybe_unused]] Frame &frame) override { throw std::runtime_error("Not implemented"); };
|
||||||
Frame read() override { return get_frame(this->current_frame++); };
|
Frame read() override { return get_frame(this->current_frame++); };
|
||||||
std::vector<Frame> read(size_t n_frames) override;
|
std::vector<Frame> read(size_t n_frames) override;
|
||||||
void read_into(std::byte *image_buf) override { return get_frame_into(this->current_frame++, image_buf); };
|
void read_into(std::byte *image_buf) override { return get_frame_into(this->current_frame++, image_buf); };
|
||||||
|
218
file_io/src/ClusterFile.cpp
Normal file
218
file_io/src/ClusterFile.cpp
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
#include "aare/file_io/ClusterFile.hpp"
|
||||||
|
#include "aare/core/defs.hpp"
|
||||||
|
#include "aare/utils/logger.hpp"
|
||||||
|
#include "fmt/core.h"
|
||||||
|
namespace aare {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Constructor for the ClusterFile class.
|
||||||
|
*
|
||||||
|
* Opens the file with the given mode ('r' for read, 'w' for write).
|
||||||
|
* Throws an exception if the mode is not 'r' or 'w', or if the file cannot be opened.
|
||||||
|
*
|
||||||
|
* @param fname_ The name of the file to open.
|
||||||
|
* @param mode_ The mode to open the file in.
|
||||||
|
* @param config Configuration for the file header.
|
||||||
|
*/
|
||||||
|
ClusterFile::ClusterFile(const std::filesystem::path &fname_, const std::string &mode_, ClusterFileConfig config)
|
||||||
|
: fname{fname_}, mode{mode_}, frame_number{config.frame_number}, n_clusters{config.n_clusters} {
|
||||||
|
|
||||||
|
// check if the file has the .clust extension
|
||||||
|
if (fname.extension() != ".clust") {
|
||||||
|
aare::logger::warn("file", fname, "does not have .clust extension");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode == "r") {
|
||||||
|
// check if the file exists and is a regular file
|
||||||
|
if (not std::filesystem::exists(fname)) {
|
||||||
|
throw std::invalid_argument(fmt::format("file {} does not exist", fname.c_str()));
|
||||||
|
}
|
||||||
|
if (not std::filesystem::is_regular_file(fname)) {
|
||||||
|
throw std::invalid_argument(fmt::format("file {} is not a regular file", fname.c_str()));
|
||||||
|
}
|
||||||
|
// check if the file size is a multiple of the cluster size
|
||||||
|
if ((std::filesystem::file_size(fname) - HEADER_BYTES) % sizeof(Cluster) != 0) {
|
||||||
|
aare::logger::warn("file", fname, "size is not a multiple of cluster size");
|
||||||
|
}
|
||||||
|
if (config != ClusterFileConfig()) {
|
||||||
|
aare::logger::warn("ignored ClusterFileConfig for read mode");
|
||||||
|
}
|
||||||
|
// open file
|
||||||
|
fp = fopen(fname.c_str(), "rb");
|
||||||
|
if (fp == nullptr) {
|
||||||
|
throw std::runtime_error(fmt::format("could not open file {}", fname.c_str()));
|
||||||
|
}
|
||||||
|
// read header
|
||||||
|
const size_t rc = fread(&config, sizeof(config), 1, fp);
|
||||||
|
if (rc != 1) {
|
||||||
|
throw std::runtime_error(fmt::format("could not read header from file {}", fname.c_str()));
|
||||||
|
}
|
||||||
|
frame_number = config.frame_number;
|
||||||
|
n_clusters = config.n_clusters;
|
||||||
|
} else if (mode == "w") {
|
||||||
|
// open file
|
||||||
|
fp = fopen(fname.c_str(), "wb");
|
||||||
|
if (fp == nullptr) {
|
||||||
|
throw std::runtime_error(fmt::format("could not open file {}", fname.c_str()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// write header
|
||||||
|
if (fwrite(&config, sizeof(config), 1, fp) != 1) {
|
||||||
|
throw std::runtime_error(fmt::format("could not write header to file {}", fname.c_str()));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw std::invalid_argument("mode must be 'r' or 'w'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @brief Writes a vector of clusters to the file.
|
||||||
|
*
|
||||||
|
* Each cluster is written as a binary block of size sizeof(Cluster).
|
||||||
|
*
|
||||||
|
* @param clusters The vector of clusters to write to the file.
|
||||||
|
*/
|
||||||
|
void ClusterFile::write(std::vector<Cluster> &clusters) {
|
||||||
|
fwrite(clusters.data(), sizeof(Cluster), clusters.size(), fp);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @brief Writes a single cluster to the file.
|
||||||
|
*
|
||||||
|
* The cluster is written as a binary block of size sizeof(Cluster).
|
||||||
|
*
|
||||||
|
* @param cluster The cluster to write to the file.
|
||||||
|
*/
|
||||||
|
void ClusterFile::write(Cluster &cluster) { fwrite(&cluster, sizeof(Cluster), 1, fp); }
|
||||||
|
/**
|
||||||
|
* @brief Reads a single cluster from the file.
|
||||||
|
*
|
||||||
|
* The cluster is read as a binary block of size sizeof(Cluster).
|
||||||
|
*
|
||||||
|
* @return The cluster read from the file.
|
||||||
|
*/
|
||||||
|
Cluster ClusterFile::read() {
|
||||||
|
if (tell() >= count()) {
|
||||||
|
throw std::runtime_error("cluster number out of range");
|
||||||
|
}
|
||||||
|
|
||||||
|
Cluster cluster{};
|
||||||
|
fread(&cluster, sizeof(Cluster), 1, fp);
|
||||||
|
return cluster;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Reads a specific cluster from the file.
|
||||||
|
*
|
||||||
|
* The file pointer is moved to the specific cluster, and the cluster is read as a binary block of size sizeof(Cluster).
|
||||||
|
*
|
||||||
|
* @param cluster_number The number of the cluster to read from the file.
|
||||||
|
* @return The cluster read from the file.
|
||||||
|
*/
|
||||||
|
Cluster ClusterFile::iread(size_t cluster_number) {
|
||||||
|
if (cluster_number >= count()) {
|
||||||
|
throw std::runtime_error("cluster number out of range");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto old_pos = ftell(fp);
|
||||||
|
this->seek(cluster_number);
|
||||||
|
Cluster cluster{};
|
||||||
|
fread(&cluster, sizeof(Cluster), 1, fp);
|
||||||
|
fseek(fp, old_pos, SEEK_SET); // restore the file position
|
||||||
|
return cluster;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Reads a specific number of clusters from the file.
|
||||||
|
*
|
||||||
|
* Each cluster is read as a binary block of size sizeof(Cluster).
|
||||||
|
*
|
||||||
|
* @param n_clusters The number of clusters to read from the file.
|
||||||
|
* @return A vector of clusters read from the file.
|
||||||
|
*/
|
||||||
|
std::vector<Cluster> ClusterFile::read(size_t n_clusters_) {
|
||||||
|
if (n_clusters_ + tell() > count()) {
|
||||||
|
throw std::runtime_error("cluster number out of range");
|
||||||
|
}
|
||||||
|
std::vector<Cluster> clusters(n_clusters_);
|
||||||
|
fread(clusters.data(), sizeof(Cluster), n_clusters, fp);
|
||||||
|
return clusters;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Moves the file pointer to a specific cluster.
|
||||||
|
*
|
||||||
|
* The file pointer is moved to the start of the specific cluster, based on the size of a cluster.
|
||||||
|
*
|
||||||
|
* @param cluster_number The number of the cluster to move the file pointer to.
|
||||||
|
*/
|
||||||
|
void ClusterFile::seek(size_t cluster_number) {
|
||||||
|
if (cluster_number > count()) {
|
||||||
|
throw std::runtime_error("cluster number out of range");
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto offset = static_cast<int64_t>(sizeof(ClusterFileConfig) + cluster_number * sizeof(Cluster));
|
||||||
|
|
||||||
|
fseek(fp, offset, SEEK_SET);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Gets the current position of the file pointer in terms of clusters.
|
||||||
|
*
|
||||||
|
* The position is calculated as the number of clusters from the beginning of the file to the current position of the
|
||||||
|
* file pointer.
|
||||||
|
*
|
||||||
|
* @return The current position of the file pointer in terms of clusters.
|
||||||
|
*/
|
||||||
|
size_t ClusterFile::tell() const { return ftell(fp) / sizeof(Cluster); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Counts the number of clusters in the file.
|
||||||
|
*
|
||||||
|
* The count is calculated as the size of the file divided by the size of a cluster.
|
||||||
|
*
|
||||||
|
* @return The number of clusters in the file.
|
||||||
|
*/
|
||||||
|
size_t ClusterFile::count() noexcept {
|
||||||
|
if (mode == "r") {
|
||||||
|
return n_clusters;
|
||||||
|
}
|
||||||
|
// save the current position
|
||||||
|
auto old_pos = ftell(fp);
|
||||||
|
fseek(fp, 0, SEEK_END);
|
||||||
|
const size_t n_clusters_ = ftell(fp) / sizeof(Cluster);
|
||||||
|
// restore the file position
|
||||||
|
fseek(fp, old_pos, SEEK_SET);
|
||||||
|
return n_clusters_;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t ClusterFile::frame() const { return frame_number; }
|
||||||
|
void ClusterFile::update_header() {
|
||||||
|
if (mode == "r") {
|
||||||
|
throw std::runtime_error("update header is not implemented for read mode");
|
||||||
|
}
|
||||||
|
// update the header with the correct number of clusters
|
||||||
|
aare::logger::debug("updating header with correct number of clusters", count());
|
||||||
|
auto tmp_n_clusters = count();
|
||||||
|
fseek(fp, 0, SEEK_SET);
|
||||||
|
ClusterFileConfig config(frame_number, static_cast<int32_t>(tmp_n_clusters));
|
||||||
|
if (fwrite(&config, sizeof(config), 1, fp) != 1) {
|
||||||
|
throw std::runtime_error("could not write header to file");
|
||||||
|
}
|
||||||
|
if (fflush(fp) != 0) {
|
||||||
|
throw std::runtime_error("could not flush file");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ClusterFile::~ClusterFile() noexcept {
|
||||||
|
if (mode == "w") {
|
||||||
|
try {
|
||||||
|
update_header();
|
||||||
|
} catch (std::runtime_error &e) {
|
||||||
|
aare::logger::error("error updating header", e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fp != nullptr) {
|
||||||
|
fclose(fp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace aare
|
114
file_io/test/ClusterFile.test.cpp
Normal file
114
file_io/test/ClusterFile.test.cpp
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
#include "aare/file_io/ClusterFile.hpp"
|
||||||
|
#include "aare/utils/compare_files.hpp"
|
||||||
|
#include "test_config.hpp"
|
||||||
|
#include <catch2/catch_test_macros.hpp>
|
||||||
|
#include <iostream>
|
||||||
|
#include <random>
|
||||||
|
using aare::Cluster;
|
||||||
|
using aare::ClusterFile;
|
||||||
|
using aare::ClusterFileConfig;
|
||||||
|
|
||||||
|
TEST_CASE("Read a cluster file") {
|
||||||
|
auto fpath = test_data_path() / "clusters" / "single_frame_97_clustrers.clust";
|
||||||
|
REQUIRE(std::filesystem::exists(fpath));
|
||||||
|
ClusterFile cf(fpath, "r");
|
||||||
|
|
||||||
|
SECTION("Read the header of the file") {
|
||||||
|
REQUIRE(cf.count() == 97);
|
||||||
|
REQUIRE(cf.frame() == 135);
|
||||||
|
}
|
||||||
|
SECTION("Read a single cluster") {
|
||||||
|
Cluster c = cf.read();
|
||||||
|
REQUIRE(c.x == 1);
|
||||||
|
REQUIRE(c.y == 200);
|
||||||
|
for (int i = 0; i < 9; i++) {
|
||||||
|
REQUIRE(c.data[i] == i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SECTION("Read a single cluster using iread") {
|
||||||
|
Cluster c = cf.iread(0);
|
||||||
|
REQUIRE(c.x == 1);
|
||||||
|
REQUIRE(c.y == 200);
|
||||||
|
for (int i = 0; i < 9; i++) {
|
||||||
|
REQUIRE(c.data[i] == i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SECTION("Read a cluster using seek") {
|
||||||
|
cf.seek(0);
|
||||||
|
Cluster c = cf.read();
|
||||||
|
REQUIRE(c.x == 1);
|
||||||
|
REQUIRE(c.y == 200);
|
||||||
|
for (int i = 0; i < 9; i++) {
|
||||||
|
REQUIRE(c.data[i] == i);
|
||||||
|
}
|
||||||
|
c = cf.read();
|
||||||
|
REQUIRE(c.x == 2);
|
||||||
|
REQUIRE(c.y == 201);
|
||||||
|
for (int i = 0; i < 9; i++) {
|
||||||
|
REQUIRE(c.data[i] == i + 9);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SECTION("check out of bound reading") {
|
||||||
|
REQUIRE_THROWS_AS(cf.iread(97), std::runtime_error);
|
||||||
|
REQUIRE_NOTHROW(cf.seek(97));
|
||||||
|
REQUIRE_THROWS_AS(cf.read(), std::runtime_error);
|
||||||
|
REQUIRE_THROWS_AS(cf.read(1), std::runtime_error);
|
||||||
|
REQUIRE_NOTHROW(cf.seek(0));
|
||||||
|
REQUIRE_NOTHROW(cf.read(97));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("test read multiple clusters") {
|
||||||
|
std::vector<Cluster> cluster = cf.read(97);
|
||||||
|
REQUIRE(cluster.size() == 97);
|
||||||
|
int offset = 0;
|
||||||
|
int data_offset = 0;
|
||||||
|
for (auto c : cluster) {
|
||||||
|
REQUIRE(c.x == offset + 1);
|
||||||
|
REQUIRE(c.y == offset + 200);
|
||||||
|
for (int i = 0; i < 9; i++) {
|
||||||
|
REQUIRE(c.data[i] == data_offset + i);
|
||||||
|
}
|
||||||
|
|
||||||
|
offset++;
|
||||||
|
data_offset += 9;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("write a cluster file") {
|
||||||
|
|
||||||
|
auto const FRAME_NUMBER = 1461041991;
|
||||||
|
auto const TOTAL_CLUSTERS = 214748;
|
||||||
|
|
||||||
|
std::filesystem::path const fpath_out("/tmp/file.clust");
|
||||||
|
ClusterFile cf_out(fpath_out, "w", ClusterFileConfig(FRAME_NUMBER, TOTAL_CLUSTERS));
|
||||||
|
REQUIRE(cf_out.count() == 0);
|
||||||
|
REQUIRE(cf_out.frame() == FRAME_NUMBER);
|
||||||
|
|
||||||
|
// write file with random close to bounds values
|
||||||
|
int32_t offset = 0;
|
||||||
|
std::vector<Cluster> clusters(TOTAL_CLUSTERS);
|
||||||
|
for (int32_t i = 0; i < TOTAL_CLUSTERS; i++) {
|
||||||
|
Cluster c;
|
||||||
|
c.x = INT16_MAX - offset;
|
||||||
|
c.y = INT16_MAX - (offset + 200);
|
||||||
|
for (int32_t j = 0; j < 9; j++) {
|
||||||
|
if (j % 2 == 0)
|
||||||
|
c.data[j] = -(offset * 2);
|
||||||
|
else
|
||||||
|
c.data[j] = (offset * 2);
|
||||||
|
}
|
||||||
|
clusters[i] = c;
|
||||||
|
offset++;
|
||||||
|
offset %= INT16_MAX - 200;
|
||||||
|
}
|
||||||
|
cf_out.write(clusters);
|
||||||
|
REQUIRE(cf_out.count() == TOTAL_CLUSTERS);
|
||||||
|
REQUIRE(cf_out.frame() == FRAME_NUMBER);
|
||||||
|
cf_out.update_header();
|
||||||
|
REQUIRE(cf_out.count() == TOTAL_CLUSTERS);
|
||||||
|
REQUIRE(cf_out.frame() == FRAME_NUMBER);
|
||||||
|
|
||||||
|
auto data_file = test_data_path() / "clusters" / "test_writing.clust";
|
||||||
|
REQUIRE(aare::compare_files(fpath_out, data_file));
|
||||||
|
}
|
37
utils/include/aare/utils/compare_files.hpp
Normal file
37
utils/include/aare/utils/compare_files.hpp
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
#include <algorithm>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iterator>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace aare {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Compare two files
|
||||||
|
*
|
||||||
|
* @param p1 path to the first file
|
||||||
|
* @param p2 path to the second file
|
||||||
|
* @return true if the files are the same, false otherwise
|
||||||
|
*/
|
||||||
|
bool compare_files(const std::string &p1, const std::string &p2) {
|
||||||
|
std::ifstream f1(p1, std::ifstream::binary | std::ifstream::ate);
|
||||||
|
std::ifstream f2(p2, std::ifstream::binary | std::ifstream::ate);
|
||||||
|
|
||||||
|
if (f1.fail() || f2.fail()) {
|
||||||
|
return false; // file problem
|
||||||
|
}
|
||||||
|
|
||||||
|
if (f1.tellg() != f2.tellg()) {
|
||||||
|
return false; // size mismatch
|
||||||
|
}
|
||||||
|
|
||||||
|
// seek back to beginning and use std::equal to compare contents
|
||||||
|
f1.seekg(0, std::ifstream::beg);
|
||||||
|
f2.seekg(0, std::ifstream::beg);
|
||||||
|
return std::equal(std::istreambuf_iterator<char>(f1.rdbuf()), std::istreambuf_iterator<char>(),
|
||||||
|
std::istreambuf_iterator<char>(f2.rdbuf()));
|
||||||
|
}
|
||||||
|
bool compare_files(const std::filesystem::path &p1, const std::filesystem::path &p2) {
|
||||||
|
return compare_files(p1.string(), p2.string());
|
||||||
|
}
|
||||||
|
} // namespace aare
|
@ -244,7 +244,7 @@ extern aare::logger::Logger logger_instance; // NOLINT
|
|||||||
* @param s arguments to log
|
* @param s arguments to log
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
template <LOGGING_LEVEL level, typename... Strings> void log(const Strings... s) {
|
template <LOGGING_LEVEL level, typename... Strings> void log(const Strings... s) noexcept {
|
||||||
internal::logger_instance.log<level>(s...);
|
internal::logger_instance.log<level>(s...);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
|
Loading…
x
Reference in New Issue
Block a user