diff --git a/CMakeLists.txt b/CMakeLists.txt index 584d3256e42acd4fd09280094158c6eef5484a80..e2ab0ac60c61e79b307439211cd37db7ac340d4d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -217,11 +217,12 @@ add_subdirectory(components/net) add_subdirectory(components/rgbd-sources) add_subdirectory(components/control/cpp) add_subdirectory(components/operators) +add_subdirectory(components/streams) add_subdirectory(applications/calibration) -add_subdirectory(applications/groupview) -add_subdirectory(applications/player) -add_subdirectory(applications/recorder) -add_subdirectory(applications/merger) +#add_subdirectory(applications/groupview) +#add_subdirectory(applications/player) +#add_subdirectory(applications/recorder) +#add_subdirectory(applications/merger) if (HAVE_AVFORMAT) add_subdirectory(applications/ftl2mkv) diff --git a/applications/calibration-multi/CMakeLists.txt b/applications/calibration-multi/CMakeLists.txt index ae1f1bb1698fbcbd1f8de3dc1b279123025d27b0..d14704e2ada4943f18d35cb792c682fa3dd01a25 100644 --- a/applications/calibration-multi/CMakeLists.txt +++ b/applications/calibration-multi/CMakeLists.txt @@ -9,4 +9,4 @@ add_executable(ftl-calibrate-multi ${CALIBMULTI}) target_include_directories(ftl-calibrate-multi PRIVATE src) -target_link_libraries(ftl-calibrate-multi ftlcommon ftlnet ftlrgbd Threads::Threads ${OpenCV_LIBS} ${cvsba_LIBS}) +target_link_libraries(ftl-calibrate-multi ftlcommon ftlnet ftlrgbd ftlstreams Threads::Threads ${OpenCV_LIBS} ${cvsba_LIBS}) diff --git a/applications/calibration-multi/src/main.cpp b/applications/calibration-multi/src/main.cpp index 7c92ea9eaffcf68f1af97c47e1255d95b5a533cc..b8c62e758c509e4448a361b83ae2d294c5f5c8f5 100644 --- a/applications/calibration-multi/src/main.cpp +++ b/applications/calibration-multi/src/main.cpp @@ -1,10 +1,14 @@ #include <loguru.hpp> - +#include <ftl/threads.hpp> #include <ftl/configuration.hpp> #include <ftl/net/universe.hpp> #include <ftl/rgbd/source.hpp> #include <ftl/rgbd/group.hpp> +#include <ftl/master.hpp> +#include <ftl/streams/receiver.hpp> +#include <ftl/streams/netstream.hpp> + #include <opencv2/core.hpp> #include <opencv2/aruco.hpp> #include <opencv2/core/eigen.hpp> @@ -44,95 +48,21 @@ Mat createCameraMatrix(const ftl::rgbd::Camera ¶meters) { return m; } -void to_json(nlohmann::json &json, map<string, Eigen::Matrix4d> &transformations) { - for (auto &item : transformations) { - auto val = nlohmann::json::array(); - for(size_t i = 0; i < 16; i++) { val.push_back((float) item.second.data()[i]); } - json[item.first] = val; - } -} - -// FileStorage allows only alphanumeric keys (code below does not work with URIs) - -bool saveRegistration(const string &ofile, const map<string, Mat> &data) { - cv::FileStorage fs(ofile, cv::FileStorage::WRITE); - if (!fs.isOpened()) return false; - for (auto &item : data) { fs << item.first << item.second; } - fs.release(); - return true; -} - -bool saveRegistration(const string &ofile, const map<string, Eigen::Matrix4d> &data) { - map<string, Mat> _data; - for (auto &item : data) { - Mat M; - cv::eigen2cv(item.second, M); - _data[item.first] = M; - } - return saveRegistration(ofile, _data); -} - -bool loadRegistration(const string &ifile, map<string, Mat> &data) { - cv::FileStorage fs(ifile, cv::FileStorage::READ); - if (!fs.isOpened()) return false; - for(cv::FileNodeIterator fit = fs.getFirstTopLevelNode().begin(); - fit != fs.getFirstTopLevelNode().end(); - ++fit) - { - data[(*fit).name()] = (*fit).mat(); - } - fs.release(); - return true; // TODO errors? -} - -bool loadRegistration(const string &ifile, map<string, Eigen::Matrix4d> &data) { - map<string, Mat> _data; - if (!loadRegistration(ifile, _data)) return false; - for (auto &item : _data) { - Eigen::Matrix4d M; - cv::cv2eigen(item.second, M); - data[item.first] = M; - } - return true; -} - -// - -bool saveIntrinsics(const string &ofile, const vector<Mat> &M, const Size &size) { - vector<Mat> D; - { - cv::FileStorage fs(ofile, cv::FileStorage::READ); - fs["D"] >> D; - fs.release(); - } - { - cv::FileStorage fs(ofile, cv::FileStorage::WRITE); - if (fs.isOpened()) { - fs << "resolution" << size; - fs << "K" << M << "D" << D; - fs.release(); - return true; - } - else { - LOG(ERROR) << "Error: can not save the intrinsic parameters to '" << ofile << "'"; - } - return false; - } -} +struct CalibrationParams { + string output_path; + string registration_file; + vector<size_t> idx_cameras; + bool save_extrinsic = true; + bool save_intrinsic = false; + bool optimize_intrinsic = false; + int reference_camera = -1; + double alpha = 0.0; + Size size; +}; -bool saveExtrinsics(const string &ofile, Mat &R, Mat &T, Mat &R1, Mat &R2, Mat &P1, Mat &P2, Mat &Q) { - cv::FileStorage fs; - fs.open(ofile, cv::FileStorage::WRITE); - if (fs.isOpened()) { - fs << "R" << R << "T" << T << "R1" << R1 << "R2" << R2 << "P1" - << P1 << "P2" << P2 << "Q" << Q; - fs.release(); - return true; - } else { - LOG(ERROR) << "Error: can not save the extrinsic parameters"; - } - return false; -} +//////////////////////////////////////////////////////////////////////////////// +// Visualization +//////////////////////////////////////////////////////////////////////////////// void stack(const vector<Mat> &img, Mat &out, const int rows, const int cols) { Size size = img[0].size(); @@ -156,13 +86,6 @@ void stack(const vector<Mat> &img, Mat &out) { stack(img, out, rows, cols); } -string time_now_string() { - char timestamp[18]; - std::time_t t=std::time(NULL); - std::strftime(timestamp, sizeof(timestamp), "%F-%H%M%S", std::localtime(&t)); - return string(timestamp); -} - void visualizeCalibration( MultiCameraCalibrationNew &calib, Mat &out, vector<Mat> &rgb, const vector<Mat> &map1, const vector<Mat> &map2, const vector<cv::Rect> &roi) @@ -192,21 +115,57 @@ void visualizeCalibration( MultiCameraCalibrationNew &calib, Mat &out, stack(rgb, out); } -struct CalibrationParams { - string output_path; - string registration_file; - vector<size_t> idx_cameras; - bool save_extrinsic = true; - bool save_intrinsic = false; - bool optimize_intrinsic = false; - int reference_camera = -1; - double alpha = 0.0; - Size size; -}; +//////////////////////////////////////////////////////////////////////////////// +// RPC +//////////////////////////////////////////////////////////////////////////////// -void calibrate( MultiCameraCalibrationNew &calib, vector<string> &uri_cameras, - const CalibrationParams ¶ms, vector<Mat> &map1, vector<Mat> &map2, vector<cv::Rect> &roi) -{ +bool setRectifyRPC(ftl::net::Universe* net, ftl::stream::Net* nstream, bool enabled) { + return net->call<bool>(nstream->getPeer(), "set_rectify", enabled); +} + +bool setIntrinsicsRPC(ftl::net::Universe* net, ftl::stream::Net* nstream, Size size, vector<Mat> K, vector<Mat> D) { + Mat K0 = K[0].t(); + Mat K1 = K[1].t(); + + return net->call<bool>( nstream->getPeer(), "set_intrinsics", + vector<int>{size.width, size.height}, + vector<double>(K0.begin<double>(), K0.end<double>()), + vector<double>(D[0].begin<double>(), D[0].end<double>()), + vector<double>(K1.begin<double>(), K1.end<double>()), + vector<double>(D[1].begin<double>(), D[1].end<double>()) + ); +} + +bool setExtrinsicsRPC(ftl::net::Universe* net, ftl::stream::Net* nstream, Mat R, Mat t) { + Mat rvec; + cv::Rodrigues(R, rvec); + return net->call<bool>( nstream->getPeer(), "set_extrinsics", + vector<double>(rvec.begin<double>(), rvec.end<double>()), + vector<double>(t.begin<double>(), t.end<double>()) + ); +} + +bool setPoseRPC(ftl::net::Universe* net, ftl::stream::Net* nstream, Mat pose) { + Mat P = pose.t(); + return net->call<bool>( nstream->getPeer(), "set_pose", + vector<double>(P.begin<double>(), P.end<double>())); +} + +bool saveCalibrationRPC(ftl::net::Universe* net, ftl::stream::Net* nstream) { + return net->call<bool>(nstream->getPeer(), "save_calibration"); +} + +//////////////////////////////////////////////////////////////////////////////// + +/* run calibration and perform RPC to update calibration on nodes */ + +void calibrateRPC( ftl::net::Universe* net, + MultiCameraCalibrationNew &calib, + const CalibrationParams ¶ms, + vector<ftl::stream::Net*> &nstreams, + vector<Mat> &map1, + vector<Mat> &map2, + vector<cv::Rect> &roi) { int reference_camera = params.reference_camera; if (params.reference_camera < 0) { reference_camera = calib.getOptimalReferenceCamera(); @@ -246,44 +205,23 @@ void calibrate( MultiCameraCalibrationNew &calib, vector<string> &uri_cameras, Rt_out[c] = getMat4x4(R[c], t[c]) * getMat4x4(R1, _t).inv(); Rt_out[c + 1] = getMat4x4(R[c + 1], t[c + 1]) * getMat4x4(R2, _t).inv(); - { - string node_name; - size_t pos1 = uri_cameras[c/2].find("node"); - size_t pos2 = uri_cameras[c/2].find("#", pos1); - node_name = uri_cameras[c/2].substr(pos1, pos2 - pos1); - - if (params.save_extrinsic) - { - // TODO: only R and T required when rectification performed - // on vision node. Consider saving extrinsic global - // for node as well? - saveExtrinsics( params.output_path + node_name + "-extrinsic.yml", - R_c1c2, T_c1c2, R1, R2, P1, P2, Q - ); - LOG(INFO) << "Saved: " << params.output_path + node_name + "-extrinsic.yml"; + auto *nstream = nstreams[c/2]; + while(true) { + try { + /*setIntrinsicsRPC(net, nstream, + params.size, + vector<Mat>{calib.getCameraMat(c), calib.getCameraMat(c+1)}, + vector<Mat>{calib.getDistCoeffs(c), calib.getDistCoeffs(c+1)} + );*/ + setExtrinsicsRPC(net, nstream, R_c1c2, T_c1c2); + setPoseRPC(net, nstream, Rt_out[c]); + saveCalibrationRPC(net, nstream); + LOG(INFO) << "CALIBRATION SENT"; + break; } - else - { - Mat rvec; - cv::Rodrigues(R_c1c2, rvec); - LOG(INFO) << "From camera " << c << " to " << c + 1; - LOG(INFO) << "rotation: " << rvec.t(); - LOG(INFO) << "translation: " << T_c1c2.t(); - } - - if (params.save_intrinsic) - { - saveIntrinsics( - params.output_path + node_name + "-intrinsic.yml", - { calib.getCameraMat(c), calib.getCameraMat(c + 1) }, - params.size - ); - LOG(INFO) << "Saved: " << params.output_path + node_name + "-intrinsic.yml"; - } - else if (params.optimize_intrinsic) - { - LOG(INFO) << "K1:\n" << K1; - LOG(INFO) << "K2:\n" << K2; + catch (...) { + LOG(ERROR) << "RPC failed!"; + sleep(1); } } @@ -295,88 +233,6 @@ void calibrate( MultiCameraCalibrationNew &calib, vector<string> &uri_cameras, cv::initUndistortRectifyMap(K1, D1, R1, P1, params.size, CV_16SC2, map1[c], map2[c]); cv::initUndistortRectifyMap(K2, D2, R2, P2, params.size, CV_16SC2, map1[c + 1], map2[c + 1]); } - - { - map<string, Eigen::Matrix4d> out; - for (size_t i = 0; i < n_cameras; i += 2) { - Eigen::Matrix4d M_eigen; - Mat M_cv = Rt_out[i]; - cv::cv2eigen(M_cv, M_eigen); - out[uri_cameras[i/2]] = M_eigen; - } - - nlohmann::json out_json; - to_json(out_json, out); - if (params.save_extrinsic) { - std::ofstream file_out(params.registration_file); - file_out << out_json; - } - else { - LOG(INFO) << "Registration not saved to file"; - LOG(INFO) << out_json; - } - } -} - -void calibrateFromPath( const string &path, - const string &filename, - CalibrationParams ¶ms, - bool visualize=false) -{ - size_t reference_camera = 0; - auto calib = MultiCameraCalibrationNew(0, reference_camera, Size(0, 0), CalibrationTarget(0.250)); - - vector<string> uri_cameras; - cv::FileStorage fs(path + filename, cv::FileStorage::READ); - fs["uri"] >> uri_cameras; - fs["resolution"] >> params.size; - - //params.idx_cameras = {2, 3};//{0, 1, 4, 5, 6, 7, 8, 9, 10, 11}; - params.idx_cameras.resize(uri_cameras.size() * 2); - std::iota(params.idx_cameras.begin(), params.idx_cameras.end(), 0); - - calib.loadInput(path + filename, params.idx_cameras); - - vector<Mat> map1, map2; - vector<cv::Rect> roi; - calibrate(calib, uri_cameras, params, map1, map2, roi); - - if (!visualize) return; - - vector<Scalar> colors = { - Scalar(64, 64, 255), - Scalar(64, 64, 255), - Scalar(64, 255, 64), - Scalar(64, 255, 64), - }; - vector<int> markers = {cv::MARKER_SQUARE, cv::MARKER_DIAMOND}; - - Mat out; - size_t n_cameras = calib.getCamerasCount(); - vector<Mat> rgb(n_cameras); - size_t i = 0; - while(ftl::running) { - for (size_t c = 0; c < n_cameras; c++) { - rgb[c] = cv::imread(path + std::to_string(params.idx_cameras[c]) + "_" + std::to_string(i) + ".jpg"); - for (size_t j = 0; j < rgb.size(); j++) { - vector<Point2d> points; - // TODO: indexing incorrect if all cameras used (see also loadInput) - calib.projectPointsOptimized(c, i, points); // project BA point to image - - for (Point2d &p : points) { - cv::drawMarker(rgb[c], cv::Point2i(p), colors[j % colors.size()], markers[j % markers.size()], 10 + 3 * j, 1); - } - } - } - - visualizeCalibration(calib, out, rgb, map1, map2, roi); - cv::namedWindow("Calibration", cv::WINDOW_KEEPRATIO | cv::WINDOW_NORMAL); - cv::imshow("Calibration", out); - - i = (i + 1) % calib.getViewsCount(); - - if (cv::waitKey(50) != -1) { ftl::running = false; } - } } void runCameraCalibration( ftl::Configurable* root, @@ -386,59 +242,91 @@ void runCameraCalibration( ftl::Configurable* root, CalibrationParams ¶ms) { Universe *net = ftl::create<Universe>(root, "net"); + ftl::ctrl::Master ctrl(root, net); + net->start(); net->waitConnections(); - - vector<Source*> sources = ftl::createArray<Source>(root, "sources", net); - const size_t n_sources = sources.size(); + ftl::stream::Muxer *stream = ftl::create<ftl::stream::Muxer>(root, "muxstream"); + ftl::stream::Receiver *gen = ftl::create<ftl::stream::Receiver>(root, "receiver"); + gen->setStream(stream); + auto stream_uris = net->findAll<std::string>("list_streams"); + std::vector<ftl::stream::Net*> nstreams; + + int count = 0; + for (auto &s : stream_uris) { + LOG(INFO) << " --- found stream: " << s; + auto *nstream = ftl::create<ftl::stream::Net>(stream, std::to_string(count), net); + nstream->set("uri", s); + nstreams.push_back(nstream); + stream->add(nstream); + ++count; + } + + const size_t n_sources = nstreams.size(); const size_t n_cameras = n_sources * 2; size_t reference_camera = 0; - - ftl::rgbd::Group group; - for (Source* src : sources) { - src->setChannel(Channel::Right); - group.addSource(src); - } std::mutex mutex; std::atomic<bool> new_frames = false; - vector<Mat> rgb(n_cameras), rgb_new(n_cameras); - - ftl::timer::start(false); + vector<Mat> rgb_(n_cameras), rgb_new(n_cameras); + vector<Mat> camera_parameters(n_cameras); + Size res; - group.sync([&mutex, &new_frames, &rgb_new](ftl::rgbd::FrameSet &frames) { - mutex.lock(); + gen->onFrameSet([stream, &mutex, &new_frames, &rgb_new, &camera_parameters, &res](ftl::rgbd::FrameSet &fs) { + stream->select(fs.id, Channel::Left + Channel::Right); + UNIQUE_LOCK(mutex, CALLBACK); bool good = true; - for (size_t i = 0; i < frames.sources.size(); i ++) { - frames.frames[i].download(Channel::Left+Channel::Right); - - if (frames.frames[i].get<cv::Mat>(Channel::Left).empty()) good = false; - if (frames.frames[i].get<cv::Mat>(Channel::Right).empty()) good = false; - if (frames.frames[i].get<cv::Mat>(Channel::Left).channels() != 3) good = false; // ASSERT - if (frames.frames[i].get<cv::Mat>(Channel::Right).channels() != 3) good = false; - if (!good) break; - cv::swap(frames.frames[i].get<cv::Mat>(Channel::Left), rgb_new[2 * i]); - cv::swap(frames.frames[i].get<cv::Mat>(Channel::Right), rgb_new[2 * i + 1]); + try { + for (size_t i = 0; i < fs.frames.size(); i ++) { + fs.frames[i].download(Channel::Left+Channel::Right); + + Mat left = fs.frames[i].get<Mat>(Channel::Left); + cv::cvtColor(left, rgb_new[2*i], cv::COLOR_BGRA2BGR); + Mat right = fs.frames[i].get<Mat>(Channel::Right); + cv::cvtColor(right, rgb_new[2*i+1], cv::COLOR_BGRA2BGR); + + if (left.empty()) good = false; + if (right.empty()) good = false; + if (!good) break; + + camera_parameters[2*i] = createCameraMatrix(fs.frames[i].getLeftCamera()); + camera_parameters[2*i+1] = createCameraMatrix(fs.frames[i].getRightCamera()); + if (res.empty()) res = rgb_new[2*i].size(); + } + } + catch (...) { + good = false; } - new_frames = good; - mutex.unlock(); return true; }); - for (auto &source : sources) { - while (!source->isReady()) { - std::this_thread::sleep_for(std::chrono::milliseconds(100)); + stream->begin(); + ftl::timer::start(false); + + while(true) { + if (!res.empty()) { + params.size = res; + LOG(INFO) << "Camera resolution: " << params.size; + break; } + sleep(1); } - { - auto camera = sources[0]->parameters(); - params.size = Size(camera.width, camera.height); - LOG(INFO) << "Camera resolution: " << params.size; - } + for (auto *nstream: nstreams) { + bool res = false; + try { res = setRectifyRPC(net, nstream, false); } + catch (...) {} + if (!res) { + LOG(ERROR) << "set_rectify() failed for " << *(nstream->get<string>("uri")); + } + else { + LOG(INFO) << "rectification disabled for " << *(nstream->get<string>("uri")); + } + } + params.idx_cameras.resize(n_cameras); std::iota(params.idx_cameras.begin(), params.idx_cameras.end(), 0); @@ -447,40 +335,25 @@ void runCameraCalibration( ftl::Configurable* root, params.size, CalibrationTarget(0.250) ); - for (size_t i = 0; i < n_sources; i++) { - auto camera_r = sources[i]->parameters(Channel::Right); - auto camera_l = sources[i]->parameters(); - - CHECK(params.size == Size(camera_r.width, camera_r.height)); - CHECK(params.size == Size(camera_l.width, camera_l.height)); - - Mat K; - K = createCameraMatrix(camera_r); - LOG(INFO) << "K[" << 2 * i + 1 << "] = \n" << K; - calib.setCameraParameters(2 * i + 1, K); - - K = createCameraMatrix(camera_l); - LOG(INFO) << "K[" << 2 * i << "] = \n" << K; - calib.setCameraParameters(2 * i, K); - } - int iter = 0; Mat show; vector<int> visible; vector<vector<Point2d>> points(n_cameras); + vector<Mat> rgb(n_cameras); + sleep(3); + while(calib.getMinVisibility() < n_views) { cv::waitKey(10); - while (!new_frames) { - for (auto src : sources) { src->grab(30); } - cv::waitKey(10); + + while (new_frames) { + UNIQUE_LOCK(mutex, LOCK); + if (new_frames) rgb.swap(rgb_new); + new_frames = false; } - - mutex.lock(); - rgb.swap(rgb_new); - new_frames = false; - mutex.unlock(); + + for (Mat &im : rgb) { CHECK(!im.empty()); } visible.clear(); int n_found = findCorrespondingPoints(rgb, points, visible); @@ -521,41 +394,39 @@ void runCameraCalibration( ftl::Configurable* root, } cv::destroyWindow("Cameras"); - vector<string> uri; - for (size_t i = 0; i < n_sources; i++) { - uri.push_back(sources[i]->getURI()); - } - - if (save_input) { - cv::FileStorage fs(path + filename, cv::FileStorage::WRITE); - fs << "uri" << uri; - calib.saveInput(fs); - fs.release(); + for (size_t i = 0; i < camera_parameters.size(); i++) { + //CHECK(params.size == Size(camera_r.width, camera_r.height)); + //CHECK(params.size == Size(camera_l.width, camera_l.height)); + + LOG(INFO) << "K[" << i << "] = \n" << camera_parameters[i]; + calib.setCameraParameters(i, camera_parameters[i]); } - + Mat out; vector<Mat> map1, map2; vector<cv::Rect> roi; vector<size_t> idx; - calibrate(calib, uri, params, map1, map2, roi); + calibrateRPC(net, calib, params, nstreams, map1, map2, roi); // visualize while(ftl::running) { cv::waitKey(10); while (!new_frames) { - for (auto src : sources) { src->grab(30); } if (cv::waitKey(50) != -1) { ftl::running = false; } } - mutex.lock(); - rgb.swap(rgb_new); - new_frames = false; - mutex.unlock(); + { + UNIQUE_LOCK(mutex, LOCK) + rgb.swap(rgb_new); + new_frames = false; + mutex.unlock(); + } visualizeCalibration(calib, out, rgb, map1, map2, roi); cv::namedWindow("Calibration", cv::WINDOW_KEEPRATIO | cv::WINDOW_NORMAL); cv::imshow("Calibration", out); } + } int main(int argc, char **argv) { @@ -595,25 +466,23 @@ int main(int argc, char **argv) { params.reference_camera = ref_camera; LOG(INFO) << "\n" - << "\nIMPORTANT: Remeber to set \"use_intrinsics\" to false for nodes!" << "\n" - << "\n save_input: " << (int) save_input - << "\n load_input: " << (int) load_input - << "\n save_extrinsic: " << (int) save_extrinsic - << "\n save_intrinsic: " << (int) save_intrinsic +// << "\n save_input: " << (int) save_input +// << "\n load_input: " << (int) load_input +// << "\n save_extrinsic: " << (int) save_extrinsic +// << "\n save_intrinsic: " << (int) save_intrinsic << "\n optimize_intrinsic: " << (int) optimize_intrinsic - << "\n calibration_data_dir: " << calibration_data_dir - << "\n calibration_data_file: " << calibration_data_file +// << "\n calibration_data_dir: " << calibration_data_dir +// << "\n calibration_data_file: " << calibration_data_file << "\n min_visible: " << min_visible << "\n n_views: " << n_views << "\n reference_camera: " << ref_camera << (ref_camera != -1 ? "" : " (automatic)") - << "\n registration_file: " << registration_file - << "\n output_directory: " << output_directory +// << "\n registration_file: " << registration_file +// << "\n output_directory: " << output_directory << "\n"; if (load_input) { - vector<size_t> idx = {}; - calibrateFromPath(calibration_data_dir, calibration_data_file, params, true); + LOG(FATAL) << "TODO"; } else { runCameraCalibration(root, n_views, min_visible, calibration_data_dir, calibration_data_file, save_input, params); diff --git a/applications/calibration/src/lens.cpp b/applications/calibration/src/lens.cpp index b69b26cc6e4cfec9dab04d337d75b21721d2fbae..c0c44bbe0cd387545b3f3f1980abf9fa6f5e701c 100644 --- a/applications/calibration/src/lens.cpp +++ b/applications/calibration/src/lens.cpp @@ -39,7 +39,7 @@ void ftl::calibration::intrinsic(map<string, string> &opt) { const int delay = getOptionInt(opt, "delay", 1000); const double aperture_width = getOptionDouble(opt, "aperture_width", 6.2); const double aperture_height = getOptionDouble(opt, "aperture_height", 4.6); - const string filename_intrinsics = getOptionString(opt, "profile", FTL_LOCAL_CONFIG_ROOT "/intrinsics.yml"); + const string filename_intrinsics = getOptionString(opt, "profile", FTL_LOCAL_CONFIG_ROOT "/calibration.yml"); CalibrationChessboard calib(opt); bool use_guess = getOptionInt(opt, "use_guess", 1); //bool use_guess_distortion = getOptionInt(opt, "use_guess_distortion", 0); @@ -53,7 +53,8 @@ void ftl::calibration::intrinsic(map<string, string> &opt) { LOG(INFO) << " delay: " << delay; LOG(INFO) << " aperture_width: " << aperture_width; LOG(INFO) << " aperture_height: " << aperture_height; - LOG(INFO) << " use_guess: " << use_guess; + LOG(INFO) << " use_guess: " << use_guess << "\n"; + LOG(WARNING) << "WARNING: This application overwrites existing files and does not previous values!"; //LOG(INFO) << " use_guess_distortion: " << use_guess_distortion; LOG(INFO) << "-----------------------------------"; diff --git a/applications/ftl2mkv/src/main.cpp b/applications/ftl2mkv/src/main.cpp index 598e372ca524d33e358c1f312fdeba5429fa0ea8..3e6b0e302bd7411f492a3a6eb9fbdc708dbe6fba 100644 --- a/applications/ftl2mkv/src/main.cpp +++ b/applications/ftl2mkv/src/main.cpp @@ -181,7 +181,7 @@ int main(int argc, char **argv) { bool keyframe = false; if (pkt.codec == codec_t::HEVC) { - if (ftl::codecs::hevc::isIFrame(pkt.data)) { + if (ftl::codecs::hevc::isIFrame(pkt.data.data(), pkt.data.size())) { seen_key[spkt.streamID] = true; keyframe = true; } diff --git a/applications/groupview/src/main.cpp b/applications/groupview/src/main.cpp index be99d857562bfb48ec120cccccf0737ff2d0b210..f9d763fb607d595f67f089220921f7e92f51ffff 100644 --- a/applications/groupview/src/main.cpp +++ b/applications/groupview/src/main.cpp @@ -88,7 +88,7 @@ void modeLeftRight(ftl::Configurable *root) { std::atomic<bool> new_frames = false; vector<Mat> rgb(n_cameras), rgb_new(n_cameras); - group.sync([&mutex, &new_frames, &rgb_new](ftl::rgbd::FrameSet &frames) { + group.onFrameSet([&mutex, &new_frames, &rgb_new](ftl::rgbd::FrameSet &frames) { mutex.lock(); bool good = true; for (size_t i = 0; i < frames.frames.size(); i ++) { @@ -179,7 +179,7 @@ void modeFrame(ftl::Configurable *root, int frames=1) { vector<cv::Mat> depth(sources.size()); MUTEX mtx; - group.sync([&grab,&rgb,&depth,&mtx](ftl::rgbd::FrameSet &fs) { + group.onFrameSet([&grab,&rgb,&depth,&mtx](ftl::rgbd::FrameSet &fs) { UNIQUE_LOCK(mtx, lk); //LOG(INFO) << "Complete set: " << fs.timestamp; if (!ftl::running) { return false; } diff --git a/applications/gui/CMakeLists.txt b/applications/gui/CMakeLists.txt index 9764984a7b430c4bc256918aac2b39e356a1ee16..65d7964d5cf891e2b288251a43c4c8936f4199e0 100644 --- a/applications/gui/CMakeLists.txt +++ b/applications/gui/CMakeLists.txt @@ -32,6 +32,6 @@ target_include_directories(ftl-gui PUBLIC #endif() #target_include_directories(cv-node PUBLIC ${PROJECT_SOURCE_DIR}/include) -target_link_libraries(ftl-gui ftlcommon ftlctrl ftlrgbd Threads::Threads ${OpenCV_LIBS} ${OPENVR_LIBRARIES} glog::glog ftlnet nanogui GL) +target_link_libraries(ftl-gui ftlcommon ftlctrl ftlrgbd ftlstreams ftlrender Threads::Threads ${OpenCV_LIBS} ${OPENVR_LIBRARIES} glog::glog ftlnet nanogui GL) diff --git a/applications/gui/src/camera.cpp b/applications/gui/src/camera.cpp index 3259bfa336fc62603d240488bf2db90e21c2ef63..3924410d4c4c1056634c01fe19efd9123c80ffe2 100644 --- a/applications/gui/src/camera.cpp +++ b/applications/gui/src/camera.cpp @@ -131,7 +131,7 @@ static Eigen::Affine3d create_rotation_matrix(float ax, float ay, float az) { return rz * rx * ry; } -ftl::gui::Camera::Camera(ftl::gui::Screen *screen, ftl::rgbd::Source *src) : screen_(screen), src_(src) { +ftl::gui::Camera::Camera(ftl::gui::Screen *screen, int fsid, int fid, ftl::codecs::Channel c) : screen_(screen), fsid_(fsid), fid_(fid), channel_(c),channels_(0u) { eye_ = Eigen::Vector3d(0.0f, 0.0f, 0.0f); neye_ = Eigen::Vector4d(0.0f, 0.0f, 0.0f, 0.0f); rotmat_.setIdentity(); @@ -140,24 +140,34 @@ ftl::gui::Camera::Camera(ftl::gui::Screen *screen, ftl::rgbd::Source *src) : scr sdepth_ = false; ftime_ = (float)glfwGetTime(); pause_ = false; - fileout_ = new std::ofstream(); - writer_ = new ftl::codecs::Writer(*fileout_); + //fileout_ = new std::ofstream(); + /*writer_ = new ftl::codecs::Writer(*fileout_); recorder_ = std::function([this](ftl::rgbd::Source *src, const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) { ftl::codecs::StreamPacket s = spkt; writer_->write(s, pkt); - }); + });*/ + recording_ = false; - channel_ = Channel::Left; +#ifdef HAVE_OPENVR + vr_mode_ = false; +#endif - channels_ += Channel::Left; - channels_ += Channel::Depth; + //channel_ = Channel::Left; - // Create pose window... - posewin_ = new PoseWindow(screen, src_->getURI()); - posewin_->setTheme(screen->windowtheme); - posewin_->setVisible(false); + channels_ += c; + //channels_ += Channel::Depth; + width_ = 0; + height_ = 0; - src->setCallback([this](int64_t ts, ftl::rgbd::Frame &frame) { + // Create pose window... + //posewin_ = new PoseWindow(screen, src_->getURI()); + //posewin_->setTheme(screen->windowtheme); + //posewin_->setVisible(false); + posewin_ = nullptr; + renderer_ = nullptr; + record_stream_ = nullptr; + + /*src->setCallback([this](int64_t ts, ftl::rgbd::Frame &frame) { UNIQUE_LOCK(mutex_, lk); auto &channel1 = frame.get<GpuMat>(Channel::Colour); @@ -173,16 +183,120 @@ ftl::gui::Camera::Camera(ftl::gui::Screen *screen, ftl::rgbd::Source *src) : scr channel2.download(im2_); cv::flip(im2_, im2_, 0); } - }); + });*/ + + auto *host = screen->root(); + + // Is virtual camera? + if (fid == 255) { + state_.getLeft().width = host->value("width", 1280); + state_.getLeft().height = host->value("height", 720); + state_.getLeft().fx = host->value("focal", 700.0f); + state_.getLeft().fy = state_.getLeft().fx; + state_.getLeft().cx = -(double)state_.getLeft().width / 2.0; + state_.getLeft().cy = -(double)state_.getLeft().height / 2.0; + state_.getLeft().minDepth = host->value("minDepth", 0.1f); + state_.getLeft().maxDepth = host->value("maxDepth", 20.0f); + state_.getLeft().doffs = 0; + state_.getLeft().baseline = host->value("baseline", 0.05f); + + state_.getRight().width = host->value("width", 1280); + state_.getRight().height = host->value("height", 720); + state_.getRight().fx = host->value("focal_right", 700.0f); + state_.getRight().fy = state_.getRight().fx; + state_.getRight().cx = host->value("centre_x_right", -(double)state_.getLeft().width / 2.0); + state_.getRight().cy = host->value("centre_y_right", -(double)state_.getLeft().height / 2.0); + state_.getRight().minDepth = host->value("minDepth", 0.1f); + state_.getRight().maxDepth = host->value("maxDepth", 20.0f); + state_.getRight().doffs = 0; + state_.getRight().baseline = host->value("baseline", 0.05f); + + Eigen::Matrix4d pose; + pose.setIdentity(); + state_.setPose(pose); + } } ftl::gui::Camera::~Camera() { - delete writer_; - delete fileout_; + //delete writer_; + //delete fileout_; +} + +void ftl::gui::Camera::draw(ftl::rgbd::FrameSet &fs) { + UNIQUE_LOCK(mutex_, lk); + _draw(fs); } -ftl::rgbd::Source *ftl::gui::Camera::source() { - return src_; +void ftl::gui::Camera::_draw(ftl::rgbd::FrameSet &fs) { + frame_.reset(); + frame_.setOrigin(&state_); + if (!renderer_) renderer_ = ftl::create<ftl::render::Triangular>(screen_->root(), "vcam1"); + Eigen::Matrix4d t; + t.setIdentity(); + renderer_->render(fs, frame_, channel_, t); + _downloadFrames(&frame_); + + if (record_stream_ && record_stream_->active()) { + // TODO: Allow custom channel selection + ftl::rgbd::FrameSet fs2; + auto &f = fs2.frames.emplace_back(); + fs2.count = 1; + fs2.mask = 1; + fs2.stale = false; + frame_.swapTo(Channels<0>(Channel::Colour), f); // Channel::Colour + Channel::Depth + fs2.timestamp = fs.timestamp; + fs2.id = 0; + record_sender_->post(fs2); + record_stream_->select(0, Channels<0>(Channel::Colour)); + f.swapTo(Channels<0>(Channel::Colour), frame_); + } +} + +void ftl::gui::Camera::_downloadFrames(ftl::rgbd::Frame *frame) { + if (!frame) return; + + auto &channel1 = frame->get<GpuMat>(Channel::Colour); + im1_.create(channel1.size(), channel1.type()); + channel1.download(im1_); + + // OpenGL (0,0) bottom left + cv::flip(im1_, im1_, 0); + + width_ = im1_.cols; + height_ = im1_.rows; + + if (channel_ != Channel::Colour && channel_ != Channel::None && frame->hasChannel(channel_)) { + auto &channel2 = frame->get<GpuMat>(channel_); + im2_.create(channel2.size(), channel2.type()); + channel2.download(im2_); + //LOG(INFO) << "Have channel2: " << im2_.type() << ", " << im2_.size(); + cv::flip(im2_, im2_, 0); + } +} + +void ftl::gui::Camera::update(ftl::rgbd::FrameSet &fs) { + UNIQUE_LOCK(mutex_, lk); + + ftl::rgbd::Frame *frame = nullptr; + + if (fid_ == 255) { + name_ = "Virtual Camera"; + // Do a draw if not active. If active the draw function will be called + // directly. + //if (screen_->activeCamera() != this) { + _draw(fs); + //} + } else { + if (fid_ >= fs.frames.size()) return; + frame = &fs.frames[fid_]; + _downloadFrames(frame); + auto n = frame->get<std::string>("name"); + if (n) { + name_ = *n; + } else { + name_ = "No name"; + } + } } void ftl::gui::Camera::setPose(const Eigen::Matrix4d &p) { @@ -211,7 +325,8 @@ void ftl::gui::Camera::setPose(const Eigen::Matrix4d &p) { } void ftl::gui::Camera::mouseMovement(int rx, int ry, int button) { - if (!src_->hasCapabilities(ftl::rgbd::kCapMovable)) return; + //if (!src_->hasCapabilities(ftl::rgbd::kCapMovable)) return; + if (fid_ < 255) return; if (button == 1) { float rrx = ((float)ry * 0.2f * delta_); //orientation_[2] += std::cos(orientation_[1])*((float)rel[1] * 0.2f * delta_); @@ -225,7 +340,8 @@ void ftl::gui::Camera::mouseMovement(int rx, int ry, int button) { } void ftl::gui::Camera::keyMovement(int key, int modifiers) { - if (!src_->hasCapabilities(ftl::rgbd::kCapMovable)) return; + //if (!src_->hasCapabilities(ftl::rgbd::kCapMovable)) return; + if (fid_ < 255) return; if (key == 263 || key == 262) { float mag = (modifiers & 0x1) ? 0.01f : 0.1f; float scalar = (key == 263) ? -mag : mag; @@ -254,28 +370,39 @@ void ftl::gui::Camera::showSettings() { #ifdef HAVE_OPENVR bool ftl::gui::Camera::setVR(bool on) { + if (on == vr_mode_) { LOG(WARNING) << "VR mode already enabled"; return on; } + vr_mode_ = on; if (on) { setChannel(Channel::Right); - src_->set("baseline", baseline_); + //src_->set("baseline", baseline_); + state_.getLeft().baseline = baseline_; Eigen::Matrix3d intrinsic; intrinsic = getCameraMatrix(screen_->getVR(), vr::Eye_Left); CHECK(intrinsic(0, 2) < 0 && intrinsic(1, 2) < 0); - src_->set("focal", intrinsic(0, 0)); - src_->set("centre_x", intrinsic(0, 2)); - src_->set("centre_y", intrinsic(1, 2)); + //src_->set("focal", intrinsic(0, 0)); + //src_->set("centre_x", intrinsic(0, 2)); + //src_->set("centre_y", intrinsic(1, 2)); + state_.getLeft().fx = intrinsic(0,0); + state_.getLeft().fy = intrinsic(0,0); + state_.getLeft().cx = intrinsic(0,2); + state_.getLeft().cy = intrinsic(1,2); intrinsic = getCameraMatrix(screen_->getVR(), vr::Eye_Right); CHECK(intrinsic(0, 2) < 0 && intrinsic(1, 2) < 0); - src_->set("focal_right", intrinsic(0, 0)); - src_->set("centre_x_right", intrinsic(0, 2)); - src_->set("centre_y_right", intrinsic(1, 2)); + //src_->set("focal_right", intrinsic(0, 0)); + //src_->set("centre_x_right", intrinsic(0, 2)); + //src_->set("centre_y_right", intrinsic(1, 2)); + state_.getRight().fx = intrinsic(0,0); + state_.getRight().fy = intrinsic(0,0); + state_.getRight().cx = intrinsic(0,2); + state_.getRight().cy = intrinsic(1,2); vr_mode_ = true; } @@ -284,7 +411,7 @@ bool ftl::gui::Camera::setVR(bool on) { setChannel(Channel::Left); // reset to left channel // todo restore camera params } - + return vr_mode_; } #endif @@ -298,7 +425,7 @@ void ftl::gui::Camera::setChannel(Channel c) { #endif channel_ = c; - switch (c) { + /*switch (c) { case Channel::Energy: case Channel::Density: case Channel::Flow: @@ -319,7 +446,8 @@ void ftl::gui::Camera::setChannel(Channel c) { break; default: src_->setChannel(Channel::None); - } + }*/ + // FIXME: Somehow send channel request... } static void visualizeDepthMap( const cv::Mat &depth, cv::Mat &out, @@ -385,9 +513,10 @@ cv::Mat ftl::gui::Camera::visualizeActiveChannel() { bool ftl::gui::Camera::thumbnail(cv::Mat &thumb) { UNIQUE_LOCK(mutex_, lk); - src_->grab(1,9); - if (im1_.empty()) return false; - cv::resize(im1_, thumb, cv::Size(320,180)); + /*src_->grab(1,9);*/ + cv::Mat sel = (channel_ != Channel::None && channel_ != Channel::Colour && !im2_.empty()) ? visualizeActiveChannel() : im1_; + if (sel.empty()) return false; + cv::resize(sel, thumb, cv::Size(320,180)); cv::flip(thumb, thumb, 0); return true; } @@ -397,7 +526,8 @@ const GLTexture &ftl::gui::Camera::captureFrame() { delta_ = now - ftime_; ftime_ = now; - if (src_ && src_->isReady()) { + //if (src_ && src_->isReady()) { + if (width_ > 0 && height_ > 0) { UNIQUE_LOCK(mutex_, lk); if (screen_->isVR()) { @@ -417,7 +547,8 @@ const GLTexture &ftl::gui::Camera::captureFrame() { if (baseline_in != baseline_) { baseline_ = baseline_in; - src_->set("baseline", baseline_); + //src_->set("baseline", baseline_); + state_.getLeft().baseline = baseline_; } Eigen::Matrix4d pose = ConvertSteamVRMatrixToMatrix4(rTrackedDevicePose_[vr::k_unTrackedDeviceIndex_Hmd].mDeviceToAbsoluteTracking); Eigen::Vector3d ea = pose.block<3, 3>(0, 0).eulerAngles(0, 1, 2); @@ -448,9 +579,9 @@ const GLTexture &ftl::gui::Camera::captureFrame() { Eigen::Affine3d t(trans); Eigen::Matrix4d viewPose = t.matrix() * rotmat_; - if (src_->hasCapabilities(ftl::rgbd::kCapMovable)) src_->setPose(viewPose); + if (isVirtual()) state_.setPose(viewPose); - src_->grab(); + //src_->grab(); // When switching from right to depth, client may still receive // right images from previous batch (depth.channels() == 1 check) @@ -484,7 +615,7 @@ const GLTexture &ftl::gui::Camera::captureFrame() { break; case Channel::Depth: - if (im2_.rows == 0) { break; } + if (im2_.rows == 0 || im2_.type() != CV_32F) { break; } visualizeDepthMap(im2_, tmp, 7.0); if (screen_->root()->value("showEdgesInDepth", false)) drawEdges(im1_, tmp); texture2_.update(tmp); @@ -533,6 +664,7 @@ void ftl::gui::Camera::snapshot(const std::string &filename) { UNIQUE_LOCK(mutex_, lk); cv::Mat blended; cv::Mat visualized = visualizeActiveChannel(); + if (!visualized.empty()) { double alpha = screen_->root()->value("blending", 0.5); cv::addWeighted(im1_, alpha, visualized, 1.0-alpha, 0, blended); @@ -541,23 +673,26 @@ void ftl::gui::Camera::snapshot(const std::string &filename) { } cv::Mat flipped; cv::flip(blended, flipped, 0); + cv::cvtColor(flipped, flipped, cv::COLOR_BGRA2BGR); cv::imwrite(filename, flipped); } void ftl::gui::Camera::startVideoRecording(const std::string &filename) { - fileout_->open(filename); + if (!record_stream_) { + record_stream_ = ftl::create<ftl::stream::File>(screen_->root(), "video2d"); + record_stream_->setMode(ftl::stream::File::Mode::Write); + record_sender_ = ftl::create<ftl::stream::Sender>(screen_->root(), "videoEncode"); + record_sender_->setStream(record_stream_); + } - writer_->begin(); - src_->addRawCallback(recorder_); + if (record_stream_->active()) return; - src_->inject(Channel::Calibration, src_->parameters(), Channel::Left, src_->getCapabilities()); - src_->inject(src_->getPose()); + record_stream_->set("filename", filename); + record_stream_->begin(); } void ftl::gui::Camera::stopVideoRecording() { - src_->removeRawCallback(recorder_); - writer_->end(); - fileout_->close(); + if (record_stream_ && record_stream_->active()) record_stream_->end(); } nlohmann::json ftl::gui::Camera::getMetaData() { diff --git a/applications/gui/src/camera.hpp b/applications/gui/src/camera.hpp index 90914cf599942107f8d7bbba8b8f5a9505f815e9..9146d3cce1f7384c503f6cda69390fdd40570001 100644 --- a/applications/gui/src/camera.hpp +++ b/applications/gui/src/camera.hpp @@ -1,10 +1,14 @@ #ifndef _FTL_GUI_CAMERA_HPP_ #define _FTL_GUI_CAMERA_HPP_ -#include <ftl/rgbd/source.hpp> +#include <ftl/rgbd/frameset.hpp> +#include <ftl/render/tri_render.hpp> #include <ftl/codecs/writer.hpp> #include "gltexture.hpp" +#include <ftl/streams/filestream.hpp> +#include <ftl/streams/sender.hpp> + #include <string> #ifdef HAVE_OPENVR @@ -21,15 +25,13 @@ class PoseWindow; class Camera { public: - Camera(ftl::gui::Screen *screen, ftl::rgbd::Source *src); + Camera(ftl::gui::Screen *screen, int fsid, int fid, ftl::codecs::Channel chan=ftl::codecs::Channel::Colour); ~Camera(); Camera(const Camera &)=delete; - ftl::rgbd::Source *source(); - - int width() { return (src_) ? src_->parameters().width : 0; } - int height() { return (src_) ? src_->parameters().height : 0; } + int width() const { return width_; } + int height() const { return height_; } void setPose(const Eigen::Matrix4d &p); @@ -44,7 +46,20 @@ class Camera { void togglePause(); void isPaused(); - const ftl::codecs::Channels &availableChannels() { return channels_; } + inline bool isVirtual() const { return fid_ == 255; } + const ftl::codecs::Channels<0> &availableChannels() { return channels_; } + + /** + * Main function to obtain latest frames. + */ + void update(ftl::rgbd::FrameSet &fs); + + /** + * Update the available channels. + */ + void update(const ftl::codecs::Channels<0> &c) { channels_ = c; } + + void draw(ftl::rgbd::FrameSet &fs); const GLTexture &captureFrame(); const GLTexture &getLeft() const { return texture1_; } @@ -60,6 +75,8 @@ class Camera { nlohmann::json getMetaData(); + const std::string &name() const { return name_; } + StatisticsImage *stats_ = nullptr; @@ -74,7 +91,13 @@ class Camera { cv::Mat visualizeActiveChannel(); Screen *screen_; - ftl::rgbd::Source *src_; + int fsid_; + int fid_; + // TODO: Renderer + + int width_; + int height_; + GLTexture thumb_; GLTexture texture1_; // first channel (always left at the moment) GLTexture texture2_; // second channel ("right") @@ -91,13 +114,23 @@ class Camera { bool sdepth_; bool pause_; ftl::codecs::Channel channel_; - ftl::codecs::Channels channels_; + ftl::codecs::Channels<0> channels_; cv::Mat im1_; // first channel (left) cv::Mat im2_; // second channel ("right") + + // FIXME: Recording will now be broken? bool recording_; - std::ofstream *fileout_; - ftl::codecs::Writer *writer_; - ftl::rgbd::RawCallback recorder_; + //std::ofstream *fileout_; + //ftl::codecs::Writer *writer_; + //ftl::rgbd::RawCallback recorder_; + + ftl::render::Triangular *renderer_; + ftl::rgbd::Frame frame_; + ftl::rgbd::FrameState state_; + ftl::stream::File *record_stream_; + ftl::stream::Sender *record_sender_; + + std::string name_; MUTEX mutex_; @@ -106,6 +139,9 @@ class Camera { bool vr_mode_; float baseline_; #endif + + void _downloadFrames(ftl::rgbd::Frame *frame); + void _draw(ftl::rgbd::FrameSet &fs); }; } diff --git a/applications/gui/src/main.cpp b/applications/gui/src/main.cpp index 99af829eb646f20f1e0f680837e8855739fa6be1..cd52e88ba5510e3cdb8f4fca67261cc51558c42a 100644 --- a/applications/gui/src/main.cpp +++ b/applications/gui/src/main.cpp @@ -31,6 +31,8 @@ int main(int argc, char **argv) { std::cout << " -- " << a << std::endl; }*/ + ftl::timer::start(); + try { nanogui::init(); diff --git a/applications/gui/src/media_panel.cpp b/applications/gui/src/media_panel.cpp index c2fa4285ebd433a8ac67a09eb0475e80f860100b..d7e012c5204cbab76e13bde9eed5925a46fa94ff 100644 --- a/applications/gui/src/media_panel.cpp +++ b/applications/gui/src/media_panel.cpp @@ -14,11 +14,12 @@ using ftl::gui::MediaPanel; using ftl::codecs::Channel; -MediaPanel::MediaPanel(ftl::gui::Screen *screen, ftl::gui::SourceWindow *sourceWindow) : nanogui::Window(screen, ""), screen_(screen) { +MediaPanel::MediaPanel(ftl::gui::Screen *screen, ftl::gui::SourceWindow *sourceWindow) : nanogui::Window(screen, ""), screen_(screen), sourceWindow_(sourceWindow) { using namespace nanogui; paused_ = false; disable_switch_channels_ = false; + record_mode_ = RecordMode::None; setLayout(new BoxLayout(Orientation::Horizontal, Alignment::Middle, 5, 10)); @@ -34,9 +35,6 @@ MediaPanel::MediaPanel(ftl::gui::Screen *screen, ftl::gui::SourceWindow *sourceW if (cam) cam->showPoseWindow(); }); - virtualCameraRecording_ = std::optional<ftl::gui::Camera*>(); - sceneRecording_ = std::optional<ftl::Configurable*>(); - recordbutton_ = new PopupButton(this, "", ENTYPO_ICON_CONTROLLER_RECORD); recordbutton_->setTooltip("Record"); recordbutton_->setSide(Popup::Side::Right); @@ -47,36 +45,23 @@ MediaPanel::MediaPanel(ftl::gui::Screen *screen, ftl::gui::SourceWindow *sourceW recordpopup->setAnchorHeight(150); auto itembutton = new Button(recordpopup, "2D snapshot (.png)"); itembutton->setCallback([this]() { - char timestamp[18]; - std::time_t t=std::time(NULL); - std::strftime(timestamp, sizeof(timestamp), "%F-%H%M%S", std::localtime(&t)); - screen_->activeCamera()->snapshot(std::string(timestamp) + ".png"); + _startRecording(RecordMode::Snapshot2D); recordbutton_->setPushed(false); }); itembutton = new Button(recordpopup, "Virtual camera recording (.ftl)"); itembutton->setCallback([this]() { - char timestamp[18]; - std::time_t t=std::time(NULL); - std::strftime(timestamp, sizeof(timestamp), "%F-%H%M%S", std::localtime(&t)); - auto filename = std::string(timestamp) + ".ftl"; - startRecording2D(screen_->activeCamera(), filename); + _startRecording(RecordMode::Video2D); recordbutton_->setTextColor(nanogui::Color(1.0f,0.1f,0.1f,1.0f)); recordbutton_->setPushed(false); }); itembutton = new Button(recordpopup, "3D scene snapshot (.ftl)"); itembutton->setCallback([this]() { - char timestamp[18]; - std::time_t t=std::time(NULL); - std::strftime(timestamp, sizeof(timestamp), "%F-%H%M%S", std::localtime(&t)); - snapshot3D(screen_->activeCamera(), std::string(timestamp) + ".ftl"); + _startRecording(RecordMode::Snapshot3D); recordbutton_->setPushed(false); }); itembutton = new Button(recordpopup, "3D scene recording (.ftl)"); itembutton->setCallback([this]() { - char timestamp[18]; - std::time_t t=std::time(NULL); - std::strftime(timestamp, sizeof(timestamp), "%F-%H%M%S", std::localtime(&t)); - startRecording3D(screen_->activeCamera(), std::string(timestamp) + ".ftl"); + _startRecording(RecordMode::Video3D); recordbutton_->setTextColor(nanogui::Color(1.0f,0.1f,0.1f,1.0f)); recordbutton_->setPushed(false); }); @@ -89,19 +74,10 @@ MediaPanel::MediaPanel(ftl::gui::Screen *screen, ftl::gui::SourceWindow *sourceW }); recordbutton_->setCallback([this](){ - if (virtualCameraRecording_) { - virtualCameraRecording_.value()->stopVideoRecording(); + if (record_mode_ != RecordMode::None) { + _stopRecording(); recordbutton_->setTextColor(nanogui::Color(1.0f,1.0f,1.0f,1.0f)); - - // Prevents the popup from being opened, though it is shown while the button - // is being pressed. recordbutton_->setPushed(false); - virtualCameraRecording_ = std::nullopt; - } else if (sceneRecording_) { - sceneRecording_.value()->set("record", false); - recordbutton_->setTextColor(nanogui::Color(1.0f,1.0f,1.0f,1.0f)); - recordbutton_->setPushed(false); - sceneRecording_ = std::nullopt; } }); @@ -121,39 +97,10 @@ MediaPanel::MediaPanel(ftl::gui::Screen *screen, ftl::gui::SourceWindow *sourceW button->setIcon(ENTYPO_ICON_CONTROLLER_PAUS); } }); - - //button = new Button(this, "", ENTYPO_ICON_CONTROLLER_RECORD); - - /* Doesn't work at the moment - #ifdef HAVE_LIBARCHIVE - auto button_snapshot = new Button(this, "", ENTYPO_ICON_IMAGES); - button_snapshot->setTooltip("Screen capture"); - button_snapshot->setCallback([this] { - ftl::gui::Camera *cam = screen_->activeCamera(); - if (!cam) return; - - try { - char timestamp[18]; - std::time_t t=std::time(NULL); - std::strftime(timestamp, sizeof(timestamp), "%F-%H%M%S", std::localtime(&t)); - auto writer = ftl::rgbd::SnapshotWriter(std::string(timestamp) + ".tar.gz"); - cv::Mat rgb, depth; - cam->source()->getFrames(rgb, depth); - writer.addSource( cam->source()->getURI(), - cam->source()->parameters(), - cam->source()->getPose()); - writer.addRGBD(0, rgb, depth); - } - catch(std::runtime_error) { - LOG(ERROR) << "Snapshot failed (file error)"; - } - }); -#endif - // not very useful (l/r) - auto button_dual = new Button(this, "", ENTYPO_ICON_MAP); + /*auto button_dual = new Button(this, "", ENTYPO_ICON_MAP); button_dual->setCallback([this]() { screen_->setDualView(!screen_->getDualView()); }); @@ -188,33 +135,20 @@ MediaPanel::MediaPanel(ftl::gui::Screen *screen, ftl::gui::SourceWindow *sourceW popup->setTheme(screen->toolbuttheme); popup->setAnchorHeight(150); - button = new Button(popup, "Left"); - button->setFlags(Button::RadioButton); - button->setPushed(true); - button->setCallback([this]() { - ftl::gui::Camera *cam = screen_->activeCamera(); - if (cam) { - cam->setChannel(Channel::Left); - } - }); - - right_button_ = new Button(popup, "Right"); - right_button_->setFlags(Button::RadioButton); - right_button_->setCallback([this]() { - ftl::gui::Camera *cam = screen_->activeCamera(); - if (cam) { - cam->setChannel(Channel::Right); - } - }); - - depth_button_ = new Button(popup, "Depth"); - depth_button_->setFlags(Button::RadioButton); - depth_button_->setCallback([this]() { - ftl::gui::Camera *cam = screen_->activeCamera(); - if (cam) { - cam->setChannel(Channel::Depth); - } - }); + for (int i=0; i<=2; ++i) { + ftl::codecs::Channel c = static_cast<ftl::codecs::Channel>(i); + button = new Button(popup, ftl::codecs::name(c)); + button->setFlags(Button::RadioButton); + //button->setPushed(true); + button->setVisible(false); + button->setCallback([this,c]() { + ftl::gui::Camera *cam = screen_->activeCamera(); + if (cam) { + cam->setChannel(c); + } + }); + channel_buttons_[i] = button; + } auto *popbutton = new PopupButton(popup, "More"); popbutton->setSide(Popup::Side::Right); @@ -224,110 +158,75 @@ MediaPanel::MediaPanel(ftl::gui::Screen *screen, ftl::gui::SourceWindow *sourceW popup->setTheme(screen->toolbuttheme); popup->setAnchorHeight(150); - button = new Button(popup, "Deviation"); - button->setFlags(Button::RadioButton); - button->setCallback([this]() { - ftl::gui::Camera *cam = screen_->activeCamera(); - if (cam) { - cam->setChannel(Channel::Deviation); - } - }); - - button = new Button(popup, "Normals"); - button->setFlags(Button::RadioButton); - button->setCallback([this]() { - ftl::gui::Camera *cam = screen_->activeCamera(); - if (cam) { - cam->setChannel(Channel::ColourNormals); - } - }); - - button = new Button(popup, "Flow"); - button->setFlags(Button::RadioButton); - button->setCallback([this]() { - ftl::gui::Camera *cam = screen_->activeCamera(); - if (cam) { - cam->setChannel(Channel::Flow); - } - }); + for (int i=3; i<32; ++i) { + ftl::codecs::Channel c = static_cast<ftl::codecs::Channel>(i); + button = new Button(popup, ftl::codecs::name(c)); + button->setFlags(Button::RadioButton); + //button->setPushed(true); + button->setVisible(false); + button->setCallback([this,c]() { + ftl::gui::Camera *cam = screen_->activeCamera(); + if (cam) { + cam->setChannel(c); + } + }); + channel_buttons_[i] = button; + } +} - button = new Button(popup, "Confidence"); - button->setFlags(Button::RadioButton); - button->setCallback([this]() { - ftl::gui::Camera *cam = screen_->activeCamera(); - if (cam) { - cam->setChannel(Channel::Confidence); - } - }); +MediaPanel::~MediaPanel() { - button = new Button(popup, "Energy"); - button->setFlags(Button::RadioButton); - button->setCallback([this]() { - ftl::gui::Camera *cam = screen_->activeCamera(); - if (cam) { - cam->setChannel(Channel::Energy); - } - }); +} - button = new Button(popup, "Density"); - button->setFlags(Button::RadioButton); - button->setCallback([this]() { - ftl::gui::Camera *cam = screen_->activeCamera(); - if (cam) { - cam->setChannel(Channel::Density); - } - }); +void MediaPanel::_startRecording(MediaPanel::RecordMode mode) { + char timestamp[18]; + std::time_t t=std::time(NULL); + std::strftime(timestamp, sizeof(timestamp), "%F-%H%M%S", std::localtime(&t)); + + std::string filename(timestamp); + switch(mode) { + case RecordMode::Snapshot2D : filename += ".png"; break; + case RecordMode::Snapshot3D : + case RecordMode::Video3D : filename += ".ftl"; break; + case RecordMode::Video2D : filename += ".ftl"; break; + } + if (mode == RecordMode::Video3D) { + record_mode_ = mode; + sourceWindow_->recordVideo(filename); + } else if (mode == RecordMode::Snapshot2D) { + screen_->activeCamera()->snapshot(filename); + } else if (mode == RecordMode::Video2D) { + record_mode_ = mode; + screen_->activeCamera()->startVideoRecording(filename); + } } -MediaPanel::~MediaPanel() { - +void MediaPanel::_stopRecording() { + if (record_mode_ == RecordMode::Video3D) { + sourceWindow_->stopRecordingVideo(); + } else if (record_mode_ == RecordMode::Video2D) { + screen_->activeCamera()->stopVideoRecording(); + } + record_mode_ = RecordMode::None; } // Update button enabled status void MediaPanel::cameraChanged() { ftl::gui::Camera *cam = screen_->activeCamera(); if (cam) { - if (cam->source()->hasCapabilities(ftl::rgbd::kCapStereo)) { - right_button_->setEnabled(true); - } else { - right_button_->setEnabled(false); - } - } -} - -void MediaPanel::startRecording2D(ftl::gui::Camera *camera, const std::string &filename) { - camera->startVideoRecording(filename); - virtualCameraRecording_ = std::optional<ftl::gui::Camera*>(camera); - recordbutton_->setTextColor(nanogui::Color(1.0f,0.1f,0.1f,1.0f)); -} - -void MediaPanel::snapshot3D(ftl::gui::Camera *camera, const std::string &filename) { - auto tag = camera->source()->get<std::string>("uri"); - if (tag) { - auto tagvalue = tag.value(); - auto configurables = ftl::config::findByTag(tagvalue); - if (configurables.size() > 0) { - ftl::Configurable *configurable = ftl::config::find(configurables[0]->getID() + "/controls"); - if (configurable) { - configurable->set("3D-snapshot", filename); + auto channels = cam->availableChannels(); + for (int i=0; i<32; ++i) { + if (channels.has(static_cast<ftl::codecs::Channel>(i))) { + channel_buttons_[i]->setVisible(true); + } else { + channel_buttons_[i]->setVisible(false); } - } - } -} -void MediaPanel::startRecording3D(ftl::gui::Camera *camera, const std::string &filename) { - auto tag = camera->source()->get<std::string>("uri"); - if (tag) { - auto tagvalue = tag.value(); - auto configurables = ftl::config::findByTag(tagvalue); - if (configurables.size() > 0) { - ftl::Configurable *configurable = ftl::config::find(configurables[0]->getID() + "/controls"); - if (configurable) { - configurable->set("record-name", filename); - configurable->set("record", true); - sceneRecording_ = std::optional<ftl::Configurable*>(configurable); - recordbutton_->setTextColor(nanogui::Color(1.0f,0.1f,0.1f,1.0f)); + if (cam->getChannel() == static_cast<ftl::codecs::Channel>(i)) { + channel_buttons_[i]->setPushed(true); + } else { + channel_buttons_[i]->setPushed(false); } } } diff --git a/applications/gui/src/media_panel.hpp b/applications/gui/src/media_panel.hpp index 211b84c6e0beecdcfd5fd51cb3f413e628d31d87..4cf69b5ba204d268ad9143b582d5a71a6d407c67 100644 --- a/applications/gui/src/media_panel.hpp +++ b/applications/gui/src/media_panel.hpp @@ -7,6 +7,8 @@ #include "src_window.hpp" +#include <array> + namespace ftl { namespace rgbd { @@ -24,25 +26,39 @@ class MediaPanel : public nanogui::Window { void cameraChanged(); - void startRecording2D(ftl::gui::Camera *camera, const std::string &filename); + //void startRecording2D(ftl::gui::Camera *camera, const std::string &filename); - void snapshot3D(ftl::gui::Camera *camera, const std::string &filename); + //void snapshot3D(ftl::gui::Camera *camera, const std::string &filename); - void startRecording3D(ftl::gui::Camera *camera, const std::string &filename); + //void startRecording3D(ftl::gui::Camera *camera, const std::string &filename); void recordWindowClosed(); private: ftl::gui::Screen *screen_; + ftl::gui::SourceWindow *sourceWindow_; bool paused_; bool disable_switch_channels_; ftl::rgbd::SnapshotStreamWriter *writer_; nanogui::PopupButton *button_channels_; - nanogui::Button *right_button_; - nanogui::Button *depth_button_; + //nanogui::Button *right_button_; + //nanogui::Button *depth_button_; nanogui::PopupButton *recordbutton_; + std::array<nanogui::Button*,32> channel_buttons_={}; + + enum class RecordMode { + None, + Snapshot2D, + Snapshot3D, + Video2D, + Video3D + }; + RecordMode record_mode_; + + void _startRecording(RecordMode mode); + void _stopRecording(); /** * These members indicate which type of recording is active, if any. @@ -50,8 +66,8 @@ class MediaPanel : public nanogui::Window { * to end the recording. Only one of these members should have a value * at any given time. */ - std::optional<ftl::gui::Camera*> virtualCameraRecording_; - std::optional<ftl::Configurable*> sceneRecording_; + //std::optional<ftl::gui::Camera*> virtualCameraRecording_; + //std::optional<ftl::Configurable*> sceneRecording_; }; } diff --git a/applications/gui/src/record_window.cpp b/applications/gui/src/record_window.cpp index 87fbb9dc0e50562abe10b932b05f728f4e24208e..f74fd61aa65b392fc07e361a94dbead1bd7cd183 100644 --- a/applications/gui/src/record_window.cpp +++ b/applications/gui/src/record_window.cpp @@ -33,11 +33,14 @@ RecordWindow::RecordWindow(nanogui::Widget *parent, ftl::gui::Screen *screen, co auto streamNames = std::vector<std::string>(); streamNames.reserve(streams.size()); std::optional<int> ix; + int i=1; for (const auto s : streams) { if (s == screen->activeCamera()) { ix = std::optional<int>(streamNames.size()); } - streamNames.push_back(s->source()->getURI()); + // FIXME: Find alternative to source URI + //streamNames.push_back(s->source()->getURI()); + streamNames.push_back(std::string("Stream")+std::to_string(i++)); } auto streamSelect = new ComboBox(this, streamNames); // TODO: The function availableChannels() only finds those channels that @@ -103,11 +106,11 @@ RecordWindow::RecordWindow(nanogui::Widget *parent, ftl::gui::Screen *screen, co } else if (tab == recording2D) { stream->setChannel(channels_[recordingChannel->selectedIndex()]); screen->setActiveCamera(stream); - media_panel->startRecording2D(stream, name); + //media_panel->startRecording2D(stream, name); } else if (tab == snapshot3D) { - media_panel->snapshot3D(stream, name); + //media_panel->snapshot3D(stream, name); } else if (tab == recording3D) { - media_panel->startRecording3D(stream, name); + //media_panel->startRecording3D(stream, name); } dispose(); media_panel->recordWindowClosed(); diff --git a/applications/gui/src/scene.hpp b/applications/gui/src/scene.hpp new file mode 100644 index 0000000000000000000000000000000000000000..2e2b4b89fee844ea35905a51df6ee3a424f9ef4c --- /dev/null +++ b/applications/gui/src/scene.hpp @@ -0,0 +1,25 @@ +#ifndef _FTL_GUI_SCENE_HPP_ +#define _FTL_GUI_SCENE_HPP_ + +#include <ftl/streams/receiver.hpp> + +namespace ftl { +namespace gui { + +class Camera; + +class Scene { + public: + explicit Scene(ftl::stream::Receiver *); + ~Scene(); + + inline const std::vector<ftl::gui::Camera*> cameras() const { return cameras_; }; + + private: + std::vector<ftl::gui::Camera*> cameras_; +}; + +} +} + +#endif // _FTL_GUI_SCENE_HPP_ diff --git a/applications/gui/src/screen.cpp b/applications/gui/src/screen.cpp index aa2d5e36e7c9f4dfa29cd6206efac2110f4e7d36..787d209919ef339eb71e46bda70a371d461eec8a 100644 --- a/applications/gui/src/screen.cpp +++ b/applications/gui/src/screen.cpp @@ -363,7 +363,8 @@ void ftl::gui::Screen::setActiveCamera(ftl::gui::Camera *cam) { camera_ = cam; if (cam) { - status_ = cam->source()->getURI(); + // FIXME: Alternative to source URI + status_ = cam->name(); mwindow_->setVisible(true); mwindow_->cameraChanged(); swindow_->setVisible(false); diff --git a/applications/gui/src/src_window.cpp b/applications/gui/src/src_window.cpp index 6b930c110bd21280eb1df1395cb11149d3980fe8..bb2105ce95b4b39786d57171430c50eff8b9f65b 100644 --- a/applications/gui/src/src_window.cpp +++ b/applications/gui/src/src_window.cpp @@ -2,6 +2,7 @@ #include "screen.hpp" #include "camera.hpp" +#include "scene.hpp" #include <nanogui/imageview.h> #include <nanogui/textbox.h> @@ -14,6 +15,13 @@ #include <nanogui/layout.h> #include <nanogui/vscrollpanel.h> +#include <ftl/streams/netstream.hpp> + +#include "ftl/operators/colours.hpp" +#include "ftl/operators/segmentation.hpp" +#include "ftl/operators/mask.hpp" +#include "ftl/operators/antialiasing.hpp" + #ifdef HAVE_LIBARCHIVE #include "ftl/rgbd/snapshot.hpp" #endif @@ -22,7 +30,9 @@ using ftl::gui::SourceWindow; using ftl::gui::Screen; +using ftl::gui::Scene; using ftl::rgbd::Source; +using ftl::codecs::Channel; using std::string; using std::vector; using ftl::config::json_t; @@ -33,31 +43,12 @@ SourceWindow::SourceWindow(ftl::gui::Screen *screen) using namespace nanogui; - //if (!screen->root()->get<json_t>("sources")) { - // screen->root()->getConfig()["sources"] = json_t::array(); - //} - - //src_ = ftl::create<Source>(ctrl->getRoot(), "source", ctrl->getNet()); - - //Widget *tools = new Widget(this); - // tools->setLayout(new BoxLayout(Orientation::Horizontal, - // Alignment::Middle, 0, 6)); - new Label(this, "Select Camera","sans-bold",20); - //auto select = new ComboBox(this, available_); - //select->setCallback([this,select](int ix) { - //src_->set("uri", available_[ix]); - // TODO(Nick) Check camera exists first - // screen_->setActiveCamera(cameras_[available_[ix]]); - // LOG(INFO) << "Change source: " << ix; - //}); - auto vscroll = new VScrollPanel(this); ipanel_ = new Widget(vscroll); ipanel_->setLayout(new GridLayout(nanogui::Orientation::Horizontal, 2, nanogui::Alignment::Middle, 0, 5)); - //ipanel_ = new ImageView(vscroll, 0); screen->net()->onConnect([this](ftl::net::Peer *p) { UNIQUE_LOCK(mutex_, lk); @@ -65,50 +56,185 @@ SourceWindow::SourceWindow(ftl::gui::Screen *screen) }); UNIQUE_LOCK(mutex_, lk); + stream_ = ftl::create<ftl::stream::Muxer>(screen->root(), "muxer"); + interceptor_ = ftl::create<ftl::stream::Intercept>(screen->root(), "intercept"); + interceptor_->setStream(stream_); + receiver_ = ftl::create<ftl::stream::Receiver>(screen->root(), "receiver"); + receiver_->setStream(interceptor_); - std::vector<ftl::rgbd::Source*> srcs = ftl::createArray<ftl::rgbd::Source>(screen_->root(), "sources", screen_->net()); - for (auto *src : srcs) { - available_.push_back(src->getURI()); - } + // Create a recorder + recorder_ = ftl::create<ftl::stream::File>(screen->root(), "recorder"); + recorder_->setMode(ftl::stream::File::Mode::Write); + + interceptor_->onIntercept([this] (const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) { + //LOG(INFO) << (std::string)spkt; + if (recorder_->active() && pkt.data.size() > 0) recorder_->post(spkt, pkt); + }); + + pre_pipeline_ = ftl::config::create<ftl::operators::Graph>(screen->root(), "pre_filters"); + //pre_pipeline_->append<ftl::operators::ColourChannels>("colour"); // Convert BGR to BGRA + pre_pipeline_->append<ftl::operators::CrossSupport>("cross"); + pre_pipeline_->append<ftl::operators::DiscontinuityMask>("discontinuity"); + pre_pipeline_->append<ftl::operators::CullDiscontinuity>("remove_discontinuity"); + pre_pipeline_->append<ftl::operators::VisCrossSupport>("viscross")->set("enabled", false); + + cycle_ = 0; + receiver_->onFrameSet([this](ftl::rgbd::FrameSet &fs) { + // Request the channels required by current camera configuration + interceptor_->select(fs.id, _aggregateChannels()); + + const auto *cstream = interceptor_; + _createDefaultCameras(fs, cstream->available(fs.id).has(Channel::Depth)); + + //LOG(INFO) << "Channels = " << (unsigned int)cstream->available(fs.id); + + // Enforce interpolated colour + for (int i=0; i<fs.frames.size(); ++i) { + fs.frames[i].createTexture<uchar4>(Channel::Colour, true); + } + + pre_pipeline_->apply(fs, fs, 0); + + int i=0; + for (auto cam : cameras_) { + // Only update the camera periodically unless the active camera + if (screen_->activeCamera() == cam.second.camera || + (screen_->activeCamera() == nullptr && cycle_ % cameras_.size() == i++)) cam.second.camera->update(fs); + + cam.second.camera->update(cstream->available(fs.id)); + } + ++cycle_; + + return true; + }); _updateCameras(screen_->control()->getNet()->findAll<string>("list_streams")); + + // Also check for a file on command line. + // Check paths for FTL files to load. + auto paths = (*screen->root()->get<nlohmann::json>("paths")); + + for (auto &x : paths.items()) { + std::string path = x.value().get<std::string>(); + auto eix = path.find_last_of('.'); + auto ext = path.substr(eix+1); + + // Command line path is ftl file + if (ext == "ftl") { + LOG(INFO) << "Found FTL file: " << path; + auto *fstream = ftl::create<ftl::stream::File>(screen->root(), "ftlfile"); + fstream->set("filename", path); + stream_->add(fstream); + } + } + + stream_->begin(); +} + +void SourceWindow::recordVideo(const std::string &filename) { + if (!recorder_->active()) { + recorder_->set("filename", filename); + recorder_->begin(); + LOG(INFO) << "Recording started: " << filename; + + // TODO: Inject pose and calibrations + } +} + +void SourceWindow::stopRecordingVideo() { + if (recorder_->active()) { + recorder_->end(); + LOG(INFO) << "Recording stopped."; + } +} + +ftl::codecs::Channels<0> SourceWindow::_aggregateChannels() { + ftl::codecs::Channels<0> cs = ftl::codecs::Channels<0>(Channel::Colour); + for (auto cam : cameras_) { + if (cam.second.camera->isVirtual()) { + cs += Channel::Depth; + } else { + if (cam.second.camera->getChannel() != Channel::None) { + cs += cam.second.camera->getChannel(); + } + } + } + return cs; +} + +void SourceWindow::_createDefaultCameras(ftl::rgbd::FrameSet &fs, bool makevirtual) { + for (int i=0; i<fs.frames.size(); ++i) { + int id = i; // TODO: Include frameset id + if (cameras_.find(id) == cameras_.end()) { + auto *cam = new ftl::gui::Camera(screen_, 0, i); + cameras_[id] = { + cam, + nullptr + }; + } + } + + if (makevirtual && cameras_.find(-1) == cameras_.end()) { + auto *cam = new ftl::gui::Camera(screen_, 0, 255); + cameras_[-1] = { + cam, + nullptr + }; + } } std::vector<ftl::gui::Camera*> SourceWindow::getCameras() { auto cameras = std::vector<ftl::gui::Camera*>(); cameras.reserve(cameras_.size()); - for (const auto &kv : cameras_) { - cameras.push_back(kv.second); + + for (auto cam : cameras_) { + cameras.push_back(cam.second.camera); } return cameras; } void SourceWindow::_updateCameras(const vector<string> &netcams) { + if (netcams.size() == 0) return; + for (auto s : netcams) { - if (cameras_.find(s) == cameras_.end()) { + // FIXME: Check for already existing... + //if (streams_.find(s) == cameras_.end()) { available_.push_back(s); json_t srcjson; srcjson["uri"] = s; - screen_->root()->getConfig()["sources"].push_back(srcjson); - } + screen_->root()->getConfig()["streams"].push_back(srcjson); + //screen_->root()->getConfig()["receivers"].push_back(json_t{}); + //} } - std::vector<ftl::rgbd::Source*> srcs = ftl::createArray<ftl::rgbd::Source>(screen_->root(), "sources", screen_->net()); - for (auto *src : srcs) { - if (cameras_.find(src->getURI()) == cameras_.end()) { + std::vector<ftl::stream::Net*> strms = ftl::createArray<ftl::stream::Net>(screen_->root(), "streams", screen_->net()); + + for (int i=0; i<strms.size(); ++i) { + auto *stream = strms[i]; + stream_->add(stream); + + LOG(INFO) << "Add Stream: " << stream->value("uri", std::string("NONE")); + + //Scene *scene = new Scene(receiver); + //scenes_.push_back(scene); + + /*if (.find(src->getURI()) == cameras_.end()) { LOG(INFO) << "Making camera: " << src->getURI(); + // TODO: Need to have GUI wrapper for an entire stream... which + // manages a set of cameras. + auto *cam = new ftl::gui::Camera(screen_, src); cameras_[src->getURI()] = cam; } else { //LOG(INFO) << "Camera already exists: " << s; - } + }*/ } - refresh_thumbs_ = true; - if (thumbs_.size() != available_.size()) { - thumbs_.resize(available_.size()); - } + //refresh_thumbs_ = true; + //if (thumbs_.size() != available_.size()) { + // thumbs_.resize(available_.size()); + //} } SourceWindow::~SourceWindow() { @@ -116,31 +242,36 @@ SourceWindow::~SourceWindow() { } void SourceWindow::draw(NVGcontext *ctx) { - if (refresh_thumbs_) { + //if (refresh_thumbs_) { UNIQUE_LOCK(mutex_, lk); //refresh_thumbs_ = false; - for (size_t i=0; i<thumbs_.size(); ++i) { + if (thumbs_.size() < cameras_.size()) thumbs_.resize(cameras_.size()); + + //for (size_t i=0; i<thumbs_.size(); ++i) { + int i = 0; + for (auto &camera : cameras_) { cv::Mat t; - auto *cam = cameras_[available_[i]]; + auto *cam = camera.second.camera; if (cam) { if (cam->thumbnail(t)) { thumbs_[i].update(t); - } else { - refresh_thumbs_ = true; } } - if ((size_t)ipanel_->childCount() < i+1) { + if (!camera.second.thumbview) camera.second.thumbview = new ftl::gui::ThumbView(ipanel_, screen_, cam); + + /*if ((size_t)ipanel_->childCount() < i+1) { new ftl::gui::ThumbView(ipanel_, screen_, cam); - } - if (thumbs_[i].isValid()) dynamic_cast<nanogui::ImageView*>(ipanel_->childAt(i))->bindImage(thumbs_[i].texture()); + }*/ + if (thumbs_[i].isValid()) dynamic_cast<nanogui::ImageView*>(camera.second.thumbview)->bindImage(thumbs_[i].texture()); + ++i; } // TODO(Nick) remove excess image views center(); - } + //} nanogui::Window::draw(ctx); } diff --git a/applications/gui/src/src_window.hpp b/applications/gui/src/src_window.hpp index dab3f5d4301de73d0dda2bbd32b321c3f796d160..5e8164954d090e089f99bf75514891f2c1f51221 100644 --- a/applications/gui/src/src_window.hpp +++ b/applications/gui/src/src_window.hpp @@ -12,14 +12,28 @@ #include <string> #include "gltexture.hpp" +#include <ftl/streams/stream.hpp> +#include <ftl/streams/receiver.hpp> +#include <ftl/streams/filestream.hpp> + class VirtualCameraView; namespace ftl { namespace gui { class Screen; +class Scene; class Camera; +class ThumbView; +/** + * Main class for managing all data streams and corresponding cameras. It + * will automatically locate all available streams and generate default cameras + * for each frame of each stream found. It will also add a single default + * virtual camera. Additional cameras can be added. This class directly + * receives all frameset data and then forwards it to the individual cameras + * for drawing/rendering. + */ class SourceWindow : public nanogui::Window { public: explicit SourceWindow(ftl::gui::Screen *screen); @@ -29,16 +43,34 @@ class SourceWindow : public nanogui::Window { virtual void draw(NVGcontext *ctx); + void recordVideo(const std::string &filename); + void stopRecordingVideo(); + private: ftl::gui::Screen *screen_; - std::map<std::string, ftl::gui::Camera*> cameras_; + + struct CameraEntry { + ftl::gui::Camera *camera; + ftl::gui::ThumbView *thumbview; + //GLTexture thumb; + }; + + std::map<int, CameraEntry> cameras_; + ftl::stream::Muxer *stream_; + ftl::stream::Intercept *interceptor_; + ftl::stream::File *recorder_; + ftl::stream::Receiver *receiver_; std::vector<std::string> available_; std::vector<GLTexture> thumbs_; bool refresh_thumbs_; nanogui::Widget *ipanel_; + int cycle_; + ftl::operators::Graph *pre_pipeline_; MUTEX mutex_; void _updateCameras(const std::vector<std::string> &netcams); + void _createDefaultCameras(ftl::rgbd::FrameSet &fs, bool makevirtual); + ftl::codecs::Channels<0> _aggregateChannels(); }; diff --git a/applications/gui/src/thumbview.cpp b/applications/gui/src/thumbview.cpp index 2870985873ab2a87e921e4376fabf66f965348ce..6c567e516482785dbaa0d7d69aa08bef3ed1a8e4 100644 --- a/applications/gui/src/thumbview.cpp +++ b/applications/gui/src/thumbview.cpp @@ -29,6 +29,6 @@ void ThumbView::draw(NVGcontext *ctx) { nvgScissor(ctx, mPos.x(), mPos.y(), mSize.x(), mSize.y()); nvgFontSize(ctx, 14); nvgFontFace(ctx, "sans-bold"); - nvgText(ctx, mPos.x() + 10, mPos.y()+mSize.y() - 10, cam_->source()->getURI().c_str(), NULL); + //nvgText(ctx, mPos.x() + 10, mPos.y()+mSize.y() - 10, cam_->source()->getURI().c_str(), NULL); nvgResetScissor(ctx); } diff --git a/applications/reconstruct/CMakeLists.txt b/applications/reconstruct/CMakeLists.txt index 5dc0ef5dbce517fb88cdf0fb0cc9fdc7b84a778a..b039e8466ee5374a33daa29716ec1c04c5fd2619 100644 --- a/applications/reconstruct/CMakeLists.txt +++ b/applications/reconstruct/CMakeLists.txt @@ -15,11 +15,11 @@ set(REPSRC #src/mls.cu #src/depth_camera.cu #src/depth_camera.cpp - src/ilw/ilw.cpp - src/ilw/ilw.cu - src/ilw/fill.cu - src/ilw/discontinuity.cu - src/ilw/correspondence.cu + #src/ilw/ilw.cpp + #src/ilw/ilw.cu + #src/ilw/fill.cu + #src/ilw/discontinuity.cu + #src/ilw/correspondence.cu src/reconstruction.cpp ) @@ -37,6 +37,6 @@ set_property(TARGET ftl-reconstruct PROPERTY CUDA_SEPARABLE_COMPILATION ON) endif() #target_include_directories(cv-node PUBLIC ${PROJECT_SOURCE_DIR}/include) -target_link_libraries(ftl-reconstruct ftlcommon ftlrgbd Threads::Threads ${OpenCV_LIBS} ftlctrl ftlnet ftlrender ftloperators) +target_link_libraries(ftl-reconstruct ftlcommon ftlrgbd Threads::Threads ${OpenCV_LIBS} ftlctrl ftlnet ftlrender ftloperators ftlstreams) diff --git a/applications/reconstruct/src/main.cpp b/applications/reconstruct/src/main.cpp index 5cce7a5a236699a58e74a1044dfef35a008bf72e..8108a6b5d74a9b7effcd75df4f6651d1249102e4 100644 --- a/applications/reconstruct/src/main.cpp +++ b/applications/reconstruct/src/main.cpp @@ -48,6 +48,11 @@ #include <ftl/codecs/h264.hpp> #include <ftl/codecs/hevc.hpp> +#include <ftl/streams/filestream.hpp> +#include <ftl/streams/receiver.hpp> +#include <ftl/streams/sender.hpp> +#include <ftl/streams/netstream.hpp> + #include <cuda_profiler_api.h> #ifdef WIN32 @@ -64,10 +69,6 @@ using ftl::codecs::Channel; using json = nlohmann::json; using std::this_thread::sleep_for; using std::chrono::milliseconds; -//using std::mutex; -//using std::unique_lock; - -//using cv::Mat; using ftl::registration::loadTransformations; using ftl::registration::saveTransformations; @@ -82,6 +83,57 @@ static Eigen::Affine3d create_rotation_matrix(float ax, float ay, float az) { return rz * rx * ry; } +/* Build a generator using a deprecated list of source objects. */ +static ftl::rgbd::Generator *createSourceGenerator(const std::vector<ftl::rgbd::Source*> &srcs) { + // Must find pose for each source... + if (srcs.size() > 1) { + std::map<std::string, Eigen::Matrix4d> transformations; + + if (loadTransformations(string(FTL_LOCAL_CONFIG_ROOT) + "/registration.json", transformations)) { + LOG(INFO) << "Loaded camera trasformations from file"; + } + else { + LOG(ERROR) << "Error loading camera transformations from file"; + } + + for (auto &input : srcs) { + string uri = input->getURI(); + + auto T = transformations.find(uri); + if (T == transformations.end()) { + LOG(WARNING) << "Camera pose for " + uri + " not found in transformations"; + //LOG(WARNING) << "Using only first configured source"; + // TODO: use target source if configured and found + //sources = { sources[0] }; + //sources[0]->setPose(Eigen::Matrix4d::Identity()); + //break; + input->setPose(input->getPose()); + continue; + } + input->setPose(T->second); + } + } + + auto *grp = new ftl::rgbd::Group(); + for (auto s : srcs) { + s->setChannel(Channel::Depth); + grp->addSource(s); + } + return grp; +} + +static ftl::rgbd::Generator *createFileGenerator(ftl::Configurable *root, const std::string &filename) { + ftl::stream::File *stream = ftl::create<ftl::stream::File>(root, "player"); + stream->set("filename", filename); + + ftl::stream::Receiver *gen = ftl::create<ftl::stream::Receiver>(root, "receiver"); + gen->setStream(stream); + + stream->begin(); + stream->select(0, Channel::Colour + Channel::Depth); // TODO: Choose these elsewhere + return gen; +} + static void run(ftl::Configurable *root) { Universe *net = ftl::create<Universe>(root, "net"); ftl::ctrl::Master ctrl(root, net); @@ -92,19 +144,34 @@ static void run(ftl::Configurable *root) { net->start(); net->waitConnections(); - std::vector<int> sourcecounts; - - // Add sources from the configuration file as a single group. - auto configuration_sources = root->getConfig()["sources"]; - size_t configuration_size = configuration_sources.size(); - if (configuration_size > 0) { - sourcecounts.push_back(configuration_size); - } + vector<ftl::Reconstruction*> groups; ftl::codecs::Channels channels; + ftl::stream::Sender *sender = ftl::create<ftl::stream::Sender>(root, "sender"); + ftl::stream::Net *outstream = ftl::create<ftl::stream::Net>(root, "stream", net); + outstream->set("uri", "ftl://test.utu.fi"); + outstream->begin(); + sender->setStream(outstream); + + std::vector<Source*> sources; + // Create a vector of all input RGB-Depth sources + if (root->getConfig()["sources"].size() > 0) { + sources = ftl::createArray<Source>(root, "sources", net); + auto *gen = createSourceGenerator(sources); + auto reconstr = ftl::create<ftl::Reconstruction>(root, "0", "0"); + reconstr->setGenerator(gen); + reconstr->onFrameSet([sender](ftl::rgbd::FrameSet &fs) { + fs.id = 0; + sender->post(fs); + return true; + }); + groups.push_back(reconstr); + } + // Check paths for FTL files to load. auto paths = (*root->get<nlohmann::json>("paths")); + int i = groups.size(); for (auto &x : paths.items()) { std::string path = x.value().get<std::string>(); auto eix = path.find_last_of('.'); @@ -127,220 +194,58 @@ static void run(ftl::Configurable *root) { LOG(INFO) << "Found " << (max_stream+1) << " sources in " << path; - int N = root->value("N", 100); - - // For each stream found, add a source object - int count = min(max_stream+1, N); - for (int i=0; i<count; ++i) { - root->getConfig()["sources"].push_back(nlohmann::json{{"uri",std::string("file://") + path + std::string("#") + std::to_string(i)}}); - } - sourcecounts.push_back(count); + auto *gen = createFileGenerator(root, path); + auto reconstr = ftl::create<ftl::Reconstruction>(root, std::string("recon")+std::to_string(i), std::to_string(i)); + reconstr->setGenerator(gen); + reconstr->onFrameSet([sender,i](ftl::rgbd::FrameSet &fs) { + fs.id = i; + sender->post(fs); + return true; + }); + groups.push_back(reconstr); + ++i; } } - if (channels.empty()) channels |= Channel::Depth; - - // Create a vector of all input RGB-Depth sources - auto sources = ftl::createArray<Source>(root, "sources", net); - - if (sources.size() == 0) { + if (groups.size() == 0) { LOG(ERROR) << "No sources configured!"; - return; - } - - // Must find pose for each source... - if (sources.size() > 1) { - std::map<std::string, Eigen::Matrix4d> transformations; - - if (loadTransformations(string(FTL_LOCAL_CONFIG_ROOT) + "/registration.json", transformations)) { - LOG(INFO) << "Loaded camera trasformations from file"; - } - else { - LOG(ERROR) << "Error loading camera transformations from file"; - } - - for (auto &input : sources) { - string uri = input->getURI(); - - auto T = transformations.find(uri); - if (T == transformations.end()) { - LOG(WARNING) << "Camera pose for " + uri + " not found in transformations"; - //LOG(WARNING) << "Using only first configured source"; - // TODO: use target source if configured and found - //sources = { sources[0] }; - //sources[0]->setPose(Eigen::Matrix4d::Identity()); - //break; - input->setPose(input->getPose()); - continue; - } - input->setPose(T->second); - } - } - ftl::rgbd::FrameSet fs_out; + auto stream_uris = net->findAll<std::string>("list_streams"); - //ftl::voxhash::SceneRep *scene = ftl::create<ftl::voxhash::SceneRep>(root, "voxelhash"); - ftl::rgbd::Streamer *stream = ftl::create<ftl::rgbd::Streamer>(root, "stream", net); - ftl::rgbd::VirtualSource *vs = ftl::create<ftl::rgbd::VirtualSource>(root, "virtual"); - auto tags = root->value<std::vector<std::string>>("tags", nlohmann::json::array({})); - tags.push_back(root->getID()+"/virtual"); - root->set("tags", tags); + if (stream_uris.size() > 0) { + ftl::stream::Muxer *stream = ftl::create<ftl::stream::Muxer>(root, "muxstream"); + ftl::stream::Receiver *gen = ftl::create<ftl::stream::Receiver>(root, "receiver"); + gen->setStream(stream); - int o = root->value("origin_pose", 0) % sources.size(); - vs->setPose(sources[o]->getPose()); - - vector<ftl::Reconstruction*> groups; - - size_t cumulative = 0; - for (auto c : sourcecounts) { - std::string id = std::to_string(cumulative); - auto reconstr = ftl::create<ftl::Reconstruction>(root, id, id); - for (size_t i=cumulative; i<cumulative+c; i++) { - if (channels.has(Channel::Depth)) { - sources[i]->setChannel(Channel::Depth); - } else if (channels.has(Channel::Right)) { - sources[i]->setChannel(Channel::Right); + int count = 0; + for (auto &s : stream_uris) { + LOG(INFO) << " --- found stream: " << s; + auto *nstream = ftl::create<ftl::stream::Net>(stream, std::to_string(count), net); + nstream->set("uri", s); + stream->add(nstream); + ++count; } - reconstr->addSource(sources[i]); - } - groups.push_back(reconstr); - cumulative += c; - } - - auto *renderpipe = ftl::config::create<ftl::operators::Graph>(root, "render_pipe"); - renderpipe->append<ftl::operators::ColourChannels>("colour"); // Generate interpolation texture... - renderpipe->append<ftl::operators::FXAA>("antialiasing"); - vs->onRender([vs, &groups, &renderpipe](ftl::rgbd::Frame &out) { - bool hasFrame = false; - for (auto &reconstr : groups) { - hasFrame = reconstr->render(vs, out) || hasFrame; - } + auto reconstr = ftl::create<ftl::Reconstruction>(root, std::string("recon")+std::to_string(i), std::to_string(i)); + //reconstr->setGenerator(gen); + gen->onFrameSet([stream, reconstr](ftl::rgbd::FrameSet &fs) { + stream->select(fs.id, Channel::Colour + Channel::Depth); + return reconstr->post(fs); + }); - if (hasFrame) { - renderpipe->apply(out, out, vs, 0); - return true; + int i = groups.size(); + reconstr->onFrameSet([sender,i](ftl::rgbd::FrameSet &fs) { + fs.id = i; + sender->post(fs); + return true; + }); + groups.push_back(reconstr); + stream->begin(); } else { - LOG(INFO) << "NO FRAME"; - return false; + return; } - }); - stream->add(vs); - - for (auto c : ftl::config::getChildren(root->getID())) { - LOG(INFO) << "Tagging configurable: " << c->getID(); - auto tags = c->value<std::vector<std::string>>("tags", nlohmann::json::array({})); - tags.push_back("reconstruction"); - c->set("tags", tags); } - // ---- Recording code ----------------------------------------------------- - std::ofstream fileout; - ftl::codecs::Writer writer(fileout); - - std::ofstream snapshotout; - ftl::codecs::Writer snapshotwriter(snapshotout); - - controls->set("record", false); - - int64_t timestamp = -1; - bool writingSnapshot = false; - std::unordered_set<int64_t> precedingFrames, followingFrames; - // Add a recording callback to all reconstruction scenes - for (size_t i=0; i<sources.size(); ++i) { - sources[i]->addRawCallback([&writer,&groups,&snapshotout,&snapshotwriter,×tamp,&writingSnapshot,&precedingFrames,&followingFrames,i](ftl::rgbd::Source *src, const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) { - ftl::codecs::StreamPacket s = spkt; - - // Patch stream ID to match order in group - s.streamID = i; - writer.write(s, pkt); - - if (snapshotwriter.active()) { - // The frame that is captured is the next IFrame, unless that - // IFrame is one of the first two frames seen. In this case a - // part of the frame might already have been missed, so the - // IFrame after that one is captured instead. - - // Write all pose and calibration packets. - if ((int)spkt.channel >= 64) { - snapshotwriter.write(s, pkt); - } else if (precedingFrames.size() >= 2) { - bool isIFrame = true; - switch (pkt.codec) { - case ftl::codecs::codec_t::H264: - isIFrame = ftl::codecs::h264::isIFrame(pkt.data); - break; - case ftl::codecs::codec_t::HEVC: - isIFrame = ftl::codecs::hevc::isIFrame(pkt.data); - } - - if (isIFrame && precedingFrames.count(s.timestamp) == 0) { - timestamp = s.timestamp; - writingSnapshot = true; - snapshotwriter.write(s, pkt); - } else if (writingSnapshot && s.timestamp > timestamp) { - followingFrames.insert(s.timestamp); - } - - // Keep looking for packets of the captured frame until - // packets from two following frames have been seen. - if (followingFrames.size() >= 2) { - snapshotwriter.end(); - snapshotout.close(); - } - } else { - precedingFrames.insert(s.timestamp); - } - } - }); - } - - // Allow stream recording - controls->on("record", [&fileout,&writer,&sources](const ftl::config::Event &e) { - if (e.entity->value("record", false)) { - char timestamp[18]; - std::time_t t=std::time(NULL); - std::strftime(timestamp, sizeof(timestamp), "%F-%H%M%S", std::localtime(&t)); - fileout.open(e.entity->value<std::string>("record-name", std::string(timestamp) + ".ftl")); - - writer.begin(); - - // TODO: Write pose+calibration+config packets - - for (size_t i=0; i<sources.size(); ++i) { - //writeSourceProperties(writer, i, sources[i]); - sources[i]->inject(Channel::Calibration, sources[i]->parameters(), Channel::Left, sources[i]->getCapabilities()); - sources[i]->inject(sources[i]->getPose()); - } - } else { - writer.end(); - fileout.close(); - } - }); - - controls->on("3D-snapshot", [&snapshotout,&snapshotwriter,&writingSnapshot,&precedingFrames,&followingFrames,&sources](const ftl::config::Event &e) { - if (!snapshotwriter.active()) { - char timestamp[18]; - std::time_t t=std::time(NULL); - std::strftime(timestamp, sizeof(timestamp), "%F-%H%M%S", std::localtime(&t)); - snapshotout.open(e.entity->value<std::string>("3D-snapshot", std::string(timestamp) + ".ftl")); - writingSnapshot = false; - precedingFrames.clear(); - followingFrames.clear(); - snapshotwriter.begin(); - - for (size_t i=0; i<sources.size(); ++i) { - //writeSourceProperties(writer, i, sources[i]); - sources[i]->inject(Channel::Calibration, sources[i]->parameters(), Channel::Left, sources[i]->getCapabilities()); - sources[i]->inject(sources[i]->getPose()); - } - } - }); - - // ------------------------------------------------------------------------- - - //stream->add(group); - stream->run(); LOG(INFO) << "Start timer"; ftl::timer::start(true); @@ -355,8 +260,8 @@ static void run(ftl::Configurable *root) { LOG(INFO) << "Deleting..."; - delete stream; - delete vs; + delete sender; + delete outstream; delete net; for (auto g : groups) { delete g; diff --git a/applications/reconstruct/src/reconstruction.cpp b/applications/reconstruct/src/reconstruction.cpp index 621693f0ec2d1ea9b1b734e107aa1689b84872db..11e3ca6537ef2a1e989270406750be0e4f7a2ca5 100644 --- a/applications/reconstruct/src/reconstruction.cpp +++ b/applications/reconstruct/src/reconstruction.cpp @@ -26,11 +26,9 @@ static Eigen::Affine3d create_rotation_matrix(float ax, float ay, float az) { Reconstruction::Reconstruction(nlohmann::json &config, const std::string name) : ftl::Configurable(config), busy_(false), rbusy_(false), new_frame_(false), fs_render_(), fs_align_() { - group_ = new ftl::rgbd::Group; - group_->setName("Reconstruction-" + name); - group_->setLatency(4); + gen_ = nullptr; - renderer_ = ftl::create<ftl::render::Triangular>(this, "renderer", &fs_render_); + //renderer_ = ftl::create<ftl::render::Triangular>(this, "renderer", &fs_render_); pipeline_ = ftl::config::create<ftl::operators::Graph>(this, "pre_filters"); pipeline_->append<ftl::operators::DepthChannel>("depth"); // Ensure there is a depth channel @@ -45,70 +43,82 @@ Reconstruction::Reconstruction(nlohmann::json &config, const std::string name) : pipeline_->append<ftl::operators::CrossSupport>("cross"); pipeline_->append<ftl::operators::DiscontinuityMask>("discontinuity"); pipeline_->append<ftl::operators::CrossSupport>("cross2")->set("discon_support", true); - pipeline_->append<ftl::operators::CullDiscontinuity>("remove_discontinuity"); + pipeline_->append<ftl::operators::CullDiscontinuity>("remove_discontinuity")->set("enabled", false); //pipeline_->append<ftl::operators::AggreMLS>("mls"); // Perform MLS (using smoothing channel) pipeline_->append<ftl::operators::VisCrossSupport>("viscross")->set("enabled", false); pipeline_->append<ftl::operators::MultiViewMLS>("mvmls"); - group_->sync([this](ftl::rgbd::FrameSet &fs) -> bool { - // TODO: pause - - /*if (busy_) { - LOG(WARNING) << "Group frameset dropped: " << fs.timestamp; - return true; - } - busy_ = true;*/ + //pipeline_->set("enabled", false); +} - // Swap the entire frameset to allow rapid return - // FIXME: This gets stuck on a mutex - //fs.swapTo(fs_align_); +Reconstruction::~Reconstruction() { + // TODO delete +} - //ftl::pool.push([this](int id) { - //UNIQUE_LOCK(fs.mtx, lk); +size_t Reconstruction::size() { + UNIQUE_LOCK(exchange_mtx_, lk); + return fs_align_.frames.size(); +} - pipeline_->apply(fs, fs, 0); - - // TODO: To use second GPU, could do a download, swap, device change, - // then upload to other device. Or some direct device-2-device copy. - // FIXME: Need a buffer of framesets - /*if (rbusy_) { - LOG(WARNING) << "Render frameset dropped: " << fs_align_.timestamp; - busy_ = false; - //return; - } - rbusy_ = true;*/ +ftl::rgbd::FrameState &Reconstruction::state(int ix) { + UNIQUE_LOCK(exchange_mtx_, lk); + if (ix >= 0 && ix < fs_align_.frames.size()) { + return *fs_align_.frames[ix].origin(); + } + throw ftl::exception("State index out-of-bounds"); +} - //LOG(INFO) << "Align complete... " << fs.timestamp; +void Reconstruction::onFrameSet(const ftl::rgbd::VideoCallback &cb) { + cb_ = cb; +} - // TODO: Reduce mutex locking problem here... - { - UNIQUE_LOCK(exchange_mtx_, lk); - if (new_frame_ == true) LOG(WARNING) << "Frame lost"; - fs.swapTo(fs_align_); - new_frame_ = true; - } +bool Reconstruction::post(ftl::rgbd::FrameSet &fs) { + pipeline_->apply(fs, fs, 0); + + { + UNIQUE_LOCK(exchange_mtx_, lk); + if (new_frame_ == true) LOG(WARNING) << "Frame lost"; + fs.swapTo(fs_align_); + new_frame_ = true; + } - //fs.resetFull(); + if (cb_) { + ftl::pool.push([this](int id) { + if (new_frame_) { + { + UNIQUE_LOCK(exchange_mtx_, lk); + new_frame_ = false; + fs_align_.swapTo(fs_render_); + } - //busy_ = false; - //}); - return true; - }); + if (cb_) cb_(fs_render_); + } + }); + } + + return true; } -Reconstruction::~Reconstruction() { - // TODO delete +void Reconstruction::setGenerator(ftl::rgbd::Generator *gen) { + if (gen_) { + throw ftl::exception("Reconstruction already has generator"); + } + + gen_ = gen; + gen_->onFrameSet([this](ftl::rgbd::FrameSet &fs) -> bool { + return post(fs); + }); } -void Reconstruction::addSource(ftl::rgbd::Source *src) { +/*void Reconstruction::addSource(ftl::rgbd::Source *src) { //src->setChannel(Channel::Depth); group_->addSource(src); // TODO: check if source is already in group? } void Reconstruction::addRawCallback(const std::function<void(ftl::rgbd::Source *src, const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt)> &cb) { group_->addRawCallback(cb); -} +}*/ bool Reconstruction::render(ftl::rgbd::VirtualSource *vs, ftl::rgbd::Frame &out) { { @@ -145,7 +155,7 @@ bool Reconstruction::render(ftl::rgbd::VirtualSource *vs, ftl::rgbd::Frame &out) //} Eigen::Affine3d sm = Eigen::Affine3d(Eigen::Scaling(double(value("scale", 1.0f)))); - bool res = renderer_->render(vs, out, sm.matrix() * transform); + bool res = false; //renderer_->render(vs, out, sm.matrix() * transform); //fs_render_.resetFull(); return res; } \ No newline at end of file diff --git a/applications/reconstruct/src/reconstruction.hpp b/applications/reconstruct/src/reconstruction.hpp index cb3bb8ebd4c6cca57e1f678b155fa40e95fbcfae..5c2648b3e975209239ac0f2ab38ce9bae014e8cf 100644 --- a/applications/reconstruct/src/reconstruction.hpp +++ b/applications/reconstruct/src/reconstruction.hpp @@ -11,20 +11,36 @@ namespace ftl { -class Reconstruction : public ftl::Configurable { +class Reconstruction : public ftl::Configurable, public ftl::rgbd::Generator { public: Reconstruction(nlohmann::json &config, const std::string name); ~Reconstruction(); - void addSource(ftl::rgbd::Source *); + //void addSource(ftl::rgbd::Source *); - void addRawCallback(const std::function<void(ftl::rgbd::Source *src, const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt)> &cb); + //void addRawCallback(const std::function<void(ftl::rgbd::Source *src, const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt)> &cb); + + void setGenerator(ftl::rgbd::Generator *); /** * Do the render for a specified virtual camera. */ bool render(ftl::rgbd::VirtualSource *vs, ftl::rgbd::Frame &out); + /** Number of frames in last frameset. This can change over time. */ + size_t size() override; + + /** + * Get the persistent state object for a frame. An exception is thrown + * for a bad index. + */ + ftl::rgbd::FrameState &state(int ix) override; + + /** Register a callback to receive new frame sets. */ + void onFrameSet(const ftl::rgbd::VideoCallback &) override; + + bool post(ftl::rgbd::FrameSet &fs); + private: bool busy_; bool rbusy_; @@ -33,10 +49,12 @@ class Reconstruction : public ftl::Configurable { ftl::rgbd::FrameSet fs_render_; ftl::rgbd::FrameSet fs_align_; - ftl::rgbd::Group *group_; + ftl::rgbd::Generator *gen_; ftl::operators::Graph *pipeline_; ftl::render::Triangular *renderer_; + ftl::rgbd::VideoCallback cb_; + std::vector<cv::cuda::GpuMat> rgb_; }; diff --git a/applications/recorder/src/main.cpp b/applications/recorder/src/main.cpp index d2001114f8f98c4bf35630c9676651bf6e91fae3..267c03feb31653ef62733166ddfcd57ca3fb0f5a 100644 --- a/applications/recorder/src/main.cpp +++ b/applications/recorder/src/main.cpp @@ -185,15 +185,13 @@ static void run(ftl::Configurable *root) { // ------------------------------------------------------------------------- - stream->setLatency(6); // FIXME: This depends on source!? stream->add(group); stream->run(); bool busy = false; - group->setLatency(4); group->setName("ReconGroup"); - group->sync([](ftl::rgbd::FrameSet &fs) -> bool { + group->onFrameSet([](ftl::rgbd::FrameSet &fs) -> bool { return true; }); diff --git a/applications/vision/CMakeLists.txt b/applications/vision/CMakeLists.txt index 1753488f3407b74518efad555ecdd87be54ba1e9..14fc1ad0077d633121878e9399d2187e21e44648 100644 --- a/applications/vision/CMakeLists.txt +++ b/applications/vision/CMakeLists.txt @@ -21,6 +21,6 @@ set_property(TARGET ftl-vision PROPERTY CUDA_SEPARABLE_COMPILATION OFF) endif() #target_include_directories(cv-node PUBLIC ${PROJECT_SOURCE_DIR}/include) -target_link_libraries(ftl-vision ftlrgbd ftlcommon ftlctrl ${OpenCV_LIBS} ${LIBSGM_LIBRARIES} ${CUDA_LIBRARIES} ftlnet) +target_link_libraries(ftl-vision ftlrgbd ftlcommon ftlstreams ftlctrl ${OpenCV_LIBS} ${LIBSGM_LIBRARIES} ${CUDA_LIBRARIES} ftlnet) diff --git a/applications/vision/src/main.cpp b/applications/vision/src/main.cpp index 058d3f98913cd60bf341cd23fef98901b06f80c0..da166f00cdc2071150417b9775af3bea49eb01a0 100644 --- a/applications/vision/src/main.cpp +++ b/applications/vision/src/main.cpp @@ -8,7 +8,6 @@ #include <loguru.hpp> #include <ftl/configuration.hpp> #include <ctpl_stl.h> -// #include <zlib.h> #include <string> #include <map> @@ -19,13 +18,14 @@ #include <opencv2/opencv.hpp> #include <ftl/rgbd.hpp> #include <ftl/middlebury.hpp> -//#include <ftl/display.hpp> -#include <ftl/rgbd/streamer.hpp> #include <ftl/net/universe.hpp> #include <ftl/master.hpp> #include <nlohmann/json.hpp> #include <ftl/operators/disparity.hpp> +#include <ftl/streams/netstream.hpp> +#include <ftl/streams/sender.hpp> + #include "opencv2/imgproc.hpp" #include "opencv2/imgcodecs.hpp" #include "opencv2/highgui.hpp" @@ -37,8 +37,7 @@ using ftl::rgbd::Source; using ftl::rgbd::Camera; -//using ftl::Display; -using ftl::rgbd::Streamer; +using ftl::codecs::Channel; using ftl::net::Universe; using std::string; using std::vector; @@ -58,45 +57,44 @@ static void run(ftl::Configurable *root) { if (paths && (*paths).size() > 0) file = (*paths)[(*paths).size()-1]; Source *source = nullptr; - source = ftl::create<Source>(root, "source"); - + source = ftl::create<Source>(root, "source", net); if (file != "") source->set("uri", file); - //Display *display = ftl::create<Display>(root, "display", "local"); + ftl::stream::Sender *sender = ftl::create<ftl::stream::Sender>(root, "sender"); + ftl::stream::Net *outstream = ftl::create<ftl::stream::Net>(root, "stream", net); + outstream->set("uri", outstream->getID()); + outstream->begin(); + sender->setStream(outstream); + + auto *grp = new ftl::rgbd::Group(); + source->setChannel(Channel::Depth); + grp->addSource(source); + + grp->onFrameSet([sender](ftl::rgbd::FrameSet &fs) { + fs.id = 0; + sender->post(fs); + return true; + }); - Streamer *stream = ftl::create<Streamer>(root, "stream", net); - stream->add(source); - auto pipeline = ftl::config::create<ftl::operators::Graph>(root, "pipeline"); pipeline->append<ftl::operators::DepthChannel>("depth"); // Ensure there is a depth channel - stream->group()->addPipeline(pipeline); + grp->addPipeline(pipeline); net->start(); LOG(INFO) << "Running..."; - /*if (display->hasDisplays()) { - stream->run(); - while (ftl::running && display->active()) { - cv::Mat rgb, depth; - source->getFrames(rgb, depth); - if (!rgb.empty()) display->render(rgb, depth, source->parameters()); - display->wait(10); - } - } else {*/ - stream->run(true); - //} - ftl::timer::start(true); - LOG(INFO) << "Stopping..."; ctrl.stop(); - stream->stop(); + net->shutdown(); ftl::pool.stop(); - delete stream; - //delete display; + delete grp; + delete sender; + delete outstream; + //delete source; // TODO(Nick) Add ftl::destroy delete net; } @@ -108,15 +106,8 @@ int main(int argc, char **argv) { std::cout << "FTL Vision Node " << FTL_VERSION_LONG << std::endl; auto root = ftl::configure(argc, argv, "vision_default"); - //config["ftl://vision/default"]["paths"] = paths; - - // Choose normal or middlebury modes - //if (config["middlebury"]["dataset"] == "") { - std::cout << "Loading..." << std::endl; - run(root); - //} else { - // ftl::middlebury::test(config); - //} + std::cout << "Loading..." << std::endl; + run(root); delete root; LOG(INFO) << "Terminating with code " << ftl::exit_code; diff --git a/components/codecs/include/ftl/codecs/channels.hpp b/components/codecs/include/ftl/codecs/channels.hpp index dca2eef7672a772ac9f22b2a09d42d3ee9e1d9ed..5eacf6a2c1a2ad2540517fb28f9497adf8323796 100644 --- a/components/codecs/include/ftl/codecs/channels.hpp +++ b/components/codecs/include/ftl/codecs/channels.hpp @@ -40,7 +40,9 @@ enum struct Channel : int { Configuration = 64, // JSON Data Calibration = 65, // Camera Parameters Object Pose = 66, // Eigen::Matrix4d - Index = 67, + Calibration2 = 67, // Right camera parameters + Index = 68, + Control = 69, // For stream and encoder control Data = 2048 // Custom data, any codec. }; @@ -51,14 +53,15 @@ inline bool isData(Channel c) { return (int)c >= 64; }; std::string name(Channel c); int type(Channel c); +template <int BASE=0> class Channels { public: class iterator { public: - iterator(const Channels &c, unsigned int ix) : channels_(c), ix_(ix) { } - iterator operator++(); - iterator operator++(int junk); + iterator(const Channels &c, unsigned int ix) : channels_(c), ix_(ix) { while (ix_ < 32+BASE && !channels_.has(ix_)) ++ix_; } + inline iterator operator++() { Channels::iterator i = *this; while (++ix_ < 32+BASE && !channels_.has(ix_)); return i; } + inline iterator operator++(int junk) { while (++ix_ < 32+BASE && !channels_.has(ix_)); return *this; } inline ftl::codecs::Channel operator*() { return static_cast<Channel>(static_cast<int>(ix_)); } //ftl::codecs::Channel operator->() { return ptr_; } inline bool operator==(const iterator& rhs) { return ix_ == rhs.ix_; } @@ -68,41 +71,47 @@ class Channels { unsigned int ix_; }; - inline Channels() { mask = 0; } - inline explicit Channels(unsigned int m) { mask = m; } - inline explicit Channels(Channel c) { mask = (c == Channel::None) ? 0 : 0x1 << static_cast<unsigned int>(c); } - inline Channels &operator=(Channel c) { mask = (c == Channel::None) ? 0 : 0x1 << static_cast<unsigned int>(c); return *this; } - inline Channels operator|(Channel c) const { return (c == Channel::None) ? Channels(mask) : Channels(mask | (0x1 << static_cast<unsigned int>(c))); } - inline Channels operator+(Channel c) const { return (c == Channel::None) ? Channels(mask) : Channels(mask | (0x1 << static_cast<unsigned int>(c))); } - inline Channels &operator|=(Channel c) { mask |= (c == Channel::None) ? 0 : (0x1 << static_cast<unsigned int>(c)); return *this; } - inline Channels &operator+=(Channel c) { mask |= (c == Channel::None) ? 0 : (0x1 << static_cast<unsigned int>(c)); return *this; } - inline Channels &operator-=(Channel c) { mask &= ~((c == Channel::None) ? 0 : (0x1 << static_cast<unsigned int>(c))); return *this; } - inline Channels &operator+=(unsigned int c) { mask |= (0x1 << c); return *this; } - inline Channels &operator-=(unsigned int c) { mask &= ~(0x1 << c); return *this; } + inline Channels() : mask(0) { } + inline explicit Channels(unsigned int m) : mask(m) { } + inline explicit Channels(Channel c) : mask((c == Channel::None || static_cast<unsigned int>(c) - BASE >= 32) ? 0 : 0x1 << (static_cast<unsigned int>(c) - BASE)) { } + inline Channels(const Channels<BASE> &c) : mask((unsigned int)c.mask) {} + //inline Channels(Channels<BASE> &c) : mask((unsigned int)c.mask) {} + inline Channels &operator=(const Channels &c) { mask = (unsigned int)c.mask; return *this; } + inline Channels &operator=(Channel c) { mask = (c == Channel::None || static_cast<unsigned int>(c) - BASE >= 32) ? 0 : 0x1 << (static_cast<unsigned int>(c) - BASE); return *this; } + inline Channels operator|(Channel c) const { return (c == Channel::None || static_cast<unsigned int>(c) - BASE >= 32) ? Channels(mask) : Channels(mask | (0x1 << (static_cast<unsigned int>(c) - BASE))); } + inline Channels operator+(Channel c) const { return (c == Channel::None || static_cast<unsigned int>(c) - BASE >= 32) ? Channels(mask) : Channels(mask | (0x1 << (static_cast<unsigned int>(c) - BASE))); } + inline Channels &operator|=(Channel c) { mask |= (c == Channel::None || static_cast<unsigned int>(c) - BASE >= 32) ? 0 : (0x1 << (static_cast<unsigned int>(c) - BASE)); return *this; } + inline Channels &operator+=(Channel c) { mask |= (c == Channel::None || static_cast<unsigned int>(c) - BASE >= 32) ? 0 : (0x1 << (static_cast<unsigned int>(c) - BASE)); return *this; } + inline Channels &operator-=(Channel c) { mask &= ~((c == Channel::None || static_cast<unsigned int>(c) - BASE >= 32) ? 0 : (0x1 << (static_cast<unsigned int>(c) - BASE))); return *this; } + inline Channels &operator+=(unsigned int c) { mask |= (0x1 << (c - BASE)); return *this; } + inline Channels &operator-=(unsigned int c) { mask &= ~(0x1 << (c - BASE)); return *this; } + inline Channels &operator&=(const Channels<BASE> &c) { mask &= c.mask; return *this; } + inline Channels operator&(const Channels<BASE> &c) const { return Channels<BASE>(mask & c.mask); } inline bool has(Channel c) const { - return (c == Channel::None) ? true : mask & (0x1 << static_cast<unsigned int>(c)); + return (c == Channel::None || static_cast<unsigned int>(c) - BASE >= 32) ? true : mask & (0x1 << (static_cast<unsigned int>(c) - BASE)); } inline bool has(unsigned int c) const { - return mask & (0x1 << c); + return mask & (0x1 << (c - BASE)); } - inline iterator begin() { return iterator(*this, 0); } - inline iterator end() { return iterator(*this, 32); } + inline iterator begin() { return iterator(*this, BASE); } + inline iterator end() { return iterator(*this, 32+BASE); } - inline operator unsigned int() { return mask; } - inline operator bool() { return mask > 0; } - inline operator Channel() { + inline bool operator==(const Channels<BASE> &c) const { return mask == c.mask; } + inline operator unsigned int() const { return mask; } + inline operator bool() const { return mask > 0; } + inline operator Channel() const { if (mask == 0) return Channel::None; int ix = 0; int tmask = mask; while (!(tmask & 0x1) && ++ix < 32) tmask >>= 1; - return static_cast<Channel>(ix); + return static_cast<Channel>(ix + BASE); } - inline size_t count() { return std::bitset<32>(mask).count(); } - inline bool empty() { return mask == 0; } + inline size_t count() const { return std::bitset<32>(mask).count(); } + inline bool empty() const { return mask == 0; } inline void clear() { mask = 0; } static const size_t kMax = 32; @@ -110,14 +119,12 @@ class Channels { static Channels All(); private: - unsigned int mask; + std::atomic<unsigned int> mask; }; -inline Channels::iterator Channels::iterator::operator++() { Channels::iterator i = *this; while (++ix_ < 32 && !channels_.has(ix_)); return i; } -inline Channels::iterator Channels::iterator::operator++(int junk) { while (++ix_ < 32 && !channels_.has(ix_)); return *this; } - -inline Channels Channels::All() { - return Channels(0xFFFFFFFFu); +template <int BASE> +inline Channels<BASE> Channels<BASE>::All() { + return Channels<BASE>(0xFFFFFFFFu); } static const Channels kNoChannels; @@ -140,12 +147,14 @@ inline bool isFloatChannel(ftl::codecs::Channel chan) { MSGPACK_ADD_ENUM(ftl::codecs::Channel); -inline ftl::codecs::Channels operator|(ftl::codecs::Channel a, ftl::codecs::Channel b) { - return ftl::codecs::Channels(a) | b; +template <int BASE=0> +inline ftl::codecs::Channels<BASE> operator|(ftl::codecs::Channel a, ftl::codecs::Channel b) { + return ftl::codecs::Channels<BASE>(a) | b; } -inline ftl::codecs::Channels operator+(ftl::codecs::Channel a, ftl::codecs::Channel b) { - return ftl::codecs::Channels(a) | b; +template <int BASE=0> +inline ftl::codecs::Channels<BASE> operator+(ftl::codecs::Channel a, ftl::codecs::Channel b) { + return ftl::codecs::Channels<BASE>(a) | b; } #endif // _FTL_RGBD_CHANNELS_HPP_ diff --git a/components/codecs/include/ftl/codecs/encoder.hpp b/components/codecs/include/ftl/codecs/encoder.hpp index ed817f7b1c5a59b133c36317195a5c2da9203e56..ae2d69f9d1fa549bcc85d456d67fe1c5930704dc 100644 --- a/components/codecs/include/ftl/codecs/encoder.hpp +++ b/components/codecs/include/ftl/codecs/encoder.hpp @@ -12,6 +12,7 @@ namespace ftl { namespace codecs { static const unsigned int kVideoBufferSize = 10*1024*1024; +static constexpr uint8_t kFlagMultiple = 0x80; class Encoder; diff --git a/components/codecs/include/ftl/codecs/h264.hpp b/components/codecs/include/ftl/codecs/h264.hpp index 17f649c52220f7ac8b00e6d4ed9b78e18f2847f8..c4eddd7c10c4871246ecd3de5d66c6591a2332db 100644 --- a/components/codecs/include/ftl/codecs/h264.hpp +++ b/components/codecs/include/ftl/codecs/h264.hpp @@ -51,16 +51,16 @@ enum class NALType : int { * Extract the NAL unit type from the first NAL header. * With NvPipe, the 5th byte contains the NAL Unit header. */ -inline NALType getNALType(const std::vector<uint8_t> &data) { - return static_cast<NALType>(data[4] & 0x1F); +inline NALType getNALType(const unsigned char *data, size_t size) { + return (size > 4) ? static_cast<NALType>(data[4] & 0x1F) : NALType::UNSPECIFIED_0; } /** * Check the H264 bitstream for an I-Frame. With NvPipe, all I-Frames start * with a SPS NAL unit so just check for this. */ -inline bool isIFrame(const std::vector<uint8_t> &data) { - return getNALType(data) == NALType::SPS; +inline bool isIFrame(const unsigned char *data, size_t size) { + return getNALType(data, size) == NALType::SPS; } } diff --git a/components/codecs/include/ftl/codecs/hevc.hpp b/components/codecs/include/ftl/codecs/hevc.hpp index b3a32246544f3cf24a4ad09345c2f47a96eb0735..fa436d9904f098bb39556a27f3ea5b21a2963873 100644 --- a/components/codecs/include/ftl/codecs/hevc.hpp +++ b/components/codecs/include/ftl/codecs/hevc.hpp @@ -93,20 +93,20 @@ enum class NALType : int { * Extract the NAL unit type from the first NAL header. * With NvPipe, the 5th byte contains the NAL Unit header. */ -inline NALType getNALType(const std::vector<uint8_t> &data) { - return static_cast<NALType>((data[4] >> 1) & 0x3F); +inline NALType getNALType(const unsigned char *data, size_t size) { + return (size > 4) ? static_cast<NALType>((data[4] >> 1) & 0x3F) : NALType::INVALID; } -inline bool validNAL(const std::vector<uint8_t> &data) { - return data[0] == 0 && data[1] == 0 && data[2] == 0 && data[3] == 1; +inline bool validNAL(const unsigned char *data, size_t size) { + return size > 4 && data[0] == 0 && data[1] == 0 && data[2] == 0 && data[3] == 1; } /** * Check the HEVC bitstream for an I-Frame. With NvPipe, all I-Frames start * with a VPS NAL unit so just check for this. */ -inline bool isIFrame(const std::vector<uint8_t> &data) { - return getNALType(data) == NALType::VPS; +inline bool isIFrame(const unsigned char *data, size_t size) { + return getNALType(data, size) == NALType::VPS; } } diff --git a/components/codecs/include/ftl/codecs/nvpipe_decoder.hpp b/components/codecs/include/ftl/codecs/nvpipe_decoder.hpp index 78d6d7fea43cf729c3034ed63c173ca1a8cc9797..82a61feaed8a34d7d00d1fa00d127a8c0c2361f4 100644 --- a/components/codecs/include/ftl/codecs/nvpipe_decoder.hpp +++ b/components/codecs/include/ftl/codecs/nvpipe_decoder.hpp @@ -27,6 +27,8 @@ class NvPipeDecoder : public ftl::codecs::Decoder { bool seen_iframe_; cv::cuda::GpuMat tmp_; cv::cuda::Stream stream_; + + bool _checkIFrame(ftl::codecs::codec_t codec, const unsigned char *data, size_t size); }; } diff --git a/components/codecs/include/ftl/codecs/packet.hpp b/components/codecs/include/ftl/codecs/packet.hpp index f2fa53c8fa860527f1939b61c4962b306da89043..0788653c1b95781d56ba5fd419b6f8eacb7948f5 100644 --- a/components/codecs/include/ftl/codecs/packet.hpp +++ b/components/codecs/include/ftl/codecs/packet.hpp @@ -11,12 +11,15 @@ namespace ftl { namespace codecs { +static constexpr uint8_t kAllFrames = 255; +static constexpr uint8_t kAllFramesets = 255; + /** * First bytes of our file format. */ struct Header { const char magic[4] = {'F','T','L','F'}; - uint8_t version = 3; + uint8_t version = 4; }; /** @@ -34,13 +37,22 @@ struct IndexHeader { */ struct Packet { ftl::codecs::codec_t codec; - ftl::codecs::definition_t definition; - uint8_t block_total; // Packets expected per frame - uint8_t block_number; // This packets number within a frame + ftl::codecs::definition_t definition; // Data resolution + + union { + [[deprecated]] uint8_t block_total; // v1-3 Packets expected per frame + uint8_t frame_count; // v4+ Frames included in this packet + }; + + union { + [[deprecated]] uint8_t block_number; // v1-3 This packets number within a frame + uint8_t bitrate=0; // v4+ For multi-bitrate encoding, 0=highest + }; + uint8_t flags; // Codec dependent flags (eg. I-Frame or P-Frame) std::vector<uint8_t> data; - MSGPACK_DEFINE(codec, definition, block_total, block_number, flags, data); + MSGPACK_DEFINE(codec, definition, frame_count, bitrate, flags, data); }; /** @@ -49,12 +61,24 @@ struct Packet { * or included before a frame packet structure. */ struct StreamPacket { + int version; // FTL version, Not encoded into stream + int64_t timestamp; - uint8_t streamID; // Source number... 255 = broadcast stream - uint8_t channel_count; // Number of channels to expect for this frame to complete (usually 1 or 2) + uint8_t streamID; // Source number [or v4 frameset id] + + union { + [[deprecated]] uint8_t channel_count; // v1-3 Number of channels to expect for this frame(set) to complete (usually 1 or 2) + uint8_t frame_number; // v4+ First frame number (packet may include multiple frames) + }; + ftl::codecs::Channel channel; // Actual channel of this current set of packets - MSGPACK_DEFINE(timestamp, streamID, channel_count, channel); + inline int frameNumber() const { return (version >= 4) ? frame_number : streamID; } + inline int frameSetID() const { return (version >= 4) ? streamID : 0; } + + MSGPACK_DEFINE(timestamp, streamID, frame_number, channel); + + operator std::string() const; }; /** diff --git a/components/codecs/src/bitrates.cpp b/components/codecs/src/bitrates.cpp index 37889f5a55bf0337d1b3b750538587d1cc81f537..fc101f6bbd5733389db04fc75369b5512c4a3e8e 100644 --- a/components/codecs/src/bitrates.cpp +++ b/components/codecs/src/bitrates.cpp @@ -52,7 +52,6 @@ int ftl::codecs::getHeight(definition_t d) { definition_t ftl::codecs::findDefinition(int width, int height) { int best = 0; - bool smaller = true; for(const Resolution res : resolutions) { if ((res.width == width) && (res.height == height)) { diff --git a/components/codecs/src/channels.cpp b/components/codecs/src/channels.cpp index b33427b70d5048bb707126ed67c94d55f7d3642c..b5b52fecf048902212670b0278d800bb2a32a12f 100644 --- a/components/codecs/src/channels.cpp +++ b/components/codecs/src/channels.cpp @@ -8,27 +8,27 @@ struct ChannelInfo { }; static ChannelInfo info[] = { - "Colour", CV_8UC4, - "Depth", CV_32F, - "Right", CV_8UC4, - "DepthRight", CV_32F, - "Deviation", CV_32F, - "Normals", CV_32FC4, - "Points", CV_32FC4, - "Confidence", CV_32F, - "EnergyVector", CV_32FC4, - "Flow", CV_32F, - "Energy", CV_32F, - "Mask", CV_32S, - "Density", CV_32F, - "Support1", CV_8UC4, - "Support2", CV_8UC4, - "Segmentation", CV_32S, + "Colour", CV_8UC4, // 0 + "Depth", CV_32F, // 1 + "Right", CV_8UC4, // 2 + "DepthRight", CV_32F, // 3 + "Deviation", CV_32F, // 4 + "Normals", CV_32FC4, // 5 + "Points", CV_32FC4, // 6 + "Confidence", CV_32F, // 7 + "EnergyVector", CV_32FC4, // 8 + "Flow", CV_32F, // 9 + "Energy", CV_32F, // 10 + "Mask", CV_32S, // 11 + "Density", CV_32F, // 12 + "Support1", CV_8UC4, // 13 + "Support2", CV_8UC4, // 14 + "Segmentation", CV_32S, // 15 - "NoName", 0, - "NoName", 0, - "NoName", 0, - "NoName", 0, + "ColourNormals", 0, // 16 + "ColourHighRes", 0, // 17 + "Disparity", 0, // 18 + "Smoothing", 0, // 19 "NoName", 0, "NoName", 0, "NoName", 0, diff --git a/components/codecs/src/decoder.cpp b/components/codecs/src/decoder.cpp index 399e0787f2949d54b5c180c3626f3e616470ced1..3396233f18e294f130d27bebf632413ba050d831 100644 --- a/components/codecs/src/decoder.cpp +++ b/components/codecs/src/decoder.cpp @@ -5,6 +5,14 @@ using ftl::codecs::Decoder; using ftl::codecs::codec_t; +using std::string; +using std::to_string; + +ftl::codecs::StreamPacket::operator std::string() const { + return string("[\n timestamp=") + to_string(timestamp) + string(",\n frameset=") + + to_string(streamID) + string(",\n frame=") + to_string(frame_number) + + string(",\n channel=") + to_string((int)channel) + string("\n]"); +} Decoder *ftl::codecs::allocateDecoder(const ftl::codecs::Packet &pkt) { switch(pkt.codec) { diff --git a/components/codecs/src/nvpipe_decoder.cpp b/components/codecs/src/nvpipe_decoder.cpp index d24d903d076db1c471b921b5591a4ac3771c9d85..605586990f7dc048232e2539c104b304a311decf 100644 --- a/components/codecs/src/nvpipe_decoder.cpp +++ b/components/codecs/src/nvpipe_decoder.cpp @@ -1,4 +1,5 @@ #include <ftl/codecs/nvpipe_decoder.hpp> +#include <ftl/codecs/nvpipe_encoder.hpp> #include <loguru.hpp> @@ -24,6 +25,24 @@ NvPipeDecoder::~NvPipeDecoder() { } } +template <typename T> +static T readValue(const unsigned char **data) { + const T *ptr = (const T*)(*data); + *data += sizeof(T); + return *ptr; +} + +bool NvPipeDecoder::_checkIFrame(ftl::codecs::codec_t codec, const unsigned char *data, size_t size) { + if (!seen_iframe_) { + if (codec == ftl::codecs::codec_t::HEVC || codec == ftl::codecs::codec_t::HEVC_LOSSLESS) { + if (ftl::codecs::hevc::isIFrame(data, size)) seen_iframe_ = true; + } else if (codec == ftl::codecs::codec_t::H264 || codec == ftl::codecs::codec_t::H264_LOSSLESS) { + if (ftl::codecs::h264::isIFrame(data, size)) seen_iframe_ = true; + } + } + return seen_iframe_; +} + bool NvPipeDecoder::decode(const ftl::codecs::Packet &pkt, cv::cuda::GpuMat &out) { cudaSetDevice(0); UNIQUE_LOCK(mutex_,lk); @@ -42,9 +61,6 @@ bool NvPipeDecoder::decode(const ftl::codecs::Packet &pkt, cv::cuda::GpuMat &out bool islossless = ((pkt.codec == ftl::codecs::codec_t::HEVC || pkt.codec == ftl::codecs::codec_t::H264) && is_float_frame && !(pkt.flags & 0x2)) || pkt.codec == ftl::codecs::codec_t::HEVC_LOSSLESS || pkt.codec == ftl::codecs::codec_t::H264_LOSSLESS; - //LOG(INFO) << "DECODE OUT: " << out.rows << ", " << out.type(); - //LOG(INFO) << "DECODE RESOLUTION: (" << (int)pkt.definition << ") " << ftl::codecs::getWidth(pkt.definition) << "x" << ftl::codecs::getHeight(pkt.definition); - // Build a decoder instance of the correct kind if (nv_decoder_ == nullptr) { nv_decoder_ = NvPipe_CreateDecoder( @@ -62,71 +78,60 @@ bool NvPipeDecoder::decode(const ftl::codecs::Packet &pkt, cv::cuda::GpuMat &out tmp_.create(cv::Size(ftl::codecs::getWidth(pkt.definition),ftl::codecs::getHeight(pkt.definition)), (!is_float_frame) ? CV_8UC4 : (islossless) ? CV_16U : CV_16UC4); - // Check for an I-Frame - if (!seen_iframe_) { - if (pkt.codec == ftl::codecs::codec_t::HEVC || pkt.codec == ftl::codecs::codec_t::HEVC_LOSSLESS) { - if (ftl::codecs::hevc::isIFrame(pkt.data)) seen_iframe_ = true; - } else if (pkt.codec == ftl::codecs::codec_t::H264 || pkt.codec == ftl::codecs::codec_t::H264_LOSSLESS) { - if (ftl::codecs::h264::isIFrame(pkt.data)) seen_iframe_ = true; - } - } - - // No I-Frame yet so don't attempt to decode P-Frames. - if (!seen_iframe_) return false; - // Final checks for validity if (pkt.data.size() == 0 || tmp_.size() != out.size()) { // || !ftl::codecs::hevc::validNAL(pkt.data)) { LOG(ERROR) << "Failed to decode packet"; return false; } - int rc = NvPipe_Decode(nv_decoder_, pkt.data.data(), pkt.data.size(), tmp_.data, tmp_.cols, tmp_.rows, tmp_.step); - if (rc == 0) LOG(ERROR) << "NvPipe decode error: " << NvPipe_GetError(nv_decoder_); + int rc = 0; + if (pkt.flags & ftl::codecs::kFlagMultiple) { + const unsigned char *ptr = pkt.data.data(); + const unsigned char *eptr = ptr+pkt.data.size(); - if (is_float_frame) { - // Is the received frame the same size as requested output? - //if (out.rows == ftl::codecs::getHeight(pkt.definition)) { - - //tmp_.convertTo(out, CV_32FC1, 1.0f/1000.0f, stream_); - - - if (!islossless) { - //cv::cuda::cvtColor(tmp_, tmp_, cv::COLOR_RGB2YUV, 4, stream_); - /*cv::Mat tmpHost; - tmp_.download(tmpHost); - cv::imshow("DEPTH", tmpHost); - cv::waitKey(1);*/ - ftl::cuda::vuya_to_depth(out, tmp_, 16.0f, stream_); - } else { - tmp_.convertTo(out, CV_32FC1, 1.0f/1000.0f, stream_); + while (ptr < eptr) { + int size = readValue<int>(&ptr); + + // Skip if still missing an IFrame. + if (!_checkIFrame(pkt.codec, ptr, size)) { + LOG(WARNING) << "P-Frame without I-Frame in decoder"; + ptr += size; + if (ptr < eptr) continue; + else return false; } - /*} else { - LOG(WARNING) << "Resizing decoded frame from " << tmp_.size() << " to " << out.size(); - // FIXME: This won't work on GPU - tmp_.convertTo(tmp_, CV_32FC1, 1.0f/1000.0f, stream_); - cv::cuda::resize(tmp_, out, out.size(), 0, 0, cv::INTER_NEAREST, stream_); - }*/ + rc = NvPipe_Decode(nv_decoder_, ptr, size, tmp_.data, tmp_.cols, tmp_.rows, tmp_.step); + if (rc == 0) LOG(ERROR) << "NvPipe decode error: " << NvPipe_GetError(nv_decoder_); + ptr += size; + } + + //LOG(WARNING) << "Decode of multiple frames: " << count; } else { - // Is the received frame the same size as requested output? - //if (out.rows == ftl::codecs::getHeight(pkt.definition)) { - // Flag 0x1 means frame is in RGB so needs conversion to BGR - if (pkt.flags & 0x1) { - cv::cuda::cvtColor(tmp_, out, cv::COLOR_RGBA2BGRA, 0, stream_); - } else { - //cv::cuda::cvtColor(tmp_, out, cv::COLOR_BGRA2BGR, 0, stream_); - } - /*} else { - LOG(WARNING) << "Resizing decoded frame from " << tmp_.size() << " to " << out.size(); - // FIXME: This won't work on GPU, plus it allocates extra memory... - // Flag 0x1 means frame is in RGB so needs conversion to BGR - if (pkt.flags & 0x1) { - cv::cuda::cvtColor(tmp_, tmp_, cv::COLOR_RGBA2BGR, 0, stream_); - } else { - cv::cuda::cvtColor(tmp_, tmp_, cv::COLOR_BGRA2BGR, 0, stream_); - } - cv::cuda::resize(tmp_, out, out.size(), 0.0, 0.0, cv::INTER_LINEAR, stream_); - }*/ + if (!_checkIFrame(pkt.codec, pkt.data.data(), pkt.data.size())) { + LOG(WARNING) << "P-Frame without I-Frame in decoder"; + return false; + } + rc = NvPipe_Decode(nv_decoder_, pkt.data.data(), pkt.data.size(), tmp_.data, tmp_.cols, tmp_.rows, tmp_.step); + if (rc == 0) LOG(ERROR) << "NvPipe decode error: " << NvPipe_GetError(nv_decoder_); + } + + if (is_float_frame) { + if (!islossless) { + //cv::cuda::cvtColor(tmp_, tmp_, cv::COLOR_RGB2YUV, 4, stream_); + /*cv::Mat tmpHost; + tmp_.download(tmpHost); + cv::imshow("DEPTH", tmpHost); + cv::waitKey(1);*/ + + ftl::cuda::vuya_to_depth(out, tmp_, 16.0f, stream_); + } else { + tmp_.convertTo(out, CV_32FC1, 1.0f/1000.0f, stream_); + } + } else { + // Flag 0x1 means frame is in RGB so needs conversion to BGR + if (pkt.flags & 0x1) { + cv::cuda::cvtColor(tmp_, out, cv::COLOR_RGBA2BGRA, 0, stream_); + } } stream_.waitForCompletion(); diff --git a/components/codecs/src/nvpipe_encoder.cpp b/components/codecs/src/nvpipe_encoder.cpp index 720f69d34409b6b9c8f1adc681b5e6be839b25e2..2c3b27636999676578cc7eb34301a6b0fa1757db 100644 --- a/components/codecs/src/nvpipe_encoder.cpp +++ b/components/codecs/src/nvpipe_encoder.cpp @@ -123,8 +123,8 @@ bool NvPipeEncoder::encode(const cv::cuda::GpuMat &in, definition_t odefinition, Packet pkt; pkt.codec = preference_; pkt.definition = definition; - pkt.block_total = 1; - pkt.block_number = 0; + pkt.frame_count = 1; + pkt.bitrate = 0; pkt.flags = NvPipeEncoder::kFlagRGB | NvPipeEncoder::kFlagMappedDepth; pkt.data.resize(ftl::codecs::kVideoBufferSize); diff --git a/components/codecs/src/opencv_decoder.cpp b/components/codecs/src/opencv_decoder.cpp index f721b462a17c3fb71f83b7ee4fadbfb1240e81c2..e08117ae2225eed1e3871a0a94383bae05efa67b 100644 --- a/components/codecs/src/opencv_decoder.cpp +++ b/components/codecs/src/opencv_decoder.cpp @@ -19,13 +19,13 @@ bool OpenCVDecoder::accepts(const ftl::codecs::Packet &pkt) { bool OpenCVDecoder::decode(const ftl::codecs::Packet &pkt, cv::cuda::GpuMat &out) { //CHECK(cv::Size(ftl::codecs::getWidth(pkt.definition), ftl::codecs::getHeight(pkt.definition)) == out.size()); - int chunk_dim = std::sqrt(pkt.block_total); + int chunk_dim = std::sqrt(pkt.frame_count); int chunk_width = out.cols / chunk_dim; int chunk_height = out.rows / chunk_dim; // Build chunk head - int cx = (pkt.block_number % chunk_dim) * chunk_width; - int cy = (pkt.block_number / chunk_dim) * chunk_height; + int cx = 0; //(pkt.block_number % chunk_dim) * chunk_width; + int cy = 0; //(pkt.block_number / chunk_dim) * chunk_height; cv::Rect roi(cx,cy,chunk_width,chunk_height); cv::cuda::GpuMat chunkHead = out(roi); diff --git a/components/codecs/src/opencv_encoder.cpp b/components/codecs/src/opencv_encoder.cpp index 772922e7bf740a1b47ecea2260d448afca217fe0..01310c41f55ba34dfbe08d1b99bdc256a70358cd 100644 --- a/components/codecs/src/opencv_encoder.cpp +++ b/components/codecs/src/opencv_encoder.cpp @@ -54,19 +54,19 @@ bool OpenCVEncoder::encode(const cv::cuda::GpuMat &in, definition_t definition, chunk_count_ = chunk_dim_ * chunk_dim_; jobs_ = chunk_count_; - for (int i=0; i<chunk_count_; ++i) { + //for (int i=0; i<chunk_count_; ++i) { // Add chunk job to thread pool - ftl::pool.push([this,i,cb,is_colour,bitrate](int id) { + //ftl::pool.push([this,i,cb,is_colour,bitrate](int id) { ftl::codecs::Packet pkt; - pkt.block_number = i; - pkt.block_total = chunk_count_; + pkt.bitrate = 0; + pkt.frame_count = 1; pkt.definition = current_definition_; pkt.codec = (is_colour) ? codec_t::JPG : codec_t::PNG; try { _encodeBlock(tmp_, pkt, bitrate); } catch(...) { - LOG(ERROR) << "OpenCV encode block exception: " << i; + LOG(ERROR) << "OpenCV encode block exception: "; } try { @@ -75,17 +75,17 @@ bool OpenCVEncoder::encode(const cv::cuda::GpuMat &in, definition_t definition, LOG(ERROR) << "OpenCV encoder callback exception"; } - std::unique_lock<std::mutex> lk(job_mtx_); - --jobs_; - if (jobs_ == 0) job_cv_.notify_one(); - }); - } + //std::unique_lock<std::mutex> lk(job_mtx_); + //--jobs_; + //if (jobs_ == 0) job_cv_.notify_one(); + //}); + //} - std::unique_lock<std::mutex> lk(job_mtx_); - job_cv_.wait_for(lk, std::chrono::seconds(20), [this]{ return jobs_ == 0; }); - if (jobs_ != 0) { - LOG(FATAL) << "Deadlock detected (" << jobs_ << ")"; - } + //std::unique_lock<std::mutex> lk(job_mtx_); + //job_cv_.wait_for(lk, std::chrono::seconds(20), [this]{ return jobs_ == 0; }); + //if (jobs_ != 0) { + // LOG(FATAL) << "Deadlock detected (" << jobs_ << ")"; + //} return true; } @@ -95,8 +95,8 @@ bool OpenCVEncoder::_encodeBlock(const cv::Mat &in, ftl::codecs::Packet &pkt, bi int chunk_height = in.rows / chunk_dim_; // Build chunk heads - int cx = (pkt.block_number % chunk_dim_) * chunk_width; - int cy = (pkt.block_number / chunk_dim_) * chunk_height; + int cx = 0; //(pkt.block_number % chunk_dim_) * chunk_width; + int cy = 0; //(pkt.block_number / chunk_dim_) * chunk_height; cv::Rect roi(cx,cy,chunk_width,chunk_height); cv::Mat chunkHead = in(roi); diff --git a/components/codecs/test/CMakeLists.txt b/components/codecs/test/CMakeLists.txt index d816a0572aa0935ff8d0896b00d211ad211b839a..63d40a0ea26b09429f98343bbb99de2cf5fcb44e 100644 --- a/components/codecs/test/CMakeLists.txt +++ b/components/codecs/test/CMakeLists.txt @@ -33,18 +33,18 @@ target_link_libraries(nvpipe_codec_unit add_test(NvPipeCodecUnitTest nvpipe_codec_unit) ### Reader Writer Unit ################################################################ -add_executable(rw_unit - ./tests.cpp - ../src/writer.cpp - ../src/reader.cpp - ./readwrite_test.cpp -) -target_include_directories(rw_unit PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../include") -target_link_libraries(rw_unit - Threads::Threads ${OS_LIBS} ${OpenCV_LIBS} ${CUDA_LIBRARIES} ftlcommon Eigen3::Eigen) - - -add_test(RWUnitTest rw_unit) +#add_executable(rw_unit +# ./tests.cpp +# ../src/writer.cpp +# ../src/reader.cpp +# ./readwrite_test.cpp +#) +#target_include_directories(rw_unit PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../include") +#target_link_libraries(rw_unit +# Threads::Threads ${OS_LIBS} ${OpenCV_LIBS} ${CUDA_LIBRARIES} ftlcommon Eigen3::Eigen) + + +#add_test(RWUnitTest rw_unit) ### Channel Unit ############################################################### add_executable(channel_unit diff --git a/components/codecs/test/readwrite_test.cpp b/components/codecs/test/readwrite_test.cpp index ea8781b1b3b3f9204a26cd2fca527603ee855596..70c97d82d4aa03ff3ba8524591e93d7de48cd1ca 100644 --- a/components/codecs/test/readwrite_test.cpp +++ b/components/codecs/test/readwrite_test.cpp @@ -27,8 +27,8 @@ TEST_CASE( "Write and read - Single frame" ) { pkt.codec = codec_t::JSON; pkt.definition = definition_t::Any; - pkt.block_number = 0; - pkt.block_total = 1; + pkt.bitrate = 0; + pkt.frame_count = 1; pkt.flags = 0; pkt.data = {44,44,44}; @@ -68,8 +68,8 @@ TEST_CASE( "Write and read - Multiple frames" ) { pkt.codec = codec_t::JSON; pkt.definition = definition_t::Any; - pkt.block_number = 0; - pkt.block_total = 1; + pkt.bitrate = 0; + pkt.frame_count = 1; pkt.flags = 0; pkt.data = {44,44,44}; @@ -115,8 +115,8 @@ TEST_CASE( "Write and read - Multiple streams" ) { pkt.codec = codec_t::JSON; pkt.definition = definition_t::Any; - pkt.block_number = 0; - pkt.block_total = 1; + pkt.bitrate = 0; + pkt.frame_count = 1; pkt.flags = 0; pkt.data = {44,44,44}; @@ -170,8 +170,8 @@ TEST_CASE( "Write and read - Multiple frames with limit" ) { pkt.codec = codec_t::JSON; pkt.definition = definition_t::Any; - pkt.block_number = 0; - pkt.block_total = 1; + pkt.bitrate = 0; + pkt.frame_count = 1; pkt.flags = 0; pkt.data = {44,44,44}; @@ -217,8 +217,8 @@ TEST_CASE( "Write and read - Multiple reads" ) { pkt.codec = codec_t::JSON; pkt.definition = definition_t::Any; - pkt.block_number = 0; - pkt.block_total = 1; + pkt.bitrate = 0; + pkt.frame_count = 1; pkt.flags = 0; pkt.data = {44,44,44}; diff --git a/components/common/cpp/src/timer.cpp b/components/common/cpp/src/timer.cpp index f13255e7359747406feb5c3d7da3b1ba779c617f..5765eb451ee4ca75a953775b447c4f4163ccc421 100644 --- a/components/common/cpp/src/timer.cpp +++ b/components/common/cpp/src/timer.cpp @@ -156,11 +156,14 @@ static void trigger_jobs() { } j.active = true; active_jobs++; - ftl::pool.push([&j,ts](int id) { - bool doremove = !j.job(ts); - j.active = false; + + auto *pj = &j; + + ftl::pool.push([pj,ts](int id) { + bool doremove = !pj->job(ts); + pj->active = false; active_jobs--; - if (doremove) removeJob(j.id); + if (doremove) removeJob(pj->id); }); } } diff --git a/components/net/cpp/include/ftl/net/dispatcher.hpp b/components/net/cpp/include/ftl/net/dispatcher.hpp index 0b4d3b34dcb4e1662a43c834abe740319c1f56d2..6f12e0e470a2c627979cec7f9bdfe5b7d6523e59 100644 --- a/components/net/cpp/include/ftl/net/dispatcher.hpp +++ b/components/net/cpp/include/ftl/net/dispatcher.hpp @@ -19,6 +19,10 @@ namespace ftl { +namespace net { +class Peer; +} + namespace internal { //! \brief Calls a functor with argument provided directly template <typename Functor, typename Arg> @@ -34,16 +38,28 @@ namespace internal { return func(std::get<I>(params)...); } + template <typename Functor, typename... Args, std::size_t... I> + decltype(auto) call_helper(Functor func, ftl::net::Peer &p, std::tuple<Args...> &¶ms, + std::index_sequence<I...>) { + return func(p, std::get<I>(params)...); + } + //! \brief Calls a functor with arguments provided as a tuple template <typename Functor, typename... Args> decltype(auto) call(Functor f, std::tuple<Args...> &args) { return call_helper(f, std::forward<std::tuple<Args...>>(args), std::index_sequence_for<Args...>{}); } + + //! \brief Calls a functor with arguments provided as a tuple + template <typename Functor, typename... Args> + decltype(auto) call(Functor f, ftl::net::Peer &p, std::tuple<Args...> &args) { + return call_helper(f, p, std::forward<std::tuple<Args...>>(args), + std::index_sequence_for<Args...>{}); + } } namespace net { -class Peer; class Dispatcher { public: @@ -51,14 +67,17 @@ class Dispatcher { //void dispatch(Peer &, const std::string &msg); void dispatch(Peer &, const msgpack::object &msg); + + // Without peer object ===================================================== template <typename F> void bind(std::string const &name, F func, ftl::internal::tags::void_result const &, - ftl::internal::tags::zero_arg const &) { + ftl::internal::tags::zero_arg const &, + ftl::internal::false_ const &) { enforce_unique_name(name); funcs_.insert( - std::make_pair(name, [func, name](msgpack::object const &args) { + std::make_pair(name, [func, name](ftl::net::Peer &p, msgpack::object const &args) { enforce_arg_count(name, 0, args.via.array.size); func(); return std::make_unique<msgpack::object_handle>(); @@ -68,13 +87,14 @@ class Dispatcher { template <typename F> void bind(std::string const &name, F func, ftl::internal::tags::void_result const &, - ftl::internal::tags::nonzero_arg const &) { + ftl::internal::tags::nonzero_arg const &, + ftl::internal::false_ const &) { using ftl::internal::func_traits; using args_type = typename func_traits<F>::args_type; enforce_unique_name(name); funcs_.insert( - std::make_pair(name, [func, name](msgpack::object const &args) { + std::make_pair(name, [func, name](ftl::net::Peer &p, msgpack::object const &args) { constexpr int args_count = std::tuple_size<args_type>::value; enforce_arg_count(name, args_count, args.via.array.size); args_type args_real; @@ -87,12 +107,13 @@ class Dispatcher { template <typename F> void bind(std::string const &name, F func, ftl::internal::tags::nonvoid_result const &, - ftl::internal::tags::zero_arg const &) { + ftl::internal::tags::zero_arg const &, + ftl::internal::false_ const &) { using ftl::internal::func_traits; enforce_unique_name(name); funcs_.insert(std::make_pair(name, [func, - name](msgpack::object const &args) { + name](ftl::net::Peer &p, msgpack::object const &args) { enforce_arg_count(name, 0, args.via.array.size); auto z = std::make_unique<msgpack::zone>(); auto result = msgpack::object(func(), *z); @@ -103,13 +124,14 @@ class Dispatcher { template <typename F> void bind(std::string const &name, F func, ftl::internal::tags::nonvoid_result const &, - ftl::internal::tags::nonzero_arg const &) { + ftl::internal::tags::nonzero_arg const &, + ftl::internal::false_ const &) { using ftl::internal::func_traits; using args_type = typename func_traits<F>::args_type; enforce_unique_name(name); funcs_.insert(std::make_pair(name, [func, - name](msgpack::object const &args) { + name](ftl::net::Peer &p, msgpack::object const &args) { constexpr int args_count = std::tuple_size<args_type>::value; enforce_arg_count(name, args_count, args.via.array.size); args_type args_real; @@ -120,6 +142,82 @@ class Dispatcher { })); } + // With peer object ======================================================== + + template <typename F> + void bind(std::string const &name, F func, + ftl::internal::tags::void_result const &, + ftl::internal::tags::zero_arg const &, + ftl::internal::true_ const &) { + enforce_unique_name(name); + funcs_.insert( + std::make_pair(name, [func, name](ftl::net::Peer &p, msgpack::object const &args) { + enforce_arg_count(name, 0, args.via.array.size); + func(p); + return std::make_unique<msgpack::object_handle>(); + })); + } + + template <typename F> + void bind(std::string const &name, F func, + ftl::internal::tags::void_result const &, + ftl::internal::tags::nonzero_arg const &, + ftl::internal::true_ const &) { + using ftl::internal::func_traits; + using args_type = typename func_traits<F>::args_type; + + enforce_unique_name(name); + funcs_.insert( + std::make_pair(name, [func, name](ftl::net::Peer &p, msgpack::object const &args) { + constexpr int args_count = std::tuple_size<args_type>::value; + enforce_arg_count(name, args_count, args.via.array.size); + args_type args_real; + args.convert(args_real); + ftl::internal::call(func, p, args_real); + return std::make_unique<msgpack::object_handle>(); + })); + } + + template <typename F> + void bind(std::string const &name, F func, + ftl::internal::tags::nonvoid_result const &, + ftl::internal::tags::zero_arg const &, + ftl::internal::true_ const &) { + using ftl::internal::func_traits; + + enforce_unique_name(name); + funcs_.insert(std::make_pair(name, [func, + name](ftl::net::Peer &p, msgpack::object const &args) { + enforce_arg_count(name, 0, args.via.array.size); + auto z = std::make_unique<msgpack::zone>(); + auto result = msgpack::object(func(p), *z); + return std::make_unique<msgpack::object_handle>(result, std::move(z)); + })); + } + + template <typename F> + void bind(std::string const &name, F func, + ftl::internal::tags::nonvoid_result const &, + ftl::internal::tags::nonzero_arg const &, + ftl::internal::true_ const &) { + using ftl::internal::func_traits; + using args_type = typename func_traits<F>::args_type; + + enforce_unique_name(name); + funcs_.insert(std::make_pair(name, [func, + name](ftl::net::Peer &p, msgpack::object const &args) { + constexpr int args_count = std::tuple_size<args_type>::value; + enforce_arg_count(name, args_count, args.via.array.size); + args_type args_real; + args.convert(args_real); + auto z = std::make_unique<msgpack::zone>(); + auto result = msgpack::object(ftl::internal::call(func, p, args_real), *z); + return std::make_unique<msgpack::object_handle>(result, std::move(z)); + })); + } + + //========================================================================== + void unbind(const std::string &name) { auto i = funcs_.find(name); if (i != funcs_.end()) { @@ -128,9 +226,11 @@ class Dispatcher { } std::vector<std::string> getBindings() const; + + bool isBound(const std::string &name) const; using adaptor_type = std::function<std::unique_ptr<msgpack::object_handle>( - msgpack::object const &)>; + ftl::net::Peer &, msgpack::object const &)>; //! \brief This is the type of messages as per the msgpack-rpc spec. using call_t = std::tuple<int8_t, uint32_t, std::string, msgpack::object>; diff --git a/components/net/cpp/include/ftl/net/func_traits.hpp b/components/net/cpp/include/ftl/net/func_traits.hpp index ae12b335a76d4231c7e497dc0c5440dc7301f5a6..1008f364d514a8652858882bc0883d136dbade9e 100644 --- a/components/net/cpp/include/ftl/net/func_traits.hpp +++ b/components/net/cpp/include/ftl/net/func_traits.hpp @@ -9,6 +9,11 @@ #include <tuple> namespace ftl { + +namespace net { +class Peer; +} + namespace internal { template<typename T> @@ -58,6 +63,12 @@ struct func_traits<R (C::*)(Args...)> : func_traits<R (*)(Args...)> {}; template <typename C, typename R, typename... Args> struct func_traits<R (C::*)(Args...) const> : func_traits<R (*)(Args...)> {}; +template <typename R, typename... Args> struct func_traits<R (*)(ftl::net::Peer &,Args...)> { + using result_type = R; + using arg_count = std::integral_constant<std::size_t, sizeof...(Args)>; + using args_type = std::tuple<typename std::decay<Args>::type...>; +}; + template <typename R, typename... Args> struct func_traits<R (*)(Args...)> { using result_type = R; using arg_count = std::integral_constant<std::size_t, sizeof...(Args)>; @@ -80,9 +91,16 @@ template <typename C, typename R, typename... Args> struct func_kind_info<R (C::*)(Args...) const> : func_kind_info<R (*)(Args...)> {}; +template <typename R, typename... Args> struct func_kind_info<R (*)(ftl::net::Peer &,Args...)> { + typedef typename tags::arg_count_trait<sizeof...(Args)>::type args_kind; + typedef typename tags::result_trait<R>::type result_kind; + typedef true_ has_peer; +}; + template <typename R, typename... Args> struct func_kind_info<R (*)(Args...)> { typedef typename tags::arg_count_trait<sizeof...(Args)>::type args_kind; typedef typename tags::result_trait<R>::type result_kind; + typedef false_ has_peer; }; template <typename F> using is_zero_arg = is_zero<func_traits<F>::arg_count>; diff --git a/components/net/cpp/include/ftl/net/peer.hpp b/components/net/cpp/include/ftl/net/peer.hpp index 976155ac94e323b112306f4fa675512ff3d23aca..4378a25786bdd589157a3aee54cc1584a5a5822b 100644 --- a/components/net/cpp/include/ftl/net/peer.hpp +++ b/components/net/cpp/include/ftl/net/peer.hpp @@ -311,7 +311,8 @@ template <typename F> void Peer::bind(const std::string &name, F func) { disp_->bind(name, func, typename ftl::internal::func_kind_info<F>::result_kind(), - typename ftl::internal::func_kind_info<F>::args_kind()); + typename ftl::internal::func_kind_info<F>::args_kind(), + typename ftl::internal::func_kind_info<F>::has_peer()); } template <typename R, typename... ARGS> diff --git a/components/net/cpp/include/ftl/net/universe.hpp b/components/net/cpp/include/ftl/net/universe.hpp index 29680c601c19ba37ff20771659ef379ce3511631..69d9557397909091abf0e6c924eb05e13566823d 100644 --- a/components/net/cpp/include/ftl/net/universe.hpp +++ b/components/net/cpp/include/ftl/net/universe.hpp @@ -110,6 +110,11 @@ class Universe : public ftl::Configurable { void unbind(const std::string &name); + /** + * Check if an RPC name is already bound. + */ + inline bool isBound(const std::string &name) const { return disp_.isBound(name); } + /** * Subscribe a function to a resource. The subscribed function is * triggered whenever that resource is published to. It is akin to @@ -259,7 +264,8 @@ void Universe::bind(const std::string &name, F func) { UNIQUE_LOCK(net_mutex_,lk); disp_.bind(name, func, typename ftl::internal::func_kind_info<F>::result_kind(), - typename ftl::internal::func_kind_info<F>::args_kind()); + typename ftl::internal::func_kind_info<F>::args_kind(), + typename ftl::internal::func_kind_info<F>::has_peer()); } /*template <typename F> diff --git a/components/net/cpp/src/dispatcher.cpp b/components/net/cpp/src/dispatcher.cpp index 7a5df5091235dc081c0c9e51c199dcca5e7f0f31..eac33f5e134c0ab38194b3726e2036840867eab2 100644 --- a/components/net/cpp/src/dispatcher.cpp +++ b/components/net/cpp/src/dispatcher.cpp @@ -76,7 +76,7 @@ void ftl::net::Dispatcher::dispatch_call(Peer &s, const msgpack::object &msg) { if (func) { //DLOG(INFO) << "Found binding for " << name; try { - auto result = (*func)(args); //->get(); + auto result = (*func)(s, args); //->get(); s._sendResponse(id, result->get()); /*response_t res_obj = std::make_tuple(1,id,msgpack::object(),result->get()); std::stringstream buf; @@ -112,6 +112,10 @@ optional<Dispatcher::adaptor_type> ftl::net::Dispatcher::_locateHandler(const st } } +bool ftl::net::Dispatcher::isBound(const std::string &name) const { + return funcs_.find(name) != funcs_.end(); +} + void ftl::net::Dispatcher::dispatch_notification(Peer &s, msgpack::object const &msg) { notification_t the_call; msg.convert(the_call); @@ -128,7 +132,7 @@ void ftl::net::Dispatcher::dispatch_notification(Peer &s, msgpack::object const if (binding) { try { - auto result = (*binding)(args); + auto result = (*binding)(s, args); } catch (const int &e) { LOG(ERROR) << "Exception in bound function"; throw &e; diff --git a/components/net/cpp/test/net_configurable_unit.cpp b/components/net/cpp/test/net_configurable_unit.cpp index e3fef144490f64ce06375ad8226056afd2f5329f..0b69512f11c0e33f9a83630f76591fec2c353333 100644 --- a/components/net/cpp/test/net_configurable_unit.cpp +++ b/components/net/cpp/test/net_configurable_unit.cpp @@ -7,7 +7,7 @@ using ftl::NetConfigurable; SCENARIO( "NetConfigurable::set()" ) { GIVEN( "valid peer UUID, URI and Master" ) { // Set up Master - nlohmann::json json = {{"$id", "root"}, {"test", {{"listen", "tcp://localhost:7077"}}}}; // Check what values are needed + nlohmann::json json = nlohmann::json{{"$id", "root"}, {"test", {{"listen", "tcp://localhost:7077"}}}}; // Check what values are needed ftl::Configurable *root; root = new ftl::Configurable(json); ftl::net::Universe *net = ftl::config::create<ftl::net::Universe>(root, std::string("test")); @@ -15,7 +15,7 @@ SCENARIO( "NetConfigurable::set()" ) { ftl::ctrl::Master *controller = new ftl::ctrl::Master(root, net); // Set up a slave, then call getControllers() to get the UUID string - nlohmann::json jsonSlave = {{"$id", "slave"}, {"test", {{"peers", {"tcp://localhost:7077"}}}}}; + nlohmann::json jsonSlave = nlohmann::json{{"$id", "slave"}, {"test", {{"peers", {"tcp://localhost:7077"}}}}}; ftl::Configurable *rootSlave; rootSlave = new ftl::Configurable(jsonSlave); ftl::net::Universe *netSlave = ftl::config::create<ftl::net::Universe>(rootSlave, std::string("test")); @@ -29,7 +29,7 @@ SCENARIO( "NetConfigurable::set()" ) { ftl::UUID peer = ftl::UUID(controllers[0]["id"].get<std::string>()); const std::string suri = "slave_test"; - nlohmann::json jsonTest = {{"$id", "slave_test"}, {"test", {{"peers", {"tcp://localhost:7077"}}}}}; + nlohmann::json jsonTest = nlohmann::json{{"$id", "slave_test"}, {"test", {{"peers", {"tcp://localhost:7077"}}}}}; NetConfigurable nc(peer, suri, *controller, jsonTest); nc.set("test_value", 5); REQUIRE( nc.get<int>("test_value") == 5 ); diff --git a/components/operators/include/ftl/operators/antialiasing.hpp b/components/operators/include/ftl/operators/antialiasing.hpp index 302631253f0d1aab6e758cf48c0af213bcf3f002..295729bd361e6a17287b845460068a59e4d7555e 100644 --- a/components/operators/include/ftl/operators/antialiasing.hpp +++ b/components/operators/include/ftl/operators/antialiasing.hpp @@ -17,7 +17,7 @@ class FXAA : public ftl::operators::Operator { inline Operator::Type type() const override { return Operator::Type::OneToOne; } - bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *src, cudaStream_t stream) override; + bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) override; }; diff --git a/components/operators/include/ftl/operators/colours.hpp b/components/operators/include/ftl/operators/colours.hpp index 5f8ba6cd1d6a7194800491cee47ec079ea304eef..788f7b4f50dec2472453d30ebf4351616de228b2 100644 --- a/components/operators/include/ftl/operators/colours.hpp +++ b/components/operators/include/ftl/operators/colours.hpp @@ -13,7 +13,7 @@ class ColourChannels : public ftl::operators::Operator { inline Operator::Type type() const override { return Operator::Type::OneToOne; } - bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *src, cudaStream_t stream) override; + bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) override; private: cv::cuda::GpuMat temp_; diff --git a/components/operators/include/ftl/operators/disparity.hpp b/components/operators/include/ftl/operators/disparity.hpp index e4ff99d7834ecd8c1f07ee7b950f5dd01479f0e1..f45fa244fc922417e359af7fa22653979b108059 100644 --- a/components/operators/include/ftl/operators/disparity.hpp +++ b/components/operators/include/ftl/operators/disparity.hpp @@ -26,7 +26,7 @@ class FixstarsSGM : public ftl::operators::Operator { ~FixstarsSGM(); inline Operator::Type type() const override { return Operator::Type::OneToOne; } - bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *src, cudaStream_t stream) override; + bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) override; private: bool init(); @@ -52,7 +52,7 @@ class DisparityBilateralFilter : public::ftl::operators::Operator { ~DisparityBilateralFilter() {}; inline Operator::Type type() const override { return Operator::Type::OneToOne; } - bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *src, cudaStream_t stream) override; + bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) override; private: cv::Ptr<cv::cuda::DisparityBilateralFilter> filter_; @@ -74,7 +74,7 @@ class DisparityToDepth : public ftl::operators::Operator { ~DisparityToDepth() {}; inline Operator::Type type() const override { return Operator::Type::OneToOne; } - bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *src, cudaStream_t stream) override; + bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) override; }; /** @@ -90,6 +90,7 @@ class DepthChannel : public ftl::operators::Operator { inline Operator::Type type() const override { return Operator::Type::ManyToMany; } bool apply(ftl::rgbd::FrameSet &in, ftl::rgbd::FrameSet &out, cudaStream_t stream) override; + bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) override; private: ftl::operators::Graph *pipe_; @@ -110,7 +111,7 @@ class OpticalFlowTemporalSmoothing : public ftl::operators::Operator { inline Operator::Type type() const override { return Operator::Type::OneToOne; } - bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *src, cudaStream_t stream) override; + bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) override; private: bool init(); diff --git a/components/operators/include/ftl/operators/filling.hpp b/components/operators/include/ftl/operators/filling.hpp index 2825dde5f6840658d7c49dd374792e80952e9391..6de380213ba4fb69bbf966a87db11a7de81f0946 100644 --- a/components/operators/include/ftl/operators/filling.hpp +++ b/components/operators/include/ftl/operators/filling.hpp @@ -17,7 +17,7 @@ class ScanFieldFill : public ftl::operators::Operator { inline Operator::Type type() const override { return Operator::Type::OneToOne; } - bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *src, cudaStream_t stream) override; + bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) override; }; @@ -28,7 +28,7 @@ class CrossSupportFill : public ftl::operators::Operator { inline Operator::Type type() const override { return Operator::Type::OneToOne; } - bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *src, cudaStream_t stream) override; + bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) override; }; diff --git a/components/operators/include/ftl/operators/mask.hpp b/components/operators/include/ftl/operators/mask.hpp index 579b1f6fe83daf7402041fe139116abe09ee3234..5c542207dbf7216658b2de0d85cf446808db76d3 100644 --- a/components/operators/include/ftl/operators/mask.hpp +++ b/components/operators/include/ftl/operators/mask.hpp @@ -19,7 +19,7 @@ class DiscontinuityMask : public ftl::operators::Operator { inline Operator::Type type() const override { return Operator::Type::OneToOne; } - bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *src, cudaStream_t stream) override; + bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) override; }; @@ -33,7 +33,7 @@ class CullDiscontinuity : public ftl::operators::Operator { inline Operator::Type type() const override { return Operator::Type::OneToOne; } - bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *src, cudaStream_t stream) override; + bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) override; }; diff --git a/components/operators/include/ftl/operators/normals.hpp b/components/operators/include/ftl/operators/normals.hpp index 5aff09e4da3b5867a77631c81750c42a6c23fdbd..a46d2ff43449deebd2f9a12a87ebebdc2bb3a231 100644 --- a/components/operators/include/ftl/operators/normals.hpp +++ b/components/operators/include/ftl/operators/normals.hpp @@ -17,7 +17,7 @@ class Normals : public ftl::operators::Operator { inline Operator::Type type() const override { return Operator::Type::OneToOne; } - bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *src, cudaStream_t stream) override; + bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) override; }; @@ -32,7 +32,7 @@ class SmoothNormals : public ftl::operators::Operator { inline Operator::Type type() const override { return Operator::Type::OneToOne; } - bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *src, cudaStream_t stream) override; + bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) override; private: ftl::cuda::TextureObject<float4> temp_; diff --git a/components/operators/include/ftl/operators/operator.hpp b/components/operators/include/ftl/operators/operator.hpp index bc47afef9a976d21e1cb934a4d491a1575f7022d..71d2e939ad5cb90f024a040d3f14a859c35c3936 100644 --- a/components/operators/include/ftl/operators/operator.hpp +++ b/components/operators/include/ftl/operators/operator.hpp @@ -37,9 +37,9 @@ class Operator { */ virtual Type type() const =0; - virtual bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *s, cudaStream_t stream); + virtual bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream); virtual bool apply(ftl::rgbd::FrameSet &in, ftl::rgbd::FrameSet &out, cudaStream_t stream); - virtual bool apply(ftl::rgbd::FrameSet &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *os, cudaStream_t stream); + virtual bool apply(ftl::rgbd::FrameSet &in, ftl::rgbd::Frame &out, cudaStream_t stream); inline void enable() { enabled_ = true; } inline void disable() { enabled_ = false; } @@ -92,9 +92,9 @@ class Graph : public ftl::Configurable { template <typename T> ftl::Configurable *append(const std::string &name); - bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *s, cudaStream_t stream=0); + bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream=0); bool apply(ftl::rgbd::FrameSet &in, ftl::rgbd::FrameSet &out, cudaStream_t stream=0); - bool apply(ftl::rgbd::FrameSet &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *s, cudaStream_t stream=0); + bool apply(ftl::rgbd::FrameSet &in, ftl::rgbd::Frame &out, cudaStream_t stream=0); private: std::list<ftl::operators::detail::OperatorNode> operators_; diff --git a/components/operators/include/ftl/operators/opticalflow.hpp b/components/operators/include/ftl/operators/opticalflow.hpp index 81afc3914b3a3d31975deca14383f319588209d5..e80160631e3cb3270d71da12a72e896231ebd0fd 100644 --- a/components/operators/include/ftl/operators/opticalflow.hpp +++ b/components/operators/include/ftl/operators/opticalflow.hpp @@ -17,7 +17,7 @@ class NVOpticalFlow : public ftl::operators::Operator { inline Operator::Type type() const override { return Operator::Type::OneToOne; } - bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *src, cudaStream_t stream) override; + bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) override; protected: bool init(); diff --git a/components/operators/include/ftl/operators/segmentation.hpp b/components/operators/include/ftl/operators/segmentation.hpp index f905ce96966d0f68171ee94d209b6d888581005a..d7447615c6d5230f997f9dab857b1ba824b22ce2 100644 --- a/components/operators/include/ftl/operators/segmentation.hpp +++ b/components/operators/include/ftl/operators/segmentation.hpp @@ -16,7 +16,7 @@ class CrossSupport : public ftl::operators::Operator { inline Operator::Type type() const override { return Operator::Type::OneToOne; } - bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *src, cudaStream_t stream) override; + bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) override; }; @@ -30,7 +30,7 @@ class VisCrossSupport : public ftl::operators::Operator { inline Operator::Type type() const override { return Operator::Type::OneToOne; } - bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *src, cudaStream_t stream) override; + bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) override; }; diff --git a/components/operators/include/ftl/operators/smoothing.hpp b/components/operators/include/ftl/operators/smoothing.hpp index 6e0c6ff75e382f8a54d99587f545195c70d59e6b..2714af64fa498e6c22bdf4eb7cd242c0d5773e2d 100644 --- a/components/operators/include/ftl/operators/smoothing.hpp +++ b/components/operators/include/ftl/operators/smoothing.hpp @@ -19,7 +19,7 @@ class HFSmoother : public ftl::operators::Operator { inline Operator::Type type() const override { return Operator::Type::OneToOne; } - bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *src, cudaStream_t stream) override; + bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) override; private: cv::cuda::GpuMat temp_; @@ -40,7 +40,7 @@ class SmoothChannel : public ftl::operators::Operator { inline Operator::Type type() const override { return Operator::Type::OneToOne; } - bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *src, cudaStream_t stream) override; + bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) override; private: ftl::rgbd::Frame temp_[6]; @@ -58,7 +58,7 @@ class SimpleMLS : public ftl::operators::Operator { inline Operator::Type type() const override { return Operator::Type::OneToOne; } - bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *src, cudaStream_t stream) override; + bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) override; private: }; @@ -74,7 +74,7 @@ class ColourMLS : public ftl::operators::Operator { inline Operator::Type type() const override { return Operator::Type::OneToOne; } - bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *src, cudaStream_t stream) override; + bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) override; private: }; @@ -116,7 +116,7 @@ class AggreMLS : public ftl::operators::Operator { inline Operator::Type type() const override { return Operator::Type::OneToOne; } - bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *src, cudaStream_t stream) override; + bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) override; private: ftl::cuda::TextureObject<float4> centroid_horiz_; @@ -138,7 +138,7 @@ class AdaptiveMLS : public ftl::operators::Operator { inline Operator::Type type() const override { return Operator::Type::OneToOne; } - bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *src, cudaStream_t stream) override; + bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) override; private: }; diff --git a/components/operators/src/antialiasing.cpp b/components/operators/src/antialiasing.cpp index 575cc26a5183f38dbaa0c861bac5a0cbe70ace18..00bfe3890983f060fe490e003e6c849ab0c47357 100644 --- a/components/operators/src/antialiasing.cpp +++ b/components/operators/src/antialiasing.cpp @@ -12,7 +12,7 @@ FXAA::~FXAA() { } -bool FXAA::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *s, cudaStream_t stream) { +bool FXAA::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) { ftl::cuda::fxaa( in.getTexture<uchar4>(Channel::Colour), stream diff --git a/components/operators/src/clipping.cpp b/components/operators/src/clipping.cpp index b37abe2c33b55b86b697565d1565b523c073d80f..2d13f65f662973f0dc6c6a47508c97cc8e143a47 100644 --- a/components/operators/src/clipping.cpp +++ b/components/operators/src/clipping.cpp @@ -47,13 +47,13 @@ bool ClipScene::apply(ftl::rgbd::FrameSet &in, ftl::rgbd::FrameSet &out, cudaStr for (size_t i=0; i<in.frames.size(); ++i) { auto &f = in.frames[i]; - auto *s = in.sources[i]; + //auto *s = in.sources[i]; - auto pose = MatrixConversion::toCUDA(s->getPose().cast<float>()); + auto pose = MatrixConversion::toCUDA(f.getPose().cast<float>()); auto sclip = clip; sclip.origin = sclip.origin * pose; - ftl::cuda::clipping(f.createTexture<float>(Channel::Depth), s->parameters(), sclip, stream); + ftl::cuda::clipping(f.createTexture<float>(Channel::Depth), f.getLeftCamera(), sclip, stream); } return true; diff --git a/components/operators/src/colours.cpp b/components/operators/src/colours.cpp index 267bcb8ca7b9dfa7f748fa3d2d3482bae5c5abe0..4d8939f0134eaa018f6b2b51995825b21b522d9e 100644 --- a/components/operators/src/colours.cpp +++ b/components/operators/src/colours.cpp @@ -11,7 +11,7 @@ ColourChannels::~ColourChannels() { } -bool ColourChannels::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *s, cudaStream_t stream) { +bool ColourChannels::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) { auto cvstream = cv::cuda::StreamAccessor::wrapStream(stream); auto &col = in.get<cv::cuda::GpuMat>(Channel::Colour); diff --git a/components/operators/src/depth.cpp b/components/operators/src/depth.cpp index 7122513b1e6e90ea98533972391481dffc2f3e7c..8983f33add79c6a3d6239c4660580a04299aa6da 100644 --- a/components/operators/src/depth.cpp +++ b/components/operators/src/depth.cpp @@ -68,9 +68,43 @@ bool DepthChannel::apply(ftl::rgbd::FrameSet &in, ftl::rgbd::FrameSet &out, cuda cv::cuda::swap(right, rbuf_[i]); }*/ - pipe_->apply(f, f, in.sources[i], stream); + pipe_->apply(f, f, stream); } } return true; } + +bool DepthChannel::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) { + auto cvstream = cv::cuda::StreamAccessor::wrapStream(stream); + + //rbuf_.resize(1); + + auto &f = in; + if (!f.hasChannel(Channel::Depth) && f.hasChannel(Channel::Right)) { + _createPipeline(); + + cv::cuda::GpuMat& left = f.get<cv::cuda::GpuMat>(Channel::Left); + cv::cuda::GpuMat& right = f.get<cv::cuda::GpuMat>(Channel::Right); + cv::cuda::GpuMat& depth = f.create<cv::cuda::GpuMat>(Channel::Depth); + depth.create(depth_size_, CV_32FC1); + + if (left.empty() || right.empty()) return false; + + /*if (depth_size_ != left.size()) { + auto &col2 = f.create<cv::cuda::GpuMat>(Channel::ColourHighRes); + cv::cuda::resize(left, col2, depth_size_, 0.0, 0.0, cv::INTER_CUBIC, cvstream); + f.createTexture<uchar4>(Channel::ColourHighRes, true); + f.swapChannels(Channel::Colour, Channel::ColourHighRes); + } + + if (depth_size_ != right.size()) { + cv::cuda::resize(right, rbuf_[i], depth_size_, 0.0, 0.0, cv::INTER_CUBIC, cvstream); + cv::cuda::swap(right, rbuf_[i]); + }*/ + + pipe_->apply(f, f, stream); + } + + return true; +} diff --git a/components/operators/src/disparity/bilateral_filter.cpp b/components/operators/src/disparity/bilateral_filter.cpp index 13f2ed1eae60a2de3b3ad8766c395855929bd3fe..0c766596e1307417c23c51602edc72ec954adda3 100644 --- a/components/operators/src/disparity/bilateral_filter.cpp +++ b/components/operators/src/disparity/bilateral_filter.cpp @@ -20,7 +20,7 @@ DisparityBilateralFilter::DisparityBilateralFilter(ftl::Configurable* cfg) : } bool DisparityBilateralFilter::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, - ftl::rgbd::Source *src, cudaStream_t stream) { + cudaStream_t stream) { if (!in.hasChannel(Channel::Colour)) { LOG(ERROR) << "Joint Bilateral Filter is missing Colour"; @@ -29,7 +29,7 @@ bool DisparityBilateralFilter::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out // Have depth, so calculate disparity... if (in.hasChannel(Channel::Depth)) { // No disparity, so create it. - const auto params = src->parameters(); + const auto params = in.getLeftCamera(); const GpuMat &depth = in.get<GpuMat>(Channel::Depth); GpuMat &disp = out.create<GpuMat>(Channel::Disparity); diff --git a/components/operators/src/disparity/disparity_to_depth.cpp b/components/operators/src/disparity/disparity_to_depth.cpp index d3cc337728d81b1e73adba27283b935a7878ed04..18a284915f449c34b5b2d9547ed483ef57c9e4dd 100644 --- a/components/operators/src/disparity/disparity_to_depth.cpp +++ b/components/operators/src/disparity/disparity_to_depth.cpp @@ -7,14 +7,14 @@ using ftl::codecs::Channel; using cv::cuda::GpuMat; bool DisparityToDepth::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, - ftl::rgbd::Source *src, cudaStream_t stream) { + cudaStream_t stream) { if (!in.hasChannel(Channel::Disparity)) { LOG(ERROR) << "Missing disparity before convert to depth"; return false; } - const auto params = src->parameters(); + const auto params = in.getLeftCamera(); const GpuMat &disp = in.get<GpuMat>(Channel::Disparity); GpuMat &depth = out.create<GpuMat>(Channel::Depth); diff --git a/components/operators/src/disparity/fixstars_sgm.cpp b/components/operators/src/disparity/fixstars_sgm.cpp index ae15033a2d59f8bf6f235968837b0be4641d04ab..4d605f88a3dbd310a610332f31df217b21f46f3c 100644 --- a/components/operators/src/disparity/fixstars_sgm.cpp +++ b/components/operators/src/disparity/fixstars_sgm.cpp @@ -106,7 +106,7 @@ bool FixstarsSGM::updateParameters() { sgm::StereoSGM::Parameters(P1_, P2_, uniqueness_, true)); } -bool FixstarsSGM::apply(Frame &in, Frame &out, Source *src, cudaStream_t stream) { +bool FixstarsSGM::apply(Frame &in, Frame &out, cudaStream_t stream) { if (!in.hasChannel(Channel::Left) || !in.hasChannel(Channel::Right)) { LOG(ERROR) << "Fixstars is missing Left or Right channel"; return false; diff --git a/components/operators/src/disparity/optflow_smoothing.cpp b/components/operators/src/disparity/optflow_smoothing.cpp index efd5d1cc18f41ddd9bff9ee219b4235d48968ef3..71f379c9b26868975a74e371b4e5cc39a4d730fa 100644 --- a/components/operators/src/disparity/optflow_smoothing.cpp +++ b/components/operators/src/disparity/optflow_smoothing.cpp @@ -74,7 +74,7 @@ bool OpticalFlowTemporalSmoothing::init() { return true; } -bool OpticalFlowTemporalSmoothing::apply(Frame &in, Frame &out, Source *src, cudaStream_t stream) { +bool OpticalFlowTemporalSmoothing::apply(Frame &in, Frame &out, cudaStream_t stream) { if (!out.hasChannel(channel_) || !in.hasChannel(Channel::Flow)) { return false; } auto cvstream = cv::cuda::StreamAccessor::wrapStream(stream); diff --git a/components/operators/src/filling.cpp b/components/operators/src/filling.cpp index da7efea6bdf085eb797bc56500f4e783e30f6162..87298cc052cb8d48673a6c2ce13943c35a68caee 100644 --- a/components/operators/src/filling.cpp +++ b/components/operators/src/filling.cpp @@ -14,7 +14,7 @@ ScanFieldFill::~ScanFieldFill() { } -bool ScanFieldFill::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *s, cudaStream_t stream) { +bool ScanFieldFill::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) { float thresh = config()->value("threshold", 0.1f); ftl::cuda::scan_field_fill( @@ -22,7 +22,7 @@ bool ScanFieldFill::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd in.createTexture<float>(Channel::Depth), in.createTexture<float>(Channel::Smoothing), thresh, - s->parameters(), stream + in.getLeftCamera(), stream ); return true; @@ -37,7 +37,7 @@ CrossSupportFill::~CrossSupportFill() { } -bool CrossSupportFill::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *s, cudaStream_t stream) { +bool CrossSupportFill::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) { /*ftl::cuda::filling_csr( in.createTexture<uchar4>(Channel::Colour2), diff --git a/components/operators/src/mask.cpp b/components/operators/src/mask.cpp index e914a5ad8dd8959b840394a543f789fc5252db08..8a8fdaa4b5599ced36cc89d90695a5b8ff20fe81 100644 --- a/components/operators/src/mask.cpp +++ b/components/operators/src/mask.cpp @@ -14,7 +14,7 @@ DiscontinuityMask::~DiscontinuityMask() { } -bool DiscontinuityMask::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *s, cudaStream_t stream) { +bool DiscontinuityMask::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) { if (in.hasChannel(Channel::Mask)) return true; int radius = config()->value("radius", 2); @@ -27,7 +27,7 @@ bool DiscontinuityMask::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl:: in.createTexture<uchar4>(Channel::Support1), in.createTexture<float>(Channel::Depth), in.get<cv::cuda::GpuMat>(Channel::Depth).size(), - s->parameters().minDepth, s->parameters().maxDepth, + in.getLeftCamera().minDepth, in.getLeftCamera().maxDepth, radius, threshold, stream ); @@ -44,7 +44,8 @@ CullDiscontinuity::~CullDiscontinuity() { } -bool CullDiscontinuity::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *s, cudaStream_t stream) { +bool CullDiscontinuity::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) { + out.clearPackets(Channel::Depth); // Force reset ftl::cuda::cull_discontinuity( in.createTexture<int>(Channel::Mask), out.createTexture<float>(Channel::Depth), diff --git a/components/operators/src/mvmls.cpp b/components/operators/src/mvmls.cpp index 8ff3c89dd8ce9f10882e4c5386b4de631f7836b9..3a71dcf4c1350d454751319a38a6ed36c554009c 100644 --- a/components/operators/src/mvmls.cpp +++ b/components/operators/src/mvmls.cpp @@ -89,7 +89,7 @@ bool MultiViewMLS::apply(ftl::rgbd::FrameSet &in, ftl::rgbd::FrameSet &out, cuda //f1.get<GpuMat>(Channel::Confidence).setTo(cv::Scalar(0.0f), cvstream); Eigen::Vector4d d1(0.0, 0.0, 1.0, 0.0); - d1 = in.sources[i]->getPose() * d1; + d1 = f1.getPose() * d1; for (size_t j=0; j<in.frames.size(); ++j) { if (i == j) continue; @@ -97,16 +97,16 @@ bool MultiViewMLS::apply(ftl::rgbd::FrameSet &in, ftl::rgbd::FrameSet &out, cuda //LOG(INFO) << "Running phase1"; auto &f2 = in.frames[j]; - auto s1 = in.sources[i]; - auto s2 = in.sources[j]; + //auto s1 = in.sources[i]; + //auto s2 = in.sources[j]; // Are cameras facing similar enough direction? Eigen::Vector4d d2(0.0, 0.0, 1.0, 0.0); - d2 = in.sources[j]->getPose() * d2; + d2 = f2.getPose() * d2; // No, so skip this combination if (d1.dot(d2) <= 0.0) continue; - auto pose2 = MatrixConversion::toCUDA(s2->getPose().cast<float>().inverse() * s1->getPose().cast<float>()); + auto pose2 = MatrixConversion::toCUDA(f2.getPose().cast<float>().inverse() * f1.getPose().cast<float>()); //auto transform = pose2 * pose1; @@ -121,8 +121,8 @@ bool MultiViewMLS::apply(ftl::rgbd::FrameSet &in, ftl::rgbd::FrameSet &out, cuda f1.getTexture<float>(Channel::Confidence), f1.getTexture<int>(Channel::Mask), pose2, - s1->parameters(), - s2->parameters(), + f1.getLeftCamera(), + f2.getLeftCamera(), params, win, stream @@ -200,7 +200,7 @@ bool MultiViewMLS::apply(ftl::rgbd::FrameSet &in, ftl::rgbd::FrameSet &out, cuda // But don't do the final move step. for (size_t i=0; i<in.frames.size(); ++i) { auto &f = in.frames[i]; - auto *s = in.sources[i]; + //auto *s = in.sources[i]; // Clear data cv::cuda::GpuMat data(contributions_[i].height(), contributions_[i].width(), CV_32F, contributions_[i].pixelPitch()); @@ -224,7 +224,7 @@ bool MultiViewMLS::apply(ftl::rgbd::FrameSet &in, ftl::rgbd::FrameSet &out, cuda thresh, col_smooth, radius, - s->parameters(), + f.getLeftCamera(), stream ); @@ -239,7 +239,7 @@ bool MultiViewMLS::apply(ftl::rgbd::FrameSet &in, ftl::rgbd::FrameSet &out, cuda thresh, col_smooth, radius, - s->parameters(), + f.getLeftCamera(), stream ); } @@ -254,7 +254,7 @@ bool MultiViewMLS::apply(ftl::rgbd::FrameSet &in, ftl::rgbd::FrameSet &out, cuda //f1.get<GpuMat>(Channel::Confidence).setTo(cv::Scalar(0.0f), cvstream); Eigen::Vector4d d1(0.0, 0.0, 1.0, 0.0); - d1 = in.sources[i]->getPose() * d1; + d1 = f1.getPose() * d1; for (size_t j=0; j<in.frames.size(); ++j) { if (i == j) continue; @@ -262,19 +262,19 @@ bool MultiViewMLS::apply(ftl::rgbd::FrameSet &in, ftl::rgbd::FrameSet &out, cuda //LOG(INFO) << "Running phase1"; auto &f2 = in.frames[j]; - auto s1 = in.sources[i]; - auto s2 = in.sources[j]; + //auto s1 = in.sources[i]; + //auto s2 = in.sources[j]; // Are cameras facing similar enough direction? Eigen::Vector4d d2(0.0, 0.0, 1.0, 0.0); - d2 = in.sources[j]->getPose() * d2; + d2 = f2.getPose() * d2; // No, so skip this combination if (d1.dot(d2) <= 0.0) continue; - auto pose1 = MatrixConversion::toCUDA(s1->getPose().cast<float>()); - auto pose1_inv = MatrixConversion::toCUDA(s1->getPose().cast<float>().inverse()); - auto pose2 = MatrixConversion::toCUDA(s2->getPose().cast<float>().inverse()); - auto pose2_inv = MatrixConversion::toCUDA(s2->getPose().cast<float>()); + auto pose1 = MatrixConversion::toCUDA(f1.getPose().cast<float>()); + auto pose1_inv = MatrixConversion::toCUDA(f1.getPose().cast<float>().inverse()); + auto pose2 = MatrixConversion::toCUDA(f2.getPose().cast<float>().inverse()); + auto pose2_inv = MatrixConversion::toCUDA(f2.getPose().cast<float>()); auto transform = pose2 * pose1; @@ -289,8 +289,8 @@ bool MultiViewMLS::apply(ftl::rgbd::FrameSet &in, ftl::rgbd::FrameSet &out, cuda //contributions_[j], //f1.getTexture<short2>(Channel::Screen), transform, - s1->parameters(), - s2->parameters(), + f1.getLeftCamera(), + f2.getLeftCamera(), stream ); @@ -303,7 +303,7 @@ bool MultiViewMLS::apply(ftl::rgbd::FrameSet &in, ftl::rgbd::FrameSet &out, cuda // Normalise aggregations and move the points for (size_t i=0; i<in.frames.size(); ++i) { auto &f = in.frames[i]; - auto *s = in.sources[i]; + //auto *s = in.sources[i]; auto size = f.get<GpuMat>(Channel::Depth).size(); /*if (do_corr) { @@ -320,7 +320,7 @@ bool MultiViewMLS::apply(ftl::rgbd::FrameSet &in, ftl::rgbd::FrameSet &out, cuda centroid_vert_[i], f.getTexture<float>(Channel::Depth), f.createTexture<float>(Channel::Depth2, ftl::rgbd::Format<float>(size)), - s->parameters(), + f.getLeftCamera(), stream ); diff --git a/components/operators/src/normals.cpp b/components/operators/src/normals.cpp index 65fcef37bac5b55f16e9f1d07ac7e12f1c58cfad..8e2ab7d3f52b0b3a69d895c211f7bd90780c76fc 100644 --- a/components/operators/src/normals.cpp +++ b/components/operators/src/normals.cpp @@ -15,7 +15,7 @@ Normals::~Normals() { } -bool Normals::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *s, cudaStream_t stream) { +bool Normals::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) { if (!in.hasChannel(Channel::Depth)) { LOG(ERROR) << "Missing depth channel in Normals operator"; return false; @@ -29,7 +29,7 @@ bool Normals::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Sour ftl::cuda::normals( out.createTexture<float4>(Channel::Normals, ftl::rgbd::Format<float4>(in.get<cv::cuda::GpuMat>(Channel::Depth).size())), in.createTexture<float>(Channel::Depth), - s->parameters(), stream + in.getLeftCamera(), stream ); return true; @@ -44,7 +44,7 @@ SmoothNormals::~SmoothNormals() { } -bool SmoothNormals::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *s, cudaStream_t stream) { +bool SmoothNormals::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) { float smoothing = config()->value("normal_smoothing", 0.02f); int radius = max(0, min(config()->value("radius",1), 5)); @@ -67,9 +67,9 @@ bool SmoothNormals::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd temp_, in.createTexture<float>(Channel::Depth), radius, smoothing, - s->parameters(), - MatrixConversion::toCUDA(s->getPose().cast<float>().inverse()).getFloat3x3(), - MatrixConversion::toCUDA(s->getPose().cast<float>()).getFloat3x3(), + in.getLeftCamera(), + MatrixConversion::toCUDA(in.getPose().cast<float>().inverse()).getFloat3x3(), + MatrixConversion::toCUDA(in.getPose().cast<float>()).getFloat3x3(), stream ); diff --git a/components/operators/src/nvopticalflow.cpp b/components/operators/src/nvopticalflow.cpp index a8b6d406bc7421d48d0cfb15de40d0eb48cafebe..6ce98d757d076496077a13b5b0dc64f8e51560cb 100644 --- a/components/operators/src/nvopticalflow.cpp +++ b/components/operators/src/nvopticalflow.cpp @@ -28,7 +28,7 @@ bool NVOpticalFlow::init() { return true; } -bool NVOpticalFlow::apply(Frame &in, Frame &out, Source *src, cudaStream_t stream) { +bool NVOpticalFlow::apply(Frame &in, Frame &out, cudaStream_t stream) { if (!in.hasChannel(channel_in_)) { return false; } if (in.get<GpuMat>(channel_in_).size() != size_) { diff --git a/components/operators/src/operator.cpp b/components/operators/src/operator.cpp index 6b796267d6f34bfde24e71d0a13afdd262c1ac45..ff1ebc6693e06ab7f5c6563470ef681c135e5deb 100644 --- a/components/operators/src/operator.cpp +++ b/components/operators/src/operator.cpp @@ -16,15 +16,18 @@ Operator::Operator(ftl::Configurable *config) : config_(config) { Operator::~Operator() {} -bool Operator::apply(Frame &in, Frame &out, Source *s, cudaStream_t stream) { +bool Operator::apply(Frame &in, Frame &out, cudaStream_t stream) { + LOG(ERROR) << "Operation application to frame not supported"; return false; } bool Operator::apply(FrameSet &in, FrameSet &out, cudaStream_t stream) { + LOG(ERROR) << "Operation application to frameset not supported"; return false; } -bool Operator::apply(FrameSet &in, Frame &out, Source *os, cudaStream_t stream) { +bool Operator::apply(FrameSet &in, Frame &out, cudaStream_t stream) { + LOG(ERROR) << "Operation application as a reduction not supported"; return false; } @@ -55,7 +58,7 @@ bool Graph::apply(FrameSet &in, FrameSet &out, cudaStream_t stream) { //while (i.instances.size() < in.frames.size()) { //i.instances.push_back(i.maker->make()); //} - if (in.frames.size() > 1) { + if (in.frames.size() > 1 && i.instances.size() < 2) { i.instances.push_back(i.maker->make()); } @@ -64,7 +67,7 @@ bool Graph::apply(FrameSet &in, FrameSet &out, cudaStream_t stream) { if (instance->enabled()) { try { - if (!instance->apply(in.frames[j], out.frames[j], in.sources[j], stream_actual)) return false; + if (!instance->apply(in.frames[j], out.frames[j], stream_actual)) return false; } catch (const std::exception &e) { LOG(ERROR) << "Operator exception: " << e.what(); } @@ -91,9 +94,11 @@ bool Graph::apply(FrameSet &in, FrameSet &out, cudaStream_t stream) { return true; } -bool Graph::apply(Frame &in, Frame &out, Source *s, cudaStream_t stream) { +bool Graph::apply(Frame &in, Frame &out, cudaStream_t stream) { if (!value("enabled", true)) return false; + auto stream_actual = (stream == 0) ? stream_ : stream; + for (auto &i : operators_) { // Make sure there are enough instances if (i.instances.size() < 1) { @@ -103,10 +108,15 @@ bool Graph::apply(Frame &in, Frame &out, Source *s, cudaStream_t stream) { auto *instance = i.instances[0]; if (instance->enabled()) { - if (!instance->apply(in, out, s, stream)) return false; + if (!instance->apply(in, out, stream_actual)) return false; } } + if (stream == 0) { + cudaStreamSynchronize(stream_actual); + cudaSafeCall( cudaGetLastError() ); + } + return true; } diff --git a/components/operators/src/segmentation.cpp b/components/operators/src/segmentation.cpp index 005c436d2dd25f158a92cd511825a13c785b4977..32de62112d856cb186c6db21bf3534d0733246ce 100644 --- a/components/operators/src/segmentation.cpp +++ b/components/operators/src/segmentation.cpp @@ -13,7 +13,7 @@ CrossSupport::~CrossSupport() { } -bool CrossSupport::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *s, cudaStream_t stream) { +bool CrossSupport::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) { bool use_mask = config()->value("discon_support", false); @@ -51,7 +51,7 @@ VisCrossSupport::~VisCrossSupport() { } -bool VisCrossSupport::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *s, cudaStream_t stream) { +bool VisCrossSupport::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) { bool show_depth = false; if (in.hasChannel(Channel::Support2) && config()->value("show_depth_support", false)) { show_depth = true; diff --git a/components/operators/src/smoothing.cpp b/components/operators/src/smoothing.cpp index 2eafe7977a827cd0a1e90017f749fd1245ce1b68..dd403d4b8508d66daf522a02714e8bcb4c8e09df 100644 --- a/components/operators/src/smoothing.cpp +++ b/components/operators/src/smoothing.cpp @@ -21,7 +21,7 @@ HFSmoother::~HFSmoother() { } -bool HFSmoother::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *s, cudaStream_t stream) { +bool HFSmoother::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) { float var_thresh = config()->value("variance_threshold", 0.0002f); int levels = max(0, min(config()->value("levels",0), 4)); int iters = config()->value("iterations",5); @@ -34,7 +34,7 @@ bool HFSmoother::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::S in.createTexture<float>(Channel::Energy, ftl::rgbd::Format<float>(in.get<cv::cuda::GpuMat>(Channel::Depth).size())), in.createTexture<float>(Channel::Smoothing, ftl::rgbd::Format<float>(in.get<cv::cuda::GpuMat>(Channel::Depth).size())), var_thresh, - s->parameters(), stream + in.getLeftCamera(), stream ); } @@ -72,13 +72,13 @@ SmoothChannel::~SmoothChannel() { } -bool SmoothChannel::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *s, cudaStream_t stream) { +bool SmoothChannel::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) { int radius = config()->value("radius", 3); float threshold = config()->value("threshold", 30.0f); int iters = max(0, min(6, config()->value("levels", 4))); - int width = s->parameters().width; - int height = s->parameters().height; + int width = in.getLeftCamera().width; + int height = in.getLeftCamera().height; float scale = 1.0f; // Clear to max smoothing @@ -89,7 +89,7 @@ bool SmoothChannel::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd in.createTexture<uchar4>(Channel::Colour), //in.createTexture<float>(Channel::Depth), out.createTexture<float>(Channel::Smoothing), - s->parameters(), + in.getLeftCamera(), threshold, scale, radius, @@ -101,7 +101,7 @@ bool SmoothChannel::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd height /= 2; scale *= 2.0f; - ftl::rgbd::Camera scaledCam = s->parameters().scaled(width, height); + ftl::rgbd::Camera scaledCam = in.getLeftCamera().scaled(width, height); // Downscale images for next pass cv::cuda::resize(in.get<GpuMat>(Channel::Colour), temp_[i].create<GpuMat>(Channel::Colour), cv::Size(width, height), 0.0, 0.0, cv::INTER_LINEAR); @@ -134,7 +134,7 @@ SimpleMLS::~SimpleMLS() { } -bool SimpleMLS::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *s, cudaStream_t stream) { +bool SimpleMLS::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) { float thresh = config()->value("mls_threshold", 0.04f); int iters = config()->value("mls_iterations", 1); int radius = config()->value("mls_radius",2); @@ -158,7 +158,7 @@ bool SimpleMLS::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::So in.createTexture<float>(Channel::Depth2, ftl::rgbd::Format<float>(in.get<cv::cuda::GpuMat>(Channel::Depth).size())), thresh, radius, - s->parameters(), + in.getLeftCamera(), stream ); @@ -179,7 +179,7 @@ ColourMLS::~ColourMLS() { } -bool ColourMLS::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *s, cudaStream_t stream) { +bool ColourMLS::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) { float thresh = config()->value("mls_threshold", 0.4f); float col_smooth = config()->value("mls_colour_smoothing", 30.0f); int iters = config()->value("mls_iterations", 3); @@ -204,7 +204,7 @@ bool ColourMLS::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::So thresh, col_smooth, radius, - s->parameters(), + in.getLeftCamera(), stream ); } else { @@ -218,7 +218,7 @@ bool ColourMLS::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::So thresh, col_smooth, filling, - s->parameters(), + in.getLeftCamera(), stream ); } @@ -241,7 +241,7 @@ AggreMLS::~AggreMLS() { } -bool AggreMLS::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *s, cudaStream_t stream) { +bool AggreMLS::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) { float thresh = config()->value("mls_threshold", 0.4f); float col_smooth = config()->value("mls_colour_smoothing", 30.0f); int iters = config()->value("mls_iterations", 3); @@ -277,7 +277,7 @@ bool AggreMLS::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Sou thresh, col_smooth, radius, - s->parameters(), + in.getLeftCamera(), stream ); @@ -292,7 +292,7 @@ bool AggreMLS::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Sou thresh, col_smooth, radius, - s->parameters(), + in.getLeftCamera(), stream ); @@ -301,7 +301,7 @@ bool AggreMLS::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Sou centroid_vert_, in.createTexture<float>(Channel::Depth), in.createTexture<float>(Channel::Depth2, ftl::rgbd::Format<float>(size)), - s->parameters(), + in.getLeftCamera(), stream ); @@ -319,7 +319,7 @@ bool AggreMLS::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Sou thresh, col_smooth, false, - s->parameters(), + in.getLeftCamera(), stream ); @@ -342,7 +342,7 @@ AdaptiveMLS::~AdaptiveMLS() { } -bool AdaptiveMLS::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *s, cudaStream_t stream) { +bool AdaptiveMLS::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) { int iters = config()->value("mls_iterations", 1); int radius = config()->value("mls_radius",2); @@ -360,7 +360,7 @@ bool AdaptiveMLS::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd:: in.createTexture<float>(Channel::Depth2, ftl::rgbd::Format<float>(in.get<cv::cuda::GpuMat>(Channel::Depth).size())), in.createTexture<float>(Channel::Smoothing), radius, - s->parameters(), + in.getLeftCamera(), stream ); diff --git a/components/renderers/cpp/CMakeLists.txt b/components/renderers/cpp/CMakeLists.txt index 112650feee5853b71c8c3cd7f1e265c3e2c0e564..0d292b397c07d5406ab68e9d8084fb27cc9129b7 100644 --- a/components/renderers/cpp/CMakeLists.txt +++ b/components/renderers/cpp/CMakeLists.txt @@ -1,5 +1,4 @@ add_library(ftlrender - src/splatter.cpp src/splatter.cu src/points.cu src/normals.cu diff --git a/components/renderers/cpp/include/ftl/render/renderer.hpp b/components/renderers/cpp/include/ftl/render/renderer.hpp index 605fa27d182fec5c6463faff397af83b25b43d85..3a36655147fc58a34c2b5605f28f949d93238fab 100644 --- a/components/renderers/cpp/include/ftl/render/renderer.hpp +++ b/components/renderers/cpp/include/ftl/render/renderer.hpp @@ -2,7 +2,7 @@ #define _FTL_RENDER_RENDERER_HPP_ #include <ftl/configurable.hpp> -#include <ftl/rgbd/virtual.hpp> +#include <ftl/rgbd/frameset.hpp> #include <ftl/cuda_common.hpp> namespace ftl { @@ -22,11 +22,10 @@ class Renderer : public ftl::Configurable { virtual ~Renderer() {}; /** - * Generate a single virtual camera frame. The frame takes its pose from - * the virtual camera object passed, and writes the result into the - * virtual camera. + * Generate a single virtual frame. The frame takes its pose and calibration + * from the output frame pose and calibration channels. */ - virtual bool render(ftl::rgbd::VirtualSource *, ftl::rgbd::Frame &, const Eigen::Matrix4d &)=0; + virtual bool render(ftl::rgbd::FrameSet &, ftl::rgbd::Frame &, ftl::codecs::Channel, const Eigen::Matrix4d &)=0; }; } diff --git a/components/renderers/cpp/include/ftl/render/tri_render.hpp b/components/renderers/cpp/include/ftl/render/tri_render.hpp index d15ba9c73116e4dfeb3a15ca006ae88329e4cc80..c709e818f97bc9284e75836c13209513be666f53 100644 --- a/components/renderers/cpp/include/ftl/render/tri_render.hpp +++ b/components/renderers/cpp/include/ftl/render/tri_render.hpp @@ -16,10 +16,10 @@ namespace render { */ class Triangular : public ftl::render::Renderer { public: - explicit Triangular(nlohmann::json &config, ftl::rgbd::FrameSet *fs); + explicit Triangular(nlohmann::json &config); ~Triangular(); - bool render(ftl::rgbd::VirtualSource *src, ftl::rgbd::Frame &out, const Eigen::Matrix4d &t) override; + bool render(ftl::rgbd::FrameSet &in, ftl::rgbd::Frame &out, ftl::codecs::Channel, const Eigen::Matrix4d &t) override; //void setOutputDevice(int); protected: @@ -55,7 +55,16 @@ class Triangular : public ftl::render::Renderer { void __reprojectChannel(ftl::rgbd::Frame &, ftl::codecs::Channel in, ftl::codecs::Channel out, const Eigen::Matrix4d &t, cudaStream_t); void _reprojectChannel(ftl::rgbd::Frame &, ftl::codecs::Channel in, ftl::codecs::Channel out, const Eigen::Matrix4d &t, cudaStream_t); void _dibr(ftl::rgbd::Frame &, const Eigen::Matrix4d &t, cudaStream_t); - void _mesh(ftl::rgbd::Frame &, ftl::rgbd::Source *, const Eigen::Matrix4d &t, cudaStream_t); + void _mesh(ftl::rgbd::Frame &, const Eigen::Matrix4d &t, cudaStream_t); + void _preprocessColours(); + void _updateParameters(ftl::rgbd::Frame &out); + void _allocateChannels(ftl::rgbd::Frame &out); + void _postprocessColours(ftl::rgbd::Frame &out); + + void _renderNormals(ftl::rgbd::Frame &out); + void _renderDensity(ftl::rgbd::Frame &out, const Eigen::Matrix4d &t); + void _renderRight(ftl::rgbd::Frame &out, const Eigen::Matrix4d &t); + void _renderSecond(ftl::rgbd::Frame &out, ftl::codecs::Channel chan, const Eigen::Matrix4d &t); bool _alreadySeen() const { return last_frame_ == scene_->timestamp; } }; diff --git a/components/renderers/cpp/src/splatter.cpp b/components/renderers/cpp/src/splatter.cpp index fad38ba82a1c3925069afd982170761f8d57cd78..731efec404dc24a972f290c61d8fc0c6d2ba34c7 100644 --- a/components/renderers/cpp/src/splatter.cpp +++ b/components/renderers/cpp/src/splatter.cpp @@ -204,7 +204,7 @@ void Splatter::_dibr(cudaStream_t stream) { for (size_t i=0; i < scene_->frames.size(); ++i) { auto &f = scene_->frames[i]; - auto *s = scene_->sources[i]; + //auto *s = scene_->sources[i]; if (f.empty(Channel::Depth + Channel::Colour)) { LOG(ERROR) << "Missing required channel"; @@ -355,7 +355,7 @@ bool Splatter::render(ftl::rgbd::VirtualSource *src, ftl::rgbd::Frame &out, cons temp_.createTexture<float4>(Channel::Normals); for (int i=0; i<scene_->frames.size(); ++i) { auto &f = scene_->frames[i]; - auto s = scene_->sources[i]; + //auto s = scene_->sources[i]; if (f.hasChannel(Channel::Mask)) { if (show_discon) { @@ -371,8 +371,8 @@ bool Splatter::render(ftl::rgbd::VirtualSource *src, ftl::rgbd::Frame &out, cons //LOG(INFO) << "Creating points... " << s->parameters().width; auto &t = f.createTexture<float4>(Channel::Points, Format<float4>(f.get<GpuMat>(Channel::Colour).size())); - auto pose = MatrixConversion::toCUDA(s->getPose().cast<float>()); //.inverse()); - ftl::cuda::point_cloud(t, f.createTexture<float>(Channel::Depth), s->parameters(), pose, 0, stream_); + auto pose = MatrixConversion::toCUDA(f.getPose().cast<float>()); //.inverse()); + ftl::cuda::point_cloud(t, f.createTexture<float>(Channel::Depth), f.getLeftCamera(), pose, 0, stream_); //LOG(INFO) << "POINTS Added"; } @@ -383,7 +383,7 @@ bool Splatter::render(ftl::rgbd::VirtualSource *src, ftl::rgbd::Frame &out, cons } if (!f.hasChannel(Channel::Normals)) { - Eigen::Matrix4f matrix = s->getPose().cast<float>().transpose(); + Eigen::Matrix4f matrix = f.getPose().cast<float>().transpose(); auto pose = MatrixConversion::toCUDA(matrix); auto &g = f.get<GpuMat>(Channel::Colour); @@ -391,10 +391,10 @@ bool Splatter::render(ftl::rgbd::VirtualSource *src, ftl::rgbd::Frame &out, cons temp_.getTexture<float4>(Channel::Normals), f.getTexture<float4>(Channel::Points), 1, 0.02f, - s->parameters(), pose.getFloat3x3(), stream_); + f.getLeftCamera(), pose.getFloat3x3(), stream_); if (norm_filter_ > -0.1f) { - ftl::cuda::normal_filter(f.getTexture<float4>(Channel::Normals), f.getTexture<float4>(Channel::Points), s->parameters(), pose, norm_filter_, stream_); + ftl::cuda::normal_filter(f.getTexture<float4>(Channel::Normals), f.getTexture<float4>(Channel::Points), f.getLeftCamera(), pose, norm_filter_, stream_); } } } diff --git a/components/renderers/cpp/src/tri_render.cpp b/components/renderers/cpp/src/tri_render.cpp index 6df15bbd19cd63df9a8cdd2a5d4edbb1fc20329b..659bcc8a99c929840506604d1a2dddb4b5dbc58f 100644 --- a/components/renderers/cpp/src/tri_render.cpp +++ b/components/renderers/cpp/src/tri_render.cpp @@ -67,7 +67,7 @@ static uchar4 parseCUDAColour(const std::string &colour) { return make_uchar4(0,0,0,0); } -Triangular::Triangular(nlohmann::json &config, ftl::rgbd::FrameSet *fs) : ftl::render::Renderer(config), scene_(fs) { +Triangular::Triangular(nlohmann::json &config) : ftl::render::Renderer(config), scene_(nullptr) { if (config["clipping"].is_object()) { auto &c = config["clipping"]; float rx = c.value("pitch", 0.0f); @@ -219,7 +219,7 @@ void Triangular::__reprojectChannel(ftl::rgbd::Frame &output, ftl::codecs::Chann for (size_t i=0; i < scene_->frames.size(); ++i) { auto &f = scene_->frames[i]; - auto *s = scene_->sources[i]; + //auto *s = scene_->sources[i]; /*if (f.get<GpuMat>(in).type() == CV_8UC3) { // Convert to 4 channel colour @@ -229,8 +229,8 @@ void Triangular::__reprojectChannel(ftl::rgbd::Frame &output, ftl::codecs::Chann cv::cuda::cvtColor(tmp,col, cv::COLOR_BGR2BGRA); }*/ - auto transform = MatrixConversion::toCUDA(s->getPose().cast<float>().inverse() * t.cast<float>().inverse()) * params_.m_viewMatrixInverse; - auto transformR = MatrixConversion::toCUDA(s->getPose().cast<float>().inverse()).getFloat3x3(); + auto transform = MatrixConversion::toCUDA(f.getPose().cast<float>().inverse() * t.cast<float>().inverse()) * params_.m_viewMatrixInverse; + auto transformR = MatrixConversion::toCUDA(f.getPose().cast<float>().inverse()).getFloat3x3(); if (mesh_) { ftl::cuda::reproject( @@ -241,7 +241,7 @@ void Triangular::__reprojectChannel(ftl::rgbd::Frame &output, ftl::codecs::Chann temp_.createTexture<typename AccumSelector<T>::type>(AccumSelector<T>::channel), temp_.getTexture<float>(Channel::Contribution), params_, - s->parameters(), + f.getLeftCamera(), transform, transformR, stream ); } else { @@ -253,7 +253,7 @@ void Triangular::__reprojectChannel(ftl::rgbd::Frame &output, ftl::codecs::Chann temp_.createTexture<typename AccumSelector<T>::type>(AccumSelector<T>::channel), temp_.getTexture<float>(Channel::Contribution), params_, - s->parameters(), + f.getLeftCamera(), transform, stream ); } @@ -295,20 +295,20 @@ void Triangular::_dibr(ftl::rgbd::Frame &out, const Eigen::Matrix4d &t, cudaStre for (size_t i=0; i < scene_->frames.size(); ++i) { auto &f = scene_->frames[i]; - auto *s = scene_->sources[i]; + //auto *s = scene_->sources[i]; if (f.empty(Channel::Depth + Channel::Colour)) { LOG(ERROR) << "Missing required channel"; continue; } - auto transform = params_.m_viewMatrix * MatrixConversion::toCUDA(t.cast<float>() * s->getPose().cast<float>()); + auto transform = params_.m_viewMatrix * MatrixConversion::toCUDA(t.cast<float>() * f.getPose().cast<float>()); ftl::cuda::dibr_merge( f.createTexture<float>(Channel::Depth), temp_.createTexture<int>(Channel::Depth2), transform, - s->parameters(), + f.getLeftCamera(), params_, stream ); } @@ -317,7 +317,7 @@ void Triangular::_dibr(ftl::rgbd::Frame &out, const Eigen::Matrix4d &t, cudaStre temp_.get<GpuMat>(Channel::Depth2).convertTo(out.get<GpuMat>(Channel::Depth), CV_32F, 1.0f / 100000.0f, cvstream); } -void Triangular::_mesh(ftl::rgbd::Frame &out, ftl::rgbd::Source *src, const Eigen::Matrix4d &t, cudaStream_t stream) { +void Triangular::_mesh(ftl::rgbd::Frame &out, const Eigen::Matrix4d &t, cudaStream_t stream) { cv::cuda::Stream cvstream = cv::cuda::StreamAccessor::wrapStream(stream); bool do_blend = value("mesh_blend", true); @@ -332,21 +332,21 @@ void Triangular::_mesh(ftl::rgbd::Frame &out, ftl::rgbd::Source *src, const Eige // For each source depth map for (size_t i=0; i < scene_->frames.size(); ++i) { auto &f = scene_->frames[i]; - auto *s = scene_->sources[i]; + //auto *s = scene_->sources[i]; if (f.empty(Channel::Depth + Channel::Colour)) { LOG(ERROR) << "Missing required channel"; continue; } - auto pose = MatrixConversion::toCUDA(t.cast<float>() * s->getPose().cast<float>()); + auto pose = MatrixConversion::toCUDA(t.cast<float>() * f.getPose().cast<float>()); // Calculate and save virtual view screen position of each source pixel ftl::cuda::screen_coord( f.createTexture<float>(Channel::Depth), f.createTexture<float>(Channel::Depth2, Format<float>(f.get<GpuMat>(Channel::Depth).size())), f.createTexture<short2>(Channel::Screen, Format<short2>(f.get<GpuMat>(Channel::Depth).size())), - params_, pose, s->parameters(), stream + params_, pose, f.getLeftCamera(), stream ); // Must reset depth channel if blending @@ -465,72 +465,53 @@ static cv::Scalar HSVtoRGB(int H, double S, double V) { return cv::Scalar((Bs + m) * 255, (Gs + m) * 255, (Rs + m) * 255, 0); } -bool Triangular::render(ftl::rgbd::VirtualSource *src, ftl::rgbd::Frame &out, const Eigen::Matrix4d &t) { - SHARED_LOCK(scene_->mtx, lk); - if (!src->isReady()) return false; - - //scene_->upload(Channel::Colour + Channel::Depth, stream_); - - const auto &camera = src->parameters(); +void Triangular::_allocateChannels(ftl::rgbd::Frame &out) { + const auto &camera = out.getLeftCamera(); cv::cuda::Stream cvstream = cv::cuda::StreamAccessor::wrapStream(stream_); - //cudaSafeCall(cudaSetDevice(scene_->getCUDADevice())); - // Parameters object to pass to CUDA describing the camera - SplatParams ¶ms = params_; - params.triangle_limit = value("triangle_limit", 200); - params.depthThreshold = value("depth_threshold", 0.04f); - params.m_flags = 0; - //if () params.m_flags |= ftl::render::kShowDisconMask; - if (value("normal_weight_colours", true)) params.m_flags |= ftl::render::kNormalWeightColours; - params.m_viewMatrix = MatrixConversion::toCUDA(src->getPose().cast<float>().inverse()); - params.m_viewMatrixInverse = MatrixConversion::toCUDA(src->getPose().cast<float>()); - params.camera = camera; - - // Create all the required channels - + // Only do this if not already done, allows for multiple render passes with + // different framesets. if (!out.hasChannel(Channel::Depth)) { out.create<GpuMat>(Channel::Depth, Format<float>(camera.width, camera.height)); out.create<GpuMat>(Channel::Colour, Format<uchar4>(camera.width, camera.height)); out.createTexture<uchar4>(Channel::Colour, true); // Force interpolated colour - out.get<GpuMat>(Channel::Depth).setTo(cv::Scalar(1000.0f), cvstream); - - if (env_image_.empty() || !value("environment_enabled", false)) { - out.get<GpuMat>(Channel::Colour).setTo(background_, cvstream); - } else { - auto pose = params.m_viewMatrixInverse.getFloat3x3(); - ftl::cuda::equirectangular_reproject( - env_tex_, - out.createTexture<uchar4>(Channel::Colour, true), - camera, pose, stream_); - } } - if (scene_->frames.size() == 0) return false; - auto &g = scene_->frames[0].get<GpuMat>(Channel::Colour); - temp_.create<GpuMat>(Channel::Colour, Format<float4>(camera.width, camera.height)); temp_.create<GpuMat>(Channel::Contribution, Format<float>(camera.width, camera.height)); temp_.create<GpuMat>(Channel::Depth, Format<int>(camera.width, camera.height)); temp_.create<GpuMat>(Channel::Depth2, Format<int>(camera.width, camera.height)); - temp_.create<GpuMat>(Channel::Normals, Format<float4>(camera.width, camera.height)); //g.cols, g.rows)); + temp_.create<GpuMat>(Channel::Normals, Format<float4>(camera.width, camera.height)); + temp_.createTexture<int>(Channel::Depth); +} - //LOG(INFO) << "Render ready: " << camera.width << "," << camera.height; +void Triangular::_updateParameters(ftl::rgbd::Frame &out) { + const auto &camera = out.getLeftCamera(); + + // Parameters object to pass to CUDA describing the camera + params_.triangle_limit = value("triangle_limit", 200); + params_.depthThreshold = value("depth_threshold", 0.04f); + params_.m_flags = 0; + if (value("normal_weight_colours", true)) params_.m_flags |= ftl::render::kNormalWeightColours; + params_.m_viewMatrix = MatrixConversion::toCUDA(out.getPose().cast<float>().inverse()); + params_.m_viewMatrixInverse = MatrixConversion::toCUDA(out.getPose().cast<float>()); + params_.camera = camera; +} +void Triangular::_preprocessColours() { bool show_discon = value("show_discontinuity_mask", false); bool show_fill = value("show_filled", false); bool colour_sources = value("colour_sources", false); - temp_.createTexture<int>(Channel::Depth); - //temp_.get<GpuMat>(Channel::Normals).setTo(cv::Scalar(0.0f,0.0f,0.0f,0.0f), cvstream); + cv::cuda::Stream cvstream = cv::cuda::StreamAccessor::wrapStream(stream_); // Display mask values or otherwise alter colour image for (int i=0; i<scene_->frames.size(); ++i) { auto &f = scene_->frames[i]; - auto s = scene_->sources[i]; if (colour_sources) { - auto colour = HSVtoRGB(360 / scene_->frames.size() * i, 0.6, 0.85); //(i == 0) ? cv::Scalar(255,0,0,0) : cv::Scalar(0,255,0,0); + auto colour = HSVtoRGB(360 / scene_->frames.size() * i, 0.6, 0.85); f.get<GpuMat>(Channel::Colour).setTo(colour, cvstream); } @@ -543,87 +524,14 @@ bool Triangular::render(ftl::rgbd::VirtualSource *src, ftl::rgbd::Frame &out, co } } - /*// Needs to create points channel first? - if (!f.hasChannel(Channel::Points)) { - //LOG(INFO) << "Creating points... " << s->parameters().width; - - auto &t = f.createTexture<float4>(Channel::Points, Format<float4>(f.get<GpuMat>(Channel::Colour).size())); - auto pose = MatrixConversion::toCUDA(s->getPose().cast<float>()); //.inverse()); - ftl::cuda::point_cloud(t, f.createTexture<float>(Channel::Depth), s->parameters(), pose, 0, stream_); - - //LOG(INFO) << "POINTS Added"; - } - - // Clip first? - if (clipping_) { - ftl::cuda::clipping(f.createTexture<float4>(Channel::Points), clip_, stream_); - } - - if (!f.hasChannel(Channel::Normals)) { - Eigen::Matrix4f matrix = s->getPose().cast<float>().transpose(); - auto pose = MatrixConversion::toCUDA(matrix); - - auto &g = f.get<GpuMat>(Channel::Colour); - ftl::cuda::normals(f.createTexture<float4>(Channel::Normals, Format<float4>(g.cols, g.rows)), - temp_.getTexture<float4>(Channel::Normals), - f.getTexture<float4>(Channel::Points), - 1, 0.02f, - s->parameters(), pose.getFloat3x3(), stream_); - - if (norm_filter_ > -0.1f) { - ftl::cuda::normal_filter(f.getTexture<float4>(Channel::Normals), f.getTexture<float4>(Channel::Points), s->parameters(), pose, norm_filter_, stream_); - } - }*/ - } - - Channel chan = src->getChannel(); - - int aligned_source = value("aligned_source",-1); - if (aligned_source >= 0 && aligned_source < scene_->frames.size()) { - // Can only send at originally received frame rate due to reuse of - // encodings that can't be sent twice. - if (_alreadySeen()) { - out.reset(); - return false; - } - - // FIXME: Output may not be same resolution as source! - cudaSafeCall(cudaStreamSynchronize(stream_)); - scene_->frames[aligned_source].copyTo(Channel::Depth + Channel::Colour + Channel::Smoothing + Channel::Confidence, out); - - if (chan == Channel::ColourNormals) { - // Convert normal to single float value - out.create<GpuMat>(Channel::ColourNormals, Format<uchar4>(out.get<GpuMat>(Channel::Colour).size())).setTo(cv::Scalar(0,0,0,0), cvstream); - ftl::cuda::normal_visualise(scene_->frames[aligned_source].getTexture<float4>(Channel::Normals), out.createTexture<uchar4>(Channel::ColourNormals), - light_pos_, - light_diffuse_, - light_ambient_, stream_); - - // Put in output as single float - //cv::cuda::swap(temp_.get<GpuMat>(Channel::Colour), out.create<GpuMat>(Channel::Normals)); - //out.resetTexture(Channel::Normals); - } - - last_frame_ = scene_->timestamp; - return true; + // Force interpolated colour + f.createTexture<uchar4>(Channel::Colour, true); } +} - // Create and render triangles for depth - if (mesh_) { - _mesh(out, src, t, stream_); - } else { - _dibr(out, t, stream_); - } - - // Reprojection of colours onto surface - auto main_channel = (scene_->frames[0].hasChannel(Channel::ColourHighRes)) ? Channel::ColourHighRes : Channel::Colour; - //if (scene_->frames[0].hasChannel(Channel::ColourHighRes)) { - // LOG(INFO) << "HAVE HIGH RES: " << scene_->frames[0].get<GpuMat>(Channel::ColourHighRes).rows; - //} - _renderChannel(out, main_channel, Channel::Colour, t, stream_); - +void Triangular::_postprocessColours(ftl::rgbd::Frame &out) { if (value("cool_effect", false)) { - auto pose = params.m_viewMatrixInverse.getFloat3x3(); + auto pose = params_.m_viewMatrixInverse.getFloat3x3(); auto col = parseCUDAColour(value("cool_effect_colour", std::string("#2222ff"))); ftl::cuda::cool_blue( @@ -640,76 +548,121 @@ bool Triangular::render(ftl::rgbd::VirtualSource *src, ftl::rgbd::Frame &out, co out.getTexture<uchar4>(Channel::Colour), temp_.getTexture<float>(Channel::Contribution), make_uchar4(255,0,0,0), - camera, + params_.camera, stream_ ); } +} + +void Triangular::_renderNormals(ftl::rgbd::Frame &out) { + cv::cuda::Stream cvstream = cv::cuda::StreamAccessor::wrapStream(stream_); + + // Visualise normals to RGBA + out.create<GpuMat>(Channel::ColourNormals, Format<uchar4>(params_.camera.width, params_.camera.height)).setTo(cv::Scalar(0,0,0,0), cvstream); + + ftl::cuda::normal_visualise(out.getTexture<float4>(Channel::Normals), out.createTexture<uchar4>(Channel::ColourNormals), + light_pos_, + light_diffuse_, + light_ambient_, stream_); +} + +void Triangular::_renderDensity(ftl::rgbd::Frame &out, const Eigen::Matrix4d &t) { + cv::cuda::Stream cvstream = cv::cuda::StreamAccessor::wrapStream(stream_); + out.create<GpuMat>(Channel::Density, Format<float>(params_.camera.width, params_.camera.height)); + out.get<GpuMat>(Channel::Density).setTo(cv::Scalar(0.0f), cvstream); + _renderChannel(out, Channel::Depth, Channel::Density, t, stream_); +} + +void Triangular::_renderRight(ftl::rgbd::Frame &out, const Eigen::Matrix4d &t) { + cv::cuda::Stream cvstream = cv::cuda::StreamAccessor::wrapStream(stream_); + + float baseline = params_.camera.baseline; + + Eigen::Matrix4f transform = Eigen::Matrix4f::Identity(); + transform(0, 3) = baseline; + Eigen::Matrix4f matrix = out.getPose().cast<float>() * transform.inverse(); + + params_.m_viewMatrix = MatrixConversion::toCUDA(matrix.inverse()); + params_.m_viewMatrixInverse = MatrixConversion::toCUDA(matrix); + params_.camera = out.getRightCamera(); - if (chan == Channel::Depth) - { - // Just convert int depth to float depth - //temp_.get<GpuMat>(Channel::Depth2).convertTo(out.get<GpuMat>(Channel::Depth), CV_32F, 1.0f / 100000.0f, cvstream); - } else if (chan == Channel::ColourNormals) { - // Visualise normals to RGBA - out.create<GpuMat>(Channel::ColourNormals, Format<uchar4>(camera.width, camera.height)).setTo(cv::Scalar(0,0,0,0), cvstream); - - ftl::cuda::normal_visualise(out.getTexture<float4>(Channel::Normals), out.createTexture<uchar4>(Channel::ColourNormals), - light_pos_, - light_diffuse_, - light_ambient_, stream_); - - //accum_.swapTo(Channels(Channel::Normals), out); - //cv::cuda::swap(accum_.get<GpuMat>(Channel::Normals), out.get<GpuMat>(Channel::Normals)); - //out.resetTexture(Channel::Normals); - //accum_.resetTexture(Channel::Normals); + out.create<GpuMat>(Channel::Right, Format<uchar4>(params_.camera.width, params_.camera.height)); + out.get<GpuMat>(Channel::Right).setTo(background_, cvstream); + out.createTexture<uchar4>(Channel::Right, true); + + // Need to re-dibr due to pose change + if (mesh_) { + _mesh(out, t, stream_); + } else { + _dibr(out, t, stream_); } - //else if (chan == Channel::Contribution) - //{ - // cv::cuda::swap(temp_.get<GpuMat>(Channel::Contribution), out.create<GpuMat>(Channel::Contribution)); - //} - else if (chan == Channel::Density) { - out.create<GpuMat>(chan, Format<float>(camera.width, camera.height)); + _renderChannel(out, Channel::Left, Channel::Right, t, stream_); +} + +void Triangular::_renderSecond(ftl::rgbd::Frame &out, ftl::codecs::Channel chan, const Eigen::Matrix4d &t) { + cv::cuda::Stream cvstream = cv::cuda::StreamAccessor::wrapStream(stream_); + + if (ftl::codecs::isFloatChannel(chan)) { + out.create<GpuMat>(chan, Format<float>(params_.camera.width, params_.camera.height)); out.get<GpuMat>(chan).setTo(cv::Scalar(0.0f), cvstream); - _renderChannel(out, Channel::Depth, Channel::Density, t, stream_); + } else { + out.create<GpuMat>(chan, Format<uchar4>(params_.camera.width, params_.camera.height)); + out.get<GpuMat>(chan).setTo(background_, cvstream); } - else if (chan == Channel::Right) - { - float baseline = camera.baseline; - - //Eigen::Translation3f translation(baseline, 0.0f, 0.0f); - //Eigen::Affine3f transform(translation); - //Eigen::Matrix4f matrix = transform.matrix() * src->getPose().cast<float>(); + _renderChannel(out, chan, chan, t, stream_); +} - Eigen::Matrix4f transform = Eigen::Matrix4f::Identity(); - transform(0, 3) = baseline; - Eigen::Matrix4f matrix = src->getPose().cast<float>() * transform.inverse(); - - params.m_viewMatrix = MatrixConversion::toCUDA(matrix.inverse()); - params.m_viewMatrixInverse = MatrixConversion::toCUDA(matrix); +bool Triangular::render(ftl::rgbd::FrameSet &in, ftl::rgbd::Frame &out, Channel chan, const Eigen::Matrix4d &t) { + scene_ = ∈ + if (scene_->frames.size() == 0) return false; - params.camera = src->parameters(Channel::Right); - - out.create<GpuMat>(Channel::Right, Format<uchar4>(camera.width, camera.height)); - out.get<GpuMat>(Channel::Right).setTo(background_, cvstream); - out.createTexture<uchar4>(Channel::Right, true); + const auto &camera = out.getLeftCamera(); + cv::cuda::Stream cvstream = cv::cuda::StreamAccessor::wrapStream(stream_); + //cudaSafeCall(cudaSetDevice(scene_->getCUDADevice())); - // Need to re-dibr due to pose change - if (mesh_) { - _mesh(out, src, t, stream_); - } else { - _dibr(out, t, stream_); - } - _renderChannel(out, Channel::Left, Channel::Right, t, stream_); + _updateParameters(out); - } else if (chan != Channel::None) { - if (ftl::codecs::isFloatChannel(chan)) { - out.create<GpuMat>(chan, Format<float>(camera.width, camera.height)); - out.get<GpuMat>(chan).setTo(cv::Scalar(0.0f), cvstream); - } else { - out.create<GpuMat>(chan, Format<uchar4>(camera.width, camera.height)); - out.get<GpuMat>(chan).setTo(background_, cvstream); - } - _renderChannel(out, chan, chan, t, stream_); + // Create all the required channels + _allocateChannels(out); + + // Apply a colour background + if (env_image_.empty() || !value("environment_enabled", false)) { + out.get<GpuMat>(Channel::Colour).setTo(background_, cvstream); + } else { + auto pose = params_.m_viewMatrixInverse.getFloat3x3(); + ftl::cuda::equirectangular_reproject( + env_tex_, + out.createTexture<uchar4>(Channel::Colour, true), + camera, pose, stream_); + } + + // Render source specific debug info into colour channels + _preprocessColours(); + + if (mesh_) { + // Render depth channel using triangles + _mesh(out, t, stream_); + } else { + // Render depth channel as a point cloud + _dibr(out, t, stream_); + } + + // Reprojection of colours onto surface + auto main_channel = (scene_->frames[0].hasChannel(Channel::ColourHighRes)) ? Channel::ColourHighRes : Channel::Colour; + _renderChannel(out, main_channel, Channel::Colour, t, stream_); + + // Debug colour info relating to the rendering process + _postprocessColours(out); + + // Support rendering of a second channel without redoing all the work + switch(chan) { + case Channel::None : + case Channel::Left : + case Channel::Depth : break; + case Channel::ColourNormals : _renderNormals(out); break; + case Channel::Density : _renderDensity(out, t); break; + case Channel::Right : _renderRight(out, t); break; + default : _renderSecond(out, chan, t); } cudaSafeCall(cudaStreamSynchronize(stream_)); diff --git a/components/rgbd-sources/CMakeLists.txt b/components/rgbd-sources/CMakeLists.txt index b22623bcef38c04fd5b2d7535728bee1616c5fb6..405712691cd9cea781fd8cab5e41fdfccef4e05c 100644 --- a/components/rgbd-sources/CMakeLists.txt +++ b/components/rgbd-sources/CMakeLists.txt @@ -5,15 +5,15 @@ set(RGBDSRC src/frame.cpp src/frameset.cpp src/sources/stereovideo/stereovideo.cpp - src/sources/net/net.cpp - src/streamer.cpp + #src/sources/net/net.cpp + #src/streamer.cpp src/colour.cpp src/group.cpp src/cb_segmentation.cpp src/abr.cpp - src/sources/virtual/virtual.cpp - src/sources/ftlfile/file_source.cpp - src/sources/ftlfile/player.cpp + #src/sources/virtual/virtual.cpp + #src/sources/ftlfile/file_source.cpp + #src/sources/ftlfile/player.cpp ) if (HAVE_REALSENSE) diff --git a/components/rgbd-sources/include/ftl/rgbd/detail/source.hpp b/components/rgbd-sources/include/ftl/rgbd/detail/source.hpp index cc5aeec50c1acc61ad4ed04acdbbaf03b2c4ba08..bb162c925586d177ed37465a778555254000d631 100644 --- a/components/rgbd-sources/include/ftl/rgbd/detail/source.hpp +++ b/components/rgbd-sources/include/ftl/rgbd/detail/source.hpp @@ -28,7 +28,7 @@ class Source { friend class ftl::rgbd::Source; public: - explicit Source(ftl::rgbd::Source *host) : capabilities_(0), host_(host), params_({0}), timestamp_(0) { } + explicit Source(ftl::rgbd::Source *host) : capabilities_(0), host_(host), params_(state_.getLeft()), timestamp_(0) { } virtual ~Source() {} /** @@ -54,17 +54,18 @@ class Source { virtual void swap() {} virtual bool isReady() { return false; }; - virtual void setPose(const Eigen::Matrix4d &pose) { }; + virtual void setPose(const Eigen::Matrix4d &pose) { state_.setPose(pose); }; virtual Camera parameters(ftl::codecs::Channel) { return params_; }; protected: + ftl::rgbd::FrameState state_; capability_t capabilities_; ftl::rgbd::Source *host_; - ftl::rgbd::Camera params_; + ftl::rgbd::Camera ¶ms_; ftl::rgbd::Frame frame_; int64_t timestamp_; - //Eigen::Matrix4f pose_; + //Eigen::Matrix4d &pose_; }; } diff --git a/components/rgbd-sources/include/ftl/rgbd/frame.hpp b/components/rgbd-sources/include/ftl/rgbd/frame.hpp index 109eb008a9e51ed5e8bdf77d68b3c59f6daccb64..85b97326b263ccd354924253f1a2652ea22959f9 100644 --- a/components/rgbd-sources/include/ftl/rgbd/frame.hpp +++ b/components/rgbd-sources/include/ftl/rgbd/frame.hpp @@ -10,6 +10,7 @@ #include <ftl/codecs/channels.hpp> #include <ftl/rgbd/format.hpp> +#include <ftl/rgbd/camera.hpp> #include <ftl/codecs/bitrates.hpp> #include <ftl/codecs/packet.hpp> @@ -19,6 +20,8 @@ #include <array> #include <list> +#include <Eigen/Eigen> + namespace ftl { namespace rgbd { @@ -28,15 +31,130 @@ namespace rgbd { class Frame; class Source; +/** + * Represent state that is persistent across frames. Such state may or may not + * change from one frame to the next so a record of what has changed must be + * kept. Changing state should be done at origin and not in the frame. State + * that is marked as changed will then be send into a stream and the changed + * status will be cleared, allowing data to only be sent/saved when actual + * changes occur. + */ +class FrameState { + public: + FrameState(); + FrameState(FrameState &); + FrameState(FrameState &&); + + /** + * Update the pose and mark as changed. + */ + void setPose(const Eigen::Matrix4d &pose); + + /** + * Update the left camera intrinsics and mark as changed. + */ + void setLeft(const ftl::rgbd::Camera &p); + + /** + * Update the right camera intrinsics and mark as changed. + */ + void setRight(const ftl::rgbd::Camera &p); + + /** + * Get the current camera pose. + */ + inline const Eigen::Matrix4d &getPose() const { return pose_; } + + /** + * Get the left camera intrinsics. + */ + inline const ftl::rgbd::Camera &getLeft() const { return camera_left_; } + + /** + * Get the right camera intrinsics. + */ + inline const ftl::rgbd::Camera &getRight() const { return camera_right_; } + + /** + * Get a modifiable pose reference that does not change the changed status. + * @attention Should only be used internally. + * @todo Make private eventually. + */ + inline Eigen::Matrix4d &getPose() { return pose_; } + + /** + * Get a modifiable left camera intrinsics reference that does not change + * the changed status. Modifications made using this will not be propagated. + * @attention Should only be used internally. + * @todo Make private eventually. + */ + inline ftl::rgbd::Camera &getLeft() { return camera_left_; } + + /** + * Get a modifiable right camera intrinsics reference that does not change + * the changed status. Modifications made using this will not be propagated. + * @attention Should only be used internally. + * @todo Make private eventually. + */ + inline ftl::rgbd::Camera &getRight() { return camera_right_; } + + /** + * Get a named config property. + */ + template <typename T> + std::optional<T> get(const std::string &name) { + try { + return config_[name].get<T>(); + } catch (...) { + return {}; + } + } + + /** + * Set a named config property. Also makes state as changed to be resent. + */ + template <typename T> + void set(const std::string &name, T value) { + config_[name] = value; + changed_ += ftl::codecs::Channel::Configuration; + } + + inline const nlohmann::json &getConfig() const { return config_; } + + inline nlohmann::json &getConfig() { return config_; } + + /** + * Check if pose of intrinsics have been modified and not yet forwarded. + * Once forwarded through a pipeline / stream the changed status is cleared. + */ + inline bool hasChanged(ftl::codecs::Channel c) const { return changed_.has(c); } + + /** + * Copy assignment will clear the changed status of the original. + */ + FrameState &operator=(FrameState &); + + FrameState &operator=(FrameState &&); + + /** + * Clear the changed status to unchanged. + */ + inline void clear() { changed_.clear(); } + + private: + Eigen::Matrix4d pose_; + ftl::rgbd::Camera camera_left_; + ftl::rgbd::Camera camera_right_; + nlohmann::json config_; + ftl::codecs::Channels<64> changed_; // Have the state channels changed? +}; + /** * Manage a set of image channels corresponding to a single camera frame. */ class Frame { public: - Frame() : src_(nullptr) {} - explicit Frame(ftl::rgbd::Source *src) : src_(src) {} - - inline ftl::rgbd::Source *source() const { return src_; } + Frame() : origin_(nullptr) {} // Prevent frame copy, instead use a move. //Frame(const Frame &)=delete; @@ -44,27 +162,27 @@ public: void download(ftl::codecs::Channel c, cv::cuda::Stream stream); void upload(ftl::codecs::Channel c, cv::cuda::Stream stream); - void download(ftl::codecs::Channels c, cv::cuda::Stream stream); - void upload(ftl::codecs::Channels c, cv::cuda::Stream stream); + void download(ftl::codecs::Channels<0> c, cv::cuda::Stream stream); + void upload(ftl::codecs::Channels<0> c, cv::cuda::Stream stream); inline void download(ftl::codecs::Channel c, cudaStream_t stream=0) { download(c, cv::cuda::StreamAccessor::wrapStream(stream)); }; inline void upload(ftl::codecs::Channel c, cudaStream_t stream=0) { upload(c, cv::cuda::StreamAccessor::wrapStream(stream)); }; - inline void download(ftl::codecs::Channels c, cudaStream_t stream=0) { download(c, cv::cuda::StreamAccessor::wrapStream(stream)); }; - inline void upload(ftl::codecs::Channels c, cudaStream_t stream=0) { upload(c, cv::cuda::StreamAccessor::wrapStream(stream)); }; + inline void download(const ftl::codecs::Channels<0> &c, cudaStream_t stream=0) { download(c, cv::cuda::StreamAccessor::wrapStream(stream)); }; + inline void upload(const ftl::codecs::Channels<0> &c, cudaStream_t stream=0) { upload(c, cv::cuda::StreamAccessor::wrapStream(stream)); }; /** * Perform a buffer swap of the selected channels. This is intended to be * a copy from `this` to the passed frame object but by buffer swap * instead of memory copy, meaning `this` may become invalid afterwards. */ - void swapTo(ftl::codecs::Channels, Frame &); + void swapTo(ftl::codecs::Channels<0>, Frame &); void swapChannels(ftl::codecs::Channel, ftl::codecs::Channel); /** * Does a host or device memory copy into the given frame. */ - void copyTo(ftl::codecs::Channels, Frame &); + void copyTo(ftl::codecs::Channels<0>, Frame &); /** * Create a channel with a given format. This will discard any existing @@ -100,8 +218,22 @@ public: */ void pushPacket(ftl::codecs::Channel c, ftl::codecs::Packet &pkt); + /** + * Obtain a list of any existing encodings for this channel. + */ const std::list<ftl::codecs::Packet> &getPackets(ftl::codecs::Channel c) const; + /** + * Clear any existing encoded packets. Used when the channel data is + * modified and the encodings are therefore out-of-date. + */ + void clearPackets(ftl::codecs::Channel c); + + /** + * Packets from multiple frames are merged together in sequence. An example + * case is if a frame gets dropped but the original encoding is inter-frame + * and hence still requires the dropped frames encoding data. + */ void mergeEncoding(ftl::rgbd::Frame &f); void resetTexture(ftl::codecs::Channel c); @@ -116,21 +248,34 @@ public: */ void resetFull(); - bool empty(ftl::codecs::Channels c); + /** + * Check if any specified channels are empty or missing. + */ + bool empty(ftl::codecs::Channels<0> c); + /** + * Check if a specific channel is missing or has no memory allocated. + */ inline bool empty(ftl::codecs::Channel c) { auto &m = _get(c); return !hasChannel(c) || (m.host.empty() && m.gpu.empty()); } /** - * Is there valid data in channel (either host or gpu). + * Is there valid data in channel (either host or gpu). This does not + * verify that any memory or data exists for the channel. */ inline bool hasChannel(ftl::codecs::Channel channel) const { - return channels_.has(channel); + int c = static_cast<int>(channel); + if (c >= 64 && c <= 68) return true; + else if (c >= 32) return false; + else return channels_.has(channel); } - inline ftl::codecs::Channels getChannels() const { return channels_; } + /** + * Obtain a mask of all available channels in the frame. + */ + inline ftl::codecs::Channels<0> getChannels() const { return channels_; } /** * Is the channel data currently located on GPU. This also returns false if @@ -148,6 +293,15 @@ public: return channels_.has(channel) && !gpu_.has(channel); } + /** + * Does this frame have new data for a channel. This is compared with a + * previous frame and always returns true for image data. It may return + * false for persistent state data (calibration, pose etc). + */ + inline bool hasChanged(ftl::codecs::Channel c) const { + return (static_cast<int>(c) < 32) ? true : state_.hasChanged(c); + } + /** * Method to get reference to the channel content. * @param Channel type @@ -170,9 +324,82 @@ public: */ template <typename T> T& get(ftl::codecs::Channel channel); + /** + * Get an existing CUDA texture object. + */ template <typename T> const ftl::cuda::TextureObject<T> &getTexture(ftl::codecs::Channel) const; + + /** + * Get an existing CUDA texture object. + */ template <typename T> ftl::cuda::TextureObject<T> &getTexture(ftl::codecs::Channel); + /** + * Wrapper accessor function to get frame pose. + */ + const Eigen::Matrix4d &getPose() const; + + /** + * Change the pose of the origin state and mark as changed. + */ + void setPose(const Eigen::Matrix4d &pose); + + /** + * Wrapper to access left camera intrinsics channel. + */ + const ftl::rgbd::Camera &getLeftCamera() const; + + /** + * Wrapper to access right camera intrinsics channel. + */ + const ftl::rgbd::Camera &getRightCamera() const; + + /** + * Change left camera intrinsics in the origin state. This should send + * the changed parameters in reverse through a stream. + */ + void setLeftCamera(const ftl::rgbd::Camera &c); + + /** + * Change right camera intrinsics in the origin state. This should send + * the changed parameters in reverse through a stream. + */ + void setRightCamera(const ftl::rgbd::Camera &c); + + /** + * Dump the current frame config object to a json string. + */ + std::string getConfigString() const; + + /** + * Wrapper to access a config property. If the property does not exist or + * is not of the requested type then the returned optional is false. + */ + template <typename T> + std::optional<T> get(const std::string &name) { return state_.get<T>(name); } + + /** + * Modify a config property. This does not modify the origin config so + * will not get transmitted over the stream. + * @todo Modify origin to send backwards over a stream. + */ + template <typename T> + void set(const std::string &name, T value) { state_.set(name, value); } + + /** + * Set the persistent state for the frame. This can only be done after + * construction or a reset. Multiple calls to this otherwise will throw + * an exception. The pointer must remain valid for the life of the frame. + */ + void setOrigin(ftl::rgbd::FrameState *state); + + /** + * Get the original frame state object. This can be a nullptr in some rare + * cases. When wishing to change state (pose, calibration etc) then those + * changes must be done on this origin, either directly or via wrappers. + */ + FrameState *origin() const { return origin_; } + private: struct ChannelData { ftl::cuda::TextureObjectBase tex; @@ -181,13 +408,16 @@ private: std::list<ftl::codecs::Packet> encoded; }; - std::array<ChannelData, ftl::codecs::Channels::kMax> data_; + std::array<ChannelData, ftl::codecs::Channels<0>::kMax> data_; - ftl::codecs::Channels channels_; // Does it have a channel - ftl::codecs::Channels gpu_; // Is the channel on a GPU + ftl::codecs::Channels<0> channels_; // Does it have a channel + ftl::codecs::Channels<0> gpu_; // Is the channel on a GPU - ftl::rgbd::Source *src_; + // Persistent state + FrameState state_; + FrameState *origin_; + /* Lookup internal state for a given channel. */ inline ChannelData &_get(ftl::codecs::Channel c) { return data_[static_cast<unsigned int>(c)]; } inline const ChannelData &_get(ftl::codecs::Channel c) const { return data_[static_cast<unsigned int>(c)]; } }; @@ -199,6 +429,9 @@ template<> const cv::cuda::GpuMat& Frame::get(ftl::codecs::Channel channel) cons template<> cv::Mat& Frame::get(ftl::codecs::Channel channel); template<> cv::cuda::GpuMat& Frame::get(ftl::codecs::Channel channel); +//template<> const Eigen::Matrix4d &Frame::get(ftl::codecs::Channel channel) const; +template<> const ftl::rgbd::Camera &Frame::get(ftl::codecs::Channel channel) const; + template <> cv::Mat &Frame::create(ftl::codecs::Channel c, const ftl::rgbd::FormatBase &); template <> cv::cuda::GpuMat &Frame::create(ftl::codecs::Channel c, const ftl::rgbd::FormatBase &); template <> cv::Mat &Frame::create(ftl::codecs::Channel c); diff --git a/components/rgbd-sources/include/ftl/rgbd/frameset.hpp b/components/rgbd-sources/include/ftl/rgbd/frameset.hpp index ee1322b73f6ea147aaad5b986791dc9127342db5..b85e99be987c31fdcb5f3116372efcb631d5442d 100644 --- a/components/rgbd-sources/include/ftl/rgbd/frameset.hpp +++ b/components/rgbd-sources/include/ftl/rgbd/frameset.hpp @@ -2,6 +2,7 @@ #define _FTL_RGBD_FRAMESET_HPP_ #include <ftl/threads.hpp> +#include <ftl/timer.hpp> #include <ftl/rgbd/frame.hpp> #include <opencv2/opencv.hpp> @@ -10,6 +11,10 @@ namespace ftl { namespace rgbd { +// Allows a latency of 20 frames maximum +static const size_t kMaxFramesets = 15; +static const size_t kMaxFramesInSet = 32; + class Source; /** @@ -18,21 +23,132 @@ class Source; * timestamp. */ struct FrameSet { + int id=0; int64_t timestamp; // Millisecond timestamp of all frames - std::vector<Source*> sources; // All source objects involved. std::vector<ftl::rgbd::Frame> frames; std::atomic<int> count; // Number of valid frames std::atomic<unsigned int> mask; // Mask of all sources that contributed bool stale; // True if buffers have been invalidated SHARED_MUTEX mtx; - void upload(ftl::codecs::Channels, cudaStream_t stream=0); - void download(ftl::codecs::Channels, cudaStream_t stream=0); + /** + * Upload all specified host memory channels to GPU memory. + */ + void upload(ftl::codecs::Channels<0>, cudaStream_t stream=0); + + /** + * Download all specified GPU memory channels to host memory. + */ + void download(ftl::codecs::Channels<0>, cudaStream_t stream=0); + + /** + * Move the entire frameset to another frameset object. This will + * invalidate the current frameset object as all memory buffers will be + * moved. + */ void swapTo(ftl::rgbd::FrameSet &); + /** + * Clear all channels and all memory allocations within those channels. + * This will perform a resetFull on all frames in the frameset. + */ void resetFull(); }; +/** + * Callback type for receiving video frames. + */ +typedef std::function<bool(ftl::rgbd::FrameSet &)> VideoCallback; + +/** + * Abstract class for any generator of FrameSet structures. A generator + * produces (decoded) frame sets at regular frame intervals depending on the + * global timer settings. The `onFrameSet` callback may be triggered from any + * thread and also may drop frames and not be called for a given timestamp. + */ +class Generator { + public: + Generator() {} + virtual ~Generator() {} + + /** Number of frames in last frameset. This can change over time. */ + virtual size_t size()=0; + + /** + * Get the persistent state object for a frame. An exception is thrown + * for a bad index. + */ + virtual ftl::rgbd::FrameState &state(int ix)=0; + + inline ftl::rgbd::FrameState &operator[](int ix) { return state(ix); } + + /** Register a callback to receive new frame sets. */ + virtual void onFrameSet(const ftl::rgbd::VideoCallback &)=0; +}; + +/** + * Accept frames and generate framesets as they become completed. This can + * directly act as a generator of framesets, each frameset being generated + * by the global timer. Once the expected number of frames have been received, + * a frameset is marked as complete and can then be passed to the callback at + * the appropriate time. If frames are generated faster than consumed then they + * are buffered and merged into a single frameset. The buffer has a limited size + * so a longer delay in a callback will cause buffer failures. If frames are + * generated below framerate then the on frameset callback is just not called. + */ +class Builder : public Generator { + public: + Builder(); + ~Builder(); + + size_t size() override; + + ftl::rgbd::FrameState &state(int ix) override; + + void onFrameSet(const ftl::rgbd::VideoCallback &) override; + + /** + * Add a new frame at a given timestamp. + */ + void push(int64_t timestamp, int ix, ftl::rgbd::Frame &f); + + void setName(const std::string &name); + + private: + std::list<FrameSet*> framesets_; // Active framesets + std::list<FrameSet*> allocated_; // Keep memory allocations + + size_t head_; + ftl::rgbd::VideoCallback callback_; + MUTEX mutex_; + int mspf_; + float latency_; + float fps_; + int stats_count_; + int64_t last_ts_; + std::atomic<int> jobs_; + volatile bool skip_; + ftl::timer::TimerHandle main_id_; + size_t size_; + std::vector<ftl::rgbd::FrameState*> states_; + + std::string name_; + + /* Insert a new frameset into the buffer, along with all intermediate + * framesets between the last in buffer and the new one. + */ + ftl::rgbd::FrameSet *_addFrameset(int64_t timestamp); + + /* Find a frameset with given latency in frames. */ + ftl::rgbd::FrameSet *_getFrameset(); + + /* Search for a matching frameset. */ + ftl::rgbd::FrameSet *_findFrameset(int64_t ts); + void _freeFrameset(ftl::rgbd::FrameSet *); + + void _recordStats(float fps, float latency); +}; + } } diff --git a/components/rgbd-sources/include/ftl/rgbd/group.hpp b/components/rgbd-sources/include/ftl/rgbd/group.hpp index 8df9ab1f328368c929c5ce850cff11259bbd9d86..a4955d1d83e8eff268bca6c0ea99baa4e9907f59 100644 --- a/components/rgbd-sources/include/ftl/rgbd/group.hpp +++ b/components/rgbd-sources/include/ftl/rgbd/group.hpp @@ -20,9 +20,6 @@ namespace rgbd { class Source; -// Allows a latency of 20 frames maximum -static const size_t kMaxFramesets = 15; - /** * Manage a group of RGB-D sources to obtain synchronised sets of frames from * those sources. The Group class provides a synchronised callback mechanism @@ -33,7 +30,7 @@ static const size_t kMaxFramesets = 15; * then it will be used. This can be disabled. It is also possible to allow * incomplete frames to be used, but this is disabled by default. */ -class Group { +class Group : public ftl::rgbd::Generator { public: Group(); ~Group(); @@ -74,7 +71,7 @@ class Group { * the frameset are swapped during the function call, meaning that the * frameset data is no longer valid upon returning. */ - void sync(std::function<bool(FrameSet &)>); + void onFrameSet(const VideoCallback &cb) override; /** * Whenever any source within the group receives raw data, this callback @@ -88,66 +85,33 @@ class Group { /** * Removes a raw data callback from all sources in the group. */ - void removeRawCallback(const std::function<void(ftl::rgbd::Source*, const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt)> &); + //void removeRawCallback(const std::function<void(ftl::rgbd::Source*, const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt)> &); inline std::vector<Source*> sources() const { return sources_; } - /** @deprecated */ - //bool getFrames(FrameSet &, bool complete=false); + size_t size() override { return builder_.size(); } - /** To be deprecated in favour of ftl::timer::setInterval. - */ - //void setFPS(int fps); - - /** - * Set the minimum number of frames latency. The latency is from the most - * recent frame obtained, meaning that the timestamp of the input frames is - * the reference point, this may already be several frames old. Latency - * does not correspond to actual current time. - */ - void setLatency(int frames) { } + ftl::rgbd::FrameState &state(int ix) override { return builder_.state(ix); } void stop() {} int streamID(const ftl::rgbd::Source *s) const; private: - std::list<FrameSet*> framesets_; // Active framesets - std::list<FrameSet*> allocated_; // Keep memory allocations - + ftl::rgbd::Builder builder_; std::vector<Source*> sources_; ftl::operators::Graph *pipeline_; - size_t head_; - std::function<bool(FrameSet &)> callback_; - MUTEX mutex_; - int mspf_; - float latency_; - float fps_; - int stats_count_; - int64_t last_ts_; + std::atomic<int> jobs_; volatile bool skip_; ftl::timer::TimerHandle cap_id_; ftl::timer::TimerHandle swap_id_; ftl::timer::TimerHandle main_id_; std::string name_; - - /* Insert a new frameset into the buffer, along with all intermediate - * framesets between the last in buffer and the new one. - */ - ftl::rgbd::FrameSet *_addFrameset(int64_t timestamp); + MUTEX mutex_; void _retrieveJob(ftl::rgbd::Source *); void _computeJob(ftl::rgbd::Source *); - - /* Find a frameset with given latency in frames. */ - ftl::rgbd::FrameSet *_getFrameset(); - - /* Search for a matching frameset. */ - ftl::rgbd::FrameSet *_findFrameset(int64_t ts); - void _freeFrameset(ftl::rgbd::FrameSet *); - - void _recordStats(float fps, float latency); }; } diff --git a/components/rgbd-sources/include/ftl/rgbd/source.hpp b/components/rgbd-sources/include/ftl/rgbd/source.hpp index dc41f10a125584f9a070a2025d78c9f22d07b0d0..23fff94401e9f2bb27420cd3373c96781b68592b 100644 --- a/components/rgbd-sources/include/ftl/rgbd/source.hpp +++ b/components/rgbd-sources/include/ftl/rgbd/source.hpp @@ -155,7 +155,7 @@ class Source : public ftl::Configurable { /** * Get the camera position as a pose matrix. */ - const Eigen::Matrix4d &getPose() const; + [[deprecated]] const Eigen::Matrix4d &getPose() const; /** * Check what features this source has available. @@ -173,6 +173,8 @@ class Source : public ftl::Configurable { std::string getURI() { return value("uri", std::string("")); } + ftl::rgbd::FrameState &state() { return impl_->state_; } + //void customImplementation(detail::Source *); SHARED_MUTEX &mutex() { return mutex_; } @@ -267,11 +269,11 @@ void ftl::rgbd::Source::inject(ftl::codecs::Channel c, ARGS... args) { spkt.timestamp = impl_->timestamp_; spkt.channel = c; - spkt.channel_count = 0; + spkt.frame_number = 0; spkt.streamID = 0; pkt.codec = ftl::codecs::codec_t::MSGPACK; - pkt.block_number = 0; - pkt.block_total = 1; + pkt.bitrate = 0; + pkt.frame_count = 1; pkt.definition = ftl::codecs::definition_t::Any; pkt.flags = 0; diff --git a/components/rgbd-sources/include/ftl/rgbd/streamer.hpp b/components/rgbd-sources/include/ftl/rgbd/streamer.hpp index a33a6d174f2fec98f660e9fcd7996ae66162d163..f6fc1a044a98cd0faa5235772617741920d7ca21 100644 --- a/components/rgbd-sources/include/ftl/rgbd/streamer.hpp +++ b/components/rgbd-sources/include/ftl/rgbd/streamer.hpp @@ -94,7 +94,7 @@ enum encoder_t { */ class Streamer : public ftl::Configurable { public: - Streamer(nlohmann::json &config, ftl::net::Universe *net); + [[deprecated]] Streamer(nlohmann::json &config, ftl::net::Universe *net); ~Streamer(); /** @@ -125,8 +125,6 @@ class Streamer : public ftl::Configurable { void wait(); - void setLatency(int l) { group_.setLatency(l); } - /** * Alternative to calling run(), it will operate a single frame capture, * compress and stream cycle. @@ -138,6 +136,7 @@ class Streamer : public ftl::Configurable { private: ftl::rgbd::Group group_; std::map<std::string, detail::StreamSource*> sources_; + std::vector<detail::StreamSource*> sourcesByNum_; std::list<ftl::rgbd::Group*> proxy_grps_; //ctpl::thread_pool pool_; SHARED_MUTEX mutex_; @@ -151,7 +150,7 @@ class Streamer : public ftl::Configurable { int64_t frame_no_; bool insert_iframes_; - encoder_t encode_mode_; + ftl::codecs::Channel second_channel_; int64_t mspf_; float actual_fps_; diff --git a/components/rgbd-sources/src/frame.cpp b/components/rgbd-sources/src/frame.cpp index c1b76e117852e30d61f1e79e2e88882ee6855981..a0b9be98b1c00ed45ae5f65953d41fbd8eabec12 100644 --- a/components/rgbd-sources/src/frame.cpp +++ b/components/rgbd-sources/src/frame.cpp @@ -2,24 +2,90 @@ #include <ftl/rgbd/frame.hpp> using ftl::rgbd::Frame; +using ftl::rgbd::FrameState; using ftl::codecs::Channels; using ftl::codecs::Channel; static cv::Mat none; static cv::cuda::GpuMat noneGPU; +FrameState::FrameState() : camera_left_({0}), camera_right_({0}), config_(nlohmann::json::value_t::object) { + pose_ = Eigen::Matrix4d::Identity(); +} + +FrameState::FrameState(FrameState &f) { + pose_ = f.pose_; + camera_left_ = f.camera_left_; + camera_right_ = f.camera_right_; + changed_ = f.changed_; + config_ = f.config_; + // TODO: Add mutex lock + f.changed_.clear(); +} + +FrameState::FrameState(FrameState &&f) { + pose_ = f.pose_; + camera_left_ = f.camera_left_; + camera_right_ = f.camera_right_; + changed_ = f.changed_; + config_ = std::move(f.config_); + // TODO: Add mutex lock + f.changed_.clear(); +} + +FrameState &FrameState::operator=(FrameState &f) { + pose_ = f.pose_; + camera_left_ = f.camera_left_; + camera_right_ = f.camera_right_; + changed_ = f.changed_; + config_ = f.config_; + // TODO: Add mutex lock + f.changed_.clear(); + return *this; +} + +FrameState &FrameState::operator=(FrameState &&f) { + pose_ = f.pose_; + camera_left_ = f.camera_left_; + camera_right_ = f.camera_right_; + changed_ = f.changed_; + config_ = std::move(f.config_); + // TODO: Add mutex lock + f.changed_.clear(); + return *this; +} + +void FrameState::setPose(const Eigen::Matrix4d &pose) { + pose_ = pose; + changed_ += Channel::Pose; +} + +void FrameState::setLeft(const ftl::rgbd::Camera &p) { + camera_left_ = p; + changed_ += Channel::Calibration; +} + +void FrameState::setRight(const ftl::rgbd::Camera &p) { + camera_right_ = p; + changed_ += Channel::Calibration2; +} + +// ============================================================================= + void Frame::reset() { + origin_ = nullptr; channels_.clear(); gpu_.clear(); - for (size_t i=0u; i<Channels::kMax; ++i) { + for (size_t i=0u; i<Channels<0>::kMax; ++i) { data_[i].encoded.clear(); } } void Frame::resetFull() { + origin_ = nullptr; channels_.clear(); gpu_.clear(); - for (size_t i=0u; i<Channels::kMax; ++i) { + for (size_t i=0u; i<Channels<0>::kMax; ++i) { data_[i].gpu = cv::cuda::GpuMat(); data_[i].host = cv::Mat(); data_[i].encoded.clear(); @@ -34,8 +100,8 @@ void Frame::upload(Channel c, cv::cuda::Stream stream) { upload(Channels(c), stream); } -void Frame::download(Channels c, cv::cuda::Stream stream) { - for (size_t i=0u; i<Channels::kMax; ++i) { +void Frame::download(Channels<0> c, cv::cuda::Stream stream) { + for (size_t i=0u; i<Channels<0>::kMax; ++i) { if (c.has(i) && channels_.has(i) && gpu_.has(i)) { data_[i].gpu.download(data_[i].host, stream); gpu_ -= i; @@ -43,8 +109,8 @@ void Frame::download(Channels c, cv::cuda::Stream stream) { } } -void Frame::upload(Channels c, cv::cuda::Stream stream) { - for (size_t i=0u; i<Channels::kMax; ++i) { +void Frame::upload(Channels<0> c, cv::cuda::Stream stream) { + for (size_t i=0u; i<Channels<0>::kMax; ++i) { if (c.has(i) && channels_.has(i) && !gpu_.has(i)) { data_[i].gpu.upload(data_[i].host, stream); gpu_ += i; @@ -83,15 +149,17 @@ void Frame::mergeEncoding(ftl::rgbd::Frame &f) { } } -bool Frame::empty(ftl::codecs::Channels channels) { +bool Frame::empty(ftl::codecs::Channels<0> channels) { for (auto c : channels) { if (empty(c)) return true; } return false; } -void Frame::swapTo(ftl::codecs::Channels channels, Frame &f) { +void Frame::swapTo(ftl::codecs::Channels<0> channels, Frame &f) { f.reset(); + f.origin_ = origin_; + f.state_ = state_; // For all channels in this frame object for (auto c : channels_) { @@ -141,8 +209,10 @@ void Frame::swapChannels(ftl::codecs::Channel a, ftl::codecs::Channel b) { } } -void Frame::copyTo(ftl::codecs::Channels channels, Frame &f) { +void Frame::copyTo(ftl::codecs::Channels<0> channels, Frame &f) { f.reset(); + f.origin_ = origin_; + f.state_ = state_; // For all channels in this frame object for (auto c : channels_) { @@ -228,6 +298,32 @@ template<> const cv::cuda::GpuMat& Frame::get(ftl::codecs::Channel channel) cons return _get(channel).gpu; } +template<> const Eigen::Matrix4d& Frame::get(ftl::codecs::Channel channel) const { + if (channel == Channel::Pose) { + return state_.getPose(); + } + + throw ftl::exception(ftl::Formatter() << "Invalid pose channel: " << (int)channel); +} + +template<> const ftl::rgbd::Camera& Frame::get(ftl::codecs::Channel channel) const { + if (channel == Channel::Calibration) { + return state_.getLeft(); + } else if (channel == Channel::Calibration2) { + return state_.getRight(); + } + + throw ftl::exception(ftl::Formatter() << "Invalid calibration channel: " << (int)channel); +} + +template<> const nlohmann::json& Frame::get(ftl::codecs::Channel channel) const { + if (channel == Channel::Configuration) { + return state_.getConfig(); + } + + throw ftl::exception(ftl::Formatter() << "Invalid configuration channel: " << (int)channel); +} + template <> cv::Mat &Frame::create(ftl::codecs::Channel c, const ftl::rgbd::FormatBase &f) { if (c == Channel::None) { throw ftl::exception("Cannot create a None channel"); @@ -264,6 +360,11 @@ template <> cv::cuda::GpuMat &Frame::create(ftl::codecs::Channel c, const ftl::r return m.gpu; } +void Frame::clearPackets(ftl::codecs::Channel c) { + auto &m = _get(c); + m.encoded.clear(); +} + template <> cv::Mat &Frame::create(ftl::codecs::Channel c) { if (c == Channel::None) { throw ftl::exception("Cannot create a None channel"); @@ -297,3 +398,40 @@ void Frame::resetTexture(ftl::codecs::Channel c) { m.tex.free(); } +void Frame::setOrigin(ftl::rgbd::FrameState *state) { + if (origin_ != nullptr) { + throw ftl::exception("Can only set origin once after reset"); + } + + origin_ = state; + state_ = *state; +} + +const Eigen::Matrix4d &Frame::getPose() const { + return get<Eigen::Matrix4d>(ftl::codecs::Channel::Pose); +} + +const ftl::rgbd::Camera &Frame::getLeftCamera() const { + return get<ftl::rgbd::Camera>(ftl::codecs::Channel::Calibration); +} + +const ftl::rgbd::Camera &Frame::getRightCamera() const { + return get<ftl::rgbd::Camera>(ftl::codecs::Channel::Calibration2); +} + +void ftl::rgbd::Frame::setPose(const Eigen::Matrix4d &pose) { + if (origin_) origin_->setPose(pose); +} + +void ftl::rgbd::Frame::setLeftCamera(const ftl::rgbd::Camera &c) { + if (origin_) origin_->setLeft(c); +} + +void ftl::rgbd::Frame::setRightCamera(const ftl::rgbd::Camera &c) { + if (origin_) origin_->setRight(c); +} + +std::string ftl::rgbd::Frame::getConfigString() const { + return get<nlohmann::json>(ftl::codecs::Channel::Configuration).dump(); +} + diff --git a/components/rgbd-sources/src/frameset.cpp b/components/rgbd-sources/src/frameset.cpp index 44be010a344210ca2fe3cbc206a06155ee57c0ff..9cda59838d92f41d0d10e699b3bd7cb530947345 100644 --- a/components/rgbd-sources/src/frameset.cpp +++ b/components/rgbd-sources/src/frameset.cpp @@ -1,16 +1,25 @@ #include <ftl/rgbd/frameset.hpp> +#include <ftl/timer.hpp> +#include <chrono> + +using ftl::rgbd::Builder; +using ftl::rgbd::kMaxFramesets; +using ftl::rgbd::kMaxFramesInSet; +using std::vector; +using std::chrono::milliseconds; +using std::this_thread::sleep_for; +using ftl::codecs::Channel; using ftl::rgbd::FrameSet; using ftl::codecs::Channels; -using ftl::codecs::Channel; -void FrameSet::upload(ftl::codecs::Channels c, cudaStream_t stream) { +void FrameSet::upload(ftl::codecs::Channels<0> c, cudaStream_t stream) { for (auto &f : frames) { f.upload(c, stream); } } -void FrameSet::download(ftl::codecs::Channels c, cudaStream_t stream) { +void FrameSet::download(ftl::codecs::Channels<0> c, cudaStream_t stream) { for (auto &f : frames) { f.download(c, stream); } @@ -21,8 +30,6 @@ void FrameSet::swapTo(ftl::rgbd::FrameSet &fs) { //if (fs.frames.size() != frames.size()) { // Assume "this" is correct and "fs" is not. - fs.sources.clear(); - for (auto s : sources) fs.sources.push_back(s); fs.frames.resize(frames.size()); //} @@ -32,7 +39,7 @@ void FrameSet::swapTo(ftl::rgbd::FrameSet &fs) { fs.mask = static_cast<unsigned int>(mask); for (size_t i=0; i<frames.size(); ++i) { - frames[i].swapTo(Channels::All(), fs.frames[i]); + frames[i].swapTo(Channels<0>::All(), fs.frames[i]); } stale = true; @@ -45,3 +52,248 @@ void FrameSet::resetFull() { f.resetFull(); } } + +// ============================================================================= + +Builder::Builder() : head_(0) { + jobs_ = 0; + skip_ = false; + //setFPS(20); + size_ = 0; + + mspf_ = ftl::timer::getInterval(); + name_ = "NoName"; + + latency_ = 0.0f;; + stats_count_ = 0; + fps_ = 0.0f; +} + +Builder::~Builder() { + main_id_.cancel(); + + UNIQUE_LOCK(mutex_, lk); + // Make sure all jobs have finished + while (jobs_ > 0) { + sleep_for(milliseconds(10)); + } +} + + +void Builder::push(int64_t timestamp, int ix, ftl::rgbd::Frame &frame) { + if (timestamp <= 0 || ix < 0 || ix >= kMaxFramesInSet) return; + + UNIQUE_LOCK(mutex_, lk); + + // Size is determined by largest frame index received... note that size + // cannot therefore reduce. + if (ix >= size_) { + size_ = ix+1; + states_.resize(size_); + } + states_[ix] = frame.origin(); + + auto *fs = _findFrameset(timestamp); + + if (!fs) { + // Add new frameset + fs = _addFrameset(timestamp); + if (!fs) return; + } + + if (fs->frames.size() < size_) fs->frames.resize(size_); + + lk.unlock(); + SHARED_LOCK(fs->mtx, lk2); + + frame.swapTo(ftl::codecs::kAllChannels, fs->frames[ix]); + + ++fs->count; + + if (fs->mask & (1 << ix)) { + LOG(ERROR) << "Too many frames received for given timestamp: " << timestamp << " (source " << ix << ")"; + } + fs->mask |= (1 << ix); +} + +size_t Builder::size() { + return size_; +} + +void Builder::onFrameSet(const std::function<bool(ftl::rgbd::FrameSet &)> &cb) { + // 3. Issue IO retrieve ad compute jobs before finding a valid + // frame at required latency to pass to callback. + main_id_ = ftl::timer::add(ftl::timer::kTimerMain, [this,cb](int64_t ts) { + //if (jobs_ > 0) LOG(ERROR) << "SKIPPING TIMER JOB " << ts; + if (jobs_ > 0) return true; + jobs_++; + + // Find a previous frameset and specified latency and do the sync + // callback with that frameset. + //if (latency_ > 0) { + ftl::rgbd::FrameSet *fs = nullptr; + + UNIQUE_LOCK(mutex_, lk); + fs = _getFrameset(); + + //LOG(INFO) << "Latency for " << name_ << " = " << (latency_*ftl::timer::getInterval()) << "ms"; + + if (fs) { + UNIQUE_LOCK(fs->mtx, lk2); + lk.unlock(); + // The buffers are invalid after callback so mark stale + fs->stale = true; + + //LOG(INFO) << "PROCESS FRAMESET"; + + //ftl::pool.push([this,fs,cb](int) { + try { + cb(*fs); + //LOG(INFO) << "Frameset processed (" << name_ << "): " << fs->timestamp; + } catch(std::exception &e) { + LOG(ERROR) << "Exception in group sync callback: " << e.what(); + } + + //fs->resetFull(); + + lk.lock(); + _freeFrameset(fs); + + jobs_--; + //}); + } else { + //LOG(INFO) << "NO FRAME FOUND: " << name_ << " " << size_; + //latency_++; + jobs_--; + } + //} + + //if (jobs_ == 0) LOG(INFO) << "LAST JOB = Main"; + return true; + }); +} + +ftl::rgbd::FrameState &Builder::state(int ix) { + UNIQUE_LOCK(mutex_, lk); + if (ix < 0 || ix >= states_.size()) { + throw ftl::exception("Frame state out-of-bounds"); + } + if (!states_[ix]) throw ftl::exception("Missing framestate"); + return *states_[ix]; +} + +static void mergeFrameset(ftl::rgbd::FrameSet &f1, ftl::rgbd::FrameSet &f2) { + // Prepend all frame encodings in f2 into corresponding frame in f1. + for (int i=0; i<f1.frames.size(); ++i) { + f1.frames[i].mergeEncoding(f2.frames[i]); + } +} + +void Builder::_recordStats(float fps, float latency) { + latency_ += latency; + fps_ += fps; + ++stats_count_; + + if (fps_/float(stats_count_) <= float(stats_count_)) { + fps_ /= float(stats_count_); + latency_ /= float(stats_count_); + LOG(INFO) << name_ << ": fps = " << fps_ << ", latency = " << latency_; + fps_ = 0.0f; + latency_ = 0.0f; + stats_count_ = 0; + } +} + +ftl::rgbd::FrameSet *Builder::_findFrameset(int64_t ts) { + // Search backwards to find match + for (auto f : framesets_) { + if (f->timestamp == ts) { + return f; + } else if (f->timestamp < ts) { + return nullptr; + } + } + + return nullptr; +} + +/* + * Get the most recent completed frameset that isn't stale. + * Note: Must occur inside a mutex lock. + */ +ftl::rgbd::FrameSet *Builder::_getFrameset() { + for (auto i=framesets_.begin(); i!=framesets_.end(); i++) { + auto *f = *i; + if (!f->stale && f->count >= size_) { + //LOG(INFO) << "GET FRAMESET and remove: " << f->timestamp; + auto j = framesets_.erase(i); + + int count = 0; + // Merge all previous frames + for (; j!=framesets_.end(); j++) { + ++count; + + auto *f2 = *j; + j = framesets_.erase(j); + mergeFrameset(*f,*f2); + _freeFrameset(f2); + } + + //if (count > 0) LOG(INFO) << "COUNT = " << count; + + int64_t now = ftl::timer::get_time(); + float framerate = 1000.0f / float(now - last_ts_); + _recordStats(framerate, now - f->timestamp); + last_ts_ = now; + return f; + } + } + + return nullptr; +} + +void Builder::_freeFrameset(ftl::rgbd::FrameSet *fs) { + allocated_.push_back(fs); +} + +ftl::rgbd::FrameSet *Builder::_addFrameset(int64_t timestamp) { + if (allocated_.size() == 0) { + if (framesets_.size() < kMaxFramesets) { + allocated_.push_back(new ftl::rgbd::FrameSet); + } else { + LOG(ERROR) << "Could not allocate framesetL: " << timestamp; + return nullptr; + } + } + FrameSet *newf = allocated_.front(); + allocated_.pop_front(); + + newf->timestamp = timestamp; + newf->id = 0; + newf->count = 0; + newf->mask = 0; + newf->stale = false; + newf->frames.resize(size_); + + for (auto &f : newf->frames) f.reset(); + + // Insertion sort by timestamp + for (auto i=framesets_.begin(); i!=framesets_.end(); i++) { + auto *f = *i; + + if (timestamp > f->timestamp) { + framesets_.insert(i, newf); + return newf; + } + } + + framesets_.push_back(newf); + return newf; +} + +void Builder::setName(const std::string &name) { + name_ = name; +} + + + diff --git a/components/rgbd-sources/src/group.cpp b/components/rgbd-sources/src/group.cpp index ca64d4764def2c4a2c5c79b96798d916eac6e6a9..409559d5881b55ff1826c12e5b5ea29852743a1a 100644 --- a/components/rgbd-sources/src/group.cpp +++ b/components/rgbd-sources/src/group.cpp @@ -13,29 +13,10 @@ using std::chrono::milliseconds; using std::this_thread::sleep_for; using ftl::codecs::Channel; -Group::Group() : pipeline_(nullptr), head_(0) { - //framesets_[0].timestamp = -1; - //framesets_[0].stale = true; - - /*for (auto &i : framesets_) { - i.stale = true; - }*/ - - // Allocate some initial framesets - //for (int i=0; i<10; ++i) { - // allocated_.push_back(new ftl::rgbd::FrameSet); - //} - +Group::Group() : pipeline_(nullptr) { jobs_ = 0; skip_ = false; - //setFPS(20); - - mspf_ = ftl::timer::getInterval(); name_ = "NoName"; - - latency_ = 0.0f;; - stats_count_ = 0; - fps_ = 0.0f; } Group::~Group() { @@ -54,80 +35,17 @@ Group::~Group() { } } -//void Group::setFPS(int fps) { -// mspf_ = 1000 / fps; -// ftl::timer::setInterval(mspf_); -//} - void Group::addSource(ftl::rgbd::Source *src) { UNIQUE_LOCK(mutex_, lk); size_t ix = sources_.size(); sources_.push_back(src); src->setCallback([this,ix,src](int64_t timestamp, ftl::rgbd::Frame &frame) { - if (timestamp == 0) return; - - auto chan = src->getChannel(); - - //LOG(INFO) << "SRC CB (" << name_ << "): " << timestamp << " (" << ")"; - - UNIQUE_LOCK(mutex_, lk); - auto *fs = _findFrameset(timestamp); - - if (!fs) { - // Add new frameset - fs = _addFrameset(timestamp); - - if (!fs) return; - } /*else if (framesets_[(head_+1)%kFrameBufferSize].timestamp > timestamp) { - // Too old, just ditch it - LOG(WARNING) << "Received frame too old for buffer"; - return; - }*/ - - // Search backwards to find match - //for (size_t i=0; i<kFrameBufferSize; ++i) { - // FrameSet &fs = framesets_[(head_+kFrameBufferSize-i) % kFrameBufferSize]; - //if (fs.timestamp == timestamp) { - lk.unlock(); - SHARED_LOCK(fs->mtx, lk2); - - frame.swapTo(ftl::codecs::kAllChannels, fs->frames[ix]); - - if (fs->count+1 == sources_.size()) { - if (pipeline_) { - pipeline_->apply(*fs, *fs, 0); - } - } - - ++fs->count; - fs->mask |= (1 << ix); - - if (fs->count == sources_.size()) { - //LOG(INFO) << "COMPLETE SET (" << name_ << "): " << fs->timestamp; - } else if (fs->count > sources_.size()) { - LOG(ERROR) << "Too many frames for frame set: " << fs->timestamp << " sources=" << sources_.size(); - } else { - //LOG(INFO) << "INCOMPLETE SET (" << ix << "): " << fs.timestamp; - } - - /*if (callback_ && fs->count == sources_.size()) { - try { - if (callback_(fs)) { - // TODO: Remove callback if returns false? - } - } catch (...) { - LOG(ERROR) << "Exception in group callback"; - } - - // Reset count to prevent multiple reads of these frames - //fs.count = 0; - }*/ - - return; - //} - //} - //LOG(WARNING) << "Frame timestamp not found in buffer"; + // FIXME: Not safe for multiple sources + if (pipeline_) { + pipeline_->apply(frame, frame, 0); + } + builder_.push(timestamp, ix, frame); }); } @@ -166,7 +84,7 @@ int Group::streamID(const ftl::rgbd::Source *s) const { return -1; } -void Group::sync(std::function<bool(ftl::rgbd::FrameSet &)> cb) { +void Group::onFrameSet(const ftl::rgbd::VideoCallback &cb) { //if (latency_ == 0) { // callback_ = cb; //} @@ -198,7 +116,7 @@ void Group::sync(std::function<bool(ftl::rgbd::FrameSet &)> cb) { main_id_ = ftl::timer::add(ftl::timer::kTimerMain, [this,cb](int64_t ts) { //if (skip_) LOG(ERROR) << "SKIPPING TIMER JOB " << ts; if (skip_) return true; - jobs_++; + //jobs_++; for (auto s : sources_) { jobs_ += 2; @@ -214,46 +132,11 @@ void Group::sync(std::function<bool(ftl::rgbd::FrameSet &)> cb) { --jobs_; }); } + return true; + }); - // Find a previous frameset and specified latency and do the sync - // callback with that frameset. - //if (latency_ > 0) { - ftl::rgbd::FrameSet *fs = nullptr; - - UNIQUE_LOCK(mutex_, lk); - fs = _getFrameset(); - - //LOG(INFO) << "Latency for " << name_ << " = " << (latency_*ftl::timer::getInterval()) << "ms"; - - if (fs) { - UNIQUE_LOCK(fs->mtx, lk2); - lk.unlock(); - // The buffers are invalid after callback so mark stale - fs->stale = true; - - //ftl::pool.push([this,fs,cb](int) { - try { - cb(*fs); - //LOG(INFO) << "Frameset processed (" << name_ << "): " << fs->timestamp; - } catch(std::exception &e) { - LOG(ERROR) << "Exception in group sync callback: " << e.what(); - } - - //fs->resetFull(); - - lk.lock(); - _freeFrameset(fs); - - jobs_--; - //}); - } else { - //LOG(INFO) << "NO FRAME FOUND: " << last_ts_ - latency_*mspf_; - //latency_++; - jobs_--; - } - //} - - //if (jobs_ == 0) LOG(INFO) << "LAST JOB = Main"; + builder_.onFrameSet([this,cb](ftl::rgbd::FrameSet &fs) { + cb(fs); return true; }); } @@ -264,186 +147,15 @@ void Group::addRawCallback(const std::function<void(ftl::rgbd::Source*, const ft } } -void Group::removeRawCallback(const std::function<void(ftl::rgbd::Source*, const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt)> &f) { +/*void Group::removeRawCallback(const std::function<void(ftl::rgbd::Source*, const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt)> &f) { for (auto s : sources_) { s->removeRawCallback(f); } -} - -static void mergeFrameset(ftl::rgbd::FrameSet &f1, ftl::rgbd::FrameSet &f2) { - // Prepend all frame encodings in f2 into corresponding frame in f1. - for (int i=0; i<f1.frames.size(); ++i) { - f1.frames[i].mergeEncoding(f2.frames[i]); - } -} - -void Group::_recordStats(float fps, float latency) { - latency_ += latency; - fps_ += fps; - ++stats_count_; - - if (fps_/float(stats_count_) <= float(stats_count_)) { - fps_ /= float(stats_count_); - latency_ /= float(stats_count_); - LOG(INFO) << name_ << ": fps = " << fps_ << ", latency = " << latency_; - fps_ = 0.0f; - latency_ = 0.0f; - stats_count_ = 0; - } -} - -ftl::rgbd::FrameSet *Group::_findFrameset(int64_t ts) { - // Search backwards to find match - for (auto f : framesets_) { - if (f->timestamp == ts) { - return f; - } else if (f->timestamp < ts) { - return nullptr; - } - } - - return nullptr; -} - -/* - * Get the most recent completed frameset that isn't stale. - * Note: Must occur inside a mutex lock. - */ -ftl::rgbd::FrameSet *Group::_getFrameset() { - for (auto i=framesets_.begin(); i!=framesets_.end(); i++) { - auto *f = *i; - if (!f->stale && f->count == sources_.size()) { - //LOG(INFO) << "GET FRAMESET and remove: " << f->timestamp; - auto j = framesets_.erase(i); - - int count = 0; - // Merge all previous frames - for (; j!=framesets_.end(); j++) { - ++count; - - auto *f2 = *j; - j = framesets_.erase(j); - mergeFrameset(*f,*f2); - _freeFrameset(f2); - } - - //if (count > 0) LOG(INFO) << "COUNT = " << count; - - int64_t now = ftl::timer::get_time(); - float framerate = 1000.0f / float(now - last_ts_); - _recordStats(framerate, now - f->timestamp); - last_ts_ = now; - return f; - } - } - - return nullptr; -} - -void Group::_freeFrameset(ftl::rgbd::FrameSet *fs) { - allocated_.push_back(fs); -} - -ftl::rgbd::FrameSet *Group::_addFrameset(int64_t timestamp) { - if (allocated_.size() == 0) { - if (framesets_.size() < kMaxFramesets) { - allocated_.push_back(new ftl::rgbd::FrameSet); - } else { - LOG(ERROR) << "Could not allocate frameset."; - return nullptr; - } - } - FrameSet *newf = allocated_.front(); - allocated_.pop_front(); - - newf->timestamp = timestamp; - newf->count = 0; - newf->mask = 0; - newf->stale = false; - newf->frames.resize(sources_.size()); - - for (auto &f : newf->frames) f.reset(); - - if (newf->sources.size() != sources_.size()) { - newf->sources.clear(); - for (auto s : sources_) newf->sources.push_back(s); - } - - // Insertion sort by timestamp - for (auto i=framesets_.begin(); i!=framesets_.end(); i++) { - auto *f = *i; - - if (timestamp > f->timestamp) { - framesets_.insert(i, newf); - return newf; - } - } - - framesets_.push_back(newf); - return newf; - - - //int count = (framesets_[head_].timestamp == -1) ? 1 : (timestamp - framesets_[head_].timestamp) / mspf_; - //LOG(INFO) << "Massive timestamp difference: " << count; - - // Allow for massive timestamp changes (Windows clock adjust) - // Only add a single frameset for large changes - //if (count < -int(kFrameBufferSize) || count >= kFrameBufferSize-1) { - /*if (framesets_[head_].timestamp < timestamp) { - head_ = (head_+1) % kFrameBufferSize; - - //if (framesets_[head_].stale == false) LOG(FATAL) << "Buffer exceeded"; - - #ifdef DEBUG_MUTEX - std::unique_lock<std::shared_timed_mutex> lk(framesets_[head_].mtx, std::defer_lock); - #else - std::unique_lock<std::shared_mutex> lk(framesets_[head_].mtx, std::defer_lock); - #endif - if (!lk.try_lock()) { - LOG(ERROR) << "Frameset in use!!"; - return; - } - framesets_[head_].timestamp = timestamp; - framesets_[head_].count = 0; - framesets_[head_].mask = 0; - framesets_[head_].stale = false; - framesets_[head_].frames.resize(sources_.size()); - - for (auto &f : framesets_[head_].frames) f.reset(); - - if (framesets_[head_].sources.size() != sources_.size()) { - framesets_[head_].sources.clear(); - for (auto s : sources_) framesets_[head_].sources.push_back(s); - } - - // Find number of valid frames that will be skipped - int count = 0; - //for (int j=1; j<kFrameBufferSize; ++j) { - int idx2 = (head_+kFrameBufferSize-1)%kFrameBufferSize; - //if (framesets_[idx2].stale || framesets_[idx2].timestamp <= 0) break; - ++count; - - // Make sure encoded packets are moved from skipped frames - //if (framesets_[idx2].count >= framesets_[idx2].sources.size()) { - if (framesets_[idx2].stale == false) { - mergeFrameset(framesets_[head_], framesets_[idx2]); - framesets_[idx2].stale = true; - framesets_[idx2].timestamp = -1; - } - //} - - float framerate = 1000.0f / float((count+1)*ftl::timer::getInterval()); - _recordStats(framerate, ftl::timer::get_time() - timestamp); - - return; - } else { - LOG(ERROR) << "Old frame received: " << (framesets_[head_].timestamp - timestamp); - return; - }*/ -} +}*/ void Group::setName(const std::string &name) { name_ = name; + builder_.setName(name); } diff --git a/components/rgbd-sources/src/source.cpp b/components/rgbd-sources/src/source.cpp index 384978e7f3431847806253febbe37b149aca31aa..a71ea24e9ed6106425bf88f3726f226416a39e76 100644 --- a/components/rgbd-sources/src/source.cpp +++ b/components/rgbd-sources/src/source.cpp @@ -2,7 +2,7 @@ #include <ftl/rgbd/source.hpp> #include <ftl/threads.hpp> -#include "sources/net/net.hpp" +//#include "sources/net/net.hpp" #include "sources/stereovideo/stereovideo.hpp" #include "sources/image/image.hpp" #include "sources/middlebury/middlebury_source.hpp" @@ -12,7 +12,7 @@ #include "sources/snapshot/snapshot_source.hpp" #endif -#include "sources/ftlfile/file_source.hpp" +//#include "sources/ftlfile/file_source.hpp" #ifdef HAVE_REALSENSE #include "sources/realsense/realsense_source.hpp" @@ -25,12 +25,12 @@ using ftl::rgbd::Source; using ftl::Configurable; using std::string; using ftl::rgbd::detail::StereoVideoSource; -using ftl::rgbd::detail::NetSource; +//using ftl::rgbd::detail::NetSource; using ftl::rgbd::detail::ImageSource; using ftl::rgbd::detail::MiddleburySource; using ftl::rgbd::capability_t; using ftl::codecs::Channel; -using ftl::rgbd::detail::FileSource; +//using ftl::rgbd::detail::FileSource; using ftl::rgbd::Camera; using ftl::rgbd::RawCallback; using ftl::rgbd::FrameCallback; @@ -118,9 +118,11 @@ ftl::rgbd::detail::Source *Source::_createFileImpl(const ftl::URI &uri) { string ext = path.substr(eix+1); if (ext == "ftl") { - ftl::rgbd::Player *reader = __createReader(path); - LOG(INFO) << "Playing track: " << uri.getFragment(); - return new FileSource(this, reader, std::stoi(uri.getFragment())); + //ftl::rgbd::Player *reader = __createReader(path); + //LOG(INFO) << "Playing track: " << uri.getFragment(); + //return new FileSource(this, reader, std::stoi(uri.getFragment())); + LOG(FATAL) << "File sources not supported"; + return nullptr; } else if (ext == "png" || ext == "jpg") { return new ImageSource(this, path); } else if (ext == "mp4") { @@ -144,7 +146,7 @@ ftl::rgbd::detail::Source *Source::_createFileImpl(const ftl::URI &uri) { return nullptr; } -ftl::rgbd::Player *Source::__createReader(const std::string &path) { +/*ftl::rgbd::Player *Source::__createReader(const std::string &path) { if (readers__.find(path) != readers__.end()) { return readers__[path]; } @@ -158,10 +160,12 @@ ftl::rgbd::Player *Source::__createReader(const std::string &path) { readers__[path] = r; r->begin(); return r; -} +}*/ ftl::rgbd::detail::Source *Source::_createNetImpl(const ftl::URI &uri) { - return new NetSource(this); + LOG(FATAL) << "Net sources no longer supported"; + //return new NetSource(this); + return nullptr; } ftl::rgbd::detail::Source *Source::_createDeviceImpl(const ftl::URI &uri) { @@ -301,6 +305,7 @@ Camera Camera::scaled(int width, int height) const { } void Source::notify(int64_t ts, ftl::rgbd::Frame &f) { + //if (impl_) f.setOrigin(&impl_->state_); if (callback_) callback_(ts, f); } @@ -309,13 +314,13 @@ void Source::inject(const Eigen::Matrix4d &pose) { ftl::codecs::Packet pkt; spkt.timestamp = impl_->timestamp_; - spkt.channel_count = 0; + spkt.frame_number = 0; spkt.channel = Channel::Pose; spkt.streamID = 0; pkt.codec = ftl::codecs::codec_t::MSGPACK; pkt.definition = ftl::codecs::definition_t::Any; - pkt.block_number = 0; - pkt.block_total = 1; + pkt.bitrate = 0; + pkt.frame_count = 1; pkt.flags = 0; std::vector<double> data(pose.data(), pose.data() + 4*4*sizeof(double)); diff --git a/components/rgbd-sources/src/sources/ftlfile/file_source.cpp b/components/rgbd-sources/src/sources/ftlfile/file_source.cpp index 01b602c207d6cfa7192a7534890e913655ee4286..f0ba1e010ef00e880a4ff4a532dfd310673c275c 100644 --- a/components/rgbd-sources/src/sources/ftlfile/file_source.cpp +++ b/components/rgbd-sources/src/sources/ftlfile/file_source.cpp @@ -76,7 +76,7 @@ FileSource::FileSource(ftl::rgbd::Source *s, ftl::rgbd::Player *r, int sid) : ft // TODO: Check I-Frames for H264 if (pkt.codec == codec_t::HEVC) { - if (ftl::codecs::hevc::isIFrame(pkt.data)) _removeChannel(spkt.channel); + if (ftl::codecs::hevc::isIFrame(pkt.data.data(), pkt.data.size())) _removeChannel(spkt.channel); } cache_[cache_write_].emplace_back(); auto &c = cache_[cache_write_].back(); @@ -178,6 +178,7 @@ bool FileSource::compute(int n, int b) { int lastc = 0; frame_.reset(); + frame_.setOrigin(&state_); // Decide which channels to decode decode_channels_ = Channel::Colour; diff --git a/components/rgbd-sources/src/sources/ftlfile/file_source.hpp b/components/rgbd-sources/src/sources/ftlfile/file_source.hpp index 839121f3afdde383d73635428cd172a189d7af47..9aa11b929f1d87b486064ec4337de3b48f0d1d70 100644 --- a/components/rgbd-sources/src/sources/ftlfile/file_source.hpp +++ b/components/rgbd-sources/src/sources/ftlfile/file_source.hpp @@ -46,8 +46,8 @@ class FileSource : public detail::Source { bool freeze_; bool have_frozen_; - ftl::codecs::Channels decode_channels_; - ftl::codecs::Channels available_channels_; + ftl::codecs::Channels<0> decode_channels_; + ftl::codecs::Channels<0> available_channels_; void _processCalibration(ftl::codecs::Packet &pkt); void _processPose(ftl::codecs::Packet &pkt); diff --git a/components/rgbd-sources/src/sources/net/net.cpp b/components/rgbd-sources/src/sources/net/net.cpp index 4526470083962f04e16d12c69cdb467bd69732de..57b494032afa0331ad21bcb3ac4108df114dbf9b 100644 --- a/components/rgbd-sources/src/sources/net/net.cpp +++ b/components/rgbd-sources/src/sources/net/net.cpp @@ -393,6 +393,7 @@ void NetSource::_completeFrame(NetFrame &frame, int64_t latency) { adaptive_ = abr_.selectBitrate(frame); frame_.reset(); + frame_.setOrigin(&state_); cv::cuda::swap(frame_.create<GpuMat>(Channel::Colour), frame.channel[0]); if (host_->getChannel() != Channel::None) @@ -406,6 +407,7 @@ void NetSource::_completeFrame(NetFrame &frame, int64_t latency) { } void NetSource::setPose(const Eigen::Matrix4d &pose) { + ftl::rgbd::detail::Source::setPose(pose); if (!active_) return; vector<unsigned char> vec((unsigned char*)pose.data(), (unsigned char*)(pose.data()+(pose.size()))); diff --git a/components/rgbd-sources/src/sources/stereovideo/calibrate.cpp b/components/rgbd-sources/src/sources/stereovideo/calibrate.cpp index b2e1886bbcc0e75750099fd48f6cade7b48c1f1d..fd3b9ea917e8506a9a58acaa8ecabaff7eab2ed0 100644 --- a/components/rgbd-sources/src/sources/stereovideo/calibrate.cpp +++ b/components/rgbd-sources/src/sources/stereovideo/calibrate.cpp @@ -43,138 +43,243 @@ using cv::Scalar; using std::string; using std::vector; -Calibrate::Calibrate(nlohmann::json &config, cv::Size image_size, cv::cuda::Stream &stream) : ftl::Configurable(config) { - if (!_loadCalibration()) { - throw ftl::exception("Loading calibration failed"); - } - _calculateRectificationParameters(image_size); +//////////////////////////////////////////////////////////////////////////////// + +static bool isValidTranslationForRectification(const Mat t) { + if (t.type() != CV_64F) { return false; } + if (t.channels() != 1) { return false; } + if (t.size() != Size(1, 3)) { return false; } + if (cv::norm(t, cv::NORM_L2) == 0) { return false; } + return true; +} - LOG(INFO) << "Calibration loaded from file"; - rectify_ = value("rectify", true);; +static bool isValidRotationMatrix(const Mat M) { + if (M.type() != CV_64F) { return false; } + if (M.channels() != 1) { return false; } + if (M.size() != Size(3, 3)) { return false; } - this->on("use_intrinsics", [this](const ftl::config::Event &e) { - rectify_ = value("rectify", true); - }); + if (abs(cv::determinant(M) - 1.0) > 0.00001) + { return false; } + + // accuracy problems + // rotation matrix is orthogonal: M.T * M == M * M.T == I + //if (cv::countNonZero((M.t() * M) != Mat::eye(Size(3, 3), CV_64FC1)) != 0) + // { return false; } + + return true; } -bool Calibrate::_loadCalibration() { - FileStorage fs; +static bool isValidPose(const Mat M) { + if (M.size() != Size(4, 4)) { return false; } + // check last row: 0 0 0 1 + return isValidRotationMatrix(M(cv::Rect(0 , 0, 3, 3))); +} - // read intrinsic parameters - auto ifile = ftl::locateFile(value("intrinsics", std::string("intrinsics.yml"))); - if (ifile) { - fs.open((*ifile).c_str(), FileStorage::READ); - if (!fs.isOpened()) { - LOG(WARNING) << "Could not open intrinsics file"; - return false; - } +static bool isValidCamera(const Mat M) { + if (M.type() != CV_64F) { return false; } + if (M.channels() != 1) { return false; } + if (M.size() != Size(3, 3)) { return false; } + // TODO: last row should be (0 0 0 1) ... + return true; +} - LOG(INFO) << "Intrinsics from: " << *ifile; - } - else { - LOG(WARNING) << "Calibration intrinsics file not found"; - return false; - } +static Mat scaleCameraIntrinsics(Mat K, Size size_new, Size size_old) { + Mat S(cv::Size(3, 3), CV_64F, 0.0); + double scale_x = ((double) size_new.width) / ((double) size_old.width); + double scale_y = ((double) size_new.height) / ((double) size_old.height); - fs["K"] >> K_; - fs["D"] >> D_; - fs["resolution"] >> calib_size_; + S.at<double>(0, 0) = scale_x; + S.at<double>(1, 1) = scale_y; + S.at<double>(2, 2) = 1.0; + return S * K; +} - if ((K_.size() != 2) || (D_.size() != 2)) { - LOG(ERROR) << "Not enough intrinsic paramters, expected 2"; - return false; - } +//////////////////////////////////////////////////////////////////////////////// - fs.release(); +Calibrate::Calibrate(nlohmann::json &config, Size image_size, cv::cuda::Stream &stream) : + ftl::Configurable(config) { + + img_size_ = image_size; + calib_size_ = image_size; + + K_ = vector<Mat>(2); + K_[0] = Mat::eye(Size(3, 3), CV_64FC1); + K_[1] = Mat::eye(Size(3, 3), CV_64FC1); + D_ = vector<Mat>(2); + D_[0] = Mat::zeros(Size(5, 1), CV_64FC1); + D_[1] = Mat::zeros(Size(5, 1), CV_64FC1); + pose_ = Mat::eye(Size(4, 4), CV_64FC1); + Q_ = Mat::eye(Size(4, 4), CV_64FC1); + Q_.at<double>(3, 2) = -1; + Q_.at<double>(2, 3) = 1; + + setRectify(true); +} - if (calib_size_.empty()) { - LOG(ERROR) << "Calibration resolution missing"; - return false; - } +Mat Calibrate::_getK(size_t idx, Size size) { + CHECK(idx < K_.size()); + CHECK(!size.empty()); + return scaleCameraIntrinsics(K_[idx], size, calib_size_); +} - for (const Mat &K : K_) { - if (K.size() != Size(3, 3)) { - LOG(ERROR) << "Invalid intrinsic parameters"; - return false; - } - } - for (const Mat &D : D_) { - if (D.size() != Size(5, 1)) { - LOG(ERROR) << "Invalid intrinsic parameters"; - return false; - } +Mat Calibrate::_getK(size_t idx) { + return _getK(idx, img_size_); +} + +cv::Mat Calibrate::getCameraMatrixLeft(const cv::Size res) { + if (rectify_) { + return Mat(P1_, cv::Rect(0, 0, 3, 3)); + } else { + return scaleCameraIntrinsics(K_[0], res, img_size_); } +} - // read extrinsic parameters - auto efile = ftl::locateFile(value("extrinsics", std::string("extrinsics.yml"))); - if (efile) { - fs.open((*efile).c_str(), FileStorage::READ); - if (!fs.isOpened()) { - LOG(ERROR) << "Could not open extrinsics file"; - return false; - } +cv::Mat Calibrate::getCameraMatrixRight(const cv::Size res) { + if (rectify_) { + return Mat(P2_, cv::Rect(0, 0, 3, 3)); + } else { + return scaleCameraIntrinsics(K_[1], res, img_size_); + } +} - LOG(INFO) << "Extrinsics from: " << *efile; +bool Calibrate::setRectify(bool enabled) { + if (t_.empty() || R_.empty()) { enabled = false; } + if (enabled) { + rectify_ = calculateRectificationParameters(); } else { - LOG(ERROR) << "Calibration extrinsics file not found"; - return false; + rectify_ = false; } + return rectify_; +} - fs["R"] >> R_; - fs["T"] >> T_; - fs["pose"] >> pose_; +bool Calibrate::setIntrinsics(const Size size, const vector<Mat> K, const vector<Mat> D) { + if (size.empty() || size.width <= 0 || size.height <= 0) { return false; } + if ((K.size() != 2) || (D.size() != 2)) { return false; } + for (const auto k : K) { if (!isValidCamera(k)) { return false; }} + for (const auto d : D) { if (d.size() != Size(5, 1)) { return false; }} + + calib_size_ = size; + K[0].copyTo(K_[0]); + K[1].copyTo(K_[1]); + D[0].copyTo(D_[0]); + D[1].copyTo(D_[1]); + return true; +} - if (pose_.size() != Size(4, 4)) { - LOG(ERROR) << "Pose not in calibration (using identity)"; - // TODO: return false (raises exception in constructor) - // use config option to make pose optional (and not return false) +bool Calibrate::setExtrinsics(const Mat R, const Mat t) { + if (!isValidRotationMatrix(R) || + !isValidTranslationForRectification(t)) { return false; } + + R.copyTo(R_); + t.copyTo(t_); + return true; +} - pose_ = Mat::eye(Size(4, 4), CV_64FC1); - } +bool Calibrate::setPose(const Mat P) { + if (!isValidPose(P)) { return false; } + P.copyTo(pose_); + return true; +} - if ((R_.size() != Size(3, 3)) || - (T_.size() != Size(1, 3))) { +bool Calibrate::loadCalibration(const string fname) { + FileStorage fs; - LOG(ERROR) << "Invalid extrinsic parameters"; + fs.open((fname).c_str(), FileStorage::READ); + if (!fs.isOpened()) { + LOG(WARNING) << "Could not open calibration file"; return false; } + + Size calib_size; + vector<Mat> K; + vector<Mat> D; + Mat R; + Mat t; + Mat pose; + + fs["resolution"] >> calib_size; + fs["K"] >> K; + fs["D"] >> D; + fs["R"] >> R; + fs["t"] >> t; + fs["P"] >> pose; fs.release(); + if (calib_size.empty()) { + LOG(ERROR) << "calibration resolution missing in calibration file"; + return false; + } + if (!setIntrinsics(calib_size, K, D)) { + LOG(ERROR) << "invalid intrinsics in calibration file"; + return false; + } + if (!setExtrinsics(R, t)) { + LOG(ERROR) << "invalid extrinsics in calibration file"; + return false; + } + if (!setPose(pose)) { + LOG(ERROR) << "invalid pose in calibration file"; + return false; // TODO: allow missing pose? (config option) + } + + LOG(INFO) << "calibration loaded from: " << fname; + return true; +} + +bool Calibrate::writeCalibration( const string fname, const Size size, + const vector<Mat> K, const vector<Mat> D, + const Mat R, const Mat t, const Mat pose) { + + cv::FileStorage fs(fname, cv::FileStorage::WRITE); + if (!fs.isOpened()) { return false; } + + fs << "resolution" << size + << "K" << K + << "D" << D + << "R" << R + << "t" << t + << "P" << pose + ; + + fs.release(); return true; } -void Calibrate::_calculateRectificationParameters(Size img_size) { +bool Calibrate::saveCalibration(const string fname) { + // note: never write rectified parameters! + return writeCalibration(fname, calib_size_, K_, D_, R_, t_, pose_); +} + +bool Calibrate::calculateRectificationParameters() { - img_size_ = img_size; - Mat K1 = _getK(0, img_size); + Mat K1 = _getK(0, img_size_); Mat D1 = D_[0]; - Mat K2 = _getK(1, img_size); + Mat K2 = _getK(1, img_size_); Mat D2 = D_[1]; double alpha = value("alpha", 0.0); - cv::stereoRectify( K1, D1, K2, D2, - img_size, R_, T_, - R1_, R2_, P1_, P2_, Q_, 0, alpha); - - // TODO use fixed point maps for CPU (gpu remap() requires floating point) - initUndistortRectifyMap(K1, D1, R1_, P1_, img_size, CV_32FC1, map1_.first, map2_.first); - initUndistortRectifyMap(K2, D2, R2_, P2_, img_size, CV_32FC1, map1_.second, map2_.second); + try { + cv::stereoRectify( K1, D1, K2, D2, + img_size_, R_, t_, + R1_, R2_, P1_, P2_, Q_, 0, alpha); + + // TODO use fixed point maps for CPU (gpu remap() requires floating point) + initUndistortRectifyMap(K1, D1, R1_, P1_, img_size_, CV_32FC1, map1_.first, map2_.first); + initUndistortRectifyMap(K2, D2, R2_, P2_, img_size_, CV_32FC1, map1_.second, map2_.second); + } + catch (cv::Exception ex) { + LOG(ERROR) << ex.what(); + return false; + } // CHECK Is this thread safe!!!! map1_gpu_.first.upload(map1_.first); map1_gpu_.second.upload(map1_.second); map2_gpu_.first.upload(map2_.first); map2_gpu_.second.upload(map2_.second); -} - -void Calibrate::updateCalibration(const ftl::rgbd::Camera &p) { - Q_.at<double>(3, 2) = 1.0 / p.baseline; - Q_.at<double>(2, 3) = p.fx; - Q_.at<double>(0, 3) = p.cx; - Q_.at<double>(1, 3) = p.cy; - // FIXME:(Nick) Update camera matrix also... + return true; } void Calibrate::rectifyStereo(GpuMat &l, GpuMat &r, Stream &stream) { @@ -196,44 +301,3 @@ void Calibrate::rectifyStereo(cv::Mat &l, cv::Mat &r) { cv::remap(l, l, map1_.first, map2_.first, cv::INTER_LINEAR, 0, cv::Scalar()); cv::remap(r, r, map1_.second, map2_.second, cv::INTER_LINEAR, 0, cv::Scalar()); } - -static Mat scaleCameraIntrinsics(Mat K, Size size_new, Size size_old) { - Mat S(cv::Size(3, 3), CV_64F, 0.0); - double scale_x = ((double) size_new.width) / ((double) size_old.width); - double scale_y = ((double) size_new.height) / ((double) size_old.height); - - S.at<double>(0, 0) = scale_x; - S.at<double>(1, 1) = scale_y; - S.at<double>(2, 2) = 1.0; - return S * K; -} - -Mat Calibrate::_getK(size_t idx, Size size) { - CHECK(idx < K_.size()); - CHECK(!size.empty()); - return scaleCameraIntrinsics(K_[idx], size, calib_size_); -} - -Mat Calibrate::_getK(size_t idx) { - return _getK(idx, img_size_); -} - -cv::Mat Calibrate::getCameraMatrixLeft(const cv::Size res) { - Mat M; - if (rectify_) { - M = Mat(P1_, cv::Rect(0, 0, 3, 3)); - } else { - M = K_[0]; - } - return scaleCameraIntrinsics(M, res, img_size_); -} - -cv::Mat Calibrate::getCameraMatrixRight(const cv::Size res) { - Mat M; - if (rectify_) { - M = Mat(P2_, cv::Rect(0, 0, 3, 3)); - } else { - M = K_[1]; - } - return scaleCameraIntrinsics(M, res, img_size_); -} diff --git a/components/rgbd-sources/src/sources/stereovideo/calibrate.hpp b/components/rgbd-sources/src/sources/stereovideo/calibrate.hpp index 82a06e756591ced2fe11535011b7d422320b7597..ed63f125ec49d22767dc1ea7a1c665503147ebe3 100644 --- a/components/rgbd-sources/src/sources/stereovideo/calibrate.hpp +++ b/components/rgbd-sources/src/sources/stereovideo/calibrate.hpp @@ -29,11 +29,13 @@ class Calibrate : public ftl::Configurable { public: Calibrate(nlohmann::json &config, cv::Size image_size, cv::cuda::Stream &stream); - /* @brief Rectify and undistort stereo pair images (GPU) + /** + * @brief Rectify and undistort stereo pair images (GPU) */ void rectifyStereo(cv::cuda::GpuMat &l, cv::cuda::GpuMat &r, cv::cuda::Stream &stream); - /* @brief Rectify and undistort stereo pair images (CPU) + /** + * @brief Rectify and undistort stereo pair images (CPU) * @todo Uses same rectification maps as GPU version, according to OpenCV * documentation for remap(), fixed point versions faster for CPU */ @@ -41,10 +43,13 @@ class Calibrate : public ftl::Configurable { void updateCalibration(const ftl::rgbd::Camera &p); - /* @brief Get disparity to depth matrix + /** + * @brief Get disparity to depth matrix * - * 2020/01/15: Not used, StereoVideoSource creates a Camera object which - * is used to calculate depth from disparity (disp2depth.cu) + * 2020/01/15: StereoVideoSource creates a Camera object which is used to + * calculate depth from disparity (disp2depth.cu). Seems to be + * used only in StereoVideoSource to get doff and baseline + * parameter values in updateParameters() */ const cv::Mat &getQ() const { return Q_; } @@ -54,39 +59,100 @@ class Calibrate : public ftl::Configurable { cv::Mat getCameraMatrixLeft(const cv::Size res); cv::Mat getCameraMatrixRight(const cv::Size res); - /* @brief Get camera pose from calibration + /** + * @brief Get camera pose from calibration */ - cv::Mat getPose() { return pose_; }; + const cv::Mat &getPose() const { return pose_; }; - /* @brief Enable/disable recitification. If disabled, instance returns + /** + * @brief Enable/disable recitification. If disabled, instance returns * original camera intrinsic parameters (getCameraMatrixLeft() and - getCameraMatrixRight() methods). When enabled (default), those - methods return camera parameters for rectified images. + * getCameraMatrixRight() methods). When enabled (default), those + * methods return camera parameters for rectified images. Does not + * enable rectification, if valid parameters are missing. * @param Rectification on/off + * @returns Status after call */ - void setRectify(bool enabled) { rectify_ = enabled; } + bool setRectify(bool enabled); + + /** + * @brief Set intrinsic parameters for both cameras. + * + * @param size calibration size + * @param K 2 camera matricies (3x3) + * @param D 2 distortion parameters (5x1) + * @returns true if valid parameters + */ + bool setIntrinsics(const cv::Size size, const std::vector<cv::Mat> K, const std::vector<cv::Mat> D); + + /** + * @brief Set extrinsic parameters. + * + * @param R Rotation matrix (3x3) from left to right camera + * @param t Translation vector (1x3) from left to right camera + * @returns true if valid parameters + */ + bool setExtrinsics(const cv::Mat R, const cv::Mat t); + + /** + * @brief Set pose + * @param pose Pose for left camera + * @returns true if valid pose + */ + bool setPose(const cv::Mat P); + + /** + * @brief Calculate rectification parameters and maps. Can fail if + * calibration parameters are invalid. + * @returns true if successful + */ + bool calculateRectificationParameters(); + + /** + * @brief Load calibration from file + * @param fname File name + */ + bool loadCalibration(const std::string fname); + + /** + * @brief Write calibration parameters to file + * + * Assumes two cameras and intrinsic calibration parameters have the same + * resolution. + * + * @todo Validate loaded values + * + * @param fname file name + * @param size calibration resolution (intrinsic parameters) + * @param K intrinsic matrices + * @param D distortion coefficients + * @param R rotation from first camera to second + * @param t translation from first camera to second + * @param pose first camera's pose + */ + static bool writeCalibration(std::string fname, + cv::Size size, + std::vector<cv::Mat> K, std::vector<cv::Mat> D, + cv::Mat R, cv::Mat t, + cv::Mat pose); + + /* @brief Save current calibration to file + * @param File name + */ + bool saveCalibration(const std::string fname); private: // rectification enabled/disabled bool rectify_; - /* @brief Get intrinsic matrix saved in calibration. + /** + * @brief Get intrinsic matrix saved in calibration. * @param Camera index (0 left, 1 right) * @param Resolution */ cv::Mat _getK(size_t idx, cv::Size size); cv::Mat _getK(size_t idx); - /* @brief Calculate rectification parameters and maps - * @param Camera resolution - */ - void _calculateRectificationParameters(cv::Size img_size); - - /* @brief Load calibration from file - * @todo File names as arguments - */ - bool _loadCalibration(); - // calibration resolution (loaded from file by _loadCalibration) cv::Size calib_size_; // camera resolution (set by _calculateRecitificationParameters) @@ -98,12 +164,6 @@ private: std::pair<cv::cuda::GpuMat, cv::cuda::GpuMat> map1_gpu_; std::pair<cv::cuda::GpuMat, cv::cuda::GpuMat> map2_gpu_; - // transformation from left to right camera: R_ and T_ - cv::Mat R_; - cv::Mat T_; - // pose for left camera - cv::Mat pose_; - // parameters for rectification, see cv::stereoRectify() documentation cv::Mat R1_; cv::Mat P1_; @@ -116,6 +176,12 @@ private: // intrinsic parameters and distortion coefficients std::vector<cv::Mat> K_; std::vector<cv::Mat> D_; + + // transformation from left to right camera: R_ and T_ + cv::Mat R_; + cv::Mat t_; + // pose for left camera + cv::Mat pose_; }; } diff --git a/components/rgbd-sources/src/sources/stereovideo/stereovideo.cpp b/components/rgbd-sources/src/sources/stereovideo/stereovideo.cpp index 330a5035c46fd38cc4f1ea9ccb6b61bbc12828d8..ccb3cada41924f3ab9fd0e0c12ba62aa48d02375 100644 --- a/components/rgbd-sources/src/sources/stereovideo/stereovideo.cpp +++ b/components/rgbd-sources/src/sources/stereovideo/stereovideo.cpp @@ -1,5 +1,8 @@ #include <loguru.hpp> +#include <Eigen/Eigen> +#include <opencv2/core/eigen.hpp> + #include "stereovideo.hpp" #include "ftl/configuration.hpp" @@ -67,7 +70,7 @@ void StereoVideoSource::init(const string &file) { LOG(INFO) << "Using cameras..."; lsrc_ = ftl::create<LocalSource>(host_, "feed"); } - + color_size_ = cv::Size(lsrc_->width(), lsrc_->height()); frames_ = std::vector<Frame>(2); @@ -76,99 +79,133 @@ void StereoVideoSource::init(const string &file) { pipeline_input_->append<ftl::operators::NVOpticalFlow>("optflow"); #endif - //depth_size_ = cv::Size( host_->value("depth_width", 1280), - // host_->value("depth_height", 720)); - - /*pipeline_depth_ = ftl::config::create<ftl::operators::Graph>(host_, "disparity"); - depth_size_ = cv::Size( pipeline_depth_->value("width", color_size_.width), - pipeline_depth_->value("height", color_size_.height)); + calib_ = ftl::create<Calibrate>(host_, "calibration", cv::Size(lsrc_->fullWidth(), lsrc_->fullHeight()), stream_); - pipeline_depth_->append<ftl::operators::FixstarsSGM>("algorithm"); - #ifdef HAVE_OPTFLOW - pipeline_depth_->append<ftl::operators::OpticalFlowTemporalSmoothing>("optflow_filter"); - #endif - pipeline_depth_->append<ftl::operators::DisparityBilateralFilter>("bilateral_filter"); - pipeline_depth_->append<ftl::operators::DisparityToDepth>("calculate_depth"); - pipeline_depth_->append<ftl::operators::ColourChannels>("colour"); // Convert BGR to BGRA - pipeline_depth_->append<ftl::operators::Normals>("normals"); // Estimate surface normals - pipeline_depth_->append<ftl::operators::CrossSupport>("cross"); - pipeline_depth_->append<ftl::operators::DiscontinuityMask>("discontinuity_mask"); - pipeline_depth_->append<ftl::operators::AggreMLS>("mls"); // Perform MLS (using smoothing channel)*/ + string fname_default = "calibration.yml"; + auto fname_config = calib_->get<string>("calibration"); + string fname = fname_config ? *fname_config : fname_default; + auto calibf = ftl::locateFile(fname); + if (calibf) { + fname = *calibf; + if (calib_->loadCalibration(fname)) { + calib_->calculateRectificationParameters(); + calib_->setRectify(true); + } + } + else { + fname = fname_config ? *fname_config : + string(FTL_LOCAL_CONFIG_ROOT) + "/" + + std::string("calibration.yml"); + + LOG(ERROR) << "No calibration, default path set to " + fname; + } - calib_ = ftl::create<Calibrate>(host_, "calibration", cv::Size(lsrc_->fullWidth(), lsrc_->fullHeight()), stream_); + //////////////////////////////////////////////////////////////////////////// + // RPC callbacks to update calibration + // Should only be used by calibration app (interface may change) + // Tries to follow interface of ftl::Calibrate - // Generate camera parameters from camera matrix - cv::Mat K = calib_->getCameraMatrixLeft(color_size_); - params_ = { - K.at<double>(0,0), // Fx - K.at<double>(1,1), // Fy - -K.at<double>(0,2), // Cx - -K.at<double>(1,2), // Cy - (unsigned int) lsrc_->width(), - (unsigned int) lsrc_->height(), - 0.0f, // 0m min - 15.0f, // 15m max - 1.0 / calib_->getQ().at<double>(3,2), // Baseline - 0.0f // doffs - }; - params_.doffs = -calib_->getQ().at<double>(3,3) * params_.baseline; - - // Add calibration to config object - host_->getConfig()["focal"] = params_.fx; - host_->getConfig()["centre_x"] = params_.cx; - host_->getConfig()["centre_y"] = params_.cy; - host_->getConfig()["baseline"] = params_.baseline; - host_->getConfig()["doffs"] = params_.doffs; + host_->getNet()->bind("set_pose", + [this](std::vector<double> data){ + if (data.size() != 16) { + LOG(ERROR) << "invalid pose received (wrong size)"; + return false; + } + + cv::Mat M = cv::Mat(data).reshape(1, 4).t(); + if (!calib_->setPose(M)) { + LOG(ERROR) << "invalid pose received (bad value)"; + return false; + } + + return true; + }); - // TODO: remove (not used, fx/fy/baseline/.. do not change) - // in case they are modified, update using Calibrate - // (requires new method) + host_->getNet()->bind("set_intrinsics", + [this]( std::vector<int> size, + std::vector<double> camera_l, std::vector<double> d_left, + std::vector<double> camera_r, std::vector<double> d_right) { + if ((size.size() != 2) || (camera_l.size() != 9) || (camera_r.size() != 9)) { + LOG(ERROR) << "bad intrinsic parameters (wrong size)"; + return false; + } + cv::Size calib_size(size[0], size[1]); + cv::Mat K_l = cv::Mat(camera_l).reshape(1, 3).t(); + cv::Mat K_r = cv::Mat(camera_r).reshape(1, 3).t(); + cv::Mat D_l = cv::Mat(D_l); + cv::Mat D_r = cv::Mat(D_r); + + if (!calib_->setIntrinsics(calib_size, {K_l, K_r}, {D_l, D_r})) { + LOG(ERROR) << "bad intrinsic parameters (bad values)"; + return false; + } + return true; + }); - host_->on("baseline", [this](const ftl::config::Event &e) { - params_.baseline = host_->value("baseline", params_.baseline); - UNIQUE_LOCK(host_->mutex(), lk); - calib_->updateCalibration(params_); + host_->getNet()->bind("set_extrinsics", + [this](std::vector<double> data_rvec, std::vector<double> data_tvec){ + if ((data_rvec.size() != 3) || (data_tvec.size() != 3)) { + LOG(ERROR) << "invalid extrinsic parameters received (wrong size)"; + return false; + } + + cv::Mat R; + cv::Rodrigues(data_rvec, R); + cv::Mat t(data_tvec); + + if (!calib_->setExtrinsics(R, t)) { + LOG(ERROR) << "invalid extrinsic parameters (bad values)"; + return false; + } + return true; }); - host_->on("focal", [this](const ftl::config::Event &e) { - params_.fx = host_->value("focal", params_.fx); - params_.fy = params_.fx; - UNIQUE_LOCK(host_->mutex(), lk); - calib_->updateCalibration(params_); + host_->getNet()->bind("save_calibration", + [this, fname](){ + return calib_->saveCalibration(fname); }); - // - host_->on("doffs", [this](const ftl::config::Event &e) { - params_.doffs = host_->value("doffs", params_.doffs); + host_->getNet()->bind("use_rectify", + [this](bool enable){ + bool retval = enable && calib_->setRectify(enable); + updateParameters(); + return retval; }); - - // left and right masks (areas outside rectified images) - // TODO: remove mask - cv::cuda::GpuMat mask_r_gpu(lsrc_->height(), lsrc_->width(), CV_8U, 255); - cv::cuda::GpuMat mask_l_gpu(lsrc_->height(), lsrc_->width(), CV_8U, 255); - calib_->rectifyStereo(mask_l_gpu, mask_r_gpu, stream_); - stream_.waitForCompletion(); - cv::Mat mask_l; - mask_l_gpu.download(mask_l); - mask_l_ = (mask_l == 0); - + //////////////////////////////////////////////////////////////////////////// + // Generate camera parameters from camera matrix + updateParameters(); + LOG(INFO) << "StereoVideo source ready..."; ready_ = true; + + state_.set("name", host_->value("name", host_->getID())); } ftl::rgbd::Camera StereoVideoSource::parameters(Channel chan) { - cv::Mat K; - if (chan == Channel::Right) { - K = calib_->getCameraMatrixRight(color_size_); + return state_.getRight(); } else { - K = calib_->getCameraMatrixLeft(color_size_); + return state_.getLeft(); } +} + +void StereoVideoSource::updateParameters() { + Eigen::Matrix4d pose; + cv::cv2eigen(calib_->getPose(), pose); + setPose(pose); + + cv::Mat K; + + // same for left and right + double baseline = calib_->getQ().at<double>(3,2); + double doff = -calib_->getQ().at<double>(3,3) * baseline; + + // left - // TODO: remove hardcoded values (min/max), move to Calibrate? - ftl::rgbd::Camera params = { + K = calib_->getCameraMatrixLeft(color_size_); + state_.getLeft() = { K.at<double>(0,0), // Fx K.at<double>(1,1), // Fy -K.at<double>(0,2), // Cx @@ -177,11 +214,31 @@ ftl::rgbd::Camera StereoVideoSource::parameters(Channel chan) { (unsigned int) color_size_.height, 0.0f, // 0m min 15.0f, // 15m max - 1.0 / calib_->getQ().at<double>(3,2), // Baseline - 0.0f // doffs + 1.0 / baseline, // Baseline + doff }; - return params; + host_->getConfig()["focal"] = params_.fx; + host_->getConfig()["centre_x"] = params_.cx; + host_->getConfig()["centre_y"] = params_.cy; + host_->getConfig()["baseline"] = params_.baseline; + host_->getConfig()["doffs"] = params_.doffs; + + // right + + K = calib_->getCameraMatrixRight(color_size_); + state_.getRight() = { + K.at<double>(0,0), // Fx + K.at<double>(1,1), // Fy + -K.at<double>(0,2), // Cx + -K.at<double>(1,2), // Cy + (unsigned int) color_size_.width, + (unsigned int) color_size_.height, + 0.0f, // 0m min + 15.0f, // 15m max + 1.0 / baseline, // Baseline + doff + }; } bool StereoVideoSource::capture(int64_t ts) { @@ -193,6 +250,7 @@ bool StereoVideoSource::capture(int64_t ts) { bool StereoVideoSource::retrieve() { auto &frame = frames_[0]; frame.reset(); + frame.setOrigin(&state_); auto &left = frame.create<cv::cuda::GpuMat>(Channel::Left); auto &right = frame.create<cv::cuda::GpuMat>(Channel::Right); cv::cuda::GpuMat dummy; @@ -202,7 +260,7 @@ bool StereoVideoSource::retrieve() { //LOG(INFO) << "Channel size: " << hres.size(); - pipeline_input_->apply(frame, frame, host_, cv::cuda::StreamAccessor::getStream(stream2_)); + pipeline_input_->apply(frame, frame, cv::cuda::StreamAccessor::getStream(stream2_)); stream2_.waitForCompletion(); return true; @@ -232,43 +290,6 @@ bool StereoVideoSource::compute(int n, int b) { //stream_.waitForCompletion(); host_->notify(timestamp_, frame); - /*if (chan == Channel::Depth) { - // stereo algorithms assume input same size as output - bool resize = (depth_size_ != color_size_); - - cv::cuda::GpuMat& left = frame.get<cv::cuda::GpuMat>(Channel::Left); - cv::cuda::GpuMat& right = frame.get<cv::cuda::GpuMat>(Channel::Right); - - if (left.empty() || right.empty()) { - return false; - } - - if (resize) { - cv::cuda::swap(fullres_left_, left); - cv::cuda::swap(fullres_right_, right); - cv::cuda::resize(fullres_left_, left, depth_size_, 0, 0, cv::INTER_CUBIC, stream_); - cv::cuda::resize(fullres_right_, right, depth_size_, 0, 0, cv::INTER_CUBIC, stream_); - } - - pipeline_depth_->apply(frame, frame, host_, cv::cuda::StreamAccessor::getStream(stream_)); - stream_.waitForCompletion(); - - if (resize) { - cv::cuda::swap(fullres_left_, left); - cv::cuda::swap(fullres_right_, right); - } - - host_->notify(timestamp_, frame); - - } else if (chan == Channel::Right) { - stream_.waitForCompletion(); - host_->notify(timestamp_, frame); - - } else { - stream_.waitForCompletion(); - host_->notify(timestamp_, frame); - }*/ - return true; } diff --git a/components/rgbd-sources/src/sources/stereovideo/stereovideo.hpp b/components/rgbd-sources/src/sources/stereovideo/stereovideo.hpp index 9532e618889e78da51e1e152673195000e93be7f..4b6b60e63a522c8031f5cc386fe6211eba064b9f 100644 --- a/components/rgbd-sources/src/sources/stereovideo/stereovideo.hpp +++ b/components/rgbd-sources/src/sources/stereovideo/stereovideo.hpp @@ -30,9 +30,12 @@ class StereoVideoSource : public detail::Source { bool retrieve(); bool compute(int n, int b); bool isReady(); + Camera parameters(ftl::codecs::Channel chan) override; private: + void updateParameters(); + LocalSource *lsrc_; Calibrate *calib_; diff --git a/components/rgbd-sources/src/sources/virtual/virtual.cpp b/components/rgbd-sources/src/sources/virtual/virtual.cpp index 0827c0ffdf81af80565a07dbd4ee379c959700bc..1580750afb047150e7f2fe2970aef80661d6fff9 100644 --- a/components/rgbd-sources/src/sources/virtual/virtual.cpp +++ b/components/rgbd-sources/src/sources/virtual/virtual.cpp @@ -81,6 +81,7 @@ class VirtualImpl : public ftl::rgbd::detail::Source { bool compute(int n, int b) override { if (callback) { frame_.reset(); + frame_.setOrigin(&state_); bool goodFrame = false; try { diff --git a/components/rgbd-sources/src/streamer.cpp b/components/rgbd-sources/src/streamer.cpp index 75165ffd1d49dc89a268f9a1c3bb92575b644980..e6c08e5b53feaba743dcbb3816cb16fe7f9ad7ed 100644 --- a/components/rgbd-sources/src/streamer.cpp +++ b/components/rgbd-sources/src/streamer.cpp @@ -43,12 +43,11 @@ Streamer::Streamer(nlohmann::json &config, Universe *net) //last_dropped_ = 0; //drop_count_ = 0; - encode_mode_ = ftl::rgbd::kEncodeVideo; + second_channel_ = Channel::None; hq_devices_ = (value("disable_hardware_encode", false)) ? device_t::Software : device_t::Any; hq_codec_ = value("video_codec", ftl::codecs::codec_t::Any); //group_.setFPS(value("fps", 20)); - group_.setLatency(4); group_.setName("NetStreamer"); compress_level_ = value("compression", 1); @@ -116,9 +115,10 @@ Streamer::Streamer(nlohmann::json &config, Universe *net) net->bind("set_channel", [this](const string &uri, Channel chan) { SHARED_LOCK(mutex_,slk); - if (sources_.find(uri) != sources_.end()) { - sources_[uri]->src->setChannel(chan); - } + //if (sources_.find(uri) != sources_.end()) { + // sources_[uri]->src->setChannel(chan); + //} + second_channel_ = chan; }); //net->bind("sync_streams", [this](unsigned long long time) { @@ -202,6 +202,7 @@ void Streamer::add(Source *src) { s->lq_bitrate = value("lq_bitrate", ftl::codecs::kPresetWorst); sources_[src->getID()] = s; + sourcesByNum_.push_back(s); group_.addSource(src); @@ -219,6 +220,17 @@ void Streamer::add(Source *src) { LOG(INFO) << "Streaming: " << src->getID(); net_->broadcast("add_stream", src->getID()); + + // FIXME: Temp hack for forward compatibility. + net_->bind(src->getID(), [this,src](ftl::net::Peer &p, short ttime, const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) { + LOG(INFO) << "RECEIVED PACKET: " << spkt.timestamp; + _addClient(src->getID(), pkt.frame_count, 0, p.id(), src->getID()); + if (spkt.channel != Channel::Colour && src->getChannel() != spkt.channel) { + LOG(INFO) << "SET CHANNEL: " << (int)spkt.channel; + src->setChannel(spkt.channel); + second_channel_ = spkt.channel; + } + }); } void Streamer::add(ftl::rgbd::Group *grp) { @@ -380,14 +392,14 @@ void Streamer::stop() { void Streamer::run(bool block) { if (block) { - group_.sync([this](FrameSet &fs) -> bool { + group_.onFrameSet([this](FrameSet &fs) -> bool { _process(fs); return true; }); } else { // Create thread job for frame ticking ftl::pool.push([this](int id) { - group_.sync([this](FrameSet &fs) -> bool { + group_.onFrameSet([this](FrameSet &fs) -> bool { _process(fs); return true; }); @@ -454,29 +466,29 @@ void Streamer::_process(ftl::rgbd::FrameSet &fs) { frame_no_ = fs.timestamp; - for (int j=0; j<fs.sources.size(); ++j) { - StreamSource *src = sources_[fs.sources[j]->getID()]; + for (int j=0; j<fs.frames.size(); ++j) { + StreamSource *src = sourcesByNum_[j]; SHARED_LOCK(src->mutex,lk); // Don't do any work in the following cases if (!src) continue; - if (!fs.sources[j]->isReady()) continue; + //if (!fs.sources[j]->isReady()) continue; if (src->clientCount == 0) continue; //if (fs.channel1[j].empty() || (fs.sources[j]->getChannel() != ftl::rgbd::kChanNone && fs.channel2[j].empty())) continue; - if (!fs.frames[j].hasChannel(Channel::Colour) || !fs.frames[j].hasChannel(fs.sources[j]->getChannel())) { - LOG(WARNING) << "Missing required channel when streaming: " << (int)fs.sources[j]->getChannel(); + if (!fs.frames[j].hasChannel(Channel::Colour) || !fs.frames[j].hasChannel(second_channel_)) { + LOG(WARNING) << "Missing required channel when streaming: " << (int)second_channel_; continue; } - bool hasChan2 = fs.sources[j]->getChannel() != Channel::None && - fs.frames[j].hasChannel(fs.sources[j]->getChannel()); + bool hasChan2 = second_channel_ != Channel::None && + fs.frames[j].hasChannel(second_channel_); totalclients += src->clientCount; // Do we need to do high quality encoding? if (src->hq_count > 0) { - auto chan = fs.sources[j]->getChannel(); + auto chan = second_channel_; // Do we have the resources to do a HQ encoding? ///if (src->hq_encoder_c1 && (!hasChan2 || src->hq_encoder_c2)) { @@ -502,7 +514,7 @@ void Streamer::_process(ftl::rgbd::FrameSet &fs) { // TODO: Stagger the reset between nodes... random phasing if (insert_iframes_ && fs.timestamp % (10*ftl::timer::getInterval()) == 0) enc->reset(); - auto chan = fs.sources[j]->getChannel(); + auto chan = second_channel_; try { enc->encode(fs.frames[j].get<cv::cuda::GpuMat>(chan), src->hq_bitrate, [this,src,hasChan2,chan,&cv,&chan2done](const ftl::codecs::Packet &blk){ @@ -523,7 +535,7 @@ void Streamer::_process(ftl::rgbd::FrameSet &fs) { } else { // Already have an encoding so send this const auto &packets = fs.frames[j].getPackets(chan); - LOG(INFO) << "Send existing chan2 encoding: " << packets.size(); + //LOG(INFO) << "Send existing chan2 encoding: " << packets.size(); for (const auto &i : packets) { _transmitPacket(src, i, chan, hasChan2, Quality::High); } @@ -554,7 +566,7 @@ void Streamer::_process(ftl::rgbd::FrameSet &fs) { const auto &packets = fs.frames[j].getPackets(colChan); // FIXME: Adjust block number and total to match number of packets // Also requires the receiver to decode in block number order. - LOG(INFO) << "Send existing encoding: " << packets.size(); + //LOG(INFO) << "Send existing encoding: " << packets.size(); for (const auto &i : packets) { _transmitPacket(src, i, Channel::Colour, hasChan2, Quality::High); } @@ -581,7 +593,7 @@ void Streamer::_process(ftl::rgbd::FrameSet &fs) { // Important to send channel 2 first if needed... // Receiver only waits for channel 1 by default if (hasChan2) { - auto chan = fs.sources[j]->getChannel(); + auto chan = second_channel_; enc2->encode(fs.frames[j].get<cv::cuda::GpuMat>(chan), src->lq_bitrate, [this,src,hasChan2,chan](const ftl::codecs::Packet &blk){ _transmitPacket(src, blk, chan, hasChan2, Quality::Low); @@ -613,15 +625,29 @@ void Streamer::_process(ftl::rgbd::FrameSet &fs) { } void Streamer::_transmitPacket(StreamSource *src, const ftl::codecs::Packet &pkt, Channel chan, bool hasChan2, Quality q) { - ftl::codecs::StreamPacket spkt = { - frame_no_, - src->id, - (hasChan2) ? 2 : 1, - chan - //static_cast<uint8_t>((chan & 0x1) | ((hasChan2) ? 0x2 : 0x0)) - }; - - _transmitPacket(src, spkt, pkt, q); + const int version = 4; // FIXME: Needs to be selected based on client version? Or removed when refactor complete. + + if (version <= 3) { + ftl::codecs::StreamPacket spkt = { + 3, // Version + frame_no_, + src->id, + (hasChan2) ? 2 : 1, + chan + //static_cast<uint8_t>((chan & 0x1) | ((hasChan2) ? 0x2 : 0x0)) + }; + _transmitPacket(src, spkt, pkt, q); + } else { + ftl::codecs::StreamPacket spkt = { + 4, // Version + frame_no_, + 0, + src->id, + chan + //static_cast<uint8_t>((chan & 0x1) | ((hasChan2) ? 0x2 : 0x0)) + }; + _transmitPacket(src, spkt, pkt, q); + } } void Streamer::_transmitPacket(StreamSource *src, const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt, Quality q) { @@ -650,7 +676,7 @@ void Streamer::_transmitPacket(StreamSource *src, const ftl::codecs::StreamPacke (*c).txcount = (*c).txmax; } else { // Count frame as completed only if last block and channel is 0 - if (pkt.block_number == pkt.block_total - 1 && spkt.channel == Channel::Colour) ++(*c).txcount; + if (spkt.channel == Channel::Colour) ++(*c).txcount; // pkt.block_number == pkt.block_total - 1 && } } catch(...) { (*c).txcount = (*c).txmax; diff --git a/components/rgbd-sources/test/CMakeLists.txt b/components/rgbd-sources/test/CMakeLists.txt index 9611e8081d942a6051e0ea254b3308d406161cd7..e16a37c5b7606f49bc3f06d7424ad12f726daee1 100644 --- a/components/rgbd-sources/test/CMakeLists.txt +++ b/components/rgbd-sources/test/CMakeLists.txt @@ -1,6 +1,7 @@ ### Source Unit ################################################################ add_executable(source_unit ./tests.cpp + ../src/frame.cpp ./source_unit.cpp ) target_include_directories(source_unit PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../include") diff --git a/components/rgbd-sources/test/frame_unit.cpp b/components/rgbd-sources/test/frame_unit.cpp index 6d858a62ab7765f1efc6f98ab09c5109f8101603..656971ae47b0dc8f6abc489854e05c7598589295 100644 --- a/components/rgbd-sources/test/frame_unit.cpp +++ b/components/rgbd-sources/test/frame_unit.cpp @@ -2,6 +2,7 @@ #include <ftl/rgbd/frame.hpp> using ftl::rgbd::Frame; +using ftl::rgbd::FrameState; using ftl::codecs::Channel; using ftl::codecs::Channels; using ftl::rgbd::Format; @@ -275,9 +276,104 @@ TEST_CASE("Frame::swapTo()", "") { Frame f2; f1.create<cv::Mat>(Channel::Colour, Format<uchar3>(100,100)); - f1.swapTo(Channels::All(), f2); + f1.swapTo(Channels<0>::All(), f2); REQUIRE( f2.hasChannel(Channel::Colour) ); REQUIRE( (f2.get<cv::Mat>(Channel::Colour).cols == 100) ); } } + +TEST_CASE("Frame::setOrigin()", "") { + SECTION("With changed pose") { + Frame f; + FrameState s; + + REQUIRE( !f.hasChanged(Channel::Pose) ); + + s.setPose(Eigen::Matrix4d()); + f.setOrigin(&s); + + REQUIRE( f.hasChanged(Channel::Pose) ); + } + + SECTION("With stale pose") { + Frame f; + FrameState s; + + REQUIRE( !f.hasChanged(Channel::Pose) ); + + s.setPose(Eigen::Matrix4d()); + f.setOrigin(&s); + + f.reset(); + f.setOrigin(&s); + + REQUIRE( !f.hasChanged(Channel::Pose) ); + } + + SECTION("With updated pose") { + Frame f; + FrameState s; + + REQUIRE( !f.hasChanged(Channel::Pose) ); + + s.setPose(Eigen::Matrix4d()); + f.setOrigin(&s); + + f.reset(); + f.setOrigin(&s); + + REQUIRE( !f.hasChanged(Channel::Pose) ); + + s.setPose(Eigen::Matrix4d()); + + REQUIRE( !f.hasChanged(Channel::Pose) ); + REQUIRE( s.hasChanged(Channel::Pose) ); + } + + SECTION("Fail on multi set") { + Frame f; + FrameState s; + + s.setPose(Eigen::Matrix4d()); + f.setOrigin(&s); + + bool failed = false; + try { + f.setOrigin(&s); + } catch (...) { + failed = true; + } + + REQUIRE( failed ); + } + + SECTION("Reset and multi set") { + Frame f; + FrameState s; + + s.setPose(Eigen::Matrix4d()); + f.setOrigin(&s); + + f.reset(); + f.setOrigin(&s); + } +} + +TEST_CASE("Frame::get() Pose", "") { + SECTION("Get valid pose") { + Frame f; + FrameState s; + + Eigen::Matrix4d pose1; + s.setPose(pose1); + f.setOrigin(&s); + + REQUIRE( f.hasChannel(Channel::Pose) ); + REQUIRE( f.hasChanged(Channel::Pose) ); + + auto pose2 = f.getPose(); + + REQUIRE( pose1 == pose2 ); + } +} diff --git a/components/rgbd-sources/test/source_unit.cpp b/components/rgbd-sources/test/source_unit.cpp index 1b69631aa0ec0312cf1cba06533967f585babf56..d672632129b98d2a752c6a495b17b104d61a9a2f 100644 --- a/components/rgbd-sources/test/source_unit.cpp +++ b/components/rgbd-sources/test/source_unit.cpp @@ -146,45 +146,6 @@ TEST_CASE("ftl::create<Source>(cfg)", "[rgbd]") { json_t global = json_t{{"$id","ftl://test"}}; ftl::config::configure(global); - SECTION("with valid image file uri") { - json_t cfg = json_t{ - {"$id","ftl://test/1"}, - {"uri","file://" FTL_SOURCE_DIRECTORY "/components/rgbd-sources/test/data/image.png"} - }; - - Source *src = ftl::create<Source>(cfg); - - REQUIRE( src ); - REQUIRE( src->isReady() ); - REQUIRE( last_type == "image"); - } - - SECTION("with valid video file uri") { - json_t cfg = json_t{ - {"$id","ftl://test/2"}, - {"uri","file://" FTL_SOURCE_DIRECTORY "/components/rgbd-sources/test/data/video.mp4"} - }; - - Source *src = ftl::create<Source>(cfg); - - REQUIRE( src ); - REQUIRE( src->isReady() ); - REQUIRE( last_type == "video"); - } - - SECTION("with valid net uri") { - json_t cfg = json_t{ - {"$id","ftl://test/2"}, - {"uri","ftl://utu.fi/dummy"} - }; - - Source *src = ftl::create<Source>(cfg); - - REQUIRE( src ); - REQUIRE( src->isReady() ); - REQUIRE( last_type == "net"); - } - SECTION("with an invalid uri") { json_t cfg = json_t{ {"$id","ftl://test/2"}, @@ -197,50 +158,6 @@ TEST_CASE("ftl::create<Source>(cfg)", "[rgbd]") { REQUIRE( !src->isReady() ); } - SECTION("with an invalid file uri") { - json_t cfg = json_t{ - {"$id","ftl://test/2"}, - {"uri","file:///not/a/file"} - }; - - Source *src = ftl::create<Source>(cfg); - - REQUIRE( src ); - REQUIRE( !src->isReady() ); - } - - SECTION("with a missing file") { - json_t cfg = json_t{ - {"$id","ftl://test/2"}, - {"uri","file:///data/image2.png"} - }; - - Source *src = ftl::create<Source>(cfg); - - REQUIRE( src ); - REQUIRE( !src->isReady() ); - } + } -TEST_CASE("Source::set(uri)", "[rgbd]") { - json_t global = json_t{{"$id","ftl://test"}}; - ftl::config::configure(global); - - SECTION("change to different valid URI type") { - json_t cfg = json_t{ - {"$id","ftl://test/1"}, - {"uri","file://" FTL_SOURCE_DIRECTORY "/components/rgbd-sources/test/data/image.png"} - }; - - Source *src = ftl::create<Source>(cfg); - - REQUIRE( src ); - REQUIRE( src->isReady() ); - REQUIRE( last_type == "image" ); - - src->set("uri", "file://" FTL_SOURCE_DIRECTORY "/components/rgbd-sources/test/data/video.mp4"); - - REQUIRE( src->isReady() ); - REQUIRE( last_type == "video" ); - } -} diff --git a/components/streams/CMakeLists.txt b/components/streams/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..063f1d7678785408b31cc45aa75b0d311deabf5a --- /dev/null +++ b/components/streams/CMakeLists.txt @@ -0,0 +1,24 @@ +set(STREAMSRC + src/stream.cpp + src/filestream.cpp + src/receiver.cpp + src/sender.cpp + src/netstream.cpp + src/injectors.cpp + src/parsers.cpp +) + +add_library(ftlstreams ${STREAMSRC}) + +# target_compile_options(ftlrgbd PUBLIC $<$<COMPILE_LANGUAGE:CXX>:-fPIC>) +# target_compile_options(ftlrgbd PUBLIC "-DMAKE_SHARED") + +target_include_directories(ftlstreams PUBLIC + $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> + $<INSTALL_INTERFACE:include> + PRIVATE src) + +#target_include_directories(cv-node PUBLIC ${PROJECT_SOURCE_DIR}/include) +target_link_libraries(ftlstreams ftlrgbd ftlcommon ${OpenCV_LIBS} Eigen3::Eigen ftlnet ftlcodecs) + +add_subdirectory(test) \ No newline at end of file diff --git a/components/streams/include/ftl/streams/filestream.hpp b/components/streams/include/ftl/streams/filestream.hpp new file mode 100644 index 0000000000000000000000000000000000000000..7baffd75aab8f842d80e658905046604455bc3a4 --- /dev/null +++ b/components/streams/include/ftl/streams/filestream.hpp @@ -0,0 +1,75 @@ +#ifndef _FTL_STREAM_FILESTREAM_HPP_ +#define _FTL_STREAM_FILESTREAM_HPP_ + +#include <ftl/streams/stream.hpp> + +namespace ftl { +namespace stream { + +/** + * Provide a packet stream to/from a file. If the file already exists it is + * opened readonly, if not it is created write only. A mode to support both + * reading and writing (to re code it) could be supported by using a temp file + * for writing and swapping files when finished. It must be possible to control + * streaming rate from the file. + */ +class File : public Stream { + public: + explicit File(nlohmann::json &config); + File(nlohmann::json &config, std::ifstream *); + File(nlohmann::json &config, std::ofstream *); + ~File(); + + bool onPacket(const std::function<void(const ftl::codecs::StreamPacket &, const ftl::codecs::Packet &)> &) override; + + bool post(const ftl::codecs::StreamPacket &, const ftl::codecs::Packet &) override; + + bool begin() override { return begin(true); } + bool begin(bool dorun); + bool end() override; + bool active() override; + + /** + * Automatically tick through the frames using a timer. Threads are used. + */ + bool run(); + + /** + * Manually tick through the frames one per call. + */ + bool tick(); + + enum class Mode { + Read, + Write, + ReadWrite + }; + + inline void setMode(Mode m) { mode_ = m; } + inline void setStart(int64_t ts) { timestamp_ = ts; } + + private: + std::ofstream *ostream_; + std::ifstream *istream_; + + Mode mode_; + msgpack::sbuffer buffer_out_; + msgpack::unpacker buffer_in_; + std::list<std::tuple<ftl::codecs::StreamPacket,ftl::codecs::Packet>> data_; + int64_t timestart_; + int64_t timestamp_; + int64_t interval_; + bool active_; + int version_; + ftl::timer::TimerHandle timer_; + + StreamCallback cb_; + MUTEX mutex_; + MUTEX data_mutex_; + std::atomic<int> jobs_; +}; + +} +} + +#endif // _FTL_STREAM_FILESTREAM_HPP_ diff --git a/components/streams/include/ftl/streams/netstream.hpp b/components/streams/include/ftl/streams/netstream.hpp new file mode 100644 index 0000000000000000000000000000000000000000..85edde6cfa7530bf93e70e3c15a0583e8c8da590 --- /dev/null +++ b/components/streams/include/ftl/streams/netstream.hpp @@ -0,0 +1,97 @@ +#ifndef _FTL_STREAM_NETSTREAM_HPP_ +#define _FTL_STREAM_NETSTREAM_HPP_ + +#include <ftl/config.h> +#include <ftl/net/universe.hpp> +#include <ftl/threads.hpp> +#include <ftl/codecs/packet.hpp> +#include <ftl/streams/stream.hpp> +#include <string> + +namespace ftl { +namespace stream { + +namespace detail { +struct StreamClient { + ftl::UUID peerid; + std::atomic<int> txcount; // Frames sent since last request + int txmax; // Frames to send in request + uint8_t quality; +}; +} + +/** + * The maximum number of frames a client can request in a single request. + */ +static const int kMaxFrames = 100; + +/** + * Allows network streaming of a number of RGB-Depth sources. Remote machines + * can discover available streams from an instance of Streamer. It also allows + * for adaptive bitrate streams where bandwidth can be monitored and different + * data rates can be requested, it is up to the remote machine to decide on + * desired bitrate. + * + * The capture and compression steps operate in different threads and each + * source and bitrate also operate on different threads. For a specific source + * and specific bitrate there is a single thread that sends data to all + * requesting clients. + * + * Use ftl::create<Streamer>(parent, name) to construct, don't construct + * directly. + * + * Note that the streamer attempts to maintain a fixed frame rate by + * monitoring job processing times and sleeping if there is spare time. + */ +class Net : public Stream { + public: + Net(nlohmann::json &config, ftl::net::Universe *net); + ~Net(); + + bool onPacket(const std::function<void(const ftl::codecs::StreamPacket &, const ftl::codecs::Packet &)> &) override; + + bool post(const ftl::codecs::StreamPacket &, const ftl::codecs::Packet &) override; + + bool begin() override; + bool end() override; + bool active() override; + + inline const ftl::UUID &getPeer() const { return peer_; } + + private: + SHARED_MUTEX mutex_; + bool active_; + ftl::net::Universe *net_; + bool late_; + int64_t clock_adjust_; + ftl::UUID time_peer_; + ftl::UUID peer_; + int64_t last_frame_; + int64_t frame_no_; + std::string uri_; + bool host_; + int tally_; + + float req_bitrate_; + float sample_count_; + int64_t last_msg_; + MUTEX msg_mtx_; + + std::list<detail::StreamClient> clients_; + + StreamCallback cb_; + + bool _processRequest(ftl::net::Peer &p, const ftl::codecs::Packet &pkt); + void _checkDataRate(size_t tx_size, int64_t tx_latency, int64_t ts); + bool _sendRequest(ftl::codecs::Channel c, uint8_t frameset, uint8_t frames, uint8_t count, uint8_t bitrate); + void _cleanUp(); + + //void _addClient(int N, int rate, const ftl::UUID &peer, const std::string &dest); + //void _transmitPacket(const ftl::codecs::Packet &pkt, ftl::codecs::Channel chan, int count); + //void _transmitPacket(const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt); +}; + +} +} + +#endif // _FTL_STREAM_NETSTREAM_HPP_ diff --git a/components/streams/include/ftl/streams/receiver.hpp b/components/streams/include/ftl/streams/receiver.hpp new file mode 100644 index 0000000000000000000000000000000000000000..e75c3b5987282caa445bcc4c0d612c4e28855e36 --- /dev/null +++ b/components/streams/include/ftl/streams/receiver.hpp @@ -0,0 +1,71 @@ +#ifndef _FTL_DATA_TRANSCODER_HPP_ +#define _FTL_DATA_TRANSCODER_HPP_ + +#include <functional> +#include <ftl/rgbd/frameset.hpp> +#include <ftl/streams/stream.hpp> +#include <ftl/codecs/decoder.hpp> + +namespace ftl { +namespace stream { + +/** + * Convert packet streams into framesets. + */ +class Receiver : public ftl::Configurable, public ftl::rgbd::Generator { + public: + explicit Receiver(nlohmann::json &config); + ~Receiver(); + + void setStream(ftl::stream::Stream*); + + /** + * Encode and transmit an entire frame set. Frames may already contain + * an encoded form, in which case that is used instead. + */ + //void post(const ftl::rgbd::FrameSet &fs); + + // void write(const ftl::audio::FrameSet &f); + + size_t size() override; + + ftl::rgbd::FrameState &state(int ix) override; + + /** + * Register a callback for received framesets. Sources are automatically + * created to match the data received. + */ + void onFrameSet(const ftl::rgbd::VideoCallback &cb) override; + + // void onFrameSet(const AudioCallback &cb); + + private: + ftl::stream::Stream *stream_; + ftl::rgbd::VideoCallback fs_callback_; + ftl::rgbd::Builder builder_; + ftl::codecs::Channel second_channel_; + int64_t timestamp_; + SHARED_MUTEX mutex_; + + struct InternalStates { + InternalStates(); + + int64_t timestamp; + ftl::rgbd::FrameState state; + ftl::rgbd::Frame frame; + ftl::codecs::Decoder* decoders[32]; + MUTEX mutex; + ftl::codecs::Channels<0> completed; + }; + + std::vector<InternalStates*> frames_; + + void _processConfig(InternalStates &frame, const ftl::codecs::Packet &pkt); + void _createDecoder(InternalStates &frame, int chan, const ftl::codecs::Packet &pkt); + InternalStates &_getFrame(const ftl::codecs::StreamPacket &spkt); +}; + +} +} + +#endif // _FTL_DATA_TRANSCODER_HPP_ diff --git a/components/streams/include/ftl/streams/sender.hpp b/components/streams/include/ftl/streams/sender.hpp new file mode 100644 index 0000000000000000000000000000000000000000..2cfc57bb31d71cff17e322c36ec46960796ef13b --- /dev/null +++ b/components/streams/include/ftl/streams/sender.hpp @@ -0,0 +1,47 @@ +#ifndef _FTL_STREAM_SENDER_HPP_ +#define _FTL_STREAM_SENDER_HPP_ + +#include <functional> +#include <ftl/rgbd/frameset.hpp> +#include <ftl/streams/stream.hpp> +#include <ftl/codecs/encoder.hpp> + +#include <unordered_map> + +namespace ftl { +namespace stream { + +/** + * Convert framesets into packet streams. + */ +class Sender : public ftl::Configurable { + public: + explicit Sender(nlohmann::json &config); + ~Sender(); + + void setStream(ftl::stream::Stream*); + + /** + * Encode and transmit an entire frame set. Frames may already contain + * an encoded form, in which case that is used instead. + */ + void post(const ftl::rgbd::FrameSet &fs); + + //void onStateChange(const std::function<void(ftl::codecs::Channel, int, int)>&); + + private: + ftl::stream::Stream *stream_; + int64_t timestamp_; + SHARED_MUTEX mutex_; + std::atomic_flag do_inject_; + //std::function<void(ftl::codecs::Channel, int, int)> state_cb_; + + std::unordered_map<int, ftl::codecs::Encoder*> encoders_; + + ftl::codecs::Encoder *_getEncoder(int fsid, int fid, ftl::codecs::Channel c); +}; + +} +} + +#endif // _FTL_STREAM_SENDER_HPP_ diff --git a/components/streams/include/ftl/streams/stream.hpp b/components/streams/include/ftl/streams/stream.hpp new file mode 100644 index 0000000000000000000000000000000000000000..08064b6c9a6c69813930775ddac85d13f9ac3d9e --- /dev/null +++ b/components/streams/include/ftl/streams/stream.hpp @@ -0,0 +1,190 @@ +#ifndef _FTL_STREAM_STREAM_HPP_ +#define _FTL_STREAM_STREAM_HPP_ + +#include <loguru.hpp> +#include <ftl/configuration.hpp> +#include <ftl/configurable.hpp> +#include <ftl/rgbd/source.hpp> +#include <ftl/rgbd/group.hpp> +#include <ftl/net/universe.hpp> +#include <ftl/codecs/encoder.hpp> +#include <ftl/threads.hpp> +#include <string> +#include <vector> +#include <map> +#include <atomic> + +namespace ftl { +namespace stream { + +typedef std::function<void(const ftl::codecs::StreamPacket &, const ftl::codecs::Packet &)> StreamCallback; + +/** + * Base stream class to be implemented. Provides encode and decode functionality + * around a generic packet read and write mechanism. Some specialisations will + * provide and automatically handle control signals. + * + * Streams are bidirectional, frames can be both received and written. + */ +class Stream : public ftl::Configurable { + public: + explicit Stream(nlohmann::json &config) : ftl::Configurable(config) {}; + virtual ~Stream() {}; + + /** + * Obtain all packets for next frame. The provided callback function is + * called once for every packet. This function might continue to call the + * callback even after the read function returns, for example with a + * NetStream. + */ + virtual bool onPacket(const StreamCallback &)=0; + + virtual bool post(const ftl::codecs::StreamPacket &, const ftl::codecs::Packet &)=0; + + /** + * Start the stream. Calls to the onPacket callback will only occur after + * a call to this function (and before a call to end()). + */ + virtual bool begin()=0; + + virtual bool end()=0; + + /** + * Is the stream active? Generally true if begin() has been called, false + * initially and after end(). However, it may go false for other reasons. + * If false, no calls to onPacket will occur and posts will be ignored. + */ + virtual bool active()=0; + + /** + * Query available video channels for a frameset. + */ + const ftl::codecs::Channels<0> &available(int fs) const; + + /** + * Query selected channels for a frameset. Channels not selected may not + * be transmitted, received or decoded. + */ + const ftl::codecs::Channels<0> &selected(int fs) const; + + /** + * Change the video channel selection for a frameset. + */ + void select(int fs, const ftl::codecs::Channels<0> &); + + /** + * Number of framesets in stream. + */ + inline size_t size() const { return state_.size(); } + + protected: + + /** + * Allow modification of available channels. Calling this with an invalid + * fs number will create that frameset and increase the size. + */ + ftl::codecs::Channels<0> &available(int fs); + + private: + struct FSState { + ftl::codecs::Channels<0> selected; + ftl::codecs::Channels<0> available; + }; + + std::vector<FSState> state_; + mutable SHARED_MUTEX mtx_; +}; + +/** + * Combine multiple streams into a single stream. StreamPackets are modified + * by mapping the stream identifiers consistently to new values. Both reading + * and writing are supported but a write must be preceeded by a read for the + * stream mapping to be registered. + */ +class Muxer : public Stream { + public: + explicit Muxer(nlohmann::json &config); + virtual ~Muxer(); + + void add(Stream *); + + bool onPacket(const StreamCallback &) override; + + bool post(const ftl::codecs::StreamPacket &, const ftl::codecs::Packet &) override; + + bool begin() override; + bool end() override; + bool active() override; + + private: + struct StreamEntry { + Stream *stream; + std::vector<int> maps; + }; + + std::vector<StreamEntry> streams_; + std::vector<std::pair<int,int>> revmap_; + int nid_; + StreamCallback cb_; + SHARED_MUTEX mutex_; + + void _notify(const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt); + int _lookup(int sid, int ssid); +}; + +/** + * Forward all data to all child streams. Unlike the muxer which remaps the + * stream identifiers in the stream packet, this does not alter the stream + * packets. + */ +class Broadcast : public Stream { + public: + explicit Broadcast(nlohmann::json &config); + virtual ~Broadcast(); + + void add(Stream *); + + bool onPacket(const std::function<void(const ftl::codecs::StreamPacket &, const ftl::codecs::Packet &)> &) override; + + bool post(const ftl::codecs::StreamPacket &, const ftl::codecs::Packet &) override; + + bool begin() override; + bool end() override; + bool active() override; + + private: + std::list<Stream*> streams_; + StreamCallback cb_; + SHARED_MUTEX mutex_; +}; + +/** + * Allow packet interception by a callback between two other streams. + */ +class Intercept : public Stream { + public: + explicit Intercept(nlohmann::json &config); + virtual ~Intercept(); + + void setStream(Stream *); + + bool onPacket(const StreamCallback &) override; + bool onIntercept(const StreamCallback &); + + bool post(const ftl::codecs::StreamPacket &, const ftl::codecs::Packet &) override; + + bool begin() override; + bool end() override; + bool active() override; + + private: + Stream *stream_; + StreamCallback cb_; + StreamCallback intercept_; + SHARED_MUTEX mutex_; +}; + +} +} + +#endif // _FTL_STREAM_STREAM_HPP_ diff --git a/components/streams/src/filestream.cpp b/components/streams/src/filestream.cpp new file mode 100644 index 0000000000000000000000000000000000000000..232a929f6bf574baaa09e993e8a0be22ad7c9421 --- /dev/null +++ b/components/streams/src/filestream.cpp @@ -0,0 +1,277 @@ +#include <fstream> +#include <ftl/streams/filestream.hpp> + +using ftl::stream::File; +using ftl::codecs::StreamPacket; +using ftl::codecs::Packet; +using std::get; + +File::File(nlohmann::json &config) : Stream(config), ostream_(nullptr), istream_(nullptr), active_(false) { + mode_ = Mode::Read; + jobs_ = 0; +} + +File::File(nlohmann::json &config, std::ifstream *is) : Stream(config), ostream_(nullptr), istream_(is), active_(false) { + mode_ = Mode::Read; + jobs_ = 0; +} + +File::File(nlohmann::json &config, std::ofstream *os) : Stream(config), ostream_(os), istream_(nullptr), active_(false) { + mode_ = Mode::Write; + jobs_ = 0; +} + +File::~File() { + end(); +} + +bool File::onPacket(const std::function<void(const ftl::codecs::StreamPacket &, const ftl::codecs::Packet &)> &f) { + cb_ = f; + return true; +} + +bool File::post(const ftl::codecs::StreamPacket &s, const ftl::codecs::Packet &p) { + if (!active_) return false; + if (mode_ != Mode::Write) { + LOG(WARNING) << "Cannot write to read-only ftl file"; + return false; + } + + // Don't write dummy packets to files. + if (p.data.size() == 0) return true; + + available(s.streamID) += s.channel; + + ftl::codecs::StreamPacket s2 = s; + // Adjust timestamp relative to start of file. + s2.timestamp -= timestart_; + + auto data = std::make_tuple(s2,p); + msgpack::sbuffer buffer; + msgpack::pack(buffer, data); + + UNIQUE_LOCK(mutex_, lk); + ostream_->write(buffer.data(), buffer.size()); + return ostream_->good(); +} + +bool File::tick() { + if (!active_) return false; + if (mode_ != Mode::Read) { + LOG(ERROR) << "Cannot read from a write only file"; + return false; + } + + //UNIQUE_LOCK(mtx_, lk); + std::unique_lock<std::mutex> lk(mutex_, std::defer_lock); + if (!lk.try_lock()) return true; + + if (jobs_ > 0) { + //LOG(ERROR) << "STILL HAS JOBS"; + return true; + } + + // Check buffer first for frames already read + { + UNIQUE_LOCK(data_mutex_, dlk); + for (auto i = data_.begin(); i != data_.end(); ++i) { + if (std::get<0>(*i).timestamp <= timestamp_) { + ++jobs_; + ftl::pool.push([this,i](int id) { + auto &spkt = std::get<0>(*i); + auto &pkt = std::get<1>(*i); + + try { + if (cb_) cb_(spkt, pkt); + } catch (std::exception &e) { + LOG(ERROR) << "Exception in packet callback: " << e.what(); + } + //LOG(INFO) << "ERASE: " << spkt.timestamp << ", " << spkt.frameNumber() << ", " << (int)spkt.channel; + UNIQUE_LOCK(data_mutex_, dlk); + data_.erase(i); + --jobs_; + }); + } + } + } + + bool partial = false; + int64_t extended_ts = timestamp_ + 200; // Buffer 200ms ahead + + while (active_ && istream_->good() || buffer_in_.nonparsed_size() > 0) { + if (buffer_in_.nonparsed_size() == 0 || (partial && buffer_in_.nonparsed_size() < 10000000)) { + buffer_in_.reserve_buffer(10000000); + istream_->read(buffer_in_.buffer(), buffer_in_.buffer_capacity()); + //if (stream_->bad()) return false; + + int bytes = istream_->gcount(); + if (bytes == 0) break; + buffer_in_.buffer_consumed(bytes); + partial = false; + } + + msgpack::object_handle msg; + if (!buffer_in_.next(msg)) { + partial = true; + continue; + } + + msgpack::object obj = msg.get(); + + UNIQUE_LOCK(data_mutex_, dlk); + auto &data = data_.emplace_back(); + dlk.unlock(); + + try { + obj.convert(data); + } catch (std::exception &e) { + LOG(INFO) << "Corrupt message: " << buffer_in_.nonparsed_size() << " - " << e.what(); + active_ = false; + return false; + } + + // Adjust timestamp + std::get<0>(data).timestamp = ((std::get<0>(data).timestamp) / interval_) * interval_ + timestart_; + //std::get<0>(data).timestamp = std::get<0>(data).timestamp + timestart_; + + // Fix to clear flags for version 2. + if (version_ <= 2) { + std::get<1>(data).flags = 0; + } + if (version_ < 4) { + std::get<0>(data).frame_number = std::get<0>(data).streamID; + std::get<0>(data).streamID = 0; + } + std::get<0>(data).version = 4; + + // Maintain availability of channels. + available(0) += std::get<0>(data).channel; + + // This should only occur for first few frames, generally otherwise + // the data buffer is already several frames ahead so is processed + // above. Hence, no need to bother parallelising this bit. + if (std::get<0>(data).timestamp <= timestamp_) { + if (cb_) { + dlk.lock(); + try { + cb_(std::get<0>(data),std::get<1>(data)); + } catch (std::exception &e) { + LOG(ERROR) << "Exception in packet callback: " << e.what(); + } + data_.pop_back(); + } + } else if (std::get<0>(data).timestamp > extended_ts) { + break; + } + } + + timestamp_ += interval_; + + if (data_.size() == 0 && value("looping", true)) { + istream_->clear(); + istream_->seekg(0); + + ftl::codecs::Header h; + (*istream_).read((char*)&h, sizeof(h)); + if (h.version >= 2) { + ftl::codecs::IndexHeader ih; + (*istream_).read((char*)&ih, sizeof(ih)); + } + + timestart_ = (ftl::timer::get_time() / ftl::timer::getInterval()) * ftl::timer::getInterval(); + timestamp_ = timestart_; + return true; + } + + return data_.size() > 0; +} + +bool File::run() { + timer_ = ftl::timer::add(ftl::timer::kTimerMain, [this](int64_t ts) { + tick(); + return active_; + }); + return true; +} + +bool File::begin(bool dorun) { + if (mode_ == Mode::Read) { + if (!istream_) istream_ = new std::ifstream; + istream_->open(*get<std::string>("filename")); + + if (!istream_->good()) { + LOG(ERROR) << "Could not open file: " << *get<std::string>("filename"); + return false; + } + + ftl::codecs::Header h; + (*istream_).read((char*)&h, sizeof(h)); + if (h.magic[0] != 'F' || h.magic[1] != 'T' || h.magic[2] != 'L' || h.magic[3] != 'F') return false; + + if (h.version >= 2) { + ftl::codecs::IndexHeader ih; + (*istream_).read((char*)&ih, sizeof(ih)); + } + + version_ = h.version; + LOG(INFO) << "FTL format version " << version_; + + // Capture current time to adjust timestamps + timestart_ = (ftl::timer::get_time() / ftl::timer::getInterval()) * ftl::timer::getInterval(); + active_ = true; + interval_ = ftl::timer::getInterval(); + timestamp_ = timestart_; + + tick(); // Do some now! + if (dorun) run(); + } else if (mode_ == Mode::Write) { + if (!ostream_) ostream_ = new std::ofstream; + ostream_->open(*get<std::string>("filename")); + + if (!ostream_->good()) { + LOG(ERROR) << "Could not open file: " << *get<std::string>("filename"); + return false; + } + + ftl::codecs::Header h; + //h.version = 2; + (*ostream_).write((const char*)&h, sizeof(h)); + + ftl::codecs::IndexHeader ih; + ih.reserved[0] = -1; + (*ostream_).write((const char*)&ih, sizeof(ih)); + + // Capture current time to adjust timestamps + timestart_ = ftl::timer::get_time(); + active_ = true; + interval_ = ftl::timer::getInterval(); + timestamp_ = timestart_; + } + + return true; +} + +bool File::end() { + if (!active_) return false; + active_ = false; + timer_.cancel(); + + if (mode_ == Mode::Read) { + if (istream_) { + istream_->close(); + delete istream_; + istream_ = nullptr; + } + } else if (mode_ == Mode::Write) { + if (ostream_) { + ostream_->close(); + delete ostream_; + ostream_ = nullptr; + } + } + return true; +} + +bool File::active() { + return active_; +} diff --git a/components/streams/src/injectors.cpp b/components/streams/src/injectors.cpp new file mode 100644 index 0000000000000000000000000000000000000000..fdc27df8a898ec97ee5baa0a182faf7baca133ef --- /dev/null +++ b/components/streams/src/injectors.cpp @@ -0,0 +1,94 @@ +#include "injectors.hpp" + +using ftl::codecs::Channel; + +class VectorBuffer2 { + public: + inline explicit VectorBuffer2(std::vector<unsigned char> &v) : vector_(v) {} + + inline void write(const char *data, std::size_t size) { + vector_.insert(vector_.end(), (const unsigned char*)data, (const unsigned char*)data+size); + } + + private: + std::vector<unsigned char> &vector_; +}; + +void ftl::stream::injectCalibration(ftl::stream::Stream *stream, const ftl::rgbd::FrameSet &fs, int ix, bool right) { + ftl::stream::injectCalibration(stream, fs.frames[ix], fs.timestamp, ix, right); +} + +void ftl::stream::injectPose(ftl::stream::Stream *stream, const ftl::rgbd::FrameSet &fs, int ix) { + ftl::stream::injectPose(stream, fs.frames[ix], fs.timestamp, ix); +} + +void ftl::stream::injectConfig(ftl::stream::Stream *stream, const ftl::rgbd::FrameSet &fs, int ix) { + ftl::codecs::StreamPacket spkt = { + 4, + fs.timestamp, + 0, + static_cast<uint8_t>(ix), + Channel::Configuration + }; + + ftl::codecs::Packet pkt; + pkt.codec = ftl::codecs::codec_t::MSGPACK; + pkt.definition = ftl::codecs::definition_t::Any; + pkt.bitrate = 0; + pkt.frame_count = 1; + pkt.flags = 0; + + VectorBuffer2 buf(pkt.data); + msgpack::pack(buf, fs.frames[ix].getConfigString()); + + stream->post(spkt, pkt); +} + +void ftl::stream::injectPose(ftl::stream::Stream *stream, const ftl::rgbd::Frame &f, int64_t ts, int ix) { + ftl::codecs::StreamPacket spkt = { + 4, + ts, + 0, + static_cast<uint8_t>(ix), + Channel::Pose + }; + + ftl::codecs::Packet pkt; + pkt.codec = ftl::codecs::codec_t::MSGPACK; + pkt.definition = ftl::codecs::definition_t::Any; + pkt.bitrate = 0; + pkt.frame_count = 1; + pkt.flags = 0; + + auto &pose = f.getPose(); + std::vector<double> data(pose.data(), pose.data() + 4*4); + VectorBuffer2 buf(pkt.data); + msgpack::pack(buf, data); + + stream->post(spkt, pkt); +} + +void ftl::stream::injectCalibration(ftl::stream::Stream *stream, const ftl::rgbd::Frame &f, int64_t ts, int ix, bool right) { + ftl::codecs::StreamPacket spkt = { + 4, + ts, + 0, + static_cast<uint8_t>(ix), + (right) ? Channel::Calibration2 : Channel::Calibration + }; + + auto data = (right) ? + std::make_tuple(f.getRightCamera(), Channel::Right, 0) : + std::make_tuple(f.getLeftCamera(), Channel::Left, 0); + + ftl::codecs::Packet pkt; + pkt.codec = ftl::codecs::codec_t::MSGPACK; + pkt.definition = ftl::codecs::definition_t::Any; + pkt.bitrate = 0; + pkt.frame_count = 1; + pkt.flags = 0; + + VectorBuffer2 buf(pkt.data); + msgpack::pack(buf, data); + stream->post(spkt, pkt); +} diff --git a/components/streams/src/injectors.hpp b/components/streams/src/injectors.hpp new file mode 100644 index 0000000000000000000000000000000000000000..6fbb76031c7213545fb1cb9e9708764b936a00ac --- /dev/null +++ b/components/streams/src/injectors.hpp @@ -0,0 +1,19 @@ +#ifndef _FTL_STREAM_INJECTORS_HPP_ +#define _FTL_STREAM_INJECTORS_HPP_ + +#include <ftl/streams/stream.hpp> + +namespace ftl { +namespace stream { + +void injectPose(ftl::stream::Stream *stream, const ftl::rgbd::FrameSet &fs, int ix); +void injectCalibration(ftl::stream::Stream *stream, const ftl::rgbd::FrameSet &fs, int ix, bool right=false); +void injectConfig(ftl::stream::Stream *stream, const ftl::rgbd::FrameSet &fs, int ix); + +void injectPose(ftl::stream::Stream *stream, const ftl::rgbd::Frame &fs, int64_t ts, int ix); +void injectCalibration(ftl::stream::Stream *stream, const ftl::rgbd::Frame &fs, int64_t ts, int ix, bool right=false); + +} +} + +#endif // _FTL_STREAM_INJECTORS_HPP_ diff --git a/components/streams/src/netstream.cpp b/components/streams/src/netstream.cpp new file mode 100644 index 0000000000000000000000000000000000000000..964965edf6cabbcae39b5d999292b0a0a2ea726d --- /dev/null +++ b/components/streams/src/netstream.cpp @@ -0,0 +1,325 @@ +#include <ftl/streams/netstream.hpp> + +using ftl::stream::Net; +using ftl::codecs::StreamPacket; +using ftl::codecs::Packet; +using ftl::codecs::Channel; +using ftl::codecs::codec_t; +using ftl::codecs::definition_t; +using ftl::codecs::kAllFrames; +using ftl::codecs::kAllFramesets; +using std::string; +using std::vector; +using std::optional; + +Net::Net(nlohmann::json &config, ftl::net::Universe *net) : Stream(config), net_(net), active_(false) { + // TODO: Install "find_stream" binding if not installed... + if (!net_->isBound("find_stream")) { + net_->bind("find_stream", [this](const std::string &uri) -> optional<ftl::UUID> { + LOG(INFO) << "REQUEST FIND STREAM: " << uri; + if (uri_ == uri) { + return net_->id(); + } else { + return {}; + } + }); + } + + if (!net_->isBound("list_streams")) { + net_->bind("list_streams", [this]() { + LOG(INFO) << "REQUEST LIST STREAMS"; + vector<string> streams; + streams.push_back(uri_); + return streams; + }); + } + + last_frame_ = 0; + time_peer_ = ftl::UUID(0); +} + +Net::~Net() { + end(); +} + +bool Net::onPacket(const std::function<void(const ftl::codecs::StreamPacket &, const ftl::codecs::Packet &)> &f) { + cb_ = f; + return true; +} + +bool Net::post(const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) { + if (!active_) return false; + + // Lock to prevent clients being added / removed + { + SHARED_LOCK(mutex_,lk); + available(spkt.frameSetID()) += spkt.channel; + + if (host_) { + auto c = clients_.begin(); + while (c != clients_.end()) { + auto &client = *c; + + // Quality filter the packets + if (client.quality >= 0 && pkt.bitrate != client.quality) { + ++c; + LOG(INFO) << "INCORRECT QUALITY"; + continue; + } + + try { + // FIXME: This doesn't work for file sources with file relative timestamps... + short pre_transmit_latency = short(ftl::timer::get_time() - spkt.timestamp); + if (!net_->send(client.peerid, + uri_, + pre_transmit_latency, // Time since timestamp for tx + spkt, + pkt)) { + + // Send failed so mark as client stream completed + client.txcount = client.txmax; + } else { + // Count frame as completed only if last block and channel is 0 + if (spkt.streamID == 0 && spkt.frame_number == 0 && spkt.channel == Channel::Colour) ++client.txcount; + } + } catch(...) { + client.txcount = client.txmax; + } + ++c; + } + } else { + try { + short pre_transmit_latency = short(ftl::timer::get_time() - spkt.timestamp); + if (!net_->send(peer_, + uri_, + pre_transmit_latency, // Time since timestamp for tx + spkt, + pkt)) { + + } else { + // TODO: Some disconnect error + } + } catch(...) { + // TODO: Some disconnect error + } + } + } + + _cleanUp(); + + return true; +} + +bool Net::begin() { + if (!get<string>("uri")) return false; + active_ = true; + + uri_ = *get<string>("uri"); + + if (net_->isBound(uri_)) { + LOG(ERROR) << "Stream already exists! - " << uri_; + active_ = false; + return false; + } + + net_->bind(uri_, [this](ftl::net::Peer &p, short ttimeoff, const ftl::codecs::StreamPacket &spkt_raw, const ftl::codecs::Packet &pkt) { + int64_t now = std::chrono::time_point_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now()).time_since_epoch().count(); + + if (!active_) return; + + StreamPacket spkt = spkt_raw; + spkt.version = 4; + + // Manage recuring requests + if (last_frame_ != spkt.timestamp) { + UNIQUE_LOCK(mutex_, lk); + if (last_frame_ != spkt.timestamp) { + last_frame_ = spkt.timestamp; + if (tally_ <= 5) { + if (size() > 0) { + auto sel = selected(0); + // FIXME: Send selection changes immediately. + for (auto c : sel) { + _sendRequest(c, kAllFramesets, kAllFrames, 30, 0); + } + } + tally_ = 30; + } else { + --tally_; + } + } + } + + // If hosting and no data then it is a request for data + // Note: a non host can receive empty data, meaning data is available + // but that you did not request it + if (host_ && pkt.data.size() == 0) { + // FIXME: Allow unselecting ...? + if (spkt.frameSetID() == 255) { + for (int i=0; i<size(); ++i) { + select(i, selected(i) + spkt.channel); + } + } else { + select(spkt.frameSetID(), selected(spkt.frameSetID()) + spkt.channel); + } + _processRequest(p, pkt); + } else { + // FIXME: Allow availability to change... + available(spkt.frameSetID()) += spkt.channel; + //LOG(INFO) << "AVAILABLE: " << (int)spkt.channel; + } + + if (cb_) { + cb_(spkt, pkt); + if (pkt.data.size() > 0) _checkDataRate(pkt.data.size(), now-(spkt.timestamp+ttimeoff), spkt.timestamp); + } + }); + + auto p = net_->findOne<ftl::UUID>("find_stream", uri_); + if (!p) { + LOG(INFO) << "Hosting stream: " << uri_; + // TODO: Register URI as available. + host_ = true; + return true; + } + + host_ = false; + peer_ = *p; + tally_ = 30; + + // Initially send a colour request just to create the connection + _sendRequest(Channel::Colour, kAllFramesets, kAllFrames, 30, 0); + + return true; +} + +bool Net::_sendRequest(Channel c, uint8_t frameset, uint8_t frames, uint8_t count, uint8_t bitrate) { + if (!active_ || host_) return false; + + //LOG(INFO) << "SENDING REQUEST FOR " << (int)c; + + Packet pkt = { + codec_t::Any, // TODO: Allow specific codec requests + definition_t::Any, // TODO: Allow specific definition requests + count, + bitrate + }; + + StreamPacket spkt = { + 4, + ftl::timer::get_time(), + frameset, + frames, + c + }; + + net_->send(peer_, uri_, (short)0, spkt, pkt); + + return true; +} + +void Net::_cleanUp() { + UNIQUE_LOCK(mutex_,lk); + for (auto i=clients_.begin(); i!=clients_.end(); ++i) { + auto &client = *i; + if (client.txcount >= client.txmax) { + if (client.peerid == time_peer_) { + time_peer_ = ftl::UUID(0); + } + LOG(INFO) << "Remove peer: " << client.peerid.to_string(); + i = clients_.erase(i); + } + } +} + +/* Packets for specific framesets, frames and channels are requested in + * batches (max 255 unique frames by timestamp). Requests are in the form + * of packets that match the request except the data component is empty. + */ +bool Net::_processRequest(ftl::net::Peer &p, const ftl::codecs::Packet &pkt) { + { + UNIQUE_LOCK(mutex_,lk); + bool found = false; + + // Does the client already exist + for (auto &c : clients_) { + if (c.peerid == p.id()) { + // Yes, so reset internal request counters + c.txcount = 0; + c.txmax = pkt.frame_count; + found = true; + } + } + + // No existing client, so add a new one. + if (!found) { + auto &client = clients_.emplace_back(); + client.peerid = p.id(); + client.quality = 0; // TODO: Use quality given in packet + client.txcount = 0; + client.txmax = pkt.frame_count; + } + + // First connected peer (or reconnecting peer) becomes a time server + if (time_peer_ == ftl::UUID(0)) { + time_peer_ = p.id(); + DLOG(INFO) << "Adding time peer"; + } + } + + // Sync clocks! + if (p.id() == time_peer_) { + auto start = std::chrono::high_resolution_clock::now(); + int64_t mastertime; + + try { + mastertime = net_->call<int64_t>(time_peer_, "__ping__"); + } catch (...) { + LOG(ERROR) << "Ping failed"; + // Reset time peer and remove timer + time_peer_ = ftl::UUID(0); + return false; + } + + auto elapsed = std::chrono::high_resolution_clock::now() - start; + int64_t latency = std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count(); + auto clock_adjust = mastertime - (ftl::timer::get_time() + (latency/2)); + + if (clock_adjust > 0) { + LOG(INFO) << "Clock adjustment: " << clock_adjust; + //LOG(INFO) << "Latency: " << (latency / 2); + //LOG(INFO) << "Local: " << std::chrono::time_point_cast<std::chrono::milliseconds>(start).time_since_epoch().count() << ", master: " << mastertime; + ftl::timer::setClockAdjustment(clock_adjust); + } + } + + return false; +} + +void Net::_checkDataRate(size_t tx_size, int64_t tx_latency, int64_t ts) { + float actual_mbps = (float(tx_size) * 8.0f * (1000.0f / float(tx_latency))) / 1048576.0f; + float min_mbps = (float(tx_size) * 8.0f * (1000.0f / float(ftl::timer::getInterval()))) / 1048576.0f; + if (actual_mbps > 0.0f && actual_mbps < min_mbps) LOG(WARNING) << "Bitrate = " << actual_mbps << "Mbps, min required = " << min_mbps << "Mbps"; + + UNIQUE_LOCK(msg_mtx_,lk); + req_bitrate_ += float(tx_size) * 8.0f; + sample_count_ += 1.0f; + + if (ts - last_msg_ >= 1000) { + LOG(INFO) << "Required Bitrate = " << (req_bitrate_ / float(ts - last_msg_) * 1000.0f / 1048576.0f) << "Mbps"; + last_msg_ = ts; + req_bitrate_ = 0.0f; + sample_count_ = 0.0f; + } +} + +bool Net::end() { + if (!active_) return false; + active_ = false; + net_->unbind(uri_); + return true; +} + +bool Net::active() { + return active_; +} diff --git a/components/streams/src/parsers.cpp b/components/streams/src/parsers.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ebca4576bfb14f4919aaea5ad4376e0bd5a667a6 --- /dev/null +++ b/components/streams/src/parsers.cpp @@ -0,0 +1,39 @@ +#include "parsers.hpp" + +#include <loguru.hpp> + +#include <ftl/codecs/channels.hpp> +#include <tuple> + +ftl::rgbd::Camera ftl::stream::parseCalibration(const ftl::codecs::Packet &pkt) { + std::tuple<ftl::rgbd::Camera, ftl::codecs::Channel, unsigned int> params; + auto unpacked = msgpack::unpack((const char*)pkt.data.data(), pkt.data.size()); + unpacked.get().convert(params); + + LOG(INFO) << "Got Calibration: " << std::get<0>(params).width << "x" << std::get<0>(params).height; + return std::get<0>(params); +} + +Eigen::Matrix4d ftl::stream::parsePose(const ftl::codecs::Packet &pkt) { + Eigen::Matrix4d p; + + if (pkt.codec == ftl::codecs::codec_t::POSE) { + p = Eigen::Map<Eigen::Matrix4d>((double*)pkt.data.data()); + } else if (pkt.codec == ftl::codecs::codec_t::MSGPACK) { + auto unpacked = msgpack::unpack((const char*)pkt.data.data(), pkt.data.size()); + std::vector<double> posevec; + unpacked.get().convert(posevec); + p = Eigen::Matrix4d(posevec.data()); + } + + return p; +} + +std::string ftl::stream::parseConfig(const ftl::codecs::Packet &pkt) { + std::string cfg; + auto unpacked = msgpack::unpack((const char*)pkt.data.data(), pkt.data.size()); + unpacked.get().convert(cfg); + + LOG(INFO) << "Config Received: " << cfg; + return cfg; +} diff --git a/components/streams/src/parsers.hpp b/components/streams/src/parsers.hpp new file mode 100644 index 0000000000000000000000000000000000000000..e463672d8819109bf58a5d1a7294e41024d19313 --- /dev/null +++ b/components/streams/src/parsers.hpp @@ -0,0 +1,18 @@ +#ifndef _FTL_STREAM_PARSERS_HPP_ +#define _FTL_STREAM_PARSERS_HPP_ + +#include <ftl/rgbd/camera.hpp> +#include <ftl/codecs/packet.hpp> +#include <Eigen/Eigen> + +namespace ftl { +namespace stream { + +Eigen::Matrix4d parsePose(const ftl::codecs::Packet &pkt); +ftl::rgbd::Camera parseCalibration(const ftl::codecs::Packet &pkt); +std::string parseConfig(const ftl::codecs::Packet &pkt); + +} +} + +#endif // _FTL_STREAM_PARSERS_HPP_ diff --git a/components/streams/src/receiver.cpp b/components/streams/src/receiver.cpp new file mode 100644 index 0000000000000000000000000000000000000000..36b454759e84be7d63fbb12596daab67fa4b5d8d --- /dev/null +++ b/components/streams/src/receiver.cpp @@ -0,0 +1,190 @@ +#include <ftl/streams/receiver.hpp> + +#include "parsers.hpp" +#include "injectors.hpp" + +using ftl::stream::Receiver; +using ftl::stream::Stream; +using ftl::codecs::StreamPacket; +using ftl::codecs::Packet; +using ftl::codecs::Channel; +using std::string; +using ftl::stream::parseCalibration; +using ftl::stream::parsePose; +using ftl::stream::parseConfig; +using ftl::stream::injectCalibration; +using ftl::stream::injectPose; + +Receiver::Receiver(nlohmann::json &config) : ftl::Configurable(config), stream_(nullptr) { + timestamp_ = 0; + second_channel_ = Channel::Depth; +} + +Receiver::~Receiver() { + +} + +/*void Receiver::_processConfig(InternalStates &frame, const ftl::codecs::Packet &pkt) { + std::string cfg; + auto unpacked = msgpack::unpack((const char*)pkt.data.data(), pkt.data.size()); + unpacked.get().convert(cfg); + + LOG(INFO) << "Config Received: " << cfg; + // TODO: This needs to be put in safer / better location + //host_->set(std::get<0>(cfg), nlohmann::json::parse(std::get<1>(cfg))); +}*/ + +void Receiver::_createDecoder(InternalStates &frame, int chan, const ftl::codecs::Packet &pkt) { + //UNIQUE_LOCK(mutex_,lk); + auto *decoder = frame.decoders[chan]; + if (decoder) { + if (!decoder->accepts(pkt)) { + //UNIQUE_LOCK(mutex_,lk); + ftl::codecs::free(frame.decoders[chan]); + } else { + return; + } + } + + //UNIQUE_LOCK(mutex_,lk); + frame.decoders[chan] = ftl::codecs::allocateDecoder(pkt); +} + +Receiver::InternalStates::InternalStates() { + for (int i=0; i<32; ++i) decoders[i] = nullptr; +} + +Receiver::InternalStates &Receiver::_getFrame(const StreamPacket &spkt) { + UNIQUE_LOCK(mutex_, lk); + while (frames_.size() <= spkt.frameNumber()) { + //frames_.resize(spkt.frameNumber()+1); + frames_.push_back(new InternalStates); + frames_[frames_.size()-1]->state.set("name",std::string("Source ")+std::to_string(spkt.frameNumber()+1)); + } + auto &f = *frames_[spkt.frameNumber()]; + if (!f.frame.origin()) f.frame.setOrigin(&f.state); + return f; +} + +void Receiver::setStream(ftl::stream::Stream *s) { + if (stream_) { + stream_->onPacket(nullptr); + } + + stream_ = s; + + s->onPacket([this](const StreamPacket &spkt, const Packet &pkt) { + //const ftl::codecs::Channel chan = second_channel_; + const ftl::codecs::Channel rchan = spkt.channel; + const unsigned int channum = (unsigned int)spkt.channel; + + //LOG(INFO) << "PACKET: " << spkt.timestamp << ", " << (int)spkt.channel << ", " << (int)pkt.codec; + + // TODO: Allow for multiple framesets + if (spkt.frameSetID() > 0) return; + + // Too many frames, so ignore. + if (spkt.frameNumber() >= value("max_frames",32)) return; + + // Dummy no data packet. + if (pkt.data.size() == 0) return; + + InternalStates &frame = _getFrame(spkt); + + //if (spkt.timestamp > frame.timestamp && !frame.completed.empty()) { + // LOG(WARNING) << "Next frame received"; + //return; + //} + + // Deal with the special channels... + switch (rchan) { + case Channel::Configuration : frame.state.getConfig() = nlohmann::json::parse(parseConfig(pkt)); return; + case Channel::Calibration : frame.state.getLeft() = parseCalibration(pkt); return; + case Channel::Calibration2 : frame.state.getRight() = parseCalibration(pkt); return; + case Channel::Pose : frame.state.getPose() = parsePose(pkt); return; + default: break; + } + + // Packets are for unwanted channel. + //if (rchan != Channel::Colour && rchan != chan) return; + + if (frame.frame.hasChannel(spkt.channel)) { + // FIXME: Is this a corruption in recording or in playback? + // Seems to occur in same place in ftl file, one channel is missing + LOG(ERROR) << "Previous frame not complete: " << frame.timestamp; + //LOG(ERROR) << " --- " << (string)spkt; + UNIQUE_LOCK(frame.mutex, lk); + frame.frame.reset(); + frame.completed.clear(); + } + frame.timestamp = spkt.timestamp; + + // Add channel to frame and allocate memory if required + const cv::Size size = cv::Size(ftl::codecs::getWidth(pkt.definition), ftl::codecs::getHeight(pkt.definition)); + frame.frame.create<cv::cuda::GpuMat>(spkt.channel).create(size, (isFloatChannel(rchan) ? CV_32FC1 : CV_8UC4)); + + Packet tmppkt = pkt; + frame.frame.pushPacket(spkt.channel, tmppkt); + + //LOG(INFO) << " CODEC = " << (int)pkt.codec << " " << (int)pkt.flags << " " << (int)spkt.channel; + + // Do the actual decode + _createDecoder(frame, channum, pkt); + auto *decoder = frame.decoders[channum]; + if (!decoder) { + LOG(ERROR) << "No frame decoder available"; + return; + } + + try { + //LOG(INFO) << "TYPE = " << frame.frame.get<cv::cuda::GpuMat>(spkt.channel).type(); + decoder->decode(pkt, frame.frame.get<cv::cuda::GpuMat>(spkt.channel)); + } catch (std::exception &e) { + LOG(ERROR) << "Decode failed for " << spkt.timestamp << ": " << e.what(); + } + + frame.completed += spkt.channel; + + // Complete if all requested frames are found + auto sel = stream_->selected(spkt.frameSetID()); + + if ((frame.completed & sel) == sel) { + UNIQUE_LOCK(frame.mutex, lk); + if ((frame.completed & sel) == sel) { + timestamp_ = frame.timestamp; + + //LOG(INFO) << "BUILDER PUSH: " << timestamp_ << ", " << spkt.frameNumber(); + + if (frame.state.getLeft().width == 0) { + LOG(WARNING) << "Missing calibration, skipping frame"; + //frame.frame.reset(); + //frame.completed.clear(); + //return; + } + + // TODO: Have multiple builders for different framesets. + builder_.push(frame.timestamp, spkt.frameNumber(), frame.frame); + + // Check for any state changes and send them back + if (frame.state.hasChanged(Channel::Pose)) injectPose(stream_, frame.frame, frame.timestamp, spkt.frameNumber()); + if (frame.state.hasChanged(Channel::Calibration)) injectCalibration(stream_, frame.frame, frame.timestamp, spkt.frameNumber()); + if (frame.state.hasChanged(Channel::Calibration2)) injectCalibration(stream_, frame.frame, frame.timestamp, spkt.frameNumber(), true); + + frame.frame.reset(); + frame.completed.clear(); + } + } + }); +} + +size_t Receiver::size() { + return builder_.size(); +} + +ftl::rgbd::FrameState &Receiver::state(int ix) { + return builder_.state(ix); +} + +void Receiver::onFrameSet(const ftl::rgbd::VideoCallback &cb) { + builder_.onFrameSet(cb); +} diff --git a/components/streams/src/sender.cpp b/components/streams/src/sender.cpp new file mode 100644 index 0000000000000000000000000000000000000000..275ad63f0cabc538004492dbd31e6e7c33acf040 --- /dev/null +++ b/components/streams/src/sender.cpp @@ -0,0 +1,186 @@ +#include <ftl/streams/sender.hpp> + +#include "injectors.hpp" + +using ftl::stream::Sender; +using ftl::codecs::StreamPacket; +using ftl::codecs::Packet; +using ftl::codecs::Channels; +using ftl::codecs::Channel; +using ftl::codecs::definition_t; +using ftl::codecs::device_t; +using ftl::codecs::codec_t; +using ftl::stream::injectCalibration; +using ftl::stream::injectPose; +using ftl::stream::injectConfig; + +Sender::Sender(nlohmann::json &config) : ftl::Configurable(config), stream_(nullptr) { + //do_inject_ = false; +} + +Sender::~Sender() { + // Delete all encoders +} + +/*void Sender::onStateChange(const std::function<void(ftl::codecs::Channel,const ftl::rgbd::FrameState&)> &cb) { + if (cb && state_cb_) throw ftl::exception("State change callback already set"); + state_cb_ = cb; +}*/ + +void Sender::setStream(ftl::stream::Stream*s) { + if (stream_) stream_->onPacket(nullptr); + stream_ = s; + stream_->onPacket([this](const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) { + LOG(INFO) << "SENDER REQUEST : " << (int)spkt.channel; + + //if (state_cb_) state_cb_(spkt.channel, spkt.streamID, spkt.frame_number); + + // Inject state packets + //do_inject_ = true; + do_inject_.clear(); + }); +} + +template <typename T> +static void writeValue(std::vector<unsigned char> &data, T value) { + unsigned char *pvalue_start = (unsigned char*)&value; + data.insert(data.end(), pvalue_start, pvalue_start+sizeof(T)); +} + +static void mergeNALUnits(const std::list<ftl::codecs::Packet> &pkts, ftl::codecs::Packet &pkt) { + size_t size = 0; + for (auto i=pkts.begin(); i!=pkts.end(); ++i) size += (*i).data.size(); + + // TODO: Check Codec, jpg etc can just use last frame. + // TODO: Also if iframe, can just use that instead + + const auto &first = pkts.front(); + pkt.codec = first.codec; + pkt.definition = first.definition; + pkt.frame_count = first.frame_count; + pkt.bitrate = first.bitrate; + pkt.flags = first.flags | ftl::codecs::kFlagMultiple; // means merged packets + pkt.data.reserve(size+pkts.size()*sizeof(int)); + + for (auto i=pkts.begin(); i!=pkts.end(); ++i) { + writeValue<int>(pkt.data, (*i).data.size()); + //LOG(INFO) << "NAL Count = " << (*i).data.size(); + pkt.data.insert(pkt.data.end(), (*i).data.begin(), (*i).data.end()); + } +} + +void Sender::post(const ftl::rgbd::FrameSet &fs) { + if (!stream_) return; + + Channels selected; + if (stream_->size() > 0) selected = stream_->selected(0); + + Channels available; // but not selected and actually sent. + + bool do_inject = !do_inject_.test_and_set(); + //do_inject_ = false; + + for (int i=0; i<fs.frames.size(); ++i) { + const auto &frame = fs.frames[i]; + + if (do_inject) { + //LOG(INFO) << "Force inject calibration"; + injectCalibration(stream_, fs, i); + injectCalibration(stream_, fs, i, true); + injectPose(stream_, fs, i); + injectConfig(stream_, fs, i); + } else { + if (frame.hasChanged(Channel::Pose)) injectPose(stream_, fs, i); + if (frame.hasChanged(Channel::Calibration)) injectCalibration(stream_, fs, i); + if (frame.hasChanged(Channel::Calibration2)) injectCalibration(stream_, fs, i, true); + if (frame.hasChanged(Channel::Configuration)) injectConfig(stream_, fs, i); + } + + for (auto c : frame.getChannels()) { + if (selected.has(c)) { + // FIXME: Sends high res colour, but receive end currently broken + //auto cc = (c == Channel::Colour && frame.hasChannel(Channel::ColourHighRes)) ? Channel::ColourHighRes : Channel::Colour; + auto cc = c; + + StreamPacket spkt; + spkt.version = 4; + spkt.timestamp = fs.timestamp; + spkt.streamID = 0; //fs.id; + spkt.frame_number = i; + spkt.channel = c; + + // Check if there are existing encoded packets + const auto &packets = frame.getPackets(cc); + if (packets.size() > 0) { + if (packets.size() > 1) { + LOG(WARNING) << "Multi-packet send"; + ftl::codecs::Packet pkt; + mergeNALUnits(packets, pkt); + stream_->post(spkt, pkt); + } else { + // Send existing encoding instead of re-encoding + //for (auto &pkt : packets) { + stream_->post(spkt, packets.front()); + //} + } + } else { + auto *enc = _getEncoder(fs.id, i, cc); + + if (enc) { + // FIXME: Timestamps may not always be aligned to interval. + //if (do_inject || fs.timestamp % (10*ftl::timer::getInterval()) == 0) enc->reset(); + if (do_inject) enc->reset(); + try { + enc->encode(frame.get<cv::cuda::GpuMat>(cc), 0, [this,&spkt](const ftl::codecs::Packet &pkt){ + stream_->post(spkt, pkt); + }); + } catch (std::exception &e) { + LOG(ERROR) << "Exception in encoder: " << e.what(); + } + } else { + LOG(ERROR) << "No encoder"; + } + } + } else { + available += c; + } + } + } + + for (auto c : available) { + // Not selected so send an empty packet... + StreamPacket spkt; + spkt.version = 4; + spkt.timestamp = fs.timestamp; + spkt.streamID = 0; // FIXME: fs.id; + spkt.frame_number = 255; + spkt.channel = c; + + Packet pkt; + pkt.codec = codec_t::Any; + pkt.definition = definition_t::Any; + pkt.frame_count = 1; + pkt.bitrate = 0; + stream_->post(spkt, pkt); + } + + //do_inject_ = false; +} + +ftl::codecs::Encoder *Sender::_getEncoder(int fsid, int fid, Channel c) { + int id = (fsid << 16) | (fid << 8) | (int)c; + + { + SHARED_LOCK(mutex_, lk); + auto i = encoders_.find(id); + if (i != encoders_.end()) { + return (*i).second; + } + } + + auto *enc = ftl::codecs::allocateEncoder( + definition_t::HD1080, device_t::Any, codec_t::Any); + UNIQUE_LOCK(mutex_, lk); + encoders_[id] = enc; + return enc; +} diff --git a/components/streams/src/stream.cpp b/components/streams/src/stream.cpp new file mode 100644 index 0000000000000000000000000000000000000000..4a01287f479bd815c4a1456940ac211b9d58069b --- /dev/null +++ b/components/streams/src/stream.cpp @@ -0,0 +1,246 @@ +#include <ftl/streams/stream.hpp> + +using ftl::stream::Muxer; +using ftl::stream::Broadcast; +using ftl::stream::Intercept; +using ftl::stream::Stream; + +const ftl::codecs::Channels<0> &Stream::available(int fs) const { + SHARED_LOCK(mtx_, lk); + if (fs < 0 || fs >= state_.size()) throw ftl::exception("Frameset index out-of-bounds"); + return state_[fs].available; +} + +const ftl::codecs::Channels<0> &Stream::selected(int fs) const { + SHARED_LOCK(mtx_, lk); + if (fs < 0 || fs >= state_.size()) throw ftl::exception("Frameset index out-of-bounds"); + return state_[fs].selected; +} + +void Stream::select(int fs, const ftl::codecs::Channels<0> &s) { + UNIQUE_LOCK(mtx_, lk); + if (fs < 0 || fs >= state_.size()) throw ftl::exception("Frameset index out-of-bounds"); + state_[fs].selected = s; +} + +ftl::codecs::Channels<0> &Stream::available(int fs) { + UNIQUE_LOCK(mtx_, lk); + if (fs < 0) throw ftl::exception("Frameset index out-of-bounds"); + if (fs >= state_.size()) state_.resize(fs+1); + return state_[fs].available; +} + +// ==== Muxer ================================================================== + +Muxer::Muxer(nlohmann::json &config) : Stream(config), nid_(0) { + +} + +Muxer::~Muxer() { + +} + + +void Muxer::add(Stream *s) { + UNIQUE_LOCK(mutex_,lk); + + auto &se = streams_.emplace_back(); + int i = streams_.size()-1; + se.stream = s; + + s->onPacket([this,s,i](const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) { + //SHARED_LOCK(mutex_, lk); + int id = _lookup(i, spkt.frame_number); + + ftl::codecs::StreamPacket spkt2 = spkt; + spkt2.streamID = 0; + spkt2.frame_number = id; + _notify(spkt2, pkt); + s->select(spkt.streamID, selected(0)); + }); +} + +bool Muxer::onPacket(const std::function<void(const ftl::codecs::StreamPacket &, const ftl::codecs::Packet &)> &cb) { + UNIQUE_LOCK(mutex_,lk); + cb_ = cb; + return true; +} + +bool Muxer::post(const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) { + SHARED_LOCK(mutex_, lk); + available(spkt.frameSetID()) += spkt.channel; + + if (spkt.frame_number < revmap_.size()) { + auto [sid, ssid] = revmap_[spkt.frame_number]; + auto &se = streams_[sid]; + + ftl::codecs::StreamPacket spkt2 = spkt; + spkt2.streamID = 0; + spkt2.frame_number = ssid; + se.stream->select(0, selected(spkt.frameSetID())); + return se.stream->post(spkt2, pkt); + } else { + return false; + } +} + +bool Muxer::begin() { + bool r = true; + for (auto &s : streams_) { + r = r && s.stream->begin(); + } + return r; +} + +bool Muxer::end() { + bool r = true; + for (auto &s : streams_) { + r = r && s.stream->end(); + } + return r; +} + +bool Muxer::active() { + bool r = true; + for (auto &s : streams_) { + r = r && s.stream->active(); + } + return r; +} + +int Muxer::_lookup(int sid, int ssid) { + SHARED_LOCK(mutex_, lk); + auto &se = streams_[sid]; + if (ssid >= se.maps.size()) { + lk.unlock(); + { + UNIQUE_LOCK(mutex_, lk2); + int nid = nid_++; + se.maps.push_back(nid); + revmap_.push_back({sid,ssid}); + } + lk.lock(); + } + return se.maps[ssid]; +} + +void Muxer::_notify(const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) { + SHARED_LOCK(mutex_, lk); + available(spkt.frameSetID()) += spkt.channel; + if (cb_) cb_(spkt, pkt); +} + +// ==== Broadcaster ============================================================ + +Broadcast::Broadcast(nlohmann::json &config) : Stream(config) { + +} + +Broadcast::~Broadcast() { + +} + +void Broadcast::add(Stream *s) { + UNIQUE_LOCK(mutex_,lk); + + streams_.push_back(s); + s->onPacket([this](const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) { + SHARED_LOCK(mutex_, lk); + if (cb_) cb_(spkt, pkt); + }); +} + +bool Broadcast::onPacket(const std::function<void(const ftl::codecs::StreamPacket &, const ftl::codecs::Packet &)> &cb) { + UNIQUE_LOCK(mutex_,lk); + cb_ = cb; + return true; +} + +bool Broadcast::post(const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) { + SHARED_LOCK(mutex_, lk); + + bool status = true; + for (auto s : streams_) { + status = status && s->post(spkt, pkt); + } + return status; +} + +bool Broadcast::begin() { + bool r = true; + for (auto &s : streams_) { + r = r && s->begin(); + } + return r; +} + +bool Broadcast::end() { + bool r = true; + for (auto &s : streams_) { + r = r && s->end(); + } + return r; +} + +bool Broadcast::active() { + bool r = true; + for (auto &s : streams_) { + r = r && s->active(); + } + return r; +} + +// ==== Intercept ============================================================== + +Intercept::Intercept(nlohmann::json &config) : Stream(config) { + stream_ = nullptr; +} + +Intercept::~Intercept() { + +} + +void Intercept::setStream(Stream *s) { + UNIQUE_LOCK(mutex_,lk); + + stream_ = s; + s->onPacket([this](const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) { + SHARED_LOCK(mutex_, lk); + available(spkt.frameSetID()) += spkt.channel; + if (cb_) cb_(spkt, pkt); + if (intercept_) intercept_(spkt, pkt); + stream_->select(spkt.streamID, selected(spkt.streamID)); + }); +} + +bool Intercept::onPacket(const std::function<void(const ftl::codecs::StreamPacket &, const ftl::codecs::Packet &)> &cb) { + UNIQUE_LOCK(mutex_,lk); + cb_ = cb; + return true; +} + +bool Intercept::onIntercept(const std::function<void(const ftl::codecs::StreamPacket &, const ftl::codecs::Packet &)> &cb) { + UNIQUE_LOCK(mutex_,lk); + intercept_ = cb; + return true; +} + +bool Intercept::post(const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) { + SHARED_LOCK(mutex_, lk); + available(spkt.frameSetID()) += spkt.channel; + stream_->select(spkt.streamID, selected(spkt.streamID)); + //if (intercept_) intercept_(spkt, pkt); + return stream_->post(spkt, pkt); +} + +bool Intercept::begin() { + return stream_->begin(); +} + +bool Intercept::end() { + return stream_->end(); +} + +bool Intercept::active() { + return stream_->active(); +} diff --git a/components/streams/test/CMakeLists.txt b/components/streams/test/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..6fec3245c6eae652c93a68549bbedc513334212d --- /dev/null +++ b/components/streams/test/CMakeLists.txt @@ -0,0 +1,36 @@ +### Stream Unit ################################################################ +add_executable(stream_unit + ./tests.cpp + ./stream_unit.cpp + ../src/stream.cpp +) +target_include_directories(stream_unit PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../include") +target_link_libraries(stream_unit + ftlcommon ftlcodecs ftlrgbd) + +add_test(StreamUnitTest stream_unit) + +### File Stream Unit ########################################################### +add_executable(filestream_unit + ./tests.cpp + ./filestream_unit.cpp + ../src/filestream.cpp + ../src/stream.cpp +) +target_include_directories(filestream_unit PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../include") +target_link_libraries(filestream_unit + ftlcommon ftlcodecs ftlrgbd) + +add_test(FileStreamUnitTest filestream_unit) + +### Net Stream Unit ########################################################### +#add_executable(netstream_unit +# ./tests.cpp +# ./netstream_unit.cpp +# ../src/stream.cpp +#) +#target_include_directories(netstream_unit PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../include") +#target_link_libraries(netstream_unit +# ftlcommon ftlcodecs ftlrgbd ftlnet) + +#add_test(NetStreamUnitTest netstream_unit) diff --git a/components/streams/test/catch.hpp b/components/streams/test/catch.hpp new file mode 100644 index 0000000000000000000000000000000000000000..b1b2411d24885571e21ec4b3653af58c57011c3e --- /dev/null +++ b/components/streams/test/catch.hpp @@ -0,0 +1,14362 @@ +/* + * Catch v2.5.0 + * Generated: 2018-11-26 20:46:12.165372 + * ---------------------------------------------------------- + * This file has been merged from multiple headers. Please don't edit it directly + * Copyright (c) 2018 Two Blue Cubes Ltd. All rights reserved. + * + * Distributed under the Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + */ +#ifndef TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED +#define TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED +// start catch.hpp + + +#define CATCH_VERSION_MAJOR 2 +#define CATCH_VERSION_MINOR 5 +#define CATCH_VERSION_PATCH 0 + +#ifdef __clang__ +# pragma clang system_header +#elif defined __GNUC__ +# pragma GCC system_header +#endif + +// start catch_suppress_warnings.h + +#ifdef __clang__ +# ifdef __ICC // icpc defines the __clang__ macro +# pragma warning(push) +# pragma warning(disable: 161 1682) +# else // __ICC +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wpadded" +# pragma clang diagnostic ignored "-Wswitch-enum" +# pragma clang diagnostic ignored "-Wcovered-switch-default" +# endif +#elif defined __GNUC__ + // GCC likes to warn on REQUIREs, and we cannot suppress them + // locally because g++'s support for _Pragma is lacking in older, + // still supported, versions +# pragma GCC diagnostic ignored "-Wparentheses" +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wunused-variable" +# pragma GCC diagnostic ignored "-Wpadded" +#endif +// end catch_suppress_warnings.h +#if defined(CATCH_CONFIG_MAIN) || defined(CATCH_CONFIG_RUNNER) +# define CATCH_IMPL +# define CATCH_CONFIG_ALL_PARTS +#endif + +// In the impl file, we want to have access to all parts of the headers +// Can also be used to sanely support PCHs +#if defined(CATCH_CONFIG_ALL_PARTS) +# define CATCH_CONFIG_EXTERNAL_INTERFACES +# if defined(CATCH_CONFIG_DISABLE_MATCHERS) +# undef CATCH_CONFIG_DISABLE_MATCHERS +# endif +# if !defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER) +# define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER +# endif +#endif + +#if !defined(CATCH_CONFIG_IMPL_ONLY) +// start catch_platform.h + +#ifdef __APPLE__ +# include <TargetConditionals.h> +# if TARGET_OS_OSX == 1 +# define CATCH_PLATFORM_MAC +# elif TARGET_OS_IPHONE == 1 +# define CATCH_PLATFORM_IPHONE +# endif + +#elif defined(linux) || defined(__linux) || defined(__linux__) +# define CATCH_PLATFORM_LINUX + +#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) || defined(__MINGW32__) +# define CATCH_PLATFORM_WINDOWS +#endif + +// end catch_platform.h + +#ifdef CATCH_IMPL +# ifndef CLARA_CONFIG_MAIN +# define CLARA_CONFIG_MAIN_NOT_DEFINED +# define CLARA_CONFIG_MAIN +# endif +#endif + +// start catch_user_interfaces.h + +namespace Catch { + unsigned int rngSeed(); +} + +// end catch_user_interfaces.h +// start catch_tag_alias_autoregistrar.h + +// start catch_common.h + +// start catch_compiler_capabilities.h + +// Detect a number of compiler features - by compiler +// The following features are defined: +// +// CATCH_CONFIG_COUNTER : is the __COUNTER__ macro supported? +// CATCH_CONFIG_WINDOWS_SEH : is Windows SEH supported? +// CATCH_CONFIG_POSIX_SIGNALS : are POSIX signals supported? +// CATCH_CONFIG_DISABLE_EXCEPTIONS : Are exceptions enabled? +// **************** +// Note to maintainers: if new toggles are added please document them +// in configuration.md, too +// **************** + +// In general each macro has a _NO_<feature name> form +// (e.g. CATCH_CONFIG_NO_POSIX_SIGNALS) which disables the feature. +// Many features, at point of detection, define an _INTERNAL_ macro, so they +// can be combined, en-mass, with the _NO_ forms later. + +#ifdef __cplusplus + +# if (__cplusplus >= 201402L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201402L) +# define CATCH_CPP14_OR_GREATER +# endif + +# if (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) +# define CATCH_CPP17_OR_GREATER +# endif + +#endif + +#if defined(CATCH_CPP17_OR_GREATER) +# define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS +#endif + +#ifdef __clang__ + +# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + _Pragma( "clang diagnostic push" ) \ + _Pragma( "clang diagnostic ignored \"-Wexit-time-destructors\"" ) \ + _Pragma( "clang diagnostic ignored \"-Wglobal-constructors\"") +# define CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \ + _Pragma( "clang diagnostic pop" ) + +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ + _Pragma( "clang diagnostic push" ) \ + _Pragma( "clang diagnostic ignored \"-Wparentheses\"" ) +# define CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS \ + _Pragma( "clang diagnostic pop" ) + +# define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \ + _Pragma( "clang diagnostic push" ) \ + _Pragma( "clang diagnostic ignored \"-Wunused-variable\"" ) +# define CATCH_INTERNAL_UNSUPPRESS_UNUSED_WARNINGS \ + _Pragma( "clang diagnostic pop" ) + +#endif // __clang__ + +//////////////////////////////////////////////////////////////////////////////// +// Assume that non-Windows platforms support posix signals by default +#if !defined(CATCH_PLATFORM_WINDOWS) + #define CATCH_INTERNAL_CONFIG_POSIX_SIGNALS +#endif + +//////////////////////////////////////////////////////////////////////////////// +// We know some environments not to support full POSIX signals +#if defined(__CYGWIN__) || defined(__QNX__) || defined(__EMSCRIPTEN__) || defined(__DJGPP__) + #define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS +#endif + +#ifdef __OS400__ +# define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS +# define CATCH_CONFIG_COLOUR_NONE +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Android somehow still does not support std::to_string +#if defined(__ANDROID__) +# define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Not all Windows environments support SEH properly +#if defined(__MINGW32__) +# define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH +#endif + +//////////////////////////////////////////////////////////////////////////////// +// PS4 +#if defined(__ORBIS__) +# define CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Cygwin +#ifdef __CYGWIN__ + +// Required for some versions of Cygwin to declare gettimeofday +// see: http://stackoverflow.com/questions/36901803/gettimeofday-not-declared-in-this-scope-cygwin +# define _BSD_SOURCE +// some versions of cygwin (most) do not support std::to_string. Use the libstd check. +// https://gcc.gnu.org/onlinedocs/gcc-4.8.2/libstdc++/api/a01053_source.html line 2812-2813 +# if !((__cplusplus >= 201103L) && defined(_GLIBCXX_USE_C99) \ + && !defined(_GLIBCXX_HAVE_BROKEN_VSWPRINTF)) + +# define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING + +# endif +#endif // __CYGWIN__ + +//////////////////////////////////////////////////////////////////////////////// +// Visual C++ +#ifdef _MSC_VER + +# if _MSC_VER >= 1900 // Visual Studio 2015 or newer +# define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS +# endif + +// Universal Windows platform does not support SEH +// Or console colours (or console at all...) +# if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) +# define CATCH_CONFIG_COLOUR_NONE +# else +# define CATCH_INTERNAL_CONFIG_WINDOWS_SEH +# endif + +// MSVC traditional preprocessor needs some workaround for __VA_ARGS__ +// _MSVC_TRADITIONAL == 0 means new conformant preprocessor +// _MSVC_TRADITIONAL == 1 means old traditional non-conformant preprocessor +# if !defined(_MSVC_TRADITIONAL) || (defined(_MSVC_TRADITIONAL) && _MSVC_TRADITIONAL) +# define CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +# endif + +#endif // _MSC_VER + +//////////////////////////////////////////////////////////////////////////////// +// Check if we are compiled with -fno-exceptions or equivalent +#if defined(__EXCEPTIONS) || defined(__cpp_exceptions) || defined(_CPPUNWIND) +# define CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED +#endif + +//////////////////////////////////////////////////////////////////////////////// +// DJGPP +#ifdef __DJGPP__ +# define CATCH_INTERNAL_CONFIG_NO_WCHAR +#endif // __DJGPP__ + +//////////////////////////////////////////////////////////////////////////////// +// Embarcadero C++Build +#if defined(__BORLANDC__) + #define CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN +#endif + +//////////////////////////////////////////////////////////////////////////////// + +// Use of __COUNTER__ is suppressed during code analysis in +// CLion/AppCode 2017.2.x and former, because __COUNTER__ is not properly +// handled by it. +// Otherwise all supported compilers support COUNTER macro, +// but user still might want to turn it off +#if ( !defined(__JETBRAINS_IDE__) || __JETBRAINS_IDE__ >= 20170300L ) + #define CATCH_INTERNAL_CONFIG_COUNTER +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Check if string_view is available and usable +// The check is split apart to work around v140 (VS2015) preprocessor issue... +#if defined(__has_include) +#if __has_include(<string_view>) && defined(CATCH_CPP17_OR_GREATER) +# define CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW +#endif +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Check if variant is available and usable +#if defined(__has_include) +# if __has_include(<variant>) && defined(CATCH_CPP17_OR_GREATER) +# if defined(__clang__) && (__clang_major__ < 8) + // work around clang bug with libstdc++ https://bugs.llvm.org/show_bug.cgi?id=31852 + // fix should be in clang 8, workaround in libstdc++ 8.2 +# include <ciso646> +# if defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) +# define CATCH_CONFIG_NO_CPP17_VARIANT +# else +# define CATCH_INTERNAL_CONFIG_CPP17_VARIANT +# endif // defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) +# endif // defined(__clang__) && (__clang_major__ < 8) +# endif // __has_include(<variant>) && defined(CATCH_CPP17_OR_GREATER) +#endif // __has_include + +#if defined(CATCH_INTERNAL_CONFIG_COUNTER) && !defined(CATCH_CONFIG_NO_COUNTER) && !defined(CATCH_CONFIG_COUNTER) +# define CATCH_CONFIG_COUNTER +#endif +#if defined(CATCH_INTERNAL_CONFIG_WINDOWS_SEH) && !defined(CATCH_CONFIG_NO_WINDOWS_SEH) && !defined(CATCH_CONFIG_WINDOWS_SEH) && !defined(CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH) +# define CATCH_CONFIG_WINDOWS_SEH +#endif +// This is set by default, because we assume that unix compilers are posix-signal-compatible by default. +#if defined(CATCH_INTERNAL_CONFIG_POSIX_SIGNALS) && !defined(CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_POSIX_SIGNALS) +# define CATCH_CONFIG_POSIX_SIGNALS +#endif +// This is set by default, because we assume that compilers with no wchar_t support are just rare exceptions. +#if !defined(CATCH_INTERNAL_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_WCHAR) +# define CATCH_CONFIG_WCHAR +#endif + +#if !defined(CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_CPP11_TO_STRING) +# define CATCH_CONFIG_CPP11_TO_STRING +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) && !defined(CATCH_CONFIG_NO_CPP17_UNCAUGHT_EXCEPTIONS) && !defined(CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) +# define CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_NO_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_CPP17_STRING_VIEW) +# define CATCH_CONFIG_CPP17_STRING_VIEW +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_VARIANT) && !defined(CATCH_CONFIG_NO_CPP17_VARIANT) && !defined(CATCH_CONFIG_CPP17_VARIANT) +# define CATCH_CONFIG_CPP17_VARIANT +#endif + +#if defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT) +# define CATCH_INTERNAL_CONFIG_NEW_CAPTURE +#endif + +#if defined(CATCH_INTERNAL_CONFIG_NEW_CAPTURE) && !defined(CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NEW_CAPTURE) +# define CATCH_CONFIG_NEW_CAPTURE +#endif + +#if !defined(CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED) && !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) +# define CATCH_CONFIG_DISABLE_EXCEPTIONS +#endif + +#if defined(CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_NO_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_POLYFILL_ISNAN) +# define CATCH_CONFIG_POLYFILL_ISNAN +#endif + +#if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS +# define CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS +# define CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS +# define CATCH_INTERNAL_UNSUPPRESS_UNUSED_WARNINGS +#endif + +#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) +#define CATCH_TRY if ((true)) +#define CATCH_CATCH_ALL if ((false)) +#define CATCH_CATCH_ANON(type) if ((false)) +#else +#define CATCH_TRY try +#define CATCH_CATCH_ALL catch (...) +#define CATCH_CATCH_ANON(type) catch (type) +#endif + +#if defined(CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_NO_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) +#define CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#endif + +// end catch_compiler_capabilities.h +#define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line +#define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) +#ifdef CATCH_CONFIG_COUNTER +# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __COUNTER__ ) +#else +# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ ) +#endif + +#include <iosfwd> +#include <string> +#include <cstdint> + +// We need a dummy global operator<< so we can bring it into Catch namespace later +struct Catch_global_namespace_dummy {}; +std::ostream& operator<<(std::ostream&, Catch_global_namespace_dummy); + +namespace Catch { + + struct CaseSensitive { enum Choice { + Yes, + No + }; }; + + class NonCopyable { + NonCopyable( NonCopyable const& ) = delete; + NonCopyable( NonCopyable && ) = delete; + NonCopyable& operator = ( NonCopyable const& ) = delete; + NonCopyable& operator = ( NonCopyable && ) = delete; + + protected: + NonCopyable(); + virtual ~NonCopyable(); + }; + + struct SourceLineInfo { + + SourceLineInfo() = delete; + SourceLineInfo( char const* _file, std::size_t _line ) noexcept + : file( _file ), + line( _line ) + {} + + SourceLineInfo( SourceLineInfo const& other ) = default; + SourceLineInfo( SourceLineInfo && ) = default; + SourceLineInfo& operator = ( SourceLineInfo const& ) = default; + SourceLineInfo& operator = ( SourceLineInfo && ) = default; + + bool empty() const noexcept; + bool operator == ( SourceLineInfo const& other ) const noexcept; + bool operator < ( SourceLineInfo const& other ) const noexcept; + + char const* file; + std::size_t line; + }; + + std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ); + + // Bring in operator<< from global namespace into Catch namespace + // This is necessary because the overload of operator<< above makes + // lookup stop at namespace Catch + using ::operator<<; + + // Use this in variadic streaming macros to allow + // >> +StreamEndStop + // as well as + // >> stuff +StreamEndStop + struct StreamEndStop { + std::string operator+() const; + }; + template<typename T> + T const& operator + ( T const& value, StreamEndStop ) { + return value; + } +} + +#define CATCH_INTERNAL_LINEINFO \ + ::Catch::SourceLineInfo( __FILE__, static_cast<std::size_t>( __LINE__ ) ) + +// end catch_common.h +namespace Catch { + + struct RegistrarForTagAliases { + RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); + }; + +} // end namespace Catch + +#define CATCH_REGISTER_TAG_ALIAS( alias, spec ) \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } \ + CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS + +// end catch_tag_alias_autoregistrar.h +// start catch_test_registry.h + +// start catch_interfaces_testcase.h + +#include <vector> +#include <memory> + +namespace Catch { + + class TestSpec; + + struct ITestInvoker { + virtual void invoke () const = 0; + virtual ~ITestInvoker(); + }; + + using ITestCasePtr = std::shared_ptr<ITestInvoker>; + + class TestCase; + struct IConfig; + + struct ITestCaseRegistry { + virtual ~ITestCaseRegistry(); + virtual std::vector<TestCase> const& getAllTests() const = 0; + virtual std::vector<TestCase> const& getAllTestsSorted( IConfig const& config ) const = 0; + }; + + bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ); + std::vector<TestCase> filterTests( std::vector<TestCase> const& testCases, TestSpec const& testSpec, IConfig const& config ); + std::vector<TestCase> const& getAllTestCasesSorted( IConfig const& config ); + +} + +// end catch_interfaces_testcase.h +// start catch_stringref.h + +#include <cstddef> +#include <string> +#include <iosfwd> + +namespace Catch { + + class StringData; + + /// A non-owning string class (similar to the forthcoming std::string_view) + /// Note that, because a StringRef may be a substring of another string, + /// it may not be null terminated. c_str() must return a null terminated + /// string, however, and so the StringRef will internally take ownership + /// (taking a copy), if necessary. In theory this ownership is not externally + /// visible - but it does mean (substring) StringRefs should not be shared between + /// threads. + class StringRef { + public: + using size_type = std::size_t; + + private: + friend struct StringRefTestAccess; + + char const* m_start; + size_type m_size; + + char* m_data = nullptr; + + void takeOwnership(); + + static constexpr char const* const s_empty = ""; + + public: // construction/ assignment + StringRef() noexcept + : StringRef( s_empty, 0 ) + {} + + StringRef( StringRef const& other ) noexcept + : m_start( other.m_start ), + m_size( other.m_size ) + {} + + StringRef( StringRef&& other ) noexcept + : m_start( other.m_start ), + m_size( other.m_size ), + m_data( other.m_data ) + { + other.m_data = nullptr; + } + + StringRef( char const* rawChars ) noexcept; + + StringRef( char const* rawChars, size_type size ) noexcept + : m_start( rawChars ), + m_size( size ) + {} + + StringRef( std::string const& stdString ) noexcept + : m_start( stdString.c_str() ), + m_size( stdString.size() ) + {} + + ~StringRef() noexcept { + delete[] m_data; + } + + auto operator = ( StringRef const &other ) noexcept -> StringRef& { + delete[] m_data; + m_data = nullptr; + m_start = other.m_start; + m_size = other.m_size; + return *this; + } + + operator std::string() const; + + void swap( StringRef& other ) noexcept; + + public: // operators + auto operator == ( StringRef const& other ) const noexcept -> bool; + auto operator != ( StringRef const& other ) const noexcept -> bool; + + auto operator[] ( size_type index ) const noexcept -> char; + + public: // named queries + auto empty() const noexcept -> bool { + return m_size == 0; + } + auto size() const noexcept -> size_type { + return m_size; + } + + auto numberOfCharacters() const noexcept -> size_type; + auto c_str() const -> char const*; + + public: // substrings and searches + auto substr( size_type start, size_type size ) const noexcept -> StringRef; + + // Returns the current start pointer. + // Note that the pointer can change when if the StringRef is a substring + auto currentData() const noexcept -> char const*; + + private: // ownership queries - may not be consistent between calls + auto isOwned() const noexcept -> bool; + auto isSubstring() const noexcept -> bool; + }; + + auto operator + ( StringRef const& lhs, StringRef const& rhs ) -> std::string; + auto operator + ( StringRef const& lhs, char const* rhs ) -> std::string; + auto operator + ( char const* lhs, StringRef const& rhs ) -> std::string; + + auto operator += ( std::string& lhs, StringRef const& sr ) -> std::string&; + auto operator << ( std::ostream& os, StringRef const& sr ) -> std::ostream&; + + inline auto operator "" _sr( char const* rawChars, std::size_t size ) noexcept -> StringRef { + return StringRef( rawChars, size ); + } + +} // namespace Catch + +inline auto operator "" _catch_sr( char const* rawChars, std::size_t size ) noexcept -> Catch::StringRef { + return Catch::StringRef( rawChars, size ); +} + +// end catch_stringref.h +// start catch_type_traits.hpp + + +namespace Catch{ + +#ifdef CATCH_CPP17_OR_GREATER + template <typename...> + inline constexpr auto is_unique = std::true_type{}; + + template <typename T, typename... Rest> + inline constexpr auto is_unique<T, Rest...> = std::bool_constant< + (!std::is_same_v<T, Rest> && ...) && is_unique<Rest...> + >{}; +#else + +template <typename...> +struct is_unique : std::true_type{}; + +template <typename T0, typename T1, typename... Rest> +struct is_unique<T0, T1, Rest...> : std::integral_constant +<bool, + !std::is_same<T0, T1>::value + && is_unique<T0, Rest...>::value + && is_unique<T1, Rest...>::value +>{}; + +#endif +} + +// end catch_type_traits.hpp +// start catch_preprocessor.hpp + + +#define CATCH_RECURSION_LEVEL0(...) __VA_ARGS__ +#define CATCH_RECURSION_LEVEL1(...) CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL2(...) CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL3(...) CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL4(...) CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL5(...) CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(__VA_ARGS__))) + +#ifdef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_EXPAND_VARGS(...) __VA_ARGS__ +// MSVC needs more evaluations +#define CATCH_RECURSION_LEVEL6(...) CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(__VA_ARGS__))) +#define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL6(CATCH_RECURSION_LEVEL6(__VA_ARGS__)) +#else +#define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL5(__VA_ARGS__) +#endif + +#define CATCH_REC_END(...) +#define CATCH_REC_OUT + +#define CATCH_EMPTY() +#define CATCH_DEFER(id) id CATCH_EMPTY() + +#define CATCH_REC_GET_END2() 0, CATCH_REC_END +#define CATCH_REC_GET_END1(...) CATCH_REC_GET_END2 +#define CATCH_REC_GET_END(...) CATCH_REC_GET_END1 +#define CATCH_REC_NEXT0(test, next, ...) next CATCH_REC_OUT +#define CATCH_REC_NEXT1(test, next) CATCH_DEFER ( CATCH_REC_NEXT0 ) ( test, next, 0) +#define CATCH_REC_NEXT(test, next) CATCH_REC_NEXT1(CATCH_REC_GET_END test, next) + +#define CATCH_REC_LIST0(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST1(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0) ) ( f, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST2(f, x, peek, ...) f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) + +#define CATCH_REC_LIST0_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST1_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0_UD) ) ( f, userdata, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST2_UD(f, userdata, x, peek, ...) f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) + +// Applies the function macro `f` to each of the remaining parameters, inserts commas between the results, +// and passes userdata as the first parameter to each invocation, +// e.g. CATCH_REC_LIST_UD(f, x, a, b, c) evaluates to f(x, a), f(x, b), f(x, c) +#define CATCH_REC_LIST_UD(f, userdata, ...) CATCH_RECURSE(CATCH_REC_LIST2_UD(f, userdata, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) + +#define CATCH_REC_LIST(f, ...) CATCH_RECURSE(CATCH_REC_LIST2(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) + +#define INTERNAL_CATCH_EXPAND1(param) INTERNAL_CATCH_EXPAND2(param) +#define INTERNAL_CATCH_EXPAND2(...) INTERNAL_CATCH_NO## __VA_ARGS__ +#define INTERNAL_CATCH_DEF(...) INTERNAL_CATCH_DEF __VA_ARGS__ +#define INTERNAL_CATCH_NOINTERNAL_CATCH_DEF + +#define INTERNAL_CATCH_REMOVE_PARENS(...) INTERNAL_CATCH_EXPAND1(INTERNAL_CATCH_DEF __VA_ARGS__) + +#define INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME2(Name, ...) INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME3(Name, __VA_ARGS__) +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME3(Name,...) Name " - " #__VA_ARGS__ +#define INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME(Name,...) INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME2(Name, INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__)) +#else +// MSVC is adding extra space and needs more calls to properly remove () +#define INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME3(Name,...) Name " -" #__VA_ARGS__ +#define INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME1(Name, ...) INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME2(Name, __VA_ARGS__) +#define INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME(Name, ...) INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME1(Name, INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__))) +#endif + +// end catch_preprocessor.hpp +namespace Catch { + +template<typename C> +class TestInvokerAsMethod : public ITestInvoker { + void (C::*m_testAsMethod)(); +public: + TestInvokerAsMethod( void (C::*testAsMethod)() ) noexcept : m_testAsMethod( testAsMethod ) {} + + void invoke() const override { + C obj; + (obj.*m_testAsMethod)(); + } +}; + +auto makeTestInvoker( void(*testAsFunction)() ) noexcept -> ITestInvoker*; + +template<typename C> +auto makeTestInvoker( void (C::*testAsMethod)() ) noexcept -> ITestInvoker* { + return new(std::nothrow) TestInvokerAsMethod<C>( testAsMethod ); +} + +struct NameAndTags { + NameAndTags( StringRef const& name_ = StringRef(), StringRef const& tags_ = StringRef() ) noexcept; + StringRef name; + StringRef tags; +}; + +struct AutoReg : NonCopyable { + AutoReg( ITestInvoker* invoker, SourceLineInfo const& lineInfo, StringRef const& classOrMethod, NameAndTags const& nameAndTags ) noexcept; + ~AutoReg(); +}; + +} // end namespace Catch + +#if defined(CATCH_CONFIG_DISABLE) + #define INTERNAL_CATCH_TESTCASE_NO_REGISTRATION( TestName, ... ) \ + static void TestName() + #define INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION( TestName, ClassName, ... ) \ + namespace{ \ + struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName) { \ + void test(); \ + }; \ + } \ + void TestName::test() + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION( TestName, ... ) \ + template<typename TestType> \ + static void TestName() + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION( TestName, ClassName, ... ) \ + namespace{ \ + template<typename TestType> \ + struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName <TestType>) { \ + void test(); \ + }; \ + } \ + template<typename TestType> \ + void TestName::test() +#endif + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TESTCASE2( TestName, ... ) \ + static void TestName(); \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( &TestName ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ __VA_ARGS__ } ); } /* NOLINT */ \ + CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \ + static void TestName() + #define INTERNAL_CATCH_TESTCASE( ... ) \ + INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), __VA_ARGS__ ) + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, ... ) \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( &QualifiedMethod ), CATCH_INTERNAL_LINEINFO, "&" #QualifiedMethod, Catch::NameAndTags{ __VA_ARGS__ } ); } /* NOLINT */ \ + CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TEST_CASE_METHOD2( TestName, ClassName, ... )\ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ \ + struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName) { \ + void test(); \ + }; \ + Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( Catch::makeTestInvoker( &TestName::test ), CATCH_INTERNAL_LINEINFO, #ClassName, Catch::NameAndTags{ __VA_ARGS__ } ); /* NOLINT */ \ + } \ + CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \ + void TestName::test() + #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, ... ) \ + INTERNAL_CATCH_TEST_CASE_METHOD2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), ClassName, __VA_ARGS__ ) + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_REGISTER_TESTCASE( Function, ... ) \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( Function ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ __VA_ARGS__ } ); /* NOLINT */ \ + CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_2(TestName, TestFunc, Name, Tags, ... )\ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + template<typename TestType> \ + static void TestFunc();\ + namespace {\ + template<typename...Types> \ + struct TestName{\ + template<typename...Ts> \ + TestName(Ts...names){\ + CATCH_INTERNAL_CHECK_UNIQUE_TYPES(CATCH_REC_LIST(INTERNAL_CATCH_REMOVE_PARENS, __VA_ARGS__)) \ + using expander = int[];\ + (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestFunc<Types> ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ names, Tags } ), 0)... };/* NOLINT */ \ + }\ + };\ + INTERNAL_CATCH_TEMPLATE_REGISTRY_INITIATE(TestName, Name, __VA_ARGS__) \ + }\ + CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \ + template<typename TestType> \ + static void TestFunc() + +#if defined(CATCH_CPP17_OR_GREATER) +#define CATCH_INTERNAL_CHECK_UNIQUE_TYPES(...) static_assert(Catch::is_unique<__VA_ARGS__>,"Duplicate type detected in declaration of template test case"); +#else +#define CATCH_INTERNAL_CHECK_UNIQUE_TYPES(...) static_assert(Catch::is_unique<__VA_ARGS__>::value,"Duplicate type detected in declaration of template test case"); +#endif + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE(Name, Tags, ...) \ + INTERNAL_CATCH_TEMPLATE_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), Name, Tags, __VA_ARGS__ ) +#else + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE(Name, Tags, ...) \ + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), Name, Tags, __VA_ARGS__ ) ) +#endif + + #define INTERNAL_CATCH_TEMPLATE_REGISTRY_INITIATE(TestName, Name, ...)\ + static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\ + TestName<CATCH_REC_LIST(INTERNAL_CATCH_REMOVE_PARENS, __VA_ARGS__)>(CATCH_REC_LIST_UD(INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME,Name, __VA_ARGS__));\ + return 0;\ + }(); + + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( TestNameClass, TestName, ClassName, Name, Tags, ... ) \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ \ + template<typename TestType> \ + struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName <TestType>) { \ + void test();\ + };\ + template<typename...Types> \ + struct TestNameClass{\ + template<typename...Ts> \ + TestNameClass(Ts...names){\ + CATCH_INTERNAL_CHECK_UNIQUE_TYPES(CATCH_REC_LIST(INTERNAL_CATCH_REMOVE_PARENS, __VA_ARGS__)) \ + using expander = int[];\ + (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestName<Types>::test ), CATCH_INTERNAL_LINEINFO, #ClassName, Catch::NameAndTags{ names, Tags } ), 0)... };/* NOLINT */ \ + }\ + };\ + INTERNAL_CATCH_TEMPLATE_REGISTRY_INITIATE(TestNameClass, Name, __VA_ARGS__)\ + }\ + CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS\ + template<typename TestType> \ + void TestName<TestType>::test() + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( ClassName, Name, Tags,... ) \ + INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____C_L_A_S_S____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) , ClassName, Name, Tags, __VA_ARGS__ ) +#else + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( ClassName, Name, Tags,... ) \ + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____C_L_A_S_S____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) , ClassName, Name, Tags, __VA_ARGS__ ) ) +#endif + +// end catch_test_registry.h +// start catch_capture.hpp + +// start catch_assertionhandler.h + +// start catch_assertioninfo.h + +// start catch_result_type.h + +namespace Catch { + + // ResultWas::OfType enum + struct ResultWas { enum OfType { + Unknown = -1, + Ok = 0, + Info = 1, + Warning = 2, + + FailureBit = 0x10, + + ExpressionFailed = FailureBit | 1, + ExplicitFailure = FailureBit | 2, + + Exception = 0x100 | FailureBit, + + ThrewException = Exception | 1, + DidntThrowException = Exception | 2, + + FatalErrorCondition = 0x200 | FailureBit + + }; }; + + bool isOk( ResultWas::OfType resultType ); + bool isJustInfo( int flags ); + + // ResultDisposition::Flags enum + struct ResultDisposition { enum Flags { + Normal = 0x01, + + ContinueOnFailure = 0x02, // Failures fail test, but execution continues + FalseTest = 0x04, // Prefix expression with ! + SuppressFail = 0x08 // Failures are reported but do not fail the test + }; }; + + ResultDisposition::Flags operator | ( ResultDisposition::Flags lhs, ResultDisposition::Flags rhs ); + + bool shouldContinueOnFailure( int flags ); + inline bool isFalseTest( int flags ) { return ( flags & ResultDisposition::FalseTest ) != 0; } + bool shouldSuppressFailure( int flags ); + +} // end namespace Catch + +// end catch_result_type.h +namespace Catch { + + struct AssertionInfo + { + StringRef macroName; + SourceLineInfo lineInfo; + StringRef capturedExpression; + ResultDisposition::Flags resultDisposition; + + // We want to delete this constructor but a compiler bug in 4.8 means + // the struct is then treated as non-aggregate + //AssertionInfo() = delete; + }; + +} // end namespace Catch + +// end catch_assertioninfo.h +// start catch_decomposer.h + +// start catch_tostring.h + +#include <vector> +#include <cstddef> +#include <type_traits> +#include <string> +// start catch_stream.h + +#include <iosfwd> +#include <cstddef> +#include <ostream> + +namespace Catch { + + std::ostream& cout(); + std::ostream& cerr(); + std::ostream& clog(); + + class StringRef; + + struct IStream { + virtual ~IStream(); + virtual std::ostream& stream() const = 0; + }; + + auto makeStream( StringRef const &filename ) -> IStream const*; + + class ReusableStringStream { + std::size_t m_index; + std::ostream* m_oss; + public: + ReusableStringStream(); + ~ReusableStringStream(); + + auto str() const -> std::string; + + template<typename T> + auto operator << ( T const& value ) -> ReusableStringStream& { + *m_oss << value; + return *this; + } + auto get() -> std::ostream& { return *m_oss; } + }; +} + +// end catch_stream.h + +#ifdef CATCH_CONFIG_CPP17_STRING_VIEW +#include <string_view> +#endif + +#ifdef __OBJC__ +// start catch_objc_arc.hpp + +#import <Foundation/Foundation.h> + +#ifdef __has_feature +#define CATCH_ARC_ENABLED __has_feature(objc_arc) +#else +#define CATCH_ARC_ENABLED 0 +#endif + +void arcSafeRelease( NSObject* obj ); +id performOptionalSelector( id obj, SEL sel ); + +#if !CATCH_ARC_ENABLED +inline void arcSafeRelease( NSObject* obj ) { + [obj release]; +} +inline id performOptionalSelector( id obj, SEL sel ) { + if( [obj respondsToSelector: sel] ) + return [obj performSelector: sel]; + return nil; +} +#define CATCH_UNSAFE_UNRETAINED +#define CATCH_ARC_STRONG +#else +inline void arcSafeRelease( NSObject* ){} +inline id performOptionalSelector( id obj, SEL sel ) { +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" +#endif + if( [obj respondsToSelector: sel] ) + return [obj performSelector: sel]; +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + return nil; +} +#define CATCH_UNSAFE_UNRETAINED __unsafe_unretained +#define CATCH_ARC_STRONG __strong +#endif + +// end catch_objc_arc.hpp +#endif + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4180) // We attempt to stream a function (address) by const&, which MSVC complains about but is harmless +#endif + +namespace Catch { + namespace Detail { + + extern const std::string unprintableString; + + std::string rawMemoryToString( const void *object, std::size_t size ); + + template<typename T> + std::string rawMemoryToString( const T& object ) { + return rawMemoryToString( &object, sizeof(object) ); + } + + template<typename T> + class IsStreamInsertable { + template<typename SS, typename TT> + static auto test(int) + -> decltype(std::declval<SS&>() << std::declval<TT>(), std::true_type()); + + template<typename, typename> + static auto test(...)->std::false_type; + + public: + static const bool value = decltype(test<std::ostream, const T&>(0))::value; + }; + + template<typename E> + std::string convertUnknownEnumToString( E e ); + + template<typename T> + typename std::enable_if< + !std::is_enum<T>::value && !std::is_base_of<std::exception, T>::value, + std::string>::type convertUnstreamable( T const& ) { + return Detail::unprintableString; + } + template<typename T> + typename std::enable_if< + !std::is_enum<T>::value && std::is_base_of<std::exception, T>::value, + std::string>::type convertUnstreamable(T const& ex) { + return ex.what(); + } + + template<typename T> + typename std::enable_if< + std::is_enum<T>::value + , std::string>::type convertUnstreamable( T const& value ) { + return convertUnknownEnumToString( value ); + } + +#if defined(_MANAGED) + //! Convert a CLR string to a utf8 std::string + template<typename T> + std::string clrReferenceToString( T^ ref ) { + if (ref == nullptr) + return std::string("null"); + auto bytes = System::Text::Encoding::UTF8->GetBytes(ref->ToString()); + cli::pin_ptr<System::Byte> p = &bytes[0]; + return std::string(reinterpret_cast<char const *>(p), bytes->Length); + } +#endif + + } // namespace Detail + + // If we decide for C++14, change these to enable_if_ts + template <typename T, typename = void> + struct StringMaker { + template <typename Fake = T> + static + typename std::enable_if<::Catch::Detail::IsStreamInsertable<Fake>::value, std::string>::type + convert(const Fake& value) { + ReusableStringStream rss; + // NB: call using the function-like syntax to avoid ambiguity with + // user-defined templated operator<< under clang. + rss.operator<<(value); + return rss.str(); + } + + template <typename Fake = T> + static + typename std::enable_if<!::Catch::Detail::IsStreamInsertable<Fake>::value, std::string>::type + convert( const Fake& value ) { +#if !defined(CATCH_CONFIG_FALLBACK_STRINGIFIER) + return Detail::convertUnstreamable(value); +#else + return CATCH_CONFIG_FALLBACK_STRINGIFIER(value); +#endif + } + }; + + namespace Detail { + + // This function dispatches all stringification requests inside of Catch. + // Should be preferably called fully qualified, like ::Catch::Detail::stringify + template <typename T> + std::string stringify(const T& e) { + return ::Catch::StringMaker<typename std::remove_cv<typename std::remove_reference<T>::type>::type>::convert(e); + } + + template<typename E> + std::string convertUnknownEnumToString( E e ) { + return ::Catch::Detail::stringify(static_cast<typename std::underlying_type<E>::type>(e)); + } + +#if defined(_MANAGED) + template <typename T> + std::string stringify( T^ e ) { + return ::Catch::StringMaker<T^>::convert(e); + } +#endif + + } // namespace Detail + + // Some predefined specializations + + template<> + struct StringMaker<std::string> { + static std::string convert(const std::string& str); + }; + +#ifdef CATCH_CONFIG_CPP17_STRING_VIEW + template<> + struct StringMaker<std::string_view> { + static std::string convert(std::string_view str); + }; +#endif + + template<> + struct StringMaker<char const *> { + static std::string convert(char const * str); + }; + template<> + struct StringMaker<char *> { + static std::string convert(char * str); + }; + +#ifdef CATCH_CONFIG_WCHAR + template<> + struct StringMaker<std::wstring> { + static std::string convert(const std::wstring& wstr); + }; + +# ifdef CATCH_CONFIG_CPP17_STRING_VIEW + template<> + struct StringMaker<std::wstring_view> { + static std::string convert(std::wstring_view str); + }; +# endif + + template<> + struct StringMaker<wchar_t const *> { + static std::string convert(wchar_t const * str); + }; + template<> + struct StringMaker<wchar_t *> { + static std::string convert(wchar_t * str); + }; +#endif + + // TBD: Should we use `strnlen` to ensure that we don't go out of the buffer, + // while keeping string semantics? + template<int SZ> + struct StringMaker<char[SZ]> { + static std::string convert(char const* str) { + return ::Catch::Detail::stringify(std::string{ str }); + } + }; + template<int SZ> + struct StringMaker<signed char[SZ]> { + static std::string convert(signed char const* str) { + return ::Catch::Detail::stringify(std::string{ reinterpret_cast<char const *>(str) }); + } + }; + template<int SZ> + struct StringMaker<unsigned char[SZ]> { + static std::string convert(unsigned char const* str) { + return ::Catch::Detail::stringify(std::string{ reinterpret_cast<char const *>(str) }); + } + }; + + template<> + struct StringMaker<int> { + static std::string convert(int value); + }; + template<> + struct StringMaker<long> { + static std::string convert(long value); + }; + template<> + struct StringMaker<long long> { + static std::string convert(long long value); + }; + template<> + struct StringMaker<unsigned int> { + static std::string convert(unsigned int value); + }; + template<> + struct StringMaker<unsigned long> { + static std::string convert(unsigned long value); + }; + template<> + struct StringMaker<unsigned long long> { + static std::string convert(unsigned long long value); + }; + + template<> + struct StringMaker<bool> { + static std::string convert(bool b); + }; + + template<> + struct StringMaker<char> { + static std::string convert(char c); + }; + template<> + struct StringMaker<signed char> { + static std::string convert(signed char c); + }; + template<> + struct StringMaker<unsigned char> { + static std::string convert(unsigned char c); + }; + + template<> + struct StringMaker<std::nullptr_t> { + static std::string convert(std::nullptr_t); + }; + + template<> + struct StringMaker<float> { + static std::string convert(float value); + }; + template<> + struct StringMaker<double> { + static std::string convert(double value); + }; + + template <typename T> + struct StringMaker<T*> { + template <typename U> + static std::string convert(U* p) { + if (p) { + return ::Catch::Detail::rawMemoryToString(p); + } else { + return "nullptr"; + } + } + }; + + template <typename R, typename C> + struct StringMaker<R C::*> { + static std::string convert(R C::* p) { + if (p) { + return ::Catch::Detail::rawMemoryToString(p); + } else { + return "nullptr"; + } + } + }; + +#if defined(_MANAGED) + template <typename T> + struct StringMaker<T^> { + static std::string convert( T^ ref ) { + return ::Catch::Detail::clrReferenceToString(ref); + } + }; +#endif + + namespace Detail { + template<typename InputIterator> + std::string rangeToString(InputIterator first, InputIterator last) { + ReusableStringStream rss; + rss << "{ "; + if (first != last) { + rss << ::Catch::Detail::stringify(*first); + for (++first; first != last; ++first) + rss << ", " << ::Catch::Detail::stringify(*first); + } + rss << " }"; + return rss.str(); + } + } + +#ifdef __OBJC__ + template<> + struct StringMaker<NSString*> { + static std::string convert(NSString * nsstring) { + if (!nsstring) + return "nil"; + return std::string("@") + [nsstring UTF8String]; + } + }; + template<> + struct StringMaker<NSObject*> { + static std::string convert(NSObject* nsObject) { + return ::Catch::Detail::stringify([nsObject description]); + } + + }; + namespace Detail { + inline std::string stringify( NSString* nsstring ) { + return StringMaker<NSString*>::convert( nsstring ); + } + + } // namespace Detail +#endif // __OBJC__ + +} // namespace Catch + +////////////////////////////////////////////////////// +// Separate std-lib types stringification, so it can be selectively enabled +// This means that we do not bring in + +#if defined(CATCH_CONFIG_ENABLE_ALL_STRINGMAKERS) +# define CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER +# define CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER +# define CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER +# define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER +#endif + +// Separate std::pair specialization +#if defined(CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER) +#include <utility> +namespace Catch { + template<typename T1, typename T2> + struct StringMaker<std::pair<T1, T2> > { + static std::string convert(const std::pair<T1, T2>& pair) { + ReusableStringStream rss; + rss << "{ " + << ::Catch::Detail::stringify(pair.first) + << ", " + << ::Catch::Detail::stringify(pair.second) + << " }"; + return rss.str(); + } + }; +} +#endif // CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER + +// Separate std::tuple specialization +#if defined(CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER) +#include <tuple> +namespace Catch { + namespace Detail { + template< + typename Tuple, + std::size_t N = 0, + bool = (N < std::tuple_size<Tuple>::value) + > + struct TupleElementPrinter { + static void print(const Tuple& tuple, std::ostream& os) { + os << (N ? ", " : " ") + << ::Catch::Detail::stringify(std::get<N>(tuple)); + TupleElementPrinter<Tuple, N + 1>::print(tuple, os); + } + }; + + template< + typename Tuple, + std::size_t N + > + struct TupleElementPrinter<Tuple, N, false> { + static void print(const Tuple&, std::ostream&) {} + }; + + } + + template<typename ...Types> + struct StringMaker<std::tuple<Types...>> { + static std::string convert(const std::tuple<Types...>& tuple) { + ReusableStringStream rss; + rss << '{'; + Detail::TupleElementPrinter<std::tuple<Types...>>::print(tuple, rss.get()); + rss << " }"; + return rss.str(); + } + }; +} +#endif // CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER + +#if defined(CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER) && defined(CATCH_CONFIG_CPP17_VARIANT) +#include <variant> +namespace Catch { + template<> + struct StringMaker<std::monostate> { + static std::string convert(const std::monostate&) { + return "{ }"; + } + }; + + template<typename... Elements> + struct StringMaker<std::variant<Elements...>> { + static std::string convert(const std::variant<Elements...>& variant) { + if (variant.valueless_by_exception()) { + return "{valueless variant}"; + } else { + return std::visit( + [](const auto& value) { + return ::Catch::Detail::stringify(value); + }, + variant + ); + } + } + }; +} +#endif // CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER + +namespace Catch { + struct not_this_one {}; // Tag type for detecting which begin/ end are being selected + + // Import begin/ end from std here so they are considered alongside the fallback (...) overloads in this namespace + using std::begin; + using std::end; + + not_this_one begin( ... ); + not_this_one end( ... ); + + template <typename T> + struct is_range { + static const bool value = + !std::is_same<decltype(begin(std::declval<T>())), not_this_one>::value && + !std::is_same<decltype(end(std::declval<T>())), not_this_one>::value; + }; + +#if defined(_MANAGED) // Managed types are never ranges + template <typename T> + struct is_range<T^> { + static const bool value = false; + }; +#endif + + template<typename Range> + std::string rangeToString( Range const& range ) { + return ::Catch::Detail::rangeToString( begin( range ), end( range ) ); + } + + // Handle vector<bool> specially + template<typename Allocator> + std::string rangeToString( std::vector<bool, Allocator> const& v ) { + ReusableStringStream rss; + rss << "{ "; + bool first = true; + for( bool b : v ) { + if( first ) + first = false; + else + rss << ", "; + rss << ::Catch::Detail::stringify( b ); + } + rss << " }"; + return rss.str(); + } + + template<typename R> + struct StringMaker<R, typename std::enable_if<is_range<R>::value && !::Catch::Detail::IsStreamInsertable<R>::value>::type> { + static std::string convert( R const& range ) { + return rangeToString( range ); + } + }; + + template <typename T, int SZ> + struct StringMaker<T[SZ]> { + static std::string convert(T const(&arr)[SZ]) { + return rangeToString(arr); + } + }; + +} // namespace Catch + +// Separate std::chrono::duration specialization +#if defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER) +#include <ctime> +#include <ratio> +#include <chrono> + +namespace Catch { + +template <class Ratio> +struct ratio_string { + static std::string symbol(); +}; + +template <class Ratio> +std::string ratio_string<Ratio>::symbol() { + Catch::ReusableStringStream rss; + rss << '[' << Ratio::num << '/' + << Ratio::den << ']'; + return rss.str(); +} +template <> +struct ratio_string<std::atto> { + static std::string symbol(); +}; +template <> +struct ratio_string<std::femto> { + static std::string symbol(); +}; +template <> +struct ratio_string<std::pico> { + static std::string symbol(); +}; +template <> +struct ratio_string<std::nano> { + static std::string symbol(); +}; +template <> +struct ratio_string<std::micro> { + static std::string symbol(); +}; +template <> +struct ratio_string<std::milli> { + static std::string symbol(); +}; + + //////////// + // std::chrono::duration specializations + template<typename Value, typename Ratio> + struct StringMaker<std::chrono::duration<Value, Ratio>> { + static std::string convert(std::chrono::duration<Value, Ratio> const& duration) { + ReusableStringStream rss; + rss << duration.count() << ' ' << ratio_string<Ratio>::symbol() << 's'; + return rss.str(); + } + }; + template<typename Value> + struct StringMaker<std::chrono::duration<Value, std::ratio<1>>> { + static std::string convert(std::chrono::duration<Value, std::ratio<1>> const& duration) { + ReusableStringStream rss; + rss << duration.count() << " s"; + return rss.str(); + } + }; + template<typename Value> + struct StringMaker<std::chrono::duration<Value, std::ratio<60>>> { + static std::string convert(std::chrono::duration<Value, std::ratio<60>> const& duration) { + ReusableStringStream rss; + rss << duration.count() << " m"; + return rss.str(); + } + }; + template<typename Value> + struct StringMaker<std::chrono::duration<Value, std::ratio<3600>>> { + static std::string convert(std::chrono::duration<Value, std::ratio<3600>> const& duration) { + ReusableStringStream rss; + rss << duration.count() << " h"; + return rss.str(); + } + }; + + //////////// + // std::chrono::time_point specialization + // Generic time_point cannot be specialized, only std::chrono::time_point<system_clock> + template<typename Clock, typename Duration> + struct StringMaker<std::chrono::time_point<Clock, Duration>> { + static std::string convert(std::chrono::time_point<Clock, Duration> const& time_point) { + return ::Catch::Detail::stringify(time_point.time_since_epoch()) + " since epoch"; + } + }; + // std::chrono::time_point<system_clock> specialization + template<typename Duration> + struct StringMaker<std::chrono::time_point<std::chrono::system_clock, Duration>> { + static std::string convert(std::chrono::time_point<std::chrono::system_clock, Duration> const& time_point) { + auto converted = std::chrono::system_clock::to_time_t(time_point); + +#ifdef _MSC_VER + std::tm timeInfo = {}; + gmtime_s(&timeInfo, &converted); +#else + std::tm* timeInfo = std::gmtime(&converted); +#endif + + auto const timeStampSize = sizeof("2017-01-16T17:06:45Z"); + char timeStamp[timeStampSize]; + const char * const fmt = "%Y-%m-%dT%H:%M:%SZ"; + +#ifdef _MSC_VER + std::strftime(timeStamp, timeStampSize, fmt, &timeInfo); +#else + std::strftime(timeStamp, timeStampSize, fmt, timeInfo); +#endif + return std::string(timeStamp); + } + }; +} +#endif // CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +// end catch_tostring.h +#include <iosfwd> + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4389) // '==' : signed/unsigned mismatch +#pragma warning(disable:4018) // more "signed/unsigned mismatch" +#pragma warning(disable:4312) // Converting int to T* using reinterpret_cast (issue on x64 platform) +#pragma warning(disable:4180) // qualifier applied to function type has no meaning +#endif + +namespace Catch { + + struct ITransientExpression { + auto isBinaryExpression() const -> bool { return m_isBinaryExpression; } + auto getResult() const -> bool { return m_result; } + virtual void streamReconstructedExpression( std::ostream &os ) const = 0; + + ITransientExpression( bool isBinaryExpression, bool result ) + : m_isBinaryExpression( isBinaryExpression ), + m_result( result ) + {} + + // We don't actually need a virtual destructor, but many static analysers + // complain if it's not here :-( + virtual ~ITransientExpression(); + + bool m_isBinaryExpression; + bool m_result; + + }; + + void formatReconstructedExpression( std::ostream &os, std::string const& lhs, StringRef op, std::string const& rhs ); + + template<typename LhsT, typename RhsT> + class BinaryExpr : public ITransientExpression { + LhsT m_lhs; + StringRef m_op; + RhsT m_rhs; + + void streamReconstructedExpression( std::ostream &os ) const override { + formatReconstructedExpression + ( os, Catch::Detail::stringify( m_lhs ), m_op, Catch::Detail::stringify( m_rhs ) ); + } + + public: + BinaryExpr( bool comparisonResult, LhsT lhs, StringRef op, RhsT rhs ) + : ITransientExpression{ true, comparisonResult }, + m_lhs( lhs ), + m_op( op ), + m_rhs( rhs ) + {} + }; + + template<typename LhsT> + class UnaryExpr : public ITransientExpression { + LhsT m_lhs; + + void streamReconstructedExpression( std::ostream &os ) const override { + os << Catch::Detail::stringify( m_lhs ); + } + + public: + explicit UnaryExpr( LhsT lhs ) + : ITransientExpression{ false, lhs ? true : false }, + m_lhs( lhs ) + {} + }; + + // Specialised comparison functions to handle equality comparisons between ints and pointers (NULL deduces as an int) + template<typename LhsT, typename RhsT> + auto compareEqual( LhsT const& lhs, RhsT const& rhs ) -> bool { return static_cast<bool>(lhs == rhs); } + template<typename T> + auto compareEqual( T* const& lhs, int rhs ) -> bool { return lhs == reinterpret_cast<void const*>( rhs ); } + template<typename T> + auto compareEqual( T* const& lhs, long rhs ) -> bool { return lhs == reinterpret_cast<void const*>( rhs ); } + template<typename T> + auto compareEqual( int lhs, T* const& rhs ) -> bool { return reinterpret_cast<void const*>( lhs ) == rhs; } + template<typename T> + auto compareEqual( long lhs, T* const& rhs ) -> bool { return reinterpret_cast<void const*>( lhs ) == rhs; } + + template<typename LhsT, typename RhsT> + auto compareNotEqual( LhsT const& lhs, RhsT&& rhs ) -> bool { return static_cast<bool>(lhs != rhs); } + template<typename T> + auto compareNotEqual( T* const& lhs, int rhs ) -> bool { return lhs != reinterpret_cast<void const*>( rhs ); } + template<typename T> + auto compareNotEqual( T* const& lhs, long rhs ) -> bool { return lhs != reinterpret_cast<void const*>( rhs ); } + template<typename T> + auto compareNotEqual( int lhs, T* const& rhs ) -> bool { return reinterpret_cast<void const*>( lhs ) != rhs; } + template<typename T> + auto compareNotEqual( long lhs, T* const& rhs ) -> bool { return reinterpret_cast<void const*>( lhs ) != rhs; } + + template<typename LhsT> + class ExprLhs { + LhsT m_lhs; + public: + explicit ExprLhs( LhsT lhs ) : m_lhs( lhs ) {} + + template<typename RhsT> + auto operator == ( RhsT const& rhs ) -> BinaryExpr<LhsT, RhsT const&> const { + return { compareEqual( m_lhs, rhs ), m_lhs, "==", rhs }; + } + auto operator == ( bool rhs ) -> BinaryExpr<LhsT, bool> const { + return { m_lhs == rhs, m_lhs, "==", rhs }; + } + + template<typename RhsT> + auto operator != ( RhsT const& rhs ) -> BinaryExpr<LhsT, RhsT const&> const { + return { compareNotEqual( m_lhs, rhs ), m_lhs, "!=", rhs }; + } + auto operator != ( bool rhs ) -> BinaryExpr<LhsT, bool> const { + return { m_lhs != rhs, m_lhs, "!=", rhs }; + } + + template<typename RhsT> + auto operator > ( RhsT const& rhs ) -> BinaryExpr<LhsT, RhsT const&> const { + return { static_cast<bool>(m_lhs > rhs), m_lhs, ">", rhs }; + } + template<typename RhsT> + auto operator < ( RhsT const& rhs ) -> BinaryExpr<LhsT, RhsT const&> const { + return { static_cast<bool>(m_lhs < rhs), m_lhs, "<", rhs }; + } + template<typename RhsT> + auto operator >= ( RhsT const& rhs ) -> BinaryExpr<LhsT, RhsT const&> const { + return { static_cast<bool>(m_lhs >= rhs), m_lhs, ">=", rhs }; + } + template<typename RhsT> + auto operator <= ( RhsT const& rhs ) -> BinaryExpr<LhsT, RhsT const&> const { + return { static_cast<bool>(m_lhs <= rhs), m_lhs, "<=", rhs }; + } + + auto makeUnaryExpr() const -> UnaryExpr<LhsT> { + return UnaryExpr<LhsT>{ m_lhs }; + } + }; + + void handleExpression( ITransientExpression const& expr ); + + template<typename T> + void handleExpression( ExprLhs<T> const& expr ) { + handleExpression( expr.makeUnaryExpr() ); + } + + struct Decomposer { + template<typename T> + auto operator <= ( T const& lhs ) -> ExprLhs<T const&> { + return ExprLhs<T const&>{ lhs }; + } + + auto operator <=( bool value ) -> ExprLhs<bool> { + return ExprLhs<bool>{ value }; + } + }; + +} // end namespace Catch + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +// end catch_decomposer.h +// start catch_interfaces_capture.h + +#include <string> + +namespace Catch { + + class AssertionResult; + struct AssertionInfo; + struct SectionInfo; + struct SectionEndInfo; + struct MessageInfo; + struct Counts; + struct BenchmarkInfo; + struct BenchmarkStats; + struct AssertionReaction; + struct SourceLineInfo; + + struct ITransientExpression; + struct IGeneratorTracker; + + struct IResultCapture { + + virtual ~IResultCapture(); + + virtual bool sectionStarted( SectionInfo const& sectionInfo, + Counts& assertions ) = 0; + virtual void sectionEnded( SectionEndInfo const& endInfo ) = 0; + virtual void sectionEndedEarly( SectionEndInfo const& endInfo ) = 0; + + virtual auto acquireGeneratorTracker( SourceLineInfo const& lineInfo ) -> IGeneratorTracker& = 0; + + virtual void benchmarkStarting( BenchmarkInfo const& info ) = 0; + virtual void benchmarkEnded( BenchmarkStats const& stats ) = 0; + + virtual void pushScopedMessage( MessageInfo const& message ) = 0; + virtual void popScopedMessage( MessageInfo const& message ) = 0; + + virtual void handleFatalErrorCondition( StringRef message ) = 0; + + virtual void handleExpr + ( AssertionInfo const& info, + ITransientExpression const& expr, + AssertionReaction& reaction ) = 0; + virtual void handleMessage + ( AssertionInfo const& info, + ResultWas::OfType resultType, + StringRef const& message, + AssertionReaction& reaction ) = 0; + virtual void handleUnexpectedExceptionNotThrown + ( AssertionInfo const& info, + AssertionReaction& reaction ) = 0; + virtual void handleUnexpectedInflightException + ( AssertionInfo const& info, + std::string const& message, + AssertionReaction& reaction ) = 0; + virtual void handleIncomplete + ( AssertionInfo const& info ) = 0; + virtual void handleNonExpr + ( AssertionInfo const &info, + ResultWas::OfType resultType, + AssertionReaction &reaction ) = 0; + + virtual bool lastAssertionPassed() = 0; + virtual void assertionPassed() = 0; + + // Deprecated, do not use: + virtual std::string getCurrentTestName() const = 0; + virtual const AssertionResult* getLastResult() const = 0; + virtual void exceptionEarlyReported() = 0; + }; + + IResultCapture& getResultCapture(); +} + +// end catch_interfaces_capture.h +namespace Catch { + + struct TestFailureException{}; + struct AssertionResultData; + struct IResultCapture; + class RunContext; + + class LazyExpression { + friend class AssertionHandler; + friend struct AssertionStats; + friend class RunContext; + + ITransientExpression const* m_transientExpression = nullptr; + bool m_isNegated; + public: + LazyExpression( bool isNegated ); + LazyExpression( LazyExpression const& other ); + LazyExpression& operator = ( LazyExpression const& ) = delete; + + explicit operator bool() const; + + friend auto operator << ( std::ostream& os, LazyExpression const& lazyExpr ) -> std::ostream&; + }; + + struct AssertionReaction { + bool shouldDebugBreak = false; + bool shouldThrow = false; + }; + + class AssertionHandler { + AssertionInfo m_assertionInfo; + AssertionReaction m_reaction; + bool m_completed = false; + IResultCapture& m_resultCapture; + + public: + AssertionHandler + ( StringRef const& macroName, + SourceLineInfo const& lineInfo, + StringRef capturedExpression, + ResultDisposition::Flags resultDisposition ); + ~AssertionHandler() { + if ( !m_completed ) { + m_resultCapture.handleIncomplete( m_assertionInfo ); + } + } + + template<typename T> + void handleExpr( ExprLhs<T> const& expr ) { + handleExpr( expr.makeUnaryExpr() ); + } + void handleExpr( ITransientExpression const& expr ); + + void handleMessage(ResultWas::OfType resultType, StringRef const& message); + + void handleExceptionThrownAsExpected(); + void handleUnexpectedExceptionNotThrown(); + void handleExceptionNotThrownAsExpected(); + void handleThrowingCallSkipped(); + void handleUnexpectedInflightException(); + + void complete(); + void setCompleted(); + + // query + auto allowThrows() const -> bool; + }; + + void handleExceptionMatchExpr( AssertionHandler& handler, std::string const& str, StringRef const& matcherString ); + +} // namespace Catch + +// end catch_assertionhandler.h +// start catch_message.h + +#include <string> +#include <vector> + +namespace Catch { + + struct MessageInfo { + MessageInfo( StringRef const& _macroName, + SourceLineInfo const& _lineInfo, + ResultWas::OfType _type ); + + StringRef macroName; + std::string message; + SourceLineInfo lineInfo; + ResultWas::OfType type; + unsigned int sequence; + + bool operator == ( MessageInfo const& other ) const; + bool operator < ( MessageInfo const& other ) const; + private: + static unsigned int globalCount; + }; + + struct MessageStream { + + template<typename T> + MessageStream& operator << ( T const& value ) { + m_stream << value; + return *this; + } + + ReusableStringStream m_stream; + }; + + struct MessageBuilder : MessageStream { + MessageBuilder( StringRef const& macroName, + SourceLineInfo const& lineInfo, + ResultWas::OfType type ); + + template<typename T> + MessageBuilder& operator << ( T const& value ) { + m_stream << value; + return *this; + } + + MessageInfo m_info; + }; + + class ScopedMessage { + public: + explicit ScopedMessage( MessageBuilder const& builder ); + ~ScopedMessage(); + + MessageInfo m_info; + }; + + class Capturer { + std::vector<MessageInfo> m_messages; + IResultCapture& m_resultCapture = getResultCapture(); + size_t m_captured = 0; + public: + Capturer( StringRef macroName, SourceLineInfo const& lineInfo, ResultWas::OfType resultType, StringRef names ); + ~Capturer(); + + void captureValue( size_t index, std::string const& value ); + + template<typename T> + void captureValues( size_t index, T const& value ) { + captureValue( index, Catch::Detail::stringify( value ) ); + } + + template<typename T, typename... Ts> + void captureValues( size_t index, T const& value, Ts const&... values ) { + captureValue( index, Catch::Detail::stringify(value) ); + captureValues( index+1, values... ); + } + }; + +} // end namespace Catch + +// end catch_message.h +#if !defined(CATCH_CONFIG_DISABLE) + +#if !defined(CATCH_CONFIG_DISABLE_STRINGIFICATION) + #define CATCH_INTERNAL_STRINGIFY(...) #__VA_ARGS__ +#else + #define CATCH_INTERNAL_STRINGIFY(...) "Disabled by CATCH_CONFIG_DISABLE_STRINGIFICATION" +#endif + +#if defined(CATCH_CONFIG_FAST_COMPILE) || defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + +/////////////////////////////////////////////////////////////////////////////// +// Another way to speed-up compilation is to omit local try-catch for REQUIRE* +// macros. +#define INTERNAL_CATCH_TRY +#define INTERNAL_CATCH_CATCH( capturer ) + +#else // CATCH_CONFIG_FAST_COMPILE + +#define INTERNAL_CATCH_TRY try +#define INTERNAL_CATCH_CATCH( handler ) catch(...) { handler.handleUnexpectedInflightException(); } + +#endif + +#define INTERNAL_CATCH_REACT( handler ) handler.complete(); + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_TEST( macroName, resultDisposition, ... ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition ); \ + INTERNAL_CATCH_TRY { \ + CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ + catchAssertionHandler.handleExpr( Catch::Decomposer() <= __VA_ARGS__ ); \ + CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS \ + } INTERNAL_CATCH_CATCH( catchAssertionHandler ) \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( (void)0, false && static_cast<bool>( !!(__VA_ARGS__) ) ) // the expression here is never evaluated at runtime but it forces the compiler to give it a look + // The double negation silences MSVC's C4800 warning, the static_cast forces short-circuit evaluation if the type has overloaded &&. + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_IF( macroName, resultDisposition, ... ) \ + INTERNAL_CATCH_TEST( macroName, resultDisposition, __VA_ARGS__ ); \ + if( Catch::getResultCapture().lastAssertionPassed() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_ELSE( macroName, resultDisposition, ... ) \ + INTERNAL_CATCH_TEST( macroName, resultDisposition, __VA_ARGS__ ); \ + if( !Catch::getResultCapture().lastAssertionPassed() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_NO_THROW( macroName, resultDisposition, ... ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition ); \ + try { \ + static_cast<void>(__VA_ARGS__); \ + catchAssertionHandler.handleExceptionNotThrownAsExpected(); \ + } \ + catch( ... ) { \ + catchAssertionHandler.handleUnexpectedInflightException(); \ + } \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( false ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_THROWS( macroName, resultDisposition, ... ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition); \ + if( catchAssertionHandler.allowThrows() ) \ + try { \ + static_cast<void>(__VA_ARGS__); \ + catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ + } \ + catch( ... ) { \ + catchAssertionHandler.handleExceptionThrownAsExpected(); \ + } \ + else \ + catchAssertionHandler.handleThrowingCallSkipped(); \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( false ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_THROWS_AS( macroName, exceptionType, resultDisposition, expr ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(expr) ", " CATCH_INTERNAL_STRINGIFY(exceptionType), resultDisposition ); \ + if( catchAssertionHandler.allowThrows() ) \ + try { \ + static_cast<void>(expr); \ + catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ + } \ + catch( exceptionType const& ) { \ + catchAssertionHandler.handleExceptionThrownAsExpected(); \ + } \ + catch( ... ) { \ + catchAssertionHandler.handleUnexpectedInflightException(); \ + } \ + else \ + catchAssertionHandler.handleThrowingCallSkipped(); \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( false ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_MSG( macroName, messageType, resultDisposition, ... ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::StringRef(), resultDisposition ); \ + catchAssertionHandler.handleMessage( messageType, ( Catch::MessageStream() << __VA_ARGS__ + ::Catch::StreamEndStop() ).m_stream.str() ); \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( false ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_CAPTURE( varName, macroName, ... ) \ + auto varName = Catch::Capturer( macroName, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info, #__VA_ARGS__ ); \ + varName.captureValues( 0, __VA_ARGS__ ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_INFO( macroName, log ) \ + Catch::ScopedMessage INTERNAL_CATCH_UNIQUE_NAME( scopedMessage )( Catch::MessageBuilder( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log ); + +/////////////////////////////////////////////////////////////////////////////// +// Although this is matcher-based, it can be used with just a string +#define INTERNAL_CATCH_THROWS_STR_MATCHES( macroName, resultDisposition, matcher, ... ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \ + if( catchAssertionHandler.allowThrows() ) \ + try { \ + static_cast<void>(__VA_ARGS__); \ + catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ + } \ + catch( ... ) { \ + Catch::handleExceptionMatchExpr( catchAssertionHandler, matcher, #matcher##_catch_sr ); \ + } \ + else \ + catchAssertionHandler.handleThrowingCallSkipped(); \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( false ) + +#endif // CATCH_CONFIG_DISABLE + +// end catch_capture.hpp +// start catch_section.h + +// start catch_section_info.h + +// start catch_totals.h + +#include <cstddef> + +namespace Catch { + + struct Counts { + Counts operator - ( Counts const& other ) const; + Counts& operator += ( Counts const& other ); + + std::size_t total() const; + bool allPassed() const; + bool allOk() const; + + std::size_t passed = 0; + std::size_t failed = 0; + std::size_t failedButOk = 0; + }; + + struct Totals { + + Totals operator - ( Totals const& other ) const; + Totals& operator += ( Totals const& other ); + + Totals delta( Totals const& prevTotals ) const; + + int error = 0; + Counts assertions; + Counts testCases; + }; +} + +// end catch_totals.h +#include <string> + +namespace Catch { + + struct SectionInfo { + SectionInfo + ( SourceLineInfo const& _lineInfo, + std::string const& _name ); + + // Deprecated + SectionInfo + ( SourceLineInfo const& _lineInfo, + std::string const& _name, + std::string const& ) : SectionInfo( _lineInfo, _name ) {} + + std::string name; + std::string description; // !Deprecated: this will always be empty + SourceLineInfo lineInfo; + }; + + struct SectionEndInfo { + SectionInfo sectionInfo; + Counts prevAssertions; + double durationInSeconds; + }; + +} // end namespace Catch + +// end catch_section_info.h +// start catch_timer.h + +#include <cstdint> + +namespace Catch { + + auto getCurrentNanosecondsSinceEpoch() -> uint64_t; + auto getEstimatedClockResolution() -> uint64_t; + + class Timer { + uint64_t m_nanoseconds = 0; + public: + void start(); + auto getElapsedNanoseconds() const -> uint64_t; + auto getElapsedMicroseconds() const -> uint64_t; + auto getElapsedMilliseconds() const -> unsigned int; + auto getElapsedSeconds() const -> double; + }; + +} // namespace Catch + +// end catch_timer.h +#include <string> + +namespace Catch { + + class Section : NonCopyable { + public: + Section( SectionInfo const& info ); + ~Section(); + + // This indicates whether the section should be executed or not + explicit operator bool() const; + + private: + SectionInfo m_info; + + std::string m_name; + Counts m_assertions; + bool m_sectionIncluded; + Timer m_timer; + }; + +} // end namespace Catch + +#define INTERNAL_CATCH_SECTION( ... ) \ + CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \ + if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, __VA_ARGS__ ) ) \ + CATCH_INTERNAL_UNSUPPRESS_UNUSED_WARNINGS + +#define INTERNAL_CATCH_DYNAMIC_SECTION( ... ) \ + CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \ + if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, (Catch::ReusableStringStream() << __VA_ARGS__).str() ) ) \ + CATCH_INTERNAL_UNSUPPRESS_UNUSED_WARNINGS + +// end catch_section.h +// start catch_benchmark.h + +#include <cstdint> +#include <string> + +namespace Catch { + + class BenchmarkLooper { + + std::string m_name; + std::size_t m_count = 0; + std::size_t m_iterationsToRun = 1; + uint64_t m_resolution; + Timer m_timer; + + static auto getResolution() -> uint64_t; + public: + // Keep most of this inline as it's on the code path that is being timed + BenchmarkLooper( StringRef name ) + : m_name( name ), + m_resolution( getResolution() ) + { + reportStart(); + m_timer.start(); + } + + explicit operator bool() { + if( m_count < m_iterationsToRun ) + return true; + return needsMoreIterations(); + } + + void increment() { + ++m_count; + } + + void reportStart(); + auto needsMoreIterations() -> bool; + }; + +} // end namespace Catch + +#define BENCHMARK( name ) \ + for( Catch::BenchmarkLooper looper( name ); looper; looper.increment() ) + +// end catch_benchmark.h +// start catch_interfaces_exception.h + +// start catch_interfaces_registry_hub.h + +#include <string> +#include <memory> + +namespace Catch { + + class TestCase; + struct ITestCaseRegistry; + struct IExceptionTranslatorRegistry; + struct IExceptionTranslator; + struct IReporterRegistry; + struct IReporterFactory; + struct ITagAliasRegistry; + class StartupExceptionRegistry; + + using IReporterFactoryPtr = std::shared_ptr<IReporterFactory>; + + struct IRegistryHub { + virtual ~IRegistryHub(); + + virtual IReporterRegistry const& getReporterRegistry() const = 0; + virtual ITestCaseRegistry const& getTestCaseRegistry() const = 0; + virtual ITagAliasRegistry const& getTagAliasRegistry() const = 0; + + virtual IExceptionTranslatorRegistry const& getExceptionTranslatorRegistry() const = 0; + + virtual StartupExceptionRegistry const& getStartupExceptionRegistry() const = 0; + }; + + struct IMutableRegistryHub { + virtual ~IMutableRegistryHub(); + virtual void registerReporter( std::string const& name, IReporterFactoryPtr const& factory ) = 0; + virtual void registerListener( IReporterFactoryPtr const& factory ) = 0; + virtual void registerTest( TestCase const& testInfo ) = 0; + virtual void registerTranslator( const IExceptionTranslator* translator ) = 0; + virtual void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) = 0; + virtual void registerStartupException() noexcept = 0; + }; + + IRegistryHub const& getRegistryHub(); + IMutableRegistryHub& getMutableRegistryHub(); + void cleanUp(); + std::string translateActiveException(); + +} + +// end catch_interfaces_registry_hub.h +#if defined(CATCH_CONFIG_DISABLE) + #define INTERNAL_CATCH_TRANSLATE_EXCEPTION_NO_REG( translatorName, signature) \ + static std::string translatorName( signature ) +#endif + +#include <exception> +#include <string> +#include <vector> + +namespace Catch { + using exceptionTranslateFunction = std::string(*)(); + + struct IExceptionTranslator; + using ExceptionTranslators = std::vector<std::unique_ptr<IExceptionTranslator const>>; + + struct IExceptionTranslator { + virtual ~IExceptionTranslator(); + virtual std::string translate( ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd ) const = 0; + }; + + struct IExceptionTranslatorRegistry { + virtual ~IExceptionTranslatorRegistry(); + + virtual std::string translateActiveException() const = 0; + }; + + class ExceptionTranslatorRegistrar { + template<typename T> + class ExceptionTranslator : public IExceptionTranslator { + public: + + ExceptionTranslator( std::string(*translateFunction)( T& ) ) + : m_translateFunction( translateFunction ) + {} + + std::string translate( ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd ) const override { + try { + if( it == itEnd ) + std::rethrow_exception(std::current_exception()); + else + return (*it)->translate( it+1, itEnd ); + } + catch( T& ex ) { + return m_translateFunction( ex ); + } + } + + protected: + std::string(*m_translateFunction)( T& ); + }; + + public: + template<typename T> + ExceptionTranslatorRegistrar( std::string(*translateFunction)( T& ) ) { + getMutableRegistryHub().registerTranslator + ( new ExceptionTranslator<T>( translateFunction ) ); + } + }; +} + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_TRANSLATE_EXCEPTION2( translatorName, signature ) \ + static std::string translatorName( signature ); \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::ExceptionTranslatorRegistrar INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionRegistrar )( &translatorName ); } \ + CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \ + static std::string translatorName( signature ) + +#define INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION2( INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ), signature ) + +// end catch_interfaces_exception.h +// start catch_approx.h + +#include <type_traits> + +namespace Catch { +namespace Detail { + + class Approx { + private: + bool equalityComparisonImpl(double other) const; + // Validates the new margin (margin >= 0) + // out-of-line to avoid including stdexcept in the header + void setMargin(double margin); + // Validates the new epsilon (0 < epsilon < 1) + // out-of-line to avoid including stdexcept in the header + void setEpsilon(double epsilon); + + public: + explicit Approx ( double value ); + + static Approx custom(); + + Approx operator-() const; + + template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> + Approx operator()( T const& value ) { + Approx approx( static_cast<double>(value) ); + approx.m_epsilon = m_epsilon; + approx.m_margin = m_margin; + approx.m_scale = m_scale; + return approx; + } + + template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> + explicit Approx( T const& value ): Approx(static_cast<double>(value)) + {} + + template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> + friend bool operator == ( const T& lhs, Approx const& rhs ) { + auto lhs_v = static_cast<double>(lhs); + return rhs.equalityComparisonImpl(lhs_v); + } + + template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> + friend bool operator == ( Approx const& lhs, const T& rhs ) { + return operator==( rhs, lhs ); + } + + template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> + friend bool operator != ( T const& lhs, Approx const& rhs ) { + return !operator==( lhs, rhs ); + } + + template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> + friend bool operator != ( Approx const& lhs, T const& rhs ) { + return !operator==( rhs, lhs ); + } + + template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> + friend bool operator <= ( T const& lhs, Approx const& rhs ) { + return static_cast<double>(lhs) < rhs.m_value || lhs == rhs; + } + + template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> + friend bool operator <= ( Approx const& lhs, T const& rhs ) { + return lhs.m_value < static_cast<double>(rhs) || lhs == rhs; + } + + template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> + friend bool operator >= ( T const& lhs, Approx const& rhs ) { + return static_cast<double>(lhs) > rhs.m_value || lhs == rhs; + } + + template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> + friend bool operator >= ( Approx const& lhs, T const& rhs ) { + return lhs.m_value > static_cast<double>(rhs) || lhs == rhs; + } + + template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> + Approx& epsilon( T const& newEpsilon ) { + double epsilonAsDouble = static_cast<double>(newEpsilon); + setEpsilon(epsilonAsDouble); + return *this; + } + + template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> + Approx& margin( T const& newMargin ) { + double marginAsDouble = static_cast<double>(newMargin); + setMargin(marginAsDouble); + return *this; + } + + template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> + Approx& scale( T const& newScale ) { + m_scale = static_cast<double>(newScale); + return *this; + } + + std::string toString() const; + + private: + double m_epsilon; + double m_margin; + double m_scale; + double m_value; + }; +} // end namespace Detail + +namespace literals { + Detail::Approx operator "" _a(long double val); + Detail::Approx operator "" _a(unsigned long long val); +} // end namespace literals + +template<> +struct StringMaker<Catch::Detail::Approx> { + static std::string convert(Catch::Detail::Approx const& value); +}; + +} // end namespace Catch + +// end catch_approx.h +// start catch_string_manip.h + +#include <string> +#include <iosfwd> + +namespace Catch { + + bool startsWith( std::string const& s, std::string const& prefix ); + bool startsWith( std::string const& s, char prefix ); + bool endsWith( std::string const& s, std::string const& suffix ); + bool endsWith( std::string const& s, char suffix ); + bool contains( std::string const& s, std::string const& infix ); + void toLowerInPlace( std::string& s ); + std::string toLower( std::string const& s ); + std::string trim( std::string const& str ); + bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ); + + struct pluralise { + pluralise( std::size_t count, std::string const& label ); + + friend std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ); + + std::size_t m_count; + std::string m_label; + }; +} + +// end catch_string_manip.h +#ifndef CATCH_CONFIG_DISABLE_MATCHERS +// start catch_capture_matchers.h + +// start catch_matchers.h + +#include <string> +#include <vector> + +namespace Catch { +namespace Matchers { + namespace Impl { + + template<typename ArgT> struct MatchAllOf; + template<typename ArgT> struct MatchAnyOf; + template<typename ArgT> struct MatchNotOf; + + class MatcherUntypedBase { + public: + MatcherUntypedBase() = default; + MatcherUntypedBase ( MatcherUntypedBase const& ) = default; + MatcherUntypedBase& operator = ( MatcherUntypedBase const& ) = delete; + std::string toString() const; + + protected: + virtual ~MatcherUntypedBase(); + virtual std::string describe() const = 0; + mutable std::string m_cachedToString; + }; + +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wnon-virtual-dtor" +#endif + + template<typename ObjectT> + struct MatcherMethod { + virtual bool match( ObjectT const& arg ) const = 0; + }; + +#ifdef __clang__ +# pragma clang diagnostic pop +#endif + + template<typename T> + struct MatcherBase : MatcherUntypedBase, MatcherMethod<T> { + + MatchAllOf<T> operator && ( MatcherBase const& other ) const; + MatchAnyOf<T> operator || ( MatcherBase const& other ) const; + MatchNotOf<T> operator ! () const; + }; + + template<typename ArgT> + struct MatchAllOf : MatcherBase<ArgT> { + bool match( ArgT const& arg ) const override { + for( auto matcher : m_matchers ) { + if (!matcher->match(arg)) + return false; + } + return true; + } + std::string describe() const override { + std::string description; + description.reserve( 4 + m_matchers.size()*32 ); + description += "( "; + bool first = true; + for( auto matcher : m_matchers ) { + if( first ) + first = false; + else + description += " and "; + description += matcher->toString(); + } + description += " )"; + return description; + } + + MatchAllOf<ArgT>& operator && ( MatcherBase<ArgT> const& other ) { + m_matchers.push_back( &other ); + return *this; + } + + std::vector<MatcherBase<ArgT> const*> m_matchers; + }; + template<typename ArgT> + struct MatchAnyOf : MatcherBase<ArgT> { + + bool match( ArgT const& arg ) const override { + for( auto matcher : m_matchers ) { + if (matcher->match(arg)) + return true; + } + return false; + } + std::string describe() const override { + std::string description; + description.reserve( 4 + m_matchers.size()*32 ); + description += "( "; + bool first = true; + for( auto matcher : m_matchers ) { + if( first ) + first = false; + else + description += " or "; + description += matcher->toString(); + } + description += " )"; + return description; + } + + MatchAnyOf<ArgT>& operator || ( MatcherBase<ArgT> const& other ) { + m_matchers.push_back( &other ); + return *this; + } + + std::vector<MatcherBase<ArgT> const*> m_matchers; + }; + + template<typename ArgT> + struct MatchNotOf : MatcherBase<ArgT> { + + MatchNotOf( MatcherBase<ArgT> const& underlyingMatcher ) : m_underlyingMatcher( underlyingMatcher ) {} + + bool match( ArgT const& arg ) const override { + return !m_underlyingMatcher.match( arg ); + } + + std::string describe() const override { + return "not " + m_underlyingMatcher.toString(); + } + MatcherBase<ArgT> const& m_underlyingMatcher; + }; + + template<typename T> + MatchAllOf<T> MatcherBase<T>::operator && ( MatcherBase const& other ) const { + return MatchAllOf<T>() && *this && other; + } + template<typename T> + MatchAnyOf<T> MatcherBase<T>::operator || ( MatcherBase const& other ) const { + return MatchAnyOf<T>() || *this || other; + } + template<typename T> + MatchNotOf<T> MatcherBase<T>::operator ! () const { + return MatchNotOf<T>( *this ); + } + + } // namespace Impl + +} // namespace Matchers + +using namespace Matchers; +using Matchers::Impl::MatcherBase; + +} // namespace Catch + +// end catch_matchers.h +// start catch_matchers_floating.h + +#include <type_traits> +#include <cmath> + +namespace Catch { +namespace Matchers { + + namespace Floating { + + enum class FloatingPointKind : uint8_t; + + struct WithinAbsMatcher : MatcherBase<double> { + WithinAbsMatcher(double target, double margin); + bool match(double const& matchee) const override; + std::string describe() const override; + private: + double m_target; + double m_margin; + }; + + struct WithinUlpsMatcher : MatcherBase<double> { + WithinUlpsMatcher(double target, int ulps, FloatingPointKind baseType); + bool match(double const& matchee) const override; + std::string describe() const override; + private: + double m_target; + int m_ulps; + FloatingPointKind m_type; + }; + + } // namespace Floating + + // The following functions create the actual matcher objects. + // This allows the types to be inferred + Floating::WithinUlpsMatcher WithinULP(double target, int maxUlpDiff); + Floating::WithinUlpsMatcher WithinULP(float target, int maxUlpDiff); + Floating::WithinAbsMatcher WithinAbs(double target, double margin); + +} // namespace Matchers +} // namespace Catch + +// end catch_matchers_floating.h +// start catch_matchers_generic.hpp + +#include <functional> +#include <string> + +namespace Catch { +namespace Matchers { +namespace Generic { + +namespace Detail { + std::string finalizeDescription(const std::string& desc); +} + +template <typename T> +class PredicateMatcher : public MatcherBase<T> { + std::function<bool(T const&)> m_predicate; + std::string m_description; +public: + + PredicateMatcher(std::function<bool(T const&)> const& elem, std::string const& descr) + :m_predicate(std::move(elem)), + m_description(Detail::finalizeDescription(descr)) + {} + + bool match( T const& item ) const override { + return m_predicate(item); + } + + std::string describe() const override { + return m_description; + } +}; + +} // namespace Generic + + // The following functions create the actual matcher objects. + // The user has to explicitly specify type to the function, because + // infering std::function<bool(T const&)> is hard (but possible) and + // requires a lot of TMP. + template<typename T> + Generic::PredicateMatcher<T> Predicate(std::function<bool(T const&)> const& predicate, std::string const& description = "") { + return Generic::PredicateMatcher<T>(predicate, description); + } + +} // namespace Matchers +} // namespace Catch + +// end catch_matchers_generic.hpp +// start catch_matchers_string.h + +#include <string> + +namespace Catch { +namespace Matchers { + + namespace StdString { + + struct CasedString + { + CasedString( std::string const& str, CaseSensitive::Choice caseSensitivity ); + std::string adjustString( std::string const& str ) const; + std::string caseSensitivitySuffix() const; + + CaseSensitive::Choice m_caseSensitivity; + std::string m_str; + }; + + struct StringMatcherBase : MatcherBase<std::string> { + StringMatcherBase( std::string const& operation, CasedString const& comparator ); + std::string describe() const override; + + CasedString m_comparator; + std::string m_operation; + }; + + struct EqualsMatcher : StringMatcherBase { + EqualsMatcher( CasedString const& comparator ); + bool match( std::string const& source ) const override; + }; + struct ContainsMatcher : StringMatcherBase { + ContainsMatcher( CasedString const& comparator ); + bool match( std::string const& source ) const override; + }; + struct StartsWithMatcher : StringMatcherBase { + StartsWithMatcher( CasedString const& comparator ); + bool match( std::string const& source ) const override; + }; + struct EndsWithMatcher : StringMatcherBase { + EndsWithMatcher( CasedString const& comparator ); + bool match( std::string const& source ) const override; + }; + + struct RegexMatcher : MatcherBase<std::string> { + RegexMatcher( std::string regex, CaseSensitive::Choice caseSensitivity ); + bool match( std::string const& matchee ) const override; + std::string describe() const override; + + private: + std::string m_regex; + CaseSensitive::Choice m_caseSensitivity; + }; + + } // namespace StdString + + // The following functions create the actual matcher objects. + // This allows the types to be inferred + + StdString::EqualsMatcher Equals( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); + StdString::ContainsMatcher Contains( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); + StdString::EndsWithMatcher EndsWith( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); + StdString::StartsWithMatcher StartsWith( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); + StdString::RegexMatcher Matches( std::string const& regex, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); + +} // namespace Matchers +} // namespace Catch + +// end catch_matchers_string.h +// start catch_matchers_vector.h + +#include <algorithm> + +namespace Catch { +namespace Matchers { + + namespace Vector { + namespace Detail { + template <typename InputIterator, typename T> + size_t count(InputIterator first, InputIterator last, T const& item) { + size_t cnt = 0; + for (; first != last; ++first) { + if (*first == item) { + ++cnt; + } + } + return cnt; + } + template <typename InputIterator, typename T> + bool contains(InputIterator first, InputIterator last, T const& item) { + for (; first != last; ++first) { + if (*first == item) { + return true; + } + } + return false; + } + } + + template<typename T> + struct ContainsElementMatcher : MatcherBase<std::vector<T>> { + + ContainsElementMatcher(T const &comparator) : m_comparator( comparator) {} + + bool match(std::vector<T> const &v) const override { + for (auto const& el : v) { + if (el == m_comparator) { + return true; + } + } + return false; + } + + std::string describe() const override { + return "Contains: " + ::Catch::Detail::stringify( m_comparator ); + } + + T const& m_comparator; + }; + + template<typename T> + struct ContainsMatcher : MatcherBase<std::vector<T>> { + + ContainsMatcher(std::vector<T> const &comparator) : m_comparator( comparator ) {} + + bool match(std::vector<T> const &v) const override { + // !TBD: see note in EqualsMatcher + if (m_comparator.size() > v.size()) + return false; + for (auto const& comparator : m_comparator) { + auto present = false; + for (const auto& el : v) { + if (el == comparator) { + present = true; + break; + } + } + if (!present) { + return false; + } + } + return true; + } + std::string describe() const override { + return "Contains: " + ::Catch::Detail::stringify( m_comparator ); + } + + std::vector<T> const& m_comparator; + }; + + template<typename T> + struct EqualsMatcher : MatcherBase<std::vector<T>> { + + EqualsMatcher(std::vector<T> const &comparator) : m_comparator( comparator ) {} + + bool match(std::vector<T> const &v) const override { + // !TBD: This currently works if all elements can be compared using != + // - a more general approach would be via a compare template that defaults + // to using !=. but could be specialised for, e.g. std::vector<T> etc + // - then just call that directly + if (m_comparator.size() != v.size()) + return false; + for (std::size_t i = 0; i < v.size(); ++i) + if (m_comparator[i] != v[i]) + return false; + return true; + } + std::string describe() const override { + return "Equals: " + ::Catch::Detail::stringify( m_comparator ); + } + std::vector<T> const& m_comparator; + }; + + template<typename T> + struct UnorderedEqualsMatcher : MatcherBase<std::vector<T>> { + UnorderedEqualsMatcher(std::vector<T> const& target) : m_target(target) {} + bool match(std::vector<T> const& vec) const override { + // Note: This is a reimplementation of std::is_permutation, + // because I don't want to include <algorithm> inside the common path + if (m_target.size() != vec.size()) { + return false; + } + auto lfirst = m_target.begin(), llast = m_target.end(); + auto rfirst = vec.begin(), rlast = vec.end(); + // Cut common prefix to optimize checking of permuted parts + while (lfirst != llast && *lfirst == *rfirst) { + ++lfirst; ++rfirst; + } + if (lfirst == llast) { + return true; + } + + for (auto mid = lfirst; mid != llast; ++mid) { + // Skip already counted items + if (Detail::contains(lfirst, mid, *mid)) { + continue; + } + size_t num_vec = Detail::count(rfirst, rlast, *mid); + if (num_vec == 0 || Detail::count(lfirst, llast, *mid) != num_vec) { + return false; + } + } + + return true; + } + + std::string describe() const override { + return "UnorderedEquals: " + ::Catch::Detail::stringify(m_target); + } + private: + std::vector<T> const& m_target; + }; + + } // namespace Vector + + // The following functions create the actual matcher objects. + // This allows the types to be inferred + + template<typename T> + Vector::ContainsMatcher<T> Contains( std::vector<T> const& comparator ) { + return Vector::ContainsMatcher<T>( comparator ); + } + + template<typename T> + Vector::ContainsElementMatcher<T> VectorContains( T const& comparator ) { + return Vector::ContainsElementMatcher<T>( comparator ); + } + + template<typename T> + Vector::EqualsMatcher<T> Equals( std::vector<T> const& comparator ) { + return Vector::EqualsMatcher<T>( comparator ); + } + + template<typename T> + Vector::UnorderedEqualsMatcher<T> UnorderedEquals(std::vector<T> const& target) { + return Vector::UnorderedEqualsMatcher<T>(target); + } + +} // namespace Matchers +} // namespace Catch + +// end catch_matchers_vector.h +namespace Catch { + + template<typename ArgT, typename MatcherT> + class MatchExpr : public ITransientExpression { + ArgT const& m_arg; + MatcherT m_matcher; + StringRef m_matcherString; + public: + MatchExpr( ArgT const& arg, MatcherT const& matcher, StringRef const& matcherString ) + : ITransientExpression{ true, matcher.match( arg ) }, + m_arg( arg ), + m_matcher( matcher ), + m_matcherString( matcherString ) + {} + + void streamReconstructedExpression( std::ostream &os ) const override { + auto matcherAsString = m_matcher.toString(); + os << Catch::Detail::stringify( m_arg ) << ' '; + if( matcherAsString == Detail::unprintableString ) + os << m_matcherString; + else + os << matcherAsString; + } + }; + + using StringMatcher = Matchers::Impl::MatcherBase<std::string>; + + void handleExceptionMatchExpr( AssertionHandler& handler, StringMatcher const& matcher, StringRef const& matcherString ); + + template<typename ArgT, typename MatcherT> + auto makeMatchExpr( ArgT const& arg, MatcherT const& matcher, StringRef const& matcherString ) -> MatchExpr<ArgT, MatcherT> { + return MatchExpr<ArgT, MatcherT>( arg, matcher, matcherString ); + } + +} // namespace Catch + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CHECK_THAT( macroName, matcher, resultDisposition, arg ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(arg) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \ + INTERNAL_CATCH_TRY { \ + catchAssertionHandler.handleExpr( Catch::makeMatchExpr( arg, matcher, #matcher##_catch_sr ) ); \ + } INTERNAL_CATCH_CATCH( catchAssertionHandler ) \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( false ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_THROWS_MATCHES( macroName, exceptionType, resultDisposition, matcher, ... ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__) ", " CATCH_INTERNAL_STRINGIFY(exceptionType) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \ + if( catchAssertionHandler.allowThrows() ) \ + try { \ + static_cast<void>(__VA_ARGS__ ); \ + catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ + } \ + catch( exceptionType const& ex ) { \ + catchAssertionHandler.handleExpr( Catch::makeMatchExpr( ex, matcher, #matcher##_catch_sr ) ); \ + } \ + catch( ... ) { \ + catchAssertionHandler.handleUnexpectedInflightException(); \ + } \ + else \ + catchAssertionHandler.handleThrowingCallSkipped(); \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( false ) + +// end catch_capture_matchers.h +#endif +// start catch_generators.hpp + +// start catch_interfaces_generatortracker.h + + +#include <memory> + +namespace Catch { + + namespace Generators { + class GeneratorBase { + protected: + size_t m_size = 0; + + public: + GeneratorBase( size_t size ) : m_size( size ) {} + virtual ~GeneratorBase(); + auto size() const -> size_t { return m_size; } + }; + using GeneratorBasePtr = std::unique_ptr<GeneratorBase>; + + } // namespace Generators + + struct IGeneratorTracker { + virtual ~IGeneratorTracker(); + virtual auto hasGenerator() const -> bool = 0; + virtual auto getGenerator() const -> Generators::GeneratorBasePtr const& = 0; + virtual void setGenerator( Generators::GeneratorBasePtr&& generator ) = 0; + virtual auto getIndex() const -> std::size_t = 0; + }; + +} // namespace Catch + +// end catch_interfaces_generatortracker.h +// start catch_enforce.h + +#include <stdexcept> + +namespace Catch { +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + template <typename Ex> + [[noreturn]] + void throw_exception(Ex const& e) { + throw e; + } +#else // ^^ Exceptions are enabled // Exceptions are disabled vv + [[noreturn]] + void throw_exception(std::exception const& e); +#endif +} // namespace Catch; + +#define CATCH_PREPARE_EXCEPTION( type, msg ) \ + type( ( Catch::ReusableStringStream() << msg ).str() ) +#define CATCH_INTERNAL_ERROR( msg ) \ + Catch::throw_exception(CATCH_PREPARE_EXCEPTION( std::logic_error, CATCH_INTERNAL_LINEINFO << ": Internal Catch error: " << msg)) +#define CATCH_ERROR( msg ) \ + Catch::throw_exception(CATCH_PREPARE_EXCEPTION( std::domain_error, msg )) +#define CATCH_RUNTIME_ERROR( msg ) \ + Catch::throw_exception(CATCH_PREPARE_EXCEPTION( std::runtime_error, msg )) +#define CATCH_ENFORCE( condition, msg ) \ + do{ if( !(condition) ) CATCH_ERROR( msg ); } while(false) + +// end catch_enforce.h +#include <memory> +#include <vector> +#include <cassert> + +#include <utility> + +namespace Catch { +namespace Generators { + + // !TBD move this into its own location? + namespace pf{ + template<typename T, typename... Args> + std::unique_ptr<T> make_unique( Args&&... args ) { + return std::unique_ptr<T>(new T(std::forward<Args>(args)...)); + } + } + + template<typename T> + struct IGenerator { + virtual ~IGenerator() {} + virtual auto get( size_t index ) const -> T = 0; + }; + + template<typename T> + class SingleValueGenerator : public IGenerator<T> { + T m_value; + public: + SingleValueGenerator( T const& value ) : m_value( value ) {} + + auto get( size_t ) const -> T override { + return m_value; + } + }; + + template<typename T> + class FixedValuesGenerator : public IGenerator<T> { + std::vector<T> m_values; + + public: + FixedValuesGenerator( std::initializer_list<T> values ) : m_values( values ) {} + + auto get( size_t index ) const -> T override { + return m_values[index]; + } + }; + + template<typename T> + class RangeGenerator : public IGenerator<T> { + T const m_first; + T const m_last; + + public: + RangeGenerator( T const& first, T const& last ) : m_first( first ), m_last( last ) { + assert( m_last > m_first ); + } + + auto get( size_t index ) const -> T override { + // ToDo:: introduce a safe cast to catch potential overflows + return static_cast<T>(m_first+index); + } + }; + + template<typename T> + struct NullGenerator : IGenerator<T> { + auto get( size_t ) const -> T override { + CATCH_INTERNAL_ERROR("A Null Generator is always empty"); + } + }; + + template<typename T> + class Generator { + std::unique_ptr<IGenerator<T>> m_generator; + size_t m_size; + + public: + Generator( size_t size, std::unique_ptr<IGenerator<T>> generator ) + : m_generator( std::move( generator ) ), + m_size( size ) + {} + + auto size() const -> size_t { return m_size; } + auto operator[]( size_t index ) const -> T { + assert( index < m_size ); + return m_generator->get( index ); + } + }; + + std::vector<size_t> randomiseIndices( size_t selectionSize, size_t sourceSize ); + + template<typename T> + class GeneratorRandomiser : public IGenerator<T> { + Generator<T> m_baseGenerator; + + std::vector<size_t> m_indices; + public: + GeneratorRandomiser( Generator<T>&& baseGenerator, size_t numberOfItems ) + : m_baseGenerator( std::move( baseGenerator ) ), + m_indices( randomiseIndices( numberOfItems, m_baseGenerator.size() ) ) + {} + + auto get( size_t index ) const -> T override { + return m_baseGenerator[m_indices[index]]; + } + }; + + template<typename T> + struct RequiresASpecialisationFor; + + template<typename T> + auto all() -> Generator<T> { return RequiresASpecialisationFor<T>(); } + + template<> + auto all<int>() -> Generator<int>; + + template<typename T> + auto range( T const& first, T const& last ) -> Generator<T> { + return Generator<T>( (last-first), pf::make_unique<RangeGenerator<T>>( first, last ) ); + } + + template<typename T> + auto random( T const& first, T const& last ) -> Generator<T> { + auto gen = range( first, last ); + auto size = gen.size(); + + return Generator<T>( size, pf::make_unique<GeneratorRandomiser<T>>( std::move( gen ), size ) ); + } + template<typename T> + auto random( size_t size ) -> Generator<T> { + return Generator<T>( size, pf::make_unique<GeneratorRandomiser<T>>( all<T>(), size ) ); + } + + template<typename T> + auto values( std::initializer_list<T> values ) -> Generator<T> { + return Generator<T>( values.size(), pf::make_unique<FixedValuesGenerator<T>>( values ) ); + } + template<typename T> + auto value( T const& val ) -> Generator<T> { + return Generator<T>( 1, pf::make_unique<SingleValueGenerator<T>>( val ) ); + } + + template<typename T> + auto as() -> Generator<T> { + return Generator<T>( 0, pf::make_unique<NullGenerator<T>>() ); + } + + template<typename... Ts> + auto table( std::initializer_list<std::tuple<Ts...>>&& tuples ) -> Generator<std::tuple<Ts...>> { + return values<std::tuple<Ts...>>( std::forward<std::initializer_list<std::tuple<Ts...>>>( tuples ) ); + } + + template<typename T> + struct Generators : GeneratorBase { + std::vector<Generator<T>> m_generators; + + using type = T; + + Generators() : GeneratorBase( 0 ) {} + + void populate( T&& val ) { + m_size += 1; + m_generators.emplace_back( value( std::move( val ) ) ); + } + template<typename U> + void populate( U&& val ) { + populate( T( std::move( val ) ) ); + } + void populate( Generator<T>&& generator ) { + m_size += generator.size(); + m_generators.emplace_back( std::move( generator ) ); + } + + template<typename U, typename... Gs> + void populate( U&& valueOrGenerator, Gs... moreGenerators ) { + populate( std::forward<U>( valueOrGenerator ) ); + populate( std::forward<Gs>( moreGenerators )... ); + } + + auto operator[]( size_t index ) const -> T { + size_t sizes = 0; + for( auto const& gen : m_generators ) { + auto localIndex = index-sizes; + sizes += gen.size(); + if( index < sizes ) + return gen[localIndex]; + } + CATCH_INTERNAL_ERROR("Index '" << index << "' is out of range (" << sizes << ')'); + } + }; + + template<typename T, typename... Gs> + auto makeGenerators( Generator<T>&& generator, Gs... moreGenerators ) -> Generators<T> { + Generators<T> generators; + generators.m_generators.reserve( 1+sizeof...(Gs) ); + generators.populate( std::move( generator ), std::forward<Gs>( moreGenerators )... ); + return generators; + } + template<typename T> + auto makeGenerators( Generator<T>&& generator ) -> Generators<T> { + Generators<T> generators; + generators.populate( std::move( generator ) ); + return generators; + } + template<typename T, typename... Gs> + auto makeGenerators( T&& val, Gs... moreGenerators ) -> Generators<T> { + return makeGenerators( value( std::forward<T>( val ) ), std::forward<Gs>( moreGenerators )... ); + } + template<typename T, typename U, typename... Gs> + auto makeGenerators( U&& val, Gs... moreGenerators ) -> Generators<T> { + return makeGenerators( value( T( std::forward<U>( val ) ) ), std::forward<Gs>( moreGenerators )... ); + } + + auto acquireGeneratorTracker( SourceLineInfo const& lineInfo ) -> IGeneratorTracker&; + + template<typename L> + // Note: The type after -> is weird, because VS2015 cannot parse + // the expression used in the typedef inside, when it is in + // return type. Yeah, ¯\_(ツ)_/¯ + auto generate( SourceLineInfo const& lineInfo, L const& generatorExpression ) -> decltype(std::declval<decltype(generatorExpression())>()[0]) { + using UnderlyingType = typename decltype(generatorExpression())::type; + + IGeneratorTracker& tracker = acquireGeneratorTracker( lineInfo ); + if( !tracker.hasGenerator() ) + tracker.setGenerator( pf::make_unique<Generators<UnderlyingType>>( generatorExpression() ) ); + + auto const& generator = static_cast<Generators<UnderlyingType> const&>( *tracker.getGenerator() ); + return generator[tracker.getIndex()]; + } + +} // namespace Generators +} // namespace Catch + +#define GENERATE( ... ) \ + Catch::Generators::generate( CATCH_INTERNAL_LINEINFO, []{ using namespace Catch::Generators; return makeGenerators( __VA_ARGS__ ); } ) + +// end catch_generators.hpp + +// These files are included here so the single_include script doesn't put them +// in the conditionally compiled sections +// start catch_test_case_info.h + +#include <string> +#include <vector> +#include <memory> + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +namespace Catch { + + struct ITestInvoker; + + struct TestCaseInfo { + enum SpecialProperties{ + None = 0, + IsHidden = 1 << 1, + ShouldFail = 1 << 2, + MayFail = 1 << 3, + Throws = 1 << 4, + NonPortable = 1 << 5, + Benchmark = 1 << 6 + }; + + TestCaseInfo( std::string const& _name, + std::string const& _className, + std::string const& _description, + std::vector<std::string> const& _tags, + SourceLineInfo const& _lineInfo ); + + friend void setTags( TestCaseInfo& testCaseInfo, std::vector<std::string> tags ); + + bool isHidden() const; + bool throws() const; + bool okToFail() const; + bool expectedToFail() const; + + std::string tagsAsString() const; + + std::string name; + std::string className; + std::string description; + std::vector<std::string> tags; + std::vector<std::string> lcaseTags; + SourceLineInfo lineInfo; + SpecialProperties properties; + }; + + class TestCase : public TestCaseInfo { + public: + + TestCase( ITestInvoker* testCase, TestCaseInfo&& info ); + + TestCase withName( std::string const& _newName ) const; + + void invoke() const; + + TestCaseInfo const& getTestCaseInfo() const; + + bool operator == ( TestCase const& other ) const; + bool operator < ( TestCase const& other ) const; + + private: + std::shared_ptr<ITestInvoker> test; + }; + + TestCase makeTestCase( ITestInvoker* testCase, + std::string const& className, + NameAndTags const& nameAndTags, + SourceLineInfo const& lineInfo ); +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +// end catch_test_case_info.h +// start catch_interfaces_runner.h + +namespace Catch { + + struct IRunner { + virtual ~IRunner(); + virtual bool aborting() const = 0; + }; +} + +// end catch_interfaces_runner.h + +#ifdef __OBJC__ +// start catch_objc.hpp + +#import <objc/runtime.h> + +#include <string> + +// NB. Any general catch headers included here must be included +// in catch.hpp first to make sure they are included by the single +// header for non obj-usage + +/////////////////////////////////////////////////////////////////////////////// +// This protocol is really only here for (self) documenting purposes, since +// all its methods are optional. +@protocol OcFixture + +@optional + +-(void) setUp; +-(void) tearDown; + +@end + +namespace Catch { + + class OcMethod : public ITestInvoker { + + public: + OcMethod( Class cls, SEL sel ) : m_cls( cls ), m_sel( sel ) {} + + virtual void invoke() const { + id obj = [[m_cls alloc] init]; + + performOptionalSelector( obj, @selector(setUp) ); + performOptionalSelector( obj, m_sel ); + performOptionalSelector( obj, @selector(tearDown) ); + + arcSafeRelease( obj ); + } + private: + virtual ~OcMethod() {} + + Class m_cls; + SEL m_sel; + }; + + namespace Detail{ + + inline std::string getAnnotation( Class cls, + std::string const& annotationName, + std::string const& testCaseName ) { + NSString* selStr = [[NSString alloc] initWithFormat:@"Catch_%s_%s", annotationName.c_str(), testCaseName.c_str()]; + SEL sel = NSSelectorFromString( selStr ); + arcSafeRelease( selStr ); + id value = performOptionalSelector( cls, sel ); + if( value ) + return [(NSString*)value UTF8String]; + return ""; + } + } + + inline std::size_t registerTestMethods() { + std::size_t noTestMethods = 0; + int noClasses = objc_getClassList( nullptr, 0 ); + + Class* classes = (CATCH_UNSAFE_UNRETAINED Class *)malloc( sizeof(Class) * noClasses); + objc_getClassList( classes, noClasses ); + + for( int c = 0; c < noClasses; c++ ) { + Class cls = classes[c]; + { + u_int count; + Method* methods = class_copyMethodList( cls, &count ); + for( u_int m = 0; m < count ; m++ ) { + SEL selector = method_getName(methods[m]); + std::string methodName = sel_getName(selector); + if( startsWith( methodName, "Catch_TestCase_" ) ) { + std::string testCaseName = methodName.substr( 15 ); + std::string name = Detail::getAnnotation( cls, "Name", testCaseName ); + std::string desc = Detail::getAnnotation( cls, "Description", testCaseName ); + const char* className = class_getName( cls ); + + getMutableRegistryHub().registerTest( makeTestCase( new OcMethod( cls, selector ), className, NameAndTags( name.c_str(), desc.c_str() ), SourceLineInfo("",0) ) ); + noTestMethods++; + } + } + free(methods); + } + } + return noTestMethods; + } + +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) + + namespace Matchers { + namespace Impl { + namespace NSStringMatchers { + + struct StringHolder : MatcherBase<NSString*>{ + StringHolder( NSString* substr ) : m_substr( [substr copy] ){} + StringHolder( StringHolder const& other ) : m_substr( [other.m_substr copy] ){} + StringHolder() { + arcSafeRelease( m_substr ); + } + + bool match( NSString* arg ) const override { + return false; + } + + NSString* CATCH_ARC_STRONG m_substr; + }; + + struct Equals : StringHolder { + Equals( NSString* substr ) : StringHolder( substr ){} + + bool match( NSString* str ) const override { + return (str != nil || m_substr == nil ) && + [str isEqualToString:m_substr]; + } + + std::string describe() const override { + return "equals string: " + Catch::Detail::stringify( m_substr ); + } + }; + + struct Contains : StringHolder { + Contains( NSString* substr ) : StringHolder( substr ){} + + bool match( NSString* str ) const { + return (str != nil || m_substr == nil ) && + [str rangeOfString:m_substr].location != NSNotFound; + } + + std::string describe() const override { + return "contains string: " + Catch::Detail::stringify( m_substr ); + } + }; + + struct StartsWith : StringHolder { + StartsWith( NSString* substr ) : StringHolder( substr ){} + + bool match( NSString* str ) const override { + return (str != nil || m_substr == nil ) && + [str rangeOfString:m_substr].location == 0; + } + + std::string describe() const override { + return "starts with: " + Catch::Detail::stringify( m_substr ); + } + }; + struct EndsWith : StringHolder { + EndsWith( NSString* substr ) : StringHolder( substr ){} + + bool match( NSString* str ) const override { + return (str != nil || m_substr == nil ) && + [str rangeOfString:m_substr].location == [str length] - [m_substr length]; + } + + std::string describe() const override { + return "ends with: " + Catch::Detail::stringify( m_substr ); + } + }; + + } // namespace NSStringMatchers + } // namespace Impl + + inline Impl::NSStringMatchers::Equals + Equals( NSString* substr ){ return Impl::NSStringMatchers::Equals( substr ); } + + inline Impl::NSStringMatchers::Contains + Contains( NSString* substr ){ return Impl::NSStringMatchers::Contains( substr ); } + + inline Impl::NSStringMatchers::StartsWith + StartsWith( NSString* substr ){ return Impl::NSStringMatchers::StartsWith( substr ); } + + inline Impl::NSStringMatchers::EndsWith + EndsWith( NSString* substr ){ return Impl::NSStringMatchers::EndsWith( substr ); } + + } // namespace Matchers + + using namespace Matchers; + +#endif // CATCH_CONFIG_DISABLE_MATCHERS + +} // namespace Catch + +/////////////////////////////////////////////////////////////////////////////// +#define OC_MAKE_UNIQUE_NAME( root, uniqueSuffix ) root##uniqueSuffix +#define OC_TEST_CASE2( name, desc, uniqueSuffix ) \ ++(NSString*) OC_MAKE_UNIQUE_NAME( Catch_Name_test_, uniqueSuffix ) \ +{ \ +return @ name; \ +} \ ++(NSString*) OC_MAKE_UNIQUE_NAME( Catch_Description_test_, uniqueSuffix ) \ +{ \ +return @ desc; \ +} \ +-(void) OC_MAKE_UNIQUE_NAME( Catch_TestCase_test_, uniqueSuffix ) + +#define OC_TEST_CASE( name, desc ) OC_TEST_CASE2( name, desc, __LINE__ ) + +// end catch_objc.hpp +#endif + +#ifdef CATCH_CONFIG_EXTERNAL_INTERFACES +// start catch_external_interfaces.h + +// start catch_reporter_bases.hpp + +// start catch_interfaces_reporter.h + +// start catch_config.hpp + +// start catch_test_spec_parser.h + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +// start catch_test_spec.h + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +// start catch_wildcard_pattern.h + +namespace Catch +{ + class WildcardPattern { + enum WildcardPosition { + NoWildcard = 0, + WildcardAtStart = 1, + WildcardAtEnd = 2, + WildcardAtBothEnds = WildcardAtStart | WildcardAtEnd + }; + + public: + + WildcardPattern( std::string const& pattern, CaseSensitive::Choice caseSensitivity ); + virtual ~WildcardPattern() = default; + virtual bool matches( std::string const& str ) const; + + private: + std::string adjustCase( std::string const& str ) const; + CaseSensitive::Choice m_caseSensitivity; + WildcardPosition m_wildcard = NoWildcard; + std::string m_pattern; + }; +} + +// end catch_wildcard_pattern.h +#include <string> +#include <vector> +#include <memory> + +namespace Catch { + + class TestSpec { + struct Pattern { + virtual ~Pattern(); + virtual bool matches( TestCaseInfo const& testCase ) const = 0; + }; + using PatternPtr = std::shared_ptr<Pattern>; + + class NamePattern : public Pattern { + public: + NamePattern( std::string const& name ); + virtual ~NamePattern(); + virtual bool matches( TestCaseInfo const& testCase ) const override; + private: + WildcardPattern m_wildcardPattern; + }; + + class TagPattern : public Pattern { + public: + TagPattern( std::string const& tag ); + virtual ~TagPattern(); + virtual bool matches( TestCaseInfo const& testCase ) const override; + private: + std::string m_tag; + }; + + class ExcludedPattern : public Pattern { + public: + ExcludedPattern( PatternPtr const& underlyingPattern ); + virtual ~ExcludedPattern(); + virtual bool matches( TestCaseInfo const& testCase ) const override; + private: + PatternPtr m_underlyingPattern; + }; + + struct Filter { + std::vector<PatternPtr> m_patterns; + + bool matches( TestCaseInfo const& testCase ) const; + }; + + public: + bool hasFilters() const; + bool matches( TestCaseInfo const& testCase ) const; + + private: + std::vector<Filter> m_filters; + + friend class TestSpecParser; + }; +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +// end catch_test_spec.h +// start catch_interfaces_tag_alias_registry.h + +#include <string> + +namespace Catch { + + struct TagAlias; + + struct ITagAliasRegistry { + virtual ~ITagAliasRegistry(); + // Nullptr if not present + virtual TagAlias const* find( std::string const& alias ) const = 0; + virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const = 0; + + static ITagAliasRegistry const& get(); + }; + +} // end namespace Catch + +// end catch_interfaces_tag_alias_registry.h +namespace Catch { + + class TestSpecParser { + enum Mode{ None, Name, QuotedName, Tag, EscapedName }; + Mode m_mode = None; + bool m_exclusion = false; + std::size_t m_start = std::string::npos, m_pos = 0; + std::string m_arg; + std::vector<std::size_t> m_escapeChars; + TestSpec::Filter m_currentFilter; + TestSpec m_testSpec; + ITagAliasRegistry const* m_tagAliases = nullptr; + + public: + TestSpecParser( ITagAliasRegistry const& tagAliases ); + + TestSpecParser& parse( std::string const& arg ); + TestSpec testSpec(); + + private: + void visitChar( char c ); + void startNewMode( Mode mode, std::size_t start ); + void escape(); + std::string subString() const; + + template<typename T> + void addPattern() { + std::string token = subString(); + for( std::size_t i = 0; i < m_escapeChars.size(); ++i ) + token = token.substr( 0, m_escapeChars[i]-m_start-i ) + token.substr( m_escapeChars[i]-m_start-i+1 ); + m_escapeChars.clear(); + if( startsWith( token, "exclude:" ) ) { + m_exclusion = true; + token = token.substr( 8 ); + } + if( !token.empty() ) { + TestSpec::PatternPtr pattern = std::make_shared<T>( token ); + if( m_exclusion ) + pattern = std::make_shared<TestSpec::ExcludedPattern>( pattern ); + m_currentFilter.m_patterns.push_back( pattern ); + } + m_exclusion = false; + m_mode = None; + } + + void addFilter(); + }; + TestSpec parseTestSpec( std::string const& arg ); + +} // namespace Catch + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +// end catch_test_spec_parser.h +// start catch_interfaces_config.h + +#include <iosfwd> +#include <string> +#include <vector> +#include <memory> + +namespace Catch { + + enum class Verbosity { + Quiet = 0, + Normal, + High + }; + + struct WarnAbout { enum What { + Nothing = 0x00, + NoAssertions = 0x01, + NoTests = 0x02 + }; }; + + struct ShowDurations { enum OrNot { + DefaultForReporter, + Always, + Never + }; }; + struct RunTests { enum InWhatOrder { + InDeclarationOrder, + InLexicographicalOrder, + InRandomOrder + }; }; + struct UseColour { enum YesOrNo { + Auto, + Yes, + No + }; }; + struct WaitForKeypress { enum When { + Never, + BeforeStart = 1, + BeforeExit = 2, + BeforeStartAndExit = BeforeStart | BeforeExit + }; }; + + class TestSpec; + + struct IConfig : NonCopyable { + + virtual ~IConfig(); + + virtual bool allowThrows() const = 0; + virtual std::ostream& stream() const = 0; + virtual std::string name() const = 0; + virtual bool includeSuccessfulResults() const = 0; + virtual bool shouldDebugBreak() const = 0; + virtual bool warnAboutMissingAssertions() const = 0; + virtual bool warnAboutNoTests() const = 0; + virtual int abortAfter() const = 0; + virtual bool showInvisibles() const = 0; + virtual ShowDurations::OrNot showDurations() const = 0; + virtual TestSpec const& testSpec() const = 0; + virtual bool hasTestFilters() const = 0; + virtual RunTests::InWhatOrder runOrder() const = 0; + virtual unsigned int rngSeed() const = 0; + virtual int benchmarkResolutionMultiple() const = 0; + virtual UseColour::YesOrNo useColour() const = 0; + virtual std::vector<std::string> const& getSectionsToRun() const = 0; + virtual Verbosity verbosity() const = 0; + }; + + using IConfigPtr = std::shared_ptr<IConfig const>; +} + +// end catch_interfaces_config.h +// Libstdc++ doesn't like incomplete classes for unique_ptr + +#include <memory> +#include <vector> +#include <string> + +#ifndef CATCH_CONFIG_CONSOLE_WIDTH +#define CATCH_CONFIG_CONSOLE_WIDTH 80 +#endif + +namespace Catch { + + struct IStream; + + struct ConfigData { + bool listTests = false; + bool listTags = false; + bool listReporters = false; + bool listTestNamesOnly = false; + + bool showSuccessfulTests = false; + bool shouldDebugBreak = false; + bool noThrow = false; + bool showHelp = false; + bool showInvisibles = false; + bool filenamesAsTags = false; + bool libIdentify = false; + + int abortAfter = -1; + unsigned int rngSeed = 0; + int benchmarkResolutionMultiple = 100; + + Verbosity verbosity = Verbosity::Normal; + WarnAbout::What warnings = WarnAbout::Nothing; + ShowDurations::OrNot showDurations = ShowDurations::DefaultForReporter; + RunTests::InWhatOrder runOrder = RunTests::InDeclarationOrder; + UseColour::YesOrNo useColour = UseColour::Auto; + WaitForKeypress::When waitForKeypress = WaitForKeypress::Never; + + std::string outputFilename; + std::string name; + std::string processName; +#ifndef CATCH_CONFIG_DEFAULT_REPORTER +#define CATCH_CONFIG_DEFAULT_REPORTER "console" +#endif + std::string reporterName = CATCH_CONFIG_DEFAULT_REPORTER; +#undef CATCH_CONFIG_DEFAULT_REPORTER + + std::vector<std::string> testsOrTags; + std::vector<std::string> sectionsToRun; + }; + + class Config : public IConfig { + public: + + Config() = default; + Config( ConfigData const& data ); + virtual ~Config() = default; + + std::string const& getFilename() const; + + bool listTests() const; + bool listTestNamesOnly() const; + bool listTags() const; + bool listReporters() const; + + std::string getProcessName() const; + std::string const& getReporterName() const; + + std::vector<std::string> const& getTestsOrTags() const; + std::vector<std::string> const& getSectionsToRun() const override; + + virtual TestSpec const& testSpec() const override; + bool hasTestFilters() const override; + + bool showHelp() const; + + // IConfig interface + bool allowThrows() const override; + std::ostream& stream() const override; + std::string name() const override; + bool includeSuccessfulResults() const override; + bool warnAboutMissingAssertions() const override; + bool warnAboutNoTests() const override; + ShowDurations::OrNot showDurations() const override; + RunTests::InWhatOrder runOrder() const override; + unsigned int rngSeed() const override; + int benchmarkResolutionMultiple() const override; + UseColour::YesOrNo useColour() const override; + bool shouldDebugBreak() const override; + int abortAfter() const override; + bool showInvisibles() const override; + Verbosity verbosity() const override; + + private: + + IStream const* openStream(); + ConfigData m_data; + + std::unique_ptr<IStream const> m_stream; + TestSpec m_testSpec; + bool m_hasTestFilters = false; + }; + +} // end namespace Catch + +// end catch_config.hpp +// start catch_assertionresult.h + +#include <string> + +namespace Catch { + + struct AssertionResultData + { + AssertionResultData() = delete; + + AssertionResultData( ResultWas::OfType _resultType, LazyExpression const& _lazyExpression ); + + std::string message; + mutable std::string reconstructedExpression; + LazyExpression lazyExpression; + ResultWas::OfType resultType; + + std::string reconstructExpression() const; + }; + + class AssertionResult { + public: + AssertionResult() = delete; + AssertionResult( AssertionInfo const& info, AssertionResultData const& data ); + + bool isOk() const; + bool succeeded() const; + ResultWas::OfType getResultType() const; + bool hasExpression() const; + bool hasMessage() const; + std::string getExpression() const; + std::string getExpressionInMacro() const; + bool hasExpandedExpression() const; + std::string getExpandedExpression() const; + std::string getMessage() const; + SourceLineInfo getSourceInfo() const; + StringRef getTestMacroName() const; + + //protected: + AssertionInfo m_info; + AssertionResultData m_resultData; + }; + +} // end namespace Catch + +// end catch_assertionresult.h +// start catch_option.hpp + +namespace Catch { + + // An optional type + template<typename T> + class Option { + public: + Option() : nullableValue( nullptr ) {} + Option( T const& _value ) + : nullableValue( new( storage ) T( _value ) ) + {} + Option( Option const& _other ) + : nullableValue( _other ? new( storage ) T( *_other ) : nullptr ) + {} + + ~Option() { + reset(); + } + + Option& operator= ( Option const& _other ) { + if( &_other != this ) { + reset(); + if( _other ) + nullableValue = new( storage ) T( *_other ); + } + return *this; + } + Option& operator = ( T const& _value ) { + reset(); + nullableValue = new( storage ) T( _value ); + return *this; + } + + void reset() { + if( nullableValue ) + nullableValue->~T(); + nullableValue = nullptr; + } + + T& operator*() { return *nullableValue; } + T const& operator*() const { return *nullableValue; } + T* operator->() { return nullableValue; } + const T* operator->() const { return nullableValue; } + + T valueOr( T const& defaultValue ) const { + return nullableValue ? *nullableValue : defaultValue; + } + + bool some() const { return nullableValue != nullptr; } + bool none() const { return nullableValue == nullptr; } + + bool operator !() const { return nullableValue == nullptr; } + explicit operator bool() const { + return some(); + } + + private: + T *nullableValue; + alignas(alignof(T)) char storage[sizeof(T)]; + }; + +} // end namespace Catch + +// end catch_option.hpp +#include <string> +#include <iosfwd> +#include <map> +#include <set> +#include <memory> + +namespace Catch { + + struct ReporterConfig { + explicit ReporterConfig( IConfigPtr const& _fullConfig ); + + ReporterConfig( IConfigPtr const& _fullConfig, std::ostream& _stream ); + + std::ostream& stream() const; + IConfigPtr fullConfig() const; + + private: + std::ostream* m_stream; + IConfigPtr m_fullConfig; + }; + + struct ReporterPreferences { + bool shouldRedirectStdOut = false; + bool shouldReportAllAssertions = false; + }; + + template<typename T> + struct LazyStat : Option<T> { + LazyStat& operator=( T const& _value ) { + Option<T>::operator=( _value ); + used = false; + return *this; + } + void reset() { + Option<T>::reset(); + used = false; + } + bool used = false; + }; + + struct TestRunInfo { + TestRunInfo( std::string const& _name ); + std::string name; + }; + struct GroupInfo { + GroupInfo( std::string const& _name, + std::size_t _groupIndex, + std::size_t _groupsCount ); + + std::string name; + std::size_t groupIndex; + std::size_t groupsCounts; + }; + + struct AssertionStats { + AssertionStats( AssertionResult const& _assertionResult, + std::vector<MessageInfo> const& _infoMessages, + Totals const& _totals ); + + AssertionStats( AssertionStats const& ) = default; + AssertionStats( AssertionStats && ) = default; + AssertionStats& operator = ( AssertionStats const& ) = default; + AssertionStats& operator = ( AssertionStats && ) = default; + virtual ~AssertionStats(); + + AssertionResult assertionResult; + std::vector<MessageInfo> infoMessages; + Totals totals; + }; + + struct SectionStats { + SectionStats( SectionInfo const& _sectionInfo, + Counts const& _assertions, + double _durationInSeconds, + bool _missingAssertions ); + SectionStats( SectionStats const& ) = default; + SectionStats( SectionStats && ) = default; + SectionStats& operator = ( SectionStats const& ) = default; + SectionStats& operator = ( SectionStats && ) = default; + virtual ~SectionStats(); + + SectionInfo sectionInfo; + Counts assertions; + double durationInSeconds; + bool missingAssertions; + }; + + struct TestCaseStats { + TestCaseStats( TestCaseInfo const& _testInfo, + Totals const& _totals, + std::string const& _stdOut, + std::string const& _stdErr, + bool _aborting ); + + TestCaseStats( TestCaseStats const& ) = default; + TestCaseStats( TestCaseStats && ) = default; + TestCaseStats& operator = ( TestCaseStats const& ) = default; + TestCaseStats& operator = ( TestCaseStats && ) = default; + virtual ~TestCaseStats(); + + TestCaseInfo testInfo; + Totals totals; + std::string stdOut; + std::string stdErr; + bool aborting; + }; + + struct TestGroupStats { + TestGroupStats( GroupInfo const& _groupInfo, + Totals const& _totals, + bool _aborting ); + TestGroupStats( GroupInfo const& _groupInfo ); + + TestGroupStats( TestGroupStats const& ) = default; + TestGroupStats( TestGroupStats && ) = default; + TestGroupStats& operator = ( TestGroupStats const& ) = default; + TestGroupStats& operator = ( TestGroupStats && ) = default; + virtual ~TestGroupStats(); + + GroupInfo groupInfo; + Totals totals; + bool aborting; + }; + + struct TestRunStats { + TestRunStats( TestRunInfo const& _runInfo, + Totals const& _totals, + bool _aborting ); + + TestRunStats( TestRunStats const& ) = default; + TestRunStats( TestRunStats && ) = default; + TestRunStats& operator = ( TestRunStats const& ) = default; + TestRunStats& operator = ( TestRunStats && ) = default; + virtual ~TestRunStats(); + + TestRunInfo runInfo; + Totals totals; + bool aborting; + }; + + struct BenchmarkInfo { + std::string name; + }; + struct BenchmarkStats { + BenchmarkInfo info; + std::size_t iterations; + uint64_t elapsedTimeInNanoseconds; + }; + + struct IStreamingReporter { + virtual ~IStreamingReporter() = default; + + // Implementing class must also provide the following static methods: + // static std::string getDescription(); + // static std::set<Verbosity> getSupportedVerbosities() + + virtual ReporterPreferences getPreferences() const = 0; + + virtual void noMatchingTestCases( std::string const& spec ) = 0; + + virtual void testRunStarting( TestRunInfo const& testRunInfo ) = 0; + virtual void testGroupStarting( GroupInfo const& groupInfo ) = 0; + + virtual void testCaseStarting( TestCaseInfo const& testInfo ) = 0; + virtual void sectionStarting( SectionInfo const& sectionInfo ) = 0; + + // *** experimental *** + virtual void benchmarkStarting( BenchmarkInfo const& ) {} + + virtual void assertionStarting( AssertionInfo const& assertionInfo ) = 0; + + // The return value indicates if the messages buffer should be cleared: + virtual bool assertionEnded( AssertionStats const& assertionStats ) = 0; + + // *** experimental *** + virtual void benchmarkEnded( BenchmarkStats const& ) {} + + virtual void sectionEnded( SectionStats const& sectionStats ) = 0; + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) = 0; + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) = 0; + virtual void testRunEnded( TestRunStats const& testRunStats ) = 0; + + virtual void skipTest( TestCaseInfo const& testInfo ) = 0; + + // Default empty implementation provided + virtual void fatalErrorEncountered( StringRef name ); + + virtual bool isMulti() const; + }; + using IStreamingReporterPtr = std::unique_ptr<IStreamingReporter>; + + struct IReporterFactory { + virtual ~IReporterFactory(); + virtual IStreamingReporterPtr create( ReporterConfig const& config ) const = 0; + virtual std::string getDescription() const = 0; + }; + using IReporterFactoryPtr = std::shared_ptr<IReporterFactory>; + + struct IReporterRegistry { + using FactoryMap = std::map<std::string, IReporterFactoryPtr>; + using Listeners = std::vector<IReporterFactoryPtr>; + + virtual ~IReporterRegistry(); + virtual IStreamingReporterPtr create( std::string const& name, IConfigPtr const& config ) const = 0; + virtual FactoryMap const& getFactories() const = 0; + virtual Listeners const& getListeners() const = 0; + }; + +} // end namespace Catch + +// end catch_interfaces_reporter.h +#include <algorithm> +#include <cstring> +#include <cfloat> +#include <cstdio> +#include <cassert> +#include <memory> +#include <ostream> + +namespace Catch { + void prepareExpandedExpression(AssertionResult& result); + + // Returns double formatted as %.3f (format expected on output) + std::string getFormattedDuration( double duration ); + + template<typename DerivedT> + struct StreamingReporterBase : IStreamingReporter { + + StreamingReporterBase( ReporterConfig const& _config ) + : m_config( _config.fullConfig() ), + stream( _config.stream() ) + { + m_reporterPrefs.shouldRedirectStdOut = false; + if( !DerivedT::getSupportedVerbosities().count( m_config->verbosity() ) ) + CATCH_ERROR( "Verbosity level not supported by this reporter" ); + } + + ReporterPreferences getPreferences() const override { + return m_reporterPrefs; + } + + static std::set<Verbosity> getSupportedVerbosities() { + return { Verbosity::Normal }; + } + + ~StreamingReporterBase() override = default; + + void noMatchingTestCases(std::string const&) override {} + + void testRunStarting(TestRunInfo const& _testRunInfo) override { + currentTestRunInfo = _testRunInfo; + } + void testGroupStarting(GroupInfo const& _groupInfo) override { + currentGroupInfo = _groupInfo; + } + + void testCaseStarting(TestCaseInfo const& _testInfo) override { + currentTestCaseInfo = _testInfo; + } + void sectionStarting(SectionInfo const& _sectionInfo) override { + m_sectionStack.push_back(_sectionInfo); + } + + void sectionEnded(SectionStats const& /* _sectionStats */) override { + m_sectionStack.pop_back(); + } + void testCaseEnded(TestCaseStats const& /* _testCaseStats */) override { + currentTestCaseInfo.reset(); + } + void testGroupEnded(TestGroupStats const& /* _testGroupStats */) override { + currentGroupInfo.reset(); + } + void testRunEnded(TestRunStats const& /* _testRunStats */) override { + currentTestCaseInfo.reset(); + currentGroupInfo.reset(); + currentTestRunInfo.reset(); + } + + void skipTest(TestCaseInfo const&) override { + // Don't do anything with this by default. + // It can optionally be overridden in the derived class. + } + + IConfigPtr m_config; + std::ostream& stream; + + LazyStat<TestRunInfo> currentTestRunInfo; + LazyStat<GroupInfo> currentGroupInfo; + LazyStat<TestCaseInfo> currentTestCaseInfo; + + std::vector<SectionInfo> m_sectionStack; + ReporterPreferences m_reporterPrefs; + }; + + template<typename DerivedT> + struct CumulativeReporterBase : IStreamingReporter { + template<typename T, typename ChildNodeT> + struct Node { + explicit Node( T const& _value ) : value( _value ) {} + virtual ~Node() {} + + using ChildNodes = std::vector<std::shared_ptr<ChildNodeT>>; + T value; + ChildNodes children; + }; + struct SectionNode { + explicit SectionNode(SectionStats const& _stats) : stats(_stats) {} + virtual ~SectionNode() = default; + + bool operator == (SectionNode const& other) const { + return stats.sectionInfo.lineInfo == other.stats.sectionInfo.lineInfo; + } + bool operator == (std::shared_ptr<SectionNode> const& other) const { + return operator==(*other); + } + + SectionStats stats; + using ChildSections = std::vector<std::shared_ptr<SectionNode>>; + using Assertions = std::vector<AssertionStats>; + ChildSections childSections; + Assertions assertions; + std::string stdOut; + std::string stdErr; + }; + + struct BySectionInfo { + BySectionInfo( SectionInfo const& other ) : m_other( other ) {} + BySectionInfo( BySectionInfo const& other ) : m_other( other.m_other ) {} + bool operator() (std::shared_ptr<SectionNode> const& node) const { + return ((node->stats.sectionInfo.name == m_other.name) && + (node->stats.sectionInfo.lineInfo == m_other.lineInfo)); + } + void operator=(BySectionInfo const&) = delete; + + private: + SectionInfo const& m_other; + }; + + using TestCaseNode = Node<TestCaseStats, SectionNode>; + using TestGroupNode = Node<TestGroupStats, TestCaseNode>; + using TestRunNode = Node<TestRunStats, TestGroupNode>; + + CumulativeReporterBase( ReporterConfig const& _config ) + : m_config( _config.fullConfig() ), + stream( _config.stream() ) + { + m_reporterPrefs.shouldRedirectStdOut = false; + if( !DerivedT::getSupportedVerbosities().count( m_config->verbosity() ) ) + CATCH_ERROR( "Verbosity level not supported by this reporter" ); + } + ~CumulativeReporterBase() override = default; + + ReporterPreferences getPreferences() const override { + return m_reporterPrefs; + } + + static std::set<Verbosity> getSupportedVerbosities() { + return { Verbosity::Normal }; + } + + void testRunStarting( TestRunInfo const& ) override {} + void testGroupStarting( GroupInfo const& ) override {} + + void testCaseStarting( TestCaseInfo const& ) override {} + + void sectionStarting( SectionInfo const& sectionInfo ) override { + SectionStats incompleteStats( sectionInfo, Counts(), 0, false ); + std::shared_ptr<SectionNode> node; + if( m_sectionStack.empty() ) { + if( !m_rootSection ) + m_rootSection = std::make_shared<SectionNode>( incompleteStats ); + node = m_rootSection; + } + else { + SectionNode& parentNode = *m_sectionStack.back(); + auto it = + std::find_if( parentNode.childSections.begin(), + parentNode.childSections.end(), + BySectionInfo( sectionInfo ) ); + if( it == parentNode.childSections.end() ) { + node = std::make_shared<SectionNode>( incompleteStats ); + parentNode.childSections.push_back( node ); + } + else + node = *it; + } + m_sectionStack.push_back( node ); + m_deepestSection = std::move(node); + } + + void assertionStarting(AssertionInfo const&) override {} + + bool assertionEnded(AssertionStats const& assertionStats) override { + assert(!m_sectionStack.empty()); + // AssertionResult holds a pointer to a temporary DecomposedExpression, + // which getExpandedExpression() calls to build the expression string. + // Our section stack copy of the assertionResult will likely outlive the + // temporary, so it must be expanded or discarded now to avoid calling + // a destroyed object later. + prepareExpandedExpression(const_cast<AssertionResult&>( assertionStats.assertionResult ) ); + SectionNode& sectionNode = *m_sectionStack.back(); + sectionNode.assertions.push_back(assertionStats); + return true; + } + void sectionEnded(SectionStats const& sectionStats) override { + assert(!m_sectionStack.empty()); + SectionNode& node = *m_sectionStack.back(); + node.stats = sectionStats; + m_sectionStack.pop_back(); + } + void testCaseEnded(TestCaseStats const& testCaseStats) override { + auto node = std::make_shared<TestCaseNode>(testCaseStats); + assert(m_sectionStack.size() == 0); + node->children.push_back(m_rootSection); + m_testCases.push_back(node); + m_rootSection.reset(); + + assert(m_deepestSection); + m_deepestSection->stdOut = testCaseStats.stdOut; + m_deepestSection->stdErr = testCaseStats.stdErr; + } + void testGroupEnded(TestGroupStats const& testGroupStats) override { + auto node = std::make_shared<TestGroupNode>(testGroupStats); + node->children.swap(m_testCases); + m_testGroups.push_back(node); + } + void testRunEnded(TestRunStats const& testRunStats) override { + auto node = std::make_shared<TestRunNode>(testRunStats); + node->children.swap(m_testGroups); + m_testRuns.push_back(node); + testRunEndedCumulative(); + } + virtual void testRunEndedCumulative() = 0; + + void skipTest(TestCaseInfo const&) override {} + + IConfigPtr m_config; + std::ostream& stream; + std::vector<AssertionStats> m_assertions; + std::vector<std::vector<std::shared_ptr<SectionNode>>> m_sections; + std::vector<std::shared_ptr<TestCaseNode>> m_testCases; + std::vector<std::shared_ptr<TestGroupNode>> m_testGroups; + + std::vector<std::shared_ptr<TestRunNode>> m_testRuns; + + std::shared_ptr<SectionNode> m_rootSection; + std::shared_ptr<SectionNode> m_deepestSection; + std::vector<std::shared_ptr<SectionNode>> m_sectionStack; + ReporterPreferences m_reporterPrefs; + }; + + template<char C> + char const* getLineOfChars() { + static char line[CATCH_CONFIG_CONSOLE_WIDTH] = {0}; + if( !*line ) { + std::memset( line, C, CATCH_CONFIG_CONSOLE_WIDTH-1 ); + line[CATCH_CONFIG_CONSOLE_WIDTH-1] = 0; + } + return line; + } + + struct TestEventListenerBase : StreamingReporterBase<TestEventListenerBase> { + TestEventListenerBase( ReporterConfig const& _config ); + + static std::set<Verbosity> getSupportedVerbosities(); + + void assertionStarting(AssertionInfo const&) override; + bool assertionEnded(AssertionStats const&) override; + }; + +} // end namespace Catch + +// end catch_reporter_bases.hpp +// start catch_console_colour.h + +namespace Catch { + + struct Colour { + enum Code { + None = 0, + + White, + Red, + Green, + Blue, + Cyan, + Yellow, + Grey, + + Bright = 0x10, + + BrightRed = Bright | Red, + BrightGreen = Bright | Green, + LightGrey = Bright | Grey, + BrightWhite = Bright | White, + BrightYellow = Bright | Yellow, + + // By intention + FileName = LightGrey, + Warning = BrightYellow, + ResultError = BrightRed, + ResultSuccess = BrightGreen, + ResultExpectedFailure = Warning, + + Error = BrightRed, + Success = Green, + + OriginalExpression = Cyan, + ReconstructedExpression = BrightYellow, + + SecondaryText = LightGrey, + Headers = White + }; + + // Use constructed object for RAII guard + Colour( Code _colourCode ); + Colour( Colour&& other ) noexcept; + Colour& operator=( Colour&& other ) noexcept; + ~Colour(); + + // Use static method for one-shot changes + static void use( Code _colourCode ); + + private: + bool m_moved = false; + }; + + std::ostream& operator << ( std::ostream& os, Colour const& ); + +} // end namespace Catch + +// end catch_console_colour.h +// start catch_reporter_registrars.hpp + + +namespace Catch { + + template<typename T> + class ReporterRegistrar { + + class ReporterFactory : public IReporterFactory { + + virtual IStreamingReporterPtr create( ReporterConfig const& config ) const override { + return std::unique_ptr<T>( new T( config ) ); + } + + virtual std::string getDescription() const override { + return T::getDescription(); + } + }; + + public: + + explicit ReporterRegistrar( std::string const& name ) { + getMutableRegistryHub().registerReporter( name, std::make_shared<ReporterFactory>() ); + } + }; + + template<typename T> + class ListenerRegistrar { + + class ListenerFactory : public IReporterFactory { + + virtual IStreamingReporterPtr create( ReporterConfig const& config ) const override { + return std::unique_ptr<T>( new T( config ) ); + } + virtual std::string getDescription() const override { + return std::string(); + } + }; + + public: + + ListenerRegistrar() { + getMutableRegistryHub().registerListener( std::make_shared<ListenerFactory>() ); + } + }; +} + +#if !defined(CATCH_CONFIG_DISABLE) + +#define CATCH_REGISTER_REPORTER( name, reporterType ) \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::ReporterRegistrar<reporterType> catch_internal_RegistrarFor##reporterType( name ); } \ + CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS + +#define CATCH_REGISTER_LISTENER( listenerType ) \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::ListenerRegistrar<listenerType> catch_internal_RegistrarFor##listenerType; } \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS +#else // CATCH_CONFIG_DISABLE + +#define CATCH_REGISTER_REPORTER(name, reporterType) +#define CATCH_REGISTER_LISTENER(listenerType) + +#endif // CATCH_CONFIG_DISABLE + +// end catch_reporter_registrars.hpp +// Allow users to base their work off existing reporters +// start catch_reporter_compact.h + +namespace Catch { + + struct CompactReporter : StreamingReporterBase<CompactReporter> { + + using StreamingReporterBase::StreamingReporterBase; + + ~CompactReporter() override; + + static std::string getDescription(); + + ReporterPreferences getPreferences() const override; + + void noMatchingTestCases(std::string const& spec) override; + + void assertionStarting(AssertionInfo const&) override; + + bool assertionEnded(AssertionStats const& _assertionStats) override; + + void sectionEnded(SectionStats const& _sectionStats) override; + + void testRunEnded(TestRunStats const& _testRunStats) override; + + }; + +} // end namespace Catch + +// end catch_reporter_compact.h +// start catch_reporter_console.h + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable:4061) // Not all labels are EXPLICITLY handled in switch + // Note that 4062 (not all labels are handled + // and default is missing) is enabled +#endif + +namespace Catch { + // Fwd decls + struct SummaryColumn; + class TablePrinter; + + struct ConsoleReporter : StreamingReporterBase<ConsoleReporter> { + std::unique_ptr<TablePrinter> m_tablePrinter; + + ConsoleReporter(ReporterConfig const& config); + ~ConsoleReporter() override; + static std::string getDescription(); + + void noMatchingTestCases(std::string const& spec) override; + + void assertionStarting(AssertionInfo const&) override; + + bool assertionEnded(AssertionStats const& _assertionStats) override; + + void sectionStarting(SectionInfo const& _sectionInfo) override; + void sectionEnded(SectionStats const& _sectionStats) override; + + void benchmarkStarting(BenchmarkInfo const& info) override; + void benchmarkEnded(BenchmarkStats const& stats) override; + + void testCaseEnded(TestCaseStats const& _testCaseStats) override; + void testGroupEnded(TestGroupStats const& _testGroupStats) override; + void testRunEnded(TestRunStats const& _testRunStats) override; + + private: + + void lazyPrint(); + + void lazyPrintWithoutClosingBenchmarkTable(); + void lazyPrintRunInfo(); + void lazyPrintGroupInfo(); + void printTestCaseAndSectionHeader(); + + void printClosedHeader(std::string const& _name); + void printOpenHeader(std::string const& _name); + + // if string has a : in first line will set indent to follow it on + // subsequent lines + void printHeaderString(std::string const& _string, std::size_t indent = 0); + + void printTotals(Totals const& totals); + void printSummaryRow(std::string const& label, std::vector<SummaryColumn> const& cols, std::size_t row); + + void printTotalsDivider(Totals const& totals); + void printSummaryDivider(); + + private: + bool m_headerPrinted = false; + }; + +} // end namespace Catch + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +// end catch_reporter_console.h +// start catch_reporter_junit.h + +// start catch_xmlwriter.h + +#include <vector> + +namespace Catch { + + class XmlEncode { + public: + enum ForWhat { ForTextNodes, ForAttributes }; + + XmlEncode( std::string const& str, ForWhat forWhat = ForTextNodes ); + + void encodeTo( std::ostream& os ) const; + + friend std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ); + + private: + std::string m_str; + ForWhat m_forWhat; + }; + + class XmlWriter { + public: + + class ScopedElement { + public: + ScopedElement( XmlWriter* writer ); + + ScopedElement( ScopedElement&& other ) noexcept; + ScopedElement& operator=( ScopedElement&& other ) noexcept; + + ~ScopedElement(); + + ScopedElement& writeText( std::string const& text, bool indent = true ); + + template<typename T> + ScopedElement& writeAttribute( std::string const& name, T const& attribute ) { + m_writer->writeAttribute( name, attribute ); + return *this; + } + + private: + mutable XmlWriter* m_writer = nullptr; + }; + + XmlWriter( std::ostream& os = Catch::cout() ); + ~XmlWriter(); + + XmlWriter( XmlWriter const& ) = delete; + XmlWriter& operator=( XmlWriter const& ) = delete; + + XmlWriter& startElement( std::string const& name ); + + ScopedElement scopedElement( std::string const& name ); + + XmlWriter& endElement(); + + XmlWriter& writeAttribute( std::string const& name, std::string const& attribute ); + + XmlWriter& writeAttribute( std::string const& name, bool attribute ); + + template<typename T> + XmlWriter& writeAttribute( std::string const& name, T const& attribute ) { + ReusableStringStream rss; + rss << attribute; + return writeAttribute( name, rss.str() ); + } + + XmlWriter& writeText( std::string const& text, bool indent = true ); + + XmlWriter& writeComment( std::string const& text ); + + void writeStylesheetRef( std::string const& url ); + + XmlWriter& writeBlankLine(); + + void ensureTagClosed(); + + private: + + void writeDeclaration(); + + void newlineIfNecessary(); + + bool m_tagIsOpen = false; + bool m_needsNewline = false; + std::vector<std::string> m_tags; + std::string m_indent; + std::ostream& m_os; + }; + +} + +// end catch_xmlwriter.h +namespace Catch { + + class JunitReporter : public CumulativeReporterBase<JunitReporter> { + public: + JunitReporter(ReporterConfig const& _config); + + ~JunitReporter() override; + + static std::string getDescription(); + + void noMatchingTestCases(std::string const& /*spec*/) override; + + void testRunStarting(TestRunInfo const& runInfo) override; + + void testGroupStarting(GroupInfo const& groupInfo) override; + + void testCaseStarting(TestCaseInfo const& testCaseInfo) override; + bool assertionEnded(AssertionStats const& assertionStats) override; + + void testCaseEnded(TestCaseStats const& testCaseStats) override; + + void testGroupEnded(TestGroupStats const& testGroupStats) override; + + void testRunEndedCumulative() override; + + void writeGroup(TestGroupNode const& groupNode, double suiteTime); + + void writeTestCase(TestCaseNode const& testCaseNode); + + void writeSection(std::string const& className, + std::string const& rootName, + SectionNode const& sectionNode); + + void writeAssertions(SectionNode const& sectionNode); + void writeAssertion(AssertionStats const& stats); + + XmlWriter xml; + Timer suiteTimer; + std::string stdOutForSuite; + std::string stdErrForSuite; + unsigned int unexpectedExceptions = 0; + bool m_okToFail = false; + }; + +} // end namespace Catch + +// end catch_reporter_junit.h +// start catch_reporter_xml.h + +namespace Catch { + class XmlReporter : public StreamingReporterBase<XmlReporter> { + public: + XmlReporter(ReporterConfig const& _config); + + ~XmlReporter() override; + + static std::string getDescription(); + + virtual std::string getStylesheetRef() const; + + void writeSourceInfo(SourceLineInfo const& sourceInfo); + + public: // StreamingReporterBase + + void noMatchingTestCases(std::string const& s) override; + + void testRunStarting(TestRunInfo const& testInfo) override; + + void testGroupStarting(GroupInfo const& groupInfo) override; + + void testCaseStarting(TestCaseInfo const& testInfo) override; + + void sectionStarting(SectionInfo const& sectionInfo) override; + + void assertionStarting(AssertionInfo const&) override; + + bool assertionEnded(AssertionStats const& assertionStats) override; + + void sectionEnded(SectionStats const& sectionStats) override; + + void testCaseEnded(TestCaseStats const& testCaseStats) override; + + void testGroupEnded(TestGroupStats const& testGroupStats) override; + + void testRunEnded(TestRunStats const& testRunStats) override; + + private: + Timer m_testCaseTimer; + XmlWriter m_xml; + int m_sectionDepth = 0; + }; + +} // end namespace Catch + +// end catch_reporter_xml.h + +// end catch_external_interfaces.h +#endif + +#endif // ! CATCH_CONFIG_IMPL_ONLY + +#ifdef CATCH_IMPL +// start catch_impl.hpp + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wweak-vtables" +#endif + +// Keep these here for external reporters +// start catch_test_case_tracker.h + +#include <string> +#include <vector> +#include <memory> + +namespace Catch { +namespace TestCaseTracking { + + struct NameAndLocation { + std::string name; + SourceLineInfo location; + + NameAndLocation( std::string const& _name, SourceLineInfo const& _location ); + }; + + struct ITracker; + + using ITrackerPtr = std::shared_ptr<ITracker>; + + struct ITracker { + virtual ~ITracker(); + + // static queries + virtual NameAndLocation const& nameAndLocation() const = 0; + + // dynamic queries + virtual bool isComplete() const = 0; // Successfully completed or failed + virtual bool isSuccessfullyCompleted() const = 0; + virtual bool isOpen() const = 0; // Started but not complete + virtual bool hasChildren() const = 0; + + virtual ITracker& parent() = 0; + + // actions + virtual void close() = 0; // Successfully complete + virtual void fail() = 0; + virtual void markAsNeedingAnotherRun() = 0; + + virtual void addChild( ITrackerPtr const& child ) = 0; + virtual ITrackerPtr findChild( NameAndLocation const& nameAndLocation ) = 0; + virtual void openChild() = 0; + + // Debug/ checking + virtual bool isSectionTracker() const = 0; + virtual bool isIndexTracker() const = 0; + }; + + class TrackerContext { + + enum RunState { + NotStarted, + Executing, + CompletedCycle + }; + + ITrackerPtr m_rootTracker; + ITracker* m_currentTracker = nullptr; + RunState m_runState = NotStarted; + + public: + + static TrackerContext& instance(); + + ITracker& startRun(); + void endRun(); + + void startCycle(); + void completeCycle(); + + bool completedCycle() const; + ITracker& currentTracker(); + void setCurrentTracker( ITracker* tracker ); + }; + + class TrackerBase : public ITracker { + protected: + enum CycleState { + NotStarted, + Executing, + ExecutingChildren, + NeedsAnotherRun, + CompletedSuccessfully, + Failed + }; + + using Children = std::vector<ITrackerPtr>; + NameAndLocation m_nameAndLocation; + TrackerContext& m_ctx; + ITracker* m_parent; + Children m_children; + CycleState m_runState = NotStarted; + + public: + TrackerBase( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ); + + NameAndLocation const& nameAndLocation() const override; + bool isComplete() const override; + bool isSuccessfullyCompleted() const override; + bool isOpen() const override; + bool hasChildren() const override; + + void addChild( ITrackerPtr const& child ) override; + + ITrackerPtr findChild( NameAndLocation const& nameAndLocation ) override; + ITracker& parent() override; + + void openChild() override; + + bool isSectionTracker() const override; + bool isIndexTracker() const override; + + void open(); + + void close() override; + void fail() override; + void markAsNeedingAnotherRun() override; + + private: + void moveToParent(); + void moveToThis(); + }; + + class SectionTracker : public TrackerBase { + std::vector<std::string> m_filters; + public: + SectionTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ); + + bool isSectionTracker() const override; + + static SectionTracker& acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation ); + + void tryOpen(); + + void addInitialFilters( std::vector<std::string> const& filters ); + void addNextFilters( std::vector<std::string> const& filters ); + }; + + class IndexTracker : public TrackerBase { + int m_size; + int m_index = -1; + public: + IndexTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent, int size ); + + bool isIndexTracker() const override; + void close() override; + + static IndexTracker& acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation, int size ); + + int index() const; + + void moveNext(); + }; + +} // namespace TestCaseTracking + +using TestCaseTracking::ITracker; +using TestCaseTracking::TrackerContext; +using TestCaseTracking::SectionTracker; +using TestCaseTracking::IndexTracker; + +} // namespace Catch + +// end catch_test_case_tracker.h + +// start catch_leak_detector.h + +namespace Catch { + + struct LeakDetector { + LeakDetector(); + ~LeakDetector(); + }; + +} +// end catch_leak_detector.h +// Cpp files will be included in the single-header file here +// start catch_approx.cpp + +#include <cmath> +#include <limits> + +namespace { + +// Performs equivalent check of std::fabs(lhs - rhs) <= margin +// But without the subtraction to allow for INFINITY in comparison +bool marginComparison(double lhs, double rhs, double margin) { + return (lhs + margin >= rhs) && (rhs + margin >= lhs); +} + +} + +namespace Catch { +namespace Detail { + + Approx::Approx ( double value ) + : m_epsilon( std::numeric_limits<float>::epsilon()*100 ), + m_margin( 0.0 ), + m_scale( 0.0 ), + m_value( value ) + {} + + Approx Approx::custom() { + return Approx( 0 ); + } + + Approx Approx::operator-() const { + auto temp(*this); + temp.m_value = -temp.m_value; + return temp; + } + + std::string Approx::toString() const { + ReusableStringStream rss; + rss << "Approx( " << ::Catch::Detail::stringify( m_value ) << " )"; + return rss.str(); + } + + bool Approx::equalityComparisonImpl(const double other) const { + // First try with fixed margin, then compute margin based on epsilon, scale and Approx's value + // Thanks to Richard Harris for his help refining the scaled margin value + return marginComparison(m_value, other, m_margin) || marginComparison(m_value, other, m_epsilon * (m_scale + std::fabs(m_value))); + } + + void Approx::setMargin(double margin) { + CATCH_ENFORCE(margin >= 0, + "Invalid Approx::margin: " << margin << '.' + << " Approx::Margin has to be non-negative."); + m_margin = margin; + } + + void Approx::setEpsilon(double epsilon) { + CATCH_ENFORCE(epsilon >= 0 && epsilon <= 1.0, + "Invalid Approx::epsilon: " << epsilon << '.' + << " Approx::epsilon has to be in [0, 1]"); + m_epsilon = epsilon; + } + +} // end namespace Detail + +namespace literals { + Detail::Approx operator "" _a(long double val) { + return Detail::Approx(val); + } + Detail::Approx operator "" _a(unsigned long long val) { + return Detail::Approx(val); + } +} // end namespace literals + +std::string StringMaker<Catch::Detail::Approx>::convert(Catch::Detail::Approx const& value) { + return value.toString(); +} + +} // end namespace Catch +// end catch_approx.cpp +// start catch_assertionhandler.cpp + +// start catch_context.h + +#include <memory> + +namespace Catch { + + struct IResultCapture; + struct IRunner; + struct IConfig; + struct IMutableContext; + + using IConfigPtr = std::shared_ptr<IConfig const>; + + struct IContext + { + virtual ~IContext(); + + virtual IResultCapture* getResultCapture() = 0; + virtual IRunner* getRunner() = 0; + virtual IConfigPtr const& getConfig() const = 0; + }; + + struct IMutableContext : IContext + { + virtual ~IMutableContext(); + virtual void setResultCapture( IResultCapture* resultCapture ) = 0; + virtual void setRunner( IRunner* runner ) = 0; + virtual void setConfig( IConfigPtr const& config ) = 0; + + private: + static IMutableContext *currentContext; + friend IMutableContext& getCurrentMutableContext(); + friend void cleanUpContext(); + static void createContext(); + }; + + inline IMutableContext& getCurrentMutableContext() + { + if( !IMutableContext::currentContext ) + IMutableContext::createContext(); + return *IMutableContext::currentContext; + } + + inline IContext& getCurrentContext() + { + return getCurrentMutableContext(); + } + + void cleanUpContext(); +} + +// end catch_context.h +// start catch_debugger.h + +namespace Catch { + bool isDebuggerActive(); +} + +#ifdef CATCH_PLATFORM_MAC + + #define CATCH_TRAP() __asm__("int $3\n" : : ) /* NOLINT */ + +#elif defined(CATCH_PLATFORM_LINUX) + // If we can use inline assembler, do it because this allows us to break + // directly at the location of the failing check instead of breaking inside + // raise() called from it, i.e. one stack frame below. + #if defined(__GNUC__) && (defined(__i386) || defined(__x86_64)) + #define CATCH_TRAP() asm volatile ("int $3") /* NOLINT */ + #else // Fall back to the generic way. + #include <signal.h> + + #define CATCH_TRAP() raise(SIGTRAP) + #endif +#elif defined(_MSC_VER) + #define CATCH_TRAP() __debugbreak() +#elif defined(__MINGW32__) + extern "C" __declspec(dllimport) void __stdcall DebugBreak(); + #define CATCH_TRAP() DebugBreak() +#endif + +#ifdef CATCH_TRAP + #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) { CATCH_TRAP(); } +#else + namespace Catch { + inline void doNothing() {} + } + #define CATCH_BREAK_INTO_DEBUGGER() Catch::doNothing() +#endif + +// end catch_debugger.h +// start catch_run_context.h + +// start catch_fatal_condition.h + +// start catch_windows_h_proxy.h + + +#if defined(CATCH_PLATFORM_WINDOWS) + +#if !defined(NOMINMAX) && !defined(CATCH_CONFIG_NO_NOMINMAX) +# define CATCH_DEFINED_NOMINMAX +# define NOMINMAX +#endif +#if !defined(WIN32_LEAN_AND_MEAN) && !defined(CATCH_CONFIG_NO_WIN32_LEAN_AND_MEAN) +# define CATCH_DEFINED_WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +#endif + +#ifdef __AFXDLL +#include <AfxWin.h> +#else +#include <windows.h> +#endif + +#ifdef CATCH_DEFINED_NOMINMAX +# undef NOMINMAX +#endif +#ifdef CATCH_DEFINED_WIN32_LEAN_AND_MEAN +# undef WIN32_LEAN_AND_MEAN +#endif + +#endif // defined(CATCH_PLATFORM_WINDOWS) + +// end catch_windows_h_proxy.h +#if defined( CATCH_CONFIG_WINDOWS_SEH ) + +namespace Catch { + + struct FatalConditionHandler { + + static LONG CALLBACK handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo); + FatalConditionHandler(); + static void reset(); + ~FatalConditionHandler(); + + private: + static bool isSet; + static ULONG guaranteeSize; + static PVOID exceptionHandlerHandle; + }; + +} // namespace Catch + +#elif defined ( CATCH_CONFIG_POSIX_SIGNALS ) + +#include <signal.h> + +namespace Catch { + + struct FatalConditionHandler { + + static bool isSet; + static struct sigaction oldSigActions[]; + static stack_t oldSigStack; + static char altStackMem[]; + + static void handleSignal( int sig ); + + FatalConditionHandler(); + ~FatalConditionHandler(); + static void reset(); + }; + +} // namespace Catch + +#else + +namespace Catch { + struct FatalConditionHandler { + void reset(); + }; +} + +#endif + +// end catch_fatal_condition.h +#include <string> + +namespace Catch { + + struct IMutableContext; + + /////////////////////////////////////////////////////////////////////////// + + class RunContext : public IResultCapture, public IRunner { + + public: + RunContext( RunContext const& ) = delete; + RunContext& operator =( RunContext const& ) = delete; + + explicit RunContext( IConfigPtr const& _config, IStreamingReporterPtr&& reporter ); + + ~RunContext() override; + + void testGroupStarting( std::string const& testSpec, std::size_t groupIndex, std::size_t groupsCount ); + void testGroupEnded( std::string const& testSpec, Totals const& totals, std::size_t groupIndex, std::size_t groupsCount ); + + Totals runTest(TestCase const& testCase); + + IConfigPtr config() const; + IStreamingReporter& reporter() const; + + public: // IResultCapture + + // Assertion handlers + void handleExpr + ( AssertionInfo const& info, + ITransientExpression const& expr, + AssertionReaction& reaction ) override; + void handleMessage + ( AssertionInfo const& info, + ResultWas::OfType resultType, + StringRef const& message, + AssertionReaction& reaction ) override; + void handleUnexpectedExceptionNotThrown + ( AssertionInfo const& info, + AssertionReaction& reaction ) override; + void handleUnexpectedInflightException + ( AssertionInfo const& info, + std::string const& message, + AssertionReaction& reaction ) override; + void handleIncomplete + ( AssertionInfo const& info ) override; + void handleNonExpr + ( AssertionInfo const &info, + ResultWas::OfType resultType, + AssertionReaction &reaction ) override; + + bool sectionStarted( SectionInfo const& sectionInfo, Counts& assertions ) override; + + void sectionEnded( SectionEndInfo const& endInfo ) override; + void sectionEndedEarly( SectionEndInfo const& endInfo ) override; + + auto acquireGeneratorTracker( SourceLineInfo const& lineInfo ) -> IGeneratorTracker& override; + + void benchmarkStarting( BenchmarkInfo const& info ) override; + void benchmarkEnded( BenchmarkStats const& stats ) override; + + void pushScopedMessage( MessageInfo const& message ) override; + void popScopedMessage( MessageInfo const& message ) override; + + std::string getCurrentTestName() const override; + + const AssertionResult* getLastResult() const override; + + void exceptionEarlyReported() override; + + void handleFatalErrorCondition( StringRef message ) override; + + bool lastAssertionPassed() override; + + void assertionPassed() override; + + public: + // !TBD We need to do this another way! + bool aborting() const final; + + private: + + void runCurrentTest( std::string& redirectedCout, std::string& redirectedCerr ); + void invokeActiveTestCase(); + + void resetAssertionInfo(); + bool testForMissingAssertions( Counts& assertions ); + + void assertionEnded( AssertionResult const& result ); + void reportExpr + ( AssertionInfo const &info, + ResultWas::OfType resultType, + ITransientExpression const *expr, + bool negated ); + + void populateReaction( AssertionReaction& reaction ); + + private: + + void handleUnfinishedSections(); + + TestRunInfo m_runInfo; + IMutableContext& m_context; + TestCase const* m_activeTestCase = nullptr; + ITracker* m_testCaseTracker; + Option<AssertionResult> m_lastResult; + + IConfigPtr m_config; + Totals m_totals; + IStreamingReporterPtr m_reporter; + std::vector<MessageInfo> m_messages; + AssertionInfo m_lastAssertionInfo; + std::vector<SectionEndInfo> m_unfinishedSections; + std::vector<ITracker*> m_activeSections; + TrackerContext m_trackerContext; + bool m_lastAssertionPassed = false; + bool m_shouldReportUnexpected = true; + bool m_includeSuccessfulResults; + }; + +} // end namespace Catch + +// end catch_run_context.h +namespace Catch { + + namespace { + auto operator <<( std::ostream& os, ITransientExpression const& expr ) -> std::ostream& { + expr.streamReconstructedExpression( os ); + return os; + } + } + + LazyExpression::LazyExpression( bool isNegated ) + : m_isNegated( isNegated ) + {} + + LazyExpression::LazyExpression( LazyExpression const& other ) : m_isNegated( other.m_isNegated ) {} + + LazyExpression::operator bool() const { + return m_transientExpression != nullptr; + } + + auto operator << ( std::ostream& os, LazyExpression const& lazyExpr ) -> std::ostream& { + if( lazyExpr.m_isNegated ) + os << "!"; + + if( lazyExpr ) { + if( lazyExpr.m_isNegated && lazyExpr.m_transientExpression->isBinaryExpression() ) + os << "(" << *lazyExpr.m_transientExpression << ")"; + else + os << *lazyExpr.m_transientExpression; + } + else { + os << "{** error - unchecked empty expression requested **}"; + } + return os; + } + + AssertionHandler::AssertionHandler + ( StringRef const& macroName, + SourceLineInfo const& lineInfo, + StringRef capturedExpression, + ResultDisposition::Flags resultDisposition ) + : m_assertionInfo{ macroName, lineInfo, capturedExpression, resultDisposition }, + m_resultCapture( getResultCapture() ) + {} + + void AssertionHandler::handleExpr( ITransientExpression const& expr ) { + m_resultCapture.handleExpr( m_assertionInfo, expr, m_reaction ); + } + void AssertionHandler::handleMessage(ResultWas::OfType resultType, StringRef const& message) { + m_resultCapture.handleMessage( m_assertionInfo, resultType, message, m_reaction ); + } + + auto AssertionHandler::allowThrows() const -> bool { + return getCurrentContext().getConfig()->allowThrows(); + } + + void AssertionHandler::complete() { + setCompleted(); + if( m_reaction.shouldDebugBreak ) { + + // If you find your debugger stopping you here then go one level up on the + // call-stack for the code that caused it (typically a failed assertion) + + // (To go back to the test and change execution, jump over the throw, next) + CATCH_BREAK_INTO_DEBUGGER(); + } + if (m_reaction.shouldThrow) { +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + throw Catch::TestFailureException(); +#else + CATCH_ERROR( "Test failure requires aborting test!" ); +#endif + } + } + void AssertionHandler::setCompleted() { + m_completed = true; + } + + void AssertionHandler::handleUnexpectedInflightException() { + m_resultCapture.handleUnexpectedInflightException( m_assertionInfo, Catch::translateActiveException(), m_reaction ); + } + + void AssertionHandler::handleExceptionThrownAsExpected() { + m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction); + } + void AssertionHandler::handleExceptionNotThrownAsExpected() { + m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction); + } + + void AssertionHandler::handleUnexpectedExceptionNotThrown() { + m_resultCapture.handleUnexpectedExceptionNotThrown( m_assertionInfo, m_reaction ); + } + + void AssertionHandler::handleThrowingCallSkipped() { + m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction); + } + + // This is the overload that takes a string and infers the Equals matcher from it + // The more general overload, that takes any string matcher, is in catch_capture_matchers.cpp + void handleExceptionMatchExpr( AssertionHandler& handler, std::string const& str, StringRef const& matcherString ) { + handleExceptionMatchExpr( handler, Matchers::Equals( str ), matcherString ); + } + +} // namespace Catch +// end catch_assertionhandler.cpp +// start catch_assertionresult.cpp + +namespace Catch { + AssertionResultData::AssertionResultData(ResultWas::OfType _resultType, LazyExpression const & _lazyExpression): + lazyExpression(_lazyExpression), + resultType(_resultType) {} + + std::string AssertionResultData::reconstructExpression() const { + + if( reconstructedExpression.empty() ) { + if( lazyExpression ) { + ReusableStringStream rss; + rss << lazyExpression; + reconstructedExpression = rss.str(); + } + } + return reconstructedExpression; + } + + AssertionResult::AssertionResult( AssertionInfo const& info, AssertionResultData const& data ) + : m_info( info ), + m_resultData( data ) + {} + + // Result was a success + bool AssertionResult::succeeded() const { + return Catch::isOk( m_resultData.resultType ); + } + + // Result was a success, or failure is suppressed + bool AssertionResult::isOk() const { + return Catch::isOk( m_resultData.resultType ) || shouldSuppressFailure( m_info.resultDisposition ); + } + + ResultWas::OfType AssertionResult::getResultType() const { + return m_resultData.resultType; + } + + bool AssertionResult::hasExpression() const { + return m_info.capturedExpression[0] != 0; + } + + bool AssertionResult::hasMessage() const { + return !m_resultData.message.empty(); + } + + std::string AssertionResult::getExpression() const { + if( isFalseTest( m_info.resultDisposition ) ) + return "!(" + m_info.capturedExpression + ")"; + else + return m_info.capturedExpression; + } + + std::string AssertionResult::getExpressionInMacro() const { + std::string expr; + if( m_info.macroName[0] == 0 ) + expr = m_info.capturedExpression; + else { + expr.reserve( m_info.macroName.size() + m_info.capturedExpression.size() + 4 ); + expr += m_info.macroName; + expr += "( "; + expr += m_info.capturedExpression; + expr += " )"; + } + return expr; + } + + bool AssertionResult::hasExpandedExpression() const { + return hasExpression() && getExpandedExpression() != getExpression(); + } + + std::string AssertionResult::getExpandedExpression() const { + std::string expr = m_resultData.reconstructExpression(); + return expr.empty() + ? getExpression() + : expr; + } + + std::string AssertionResult::getMessage() const { + return m_resultData.message; + } + SourceLineInfo AssertionResult::getSourceInfo() const { + return m_info.lineInfo; + } + + StringRef AssertionResult::getTestMacroName() const { + return m_info.macroName; + } + +} // end namespace Catch +// end catch_assertionresult.cpp +// start catch_benchmark.cpp + +namespace Catch { + + auto BenchmarkLooper::getResolution() -> uint64_t { + return getEstimatedClockResolution() * getCurrentContext().getConfig()->benchmarkResolutionMultiple(); + } + + void BenchmarkLooper::reportStart() { + getResultCapture().benchmarkStarting( { m_name } ); + } + auto BenchmarkLooper::needsMoreIterations() -> bool { + auto elapsed = m_timer.getElapsedNanoseconds(); + + // Exponentially increasing iterations until we're confident in our timer resolution + if( elapsed < m_resolution ) { + m_iterationsToRun *= 10; + return true; + } + + getResultCapture().benchmarkEnded( { { m_name }, m_count, elapsed } ); + return false; + } + +} // end namespace Catch +// end catch_benchmark.cpp +// start catch_capture_matchers.cpp + +namespace Catch { + + using StringMatcher = Matchers::Impl::MatcherBase<std::string>; + + // This is the general overload that takes a any string matcher + // There is another overload, in catch_assertionhandler.h/.cpp, that only takes a string and infers + // the Equals matcher (so the header does not mention matchers) + void handleExceptionMatchExpr( AssertionHandler& handler, StringMatcher const& matcher, StringRef const& matcherString ) { + std::string exceptionMessage = Catch::translateActiveException(); + MatchExpr<std::string, StringMatcher const&> expr( exceptionMessage, matcher, matcherString ); + handler.handleExpr( expr ); + } + +} // namespace Catch +// end catch_capture_matchers.cpp +// start catch_commandline.cpp + +// start catch_commandline.h + +// start catch_clara.h + +// Use Catch's value for console width (store Clara's off to the side, if present) +#ifdef CLARA_CONFIG_CONSOLE_WIDTH +#define CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH +#undef CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH +#endif +#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH CATCH_CONFIG_CONSOLE_WIDTH-1 + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wweak-vtables" +#pragma clang diagnostic ignored "-Wexit-time-destructors" +#pragma clang diagnostic ignored "-Wshadow" +#endif + +// start clara.hpp +// Copyright 2017 Two Blue Cubes Ltd. All rights reserved. +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// See https://github.com/philsquared/Clara for more details + +// Clara v1.1.5 + + +#ifndef CATCH_CLARA_CONFIG_CONSOLE_WIDTH +#define CATCH_CLARA_CONFIG_CONSOLE_WIDTH 80 +#endif + +#ifndef CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH +#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH CATCH_CLARA_CONFIG_CONSOLE_WIDTH +#endif + +#ifndef CLARA_CONFIG_OPTIONAL_TYPE +#ifdef __has_include +#if __has_include(<optional>) && __cplusplus >= 201703L +#include <optional> +#define CLARA_CONFIG_OPTIONAL_TYPE std::optional +#endif +#endif +#endif + +// ----------- #included from clara_textflow.hpp ----------- + +// TextFlowCpp +// +// A single-header library for wrapping and laying out basic text, by Phil Nash +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// This project is hosted at https://github.com/philsquared/textflowcpp + + +#include <cassert> +#include <ostream> +#include <sstream> +#include <vector> + +#ifndef CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH +#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH 80 +#endif + +namespace Catch { +namespace clara { +namespace TextFlow { + +inline auto isWhitespace(char c) -> bool { + static std::string chars = " \t\n\r"; + return chars.find(c) != std::string::npos; +} +inline auto isBreakableBefore(char c) -> bool { + static std::string chars = "[({<|"; + return chars.find(c) != std::string::npos; +} +inline auto isBreakableAfter(char c) -> bool { + static std::string chars = "])}>.,:;*+-=&/\\"; + return chars.find(c) != std::string::npos; +} + +class Columns; + +class Column { + std::vector<std::string> m_strings; + size_t m_width = CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH; + size_t m_indent = 0; + size_t m_initialIndent = std::string::npos; + +public: + class iterator { + friend Column; + + Column const& m_column; + size_t m_stringIndex = 0; + size_t m_pos = 0; + + size_t m_len = 0; + size_t m_end = 0; + bool m_suffix = false; + + iterator(Column const& column, size_t stringIndex) + : m_column(column), + m_stringIndex(stringIndex) {} + + auto line() const -> std::string const& { return m_column.m_strings[m_stringIndex]; } + + auto isBoundary(size_t at) const -> bool { + assert(at > 0); + assert(at <= line().size()); + + return at == line().size() || + (isWhitespace(line()[at]) && !isWhitespace(line()[at - 1])) || + isBreakableBefore(line()[at]) || + isBreakableAfter(line()[at - 1]); + } + + void calcLength() { + assert(m_stringIndex < m_column.m_strings.size()); + + m_suffix = false; + auto width = m_column.m_width - indent(); + m_end = m_pos; + while (m_end < line().size() && line()[m_end] != '\n') + ++m_end; + + if (m_end < m_pos + width) { + m_len = m_end - m_pos; + } else { + size_t len = width; + while (len > 0 && !isBoundary(m_pos + len)) + --len; + while (len > 0 && isWhitespace(line()[m_pos + len - 1])) + --len; + + if (len > 0) { + m_len = len; + } else { + m_suffix = true; + m_len = width - 1; + } + } + } + + auto indent() const -> size_t { + auto initial = m_pos == 0 && m_stringIndex == 0 ? m_column.m_initialIndent : std::string::npos; + return initial == std::string::npos ? m_column.m_indent : initial; + } + + auto addIndentAndSuffix(std::string const &plain) const -> std::string { + return std::string(indent(), ' ') + (m_suffix ? plain + "-" : plain); + } + + public: + using difference_type = std::ptrdiff_t; + using value_type = std::string; + using pointer = value_type * ; + using reference = value_type & ; + using iterator_category = std::forward_iterator_tag; + + explicit iterator(Column const& column) : m_column(column) { + assert(m_column.m_width > m_column.m_indent); + assert(m_column.m_initialIndent == std::string::npos || m_column.m_width > m_column.m_initialIndent); + calcLength(); + if (m_len == 0) + m_stringIndex++; // Empty string + } + + auto operator *() const -> std::string { + assert(m_stringIndex < m_column.m_strings.size()); + assert(m_pos <= m_end); + return addIndentAndSuffix(line().substr(m_pos, m_len)); + } + + auto operator ++() -> iterator& { + m_pos += m_len; + if (m_pos < line().size() && line()[m_pos] == '\n') + m_pos += 1; + else + while (m_pos < line().size() && isWhitespace(line()[m_pos])) + ++m_pos; + + if (m_pos == line().size()) { + m_pos = 0; + ++m_stringIndex; + } + if (m_stringIndex < m_column.m_strings.size()) + calcLength(); + return *this; + } + auto operator ++(int) -> iterator { + iterator prev(*this); + operator++(); + return prev; + } + + auto operator ==(iterator const& other) const -> bool { + return + m_pos == other.m_pos && + m_stringIndex == other.m_stringIndex && + &m_column == &other.m_column; + } + auto operator !=(iterator const& other) const -> bool { + return !operator==(other); + } + }; + using const_iterator = iterator; + + explicit Column(std::string const& text) { m_strings.push_back(text); } + + auto width(size_t newWidth) -> Column& { + assert(newWidth > 0); + m_width = newWidth; + return *this; + } + auto indent(size_t newIndent) -> Column& { + m_indent = newIndent; + return *this; + } + auto initialIndent(size_t newIndent) -> Column& { + m_initialIndent = newIndent; + return *this; + } + + auto width() const -> size_t { return m_width; } + auto begin() const -> iterator { return iterator(*this); } + auto end() const -> iterator { return { *this, m_strings.size() }; } + + inline friend std::ostream& operator << (std::ostream& os, Column const& col) { + bool first = true; + for (auto line : col) { + if (first) + first = false; + else + os << "\n"; + os << line; + } + return os; + } + + auto operator + (Column const& other)->Columns; + + auto toString() const -> std::string { + std::ostringstream oss; + oss << *this; + return oss.str(); + } +}; + +class Spacer : public Column { + +public: + explicit Spacer(size_t spaceWidth) : Column("") { + width(spaceWidth); + } +}; + +class Columns { + std::vector<Column> m_columns; + +public: + + class iterator { + friend Columns; + struct EndTag {}; + + std::vector<Column> const& m_columns; + std::vector<Column::iterator> m_iterators; + size_t m_activeIterators; + + iterator(Columns const& columns, EndTag) + : m_columns(columns.m_columns), + m_activeIterators(0) { + m_iterators.reserve(m_columns.size()); + + for (auto const& col : m_columns) + m_iterators.push_back(col.end()); + } + + public: + using difference_type = std::ptrdiff_t; + using value_type = std::string; + using pointer = value_type * ; + using reference = value_type & ; + using iterator_category = std::forward_iterator_tag; + + explicit iterator(Columns const& columns) + : m_columns(columns.m_columns), + m_activeIterators(m_columns.size()) { + m_iterators.reserve(m_columns.size()); + + for (auto const& col : m_columns) + m_iterators.push_back(col.begin()); + } + + auto operator ==(iterator const& other) const -> bool { + return m_iterators == other.m_iterators; + } + auto operator !=(iterator const& other) const -> bool { + return m_iterators != other.m_iterators; + } + auto operator *() const -> std::string { + std::string row, padding; + + for (size_t i = 0; i < m_columns.size(); ++i) { + auto width = m_columns[i].width(); + if (m_iterators[i] != m_columns[i].end()) { + std::string col = *m_iterators[i]; + row += padding + col; + if (col.size() < width) + padding = std::string(width - col.size(), ' '); + else + padding = ""; + } else { + padding += std::string(width, ' '); + } + } + return row; + } + auto operator ++() -> iterator& { + for (size_t i = 0; i < m_columns.size(); ++i) { + if (m_iterators[i] != m_columns[i].end()) + ++m_iterators[i]; + } + return *this; + } + auto operator ++(int) -> iterator { + iterator prev(*this); + operator++(); + return prev; + } + }; + using const_iterator = iterator; + + auto begin() const -> iterator { return iterator(*this); } + auto end() const -> iterator { return { *this, iterator::EndTag() }; } + + auto operator += (Column const& col) -> Columns& { + m_columns.push_back(col); + return *this; + } + auto operator + (Column const& col) -> Columns { + Columns combined = *this; + combined += col; + return combined; + } + + inline friend std::ostream& operator << (std::ostream& os, Columns const& cols) { + + bool first = true; + for (auto line : cols) { + if (first) + first = false; + else + os << "\n"; + os << line; + } + return os; + } + + auto toString() const -> std::string { + std::ostringstream oss; + oss << *this; + return oss.str(); + } +}; + +inline auto Column::operator + (Column const& other) -> Columns { + Columns cols; + cols += *this; + cols += other; + return cols; +} +} + +} +} + +// ----------- end of #include from clara_textflow.hpp ----------- +// ........... back in clara.hpp + +#include <string> +#include <memory> +#include <set> +#include <algorithm> + +#if !defined(CATCH_PLATFORM_WINDOWS) && ( defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) ) +#define CATCH_PLATFORM_WINDOWS +#endif + +namespace Catch { namespace clara { +namespace detail { + + // Traits for extracting arg and return type of lambdas (for single argument lambdas) + template<typename L> + struct UnaryLambdaTraits : UnaryLambdaTraits<decltype( &L::operator() )> {}; + + template<typename ClassT, typename ReturnT, typename... Args> + struct UnaryLambdaTraits<ReturnT( ClassT::* )( Args... ) const> { + static const bool isValid = false; + }; + + template<typename ClassT, typename ReturnT, typename ArgT> + struct UnaryLambdaTraits<ReturnT( ClassT::* )( ArgT ) const> { + static const bool isValid = true; + using ArgType = typename std::remove_const<typename std::remove_reference<ArgT>::type>::type; + using ReturnType = ReturnT; + }; + + class TokenStream; + + // Transport for raw args (copied from main args, or supplied via init list for testing) + class Args { + friend TokenStream; + std::string m_exeName; + std::vector<std::string> m_args; + + public: + Args( int argc, char const* const* argv ) + : m_exeName(argv[0]), + m_args(argv + 1, argv + argc) {} + + Args( std::initializer_list<std::string> args ) + : m_exeName( *args.begin() ), + m_args( args.begin()+1, args.end() ) + {} + + auto exeName() const -> std::string { + return m_exeName; + } + }; + + // Wraps a token coming from a token stream. These may not directly correspond to strings as a single string + // may encode an option + its argument if the : or = form is used + enum class TokenType { + Option, Argument + }; + struct Token { + TokenType type; + std::string token; + }; + + inline auto isOptPrefix( char c ) -> bool { + return c == '-' +#ifdef CATCH_PLATFORM_WINDOWS + || c == '/' +#endif + ; + } + + // Abstracts iterators into args as a stream of tokens, with option arguments uniformly handled + class TokenStream { + using Iterator = std::vector<std::string>::const_iterator; + Iterator it; + Iterator itEnd; + std::vector<Token> m_tokenBuffer; + + void loadBuffer() { + m_tokenBuffer.resize( 0 ); + + // Skip any empty strings + while( it != itEnd && it->empty() ) + ++it; + + if( it != itEnd ) { + auto const &next = *it; + if( isOptPrefix( next[0] ) ) { + auto delimiterPos = next.find_first_of( " :=" ); + if( delimiterPos != std::string::npos ) { + m_tokenBuffer.push_back( { TokenType::Option, next.substr( 0, delimiterPos ) } ); + m_tokenBuffer.push_back( { TokenType::Argument, next.substr( delimiterPos + 1 ) } ); + } else { + if( next[1] != '-' && next.size() > 2 ) { + std::string opt = "- "; + for( size_t i = 1; i < next.size(); ++i ) { + opt[1] = next[i]; + m_tokenBuffer.push_back( { TokenType::Option, opt } ); + } + } else { + m_tokenBuffer.push_back( { TokenType::Option, next } ); + } + } + } else { + m_tokenBuffer.push_back( { TokenType::Argument, next } ); + } + } + } + + public: + explicit TokenStream( Args const &args ) : TokenStream( args.m_args.begin(), args.m_args.end() ) {} + + TokenStream( Iterator it, Iterator itEnd ) : it( it ), itEnd( itEnd ) { + loadBuffer(); + } + + explicit operator bool() const { + return !m_tokenBuffer.empty() || it != itEnd; + } + + auto count() const -> size_t { return m_tokenBuffer.size() + (itEnd - it); } + + auto operator*() const -> Token { + assert( !m_tokenBuffer.empty() ); + return m_tokenBuffer.front(); + } + + auto operator->() const -> Token const * { + assert( !m_tokenBuffer.empty() ); + return &m_tokenBuffer.front(); + } + + auto operator++() -> TokenStream & { + if( m_tokenBuffer.size() >= 2 ) { + m_tokenBuffer.erase( m_tokenBuffer.begin() ); + } else { + if( it != itEnd ) + ++it; + loadBuffer(); + } + return *this; + } + }; + + class ResultBase { + public: + enum Type { + Ok, LogicError, RuntimeError + }; + + protected: + ResultBase( Type type ) : m_type( type ) {} + virtual ~ResultBase() = default; + + virtual void enforceOk() const = 0; + + Type m_type; + }; + + template<typename T> + class ResultValueBase : public ResultBase { + public: + auto value() const -> T const & { + enforceOk(); + return m_value; + } + + protected: + ResultValueBase( Type type ) : ResultBase( type ) {} + + ResultValueBase( ResultValueBase const &other ) : ResultBase( other ) { + if( m_type == ResultBase::Ok ) + new( &m_value ) T( other.m_value ); + } + + ResultValueBase( Type, T const &value ) : ResultBase( Ok ) { + new( &m_value ) T( value ); + } + + auto operator=( ResultValueBase const &other ) -> ResultValueBase & { + if( m_type == ResultBase::Ok ) + m_value.~T(); + ResultBase::operator=(other); + if( m_type == ResultBase::Ok ) + new( &m_value ) T( other.m_value ); + return *this; + } + + ~ResultValueBase() override { + if( m_type == Ok ) + m_value.~T(); + } + + union { + T m_value; + }; + }; + + template<> + class ResultValueBase<void> : public ResultBase { + protected: + using ResultBase::ResultBase; + }; + + template<typename T = void> + class BasicResult : public ResultValueBase<T> { + public: + template<typename U> + explicit BasicResult( BasicResult<U> const &other ) + : ResultValueBase<T>( other.type() ), + m_errorMessage( other.errorMessage() ) + { + assert( type() != ResultBase::Ok ); + } + + template<typename U> + static auto ok( U const &value ) -> BasicResult { return { ResultBase::Ok, value }; } + static auto ok() -> BasicResult { return { ResultBase::Ok }; } + static auto logicError( std::string const &message ) -> BasicResult { return { ResultBase::LogicError, message }; } + static auto runtimeError( std::string const &message ) -> BasicResult { return { ResultBase::RuntimeError, message }; } + + explicit operator bool() const { return m_type == ResultBase::Ok; } + auto type() const -> ResultBase::Type { return m_type; } + auto errorMessage() const -> std::string { return m_errorMessage; } + + protected: + void enforceOk() const override { + + // Errors shouldn't reach this point, but if they do + // the actual error message will be in m_errorMessage + assert( m_type != ResultBase::LogicError ); + assert( m_type != ResultBase::RuntimeError ); + if( m_type != ResultBase::Ok ) + std::abort(); + } + + std::string m_errorMessage; // Only populated if resultType is an error + + BasicResult( ResultBase::Type type, std::string const &message ) + : ResultValueBase<T>(type), + m_errorMessage(message) + { + assert( m_type != ResultBase::Ok ); + } + + using ResultValueBase<T>::ResultValueBase; + using ResultBase::m_type; + }; + + enum class ParseResultType { + Matched, NoMatch, ShortCircuitAll, ShortCircuitSame + }; + + class ParseState { + public: + + ParseState( ParseResultType type, TokenStream const &remainingTokens ) + : m_type(type), + m_remainingTokens( remainingTokens ) + {} + + auto type() const -> ParseResultType { return m_type; } + auto remainingTokens() const -> TokenStream { return m_remainingTokens; } + + private: + ParseResultType m_type; + TokenStream m_remainingTokens; + }; + + using Result = BasicResult<void>; + using ParserResult = BasicResult<ParseResultType>; + using InternalParseResult = BasicResult<ParseState>; + + struct HelpColumns { + std::string left; + std::string right; + }; + + template<typename T> + inline auto convertInto( std::string const &source, T& target ) -> ParserResult { + std::stringstream ss; + ss << source; + ss >> target; + if( ss.fail() ) + return ParserResult::runtimeError( "Unable to convert '" + source + "' to destination type" ); + else + return ParserResult::ok( ParseResultType::Matched ); + } + inline auto convertInto( std::string const &source, std::string& target ) -> ParserResult { + target = source; + return ParserResult::ok( ParseResultType::Matched ); + } + inline auto convertInto( std::string const &source, bool &target ) -> ParserResult { + std::string srcLC = source; + std::transform( srcLC.begin(), srcLC.end(), srcLC.begin(), []( char c ) { return static_cast<char>( ::tolower(c) ); } ); + if (srcLC == "y" || srcLC == "1" || srcLC == "true" || srcLC == "yes" || srcLC == "on") + target = true; + else if (srcLC == "n" || srcLC == "0" || srcLC == "false" || srcLC == "no" || srcLC == "off") + target = false; + else + return ParserResult::runtimeError( "Expected a boolean value but did not recognise: '" + source + "'" ); + return ParserResult::ok( ParseResultType::Matched ); + } +#ifdef CLARA_CONFIG_OPTIONAL_TYPE + template<typename T> + inline auto convertInto( std::string const &source, CLARA_CONFIG_OPTIONAL_TYPE<T>& target ) -> ParserResult { + T temp; + auto result = convertInto( source, temp ); + if( result ) + target = std::move(temp); + return result; + } +#endif // CLARA_CONFIG_OPTIONAL_TYPE + + struct NonCopyable { + NonCopyable() = default; + NonCopyable( NonCopyable const & ) = delete; + NonCopyable( NonCopyable && ) = delete; + NonCopyable &operator=( NonCopyable const & ) = delete; + NonCopyable &operator=( NonCopyable && ) = delete; + }; + + struct BoundRef : NonCopyable { + virtual ~BoundRef() = default; + virtual auto isContainer() const -> bool { return false; } + virtual auto isFlag() const -> bool { return false; } + }; + struct BoundValueRefBase : BoundRef { + virtual auto setValue( std::string const &arg ) -> ParserResult = 0; + }; + struct BoundFlagRefBase : BoundRef { + virtual auto setFlag( bool flag ) -> ParserResult = 0; + virtual auto isFlag() const -> bool { return true; } + }; + + template<typename T> + struct BoundValueRef : BoundValueRefBase { + T &m_ref; + + explicit BoundValueRef( T &ref ) : m_ref( ref ) {} + + auto setValue( std::string const &arg ) -> ParserResult override { + return convertInto( arg, m_ref ); + } + }; + + template<typename T> + struct BoundValueRef<std::vector<T>> : BoundValueRefBase { + std::vector<T> &m_ref; + + explicit BoundValueRef( std::vector<T> &ref ) : m_ref( ref ) {} + + auto isContainer() const -> bool override { return true; } + + auto setValue( std::string const &arg ) -> ParserResult override { + T temp; + auto result = convertInto( arg, temp ); + if( result ) + m_ref.push_back( temp ); + return result; + } + }; + + struct BoundFlagRef : BoundFlagRefBase { + bool &m_ref; + + explicit BoundFlagRef( bool &ref ) : m_ref( ref ) {} + + auto setFlag( bool flag ) -> ParserResult override { + m_ref = flag; + return ParserResult::ok( ParseResultType::Matched ); + } + }; + + template<typename ReturnType> + struct LambdaInvoker { + static_assert( std::is_same<ReturnType, ParserResult>::value, "Lambda must return void or clara::ParserResult" ); + + template<typename L, typename ArgType> + static auto invoke( L const &lambda, ArgType const &arg ) -> ParserResult { + return lambda( arg ); + } + }; + + template<> + struct LambdaInvoker<void> { + template<typename L, typename ArgType> + static auto invoke( L const &lambda, ArgType const &arg ) -> ParserResult { + lambda( arg ); + return ParserResult::ok( ParseResultType::Matched ); + } + }; + + template<typename ArgType, typename L> + inline auto invokeLambda( L const &lambda, std::string const &arg ) -> ParserResult { + ArgType temp{}; + auto result = convertInto( arg, temp ); + return !result + ? result + : LambdaInvoker<typename UnaryLambdaTraits<L>::ReturnType>::invoke( lambda, temp ); + } + + template<typename L> + struct BoundLambda : BoundValueRefBase { + L m_lambda; + + static_assert( UnaryLambdaTraits<L>::isValid, "Supplied lambda must take exactly one argument" ); + explicit BoundLambda( L const &lambda ) : m_lambda( lambda ) {} + + auto setValue( std::string const &arg ) -> ParserResult override { + return invokeLambda<typename UnaryLambdaTraits<L>::ArgType>( m_lambda, arg ); + } + }; + + template<typename L> + struct BoundFlagLambda : BoundFlagRefBase { + L m_lambda; + + static_assert( UnaryLambdaTraits<L>::isValid, "Supplied lambda must take exactly one argument" ); + static_assert( std::is_same<typename UnaryLambdaTraits<L>::ArgType, bool>::value, "flags must be boolean" ); + + explicit BoundFlagLambda( L const &lambda ) : m_lambda( lambda ) {} + + auto setFlag( bool flag ) -> ParserResult override { + return LambdaInvoker<typename UnaryLambdaTraits<L>::ReturnType>::invoke( m_lambda, flag ); + } + }; + + enum class Optionality { Optional, Required }; + + struct Parser; + + class ParserBase { + public: + virtual ~ParserBase() = default; + virtual auto validate() const -> Result { return Result::ok(); } + virtual auto parse( std::string const& exeName, TokenStream const &tokens) const -> InternalParseResult = 0; + virtual auto cardinality() const -> size_t { return 1; } + + auto parse( Args const &args ) const -> InternalParseResult { + return parse( args.exeName(), TokenStream( args ) ); + } + }; + + template<typename DerivedT> + class ComposableParserImpl : public ParserBase { + public: + template<typename T> + auto operator|( T const &other ) const -> Parser; + + template<typename T> + auto operator+( T const &other ) const -> Parser; + }; + + // Common code and state for Args and Opts + template<typename DerivedT> + class ParserRefImpl : public ComposableParserImpl<DerivedT> { + protected: + Optionality m_optionality = Optionality::Optional; + std::shared_ptr<BoundRef> m_ref; + std::string m_hint; + std::string m_description; + + explicit ParserRefImpl( std::shared_ptr<BoundRef> const &ref ) : m_ref( ref ) {} + + public: + template<typename T> + ParserRefImpl( T &ref, std::string const &hint ) + : m_ref( std::make_shared<BoundValueRef<T>>( ref ) ), + m_hint( hint ) + {} + + template<typename LambdaT> + ParserRefImpl( LambdaT const &ref, std::string const &hint ) + : m_ref( std::make_shared<BoundLambda<LambdaT>>( ref ) ), + m_hint(hint) + {} + + auto operator()( std::string const &description ) -> DerivedT & { + m_description = description; + return static_cast<DerivedT &>( *this ); + } + + auto optional() -> DerivedT & { + m_optionality = Optionality::Optional; + return static_cast<DerivedT &>( *this ); + }; + + auto required() -> DerivedT & { + m_optionality = Optionality::Required; + return static_cast<DerivedT &>( *this ); + }; + + auto isOptional() const -> bool { + return m_optionality == Optionality::Optional; + } + + auto cardinality() const -> size_t override { + if( m_ref->isContainer() ) + return 0; + else + return 1; + } + + auto hint() const -> std::string { return m_hint; } + }; + + class ExeName : public ComposableParserImpl<ExeName> { + std::shared_ptr<std::string> m_name; + std::shared_ptr<BoundValueRefBase> m_ref; + + template<typename LambdaT> + static auto makeRef(LambdaT const &lambda) -> std::shared_ptr<BoundValueRefBase> { + return std::make_shared<BoundLambda<LambdaT>>( lambda) ; + } + + public: + ExeName() : m_name( std::make_shared<std::string>( "<executable>" ) ) {} + + explicit ExeName( std::string &ref ) : ExeName() { + m_ref = std::make_shared<BoundValueRef<std::string>>( ref ); + } + + template<typename LambdaT> + explicit ExeName( LambdaT const& lambda ) : ExeName() { + m_ref = std::make_shared<BoundLambda<LambdaT>>( lambda ); + } + + // The exe name is not parsed out of the normal tokens, but is handled specially + auto parse( std::string const&, TokenStream const &tokens ) const -> InternalParseResult override { + return InternalParseResult::ok( ParseState( ParseResultType::NoMatch, tokens ) ); + } + + auto name() const -> std::string { return *m_name; } + auto set( std::string const& newName ) -> ParserResult { + + auto lastSlash = newName.find_last_of( "\\/" ); + auto filename = ( lastSlash == std::string::npos ) + ? newName + : newName.substr( lastSlash+1 ); + + *m_name = filename; + if( m_ref ) + return m_ref->setValue( filename ); + else + return ParserResult::ok( ParseResultType::Matched ); + } + }; + + class Arg : public ParserRefImpl<Arg> { + public: + using ParserRefImpl::ParserRefImpl; + + auto parse( std::string const &, TokenStream const &tokens ) const -> InternalParseResult override { + auto validationResult = validate(); + if( !validationResult ) + return InternalParseResult( validationResult ); + + auto remainingTokens = tokens; + auto const &token = *remainingTokens; + if( token.type != TokenType::Argument ) + return InternalParseResult::ok( ParseState( ParseResultType::NoMatch, remainingTokens ) ); + + assert( !m_ref->isFlag() ); + auto valueRef = static_cast<detail::BoundValueRefBase*>( m_ref.get() ); + + auto result = valueRef->setValue( remainingTokens->token ); + if( !result ) + return InternalParseResult( result ); + else + return InternalParseResult::ok( ParseState( ParseResultType::Matched, ++remainingTokens ) ); + } + }; + + inline auto normaliseOpt( std::string const &optName ) -> std::string { +#ifdef CATCH_PLATFORM_WINDOWS + if( optName[0] == '/' ) + return "-" + optName.substr( 1 ); + else +#endif + return optName; + } + + class Opt : public ParserRefImpl<Opt> { + protected: + std::vector<std::string> m_optNames; + + public: + template<typename LambdaT> + explicit Opt( LambdaT const &ref ) : ParserRefImpl( std::make_shared<BoundFlagLambda<LambdaT>>( ref ) ) {} + + explicit Opt( bool &ref ) : ParserRefImpl( std::make_shared<BoundFlagRef>( ref ) ) {} + + template<typename LambdaT> + Opt( LambdaT const &ref, std::string const &hint ) : ParserRefImpl( ref, hint ) {} + + template<typename T> + Opt( T &ref, std::string const &hint ) : ParserRefImpl( ref, hint ) {} + + auto operator[]( std::string const &optName ) -> Opt & { + m_optNames.push_back( optName ); + return *this; + } + + auto getHelpColumns() const -> std::vector<HelpColumns> { + std::ostringstream oss; + bool first = true; + for( auto const &opt : m_optNames ) { + if (first) + first = false; + else + oss << ", "; + oss << opt; + } + if( !m_hint.empty() ) + oss << " <" << m_hint << ">"; + return { { oss.str(), m_description } }; + } + + auto isMatch( std::string const &optToken ) const -> bool { + auto normalisedToken = normaliseOpt( optToken ); + for( auto const &name : m_optNames ) { + if( normaliseOpt( name ) == normalisedToken ) + return true; + } + return false; + } + + using ParserBase::parse; + + auto parse( std::string const&, TokenStream const &tokens ) const -> InternalParseResult override { + auto validationResult = validate(); + if( !validationResult ) + return InternalParseResult( validationResult ); + + auto remainingTokens = tokens; + if( remainingTokens && remainingTokens->type == TokenType::Option ) { + auto const &token = *remainingTokens; + if( isMatch(token.token ) ) { + if( m_ref->isFlag() ) { + auto flagRef = static_cast<detail::BoundFlagRefBase*>( m_ref.get() ); + auto result = flagRef->setFlag( true ); + if( !result ) + return InternalParseResult( result ); + if( result.value() == ParseResultType::ShortCircuitAll ) + return InternalParseResult::ok( ParseState( result.value(), remainingTokens ) ); + } else { + auto valueRef = static_cast<detail::BoundValueRefBase*>( m_ref.get() ); + ++remainingTokens; + if( !remainingTokens ) + return InternalParseResult::runtimeError( "Expected argument following " + token.token ); + auto const &argToken = *remainingTokens; + if( argToken.type != TokenType::Argument ) + return InternalParseResult::runtimeError( "Expected argument following " + token.token ); + auto result = valueRef->setValue( argToken.token ); + if( !result ) + return InternalParseResult( result ); + if( result.value() == ParseResultType::ShortCircuitAll ) + return InternalParseResult::ok( ParseState( result.value(), remainingTokens ) ); + } + return InternalParseResult::ok( ParseState( ParseResultType::Matched, ++remainingTokens ) ); + } + } + return InternalParseResult::ok( ParseState( ParseResultType::NoMatch, remainingTokens ) ); + } + + auto validate() const -> Result override { + if( m_optNames.empty() ) + return Result::logicError( "No options supplied to Opt" ); + for( auto const &name : m_optNames ) { + if( name.empty() ) + return Result::logicError( "Option name cannot be empty" ); +#ifdef CATCH_PLATFORM_WINDOWS + if( name[0] != '-' && name[0] != '/' ) + return Result::logicError( "Option name must begin with '-' or '/'" ); +#else + if( name[0] != '-' ) + return Result::logicError( "Option name must begin with '-'" ); +#endif + } + return ParserRefImpl::validate(); + } + }; + + struct Help : Opt { + Help( bool &showHelpFlag ) + : Opt([&]( bool flag ) { + showHelpFlag = flag; + return ParserResult::ok( ParseResultType::ShortCircuitAll ); + }) + { + static_cast<Opt &>( *this ) + ("display usage information") + ["-?"]["-h"]["--help"] + .optional(); + } + }; + + struct Parser : ParserBase { + + mutable ExeName m_exeName; + std::vector<Opt> m_options; + std::vector<Arg> m_args; + + auto operator|=( ExeName const &exeName ) -> Parser & { + m_exeName = exeName; + return *this; + } + + auto operator|=( Arg const &arg ) -> Parser & { + m_args.push_back(arg); + return *this; + } + + auto operator|=( Opt const &opt ) -> Parser & { + m_options.push_back(opt); + return *this; + } + + auto operator|=( Parser const &other ) -> Parser & { + m_options.insert(m_options.end(), other.m_options.begin(), other.m_options.end()); + m_args.insert(m_args.end(), other.m_args.begin(), other.m_args.end()); + return *this; + } + + template<typename T> + auto operator|( T const &other ) const -> Parser { + return Parser( *this ) |= other; + } + + // Forward deprecated interface with '+' instead of '|' + template<typename T> + auto operator+=( T const &other ) -> Parser & { return operator|=( other ); } + template<typename T> + auto operator+( T const &other ) const -> Parser { return operator|( other ); } + + auto getHelpColumns() const -> std::vector<HelpColumns> { + std::vector<HelpColumns> cols; + for (auto const &o : m_options) { + auto childCols = o.getHelpColumns(); + cols.insert( cols.end(), childCols.begin(), childCols.end() ); + } + return cols; + } + + void writeToStream( std::ostream &os ) const { + if (!m_exeName.name().empty()) { + os << "usage:\n" << " " << m_exeName.name() << " "; + bool required = true, first = true; + for( auto const &arg : m_args ) { + if (first) + first = false; + else + os << " "; + if( arg.isOptional() && required ) { + os << "["; + required = false; + } + os << "<" << arg.hint() << ">"; + if( arg.cardinality() == 0 ) + os << " ... "; + } + if( !required ) + os << "]"; + if( !m_options.empty() ) + os << " options"; + os << "\n\nwhere options are:" << std::endl; + } + + auto rows = getHelpColumns(); + size_t consoleWidth = CATCH_CLARA_CONFIG_CONSOLE_WIDTH; + size_t optWidth = 0; + for( auto const &cols : rows ) + optWidth = (std::max)(optWidth, cols.left.size() + 2); + + optWidth = (std::min)(optWidth, consoleWidth/2); + + for( auto const &cols : rows ) { + auto row = + TextFlow::Column( cols.left ).width( optWidth ).indent( 2 ) + + TextFlow::Spacer(4) + + TextFlow::Column( cols.right ).width( consoleWidth - 7 - optWidth ); + os << row << std::endl; + } + } + + friend auto operator<<( std::ostream &os, Parser const &parser ) -> std::ostream& { + parser.writeToStream( os ); + return os; + } + + auto validate() const -> Result override { + for( auto const &opt : m_options ) { + auto result = opt.validate(); + if( !result ) + return result; + } + for( auto const &arg : m_args ) { + auto result = arg.validate(); + if( !result ) + return result; + } + return Result::ok(); + } + + using ParserBase::parse; + + auto parse( std::string const& exeName, TokenStream const &tokens ) const -> InternalParseResult override { + + struct ParserInfo { + ParserBase const* parser = nullptr; + size_t count = 0; + }; + const size_t totalParsers = m_options.size() + m_args.size(); + assert( totalParsers < 512 ); + // ParserInfo parseInfos[totalParsers]; // <-- this is what we really want to do + ParserInfo parseInfos[512]; + + { + size_t i = 0; + for (auto const &opt : m_options) parseInfos[i++].parser = &opt; + for (auto const &arg : m_args) parseInfos[i++].parser = &arg; + } + + m_exeName.set( exeName ); + + auto result = InternalParseResult::ok( ParseState( ParseResultType::NoMatch, tokens ) ); + while( result.value().remainingTokens() ) { + bool tokenParsed = false; + + for( size_t i = 0; i < totalParsers; ++i ) { + auto& parseInfo = parseInfos[i]; + if( parseInfo.parser->cardinality() == 0 || parseInfo.count < parseInfo.parser->cardinality() ) { + result = parseInfo.parser->parse(exeName, result.value().remainingTokens()); + if (!result) + return result; + if (result.value().type() != ParseResultType::NoMatch) { + tokenParsed = true; + ++parseInfo.count; + break; + } + } + } + + if( result.value().type() == ParseResultType::ShortCircuitAll ) + return result; + if( !tokenParsed ) + return InternalParseResult::runtimeError( "Unrecognised token: " + result.value().remainingTokens()->token ); + } + // !TBD Check missing required options + return result; + } + }; + + template<typename DerivedT> + template<typename T> + auto ComposableParserImpl<DerivedT>::operator|( T const &other ) const -> Parser { + return Parser() | static_cast<DerivedT const &>( *this ) | other; + } +} // namespace detail + +// A Combined parser +using detail::Parser; + +// A parser for options +using detail::Opt; + +// A parser for arguments +using detail::Arg; + +// Wrapper for argc, argv from main() +using detail::Args; + +// Specifies the name of the executable +using detail::ExeName; + +// Convenience wrapper for option parser that specifies the help option +using detail::Help; + +// enum of result types from a parse +using detail::ParseResultType; + +// Result type for parser operation +using detail::ParserResult; + +}} // namespace Catch::clara + +// end clara.hpp +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +// Restore Clara's value for console width, if present +#ifdef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH +#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH +#undef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH +#endif + +// end catch_clara.h +namespace Catch { + + clara::Parser makeCommandLineParser( ConfigData& config ); + +} // end namespace Catch + +// end catch_commandline.h +#include <fstream> +#include <ctime> + +namespace Catch { + + clara::Parser makeCommandLineParser( ConfigData& config ) { + + using namespace clara; + + auto const setWarning = [&]( std::string const& warning ) { + auto warningSet = [&]() { + if( warning == "NoAssertions" ) + return WarnAbout::NoAssertions; + + if ( warning == "NoTests" ) + return WarnAbout::NoTests; + + return WarnAbout::Nothing; + }(); + + if (warningSet == WarnAbout::Nothing) + return ParserResult::runtimeError( "Unrecognised warning: '" + warning + "'" ); + config.warnings = static_cast<WarnAbout::What>( config.warnings | warningSet ); + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const loadTestNamesFromFile = [&]( std::string const& filename ) { + std::ifstream f( filename.c_str() ); + if( !f.is_open() ) + return ParserResult::runtimeError( "Unable to load input file: '" + filename + "'" ); + + std::string line; + while( std::getline( f, line ) ) { + line = trim(line); + if( !line.empty() && !startsWith( line, '#' ) ) { + if( !startsWith( line, '"' ) ) + line = '"' + line + '"'; + config.testsOrTags.push_back( line + ',' ); + } + } + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const setTestOrder = [&]( std::string const& order ) { + if( startsWith( "declared", order ) ) + config.runOrder = RunTests::InDeclarationOrder; + else if( startsWith( "lexical", order ) ) + config.runOrder = RunTests::InLexicographicalOrder; + else if( startsWith( "random", order ) ) + config.runOrder = RunTests::InRandomOrder; + else + return clara::ParserResult::runtimeError( "Unrecognised ordering: '" + order + "'" ); + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const setRngSeed = [&]( std::string const& seed ) { + if( seed != "time" ) + return clara::detail::convertInto( seed, config.rngSeed ); + config.rngSeed = static_cast<unsigned int>( std::time(nullptr) ); + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const setColourUsage = [&]( std::string const& useColour ) { + auto mode = toLower( useColour ); + + if( mode == "yes" ) + config.useColour = UseColour::Yes; + else if( mode == "no" ) + config.useColour = UseColour::No; + else if( mode == "auto" ) + config.useColour = UseColour::Auto; + else + return ParserResult::runtimeError( "colour mode must be one of: auto, yes or no. '" + useColour + "' not recognised" ); + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const setWaitForKeypress = [&]( std::string const& keypress ) { + auto keypressLc = toLower( keypress ); + if( keypressLc == "start" ) + config.waitForKeypress = WaitForKeypress::BeforeStart; + else if( keypressLc == "exit" ) + config.waitForKeypress = WaitForKeypress::BeforeExit; + else if( keypressLc == "both" ) + config.waitForKeypress = WaitForKeypress::BeforeStartAndExit; + else + return ParserResult::runtimeError( "keypress argument must be one of: start, exit or both. '" + keypress + "' not recognised" ); + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const setVerbosity = [&]( std::string const& verbosity ) { + auto lcVerbosity = toLower( verbosity ); + if( lcVerbosity == "quiet" ) + config.verbosity = Verbosity::Quiet; + else if( lcVerbosity == "normal" ) + config.verbosity = Verbosity::Normal; + else if( lcVerbosity == "high" ) + config.verbosity = Verbosity::High; + else + return ParserResult::runtimeError( "Unrecognised verbosity, '" + verbosity + "'" ); + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const setReporter = [&]( std::string const& reporter ) { + IReporterRegistry::FactoryMap const& factories = getRegistryHub().getReporterRegistry().getFactories(); + + auto lcReporter = toLower( reporter ); + auto result = factories.find( lcReporter ); + + if( factories.end() != result ) + config.reporterName = lcReporter; + else + return ParserResult::runtimeError( "Unrecognized reporter, '" + reporter + "'. Check available with --list-reporters" ); + return ParserResult::ok( ParseResultType::Matched ); + }; + + auto cli + = ExeName( config.processName ) + | Help( config.showHelp ) + | Opt( config.listTests ) + ["-l"]["--list-tests"] + ( "list all/matching test cases" ) + | Opt( config.listTags ) + ["-t"]["--list-tags"] + ( "list all/matching tags" ) + | Opt( config.showSuccessfulTests ) + ["-s"]["--success"] + ( "include successful tests in output" ) + | Opt( config.shouldDebugBreak ) + ["-b"]["--break"] + ( "break into debugger on failure" ) + | Opt( config.noThrow ) + ["-e"]["--nothrow"] + ( "skip exception tests" ) + | Opt( config.showInvisibles ) + ["-i"]["--invisibles"] + ( "show invisibles (tabs, newlines)" ) + | Opt( config.outputFilename, "filename" ) + ["-o"]["--out"] + ( "output filename" ) + | Opt( setReporter, "name" ) + ["-r"]["--reporter"] + ( "reporter to use (defaults to console)" ) + | Opt( config.name, "name" ) + ["-n"]["--name"] + ( "suite name" ) + | Opt( [&]( bool ){ config.abortAfter = 1; } ) + ["-a"]["--abort"] + ( "abort at first failure" ) + | Opt( [&]( int x ){ config.abortAfter = x; }, "no. failures" ) + ["-x"]["--abortx"] + ( "abort after x failures" ) + | Opt( setWarning, "warning name" ) + ["-w"]["--warn"] + ( "enable warnings" ) + | Opt( [&]( bool flag ) { config.showDurations = flag ? ShowDurations::Always : ShowDurations::Never; }, "yes|no" ) + ["-d"]["--durations"] + ( "show test durations" ) + | Opt( loadTestNamesFromFile, "filename" ) + ["-f"]["--input-file"] + ( "load test names to run from a file" ) + | Opt( config.filenamesAsTags ) + ["-#"]["--filenames-as-tags"] + ( "adds a tag for the filename" ) + | Opt( config.sectionsToRun, "section name" ) + ["-c"]["--section"] + ( "specify section to run" ) + | Opt( setVerbosity, "quiet|normal|high" ) + ["-v"]["--verbosity"] + ( "set output verbosity" ) + | Opt( config.listTestNamesOnly ) + ["--list-test-names-only"] + ( "list all/matching test cases names only" ) + | Opt( config.listReporters ) + ["--list-reporters"] + ( "list all reporters" ) + | Opt( setTestOrder, "decl|lex|rand" ) + ["--order"] + ( "test case order (defaults to decl)" ) + | Opt( setRngSeed, "'time'|number" ) + ["--rng-seed"] + ( "set a specific seed for random numbers" ) + | Opt( setColourUsage, "yes|no" ) + ["--use-colour"] + ( "should output be colourised" ) + | Opt( config.libIdentify ) + ["--libidentify"] + ( "report name and version according to libidentify standard" ) + | Opt( setWaitForKeypress, "start|exit|both" ) + ["--wait-for-keypress"] + ( "waits for a keypress before exiting" ) + | Opt( config.benchmarkResolutionMultiple, "multiplier" ) + ["--benchmark-resolution-multiple"] + ( "multiple of clock resolution to run benchmarks" ) + + | Arg( config.testsOrTags, "test name|pattern|tags" ) + ( "which test or tests to use" ); + + return cli; + } + +} // end namespace Catch +// end catch_commandline.cpp +// start catch_common.cpp + +#include <cstring> +#include <ostream> + +namespace Catch { + + bool SourceLineInfo::empty() const noexcept { + return file[0] == '\0'; + } + bool SourceLineInfo::operator == ( SourceLineInfo const& other ) const noexcept { + return line == other.line && (file == other.file || std::strcmp(file, other.file) == 0); + } + bool SourceLineInfo::operator < ( SourceLineInfo const& other ) const noexcept { + // We can assume that the same file will usually have the same pointer. + // Thus, if the pointers are the same, there is no point in calling the strcmp + return line < other.line || ( line == other.line && file != other.file && (std::strcmp(file, other.file) < 0)); + } + + std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ) { +#ifndef __GNUG__ + os << info.file << '(' << info.line << ')'; +#else + os << info.file << ':' << info.line; +#endif + return os; + } + + std::string StreamEndStop::operator+() const { + return std::string(); + } + + NonCopyable::NonCopyable() = default; + NonCopyable::~NonCopyable() = default; + +} +// end catch_common.cpp +// start catch_config.cpp + +namespace Catch { + + Config::Config( ConfigData const& data ) + : m_data( data ), + m_stream( openStream() ) + { + TestSpecParser parser(ITagAliasRegistry::get()); + if (data.testsOrTags.empty()) { + parser.parse("~[.]"); // All not hidden tests + } + else { + m_hasTestFilters = true; + for( auto const& testOrTags : data.testsOrTags ) + parser.parse( testOrTags ); + } + m_testSpec = parser.testSpec(); + } + + std::string const& Config::getFilename() const { + return m_data.outputFilename ; + } + + bool Config::listTests() const { return m_data.listTests; } + bool Config::listTestNamesOnly() const { return m_data.listTestNamesOnly; } + bool Config::listTags() const { return m_data.listTags; } + bool Config::listReporters() const { return m_data.listReporters; } + + std::string Config::getProcessName() const { return m_data.processName; } + std::string const& Config::getReporterName() const { return m_data.reporterName; } + + std::vector<std::string> const& Config::getTestsOrTags() const { return m_data.testsOrTags; } + std::vector<std::string> const& Config::getSectionsToRun() const { return m_data.sectionsToRun; } + + TestSpec const& Config::testSpec() const { return m_testSpec; } + bool Config::hasTestFilters() const { return m_hasTestFilters; } + + bool Config::showHelp() const { return m_data.showHelp; } + + // IConfig interface + bool Config::allowThrows() const { return !m_data.noThrow; } + std::ostream& Config::stream() const { return m_stream->stream(); } + std::string Config::name() const { return m_data.name.empty() ? m_data.processName : m_data.name; } + bool Config::includeSuccessfulResults() const { return m_data.showSuccessfulTests; } + bool Config::warnAboutMissingAssertions() const { return !!(m_data.warnings & WarnAbout::NoAssertions); } + bool Config::warnAboutNoTests() const { return !!(m_data.warnings & WarnAbout::NoTests); } + ShowDurations::OrNot Config::showDurations() const { return m_data.showDurations; } + RunTests::InWhatOrder Config::runOrder() const { return m_data.runOrder; } + unsigned int Config::rngSeed() const { return m_data.rngSeed; } + int Config::benchmarkResolutionMultiple() const { return m_data.benchmarkResolutionMultiple; } + UseColour::YesOrNo Config::useColour() const { return m_data.useColour; } + bool Config::shouldDebugBreak() const { return m_data.shouldDebugBreak; } + int Config::abortAfter() const { return m_data.abortAfter; } + bool Config::showInvisibles() const { return m_data.showInvisibles; } + Verbosity Config::verbosity() const { return m_data.verbosity; } + + IStream const* Config::openStream() { + return Catch::makeStream(m_data.outputFilename); + } + +} // end namespace Catch +// end catch_config.cpp +// start catch_console_colour.cpp + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wexit-time-destructors" +#endif + +// start catch_errno_guard.h + +namespace Catch { + + class ErrnoGuard { + public: + ErrnoGuard(); + ~ErrnoGuard(); + private: + int m_oldErrno; + }; + +} + +// end catch_errno_guard.h +#include <sstream> + +namespace Catch { + namespace { + + struct IColourImpl { + virtual ~IColourImpl() = default; + virtual void use( Colour::Code _colourCode ) = 0; + }; + + struct NoColourImpl : IColourImpl { + void use( Colour::Code ) {} + + static IColourImpl* instance() { + static NoColourImpl s_instance; + return &s_instance; + } + }; + + } // anon namespace +} // namespace Catch + +#if !defined( CATCH_CONFIG_COLOUR_NONE ) && !defined( CATCH_CONFIG_COLOUR_WINDOWS ) && !defined( CATCH_CONFIG_COLOUR_ANSI ) +# ifdef CATCH_PLATFORM_WINDOWS +# define CATCH_CONFIG_COLOUR_WINDOWS +# else +# define CATCH_CONFIG_COLOUR_ANSI +# endif +#endif + +#if defined ( CATCH_CONFIG_COLOUR_WINDOWS ) ///////////////////////////////////////// + +namespace Catch { +namespace { + + class Win32ColourImpl : public IColourImpl { + public: + Win32ColourImpl() : stdoutHandle( GetStdHandle(STD_OUTPUT_HANDLE) ) + { + CONSOLE_SCREEN_BUFFER_INFO csbiInfo; + GetConsoleScreenBufferInfo( stdoutHandle, &csbiInfo ); + originalForegroundAttributes = csbiInfo.wAttributes & ~( BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_BLUE | BACKGROUND_INTENSITY ); + originalBackgroundAttributes = csbiInfo.wAttributes & ~( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY ); + } + + virtual void use( Colour::Code _colourCode ) override { + switch( _colourCode ) { + case Colour::None: return setTextAttribute( originalForegroundAttributes ); + case Colour::White: return setTextAttribute( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); + case Colour::Red: return setTextAttribute( FOREGROUND_RED ); + case Colour::Green: return setTextAttribute( FOREGROUND_GREEN ); + case Colour::Blue: return setTextAttribute( FOREGROUND_BLUE ); + case Colour::Cyan: return setTextAttribute( FOREGROUND_BLUE | FOREGROUND_GREEN ); + case Colour::Yellow: return setTextAttribute( FOREGROUND_RED | FOREGROUND_GREEN ); + case Colour::Grey: return setTextAttribute( 0 ); + + case Colour::LightGrey: return setTextAttribute( FOREGROUND_INTENSITY ); + case Colour::BrightRed: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_RED ); + case Colour::BrightGreen: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN ); + case Colour::BrightWhite: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); + case Colour::BrightYellow: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN ); + + case Colour::Bright: CATCH_INTERNAL_ERROR( "not a colour" ); + + default: + CATCH_ERROR( "Unknown colour requested" ); + } + } + + private: + void setTextAttribute( WORD _textAttribute ) { + SetConsoleTextAttribute( stdoutHandle, _textAttribute | originalBackgroundAttributes ); + } + HANDLE stdoutHandle; + WORD originalForegroundAttributes; + WORD originalBackgroundAttributes; + }; + + IColourImpl* platformColourInstance() { + static Win32ColourImpl s_instance; + + IConfigPtr config = getCurrentContext().getConfig(); + UseColour::YesOrNo colourMode = config + ? config->useColour() + : UseColour::Auto; + if( colourMode == UseColour::Auto ) + colourMode = UseColour::Yes; + return colourMode == UseColour::Yes + ? &s_instance + : NoColourImpl::instance(); + } + +} // end anon namespace +} // end namespace Catch + +#elif defined( CATCH_CONFIG_COLOUR_ANSI ) ////////////////////////////////////// + +#include <unistd.h> + +namespace Catch { +namespace { + + // use POSIX/ ANSI console terminal codes + // Thanks to Adam Strzelecki for original contribution + // (http://github.com/nanoant) + // https://github.com/philsquared/Catch/pull/131 + class PosixColourImpl : public IColourImpl { + public: + virtual void use( Colour::Code _colourCode ) override { + switch( _colourCode ) { + case Colour::None: + case Colour::White: return setColour( "[0m" ); + case Colour::Red: return setColour( "[0;31m" ); + case Colour::Green: return setColour( "[0;32m" ); + case Colour::Blue: return setColour( "[0;34m" ); + case Colour::Cyan: return setColour( "[0;36m" ); + case Colour::Yellow: return setColour( "[0;33m" ); + case Colour::Grey: return setColour( "[1;30m" ); + + case Colour::LightGrey: return setColour( "[0;37m" ); + case Colour::BrightRed: return setColour( "[1;31m" ); + case Colour::BrightGreen: return setColour( "[1;32m" ); + case Colour::BrightWhite: return setColour( "[1;37m" ); + case Colour::BrightYellow: return setColour( "[1;33m" ); + + case Colour::Bright: CATCH_INTERNAL_ERROR( "not a colour" ); + default: CATCH_INTERNAL_ERROR( "Unknown colour requested" ); + } + } + static IColourImpl* instance() { + static PosixColourImpl s_instance; + return &s_instance; + } + + private: + void setColour( const char* _escapeCode ) { + Catch::cout() << '\033' << _escapeCode; + } + }; + + bool useColourOnPlatform() { + return +#ifdef CATCH_PLATFORM_MAC + !isDebuggerActive() && +#endif +#if !(defined(__DJGPP__) && defined(__STRICT_ANSI__)) + isatty(STDOUT_FILENO) +#else + false +#endif + ; + } + IColourImpl* platformColourInstance() { + ErrnoGuard guard; + IConfigPtr config = getCurrentContext().getConfig(); + UseColour::YesOrNo colourMode = config + ? config->useColour() + : UseColour::Auto; + if( colourMode == UseColour::Auto ) + colourMode = useColourOnPlatform() + ? UseColour::Yes + : UseColour::No; + return colourMode == UseColour::Yes + ? PosixColourImpl::instance() + : NoColourImpl::instance(); + } + +} // end anon namespace +} // end namespace Catch + +#else // not Windows or ANSI /////////////////////////////////////////////// + +namespace Catch { + + static IColourImpl* platformColourInstance() { return NoColourImpl::instance(); } + +} // end namespace Catch + +#endif // Windows/ ANSI/ None + +namespace Catch { + + Colour::Colour( Code _colourCode ) { use( _colourCode ); } + Colour::Colour( Colour&& rhs ) noexcept { + m_moved = rhs.m_moved; + rhs.m_moved = true; + } + Colour& Colour::operator=( Colour&& rhs ) noexcept { + m_moved = rhs.m_moved; + rhs.m_moved = true; + return *this; + } + + Colour::~Colour(){ if( !m_moved ) use( None ); } + + void Colour::use( Code _colourCode ) { + static IColourImpl* impl = platformColourInstance(); + impl->use( _colourCode ); + } + + std::ostream& operator << ( std::ostream& os, Colour const& ) { + return os; + } + +} // end namespace Catch + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif + +// end catch_console_colour.cpp +// start catch_context.cpp + +namespace Catch { + + class Context : public IMutableContext, NonCopyable { + + public: // IContext + virtual IResultCapture* getResultCapture() override { + return m_resultCapture; + } + virtual IRunner* getRunner() override { + return m_runner; + } + + virtual IConfigPtr const& getConfig() const override { + return m_config; + } + + virtual ~Context() override; + + public: // IMutableContext + virtual void setResultCapture( IResultCapture* resultCapture ) override { + m_resultCapture = resultCapture; + } + virtual void setRunner( IRunner* runner ) override { + m_runner = runner; + } + virtual void setConfig( IConfigPtr const& config ) override { + m_config = config; + } + + friend IMutableContext& getCurrentMutableContext(); + + private: + IConfigPtr m_config; + IRunner* m_runner = nullptr; + IResultCapture* m_resultCapture = nullptr; + }; + + IMutableContext *IMutableContext::currentContext = nullptr; + + void IMutableContext::createContext() + { + currentContext = new Context(); + } + + void cleanUpContext() { + delete IMutableContext::currentContext; + IMutableContext::currentContext = nullptr; + } + IContext::~IContext() = default; + IMutableContext::~IMutableContext() = default; + Context::~Context() = default; +} +// end catch_context.cpp +// start catch_debug_console.cpp + +// start catch_debug_console.h + +#include <string> + +namespace Catch { + void writeToDebugConsole( std::string const& text ); +} + +// end catch_debug_console.h +#ifdef CATCH_PLATFORM_WINDOWS + + namespace Catch { + void writeToDebugConsole( std::string const& text ) { + ::OutputDebugStringA( text.c_str() ); + } + } + +#else + + namespace Catch { + void writeToDebugConsole( std::string const& text ) { + // !TBD: Need a version for Mac/ XCode and other IDEs + Catch::cout() << text; + } + } + +#endif // Platform +// end catch_debug_console.cpp +// start catch_debugger.cpp + +#ifdef CATCH_PLATFORM_MAC + +# include <assert.h> +# include <stdbool.h> +# include <sys/types.h> +# include <unistd.h> +# include <sys/sysctl.h> +# include <cstddef> +# include <ostream> + +namespace Catch { + + // The following function is taken directly from the following technical note: + // http://developer.apple.com/library/mac/#qa/qa2004/qa1361.html + + // Returns true if the current process is being debugged (either + // running under the debugger or has a debugger attached post facto). + bool isDebuggerActive(){ + + int mib[4]; + struct kinfo_proc info; + std::size_t size; + + // Initialize the flags so that, if sysctl fails for some bizarre + // reason, we get a predictable result. + + info.kp_proc.p_flag = 0; + + // Initialize mib, which tells sysctl the info we want, in this case + // we're looking for information about a specific process ID. + + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID; + mib[3] = getpid(); + + // Call sysctl. + + size = sizeof(info); + if( sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, nullptr, 0) != 0 ) { + Catch::cerr() << "\n** Call to sysctl failed - unable to determine if debugger is active **\n" << std::endl; + return false; + } + + // We're being debugged if the P_TRACED flag is set. + + return ( (info.kp_proc.p_flag & P_TRACED) != 0 ); + } + } // namespace Catch + +#elif defined(CATCH_PLATFORM_LINUX) + #include <fstream> + #include <string> + + namespace Catch{ + // The standard POSIX way of detecting a debugger is to attempt to + // ptrace() the process, but this needs to be done from a child and not + // this process itself to still allow attaching to this process later + // if wanted, so is rather heavy. Under Linux we have the PID of the + // "debugger" (which doesn't need to be gdb, of course, it could also + // be strace, for example) in /proc/$PID/status, so just get it from + // there instead. + bool isDebuggerActive(){ + // Libstdc++ has a bug, where std::ifstream sets errno to 0 + // This way our users can properly assert over errno values + ErrnoGuard guard; + std::ifstream in("/proc/self/status"); + for( std::string line; std::getline(in, line); ) { + static const int PREFIX_LEN = 11; + if( line.compare(0, PREFIX_LEN, "TracerPid:\t") == 0 ) { + // We're traced if the PID is not 0 and no other PID starts + // with 0 digit, so it's enough to check for just a single + // character. + return line.length() > PREFIX_LEN && line[PREFIX_LEN] != '0'; + } + } + + return false; + } + } // namespace Catch +#elif defined(_MSC_VER) + extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); + namespace Catch { + bool isDebuggerActive() { + return IsDebuggerPresent() != 0; + } + } +#elif defined(__MINGW32__) + extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); + namespace Catch { + bool isDebuggerActive() { + return IsDebuggerPresent() != 0; + } + } +#else + namespace Catch { + bool isDebuggerActive() { return false; } + } +#endif // Platform +// end catch_debugger.cpp +// start catch_decomposer.cpp + +namespace Catch { + + ITransientExpression::~ITransientExpression() = default; + + void formatReconstructedExpression( std::ostream &os, std::string const& lhs, StringRef op, std::string const& rhs ) { + if( lhs.size() + rhs.size() < 40 && + lhs.find('\n') == std::string::npos && + rhs.find('\n') == std::string::npos ) + os << lhs << " " << op << " " << rhs; + else + os << lhs << "\n" << op << "\n" << rhs; + } +} +// end catch_decomposer.cpp +// start catch_enforce.cpp + +namespace Catch { +#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) && !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS_CUSTOM_HANDLER) + [[noreturn]] + void throw_exception(std::exception const& e) { + Catch::cerr() << "Catch will terminate because it needed to throw an exception.\n" + << "The message was: " << e.what() << '\n'; + std::terminate(); + } +#endif +} // namespace Catch; +// end catch_enforce.cpp +// start catch_errno_guard.cpp + +#include <cerrno> + +namespace Catch { + ErrnoGuard::ErrnoGuard():m_oldErrno(errno){} + ErrnoGuard::~ErrnoGuard() { errno = m_oldErrno; } +} +// end catch_errno_guard.cpp +// start catch_exception_translator_registry.cpp + +// start catch_exception_translator_registry.h + +#include <vector> +#include <string> +#include <memory> + +namespace Catch { + + class ExceptionTranslatorRegistry : public IExceptionTranslatorRegistry { + public: + ~ExceptionTranslatorRegistry(); + virtual void registerTranslator( const IExceptionTranslator* translator ); + virtual std::string translateActiveException() const override; + std::string tryTranslators() const; + + private: + std::vector<std::unique_ptr<IExceptionTranslator const>> m_translators; + }; +} + +// end catch_exception_translator_registry.h +#ifdef __OBJC__ +#import "Foundation/Foundation.h" +#endif + +namespace Catch { + + ExceptionTranslatorRegistry::~ExceptionTranslatorRegistry() { + } + + void ExceptionTranslatorRegistry::registerTranslator( const IExceptionTranslator* translator ) { + m_translators.push_back( std::unique_ptr<const IExceptionTranslator>( translator ) ); + } + +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + std::string ExceptionTranslatorRegistry::translateActiveException() const { + try { +#ifdef __OBJC__ + // In Objective-C try objective-c exceptions first + @try { + return tryTranslators(); + } + @catch (NSException *exception) { + return Catch::Detail::stringify( [exception description] ); + } +#else + // Compiling a mixed mode project with MSVC means that CLR + // exceptions will be caught in (...) as well. However, these + // do not fill-in std::current_exception and thus lead to crash + // when attempting rethrow. + // /EHa switch also causes structured exceptions to be caught + // here, but they fill-in current_exception properly, so + // at worst the output should be a little weird, instead of + // causing a crash. + if (std::current_exception() == nullptr) { + return "Non C++ exception. Possibly a CLR exception."; + } + return tryTranslators(); +#endif + } + catch( TestFailureException& ) { + std::rethrow_exception(std::current_exception()); + } + catch( std::exception& ex ) { + return ex.what(); + } + catch( std::string& msg ) { + return msg; + } + catch( const char* msg ) { + return msg; + } + catch(...) { + return "Unknown exception"; + } + } + +#else // ^^ Exceptions are enabled // Exceptions are disabled vv + std::string ExceptionTranslatorRegistry::translateActiveException() const { + CATCH_INTERNAL_ERROR("Attempted to translate active exception under CATCH_CONFIG_DISABLE_EXCEPTIONS!"); + } +#endif + + std::string ExceptionTranslatorRegistry::tryTranslators() const { + if( m_translators.empty() ) + std::rethrow_exception(std::current_exception()); + else + return m_translators[0]->translate( m_translators.begin()+1, m_translators.end() ); + } +} +// end catch_exception_translator_registry.cpp +// start catch_fatal_condition.cpp + +#if defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wmissing-field-initializers" +#endif + +#if defined( CATCH_CONFIG_WINDOWS_SEH ) || defined( CATCH_CONFIG_POSIX_SIGNALS ) + +namespace { + // Report the error condition + void reportFatal( char const * const message ) { + Catch::getCurrentContext().getResultCapture()->handleFatalErrorCondition( message ); + } +} + +#endif // signals/SEH handling + +#if defined( CATCH_CONFIG_WINDOWS_SEH ) + +namespace Catch { + struct SignalDefs { DWORD id; const char* name; }; + + // There is no 1-1 mapping between signals and windows exceptions. + // Windows can easily distinguish between SO and SigSegV, + // but SigInt, SigTerm, etc are handled differently. + static SignalDefs signalDefs[] = { + { EXCEPTION_ILLEGAL_INSTRUCTION, "SIGILL - Illegal instruction signal" }, + { EXCEPTION_STACK_OVERFLOW, "SIGSEGV - Stack overflow" }, + { EXCEPTION_ACCESS_VIOLATION, "SIGSEGV - Segmentation violation signal" }, + { EXCEPTION_INT_DIVIDE_BY_ZERO, "Divide by zero error" }, + }; + + LONG CALLBACK FatalConditionHandler::handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo) { + for (auto const& def : signalDefs) { + if (ExceptionInfo->ExceptionRecord->ExceptionCode == def.id) { + reportFatal(def.name); + } + } + // If its not an exception we care about, pass it along. + // This stops us from eating debugger breaks etc. + return EXCEPTION_CONTINUE_SEARCH; + } + + FatalConditionHandler::FatalConditionHandler() { + isSet = true; + // 32k seems enough for Catch to handle stack overflow, + // but the value was found experimentally, so there is no strong guarantee + guaranteeSize = 32 * 1024; + exceptionHandlerHandle = nullptr; + // Register as first handler in current chain + exceptionHandlerHandle = AddVectoredExceptionHandler(1, handleVectoredException); + // Pass in guarantee size to be filled + SetThreadStackGuarantee(&guaranteeSize); + } + + void FatalConditionHandler::reset() { + if (isSet) { + RemoveVectoredExceptionHandler(exceptionHandlerHandle); + SetThreadStackGuarantee(&guaranteeSize); + exceptionHandlerHandle = nullptr; + isSet = false; + } + } + + FatalConditionHandler::~FatalConditionHandler() { + reset(); + } + +bool FatalConditionHandler::isSet = false; +ULONG FatalConditionHandler::guaranteeSize = 0; +PVOID FatalConditionHandler::exceptionHandlerHandle = nullptr; + +} // namespace Catch + +#elif defined( CATCH_CONFIG_POSIX_SIGNALS ) + +namespace Catch { + + struct SignalDefs { + int id; + const char* name; + }; + + // 32kb for the alternate stack seems to be sufficient. However, this value + // is experimentally determined, so that's not guaranteed. + constexpr static std::size_t sigStackSize = 32768 >= MINSIGSTKSZ ? 32768 : MINSIGSTKSZ; + + static SignalDefs signalDefs[] = { + { SIGINT, "SIGINT - Terminal interrupt signal" }, + { SIGILL, "SIGILL - Illegal instruction signal" }, + { SIGFPE, "SIGFPE - Floating point error signal" }, + { SIGSEGV, "SIGSEGV - Segmentation violation signal" }, + { SIGTERM, "SIGTERM - Termination request signal" }, + { SIGABRT, "SIGABRT - Abort (abnormal termination) signal" } + }; + + void FatalConditionHandler::handleSignal( int sig ) { + char const * name = "<unknown signal>"; + for (auto const& def : signalDefs) { + if (sig == def.id) { + name = def.name; + break; + } + } + reset(); + reportFatal(name); + raise( sig ); + } + + FatalConditionHandler::FatalConditionHandler() { + isSet = true; + stack_t sigStack; + sigStack.ss_sp = altStackMem; + sigStack.ss_size = sigStackSize; + sigStack.ss_flags = 0; + sigaltstack(&sigStack, &oldSigStack); + struct sigaction sa = { }; + + sa.sa_handler = handleSignal; + sa.sa_flags = SA_ONSTACK; + for (std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i) { + sigaction(signalDefs[i].id, &sa, &oldSigActions[i]); + } + } + + FatalConditionHandler::~FatalConditionHandler() { + reset(); + } + + void FatalConditionHandler::reset() { + if( isSet ) { + // Set signals back to previous values -- hopefully nobody overwrote them in the meantime + for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i ) { + sigaction(signalDefs[i].id, &oldSigActions[i], nullptr); + } + // Return the old stack + sigaltstack(&oldSigStack, nullptr); + isSet = false; + } + } + + bool FatalConditionHandler::isSet = false; + struct sigaction FatalConditionHandler::oldSigActions[sizeof(signalDefs)/sizeof(SignalDefs)] = {}; + stack_t FatalConditionHandler::oldSigStack = {}; + char FatalConditionHandler::altStackMem[sigStackSize] = {}; + +} // namespace Catch + +#else + +namespace Catch { + void FatalConditionHandler::reset() {} +} + +#endif // signals/SEH handling + +#if defined(__GNUC__) +# pragma GCC diagnostic pop +#endif +// end catch_fatal_condition.cpp +// start catch_generators.cpp + +// start catch_random_number_generator.h + +#include <algorithm> +#include <random> + +namespace Catch { + + struct IConfig; + + std::mt19937& rng(); + void seedRng( IConfig const& config ); + unsigned int rngSeed(); + +} + +// end catch_random_number_generator.h +#include <limits> +#include <set> + +namespace Catch { + +IGeneratorTracker::~IGeneratorTracker() {} + +namespace Generators { + + GeneratorBase::~GeneratorBase() {} + + std::vector<size_t> randomiseIndices( size_t selectionSize, size_t sourceSize ) { + + assert( selectionSize <= sourceSize ); + std::vector<size_t> indices; + indices.reserve( selectionSize ); + std::uniform_int_distribution<size_t> uid( 0, sourceSize-1 ); + + std::set<size_t> seen; + // !TBD: improve this algorithm + while( indices.size() < selectionSize ) { + auto index = uid( rng() ); + if( seen.insert( index ).second ) + indices.push_back( index ); + } + return indices; + } + + auto acquireGeneratorTracker( SourceLineInfo const& lineInfo ) -> IGeneratorTracker& { + return getResultCapture().acquireGeneratorTracker( lineInfo ); + } + + template<> + auto all<int>() -> Generator<int> { + return range( std::numeric_limits<int>::min(), std::numeric_limits<int>::max() ); + } + +} // namespace Generators +} // namespace Catch +// end catch_generators.cpp +// start catch_interfaces_capture.cpp + +namespace Catch { + IResultCapture::~IResultCapture() = default; +} +// end catch_interfaces_capture.cpp +// start catch_interfaces_config.cpp + +namespace Catch { + IConfig::~IConfig() = default; +} +// end catch_interfaces_config.cpp +// start catch_interfaces_exception.cpp + +namespace Catch { + IExceptionTranslator::~IExceptionTranslator() = default; + IExceptionTranslatorRegistry::~IExceptionTranslatorRegistry() = default; +} +// end catch_interfaces_exception.cpp +// start catch_interfaces_registry_hub.cpp + +namespace Catch { + IRegistryHub::~IRegistryHub() = default; + IMutableRegistryHub::~IMutableRegistryHub() = default; +} +// end catch_interfaces_registry_hub.cpp +// start catch_interfaces_reporter.cpp + +// start catch_reporter_listening.h + +namespace Catch { + + class ListeningReporter : public IStreamingReporter { + using Reporters = std::vector<IStreamingReporterPtr>; + Reporters m_listeners; + IStreamingReporterPtr m_reporter = nullptr; + ReporterPreferences m_preferences; + + public: + ListeningReporter(); + + void addListener( IStreamingReporterPtr&& listener ); + void addReporter( IStreamingReporterPtr&& reporter ); + + public: // IStreamingReporter + + ReporterPreferences getPreferences() const override; + + void noMatchingTestCases( std::string const& spec ) override; + + static std::set<Verbosity> getSupportedVerbosities(); + + void benchmarkStarting( BenchmarkInfo const& benchmarkInfo ) override; + void benchmarkEnded( BenchmarkStats const& benchmarkStats ) override; + + void testRunStarting( TestRunInfo const& testRunInfo ) override; + void testGroupStarting( GroupInfo const& groupInfo ) override; + void testCaseStarting( TestCaseInfo const& testInfo ) override; + void sectionStarting( SectionInfo const& sectionInfo ) override; + void assertionStarting( AssertionInfo const& assertionInfo ) override; + + // The return value indicates if the messages buffer should be cleared: + bool assertionEnded( AssertionStats const& assertionStats ) override; + void sectionEnded( SectionStats const& sectionStats ) override; + void testCaseEnded( TestCaseStats const& testCaseStats ) override; + void testGroupEnded( TestGroupStats const& testGroupStats ) override; + void testRunEnded( TestRunStats const& testRunStats ) override; + + void skipTest( TestCaseInfo const& testInfo ) override; + bool isMulti() const override; + + }; + +} // end namespace Catch + +// end catch_reporter_listening.h +namespace Catch { + + ReporterConfig::ReporterConfig( IConfigPtr const& _fullConfig ) + : m_stream( &_fullConfig->stream() ), m_fullConfig( _fullConfig ) {} + + ReporterConfig::ReporterConfig( IConfigPtr const& _fullConfig, std::ostream& _stream ) + : m_stream( &_stream ), m_fullConfig( _fullConfig ) {} + + std::ostream& ReporterConfig::stream() const { return *m_stream; } + IConfigPtr ReporterConfig::fullConfig() const { return m_fullConfig; } + + TestRunInfo::TestRunInfo( std::string const& _name ) : name( _name ) {} + + GroupInfo::GroupInfo( std::string const& _name, + std::size_t _groupIndex, + std::size_t _groupsCount ) + : name( _name ), + groupIndex( _groupIndex ), + groupsCounts( _groupsCount ) + {} + + AssertionStats::AssertionStats( AssertionResult const& _assertionResult, + std::vector<MessageInfo> const& _infoMessages, + Totals const& _totals ) + : assertionResult( _assertionResult ), + infoMessages( _infoMessages ), + totals( _totals ) + { + assertionResult.m_resultData.lazyExpression.m_transientExpression = _assertionResult.m_resultData.lazyExpression.m_transientExpression; + + if( assertionResult.hasMessage() ) { + // Copy message into messages list. + // !TBD This should have been done earlier, somewhere + MessageBuilder builder( assertionResult.getTestMacroName(), assertionResult.getSourceInfo(), assertionResult.getResultType() ); + builder << assertionResult.getMessage(); + builder.m_info.message = builder.m_stream.str(); + + infoMessages.push_back( builder.m_info ); + } + } + + AssertionStats::~AssertionStats() = default; + + SectionStats::SectionStats( SectionInfo const& _sectionInfo, + Counts const& _assertions, + double _durationInSeconds, + bool _missingAssertions ) + : sectionInfo( _sectionInfo ), + assertions( _assertions ), + durationInSeconds( _durationInSeconds ), + missingAssertions( _missingAssertions ) + {} + + SectionStats::~SectionStats() = default; + + TestCaseStats::TestCaseStats( TestCaseInfo const& _testInfo, + Totals const& _totals, + std::string const& _stdOut, + std::string const& _stdErr, + bool _aborting ) + : testInfo( _testInfo ), + totals( _totals ), + stdOut( _stdOut ), + stdErr( _stdErr ), + aborting( _aborting ) + {} + + TestCaseStats::~TestCaseStats() = default; + + TestGroupStats::TestGroupStats( GroupInfo const& _groupInfo, + Totals const& _totals, + bool _aborting ) + : groupInfo( _groupInfo ), + totals( _totals ), + aborting( _aborting ) + {} + + TestGroupStats::TestGroupStats( GroupInfo const& _groupInfo ) + : groupInfo( _groupInfo ), + aborting( false ) + {} + + TestGroupStats::~TestGroupStats() = default; + + TestRunStats::TestRunStats( TestRunInfo const& _runInfo, + Totals const& _totals, + bool _aborting ) + : runInfo( _runInfo ), + totals( _totals ), + aborting( _aborting ) + {} + + TestRunStats::~TestRunStats() = default; + + void IStreamingReporter::fatalErrorEncountered( StringRef ) {} + bool IStreamingReporter::isMulti() const { return false; } + + IReporterFactory::~IReporterFactory() = default; + IReporterRegistry::~IReporterRegistry() = default; + +} // end namespace Catch +// end catch_interfaces_reporter.cpp +// start catch_interfaces_runner.cpp + +namespace Catch { + IRunner::~IRunner() = default; +} +// end catch_interfaces_runner.cpp +// start catch_interfaces_testcase.cpp + +namespace Catch { + ITestInvoker::~ITestInvoker() = default; + ITestCaseRegistry::~ITestCaseRegistry() = default; +} +// end catch_interfaces_testcase.cpp +// start catch_leak_detector.cpp + +#ifdef CATCH_CONFIG_WINDOWS_CRTDBG +#include <crtdbg.h> + +namespace Catch { + + LeakDetector::LeakDetector() { + int flag = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG); + flag |= _CRTDBG_LEAK_CHECK_DF; + flag |= _CRTDBG_ALLOC_MEM_DF; + _CrtSetDbgFlag(flag); + _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); + _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR); + // Change this to leaking allocation's number to break there + _CrtSetBreakAlloc(-1); + } +} + +#else + + Catch::LeakDetector::LeakDetector() {} + +#endif + +Catch::LeakDetector::~LeakDetector() { + Catch::cleanUp(); +} +// end catch_leak_detector.cpp +// start catch_list.cpp + +// start catch_list.h + +#include <set> + +namespace Catch { + + std::size_t listTests( Config const& config ); + + std::size_t listTestsNamesOnly( Config const& config ); + + struct TagInfo { + void add( std::string const& spelling ); + std::string all() const; + + std::set<std::string> spellings; + std::size_t count = 0; + }; + + std::size_t listTags( Config const& config ); + + std::size_t listReporters(); + + Option<std::size_t> list( Config const& config ); + +} // end namespace Catch + +// end catch_list.h +// start catch_text.h + +namespace Catch { + using namespace clara::TextFlow; +} + +// end catch_text.h +#include <limits> +#include <algorithm> +#include <iomanip> + +namespace Catch { + + std::size_t listTests( Config const& config ) { + TestSpec testSpec = config.testSpec(); + if( config.hasTestFilters() ) + Catch::cout() << "Matching test cases:\n"; + else { + Catch::cout() << "All available test cases:\n"; + } + + auto matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); + for( auto const& testCaseInfo : matchedTestCases ) { + Colour::Code colour = testCaseInfo.isHidden() + ? Colour::SecondaryText + : Colour::None; + Colour colourGuard( colour ); + + Catch::cout() << Column( testCaseInfo.name ).initialIndent( 2 ).indent( 4 ) << "\n"; + if( config.verbosity() >= Verbosity::High ) { + Catch::cout() << Column( Catch::Detail::stringify( testCaseInfo.lineInfo ) ).indent(4) << std::endl; + std::string description = testCaseInfo.description; + if( description.empty() ) + description = "(NO DESCRIPTION)"; + Catch::cout() << Column( description ).indent(4) << std::endl; + } + if( !testCaseInfo.tags.empty() ) + Catch::cout() << Column( testCaseInfo.tagsAsString() ).indent( 6 ) << "\n"; + } + + if( !config.hasTestFilters() ) + Catch::cout() << pluralise( matchedTestCases.size(), "test case" ) << '\n' << std::endl; + else + Catch::cout() << pluralise( matchedTestCases.size(), "matching test case" ) << '\n' << std::endl; + return matchedTestCases.size(); + } + + std::size_t listTestsNamesOnly( Config const& config ) { + TestSpec testSpec = config.testSpec(); + std::size_t matchedTests = 0; + std::vector<TestCase> matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); + for( auto const& testCaseInfo : matchedTestCases ) { + matchedTests++; + if( startsWith( testCaseInfo.name, '#' ) ) + Catch::cout() << '"' << testCaseInfo.name << '"'; + else + Catch::cout() << testCaseInfo.name; + if ( config.verbosity() >= Verbosity::High ) + Catch::cout() << "\t@" << testCaseInfo.lineInfo; + Catch::cout() << std::endl; + } + return matchedTests; + } + + void TagInfo::add( std::string const& spelling ) { + ++count; + spellings.insert( spelling ); + } + + std::string TagInfo::all() const { + std::string out; + for( auto const& spelling : spellings ) + out += "[" + spelling + "]"; + return out; + } + + std::size_t listTags( Config const& config ) { + TestSpec testSpec = config.testSpec(); + if( config.hasTestFilters() ) + Catch::cout() << "Tags for matching test cases:\n"; + else { + Catch::cout() << "All available tags:\n"; + } + + std::map<std::string, TagInfo> tagCounts; + + std::vector<TestCase> matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); + for( auto const& testCase : matchedTestCases ) { + for( auto const& tagName : testCase.getTestCaseInfo().tags ) { + std::string lcaseTagName = toLower( tagName ); + auto countIt = tagCounts.find( lcaseTagName ); + if( countIt == tagCounts.end() ) + countIt = tagCounts.insert( std::make_pair( lcaseTagName, TagInfo() ) ).first; + countIt->second.add( tagName ); + } + } + + for( auto const& tagCount : tagCounts ) { + ReusableStringStream rss; + rss << " " << std::setw(2) << tagCount.second.count << " "; + auto str = rss.str(); + auto wrapper = Column( tagCount.second.all() ) + .initialIndent( 0 ) + .indent( str.size() ) + .width( CATCH_CONFIG_CONSOLE_WIDTH-10 ); + Catch::cout() << str << wrapper << '\n'; + } + Catch::cout() << pluralise( tagCounts.size(), "tag" ) << '\n' << std::endl; + return tagCounts.size(); + } + + std::size_t listReporters() { + Catch::cout() << "Available reporters:\n"; + IReporterRegistry::FactoryMap const& factories = getRegistryHub().getReporterRegistry().getFactories(); + std::size_t maxNameLen = 0; + for( auto const& factoryKvp : factories ) + maxNameLen = (std::max)( maxNameLen, factoryKvp.first.size() ); + + for( auto const& factoryKvp : factories ) { + Catch::cout() + << Column( factoryKvp.first + ":" ) + .indent(2) + .width( 5+maxNameLen ) + + Column( factoryKvp.second->getDescription() ) + .initialIndent(0) + .indent(2) + .width( CATCH_CONFIG_CONSOLE_WIDTH - maxNameLen-8 ) + << "\n"; + } + Catch::cout() << std::endl; + return factories.size(); + } + + Option<std::size_t> list( Config const& config ) { + Option<std::size_t> listedCount; + if( config.listTests() ) + listedCount = listedCount.valueOr(0) + listTests( config ); + if( config.listTestNamesOnly() ) + listedCount = listedCount.valueOr(0) + listTestsNamesOnly( config ); + if( config.listTags() ) + listedCount = listedCount.valueOr(0) + listTags( config ); + if( config.listReporters() ) + listedCount = listedCount.valueOr(0) + listReporters(); + return listedCount; + } + +} // end namespace Catch +// end catch_list.cpp +// start catch_matchers.cpp + +namespace Catch { +namespace Matchers { + namespace Impl { + + std::string MatcherUntypedBase::toString() const { + if( m_cachedToString.empty() ) + m_cachedToString = describe(); + return m_cachedToString; + } + + MatcherUntypedBase::~MatcherUntypedBase() = default; + + } // namespace Impl +} // namespace Matchers + +using namespace Matchers; +using Matchers::Impl::MatcherBase; + +} // namespace Catch +// end catch_matchers.cpp +// start catch_matchers_floating.cpp + +// start catch_polyfills.hpp + +namespace Catch { + bool isnan(float f); + bool isnan(double d); +} + +// end catch_polyfills.hpp +// start catch_to_string.hpp + +#include <string> + +namespace Catch { + template <typename T> + std::string to_string(T const& t) { +#if defined(CATCH_CONFIG_CPP11_TO_STRING) + return std::to_string(t); +#else + ReusableStringStream rss; + rss << t; + return rss.str(); +#endif + } +} // end namespace Catch + +// end catch_to_string.hpp +#include <cstdlib> +#include <cstdint> +#include <cstring> + +namespace Catch { +namespace Matchers { +namespace Floating { +enum class FloatingPointKind : uint8_t { + Float, + Double +}; +} +} +} + +namespace { + +template <typename T> +struct Converter; + +template <> +struct Converter<float> { + static_assert(sizeof(float) == sizeof(int32_t), "Important ULP matcher assumption violated"); + Converter(float f) { + std::memcpy(&i, &f, sizeof(f)); + } + int32_t i; +}; + +template <> +struct Converter<double> { + static_assert(sizeof(double) == sizeof(int64_t), "Important ULP matcher assumption violated"); + Converter(double d) { + std::memcpy(&i, &d, sizeof(d)); + } + int64_t i; +}; + +template <typename T> +auto convert(T t) -> Converter<T> { + return Converter<T>(t); +} + +template <typename FP> +bool almostEqualUlps(FP lhs, FP rhs, int maxUlpDiff) { + // Comparison with NaN should always be false. + // This way we can rule it out before getting into the ugly details + if (Catch::isnan(lhs) || Catch::isnan(rhs)) { + return false; + } + + auto lc = convert(lhs); + auto rc = convert(rhs); + + if ((lc.i < 0) != (rc.i < 0)) { + // Potentially we can have +0 and -0 + return lhs == rhs; + } + + auto ulpDiff = std::abs(lc.i - rc.i); + return ulpDiff <= maxUlpDiff; +} + +} + +namespace Catch { +namespace Matchers { +namespace Floating { + WithinAbsMatcher::WithinAbsMatcher(double target, double margin) + :m_target{ target }, m_margin{ margin } { + CATCH_ENFORCE(margin >= 0, "Invalid margin: " << margin << '.' + << " Margin has to be non-negative."); + } + + // Performs equivalent check of std::fabs(lhs - rhs) <= margin + // But without the subtraction to allow for INFINITY in comparison + bool WithinAbsMatcher::match(double const& matchee) const { + return (matchee + m_margin >= m_target) && (m_target + m_margin >= matchee); + } + + std::string WithinAbsMatcher::describe() const { + return "is within " + ::Catch::Detail::stringify(m_margin) + " of " + ::Catch::Detail::stringify(m_target); + } + + WithinUlpsMatcher::WithinUlpsMatcher(double target, int ulps, FloatingPointKind baseType) + :m_target{ target }, m_ulps{ ulps }, m_type{ baseType } { + CATCH_ENFORCE(ulps >= 0, "Invalid ULP setting: " << ulps << '.' + << " ULPs have to be non-negative."); + } + +#if defined(__clang__) +#pragma clang diagnostic push +// Clang <3.5 reports on the default branch in the switch below +#pragma clang diagnostic ignored "-Wunreachable-code" +#endif + + bool WithinUlpsMatcher::match(double const& matchee) const { + switch (m_type) { + case FloatingPointKind::Float: + return almostEqualUlps<float>(static_cast<float>(matchee), static_cast<float>(m_target), m_ulps); + case FloatingPointKind::Double: + return almostEqualUlps<double>(matchee, m_target, m_ulps); + default: + CATCH_INTERNAL_ERROR( "Unknown FloatingPointKind value" ); + } + } + +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + + std::string WithinUlpsMatcher::describe() const { + return "is within " + Catch::to_string(m_ulps) + " ULPs of " + ::Catch::Detail::stringify(m_target) + ((m_type == FloatingPointKind::Float)? "f" : ""); + } + +}// namespace Floating + +Floating::WithinUlpsMatcher WithinULP(double target, int maxUlpDiff) { + return Floating::WithinUlpsMatcher(target, maxUlpDiff, Floating::FloatingPointKind::Double); +} + +Floating::WithinUlpsMatcher WithinULP(float target, int maxUlpDiff) { + return Floating::WithinUlpsMatcher(target, maxUlpDiff, Floating::FloatingPointKind::Float); +} + +Floating::WithinAbsMatcher WithinAbs(double target, double margin) { + return Floating::WithinAbsMatcher(target, margin); +} + +} // namespace Matchers +} // namespace Catch + +// end catch_matchers_floating.cpp +// start catch_matchers_generic.cpp + +std::string Catch::Matchers::Generic::Detail::finalizeDescription(const std::string& desc) { + if (desc.empty()) { + return "matches undescribed predicate"; + } else { + return "matches predicate: \"" + desc + '"'; + } +} +// end catch_matchers_generic.cpp +// start catch_matchers_string.cpp + +#include <regex> + +namespace Catch { +namespace Matchers { + + namespace StdString { + + CasedString::CasedString( std::string const& str, CaseSensitive::Choice caseSensitivity ) + : m_caseSensitivity( caseSensitivity ), + m_str( adjustString( str ) ) + {} + std::string CasedString::adjustString( std::string const& str ) const { + return m_caseSensitivity == CaseSensitive::No + ? toLower( str ) + : str; + } + std::string CasedString::caseSensitivitySuffix() const { + return m_caseSensitivity == CaseSensitive::No + ? " (case insensitive)" + : std::string(); + } + + StringMatcherBase::StringMatcherBase( std::string const& operation, CasedString const& comparator ) + : m_comparator( comparator ), + m_operation( operation ) { + } + + std::string StringMatcherBase::describe() const { + std::string description; + description.reserve(5 + m_operation.size() + m_comparator.m_str.size() + + m_comparator.caseSensitivitySuffix().size()); + description += m_operation; + description += ": \""; + description += m_comparator.m_str; + description += "\""; + description += m_comparator.caseSensitivitySuffix(); + return description; + } + + EqualsMatcher::EqualsMatcher( CasedString const& comparator ) : StringMatcherBase( "equals", comparator ) {} + + bool EqualsMatcher::match( std::string const& source ) const { + return m_comparator.adjustString( source ) == m_comparator.m_str; + } + + ContainsMatcher::ContainsMatcher( CasedString const& comparator ) : StringMatcherBase( "contains", comparator ) {} + + bool ContainsMatcher::match( std::string const& source ) const { + return contains( m_comparator.adjustString( source ), m_comparator.m_str ); + } + + StartsWithMatcher::StartsWithMatcher( CasedString const& comparator ) : StringMatcherBase( "starts with", comparator ) {} + + bool StartsWithMatcher::match( std::string const& source ) const { + return startsWith( m_comparator.adjustString( source ), m_comparator.m_str ); + } + + EndsWithMatcher::EndsWithMatcher( CasedString const& comparator ) : StringMatcherBase( "ends with", comparator ) {} + + bool EndsWithMatcher::match( std::string const& source ) const { + return endsWith( m_comparator.adjustString( source ), m_comparator.m_str ); + } + + RegexMatcher::RegexMatcher(std::string regex, CaseSensitive::Choice caseSensitivity): m_regex(std::move(regex)), m_caseSensitivity(caseSensitivity) {} + + bool RegexMatcher::match(std::string const& matchee) const { + auto flags = std::regex::ECMAScript; // ECMAScript is the default syntax option anyway + if (m_caseSensitivity == CaseSensitive::Choice::No) { + flags |= std::regex::icase; + } + auto reg = std::regex(m_regex, flags); + return std::regex_match(matchee, reg); + } + + std::string RegexMatcher::describe() const { + return "matches " + ::Catch::Detail::stringify(m_regex) + ((m_caseSensitivity == CaseSensitive::Choice::Yes)? " case sensitively" : " case insensitively"); + } + + } // namespace StdString + + StdString::EqualsMatcher Equals( std::string const& str, CaseSensitive::Choice caseSensitivity ) { + return StdString::EqualsMatcher( StdString::CasedString( str, caseSensitivity) ); + } + StdString::ContainsMatcher Contains( std::string const& str, CaseSensitive::Choice caseSensitivity ) { + return StdString::ContainsMatcher( StdString::CasedString( str, caseSensitivity) ); + } + StdString::EndsWithMatcher EndsWith( std::string const& str, CaseSensitive::Choice caseSensitivity ) { + return StdString::EndsWithMatcher( StdString::CasedString( str, caseSensitivity) ); + } + StdString::StartsWithMatcher StartsWith( std::string const& str, CaseSensitive::Choice caseSensitivity ) { + return StdString::StartsWithMatcher( StdString::CasedString( str, caseSensitivity) ); + } + + StdString::RegexMatcher Matches(std::string const& regex, CaseSensitive::Choice caseSensitivity) { + return StdString::RegexMatcher(regex, caseSensitivity); + } + +} // namespace Matchers +} // namespace Catch +// end catch_matchers_string.cpp +// start catch_message.cpp + +// start catch_uncaught_exceptions.h + +namespace Catch { + bool uncaught_exceptions(); +} // end namespace Catch + +// end catch_uncaught_exceptions.h +#include <cassert> +#include <stack> + +namespace Catch { + + MessageInfo::MessageInfo( StringRef const& _macroName, + SourceLineInfo const& _lineInfo, + ResultWas::OfType _type ) + : macroName( _macroName ), + lineInfo( _lineInfo ), + type( _type ), + sequence( ++globalCount ) + {} + + bool MessageInfo::operator==( MessageInfo const& other ) const { + return sequence == other.sequence; + } + + bool MessageInfo::operator<( MessageInfo const& other ) const { + return sequence < other.sequence; + } + + // This may need protecting if threading support is added + unsigned int MessageInfo::globalCount = 0; + + //////////////////////////////////////////////////////////////////////////// + + Catch::MessageBuilder::MessageBuilder( StringRef const& macroName, + SourceLineInfo const& lineInfo, + ResultWas::OfType type ) + :m_info(macroName, lineInfo, type) {} + + //////////////////////////////////////////////////////////////////////////// + + ScopedMessage::ScopedMessage( MessageBuilder const& builder ) + : m_info( builder.m_info ) + { + m_info.message = builder.m_stream.str(); + getResultCapture().pushScopedMessage( m_info ); + } + + ScopedMessage::~ScopedMessage() { + if ( !uncaught_exceptions() ){ + getResultCapture().popScopedMessage(m_info); + } + } + + Capturer::Capturer( StringRef macroName, SourceLineInfo const& lineInfo, ResultWas::OfType resultType, StringRef names ) { + auto trimmed = [&] (size_t start, size_t end) { + while (names[start] == ',' || isspace(names[start])) { + ++start; + } + while (names[end] == ',' || isspace(names[end])) { + --end; + } + return names.substr(start, end - start + 1); + }; + + size_t start = 0; + std::stack<char> openings; + for (size_t pos = 0; pos < names.size(); ++pos) { + char c = names[pos]; + switch (c) { + case '[': + case '{': + case '(': + // It is basically impossible to disambiguate between + // comparison and start of template args in this context +// case '<': + openings.push(c); + break; + case ']': + case '}': + case ')': +// case '>': + openings.pop(); + break; + case ',': + if (start != pos && openings.size() == 0) { + m_messages.emplace_back(macroName, lineInfo, resultType); + m_messages.back().message = trimmed(start, pos); + m_messages.back().message += " := "; + start = pos; + } + } + } + assert(openings.size() == 0 && "Mismatched openings"); + m_messages.emplace_back(macroName, lineInfo, resultType); + m_messages.back().message = trimmed(start, names.size() - 1); + m_messages.back().message += " := "; + } + Capturer::~Capturer() { + if ( !uncaught_exceptions() ){ + assert( m_captured == m_messages.size() ); + for( size_t i = 0; i < m_captured; ++i ) + m_resultCapture.popScopedMessage( m_messages[i] ); + } + } + + void Capturer::captureValue( size_t index, std::string const& value ) { + assert( index < m_messages.size() ); + m_messages[index].message += value; + m_resultCapture.pushScopedMessage( m_messages[index] ); + m_captured++; + } + +} // end namespace Catch +// end catch_message.cpp +// start catch_output_redirect.cpp + +// start catch_output_redirect.h +#ifndef TWOBLUECUBES_CATCH_OUTPUT_REDIRECT_H +#define TWOBLUECUBES_CATCH_OUTPUT_REDIRECT_H + +#include <cstdio> +#include <iosfwd> +#include <string> + +namespace Catch { + + class RedirectedStream { + std::ostream& m_originalStream; + std::ostream& m_redirectionStream; + std::streambuf* m_prevBuf; + + public: + RedirectedStream( std::ostream& originalStream, std::ostream& redirectionStream ); + ~RedirectedStream(); + }; + + class RedirectedStdOut { + ReusableStringStream m_rss; + RedirectedStream m_cout; + public: + RedirectedStdOut(); + auto str() const -> std::string; + }; + + // StdErr has two constituent streams in C++, std::cerr and std::clog + // This means that we need to redirect 2 streams into 1 to keep proper + // order of writes + class RedirectedStdErr { + ReusableStringStream m_rss; + RedirectedStream m_cerr; + RedirectedStream m_clog; + public: + RedirectedStdErr(); + auto str() const -> std::string; + }; + +#if defined(CATCH_CONFIG_NEW_CAPTURE) + + // Windows's implementation of std::tmpfile is terrible (it tries + // to create a file inside system folder, thus requiring elevated + // privileges for the binary), so we have to use tmpnam(_s) and + // create the file ourselves there. + class TempFile { + public: + TempFile(TempFile const&) = delete; + TempFile& operator=(TempFile const&) = delete; + TempFile(TempFile&&) = delete; + TempFile& operator=(TempFile&&) = delete; + + TempFile(); + ~TempFile(); + + std::FILE* getFile(); + std::string getContents(); + + private: + std::FILE* m_file = nullptr; + #if defined(_MSC_VER) + char m_buffer[L_tmpnam] = { 0 }; + #endif + }; + + class OutputRedirect { + public: + OutputRedirect(OutputRedirect const&) = delete; + OutputRedirect& operator=(OutputRedirect const&) = delete; + OutputRedirect(OutputRedirect&&) = delete; + OutputRedirect& operator=(OutputRedirect&&) = delete; + + OutputRedirect(std::string& stdout_dest, std::string& stderr_dest); + ~OutputRedirect(); + + private: + int m_originalStdout = -1; + int m_originalStderr = -1; + TempFile m_stdoutFile; + TempFile m_stderrFile; + std::string& m_stdoutDest; + std::string& m_stderrDest; + }; + +#endif + +} // end namespace Catch + +#endif // TWOBLUECUBES_CATCH_OUTPUT_REDIRECT_H +// end catch_output_redirect.h +#include <cstdio> +#include <cstring> +#include <fstream> +#include <sstream> +#include <stdexcept> + +#if defined(CATCH_CONFIG_NEW_CAPTURE) + #if defined(_MSC_VER) + #include <io.h> //_dup and _dup2 + #define dup _dup + #define dup2 _dup2 + #define fileno _fileno + #else + #include <unistd.h> // dup and dup2 + #endif +#endif + +namespace Catch { + + RedirectedStream::RedirectedStream( std::ostream& originalStream, std::ostream& redirectionStream ) + : m_originalStream( originalStream ), + m_redirectionStream( redirectionStream ), + m_prevBuf( m_originalStream.rdbuf() ) + { + m_originalStream.rdbuf( m_redirectionStream.rdbuf() ); + } + + RedirectedStream::~RedirectedStream() { + m_originalStream.rdbuf( m_prevBuf ); + } + + RedirectedStdOut::RedirectedStdOut() : m_cout( Catch::cout(), m_rss.get() ) {} + auto RedirectedStdOut::str() const -> std::string { return m_rss.str(); } + + RedirectedStdErr::RedirectedStdErr() + : m_cerr( Catch::cerr(), m_rss.get() ), + m_clog( Catch::clog(), m_rss.get() ) + {} + auto RedirectedStdErr::str() const -> std::string { return m_rss.str(); } + +#if defined(CATCH_CONFIG_NEW_CAPTURE) + +#if defined(_MSC_VER) + TempFile::TempFile() { + if (tmpnam_s(m_buffer)) { + CATCH_RUNTIME_ERROR("Could not get a temp filename"); + } + if (fopen_s(&m_file, m_buffer, "w")) { + char buffer[100]; + if (strerror_s(buffer, errno)) { + CATCH_RUNTIME_ERROR("Could not translate errno to a string"); + } + CATCH_RUNTIME_ERROR("Coul dnot open the temp file: '" << m_buffer << "' because: " << buffer); + } + } +#else + TempFile::TempFile() { + m_file = std::tmpfile(); + if (!m_file) { + CATCH_RUNTIME_ERROR("Could not create a temp file."); + } + } + +#endif + + TempFile::~TempFile() { + // TBD: What to do about errors here? + std::fclose(m_file); + // We manually create the file on Windows only, on Linux + // it will be autodeleted +#if defined(_MSC_VER) + std::remove(m_buffer); +#endif + } + + FILE* TempFile::getFile() { + return m_file; + } + + std::string TempFile::getContents() { + std::stringstream sstr; + char buffer[100] = {}; + std::rewind(m_file); + while (std::fgets(buffer, sizeof(buffer), m_file)) { + sstr << buffer; + } + return sstr.str(); + } + + OutputRedirect::OutputRedirect(std::string& stdout_dest, std::string& stderr_dest) : + m_originalStdout(dup(1)), + m_originalStderr(dup(2)), + m_stdoutDest(stdout_dest), + m_stderrDest(stderr_dest) { + dup2(fileno(m_stdoutFile.getFile()), 1); + dup2(fileno(m_stderrFile.getFile()), 2); + } + + OutputRedirect::~OutputRedirect() { + Catch::cout() << std::flush; + fflush(stdout); + // Since we support overriding these streams, we flush cerr + // even though std::cerr is unbuffered + Catch::cerr() << std::flush; + Catch::clog() << std::flush; + fflush(stderr); + + dup2(m_originalStdout, 1); + dup2(m_originalStderr, 2); + + m_stdoutDest += m_stdoutFile.getContents(); + m_stderrDest += m_stderrFile.getContents(); + } + +#endif // CATCH_CONFIG_NEW_CAPTURE + +} // namespace Catch + +#if defined(CATCH_CONFIG_NEW_CAPTURE) + #if defined(_MSC_VER) + #undef dup + #undef dup2 + #undef fileno + #endif +#endif +// end catch_output_redirect.cpp +// start catch_polyfills.cpp + +#include <cmath> + +namespace Catch { + +#if !defined(CATCH_CONFIG_POLYFILL_ISNAN) + bool isnan(float f) { + return std::isnan(f); + } + bool isnan(double d) { + return std::isnan(d); + } +#else + // For now we only use this for embarcadero + bool isnan(float f) { + return std::_isnan(f); + } + bool isnan(double d) { + return std::_isnan(d); + } +#endif + +} // end namespace Catch +// end catch_polyfills.cpp +// start catch_random_number_generator.cpp + +namespace Catch { + + std::mt19937& rng() { + static std::mt19937 s_rng; + return s_rng; + } + + void seedRng( IConfig const& config ) { + if( config.rngSeed() != 0 ) { + std::srand( config.rngSeed() ); + rng().seed( config.rngSeed() ); + } + } + + unsigned int rngSeed() { + return getCurrentContext().getConfig()->rngSeed(); + } +} +// end catch_random_number_generator.cpp +// start catch_registry_hub.cpp + +// start catch_test_case_registry_impl.h + +#include <vector> +#include <set> +#include <algorithm> +#include <ios> + +namespace Catch { + + class TestCase; + struct IConfig; + + std::vector<TestCase> sortTests( IConfig const& config, std::vector<TestCase> const& unsortedTestCases ); + bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ); + + void enforceNoDuplicateTestCases( std::vector<TestCase> const& functions ); + + std::vector<TestCase> filterTests( std::vector<TestCase> const& testCases, TestSpec const& testSpec, IConfig const& config ); + std::vector<TestCase> const& getAllTestCasesSorted( IConfig const& config ); + + class TestRegistry : public ITestCaseRegistry { + public: + virtual ~TestRegistry() = default; + + virtual void registerTest( TestCase const& testCase ); + + std::vector<TestCase> const& getAllTests() const override; + std::vector<TestCase> const& getAllTestsSorted( IConfig const& config ) const override; + + private: + std::vector<TestCase> m_functions; + mutable RunTests::InWhatOrder m_currentSortOrder = RunTests::InDeclarationOrder; + mutable std::vector<TestCase> m_sortedFunctions; + std::size_t m_unnamedCount = 0; + std::ios_base::Init m_ostreamInit; // Forces cout/ cerr to be initialised + }; + + /////////////////////////////////////////////////////////////////////////// + + class TestInvokerAsFunction : public ITestInvoker { + void(*m_testAsFunction)(); + public: + TestInvokerAsFunction( void(*testAsFunction)() ) noexcept; + + void invoke() const override; + }; + + std::string extractClassName( StringRef const& classOrQualifiedMethodName ); + + /////////////////////////////////////////////////////////////////////////// + +} // end namespace Catch + +// end catch_test_case_registry_impl.h +// start catch_reporter_registry.h + +#include <map> + +namespace Catch { + + class ReporterRegistry : public IReporterRegistry { + + public: + + ~ReporterRegistry() override; + + IStreamingReporterPtr create( std::string const& name, IConfigPtr const& config ) const override; + + void registerReporter( std::string const& name, IReporterFactoryPtr const& factory ); + void registerListener( IReporterFactoryPtr const& factory ); + + FactoryMap const& getFactories() const override; + Listeners const& getListeners() const override; + + private: + FactoryMap m_factories; + Listeners m_listeners; + }; +} + +// end catch_reporter_registry.h +// start catch_tag_alias_registry.h + +// start catch_tag_alias.h + +#include <string> + +namespace Catch { + + struct TagAlias { + TagAlias(std::string const& _tag, SourceLineInfo _lineInfo); + + std::string tag; + SourceLineInfo lineInfo; + }; + +} // end namespace Catch + +// end catch_tag_alias.h +#include <map> + +namespace Catch { + + class TagAliasRegistry : public ITagAliasRegistry { + public: + ~TagAliasRegistry() override; + TagAlias const* find( std::string const& alias ) const override; + std::string expandAliases( std::string const& unexpandedTestSpec ) const override; + void add( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ); + + private: + std::map<std::string, TagAlias> m_registry; + }; + +} // end namespace Catch + +// end catch_tag_alias_registry.h +// start catch_startup_exception_registry.h + +#include <vector> +#include <exception> + +namespace Catch { + + class StartupExceptionRegistry { + public: + void add(std::exception_ptr const& exception) noexcept; + std::vector<std::exception_ptr> const& getExceptions() const noexcept; + private: + std::vector<std::exception_ptr> m_exceptions; + }; + +} // end namespace Catch + +// end catch_startup_exception_registry.h +// start catch_singletons.hpp + +namespace Catch { + + struct ISingleton { + virtual ~ISingleton(); + }; + + void addSingleton( ISingleton* singleton ); + void cleanupSingletons(); + + template<typename SingletonImplT, typename InterfaceT = SingletonImplT, typename MutableInterfaceT = InterfaceT> + class Singleton : SingletonImplT, public ISingleton { + + static auto getInternal() -> Singleton* { + static Singleton* s_instance = nullptr; + if( !s_instance ) { + s_instance = new Singleton; + addSingleton( s_instance ); + } + return s_instance; + } + + public: + static auto get() -> InterfaceT const& { + return *getInternal(); + } + static auto getMutable() -> MutableInterfaceT& { + return *getInternal(); + } + }; + +} // namespace Catch + +// end catch_singletons.hpp +namespace Catch { + + namespace { + + class RegistryHub : public IRegistryHub, public IMutableRegistryHub, + private NonCopyable { + + public: // IRegistryHub + RegistryHub() = default; + IReporterRegistry const& getReporterRegistry() const override { + return m_reporterRegistry; + } + ITestCaseRegistry const& getTestCaseRegistry() const override { + return m_testCaseRegistry; + } + IExceptionTranslatorRegistry const& getExceptionTranslatorRegistry() const override { + return m_exceptionTranslatorRegistry; + } + ITagAliasRegistry const& getTagAliasRegistry() const override { + return m_tagAliasRegistry; + } + StartupExceptionRegistry const& getStartupExceptionRegistry() const override { + return m_exceptionRegistry; + } + + public: // IMutableRegistryHub + void registerReporter( std::string const& name, IReporterFactoryPtr const& factory ) override { + m_reporterRegistry.registerReporter( name, factory ); + } + void registerListener( IReporterFactoryPtr const& factory ) override { + m_reporterRegistry.registerListener( factory ); + } + void registerTest( TestCase const& testInfo ) override { + m_testCaseRegistry.registerTest( testInfo ); + } + void registerTranslator( const IExceptionTranslator* translator ) override { + m_exceptionTranslatorRegistry.registerTranslator( translator ); + } + void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) override { + m_tagAliasRegistry.add( alias, tag, lineInfo ); + } + void registerStartupException() noexcept override { + m_exceptionRegistry.add(std::current_exception()); + } + + private: + TestRegistry m_testCaseRegistry; + ReporterRegistry m_reporterRegistry; + ExceptionTranslatorRegistry m_exceptionTranslatorRegistry; + TagAliasRegistry m_tagAliasRegistry; + StartupExceptionRegistry m_exceptionRegistry; + }; + } + + using RegistryHubSingleton = Singleton<RegistryHub, IRegistryHub, IMutableRegistryHub>; + + IRegistryHub const& getRegistryHub() { + return RegistryHubSingleton::get(); + } + IMutableRegistryHub& getMutableRegistryHub() { + return RegistryHubSingleton::getMutable(); + } + void cleanUp() { + cleanupSingletons(); + cleanUpContext(); + } + std::string translateActiveException() { + return getRegistryHub().getExceptionTranslatorRegistry().translateActiveException(); + } + +} // end namespace Catch +// end catch_registry_hub.cpp +// start catch_reporter_registry.cpp + +namespace Catch { + + ReporterRegistry::~ReporterRegistry() = default; + + IStreamingReporterPtr ReporterRegistry::create( std::string const& name, IConfigPtr const& config ) const { + auto it = m_factories.find( name ); + if( it == m_factories.end() ) + return nullptr; + return it->second->create( ReporterConfig( config ) ); + } + + void ReporterRegistry::registerReporter( std::string const& name, IReporterFactoryPtr const& factory ) { + m_factories.emplace(name, factory); + } + void ReporterRegistry::registerListener( IReporterFactoryPtr const& factory ) { + m_listeners.push_back( factory ); + } + + IReporterRegistry::FactoryMap const& ReporterRegistry::getFactories() const { + return m_factories; + } + IReporterRegistry::Listeners const& ReporterRegistry::getListeners() const { + return m_listeners; + } + +} +// end catch_reporter_registry.cpp +// start catch_result_type.cpp + +namespace Catch { + + bool isOk( ResultWas::OfType resultType ) { + return ( resultType & ResultWas::FailureBit ) == 0; + } + bool isJustInfo( int flags ) { + return flags == ResultWas::Info; + } + + ResultDisposition::Flags operator | ( ResultDisposition::Flags lhs, ResultDisposition::Flags rhs ) { + return static_cast<ResultDisposition::Flags>( static_cast<int>( lhs ) | static_cast<int>( rhs ) ); + } + + bool shouldContinueOnFailure( int flags ) { return ( flags & ResultDisposition::ContinueOnFailure ) != 0; } + bool shouldSuppressFailure( int flags ) { return ( flags & ResultDisposition::SuppressFail ) != 0; } + +} // end namespace Catch +// end catch_result_type.cpp +// start catch_run_context.cpp + +#include <cassert> +#include <algorithm> +#include <sstream> + +namespace Catch { + + namespace Generators { + struct GeneratorTracker : TestCaseTracking::TrackerBase, IGeneratorTracker { + size_t m_index = static_cast<size_t>( -1 ); + GeneratorBasePtr m_generator; + + GeneratorTracker( TestCaseTracking::NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ) + : TrackerBase( nameAndLocation, ctx, parent ) + {} + ~GeneratorTracker(); + + static GeneratorTracker& acquire( TrackerContext& ctx, TestCaseTracking::NameAndLocation const& nameAndLocation ) { + std::shared_ptr<GeneratorTracker> tracker; + + ITracker& currentTracker = ctx.currentTracker(); + if( TestCaseTracking::ITrackerPtr childTracker = currentTracker.findChild( nameAndLocation ) ) { + assert( childTracker ); + assert( childTracker->isIndexTracker() ); + tracker = std::static_pointer_cast<GeneratorTracker>( childTracker ); + } + else { + tracker = std::make_shared<GeneratorTracker>( nameAndLocation, ctx, ¤tTracker ); + currentTracker.addChild( tracker ); + } + + if( !ctx.completedCycle() && !tracker->isComplete() ) { + if( tracker->m_runState != ExecutingChildren && tracker->m_runState != NeedsAnotherRun ) + tracker->moveNext(); + tracker->open(); + } + + return *tracker; + } + + void moveNext() { + m_index++; + m_children.clear(); + } + + // TrackerBase interface + bool isIndexTracker() const override { return true; } + auto hasGenerator() const -> bool override { + return !!m_generator; + } + void close() override { + TrackerBase::close(); + if( m_runState == CompletedSuccessfully && m_index < m_generator->size()-1 ) + m_runState = Executing; + } + + // IGeneratorTracker interface + auto getGenerator() const -> GeneratorBasePtr const& override { + return m_generator; + } + void setGenerator( GeneratorBasePtr&& generator ) override { + m_generator = std::move( generator ); + } + auto getIndex() const -> size_t override { + return m_index; + } + }; + GeneratorTracker::~GeneratorTracker() {} + } + + RunContext::RunContext(IConfigPtr const& _config, IStreamingReporterPtr&& reporter) + : m_runInfo(_config->name()), + m_context(getCurrentMutableContext()), + m_config(_config), + m_reporter(std::move(reporter)), + m_lastAssertionInfo{ StringRef(), SourceLineInfo("",0), StringRef(), ResultDisposition::Normal }, + m_includeSuccessfulResults( m_config->includeSuccessfulResults() || m_reporter->getPreferences().shouldReportAllAssertions ) + { + m_context.setRunner(this); + m_context.setConfig(m_config); + m_context.setResultCapture(this); + m_reporter->testRunStarting(m_runInfo); + } + + RunContext::~RunContext() { + m_reporter->testRunEnded(TestRunStats(m_runInfo, m_totals, aborting())); + } + + void RunContext::testGroupStarting(std::string const& testSpec, std::size_t groupIndex, std::size_t groupsCount) { + m_reporter->testGroupStarting(GroupInfo(testSpec, groupIndex, groupsCount)); + } + + void RunContext::testGroupEnded(std::string const& testSpec, Totals const& totals, std::size_t groupIndex, std::size_t groupsCount) { + m_reporter->testGroupEnded(TestGroupStats(GroupInfo(testSpec, groupIndex, groupsCount), totals, aborting())); + } + + Totals RunContext::runTest(TestCase const& testCase) { + Totals prevTotals = m_totals; + + std::string redirectedCout; + std::string redirectedCerr; + + auto const& testInfo = testCase.getTestCaseInfo(); + + m_reporter->testCaseStarting(testInfo); + + m_activeTestCase = &testCase; + + ITracker& rootTracker = m_trackerContext.startRun(); + assert(rootTracker.isSectionTracker()); + static_cast<SectionTracker&>(rootTracker).addInitialFilters(m_config->getSectionsToRun()); + do { + m_trackerContext.startCycle(); + m_testCaseTracker = &SectionTracker::acquire(m_trackerContext, TestCaseTracking::NameAndLocation(testInfo.name, testInfo.lineInfo)); + runCurrentTest(redirectedCout, redirectedCerr); + } while (!m_testCaseTracker->isSuccessfullyCompleted() && !aborting()); + + Totals deltaTotals = m_totals.delta(prevTotals); + if (testInfo.expectedToFail() && deltaTotals.testCases.passed > 0) { + deltaTotals.assertions.failed++; + deltaTotals.testCases.passed--; + deltaTotals.testCases.failed++; + } + m_totals.testCases += deltaTotals.testCases; + m_reporter->testCaseEnded(TestCaseStats(testInfo, + deltaTotals, + redirectedCout, + redirectedCerr, + aborting())); + + m_activeTestCase = nullptr; + m_testCaseTracker = nullptr; + + return deltaTotals; + } + + IConfigPtr RunContext::config() const { + return m_config; + } + + IStreamingReporter& RunContext::reporter() const { + return *m_reporter; + } + + void RunContext::assertionEnded(AssertionResult const & result) { + if (result.getResultType() == ResultWas::Ok) { + m_totals.assertions.passed++; + m_lastAssertionPassed = true; + } else if (!result.isOk()) { + m_lastAssertionPassed = false; + if( m_activeTestCase->getTestCaseInfo().okToFail() ) + m_totals.assertions.failedButOk++; + else + m_totals.assertions.failed++; + } + else { + m_lastAssertionPassed = true; + } + + // We have no use for the return value (whether messages should be cleared), because messages were made scoped + // and should be let to clear themselves out. + static_cast<void>(m_reporter->assertionEnded(AssertionStats(result, m_messages, m_totals))); + + // Reset working state + resetAssertionInfo(); + m_lastResult = result; + } + void RunContext::resetAssertionInfo() { + m_lastAssertionInfo.macroName = StringRef(); + m_lastAssertionInfo.capturedExpression = "{Unknown expression after the reported line}"_sr; + } + + bool RunContext::sectionStarted(SectionInfo const & sectionInfo, Counts & assertions) { + ITracker& sectionTracker = SectionTracker::acquire(m_trackerContext, TestCaseTracking::NameAndLocation(sectionInfo.name, sectionInfo.lineInfo)); + if (!sectionTracker.isOpen()) + return false; + m_activeSections.push_back(§ionTracker); + + m_lastAssertionInfo.lineInfo = sectionInfo.lineInfo; + + m_reporter->sectionStarting(sectionInfo); + + assertions = m_totals.assertions; + + return true; + } + auto RunContext::acquireGeneratorTracker( SourceLineInfo const& lineInfo ) -> IGeneratorTracker& { + using namespace Generators; + GeneratorTracker& tracker = GeneratorTracker::acquire( m_trackerContext, TestCaseTracking::NameAndLocation( "generator", lineInfo ) ); + assert( tracker.isOpen() ); + m_lastAssertionInfo.lineInfo = lineInfo; + return tracker; + } + + bool RunContext::testForMissingAssertions(Counts& assertions) { + if (assertions.total() != 0) + return false; + if (!m_config->warnAboutMissingAssertions()) + return false; + if (m_trackerContext.currentTracker().hasChildren()) + return false; + m_totals.assertions.failed++; + assertions.failed++; + return true; + } + + void RunContext::sectionEnded(SectionEndInfo const & endInfo) { + Counts assertions = m_totals.assertions - endInfo.prevAssertions; + bool missingAssertions = testForMissingAssertions(assertions); + + if (!m_activeSections.empty()) { + m_activeSections.back()->close(); + m_activeSections.pop_back(); + } + + m_reporter->sectionEnded(SectionStats(endInfo.sectionInfo, assertions, endInfo.durationInSeconds, missingAssertions)); + m_messages.clear(); + } + + void RunContext::sectionEndedEarly(SectionEndInfo const & endInfo) { + if (m_unfinishedSections.empty()) + m_activeSections.back()->fail(); + else + m_activeSections.back()->close(); + m_activeSections.pop_back(); + + m_unfinishedSections.push_back(endInfo); + } + void RunContext::benchmarkStarting( BenchmarkInfo const& info ) { + m_reporter->benchmarkStarting( info ); + } + void RunContext::benchmarkEnded( BenchmarkStats const& stats ) { + m_reporter->benchmarkEnded( stats ); + } + + void RunContext::pushScopedMessage(MessageInfo const & message) { + m_messages.push_back(message); + } + + void RunContext::popScopedMessage(MessageInfo const & message) { + m_messages.erase(std::remove(m_messages.begin(), m_messages.end(), message), m_messages.end()); + } + + std::string RunContext::getCurrentTestName() const { + return m_activeTestCase + ? m_activeTestCase->getTestCaseInfo().name + : std::string(); + } + + const AssertionResult * RunContext::getLastResult() const { + return &(*m_lastResult); + } + + void RunContext::exceptionEarlyReported() { + m_shouldReportUnexpected = false; + } + + void RunContext::handleFatalErrorCondition( StringRef message ) { + // First notify reporter that bad things happened + m_reporter->fatalErrorEncountered(message); + + // Don't rebuild the result -- the stringification itself can cause more fatal errors + // Instead, fake a result data. + AssertionResultData tempResult( ResultWas::FatalErrorCondition, { false } ); + tempResult.message = message; + AssertionResult result(m_lastAssertionInfo, tempResult); + + assertionEnded(result); + + handleUnfinishedSections(); + + // Recreate section for test case (as we will lose the one that was in scope) + auto const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); + SectionInfo testCaseSection(testCaseInfo.lineInfo, testCaseInfo.name); + + Counts assertions; + assertions.failed = 1; + SectionStats testCaseSectionStats(testCaseSection, assertions, 0, false); + m_reporter->sectionEnded(testCaseSectionStats); + + auto const& testInfo = m_activeTestCase->getTestCaseInfo(); + + Totals deltaTotals; + deltaTotals.testCases.failed = 1; + deltaTotals.assertions.failed = 1; + m_reporter->testCaseEnded(TestCaseStats(testInfo, + deltaTotals, + std::string(), + std::string(), + false)); + m_totals.testCases.failed++; + testGroupEnded(std::string(), m_totals, 1, 1); + m_reporter->testRunEnded(TestRunStats(m_runInfo, m_totals, false)); + } + + bool RunContext::lastAssertionPassed() { + return m_lastAssertionPassed; + } + + void RunContext::assertionPassed() { + m_lastAssertionPassed = true; + ++m_totals.assertions.passed; + resetAssertionInfo(); + } + + bool RunContext::aborting() const { + return m_totals.assertions.failed >= static_cast<std::size_t>(m_config->abortAfter()); + } + + void RunContext::runCurrentTest(std::string & redirectedCout, std::string & redirectedCerr) { + auto const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); + SectionInfo testCaseSection(testCaseInfo.lineInfo, testCaseInfo.name); + m_reporter->sectionStarting(testCaseSection); + Counts prevAssertions = m_totals.assertions; + double duration = 0; + m_shouldReportUnexpected = true; + m_lastAssertionInfo = { "TEST_CASE"_sr, testCaseInfo.lineInfo, StringRef(), ResultDisposition::Normal }; + + seedRng(*m_config); + + Timer timer; + CATCH_TRY { + if (m_reporter->getPreferences().shouldRedirectStdOut) { +#if !defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT) + RedirectedStdOut redirectedStdOut; + RedirectedStdErr redirectedStdErr; + + timer.start(); + invokeActiveTestCase(); + redirectedCout += redirectedStdOut.str(); + redirectedCerr += redirectedStdErr.str(); +#else + OutputRedirect r(redirectedCout, redirectedCerr); + timer.start(); + invokeActiveTestCase(); +#endif + } else { + timer.start(); + invokeActiveTestCase(); + } + duration = timer.getElapsedSeconds(); + } CATCH_CATCH_ANON (TestFailureException&) { + // This just means the test was aborted due to failure + } CATCH_CATCH_ALL { + // Under CATCH_CONFIG_FAST_COMPILE, unexpected exceptions under REQUIRE assertions + // are reported without translation at the point of origin. + if( m_shouldReportUnexpected ) { + AssertionReaction dummyReaction; + handleUnexpectedInflightException( m_lastAssertionInfo, translateActiveException(), dummyReaction ); + } + } + Counts assertions = m_totals.assertions - prevAssertions; + bool missingAssertions = testForMissingAssertions(assertions); + + m_testCaseTracker->close(); + handleUnfinishedSections(); + m_messages.clear(); + + SectionStats testCaseSectionStats(testCaseSection, assertions, duration, missingAssertions); + m_reporter->sectionEnded(testCaseSectionStats); + } + + void RunContext::invokeActiveTestCase() { + FatalConditionHandler fatalConditionHandler; // Handle signals + m_activeTestCase->invoke(); + fatalConditionHandler.reset(); + } + + void RunContext::handleUnfinishedSections() { + // If sections ended prematurely due to an exception we stored their + // infos here so we can tear them down outside the unwind process. + for (auto it = m_unfinishedSections.rbegin(), + itEnd = m_unfinishedSections.rend(); + it != itEnd; + ++it) + sectionEnded(*it); + m_unfinishedSections.clear(); + } + + void RunContext::handleExpr( + AssertionInfo const& info, + ITransientExpression const& expr, + AssertionReaction& reaction + ) { + m_reporter->assertionStarting( info ); + + bool negated = isFalseTest( info.resultDisposition ); + bool result = expr.getResult() != negated; + + if( result ) { + if (!m_includeSuccessfulResults) { + assertionPassed(); + } + else { + reportExpr(info, ResultWas::Ok, &expr, negated); + } + } + else { + reportExpr(info, ResultWas::ExpressionFailed, &expr, negated ); + populateReaction( reaction ); + } + } + void RunContext::reportExpr( + AssertionInfo const &info, + ResultWas::OfType resultType, + ITransientExpression const *expr, + bool negated ) { + + m_lastAssertionInfo = info; + AssertionResultData data( resultType, LazyExpression( negated ) ); + + AssertionResult assertionResult{ info, data }; + assertionResult.m_resultData.lazyExpression.m_transientExpression = expr; + + assertionEnded( assertionResult ); + } + + void RunContext::handleMessage( + AssertionInfo const& info, + ResultWas::OfType resultType, + StringRef const& message, + AssertionReaction& reaction + ) { + m_reporter->assertionStarting( info ); + + m_lastAssertionInfo = info; + + AssertionResultData data( resultType, LazyExpression( false ) ); + data.message = message; + AssertionResult assertionResult{ m_lastAssertionInfo, data }; + assertionEnded( assertionResult ); + if( !assertionResult.isOk() ) + populateReaction( reaction ); + } + void RunContext::handleUnexpectedExceptionNotThrown( + AssertionInfo const& info, + AssertionReaction& reaction + ) { + handleNonExpr(info, Catch::ResultWas::DidntThrowException, reaction); + } + + void RunContext::handleUnexpectedInflightException( + AssertionInfo const& info, + std::string const& message, + AssertionReaction& reaction + ) { + m_lastAssertionInfo = info; + + AssertionResultData data( ResultWas::ThrewException, LazyExpression( false ) ); + data.message = message; + AssertionResult assertionResult{ info, data }; + assertionEnded( assertionResult ); + populateReaction( reaction ); + } + + void RunContext::populateReaction( AssertionReaction& reaction ) { + reaction.shouldDebugBreak = m_config->shouldDebugBreak(); + reaction.shouldThrow = aborting() || (m_lastAssertionInfo.resultDisposition & ResultDisposition::Normal); + } + + void RunContext::handleIncomplete( + AssertionInfo const& info + ) { + m_lastAssertionInfo = info; + + AssertionResultData data( ResultWas::ThrewException, LazyExpression( false ) ); + data.message = "Exception translation was disabled by CATCH_CONFIG_FAST_COMPILE"; + AssertionResult assertionResult{ info, data }; + assertionEnded( assertionResult ); + } + void RunContext::handleNonExpr( + AssertionInfo const &info, + ResultWas::OfType resultType, + AssertionReaction &reaction + ) { + m_lastAssertionInfo = info; + + AssertionResultData data( resultType, LazyExpression( false ) ); + AssertionResult assertionResult{ info, data }; + assertionEnded( assertionResult ); + + if( !assertionResult.isOk() ) + populateReaction( reaction ); + } + + IResultCapture& getResultCapture() { + if (auto* capture = getCurrentContext().getResultCapture()) + return *capture; + else + CATCH_INTERNAL_ERROR("No result capture instance"); + } +} +// end catch_run_context.cpp +// start catch_section.cpp + +namespace Catch { + + Section::Section( SectionInfo const& info ) + : m_info( info ), + m_sectionIncluded( getResultCapture().sectionStarted( m_info, m_assertions ) ) + { + m_timer.start(); + } + + Section::~Section() { + if( m_sectionIncluded ) { + SectionEndInfo endInfo{ m_info, m_assertions, m_timer.getElapsedSeconds() }; + if( uncaught_exceptions() ) + getResultCapture().sectionEndedEarly( endInfo ); + else + getResultCapture().sectionEnded( endInfo ); + } + } + + // This indicates whether the section should be executed or not + Section::operator bool() const { + return m_sectionIncluded; + } + +} // end namespace Catch +// end catch_section.cpp +// start catch_section_info.cpp + +namespace Catch { + + SectionInfo::SectionInfo + ( SourceLineInfo const& _lineInfo, + std::string const& _name ) + : name( _name ), + lineInfo( _lineInfo ) + {} + +} // end namespace Catch +// end catch_section_info.cpp +// start catch_session.cpp + +// start catch_session.h + +#include <memory> + +namespace Catch { + + class Session : NonCopyable { + public: + + Session(); + ~Session() override; + + void showHelp() const; + void libIdentify(); + + int applyCommandLine( int argc, char const * const * argv ); + #if defined(CATCH_CONFIG_WCHAR) && defined(WIN32) && defined(UNICODE) + int applyCommandLine( int argc, wchar_t const * const * argv ); + #endif + + void useConfigData( ConfigData const& configData ); + + template<typename CharT> + int run(int argc, CharT const * const argv[]) { + if (m_startupExceptions) + return 1; + int returnCode = applyCommandLine(argc, argv); + if (returnCode == 0) + returnCode = run(); + return returnCode; + } + + int run(); + + clara::Parser const& cli() const; + void cli( clara::Parser const& newParser ); + ConfigData& configData(); + Config& config(); + private: + int runInternal(); + + clara::Parser m_cli; + ConfigData m_configData; + std::shared_ptr<Config> m_config; + bool m_startupExceptions = false; + }; + +} // end namespace Catch + +// end catch_session.h +// start catch_version.h + +#include <iosfwd> + +namespace Catch { + + // Versioning information + struct Version { + Version( Version const& ) = delete; + Version& operator=( Version const& ) = delete; + Version( unsigned int _majorVersion, + unsigned int _minorVersion, + unsigned int _patchNumber, + char const * const _branchName, + unsigned int _buildNumber ); + + unsigned int const majorVersion; + unsigned int const minorVersion; + unsigned int const patchNumber; + + // buildNumber is only used if branchName is not null + char const * const branchName; + unsigned int const buildNumber; + + friend std::ostream& operator << ( std::ostream& os, Version const& version ); + }; + + Version const& libraryVersion(); +} + +// end catch_version.h +#include <cstdlib> +#include <iomanip> + +namespace Catch { + + namespace { + const int MaxExitCode = 255; + + IStreamingReporterPtr createReporter(std::string const& reporterName, IConfigPtr const& config) { + auto reporter = Catch::getRegistryHub().getReporterRegistry().create(reporterName, config); + CATCH_ENFORCE(reporter, "No reporter registered with name: '" << reporterName << "'"); + + return reporter; + } + + IStreamingReporterPtr makeReporter(std::shared_ptr<Config> const& config) { + if (Catch::getRegistryHub().getReporterRegistry().getListeners().empty()) { + return createReporter(config->getReporterName(), config); + } + + auto multi = std::unique_ptr<ListeningReporter>(new ListeningReporter); + + auto const& listeners = Catch::getRegistryHub().getReporterRegistry().getListeners(); + for (auto const& listener : listeners) { + multi->addListener(listener->create(Catch::ReporterConfig(config))); + } + multi->addReporter(createReporter(config->getReporterName(), config)); + return std::move(multi); + } + + Catch::Totals runTests(std::shared_ptr<Config> const& config) { + auto reporter = makeReporter(config); + + RunContext context(config, std::move(reporter)); + + Totals totals; + + context.testGroupStarting(config->name(), 1, 1); + + TestSpec testSpec = config->testSpec(); + + auto const& allTestCases = getAllTestCasesSorted(*config); + for (auto const& testCase : allTestCases) { + if (!context.aborting() && matchTest(testCase, testSpec, *config)) + totals += context.runTest(testCase); + else + context.reporter().skipTest(testCase); + } + + if (config->warnAboutNoTests() && totals.testCases.total() == 0) { + ReusableStringStream testConfig; + + bool first = true; + for (const auto& input : config->getTestsOrTags()) { + if (!first) { testConfig << ' '; } + first = false; + testConfig << input; + } + + context.reporter().noMatchingTestCases(testConfig.str()); + totals.error = -1; + } + + context.testGroupEnded(config->name(), totals, 1, 1); + return totals; + } + + void applyFilenamesAsTags(Catch::IConfig const& config) { + auto& tests = const_cast<std::vector<TestCase>&>(getAllTestCasesSorted(config)); + for (auto& testCase : tests) { + auto tags = testCase.tags; + + std::string filename = testCase.lineInfo.file; + auto lastSlash = filename.find_last_of("\\/"); + if (lastSlash != std::string::npos) { + filename.erase(0, lastSlash); + filename[0] = '#'; + } + + auto lastDot = filename.find_last_of('.'); + if (lastDot != std::string::npos) { + filename.erase(lastDot); + } + + tags.push_back(std::move(filename)); + setTags(testCase, tags); + } + } + + } // anon namespace + + Session::Session() { + static bool alreadyInstantiated = false; + if( alreadyInstantiated ) { + CATCH_TRY { CATCH_INTERNAL_ERROR( "Only one instance of Catch::Session can ever be used" ); } + CATCH_CATCH_ALL { getMutableRegistryHub().registerStartupException(); } + } + + // There cannot be exceptions at startup in no-exception mode. +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + const auto& exceptions = getRegistryHub().getStartupExceptionRegistry().getExceptions(); + if ( !exceptions.empty() ) { + m_startupExceptions = true; + Colour colourGuard( Colour::Red ); + Catch::cerr() << "Errors occurred during startup!" << '\n'; + // iterate over all exceptions and notify user + for ( const auto& ex_ptr : exceptions ) { + try { + std::rethrow_exception(ex_ptr); + } catch ( std::exception const& ex ) { + Catch::cerr() << Column( ex.what() ).indent(2) << '\n'; + } + } + } +#endif + + alreadyInstantiated = true; + m_cli = makeCommandLineParser( m_configData ); + } + Session::~Session() { + Catch::cleanUp(); + } + + void Session::showHelp() const { + Catch::cout() + << "\nCatch v" << libraryVersion() << "\n" + << m_cli << std::endl + << "For more detailed usage please see the project docs\n" << std::endl; + } + void Session::libIdentify() { + Catch::cout() + << std::left << std::setw(16) << "description: " << "A Catch test executable\n" + << std::left << std::setw(16) << "category: " << "testframework\n" + << std::left << std::setw(16) << "framework: " << "Catch Test\n" + << std::left << std::setw(16) << "version: " << libraryVersion() << std::endl; + } + + int Session::applyCommandLine( int argc, char const * const * argv ) { + if( m_startupExceptions ) + return 1; + + auto result = m_cli.parse( clara::Args( argc, argv ) ); + if( !result ) { + Catch::cerr() + << Colour( Colour::Red ) + << "\nError(s) in input:\n" + << Column( result.errorMessage() ).indent( 2 ) + << "\n\n"; + Catch::cerr() << "Run with -? for usage\n" << std::endl; + return MaxExitCode; + } + + if( m_configData.showHelp ) + showHelp(); + if( m_configData.libIdentify ) + libIdentify(); + m_config.reset(); + return 0; + } + +#if defined(CATCH_CONFIG_WCHAR) && defined(WIN32) && defined(UNICODE) + int Session::applyCommandLine( int argc, wchar_t const * const * argv ) { + + char **utf8Argv = new char *[ argc ]; + + for ( int i = 0; i < argc; ++i ) { + int bufSize = WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, NULL, 0, NULL, NULL ); + + utf8Argv[ i ] = new char[ bufSize ]; + + WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, utf8Argv[i], bufSize, NULL, NULL ); + } + + int returnCode = applyCommandLine( argc, utf8Argv ); + + for ( int i = 0; i < argc; ++i ) + delete [] utf8Argv[ i ]; + + delete [] utf8Argv; + + return returnCode; + } +#endif + + void Session::useConfigData( ConfigData const& configData ) { + m_configData = configData; + m_config.reset(); + } + + int Session::run() { + if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeStart ) != 0 ) { + Catch::cout() << "...waiting for enter/ return before starting" << std::endl; + static_cast<void>(std::getchar()); + } + int exitCode = runInternal(); + if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeExit ) != 0 ) { + Catch::cout() << "...waiting for enter/ return before exiting, with code: " << exitCode << std::endl; + static_cast<void>(std::getchar()); + } + return exitCode; + } + + clara::Parser const& Session::cli() const { + return m_cli; + } + void Session::cli( clara::Parser const& newParser ) { + m_cli = newParser; + } + ConfigData& Session::configData() { + return m_configData; + } + Config& Session::config() { + if( !m_config ) + m_config = std::make_shared<Config>( m_configData ); + return *m_config; + } + + int Session::runInternal() { + if( m_startupExceptions ) + return 1; + + if (m_configData.showHelp || m_configData.libIdentify) { + return 0; + } + + CATCH_TRY { + config(); // Force config to be constructed + + seedRng( *m_config ); + + if( m_configData.filenamesAsTags ) + applyFilenamesAsTags( *m_config ); + + // Handle list request + if( Option<std::size_t> listed = list( config() ) ) + return static_cast<int>( *listed ); + + auto totals = runTests( m_config ); + // Note that on unices only the lower 8 bits are usually used, clamping + // the return value to 255 prevents false negative when some multiple + // of 256 tests has failed + return (std::min) (MaxExitCode, (std::max) (totals.error, static_cast<int>(totals.assertions.failed))); + } +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + catch( std::exception& ex ) { + Catch::cerr() << ex.what() << std::endl; + return MaxExitCode; + } +#endif + } + +} // end namespace Catch +// end catch_session.cpp +// start catch_singletons.cpp + +#include <vector> + +namespace Catch { + + namespace { + static auto getSingletons() -> std::vector<ISingleton*>*& { + static std::vector<ISingleton*>* g_singletons = nullptr; + if( !g_singletons ) + g_singletons = new std::vector<ISingleton*>(); + return g_singletons; + } + } + + ISingleton::~ISingleton() {} + + void addSingleton(ISingleton* singleton ) { + getSingletons()->push_back( singleton ); + } + void cleanupSingletons() { + auto& singletons = getSingletons(); + for( auto singleton : *singletons ) + delete singleton; + delete singletons; + singletons = nullptr; + } + +} // namespace Catch +// end catch_singletons.cpp +// start catch_startup_exception_registry.cpp + +namespace Catch { +void StartupExceptionRegistry::add( std::exception_ptr const& exception ) noexcept { + CATCH_TRY { + m_exceptions.push_back(exception); + } CATCH_CATCH_ALL { + // If we run out of memory during start-up there's really not a lot more we can do about it + std::terminate(); + } + } + + std::vector<std::exception_ptr> const& StartupExceptionRegistry::getExceptions() const noexcept { + return m_exceptions; + } + +} // end namespace Catch +// end catch_startup_exception_registry.cpp +// start catch_stream.cpp + +#include <cstdio> +#include <iostream> +#include <fstream> +#include <sstream> +#include <vector> +#include <memory> + +namespace Catch { + + Catch::IStream::~IStream() = default; + + namespace detail { namespace { + template<typename WriterF, std::size_t bufferSize=256> + class StreamBufImpl : public std::streambuf { + char data[bufferSize]; + WriterF m_writer; + + public: + StreamBufImpl() { + setp( data, data + sizeof(data) ); + } + + ~StreamBufImpl() noexcept { + StreamBufImpl::sync(); + } + + private: + int overflow( int c ) override { + sync(); + + if( c != EOF ) { + if( pbase() == epptr() ) + m_writer( std::string( 1, static_cast<char>( c ) ) ); + else + sputc( static_cast<char>( c ) ); + } + return 0; + } + + int sync() override { + if( pbase() != pptr() ) { + m_writer( std::string( pbase(), static_cast<std::string::size_type>( pptr() - pbase() ) ) ); + setp( pbase(), epptr() ); + } + return 0; + } + }; + + /////////////////////////////////////////////////////////////////////////// + + struct OutputDebugWriter { + + void operator()( std::string const&str ) { + writeToDebugConsole( str ); + } + }; + + /////////////////////////////////////////////////////////////////////////// + + class FileStream : public IStream { + mutable std::ofstream m_ofs; + public: + FileStream( StringRef filename ) { + m_ofs.open( filename.c_str() ); + CATCH_ENFORCE( !m_ofs.fail(), "Unable to open file: '" << filename << "'" ); + } + ~FileStream() override = default; + public: // IStream + std::ostream& stream() const override { + return m_ofs; + } + }; + + /////////////////////////////////////////////////////////////////////////// + + class CoutStream : public IStream { + mutable std::ostream m_os; + public: + // Store the streambuf from cout up-front because + // cout may get redirected when running tests + CoutStream() : m_os( Catch::cout().rdbuf() ) {} + ~CoutStream() override = default; + + public: // IStream + std::ostream& stream() const override { return m_os; } + }; + + /////////////////////////////////////////////////////////////////////////// + + class DebugOutStream : public IStream { + std::unique_ptr<StreamBufImpl<OutputDebugWriter>> m_streamBuf; + mutable std::ostream m_os; + public: + DebugOutStream() + : m_streamBuf( new StreamBufImpl<OutputDebugWriter>() ), + m_os( m_streamBuf.get() ) + {} + + ~DebugOutStream() override = default; + + public: // IStream + std::ostream& stream() const override { return m_os; } + }; + + }} // namespace anon::detail + + /////////////////////////////////////////////////////////////////////////// + + auto makeStream( StringRef const &filename ) -> IStream const* { + if( filename.empty() ) + return new detail::CoutStream(); + else if( filename[0] == '%' ) { + if( filename == "%debug" ) + return new detail::DebugOutStream(); + else + CATCH_ERROR( "Unrecognised stream: '" << filename << "'" ); + } + else + return new detail::FileStream( filename ); + } + + // This class encapsulates the idea of a pool of ostringstreams that can be reused. + struct StringStreams { + std::vector<std::unique_ptr<std::ostringstream>> m_streams; + std::vector<std::size_t> m_unused; + std::ostringstream m_referenceStream; // Used for copy state/ flags from + + auto add() -> std::size_t { + if( m_unused.empty() ) { + m_streams.push_back( std::unique_ptr<std::ostringstream>( new std::ostringstream ) ); + return m_streams.size()-1; + } + else { + auto index = m_unused.back(); + m_unused.pop_back(); + return index; + } + } + + void release( std::size_t index ) { + m_streams[index]->copyfmt( m_referenceStream ); // Restore initial flags and other state + m_unused.push_back(index); + } + }; + + ReusableStringStream::ReusableStringStream() + : m_index( Singleton<StringStreams>::getMutable().add() ), + m_oss( Singleton<StringStreams>::getMutable().m_streams[m_index].get() ) + {} + + ReusableStringStream::~ReusableStringStream() { + static_cast<std::ostringstream*>( m_oss )->str(""); + m_oss->clear(); + Singleton<StringStreams>::getMutable().release( m_index ); + } + + auto ReusableStringStream::str() const -> std::string { + return static_cast<std::ostringstream*>( m_oss )->str(); + } + + /////////////////////////////////////////////////////////////////////////// + +#ifndef CATCH_CONFIG_NOSTDOUT // If you #define this you must implement these functions + std::ostream& cout() { return std::cout; } + std::ostream& cerr() { return std::cerr; } + std::ostream& clog() { return std::clog; } +#endif +} +// end catch_stream.cpp +// start catch_string_manip.cpp + +#include <algorithm> +#include <ostream> +#include <cstring> +#include <cctype> + +namespace Catch { + + namespace { + char toLowerCh(char c) { + return static_cast<char>( std::tolower( c ) ); + } + } + + bool startsWith( std::string const& s, std::string const& prefix ) { + return s.size() >= prefix.size() && std::equal(prefix.begin(), prefix.end(), s.begin()); + } + bool startsWith( std::string const& s, char prefix ) { + return !s.empty() && s[0] == prefix; + } + bool endsWith( std::string const& s, std::string const& suffix ) { + return s.size() >= suffix.size() && std::equal(suffix.rbegin(), suffix.rend(), s.rbegin()); + } + bool endsWith( std::string const& s, char suffix ) { + return !s.empty() && s[s.size()-1] == suffix; + } + bool contains( std::string const& s, std::string const& infix ) { + return s.find( infix ) != std::string::npos; + } + void toLowerInPlace( std::string& s ) { + std::transform( s.begin(), s.end(), s.begin(), toLowerCh ); + } + std::string toLower( std::string const& s ) { + std::string lc = s; + toLowerInPlace( lc ); + return lc; + } + std::string trim( std::string const& str ) { + static char const* whitespaceChars = "\n\r\t "; + std::string::size_type start = str.find_first_not_of( whitespaceChars ); + std::string::size_type end = str.find_last_not_of( whitespaceChars ); + + return start != std::string::npos ? str.substr( start, 1+end-start ) : std::string(); + } + + bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ) { + bool replaced = false; + std::size_t i = str.find( replaceThis ); + while( i != std::string::npos ) { + replaced = true; + str = str.substr( 0, i ) + withThis + str.substr( i+replaceThis.size() ); + if( i < str.size()-withThis.size() ) + i = str.find( replaceThis, i+withThis.size() ); + else + i = std::string::npos; + } + return replaced; + } + + pluralise::pluralise( std::size_t count, std::string const& label ) + : m_count( count ), + m_label( label ) + {} + + std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ) { + os << pluraliser.m_count << ' ' << pluraliser.m_label; + if( pluraliser.m_count != 1 ) + os << 's'; + return os; + } + +} +// end catch_string_manip.cpp +// start catch_stringref.cpp + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wexit-time-destructors" +#endif + +#include <ostream> +#include <cstring> +#include <cstdint> + +namespace { + const uint32_t byte_2_lead = 0xC0; + const uint32_t byte_3_lead = 0xE0; + const uint32_t byte_4_lead = 0xF0; +} + +namespace Catch { + StringRef::StringRef( char const* rawChars ) noexcept + : StringRef( rawChars, static_cast<StringRef::size_type>(std::strlen(rawChars) ) ) + {} + + StringRef::operator std::string() const { + return std::string( m_start, m_size ); + } + + void StringRef::swap( StringRef& other ) noexcept { + std::swap( m_start, other.m_start ); + std::swap( m_size, other.m_size ); + std::swap( m_data, other.m_data ); + } + + auto StringRef::c_str() const -> char const* { + if( isSubstring() ) + const_cast<StringRef*>( this )->takeOwnership(); + return m_start; + } + auto StringRef::currentData() const noexcept -> char const* { + return m_start; + } + + auto StringRef::isOwned() const noexcept -> bool { + return m_data != nullptr; + } + auto StringRef::isSubstring() const noexcept -> bool { + return m_start[m_size] != '\0'; + } + + void StringRef::takeOwnership() { + if( !isOwned() ) { + m_data = new char[m_size+1]; + memcpy( m_data, m_start, m_size ); + m_data[m_size] = '\0'; + m_start = m_data; + } + } + auto StringRef::substr( size_type start, size_type size ) const noexcept -> StringRef { + if( start < m_size ) + return StringRef( m_start+start, size ); + else + return StringRef(); + } + auto StringRef::operator == ( StringRef const& other ) const noexcept -> bool { + return + size() == other.size() && + (std::strncmp( m_start, other.m_start, size() ) == 0); + } + auto StringRef::operator != ( StringRef const& other ) const noexcept -> bool { + return !operator==( other ); + } + + auto StringRef::operator[](size_type index) const noexcept -> char { + return m_start[index]; + } + + auto StringRef::numberOfCharacters() const noexcept -> size_type { + size_type noChars = m_size; + // Make adjustments for uft encodings + for( size_type i=0; i < m_size; ++i ) { + char c = m_start[i]; + if( ( c & byte_2_lead ) == byte_2_lead ) { + noChars--; + if (( c & byte_3_lead ) == byte_3_lead ) + noChars--; + if( ( c & byte_4_lead ) == byte_4_lead ) + noChars--; + } + } + return noChars; + } + + auto operator + ( StringRef const& lhs, StringRef const& rhs ) -> std::string { + std::string str; + str.reserve( lhs.size() + rhs.size() ); + str += lhs; + str += rhs; + return str; + } + auto operator + ( StringRef const& lhs, const char* rhs ) -> std::string { + return std::string( lhs ) + std::string( rhs ); + } + auto operator + ( char const* lhs, StringRef const& rhs ) -> std::string { + return std::string( lhs ) + std::string( rhs ); + } + + auto operator << ( std::ostream& os, StringRef const& str ) -> std::ostream& { + return os.write(str.currentData(), str.size()); + } + + auto operator+=( std::string& lhs, StringRef const& rhs ) -> std::string& { + lhs.append(rhs.currentData(), rhs.size()); + return lhs; + } + +} // namespace Catch + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif +// end catch_stringref.cpp +// start catch_tag_alias.cpp + +namespace Catch { + TagAlias::TagAlias(std::string const & _tag, SourceLineInfo _lineInfo): tag(_tag), lineInfo(_lineInfo) {} +} +// end catch_tag_alias.cpp +// start catch_tag_alias_autoregistrar.cpp + +namespace Catch { + + RegistrarForTagAliases::RegistrarForTagAliases(char const* alias, char const* tag, SourceLineInfo const& lineInfo) { + CATCH_TRY { + getMutableRegistryHub().registerTagAlias(alias, tag, lineInfo); + } CATCH_CATCH_ALL { + // Do not throw when constructing global objects, instead register the exception to be processed later + getMutableRegistryHub().registerStartupException(); + } + } + +} +// end catch_tag_alias_autoregistrar.cpp +// start catch_tag_alias_registry.cpp + +#include <sstream> + +namespace Catch { + + TagAliasRegistry::~TagAliasRegistry() {} + + TagAlias const* TagAliasRegistry::find( std::string const& alias ) const { + auto it = m_registry.find( alias ); + if( it != m_registry.end() ) + return &(it->second); + else + return nullptr; + } + + std::string TagAliasRegistry::expandAliases( std::string const& unexpandedTestSpec ) const { + std::string expandedTestSpec = unexpandedTestSpec; + for( auto const& registryKvp : m_registry ) { + std::size_t pos = expandedTestSpec.find( registryKvp.first ); + if( pos != std::string::npos ) { + expandedTestSpec = expandedTestSpec.substr( 0, pos ) + + registryKvp.second.tag + + expandedTestSpec.substr( pos + registryKvp.first.size() ); + } + } + return expandedTestSpec; + } + + void TagAliasRegistry::add( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) { + CATCH_ENFORCE( startsWith(alias, "[@") && endsWith(alias, ']'), + "error: tag alias, '" << alias << "' is not of the form [@alias name].\n" << lineInfo ); + + CATCH_ENFORCE( m_registry.insert(std::make_pair(alias, TagAlias(tag, lineInfo))).second, + "error: tag alias, '" << alias << "' already registered.\n" + << "\tFirst seen at: " << find(alias)->lineInfo << "\n" + << "\tRedefined at: " << lineInfo ); + } + + ITagAliasRegistry::~ITagAliasRegistry() {} + + ITagAliasRegistry const& ITagAliasRegistry::get() { + return getRegistryHub().getTagAliasRegistry(); + } + +} // end namespace Catch +// end catch_tag_alias_registry.cpp +// start catch_test_case_info.cpp + +#include <cctype> +#include <exception> +#include <algorithm> +#include <sstream> + +namespace Catch { + + namespace { + TestCaseInfo::SpecialProperties parseSpecialTag( std::string const& tag ) { + if( startsWith( tag, '.' ) || + tag == "!hide" ) + return TestCaseInfo::IsHidden; + else if( tag == "!throws" ) + return TestCaseInfo::Throws; + else if( tag == "!shouldfail" ) + return TestCaseInfo::ShouldFail; + else if( tag == "!mayfail" ) + return TestCaseInfo::MayFail; + else if( tag == "!nonportable" ) + return TestCaseInfo::NonPortable; + else if( tag == "!benchmark" ) + return static_cast<TestCaseInfo::SpecialProperties>( TestCaseInfo::Benchmark | TestCaseInfo::IsHidden ); + else + return TestCaseInfo::None; + } + bool isReservedTag( std::string const& tag ) { + return parseSpecialTag( tag ) == TestCaseInfo::None && tag.size() > 0 && !std::isalnum( static_cast<unsigned char>(tag[0]) ); + } + void enforceNotReservedTag( std::string const& tag, SourceLineInfo const& _lineInfo ) { + CATCH_ENFORCE( !isReservedTag(tag), + "Tag name: [" << tag << "] is not allowed.\n" + << "Tag names starting with non alpha-numeric characters are reserved\n" + << _lineInfo ); + } + } + + TestCase makeTestCase( ITestInvoker* _testCase, + std::string const& _className, + NameAndTags const& nameAndTags, + SourceLineInfo const& _lineInfo ) + { + bool isHidden = false; + + // Parse out tags + std::vector<std::string> tags; + std::string desc, tag; + bool inTag = false; + std::string _descOrTags = nameAndTags.tags; + for (char c : _descOrTags) { + if( !inTag ) { + if( c == '[' ) + inTag = true; + else + desc += c; + } + else { + if( c == ']' ) { + TestCaseInfo::SpecialProperties prop = parseSpecialTag( tag ); + if( ( prop & TestCaseInfo::IsHidden ) != 0 ) + isHidden = true; + else if( prop == TestCaseInfo::None ) + enforceNotReservedTag( tag, _lineInfo ); + + tags.push_back( tag ); + tag.clear(); + inTag = false; + } + else + tag += c; + } + } + if( isHidden ) { + tags.push_back( "." ); + } + + TestCaseInfo info( nameAndTags.name, _className, desc, tags, _lineInfo ); + return TestCase( _testCase, std::move(info) ); + } + + void setTags( TestCaseInfo& testCaseInfo, std::vector<std::string> tags ) { + std::sort(begin(tags), end(tags)); + tags.erase(std::unique(begin(tags), end(tags)), end(tags)); + testCaseInfo.lcaseTags.clear(); + + for( auto const& tag : tags ) { + std::string lcaseTag = toLower( tag ); + testCaseInfo.properties = static_cast<TestCaseInfo::SpecialProperties>( testCaseInfo.properties | parseSpecialTag( lcaseTag ) ); + testCaseInfo.lcaseTags.push_back( lcaseTag ); + } + testCaseInfo.tags = std::move(tags); + } + + TestCaseInfo::TestCaseInfo( std::string const& _name, + std::string const& _className, + std::string const& _description, + std::vector<std::string> const& _tags, + SourceLineInfo const& _lineInfo ) + : name( _name ), + className( _className ), + description( _description ), + lineInfo( _lineInfo ), + properties( None ) + { + setTags( *this, _tags ); + } + + bool TestCaseInfo::isHidden() const { + return ( properties & IsHidden ) != 0; + } + bool TestCaseInfo::throws() const { + return ( properties & Throws ) != 0; + } + bool TestCaseInfo::okToFail() const { + return ( properties & (ShouldFail | MayFail ) ) != 0; + } + bool TestCaseInfo::expectedToFail() const { + return ( properties & (ShouldFail ) ) != 0; + } + + std::string TestCaseInfo::tagsAsString() const { + std::string ret; + // '[' and ']' per tag + std::size_t full_size = 2 * tags.size(); + for (const auto& tag : tags) { + full_size += tag.size(); + } + ret.reserve(full_size); + for (const auto& tag : tags) { + ret.push_back('['); + ret.append(tag); + ret.push_back(']'); + } + + return ret; + } + + TestCase::TestCase( ITestInvoker* testCase, TestCaseInfo&& info ) : TestCaseInfo( std::move(info) ), test( testCase ) {} + + TestCase TestCase::withName( std::string const& _newName ) const { + TestCase other( *this ); + other.name = _newName; + return other; + } + + void TestCase::invoke() const { + test->invoke(); + } + + bool TestCase::operator == ( TestCase const& other ) const { + return test.get() == other.test.get() && + name == other.name && + className == other.className; + } + + bool TestCase::operator < ( TestCase const& other ) const { + return name < other.name; + } + + TestCaseInfo const& TestCase::getTestCaseInfo() const + { + return *this; + } + +} // end namespace Catch +// end catch_test_case_info.cpp +// start catch_test_case_registry_impl.cpp + +#include <sstream> + +namespace Catch { + + std::vector<TestCase> sortTests( IConfig const& config, std::vector<TestCase> const& unsortedTestCases ) { + + std::vector<TestCase> sorted = unsortedTestCases; + + switch( config.runOrder() ) { + case RunTests::InLexicographicalOrder: + std::sort( sorted.begin(), sorted.end() ); + break; + case RunTests::InRandomOrder: + seedRng( config ); + std::shuffle( sorted.begin(), sorted.end(), rng() ); + break; + case RunTests::InDeclarationOrder: + // already in declaration order + break; + } + return sorted; + } + bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ) { + return testSpec.matches( testCase ) && ( config.allowThrows() || !testCase.throws() ); + } + + void enforceNoDuplicateTestCases( std::vector<TestCase> const& functions ) { + std::set<TestCase> seenFunctions; + for( auto const& function : functions ) { + auto prev = seenFunctions.insert( function ); + CATCH_ENFORCE( prev.second, + "error: TEST_CASE( \"" << function.name << "\" ) already defined.\n" + << "\tFirst seen at " << prev.first->getTestCaseInfo().lineInfo << "\n" + << "\tRedefined at " << function.getTestCaseInfo().lineInfo ); + } + } + + std::vector<TestCase> filterTests( std::vector<TestCase> const& testCases, TestSpec const& testSpec, IConfig const& config ) { + std::vector<TestCase> filtered; + filtered.reserve( testCases.size() ); + for( auto const& testCase : testCases ) + if( matchTest( testCase, testSpec, config ) ) + filtered.push_back( testCase ); + return filtered; + } + std::vector<TestCase> const& getAllTestCasesSorted( IConfig const& config ) { + return getRegistryHub().getTestCaseRegistry().getAllTestsSorted( config ); + } + + void TestRegistry::registerTest( TestCase const& testCase ) { + std::string name = testCase.getTestCaseInfo().name; + if( name.empty() ) { + ReusableStringStream rss; + rss << "Anonymous test case " << ++m_unnamedCount; + return registerTest( testCase.withName( rss.str() ) ); + } + m_functions.push_back( testCase ); + } + + std::vector<TestCase> const& TestRegistry::getAllTests() const { + return m_functions; + } + std::vector<TestCase> const& TestRegistry::getAllTestsSorted( IConfig const& config ) const { + if( m_sortedFunctions.empty() ) + enforceNoDuplicateTestCases( m_functions ); + + if( m_currentSortOrder != config.runOrder() || m_sortedFunctions.empty() ) { + m_sortedFunctions = sortTests( config, m_functions ); + m_currentSortOrder = config.runOrder(); + } + return m_sortedFunctions; + } + + /////////////////////////////////////////////////////////////////////////// + TestInvokerAsFunction::TestInvokerAsFunction( void(*testAsFunction)() ) noexcept : m_testAsFunction( testAsFunction ) {} + + void TestInvokerAsFunction::invoke() const { + m_testAsFunction(); + } + + std::string extractClassName( StringRef const& classOrQualifiedMethodName ) { + std::string className = classOrQualifiedMethodName; + if( startsWith( className, '&' ) ) + { + std::size_t lastColons = className.rfind( "::" ); + std::size_t penultimateColons = className.rfind( "::", lastColons-1 ); + if( penultimateColons == std::string::npos ) + penultimateColons = 1; + className = className.substr( penultimateColons, lastColons-penultimateColons ); + } + return className; + } + +} // end namespace Catch +// end catch_test_case_registry_impl.cpp +// start catch_test_case_tracker.cpp + +#include <algorithm> +#include <cassert> +#include <stdexcept> +#include <memory> +#include <sstream> + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wexit-time-destructors" +#endif + +namespace Catch { +namespace TestCaseTracking { + + NameAndLocation::NameAndLocation( std::string const& _name, SourceLineInfo const& _location ) + : name( _name ), + location( _location ) + {} + + ITracker::~ITracker() = default; + + TrackerContext& TrackerContext::instance() { + static TrackerContext s_instance; + return s_instance; + } + + ITracker& TrackerContext::startRun() { + m_rootTracker = std::make_shared<SectionTracker>( NameAndLocation( "{root}", CATCH_INTERNAL_LINEINFO ), *this, nullptr ); + m_currentTracker = nullptr; + m_runState = Executing; + return *m_rootTracker; + } + + void TrackerContext::endRun() { + m_rootTracker.reset(); + m_currentTracker = nullptr; + m_runState = NotStarted; + } + + void TrackerContext::startCycle() { + m_currentTracker = m_rootTracker.get(); + m_runState = Executing; + } + void TrackerContext::completeCycle() { + m_runState = CompletedCycle; + } + + bool TrackerContext::completedCycle() const { + return m_runState == CompletedCycle; + } + ITracker& TrackerContext::currentTracker() { + return *m_currentTracker; + } + void TrackerContext::setCurrentTracker( ITracker* tracker ) { + m_currentTracker = tracker; + } + + TrackerBase::TrackerBase( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ) + : m_nameAndLocation( nameAndLocation ), + m_ctx( ctx ), + m_parent( parent ) + {} + + NameAndLocation const& TrackerBase::nameAndLocation() const { + return m_nameAndLocation; + } + bool TrackerBase::isComplete() const { + return m_runState == CompletedSuccessfully || m_runState == Failed; + } + bool TrackerBase::isSuccessfullyCompleted() const { + return m_runState == CompletedSuccessfully; + } + bool TrackerBase::isOpen() const { + return m_runState != NotStarted && !isComplete(); + } + bool TrackerBase::hasChildren() const { + return !m_children.empty(); + } + + void TrackerBase::addChild( ITrackerPtr const& child ) { + m_children.push_back( child ); + } + + ITrackerPtr TrackerBase::findChild( NameAndLocation const& nameAndLocation ) { + auto it = std::find_if( m_children.begin(), m_children.end(), + [&nameAndLocation]( ITrackerPtr const& tracker ){ + return + tracker->nameAndLocation().location == nameAndLocation.location && + tracker->nameAndLocation().name == nameAndLocation.name; + } ); + return( it != m_children.end() ) + ? *it + : nullptr; + } + ITracker& TrackerBase::parent() { + assert( m_parent ); // Should always be non-null except for root + return *m_parent; + } + + void TrackerBase::openChild() { + if( m_runState != ExecutingChildren ) { + m_runState = ExecutingChildren; + if( m_parent ) + m_parent->openChild(); + } + } + + bool TrackerBase::isSectionTracker() const { return false; } + bool TrackerBase::isIndexTracker() const { return false; } + + void TrackerBase::open() { + m_runState = Executing; + moveToThis(); + if( m_parent ) + m_parent->openChild(); + } + + void TrackerBase::close() { + + // Close any still open children (e.g. generators) + while( &m_ctx.currentTracker() != this ) + m_ctx.currentTracker().close(); + + switch( m_runState ) { + case NeedsAnotherRun: + break; + + case Executing: + m_runState = CompletedSuccessfully; + break; + case ExecutingChildren: + if( m_children.empty() || m_children.back()->isComplete() ) + m_runState = CompletedSuccessfully; + break; + + case NotStarted: + case CompletedSuccessfully: + case Failed: + CATCH_INTERNAL_ERROR( "Illogical state: " << m_runState ); + + default: + CATCH_INTERNAL_ERROR( "Unknown state: " << m_runState ); + } + moveToParent(); + m_ctx.completeCycle(); + } + void TrackerBase::fail() { + m_runState = Failed; + if( m_parent ) + m_parent->markAsNeedingAnotherRun(); + moveToParent(); + m_ctx.completeCycle(); + } + void TrackerBase::markAsNeedingAnotherRun() { + m_runState = NeedsAnotherRun; + } + + void TrackerBase::moveToParent() { + assert( m_parent ); + m_ctx.setCurrentTracker( m_parent ); + } + void TrackerBase::moveToThis() { + m_ctx.setCurrentTracker( this ); + } + + SectionTracker::SectionTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ) + : TrackerBase( nameAndLocation, ctx, parent ) + { + if( parent ) { + while( !parent->isSectionTracker() ) + parent = &parent->parent(); + + SectionTracker& parentSection = static_cast<SectionTracker&>( *parent ); + addNextFilters( parentSection.m_filters ); + } + } + + bool SectionTracker::isSectionTracker() const { return true; } + + SectionTracker& SectionTracker::acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation ) { + std::shared_ptr<SectionTracker> section; + + ITracker& currentTracker = ctx.currentTracker(); + if( ITrackerPtr childTracker = currentTracker.findChild( nameAndLocation ) ) { + assert( childTracker ); + assert( childTracker->isSectionTracker() ); + section = std::static_pointer_cast<SectionTracker>( childTracker ); + } + else { + section = std::make_shared<SectionTracker>( nameAndLocation, ctx, ¤tTracker ); + currentTracker.addChild( section ); + } + if( !ctx.completedCycle() ) + section->tryOpen(); + return *section; + } + + void SectionTracker::tryOpen() { + if( !isComplete() && (m_filters.empty() || m_filters[0].empty() || m_filters[0] == m_nameAndLocation.name ) ) + open(); + } + + void SectionTracker::addInitialFilters( std::vector<std::string> const& filters ) { + if( !filters.empty() ) { + m_filters.push_back(""); // Root - should never be consulted + m_filters.push_back(""); // Test Case - not a section filter + m_filters.insert( m_filters.end(), filters.begin(), filters.end() ); + } + } + void SectionTracker::addNextFilters( std::vector<std::string> const& filters ) { + if( filters.size() > 1 ) + m_filters.insert( m_filters.end(), ++filters.begin(), filters.end() ); + } + + IndexTracker::IndexTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent, int size ) + : TrackerBase( nameAndLocation, ctx, parent ), + m_size( size ) + {} + + bool IndexTracker::isIndexTracker() const { return true; } + + IndexTracker& IndexTracker::acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation, int size ) { + std::shared_ptr<IndexTracker> tracker; + + ITracker& currentTracker = ctx.currentTracker(); + if( ITrackerPtr childTracker = currentTracker.findChild( nameAndLocation ) ) { + assert( childTracker ); + assert( childTracker->isIndexTracker() ); + tracker = std::static_pointer_cast<IndexTracker>( childTracker ); + } + else { + tracker = std::make_shared<IndexTracker>( nameAndLocation, ctx, ¤tTracker, size ); + currentTracker.addChild( tracker ); + } + + if( !ctx.completedCycle() && !tracker->isComplete() ) { + if( tracker->m_runState != ExecutingChildren && tracker->m_runState != NeedsAnotherRun ) + tracker->moveNext(); + tracker->open(); + } + + return *tracker; + } + + int IndexTracker::index() const { return m_index; } + + void IndexTracker::moveNext() { + m_index++; + m_children.clear(); + } + + void IndexTracker::close() { + TrackerBase::close(); + if( m_runState == CompletedSuccessfully && m_index < m_size-1 ) + m_runState = Executing; + } + +} // namespace TestCaseTracking + +using TestCaseTracking::ITracker; +using TestCaseTracking::TrackerContext; +using TestCaseTracking::SectionTracker; +using TestCaseTracking::IndexTracker; + +} // namespace Catch + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif +// end catch_test_case_tracker.cpp +// start catch_test_registry.cpp + +namespace Catch { + + auto makeTestInvoker( void(*testAsFunction)() ) noexcept -> ITestInvoker* { + return new(std::nothrow) TestInvokerAsFunction( testAsFunction ); + } + + NameAndTags::NameAndTags( StringRef const& name_ , StringRef const& tags_ ) noexcept : name( name_ ), tags( tags_ ) {} + + AutoReg::AutoReg( ITestInvoker* invoker, SourceLineInfo const& lineInfo, StringRef const& classOrMethod, NameAndTags const& nameAndTags ) noexcept { + CATCH_TRY { + getMutableRegistryHub() + .registerTest( + makeTestCase( + invoker, + extractClassName( classOrMethod ), + nameAndTags, + lineInfo)); + } CATCH_CATCH_ALL { + // Do not throw when constructing global objects, instead register the exception to be processed later + getMutableRegistryHub().registerStartupException(); + } + } + + AutoReg::~AutoReg() = default; +} +// end catch_test_registry.cpp +// start catch_test_spec.cpp + +#include <algorithm> +#include <string> +#include <vector> +#include <memory> + +namespace Catch { + + TestSpec::Pattern::~Pattern() = default; + TestSpec::NamePattern::~NamePattern() = default; + TestSpec::TagPattern::~TagPattern() = default; + TestSpec::ExcludedPattern::~ExcludedPattern() = default; + + TestSpec::NamePattern::NamePattern( std::string const& name ) + : m_wildcardPattern( toLower( name ), CaseSensitive::No ) + {} + bool TestSpec::NamePattern::matches( TestCaseInfo const& testCase ) const { + return m_wildcardPattern.matches( toLower( testCase.name ) ); + } + + TestSpec::TagPattern::TagPattern( std::string const& tag ) : m_tag( toLower( tag ) ) {} + bool TestSpec::TagPattern::matches( TestCaseInfo const& testCase ) const { + return std::find(begin(testCase.lcaseTags), + end(testCase.lcaseTags), + m_tag) != end(testCase.lcaseTags); + } + + TestSpec::ExcludedPattern::ExcludedPattern( PatternPtr const& underlyingPattern ) : m_underlyingPattern( underlyingPattern ) {} + bool TestSpec::ExcludedPattern::matches( TestCaseInfo const& testCase ) const { return !m_underlyingPattern->matches( testCase ); } + + bool TestSpec::Filter::matches( TestCaseInfo const& testCase ) const { + // All patterns in a filter must match for the filter to be a match + for( auto const& pattern : m_patterns ) { + if( !pattern->matches( testCase ) ) + return false; + } + return true; + } + + bool TestSpec::hasFilters() const { + return !m_filters.empty(); + } + bool TestSpec::matches( TestCaseInfo const& testCase ) const { + // A TestSpec matches if any filter matches + for( auto const& filter : m_filters ) + if( filter.matches( testCase ) ) + return true; + return false; + } +} +// end catch_test_spec.cpp +// start catch_test_spec_parser.cpp + +namespace Catch { + + TestSpecParser::TestSpecParser( ITagAliasRegistry const& tagAliases ) : m_tagAliases( &tagAliases ) {} + + TestSpecParser& TestSpecParser::parse( std::string const& arg ) { + m_mode = None; + m_exclusion = false; + m_start = std::string::npos; + m_arg = m_tagAliases->expandAliases( arg ); + m_escapeChars.clear(); + for( m_pos = 0; m_pos < m_arg.size(); ++m_pos ) + visitChar( m_arg[m_pos] ); + if( m_mode == Name ) + addPattern<TestSpec::NamePattern>(); + return *this; + } + TestSpec TestSpecParser::testSpec() { + addFilter(); + return m_testSpec; + } + + void TestSpecParser::visitChar( char c ) { + if( m_mode == None ) { + switch( c ) { + case ' ': return; + case '~': m_exclusion = true; return; + case '[': return startNewMode( Tag, ++m_pos ); + case '"': return startNewMode( QuotedName, ++m_pos ); + case '\\': return escape(); + default: startNewMode( Name, m_pos ); break; + } + } + if( m_mode == Name ) { + if( c == ',' ) { + addPattern<TestSpec::NamePattern>(); + addFilter(); + } + else if( c == '[' ) { + if( subString() == "exclude:" ) + m_exclusion = true; + else + addPattern<TestSpec::NamePattern>(); + startNewMode( Tag, ++m_pos ); + } + else if( c == '\\' ) + escape(); + } + else if( m_mode == EscapedName ) + m_mode = Name; + else if( m_mode == QuotedName && c == '"' ) + addPattern<TestSpec::NamePattern>(); + else if( m_mode == Tag && c == ']' ) + addPattern<TestSpec::TagPattern>(); + } + void TestSpecParser::startNewMode( Mode mode, std::size_t start ) { + m_mode = mode; + m_start = start; + } + void TestSpecParser::escape() { + if( m_mode == None ) + m_start = m_pos; + m_mode = EscapedName; + m_escapeChars.push_back( m_pos ); + } + std::string TestSpecParser::subString() const { return m_arg.substr( m_start, m_pos - m_start ); } + + void TestSpecParser::addFilter() { + if( !m_currentFilter.m_patterns.empty() ) { + m_testSpec.m_filters.push_back( m_currentFilter ); + m_currentFilter = TestSpec::Filter(); + } + } + + TestSpec parseTestSpec( std::string const& arg ) { + return TestSpecParser( ITagAliasRegistry::get() ).parse( arg ).testSpec(); + } + +} // namespace Catch +// end catch_test_spec_parser.cpp +// start catch_timer.cpp + +#include <chrono> + +static const uint64_t nanosecondsInSecond = 1000000000; + +namespace Catch { + + auto getCurrentNanosecondsSinceEpoch() -> uint64_t { + return std::chrono::duration_cast<std::chrono::nanoseconds>( std::chrono::high_resolution_clock::now().time_since_epoch() ).count(); + } + + namespace { + auto estimateClockResolution() -> uint64_t { + uint64_t sum = 0; + static const uint64_t iterations = 1000000; + + auto startTime = getCurrentNanosecondsSinceEpoch(); + + for( std::size_t i = 0; i < iterations; ++i ) { + + uint64_t ticks; + uint64_t baseTicks = getCurrentNanosecondsSinceEpoch(); + do { + ticks = getCurrentNanosecondsSinceEpoch(); + } while( ticks == baseTicks ); + + auto delta = ticks - baseTicks; + sum += delta; + + // If we have been calibrating for over 3 seconds -- the clock + // is terrible and we should move on. + // TBD: How to signal that the measured resolution is probably wrong? + if (ticks > startTime + 3 * nanosecondsInSecond) { + return sum / i; + } + } + + // We're just taking the mean, here. To do better we could take the std. dev and exclude outliers + // - and potentially do more iterations if there's a high variance. + return sum/iterations; + } + } + auto getEstimatedClockResolution() -> uint64_t { + static auto s_resolution = estimateClockResolution(); + return s_resolution; + } + + void Timer::start() { + m_nanoseconds = getCurrentNanosecondsSinceEpoch(); + } + auto Timer::getElapsedNanoseconds() const -> uint64_t { + return getCurrentNanosecondsSinceEpoch() - m_nanoseconds; + } + auto Timer::getElapsedMicroseconds() const -> uint64_t { + return getElapsedNanoseconds()/1000; + } + auto Timer::getElapsedMilliseconds() const -> unsigned int { + return static_cast<unsigned int>(getElapsedMicroseconds()/1000); + } + auto Timer::getElapsedSeconds() const -> double { + return getElapsedMicroseconds()/1000000.0; + } + +} // namespace Catch +// end catch_timer.cpp +// start catch_tostring.cpp + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wexit-time-destructors" +# pragma clang diagnostic ignored "-Wglobal-constructors" +#endif + +// Enable specific decls locally +#if !defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER) +#define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER +#endif + +#include <cmath> +#include <iomanip> + +namespace Catch { + +namespace Detail { + + const std::string unprintableString = "{?}"; + + namespace { + const int hexThreshold = 255; + + struct Endianness { + enum Arch { Big, Little }; + + static Arch which() { + union _{ + int asInt; + char asChar[sizeof (int)]; + } u; + + u.asInt = 1; + return ( u.asChar[sizeof(int)-1] == 1 ) ? Big : Little; + } + }; + } + + std::string rawMemoryToString( const void *object, std::size_t size ) { + // Reverse order for little endian architectures + int i = 0, end = static_cast<int>( size ), inc = 1; + if( Endianness::which() == Endianness::Little ) { + i = end-1; + end = inc = -1; + } + + unsigned char const *bytes = static_cast<unsigned char const *>(object); + ReusableStringStream rss; + rss << "0x" << std::setfill('0') << std::hex; + for( ; i != end; i += inc ) + rss << std::setw(2) << static_cast<unsigned>(bytes[i]); + return rss.str(); + } +} + +template<typename T> +std::string fpToString( T value, int precision ) { + if (Catch::isnan(value)) { + return "nan"; + } + + ReusableStringStream rss; + rss << std::setprecision( precision ) + << std::fixed + << value; + std::string d = rss.str(); + std::size_t i = d.find_last_not_of( '0' ); + if( i != std::string::npos && i != d.size()-1 ) { + if( d[i] == '.' ) + i++; + d = d.substr( 0, i+1 ); + } + return d; +} + +//// ======================================================= //// +// +// Out-of-line defs for full specialization of StringMaker +// +//// ======================================================= //// + +std::string StringMaker<std::string>::convert(const std::string& str) { + if (!getCurrentContext().getConfig()->showInvisibles()) { + return '"' + str + '"'; + } + + std::string s("\""); + for (char c : str) { + switch (c) { + case '\n': + s.append("\\n"); + break; + case '\t': + s.append("\\t"); + break; + default: + s.push_back(c); + break; + } + } + s.append("\""); + return s; +} + +#ifdef CATCH_CONFIG_CPP17_STRING_VIEW +std::string StringMaker<std::string_view>::convert(std::string_view str) { + return ::Catch::Detail::stringify(std::string{ str }); +} +#endif + +std::string StringMaker<char const*>::convert(char const* str) { + if (str) { + return ::Catch::Detail::stringify(std::string{ str }); + } else { + return{ "{null string}" }; + } +} +std::string StringMaker<char*>::convert(char* str) { + if (str) { + return ::Catch::Detail::stringify(std::string{ str }); + } else { + return{ "{null string}" }; + } +} + +#ifdef CATCH_CONFIG_WCHAR +std::string StringMaker<std::wstring>::convert(const std::wstring& wstr) { + std::string s; + s.reserve(wstr.size()); + for (auto c : wstr) { + s += (c <= 0xff) ? static_cast<char>(c) : '?'; + } + return ::Catch::Detail::stringify(s); +} + +# ifdef CATCH_CONFIG_CPP17_STRING_VIEW +std::string StringMaker<std::wstring_view>::convert(std::wstring_view str) { + return StringMaker<std::wstring>::convert(std::wstring(str)); +} +# endif + +std::string StringMaker<wchar_t const*>::convert(wchar_t const * str) { + if (str) { + return ::Catch::Detail::stringify(std::wstring{ str }); + } else { + return{ "{null string}" }; + } +} +std::string StringMaker<wchar_t *>::convert(wchar_t * str) { + if (str) { + return ::Catch::Detail::stringify(std::wstring{ str }); + } else { + return{ "{null string}" }; + } +} +#endif + +std::string StringMaker<int>::convert(int value) { + return ::Catch::Detail::stringify(static_cast<long long>(value)); +} +std::string StringMaker<long>::convert(long value) { + return ::Catch::Detail::stringify(static_cast<long long>(value)); +} +std::string StringMaker<long long>::convert(long long value) { + ReusableStringStream rss; + rss << value; + if (value > Detail::hexThreshold) { + rss << " (0x" << std::hex << value << ')'; + } + return rss.str(); +} + +std::string StringMaker<unsigned int>::convert(unsigned int value) { + return ::Catch::Detail::stringify(static_cast<unsigned long long>(value)); +} +std::string StringMaker<unsigned long>::convert(unsigned long value) { + return ::Catch::Detail::stringify(static_cast<unsigned long long>(value)); +} +std::string StringMaker<unsigned long long>::convert(unsigned long long value) { + ReusableStringStream rss; + rss << value; + if (value > Detail::hexThreshold) { + rss << " (0x" << std::hex << value << ')'; + } + return rss.str(); +} + +std::string StringMaker<bool>::convert(bool b) { + return b ? "true" : "false"; +} + +std::string StringMaker<signed char>::convert(signed char value) { + if (value == '\r') { + return "'\\r'"; + } else if (value == '\f') { + return "'\\f'"; + } else if (value == '\n') { + return "'\\n'"; + } else if (value == '\t') { + return "'\\t'"; + } else if ('\0' <= value && value < ' ') { + return ::Catch::Detail::stringify(static_cast<unsigned int>(value)); + } else { + char chstr[] = "' '"; + chstr[1] = value; + return chstr; + } +} +std::string StringMaker<char>::convert(char c) { + return ::Catch::Detail::stringify(static_cast<signed char>(c)); +} +std::string StringMaker<unsigned char>::convert(unsigned char c) { + return ::Catch::Detail::stringify(static_cast<char>(c)); +} + +std::string StringMaker<std::nullptr_t>::convert(std::nullptr_t) { + return "nullptr"; +} + +std::string StringMaker<float>::convert(float value) { + return fpToString(value, 5) + 'f'; +} +std::string StringMaker<double>::convert(double value) { + return fpToString(value, 10); +} + +std::string ratio_string<std::atto>::symbol() { return "a"; } +std::string ratio_string<std::femto>::symbol() { return "f"; } +std::string ratio_string<std::pico>::symbol() { return "p"; } +std::string ratio_string<std::nano>::symbol() { return "n"; } +std::string ratio_string<std::micro>::symbol() { return "u"; } +std::string ratio_string<std::milli>::symbol() { return "m"; } + +} // end namespace Catch + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif + +// end catch_tostring.cpp +// start catch_totals.cpp + +namespace Catch { + + Counts Counts::operator - ( Counts const& other ) const { + Counts diff; + diff.passed = passed - other.passed; + diff.failed = failed - other.failed; + diff.failedButOk = failedButOk - other.failedButOk; + return diff; + } + + Counts& Counts::operator += ( Counts const& other ) { + passed += other.passed; + failed += other.failed; + failedButOk += other.failedButOk; + return *this; + } + + std::size_t Counts::total() const { + return passed + failed + failedButOk; + } + bool Counts::allPassed() const { + return failed == 0 && failedButOk == 0; + } + bool Counts::allOk() const { + return failed == 0; + } + + Totals Totals::operator - ( Totals const& other ) const { + Totals diff; + diff.assertions = assertions - other.assertions; + diff.testCases = testCases - other.testCases; + return diff; + } + + Totals& Totals::operator += ( Totals const& other ) { + assertions += other.assertions; + testCases += other.testCases; + return *this; + } + + Totals Totals::delta( Totals const& prevTotals ) const { + Totals diff = *this - prevTotals; + if( diff.assertions.failed > 0 ) + ++diff.testCases.failed; + else if( diff.assertions.failedButOk > 0 ) + ++diff.testCases.failedButOk; + else + ++diff.testCases.passed; + return diff; + } + +} +// end catch_totals.cpp +// start catch_uncaught_exceptions.cpp + +#include <exception> + +namespace Catch { + bool uncaught_exceptions() { +#if defined(CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) + return std::uncaught_exceptions() > 0; +#else + return std::uncaught_exception(); +#endif + } +} // end namespace Catch +// end catch_uncaught_exceptions.cpp +// start catch_version.cpp + +#include <ostream> + +namespace Catch { + + Version::Version + ( unsigned int _majorVersion, + unsigned int _minorVersion, + unsigned int _patchNumber, + char const * const _branchName, + unsigned int _buildNumber ) + : majorVersion( _majorVersion ), + minorVersion( _minorVersion ), + patchNumber( _patchNumber ), + branchName( _branchName ), + buildNumber( _buildNumber ) + {} + + std::ostream& operator << ( std::ostream& os, Version const& version ) { + os << version.majorVersion << '.' + << version.minorVersion << '.' + << version.patchNumber; + // branchName is never null -> 0th char is \0 if it is empty + if (version.branchName[0]) { + os << '-' << version.branchName + << '.' << version.buildNumber; + } + return os; + } + + Version const& libraryVersion() { + static Version version( 2, 5, 0, "", 0 ); + return version; + } + +} +// end catch_version.cpp +// start catch_wildcard_pattern.cpp + +#include <sstream> + +namespace Catch { + + WildcardPattern::WildcardPattern( std::string const& pattern, + CaseSensitive::Choice caseSensitivity ) + : m_caseSensitivity( caseSensitivity ), + m_pattern( adjustCase( pattern ) ) + { + if( startsWith( m_pattern, '*' ) ) { + m_pattern = m_pattern.substr( 1 ); + m_wildcard = WildcardAtStart; + } + if( endsWith( m_pattern, '*' ) ) { + m_pattern = m_pattern.substr( 0, m_pattern.size()-1 ); + m_wildcard = static_cast<WildcardPosition>( m_wildcard | WildcardAtEnd ); + } + } + + bool WildcardPattern::matches( std::string const& str ) const { + switch( m_wildcard ) { + case NoWildcard: + return m_pattern == adjustCase( str ); + case WildcardAtStart: + return endsWith( adjustCase( str ), m_pattern ); + case WildcardAtEnd: + return startsWith( adjustCase( str ), m_pattern ); + case WildcardAtBothEnds: + return contains( adjustCase( str ), m_pattern ); + default: + CATCH_INTERNAL_ERROR( "Unknown enum" ); + } + } + + std::string WildcardPattern::adjustCase( std::string const& str ) const { + return m_caseSensitivity == CaseSensitive::No ? toLower( str ) : str; + } +} +// end catch_wildcard_pattern.cpp +// start catch_xmlwriter.cpp + +#include <iomanip> + +using uchar = unsigned char; + +namespace Catch { + +namespace { + + size_t trailingBytes(unsigned char c) { + if ((c & 0xE0) == 0xC0) { + return 2; + } + if ((c & 0xF0) == 0xE0) { + return 3; + } + if ((c & 0xF8) == 0xF0) { + return 4; + } + CATCH_INTERNAL_ERROR("Invalid multibyte utf-8 start byte encountered"); + } + + uint32_t headerValue(unsigned char c) { + if ((c & 0xE0) == 0xC0) { + return c & 0x1F; + } + if ((c & 0xF0) == 0xE0) { + return c & 0x0F; + } + if ((c & 0xF8) == 0xF0) { + return c & 0x07; + } + CATCH_INTERNAL_ERROR("Invalid multibyte utf-8 start byte encountered"); + } + + void hexEscapeChar(std::ostream& os, unsigned char c) { + os << "\\x" + << std::uppercase << std::hex << std::setfill('0') << std::setw(2) + << static_cast<int>(c); + } + +} // anonymous namespace + + XmlEncode::XmlEncode( std::string const& str, ForWhat forWhat ) + : m_str( str ), + m_forWhat( forWhat ) + {} + + void XmlEncode::encodeTo( std::ostream& os ) const { + // Apostrophe escaping not necessary if we always use " to write attributes + // (see: http://www.w3.org/TR/xml/#syntax) + + for( std::size_t idx = 0; idx < m_str.size(); ++ idx ) { + uchar c = m_str[idx]; + switch (c) { + case '<': os << "<"; break; + case '&': os << "&"; break; + + case '>': + // See: http://www.w3.org/TR/xml/#syntax + if (idx > 2 && m_str[idx - 1] == ']' && m_str[idx - 2] == ']') + os << ">"; + else + os << c; + break; + + case '\"': + if (m_forWhat == ForAttributes) + os << """; + else + os << c; + break; + + default: + // Check for control characters and invalid utf-8 + + // Escape control characters in standard ascii + // see http://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0 + if (c < 0x09 || (c > 0x0D && c < 0x20) || c == 0x7F) { + hexEscapeChar(os, c); + break; + } + + // Plain ASCII: Write it to stream + if (c < 0x7F) { + os << c; + break; + } + + // UTF-8 territory + // Check if the encoding is valid and if it is not, hex escape bytes. + // Important: We do not check the exact decoded values for validity, only the encoding format + // First check that this bytes is a valid lead byte: + // This means that it is not encoded as 1111 1XXX + // Or as 10XX XXXX + if (c < 0xC0 || + c >= 0xF8) { + hexEscapeChar(os, c); + break; + } + + auto encBytes = trailingBytes(c); + // Are there enough bytes left to avoid accessing out-of-bounds memory? + if (idx + encBytes - 1 >= m_str.size()) { + hexEscapeChar(os, c); + break; + } + // The header is valid, check data + // The next encBytes bytes must together be a valid utf-8 + // This means: bitpattern 10XX XXXX and the extracted value is sane (ish) + bool valid = true; + uint32_t value = headerValue(c); + for (std::size_t n = 1; n < encBytes; ++n) { + uchar nc = m_str[idx + n]; + valid &= ((nc & 0xC0) == 0x80); + value = (value << 6) | (nc & 0x3F); + } + + if ( + // Wrong bit pattern of following bytes + (!valid) || + // Overlong encodings + (value < 0x80) || + (0x80 <= value && value < 0x800 && encBytes > 2) || + (0x800 < value && value < 0x10000 && encBytes > 3) || + // Encoded value out of range + (value >= 0x110000) + ) { + hexEscapeChar(os, c); + break; + } + + // If we got here, this is in fact a valid(ish) utf-8 sequence + for (std::size_t n = 0; n < encBytes; ++n) { + os << m_str[idx + n]; + } + idx += encBytes - 1; + break; + } + } + } + + std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ) { + xmlEncode.encodeTo( os ); + return os; + } + + XmlWriter::ScopedElement::ScopedElement( XmlWriter* writer ) + : m_writer( writer ) + {} + + XmlWriter::ScopedElement::ScopedElement( ScopedElement&& other ) noexcept + : m_writer( other.m_writer ){ + other.m_writer = nullptr; + } + XmlWriter::ScopedElement& XmlWriter::ScopedElement::operator=( ScopedElement&& other ) noexcept { + if ( m_writer ) { + m_writer->endElement(); + } + m_writer = other.m_writer; + other.m_writer = nullptr; + return *this; + } + + XmlWriter::ScopedElement::~ScopedElement() { + if( m_writer ) + m_writer->endElement(); + } + + XmlWriter::ScopedElement& XmlWriter::ScopedElement::writeText( std::string const& text, bool indent ) { + m_writer->writeText( text, indent ); + return *this; + } + + XmlWriter::XmlWriter( std::ostream& os ) : m_os( os ) + { + writeDeclaration(); + } + + XmlWriter::~XmlWriter() { + while( !m_tags.empty() ) + endElement(); + } + + XmlWriter& XmlWriter::startElement( std::string const& name ) { + ensureTagClosed(); + newlineIfNecessary(); + m_os << m_indent << '<' << name; + m_tags.push_back( name ); + m_indent += " "; + m_tagIsOpen = true; + return *this; + } + + XmlWriter::ScopedElement XmlWriter::scopedElement( std::string const& name ) { + ScopedElement scoped( this ); + startElement( name ); + return scoped; + } + + XmlWriter& XmlWriter::endElement() { + newlineIfNecessary(); + m_indent = m_indent.substr( 0, m_indent.size()-2 ); + if( m_tagIsOpen ) { + m_os << "/>"; + m_tagIsOpen = false; + } + else { + m_os << m_indent << "</" << m_tags.back() << ">"; + } + m_os << std::endl; + m_tags.pop_back(); + return *this; + } + + XmlWriter& XmlWriter::writeAttribute( std::string const& name, std::string const& attribute ) { + if( !name.empty() && !attribute.empty() ) + m_os << ' ' << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << '"'; + return *this; + } + + XmlWriter& XmlWriter::writeAttribute( std::string const& name, bool attribute ) { + m_os << ' ' << name << "=\"" << ( attribute ? "true" : "false" ) << '"'; + return *this; + } + + XmlWriter& XmlWriter::writeText( std::string const& text, bool indent ) { + if( !text.empty() ){ + bool tagWasOpen = m_tagIsOpen; + ensureTagClosed(); + if( tagWasOpen && indent ) + m_os << m_indent; + m_os << XmlEncode( text ); + m_needsNewline = true; + } + return *this; + } + + XmlWriter& XmlWriter::writeComment( std::string const& text ) { + ensureTagClosed(); + m_os << m_indent << "<!--" << text << "-->"; + m_needsNewline = true; + return *this; + } + + void XmlWriter::writeStylesheetRef( std::string const& url ) { + m_os << "<?xml-stylesheet type=\"text/xsl\" href=\"" << url << "\"?>\n"; + } + + XmlWriter& XmlWriter::writeBlankLine() { + ensureTagClosed(); + m_os << '\n'; + return *this; + } + + void XmlWriter::ensureTagClosed() { + if( m_tagIsOpen ) { + m_os << ">" << std::endl; + m_tagIsOpen = false; + } + } + + void XmlWriter::writeDeclaration() { + m_os << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"; + } + + void XmlWriter::newlineIfNecessary() { + if( m_needsNewline ) { + m_os << std::endl; + m_needsNewline = false; + } + } +} +// end catch_xmlwriter.cpp +// start catch_reporter_bases.cpp + +#include <cstring> +#include <cfloat> +#include <cstdio> +#include <cassert> +#include <memory> + +namespace Catch { + void prepareExpandedExpression(AssertionResult& result) { + result.getExpandedExpression(); + } + + // Because formatting using c++ streams is stateful, drop down to C is required + // Alternatively we could use stringstream, but its performance is... not good. + std::string getFormattedDuration( double duration ) { + // Max exponent + 1 is required to represent the whole part + // + 1 for decimal point + // + 3 for the 3 decimal places + // + 1 for null terminator + const std::size_t maxDoubleSize = DBL_MAX_10_EXP + 1 + 1 + 3 + 1; + char buffer[maxDoubleSize]; + + // Save previous errno, to prevent sprintf from overwriting it + ErrnoGuard guard; +#ifdef _MSC_VER + sprintf_s(buffer, "%.3f", duration); +#else + sprintf(buffer, "%.3f", duration); +#endif + return std::string(buffer); + } + + TestEventListenerBase::TestEventListenerBase(ReporterConfig const & _config) + :StreamingReporterBase(_config) {} + + std::set<Verbosity> TestEventListenerBase::getSupportedVerbosities() { + return { Verbosity::Quiet, Verbosity::Normal, Verbosity::High }; + } + + void TestEventListenerBase::assertionStarting(AssertionInfo const &) {} + + bool TestEventListenerBase::assertionEnded(AssertionStats const &) { + return false; + } + +} // end namespace Catch +// end catch_reporter_bases.cpp +// start catch_reporter_compact.cpp + +namespace { + +#ifdef CATCH_PLATFORM_MAC + const char* failedString() { return "FAILED"; } + const char* passedString() { return "PASSED"; } +#else + const char* failedString() { return "failed"; } + const char* passedString() { return "passed"; } +#endif + + // Colour::LightGrey + Catch::Colour::Code dimColour() { return Catch::Colour::FileName; } + + std::string bothOrAll( std::size_t count ) { + return count == 1 ? std::string() : + count == 2 ? "both " : "all " ; + } + +} // anon namespace + +namespace Catch { +namespace { +// Colour, message variants: +// - white: No tests ran. +// - red: Failed [both/all] N test cases, failed [both/all] M assertions. +// - white: Passed [both/all] N test cases (no assertions). +// - red: Failed N tests cases, failed M assertions. +// - green: Passed [both/all] N tests cases with M assertions. +void printTotals(std::ostream& out, const Totals& totals) { + if (totals.testCases.total() == 0) { + out << "No tests ran."; + } else if (totals.testCases.failed == totals.testCases.total()) { + Colour colour(Colour::ResultError); + const std::string qualify_assertions_failed = + totals.assertions.failed == totals.assertions.total() ? + bothOrAll(totals.assertions.failed) : std::string(); + out << + "Failed " << bothOrAll(totals.testCases.failed) + << pluralise(totals.testCases.failed, "test case") << ", " + "failed " << qualify_assertions_failed << + pluralise(totals.assertions.failed, "assertion") << '.'; + } else if (totals.assertions.total() == 0) { + out << + "Passed " << bothOrAll(totals.testCases.total()) + << pluralise(totals.testCases.total(), "test case") + << " (no assertions)."; + } else if (totals.assertions.failed) { + Colour colour(Colour::ResultError); + out << + "Failed " << pluralise(totals.testCases.failed, "test case") << ", " + "failed " << pluralise(totals.assertions.failed, "assertion") << '.'; + } else { + Colour colour(Colour::ResultSuccess); + out << + "Passed " << bothOrAll(totals.testCases.passed) + << pluralise(totals.testCases.passed, "test case") << + " with " << pluralise(totals.assertions.passed, "assertion") << '.'; + } +} + +// Implementation of CompactReporter formatting +class AssertionPrinter { +public: + AssertionPrinter& operator= (AssertionPrinter const&) = delete; + AssertionPrinter(AssertionPrinter const&) = delete; + AssertionPrinter(std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages) + : stream(_stream) + , result(_stats.assertionResult) + , messages(_stats.infoMessages) + , itMessage(_stats.infoMessages.begin()) + , printInfoMessages(_printInfoMessages) {} + + void print() { + printSourceInfo(); + + itMessage = messages.begin(); + + switch (result.getResultType()) { + case ResultWas::Ok: + printResultType(Colour::ResultSuccess, passedString()); + printOriginalExpression(); + printReconstructedExpression(); + if (!result.hasExpression()) + printRemainingMessages(Colour::None); + else + printRemainingMessages(); + break; + case ResultWas::ExpressionFailed: + if (result.isOk()) + printResultType(Colour::ResultSuccess, failedString() + std::string(" - but was ok")); + else + printResultType(Colour::Error, failedString()); + printOriginalExpression(); + printReconstructedExpression(); + printRemainingMessages(); + break; + case ResultWas::ThrewException: + printResultType(Colour::Error, failedString()); + printIssue("unexpected exception with message:"); + printMessage(); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::FatalErrorCondition: + printResultType(Colour::Error, failedString()); + printIssue("fatal error condition with message:"); + printMessage(); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::DidntThrowException: + printResultType(Colour::Error, failedString()); + printIssue("expected exception, got none"); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::Info: + printResultType(Colour::None, "info"); + printMessage(); + printRemainingMessages(); + break; + case ResultWas::Warning: + printResultType(Colour::None, "warning"); + printMessage(); + printRemainingMessages(); + break; + case ResultWas::ExplicitFailure: + printResultType(Colour::Error, failedString()); + printIssue("explicitly"); + printRemainingMessages(Colour::None); + break; + // These cases are here to prevent compiler warnings + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + printResultType(Colour::Error, "** internal error **"); + break; + } + } + +private: + void printSourceInfo() const { + Colour colourGuard(Colour::FileName); + stream << result.getSourceInfo() << ':'; + } + + void printResultType(Colour::Code colour, std::string const& passOrFail) const { + if (!passOrFail.empty()) { + { + Colour colourGuard(colour); + stream << ' ' << passOrFail; + } + stream << ':'; + } + } + + void printIssue(std::string const& issue) const { + stream << ' ' << issue; + } + + void printExpressionWas() { + if (result.hasExpression()) { + stream << ';'; + { + Colour colour(dimColour()); + stream << " expression was:"; + } + printOriginalExpression(); + } + } + + void printOriginalExpression() const { + if (result.hasExpression()) { + stream << ' ' << result.getExpression(); + } + } + + void printReconstructedExpression() const { + if (result.hasExpandedExpression()) { + { + Colour colour(dimColour()); + stream << " for: "; + } + stream << result.getExpandedExpression(); + } + } + + void printMessage() { + if (itMessage != messages.end()) { + stream << " '" << itMessage->message << '\''; + ++itMessage; + } + } + + void printRemainingMessages(Colour::Code colour = dimColour()) { + if (itMessage == messages.end()) + return; + + // using messages.end() directly yields (or auto) compilation error: + std::vector<MessageInfo>::const_iterator itEnd = messages.end(); + const std::size_t N = static_cast<std::size_t>(std::distance(itMessage, itEnd)); + + { + Colour colourGuard(colour); + stream << " with " << pluralise(N, "message") << ':'; + } + + for (; itMessage != itEnd; ) { + // If this assertion is a warning ignore any INFO messages + if (printInfoMessages || itMessage->type != ResultWas::Info) { + stream << " '" << itMessage->message << '\''; + if (++itMessage != itEnd) { + Colour colourGuard(dimColour()); + stream << " and"; + } + } + } + } + +private: + std::ostream& stream; + AssertionResult const& result; + std::vector<MessageInfo> messages; + std::vector<MessageInfo>::const_iterator itMessage; + bool printInfoMessages; +}; + +} // anon namespace + + std::string CompactReporter::getDescription() { + return "Reports test results on a single line, suitable for IDEs"; + } + + ReporterPreferences CompactReporter::getPreferences() const { + return m_reporterPrefs; + } + + void CompactReporter::noMatchingTestCases( std::string const& spec ) { + stream << "No test cases matched '" << spec << '\'' << std::endl; + } + + void CompactReporter::assertionStarting( AssertionInfo const& ) {} + + bool CompactReporter::assertionEnded( AssertionStats const& _assertionStats ) { + AssertionResult const& result = _assertionStats.assertionResult; + + bool printInfoMessages = true; + + // Drop out if result was successful and we're not printing those + if( !m_config->includeSuccessfulResults() && result.isOk() ) { + if( result.getResultType() != ResultWas::Warning ) + return false; + printInfoMessages = false; + } + + AssertionPrinter printer( stream, _assertionStats, printInfoMessages ); + printer.print(); + + stream << std::endl; + return true; + } + + void CompactReporter::sectionEnded(SectionStats const& _sectionStats) { + if (m_config->showDurations() == ShowDurations::Always) { + stream << getFormattedDuration(_sectionStats.durationInSeconds) << " s: " << _sectionStats.sectionInfo.name << std::endl; + } + } + + void CompactReporter::testRunEnded( TestRunStats const& _testRunStats ) { + printTotals( stream, _testRunStats.totals ); + stream << '\n' << std::endl; + StreamingReporterBase::testRunEnded( _testRunStats ); + } + + CompactReporter::~CompactReporter() {} + + CATCH_REGISTER_REPORTER( "compact", CompactReporter ) + +} // end namespace Catch +// end catch_reporter_compact.cpp +// start catch_reporter_console.cpp + +#include <cfloat> +#include <cstdio> + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable:4061) // Not all labels are EXPLICITLY handled in switch + // Note that 4062 (not all labels are handled + // and default is missing) is enabled +#endif + +namespace Catch { + +namespace { + +// Formatter impl for ConsoleReporter +class ConsoleAssertionPrinter { +public: + ConsoleAssertionPrinter& operator= (ConsoleAssertionPrinter const&) = delete; + ConsoleAssertionPrinter(ConsoleAssertionPrinter const&) = delete; + ConsoleAssertionPrinter(std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages) + : stream(_stream), + stats(_stats), + result(_stats.assertionResult), + colour(Colour::None), + message(result.getMessage()), + messages(_stats.infoMessages), + printInfoMessages(_printInfoMessages) { + switch (result.getResultType()) { + case ResultWas::Ok: + colour = Colour::Success; + passOrFail = "PASSED"; + //if( result.hasMessage() ) + if (_stats.infoMessages.size() == 1) + messageLabel = "with message"; + if (_stats.infoMessages.size() > 1) + messageLabel = "with messages"; + break; + case ResultWas::ExpressionFailed: + if (result.isOk()) { + colour = Colour::Success; + passOrFail = "FAILED - but was ok"; + } else { + colour = Colour::Error; + passOrFail = "FAILED"; + } + if (_stats.infoMessages.size() == 1) + messageLabel = "with message"; + if (_stats.infoMessages.size() > 1) + messageLabel = "with messages"; + break; + case ResultWas::ThrewException: + colour = Colour::Error; + passOrFail = "FAILED"; + messageLabel = "due to unexpected exception with "; + if (_stats.infoMessages.size() == 1) + messageLabel += "message"; + if (_stats.infoMessages.size() > 1) + messageLabel += "messages"; + break; + case ResultWas::FatalErrorCondition: + colour = Colour::Error; + passOrFail = "FAILED"; + messageLabel = "due to a fatal error condition"; + break; + case ResultWas::DidntThrowException: + colour = Colour::Error; + passOrFail = "FAILED"; + messageLabel = "because no exception was thrown where one was expected"; + break; + case ResultWas::Info: + messageLabel = "info"; + break; + case ResultWas::Warning: + messageLabel = "warning"; + break; + case ResultWas::ExplicitFailure: + passOrFail = "FAILED"; + colour = Colour::Error; + if (_stats.infoMessages.size() == 1) + messageLabel = "explicitly with message"; + if (_stats.infoMessages.size() > 1) + messageLabel = "explicitly with messages"; + break; + // These cases are here to prevent compiler warnings + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + passOrFail = "** internal error **"; + colour = Colour::Error; + break; + } + } + + void print() const { + printSourceInfo(); + if (stats.totals.assertions.total() > 0) { + printResultType(); + printOriginalExpression(); + printReconstructedExpression(); + } else { + stream << '\n'; + } + printMessage(); + } + +private: + void printResultType() const { + if (!passOrFail.empty()) { + Colour colourGuard(colour); + stream << passOrFail << ":\n"; + } + } + void printOriginalExpression() const { + if (result.hasExpression()) { + Colour colourGuard(Colour::OriginalExpression); + stream << " "; + stream << result.getExpressionInMacro(); + stream << '\n'; + } + } + void printReconstructedExpression() const { + if (result.hasExpandedExpression()) { + stream << "with expansion:\n"; + Colour colourGuard(Colour::ReconstructedExpression); + stream << Column(result.getExpandedExpression()).indent(2) << '\n'; + } + } + void printMessage() const { + if (!messageLabel.empty()) + stream << messageLabel << ':' << '\n'; + for (auto const& msg : messages) { + // If this assertion is a warning ignore any INFO messages + if (printInfoMessages || msg.type != ResultWas::Info) + stream << Column(msg.message).indent(2) << '\n'; + } + } + void printSourceInfo() const { + Colour colourGuard(Colour::FileName); + stream << result.getSourceInfo() << ": "; + } + + std::ostream& stream; + AssertionStats const& stats; + AssertionResult const& result; + Colour::Code colour; + std::string passOrFail; + std::string messageLabel; + std::string message; + std::vector<MessageInfo> messages; + bool printInfoMessages; +}; + +std::size_t makeRatio(std::size_t number, std::size_t total) { + std::size_t ratio = total > 0 ? CATCH_CONFIG_CONSOLE_WIDTH * number / total : 0; + return (ratio == 0 && number > 0) ? 1 : ratio; +} + +std::size_t& findMax(std::size_t& i, std::size_t& j, std::size_t& k) { + if (i > j && i > k) + return i; + else if (j > k) + return j; + else + return k; +} + +struct ColumnInfo { + enum Justification { Left, Right }; + std::string name; + int width; + Justification justification; +}; +struct ColumnBreak {}; +struct RowBreak {}; + +class Duration { + enum class Unit { + Auto, + Nanoseconds, + Microseconds, + Milliseconds, + Seconds, + Minutes + }; + static const uint64_t s_nanosecondsInAMicrosecond = 1000; + static const uint64_t s_nanosecondsInAMillisecond = 1000 * s_nanosecondsInAMicrosecond; + static const uint64_t s_nanosecondsInASecond = 1000 * s_nanosecondsInAMillisecond; + static const uint64_t s_nanosecondsInAMinute = 60 * s_nanosecondsInASecond; + + uint64_t m_inNanoseconds; + Unit m_units; + +public: + explicit Duration(uint64_t inNanoseconds, Unit units = Unit::Auto) + : m_inNanoseconds(inNanoseconds), + m_units(units) { + if (m_units == Unit::Auto) { + if (m_inNanoseconds < s_nanosecondsInAMicrosecond) + m_units = Unit::Nanoseconds; + else if (m_inNanoseconds < s_nanosecondsInAMillisecond) + m_units = Unit::Microseconds; + else if (m_inNanoseconds < s_nanosecondsInASecond) + m_units = Unit::Milliseconds; + else if (m_inNanoseconds < s_nanosecondsInAMinute) + m_units = Unit::Seconds; + else + m_units = Unit::Minutes; + } + + } + + auto value() const -> double { + switch (m_units) { + case Unit::Microseconds: + return m_inNanoseconds / static_cast<double>(s_nanosecondsInAMicrosecond); + case Unit::Milliseconds: + return m_inNanoseconds / static_cast<double>(s_nanosecondsInAMillisecond); + case Unit::Seconds: + return m_inNanoseconds / static_cast<double>(s_nanosecondsInASecond); + case Unit::Minutes: + return m_inNanoseconds / static_cast<double>(s_nanosecondsInAMinute); + default: + return static_cast<double>(m_inNanoseconds); + } + } + auto unitsAsString() const -> std::string { + switch (m_units) { + case Unit::Nanoseconds: + return "ns"; + case Unit::Microseconds: + return "µs"; + case Unit::Milliseconds: + return "ms"; + case Unit::Seconds: + return "s"; + case Unit::Minutes: + return "m"; + default: + return "** internal error **"; + } + + } + friend auto operator << (std::ostream& os, Duration const& duration) -> std::ostream& { + return os << duration.value() << " " << duration.unitsAsString(); + } +}; +} // end anon namespace + +class TablePrinter { + std::ostream& m_os; + std::vector<ColumnInfo> m_columnInfos; + std::ostringstream m_oss; + int m_currentColumn = -1; + bool m_isOpen = false; + +public: + TablePrinter( std::ostream& os, std::vector<ColumnInfo> columnInfos ) + : m_os( os ), + m_columnInfos( std::move( columnInfos ) ) {} + + auto columnInfos() const -> std::vector<ColumnInfo> const& { + return m_columnInfos; + } + + void open() { + if (!m_isOpen) { + m_isOpen = true; + *this << RowBreak(); + for (auto const& info : m_columnInfos) + *this << info.name << ColumnBreak(); + *this << RowBreak(); + m_os << Catch::getLineOfChars<'-'>() << "\n"; + } + } + void close() { + if (m_isOpen) { + *this << RowBreak(); + m_os << std::endl; + m_isOpen = false; + } + } + + template<typename T> + friend TablePrinter& operator << (TablePrinter& tp, T const& value) { + tp.m_oss << value; + return tp; + } + + friend TablePrinter& operator << (TablePrinter& tp, ColumnBreak) { + auto colStr = tp.m_oss.str(); + // This takes account of utf8 encodings + auto strSize = Catch::StringRef(colStr).numberOfCharacters(); + tp.m_oss.str(""); + tp.open(); + if (tp.m_currentColumn == static_cast<int>(tp.m_columnInfos.size() - 1)) { + tp.m_currentColumn = -1; + tp.m_os << "\n"; + } + tp.m_currentColumn++; + + auto colInfo = tp.m_columnInfos[tp.m_currentColumn]; + auto padding = (strSize + 2 < static_cast<std::size_t>(colInfo.width)) + ? std::string(colInfo.width - (strSize + 2), ' ') + : std::string(); + if (colInfo.justification == ColumnInfo::Left) + tp.m_os << colStr << padding << " "; + else + tp.m_os << padding << colStr << " "; + return tp; + } + + friend TablePrinter& operator << (TablePrinter& tp, RowBreak) { + if (tp.m_currentColumn > 0) { + tp.m_os << "\n"; + tp.m_currentColumn = -1; + } + return tp; + } +}; + +ConsoleReporter::ConsoleReporter(ReporterConfig const& config) + : StreamingReporterBase(config), + m_tablePrinter(new TablePrinter(config.stream(), + { + { "benchmark name", CATCH_CONFIG_CONSOLE_WIDTH - 32, ColumnInfo::Left }, + { "iters", 8, ColumnInfo::Right }, + { "elapsed ns", 14, ColumnInfo::Right }, + { "average", 14, ColumnInfo::Right } + })) {} +ConsoleReporter::~ConsoleReporter() = default; + +std::string ConsoleReporter::getDescription() { + return "Reports test results as plain lines of text"; +} + +void ConsoleReporter::noMatchingTestCases(std::string const& spec) { + stream << "No test cases matched '" << spec << '\'' << std::endl; +} + +void ConsoleReporter::assertionStarting(AssertionInfo const&) {} + +bool ConsoleReporter::assertionEnded(AssertionStats const& _assertionStats) { + AssertionResult const& result = _assertionStats.assertionResult; + + bool includeResults = m_config->includeSuccessfulResults() || !result.isOk(); + + // Drop out if result was successful but we're not printing them. + if (!includeResults && result.getResultType() != ResultWas::Warning) + return false; + + lazyPrint(); + + ConsoleAssertionPrinter printer(stream, _assertionStats, includeResults); + printer.print(); + stream << std::endl; + return true; +} + +void ConsoleReporter::sectionStarting(SectionInfo const& _sectionInfo) { + m_headerPrinted = false; + StreamingReporterBase::sectionStarting(_sectionInfo); +} +void ConsoleReporter::sectionEnded(SectionStats const& _sectionStats) { + m_tablePrinter->close(); + if (_sectionStats.missingAssertions) { + lazyPrint(); + Colour colour(Colour::ResultError); + if (m_sectionStack.size() > 1) + stream << "\nNo assertions in section"; + else + stream << "\nNo assertions in test case"; + stream << " '" << _sectionStats.sectionInfo.name << "'\n" << std::endl; + } + if (m_config->showDurations() == ShowDurations::Always) { + stream << getFormattedDuration(_sectionStats.durationInSeconds) << " s: " << _sectionStats.sectionInfo.name << std::endl; + } + if (m_headerPrinted) { + m_headerPrinted = false; + } + StreamingReporterBase::sectionEnded(_sectionStats); +} + +void ConsoleReporter::benchmarkStarting(BenchmarkInfo const& info) { + lazyPrintWithoutClosingBenchmarkTable(); + + auto nameCol = Column( info.name ).width( static_cast<std::size_t>( m_tablePrinter->columnInfos()[0].width - 2 ) ); + + bool firstLine = true; + for (auto line : nameCol) { + if (!firstLine) + (*m_tablePrinter) << ColumnBreak() << ColumnBreak() << ColumnBreak(); + else + firstLine = false; + + (*m_tablePrinter) << line << ColumnBreak(); + } +} +void ConsoleReporter::benchmarkEnded(BenchmarkStats const& stats) { + Duration average(stats.elapsedTimeInNanoseconds / stats.iterations); + (*m_tablePrinter) + << stats.iterations << ColumnBreak() + << stats.elapsedTimeInNanoseconds << ColumnBreak() + << average << ColumnBreak(); +} + +void ConsoleReporter::testCaseEnded(TestCaseStats const& _testCaseStats) { + m_tablePrinter->close(); + StreamingReporterBase::testCaseEnded(_testCaseStats); + m_headerPrinted = false; +} +void ConsoleReporter::testGroupEnded(TestGroupStats const& _testGroupStats) { + if (currentGroupInfo.used) { + printSummaryDivider(); + stream << "Summary for group '" << _testGroupStats.groupInfo.name << "':\n"; + printTotals(_testGroupStats.totals); + stream << '\n' << std::endl; + } + StreamingReporterBase::testGroupEnded(_testGroupStats); +} +void ConsoleReporter::testRunEnded(TestRunStats const& _testRunStats) { + printTotalsDivider(_testRunStats.totals); + printTotals(_testRunStats.totals); + stream << std::endl; + StreamingReporterBase::testRunEnded(_testRunStats); +} + +void ConsoleReporter::lazyPrint() { + + m_tablePrinter->close(); + lazyPrintWithoutClosingBenchmarkTable(); +} + +void ConsoleReporter::lazyPrintWithoutClosingBenchmarkTable() { + + if (!currentTestRunInfo.used) + lazyPrintRunInfo(); + if (!currentGroupInfo.used) + lazyPrintGroupInfo(); + + if (!m_headerPrinted) { + printTestCaseAndSectionHeader(); + m_headerPrinted = true; + } +} +void ConsoleReporter::lazyPrintRunInfo() { + stream << '\n' << getLineOfChars<'~'>() << '\n'; + Colour colour(Colour::SecondaryText); + stream << currentTestRunInfo->name + << " is a Catch v" << libraryVersion() << " host application.\n" + << "Run with -? for options\n\n"; + + if (m_config->rngSeed() != 0) + stream << "Randomness seeded to: " << m_config->rngSeed() << "\n\n"; + + currentTestRunInfo.used = true; +} +void ConsoleReporter::lazyPrintGroupInfo() { + if (!currentGroupInfo->name.empty() && currentGroupInfo->groupsCounts > 1) { + printClosedHeader("Group: " + currentGroupInfo->name); + currentGroupInfo.used = true; + } +} +void ConsoleReporter::printTestCaseAndSectionHeader() { + assert(!m_sectionStack.empty()); + printOpenHeader(currentTestCaseInfo->name); + + if (m_sectionStack.size() > 1) { + Colour colourGuard(Colour::Headers); + + auto + it = m_sectionStack.begin() + 1, // Skip first section (test case) + itEnd = m_sectionStack.end(); + for (; it != itEnd; ++it) + printHeaderString(it->name, 2); + } + + SourceLineInfo lineInfo = m_sectionStack.back().lineInfo; + + if (!lineInfo.empty()) { + stream << getLineOfChars<'-'>() << '\n'; + Colour colourGuard(Colour::FileName); + stream << lineInfo << '\n'; + } + stream << getLineOfChars<'.'>() << '\n' << std::endl; +} + +void ConsoleReporter::printClosedHeader(std::string const& _name) { + printOpenHeader(_name); + stream << getLineOfChars<'.'>() << '\n'; +} +void ConsoleReporter::printOpenHeader(std::string const& _name) { + stream << getLineOfChars<'-'>() << '\n'; + { + Colour colourGuard(Colour::Headers); + printHeaderString(_name); + } +} + +// if string has a : in first line will set indent to follow it on +// subsequent lines +void ConsoleReporter::printHeaderString(std::string const& _string, std::size_t indent) { + std::size_t i = _string.find(": "); + if (i != std::string::npos) + i += 2; + else + i = 0; + stream << Column(_string).indent(indent + i).initialIndent(indent) << '\n'; +} + +struct SummaryColumn { + + SummaryColumn( std::string _label, Colour::Code _colour ) + : label( std::move( _label ) ), + colour( _colour ) {} + SummaryColumn addRow( std::size_t count ) { + ReusableStringStream rss; + rss << count; + std::string row = rss.str(); + for (auto& oldRow : rows) { + while (oldRow.size() < row.size()) + oldRow = ' ' + oldRow; + while (oldRow.size() > row.size()) + row = ' ' + row; + } + rows.push_back(row); + return *this; + } + + std::string label; + Colour::Code colour; + std::vector<std::string> rows; + +}; + +void ConsoleReporter::printTotals( Totals const& totals ) { + if (totals.testCases.total() == 0) { + stream << Colour(Colour::Warning) << "No tests ran\n"; + } else if (totals.assertions.total() > 0 && totals.testCases.allPassed()) { + stream << Colour(Colour::ResultSuccess) << "All tests passed"; + stream << " (" + << pluralise(totals.assertions.passed, "assertion") << " in " + << pluralise(totals.testCases.passed, "test case") << ')' + << '\n'; + } else { + + std::vector<SummaryColumn> columns; + columns.push_back(SummaryColumn("", Colour::None) + .addRow(totals.testCases.total()) + .addRow(totals.assertions.total())); + columns.push_back(SummaryColumn("passed", Colour::Success) + .addRow(totals.testCases.passed) + .addRow(totals.assertions.passed)); + columns.push_back(SummaryColumn("failed", Colour::ResultError) + .addRow(totals.testCases.failed) + .addRow(totals.assertions.failed)); + columns.push_back(SummaryColumn("failed as expected", Colour::ResultExpectedFailure) + .addRow(totals.testCases.failedButOk) + .addRow(totals.assertions.failedButOk)); + + printSummaryRow("test cases", columns, 0); + printSummaryRow("assertions", columns, 1); + } +} +void ConsoleReporter::printSummaryRow(std::string const& label, std::vector<SummaryColumn> const& cols, std::size_t row) { + for (auto col : cols) { + std::string value = col.rows[row]; + if (col.label.empty()) { + stream << label << ": "; + if (value != "0") + stream << value; + else + stream << Colour(Colour::Warning) << "- none -"; + } else if (value != "0") { + stream << Colour(Colour::LightGrey) << " | "; + stream << Colour(col.colour) + << value << ' ' << col.label; + } + } + stream << '\n'; +} + +void ConsoleReporter::printTotalsDivider(Totals const& totals) { + if (totals.testCases.total() > 0) { + std::size_t failedRatio = makeRatio(totals.testCases.failed, totals.testCases.total()); + std::size_t failedButOkRatio = makeRatio(totals.testCases.failedButOk, totals.testCases.total()); + std::size_t passedRatio = makeRatio(totals.testCases.passed, totals.testCases.total()); + while (failedRatio + failedButOkRatio + passedRatio < CATCH_CONFIG_CONSOLE_WIDTH - 1) + findMax(failedRatio, failedButOkRatio, passedRatio)++; + while (failedRatio + failedButOkRatio + passedRatio > CATCH_CONFIG_CONSOLE_WIDTH - 1) + findMax(failedRatio, failedButOkRatio, passedRatio)--; + + stream << Colour(Colour::Error) << std::string(failedRatio, '='); + stream << Colour(Colour::ResultExpectedFailure) << std::string(failedButOkRatio, '='); + if (totals.testCases.allPassed()) + stream << Colour(Colour::ResultSuccess) << std::string(passedRatio, '='); + else + stream << Colour(Colour::Success) << std::string(passedRatio, '='); + } else { + stream << Colour(Colour::Warning) << std::string(CATCH_CONFIG_CONSOLE_WIDTH - 1, '='); + } + stream << '\n'; +} +void ConsoleReporter::printSummaryDivider() { + stream << getLineOfChars<'-'>() << '\n'; +} + +CATCH_REGISTER_REPORTER("console", ConsoleReporter) + +} // end namespace Catch + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif +// end catch_reporter_console.cpp +// start catch_reporter_junit.cpp + +#include <cassert> +#include <sstream> +#include <ctime> +#include <algorithm> + +namespace Catch { + + namespace { + std::string getCurrentTimestamp() { + // Beware, this is not reentrant because of backward compatibility issues + // Also, UTC only, again because of backward compatibility (%z is C++11) + time_t rawtime; + std::time(&rawtime); + auto const timeStampSize = sizeof("2017-01-16T17:06:45Z"); + +#ifdef _MSC_VER + std::tm timeInfo = {}; + gmtime_s(&timeInfo, &rawtime); +#else + std::tm* timeInfo; + timeInfo = std::gmtime(&rawtime); +#endif + + char timeStamp[timeStampSize]; + const char * const fmt = "%Y-%m-%dT%H:%M:%SZ"; + +#ifdef _MSC_VER + std::strftime(timeStamp, timeStampSize, fmt, &timeInfo); +#else + std::strftime(timeStamp, timeStampSize, fmt, timeInfo); +#endif + return std::string(timeStamp); + } + + std::string fileNameTag(const std::vector<std::string> &tags) { + auto it = std::find_if(begin(tags), + end(tags), + [] (std::string const& tag) {return tag.front() == '#'; }); + if (it != tags.end()) + return it->substr(1); + return std::string(); + } + } // anonymous namespace + + JunitReporter::JunitReporter( ReporterConfig const& _config ) + : CumulativeReporterBase( _config ), + xml( _config.stream() ) + { + m_reporterPrefs.shouldRedirectStdOut = true; + m_reporterPrefs.shouldReportAllAssertions = true; + } + + JunitReporter::~JunitReporter() {} + + std::string JunitReporter::getDescription() { + return "Reports test results in an XML format that looks like Ant's junitreport target"; + } + + void JunitReporter::noMatchingTestCases( std::string const& /*spec*/ ) {} + + void JunitReporter::testRunStarting( TestRunInfo const& runInfo ) { + CumulativeReporterBase::testRunStarting( runInfo ); + xml.startElement( "testsuites" ); + } + + void JunitReporter::testGroupStarting( GroupInfo const& groupInfo ) { + suiteTimer.start(); + stdOutForSuite.clear(); + stdErrForSuite.clear(); + unexpectedExceptions = 0; + CumulativeReporterBase::testGroupStarting( groupInfo ); + } + + void JunitReporter::testCaseStarting( TestCaseInfo const& testCaseInfo ) { + m_okToFail = testCaseInfo.okToFail(); + } + + bool JunitReporter::assertionEnded( AssertionStats const& assertionStats ) { + if( assertionStats.assertionResult.getResultType() == ResultWas::ThrewException && !m_okToFail ) + unexpectedExceptions++; + return CumulativeReporterBase::assertionEnded( assertionStats ); + } + + void JunitReporter::testCaseEnded( TestCaseStats const& testCaseStats ) { + stdOutForSuite += testCaseStats.stdOut; + stdErrForSuite += testCaseStats.stdErr; + CumulativeReporterBase::testCaseEnded( testCaseStats ); + } + + void JunitReporter::testGroupEnded( TestGroupStats const& testGroupStats ) { + double suiteTime = suiteTimer.getElapsedSeconds(); + CumulativeReporterBase::testGroupEnded( testGroupStats ); + writeGroup( *m_testGroups.back(), suiteTime ); + } + + void JunitReporter::testRunEndedCumulative() { + xml.endElement(); + } + + void JunitReporter::writeGroup( TestGroupNode const& groupNode, double suiteTime ) { + XmlWriter::ScopedElement e = xml.scopedElement( "testsuite" ); + TestGroupStats const& stats = groupNode.value; + xml.writeAttribute( "name", stats.groupInfo.name ); + xml.writeAttribute( "errors", unexpectedExceptions ); + xml.writeAttribute( "failures", stats.totals.assertions.failed-unexpectedExceptions ); + xml.writeAttribute( "tests", stats.totals.assertions.total() ); + xml.writeAttribute( "hostname", "tbd" ); // !TBD + if( m_config->showDurations() == ShowDurations::Never ) + xml.writeAttribute( "time", "" ); + else + xml.writeAttribute( "time", suiteTime ); + xml.writeAttribute( "timestamp", getCurrentTimestamp() ); + + // Write test cases + for( auto const& child : groupNode.children ) + writeTestCase( *child ); + + xml.scopedElement( "system-out" ).writeText( trim( stdOutForSuite ), false ); + xml.scopedElement( "system-err" ).writeText( trim( stdErrForSuite ), false ); + } + + void JunitReporter::writeTestCase( TestCaseNode const& testCaseNode ) { + TestCaseStats const& stats = testCaseNode.value; + + // All test cases have exactly one section - which represents the + // test case itself. That section may have 0-n nested sections + assert( testCaseNode.children.size() == 1 ); + SectionNode const& rootSection = *testCaseNode.children.front(); + + std::string className = stats.testInfo.className; + + if( className.empty() ) { + className = fileNameTag(stats.testInfo.tags); + if ( className.empty() ) + className = "global"; + } + + if ( !m_config->name().empty() ) + className = m_config->name() + "." + className; + + writeSection( className, "", rootSection ); + } + + void JunitReporter::writeSection( std::string const& className, + std::string const& rootName, + SectionNode const& sectionNode ) { + std::string name = trim( sectionNode.stats.sectionInfo.name ); + if( !rootName.empty() ) + name = rootName + '/' + name; + + if( !sectionNode.assertions.empty() || + !sectionNode.stdOut.empty() || + !sectionNode.stdErr.empty() ) { + XmlWriter::ScopedElement e = xml.scopedElement( "testcase" ); + if( className.empty() ) { + xml.writeAttribute( "classname", name ); + xml.writeAttribute( "name", "root" ); + } + else { + xml.writeAttribute( "classname", className ); + xml.writeAttribute( "name", name ); + } + xml.writeAttribute( "time", ::Catch::Detail::stringify( sectionNode.stats.durationInSeconds ) ); + + writeAssertions( sectionNode ); + + if( !sectionNode.stdOut.empty() ) + xml.scopedElement( "system-out" ).writeText( trim( sectionNode.stdOut ), false ); + if( !sectionNode.stdErr.empty() ) + xml.scopedElement( "system-err" ).writeText( trim( sectionNode.stdErr ), false ); + } + for( auto const& childNode : sectionNode.childSections ) + if( className.empty() ) + writeSection( name, "", *childNode ); + else + writeSection( className, name, *childNode ); + } + + void JunitReporter::writeAssertions( SectionNode const& sectionNode ) { + for( auto const& assertion : sectionNode.assertions ) + writeAssertion( assertion ); + } + + void JunitReporter::writeAssertion( AssertionStats const& stats ) { + AssertionResult const& result = stats.assertionResult; + if( !result.isOk() ) { + std::string elementName; + switch( result.getResultType() ) { + case ResultWas::ThrewException: + case ResultWas::FatalErrorCondition: + elementName = "error"; + break; + case ResultWas::ExplicitFailure: + elementName = "failure"; + break; + case ResultWas::ExpressionFailed: + elementName = "failure"; + break; + case ResultWas::DidntThrowException: + elementName = "failure"; + break; + + // We should never see these here: + case ResultWas::Info: + case ResultWas::Warning: + case ResultWas::Ok: + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + elementName = "internalError"; + break; + } + + XmlWriter::ScopedElement e = xml.scopedElement( elementName ); + + xml.writeAttribute( "message", result.getExpandedExpression() ); + xml.writeAttribute( "type", result.getTestMacroName() ); + + ReusableStringStream rss; + if( !result.getMessage().empty() ) + rss << result.getMessage() << '\n'; + for( auto const& msg : stats.infoMessages ) + if( msg.type == ResultWas::Info ) + rss << msg.message << '\n'; + + rss << "at " << result.getSourceInfo(); + xml.writeText( rss.str(), false ); + } + } + + CATCH_REGISTER_REPORTER( "junit", JunitReporter ) + +} // end namespace Catch +// end catch_reporter_junit.cpp +// start catch_reporter_listening.cpp + +#include <cassert> + +namespace Catch { + + ListeningReporter::ListeningReporter() { + // We will assume that listeners will always want all assertions + m_preferences.shouldReportAllAssertions = true; + } + + void ListeningReporter::addListener( IStreamingReporterPtr&& listener ) { + m_listeners.push_back( std::move( listener ) ); + } + + void ListeningReporter::addReporter(IStreamingReporterPtr&& reporter) { + assert(!m_reporter && "Listening reporter can wrap only 1 real reporter"); + m_reporter = std::move( reporter ); + m_preferences.shouldRedirectStdOut = m_reporter->getPreferences().shouldRedirectStdOut; + } + + ReporterPreferences ListeningReporter::getPreferences() const { + return m_preferences; + } + + std::set<Verbosity> ListeningReporter::getSupportedVerbosities() { + return std::set<Verbosity>{ }; + } + + void ListeningReporter::noMatchingTestCases( std::string const& spec ) { + for ( auto const& listener : m_listeners ) { + listener->noMatchingTestCases( spec ); + } + m_reporter->noMatchingTestCases( spec ); + } + + void ListeningReporter::benchmarkStarting( BenchmarkInfo const& benchmarkInfo ) { + for ( auto const& listener : m_listeners ) { + listener->benchmarkStarting( benchmarkInfo ); + } + m_reporter->benchmarkStarting( benchmarkInfo ); + } + void ListeningReporter::benchmarkEnded( BenchmarkStats const& benchmarkStats ) { + for ( auto const& listener : m_listeners ) { + listener->benchmarkEnded( benchmarkStats ); + } + m_reporter->benchmarkEnded( benchmarkStats ); + } + + void ListeningReporter::testRunStarting( TestRunInfo const& testRunInfo ) { + for ( auto const& listener : m_listeners ) { + listener->testRunStarting( testRunInfo ); + } + m_reporter->testRunStarting( testRunInfo ); + } + + void ListeningReporter::testGroupStarting( GroupInfo const& groupInfo ) { + for ( auto const& listener : m_listeners ) { + listener->testGroupStarting( groupInfo ); + } + m_reporter->testGroupStarting( groupInfo ); + } + + void ListeningReporter::testCaseStarting( TestCaseInfo const& testInfo ) { + for ( auto const& listener : m_listeners ) { + listener->testCaseStarting( testInfo ); + } + m_reporter->testCaseStarting( testInfo ); + } + + void ListeningReporter::sectionStarting( SectionInfo const& sectionInfo ) { + for ( auto const& listener : m_listeners ) { + listener->sectionStarting( sectionInfo ); + } + m_reporter->sectionStarting( sectionInfo ); + } + + void ListeningReporter::assertionStarting( AssertionInfo const& assertionInfo ) { + for ( auto const& listener : m_listeners ) { + listener->assertionStarting( assertionInfo ); + } + m_reporter->assertionStarting( assertionInfo ); + } + + // The return value indicates if the messages buffer should be cleared: + bool ListeningReporter::assertionEnded( AssertionStats const& assertionStats ) { + for( auto const& listener : m_listeners ) { + static_cast<void>( listener->assertionEnded( assertionStats ) ); + } + return m_reporter->assertionEnded( assertionStats ); + } + + void ListeningReporter::sectionEnded( SectionStats const& sectionStats ) { + for ( auto const& listener : m_listeners ) { + listener->sectionEnded( sectionStats ); + } + m_reporter->sectionEnded( sectionStats ); + } + + void ListeningReporter::testCaseEnded( TestCaseStats const& testCaseStats ) { + for ( auto const& listener : m_listeners ) { + listener->testCaseEnded( testCaseStats ); + } + m_reporter->testCaseEnded( testCaseStats ); + } + + void ListeningReporter::testGroupEnded( TestGroupStats const& testGroupStats ) { + for ( auto const& listener : m_listeners ) { + listener->testGroupEnded( testGroupStats ); + } + m_reporter->testGroupEnded( testGroupStats ); + } + + void ListeningReporter::testRunEnded( TestRunStats const& testRunStats ) { + for ( auto const& listener : m_listeners ) { + listener->testRunEnded( testRunStats ); + } + m_reporter->testRunEnded( testRunStats ); + } + + void ListeningReporter::skipTest( TestCaseInfo const& testInfo ) { + for ( auto const& listener : m_listeners ) { + listener->skipTest( testInfo ); + } + m_reporter->skipTest( testInfo ); + } + + bool ListeningReporter::isMulti() const { + return true; + } + +} // end namespace Catch +// end catch_reporter_listening.cpp +// start catch_reporter_xml.cpp + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable:4061) // Not all labels are EXPLICITLY handled in switch + // Note that 4062 (not all labels are handled + // and default is missing) is enabled +#endif + +namespace Catch { + XmlReporter::XmlReporter( ReporterConfig const& _config ) + : StreamingReporterBase( _config ), + m_xml(_config.stream()) + { + m_reporterPrefs.shouldRedirectStdOut = true; + m_reporterPrefs.shouldReportAllAssertions = true; + } + + XmlReporter::~XmlReporter() = default; + + std::string XmlReporter::getDescription() { + return "Reports test results as an XML document"; + } + + std::string XmlReporter::getStylesheetRef() const { + return std::string(); + } + + void XmlReporter::writeSourceInfo( SourceLineInfo const& sourceInfo ) { + m_xml + .writeAttribute( "filename", sourceInfo.file ) + .writeAttribute( "line", sourceInfo.line ); + } + + void XmlReporter::noMatchingTestCases( std::string const& s ) { + StreamingReporterBase::noMatchingTestCases( s ); + } + + void XmlReporter::testRunStarting( TestRunInfo const& testInfo ) { + StreamingReporterBase::testRunStarting( testInfo ); + std::string stylesheetRef = getStylesheetRef(); + if( !stylesheetRef.empty() ) + m_xml.writeStylesheetRef( stylesheetRef ); + m_xml.startElement( "Catch" ); + if( !m_config->name().empty() ) + m_xml.writeAttribute( "name", m_config->name() ); + if( m_config->rngSeed() != 0 ) + m_xml.scopedElement( "Randomness" ) + .writeAttribute( "seed", m_config->rngSeed() ); + } + + void XmlReporter::testGroupStarting( GroupInfo const& groupInfo ) { + StreamingReporterBase::testGroupStarting( groupInfo ); + m_xml.startElement( "Group" ) + .writeAttribute( "name", groupInfo.name ); + } + + void XmlReporter::testCaseStarting( TestCaseInfo const& testInfo ) { + StreamingReporterBase::testCaseStarting(testInfo); + m_xml.startElement( "TestCase" ) + .writeAttribute( "name", trim( testInfo.name ) ) + .writeAttribute( "description", testInfo.description ) + .writeAttribute( "tags", testInfo.tagsAsString() ); + + writeSourceInfo( testInfo.lineInfo ); + + if ( m_config->showDurations() == ShowDurations::Always ) + m_testCaseTimer.start(); + m_xml.ensureTagClosed(); + } + + void XmlReporter::sectionStarting( SectionInfo const& sectionInfo ) { + StreamingReporterBase::sectionStarting( sectionInfo ); + if( m_sectionDepth++ > 0 ) { + m_xml.startElement( "Section" ) + .writeAttribute( "name", trim( sectionInfo.name ) ); + writeSourceInfo( sectionInfo.lineInfo ); + m_xml.ensureTagClosed(); + } + } + + void XmlReporter::assertionStarting( AssertionInfo const& ) { } + + bool XmlReporter::assertionEnded( AssertionStats const& assertionStats ) { + + AssertionResult const& result = assertionStats.assertionResult; + + bool includeResults = m_config->includeSuccessfulResults() || !result.isOk(); + + if( includeResults || result.getResultType() == ResultWas::Warning ) { + // Print any info messages in <Info> tags. + for( auto const& msg : assertionStats.infoMessages ) { + if( msg.type == ResultWas::Info && includeResults ) { + m_xml.scopedElement( "Info" ) + .writeText( msg.message ); + } else if ( msg.type == ResultWas::Warning ) { + m_xml.scopedElement( "Warning" ) + .writeText( msg.message ); + } + } + } + + // Drop out if result was successful but we're not printing them. + if( !includeResults && result.getResultType() != ResultWas::Warning ) + return true; + + // Print the expression if there is one. + if( result.hasExpression() ) { + m_xml.startElement( "Expression" ) + .writeAttribute( "success", result.succeeded() ) + .writeAttribute( "type", result.getTestMacroName() ); + + writeSourceInfo( result.getSourceInfo() ); + + m_xml.scopedElement( "Original" ) + .writeText( result.getExpression() ); + m_xml.scopedElement( "Expanded" ) + .writeText( result.getExpandedExpression() ); + } + + // And... Print a result applicable to each result type. + switch( result.getResultType() ) { + case ResultWas::ThrewException: + m_xml.startElement( "Exception" ); + writeSourceInfo( result.getSourceInfo() ); + m_xml.writeText( result.getMessage() ); + m_xml.endElement(); + break; + case ResultWas::FatalErrorCondition: + m_xml.startElement( "FatalErrorCondition" ); + writeSourceInfo( result.getSourceInfo() ); + m_xml.writeText( result.getMessage() ); + m_xml.endElement(); + break; + case ResultWas::Info: + m_xml.scopedElement( "Info" ) + .writeText( result.getMessage() ); + break; + case ResultWas::Warning: + // Warning will already have been written + break; + case ResultWas::ExplicitFailure: + m_xml.startElement( "Failure" ); + writeSourceInfo( result.getSourceInfo() ); + m_xml.writeText( result.getMessage() ); + m_xml.endElement(); + break; + default: + break; + } + + if( result.hasExpression() ) + m_xml.endElement(); + + return true; + } + + void XmlReporter::sectionEnded( SectionStats const& sectionStats ) { + StreamingReporterBase::sectionEnded( sectionStats ); + if( --m_sectionDepth > 0 ) { + XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResults" ); + e.writeAttribute( "successes", sectionStats.assertions.passed ); + e.writeAttribute( "failures", sectionStats.assertions.failed ); + e.writeAttribute( "expectedFailures", sectionStats.assertions.failedButOk ); + + if ( m_config->showDurations() == ShowDurations::Always ) + e.writeAttribute( "durationInSeconds", sectionStats.durationInSeconds ); + + m_xml.endElement(); + } + } + + void XmlReporter::testCaseEnded( TestCaseStats const& testCaseStats ) { + StreamingReporterBase::testCaseEnded( testCaseStats ); + XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResult" ); + e.writeAttribute( "success", testCaseStats.totals.assertions.allOk() ); + + if ( m_config->showDurations() == ShowDurations::Always ) + e.writeAttribute( "durationInSeconds", m_testCaseTimer.getElapsedSeconds() ); + + if( !testCaseStats.stdOut.empty() ) + m_xml.scopedElement( "StdOut" ).writeText( trim( testCaseStats.stdOut ), false ); + if( !testCaseStats.stdErr.empty() ) + m_xml.scopedElement( "StdErr" ).writeText( trim( testCaseStats.stdErr ), false ); + + m_xml.endElement(); + } + + void XmlReporter::testGroupEnded( TestGroupStats const& testGroupStats ) { + StreamingReporterBase::testGroupEnded( testGroupStats ); + // TODO: Check testGroupStats.aborting and act accordingly. + m_xml.scopedElement( "OverallResults" ) + .writeAttribute( "successes", testGroupStats.totals.assertions.passed ) + .writeAttribute( "failures", testGroupStats.totals.assertions.failed ) + .writeAttribute( "expectedFailures", testGroupStats.totals.assertions.failedButOk ); + m_xml.endElement(); + } + + void XmlReporter::testRunEnded( TestRunStats const& testRunStats ) { + StreamingReporterBase::testRunEnded( testRunStats ); + m_xml.scopedElement( "OverallResults" ) + .writeAttribute( "successes", testRunStats.totals.assertions.passed ) + .writeAttribute( "failures", testRunStats.totals.assertions.failed ) + .writeAttribute( "expectedFailures", testRunStats.totals.assertions.failedButOk ); + m_xml.endElement(); + } + + CATCH_REGISTER_REPORTER( "xml", XmlReporter ) + +} // end namespace Catch + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif +// end catch_reporter_xml.cpp + +namespace Catch { + LeakDetector leakDetector; +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +// end catch_impl.hpp +#endif + +#ifdef CATCH_CONFIG_MAIN +// start catch_default_main.hpp + +#ifndef __OBJC__ + +#if defined(CATCH_CONFIG_WCHAR) && defined(WIN32) && defined(_UNICODE) && !defined(DO_NOT_USE_WMAIN) +// Standard C/C++ Win32 Unicode wmain entry point +extern "C" int wmain (int argc, wchar_t * argv[], wchar_t * []) { +#else +// Standard C/C++ main entry point +int main (int argc, char * argv[]) { +#endif + + return Catch::Session().run( argc, argv ); +} + +#else // __OBJC__ + +// Objective-C entry point +int main (int argc, char * const argv[]) { +#if !CATCH_ARC_ENABLED + NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; +#endif + + Catch::registerTestMethods(); + int result = Catch::Session().run( argc, (char**)argv ); + +#if !CATCH_ARC_ENABLED + [pool drain]; +#endif + + return result; +} + +#endif // __OBJC__ + +// end catch_default_main.hpp +#endif + +#if !defined(CATCH_CONFIG_IMPL_ONLY) + +#ifdef CLARA_CONFIG_MAIN_NOT_DEFINED +# undef CLARA_CONFIG_MAIN +#endif + +#if !defined(CATCH_CONFIG_DISABLE) +////// +// If this config identifier is defined then all CATCH macros are prefixed with CATCH_ +#ifdef CATCH_CONFIG_PREFIX_ALL + +#define CATCH_REQUIRE( ... ) INTERNAL_CATCH_TEST( "CATCH_REQUIRE", Catch::ResultDisposition::Normal, __VA_ARGS__ ) +#define CATCH_REQUIRE_FALSE( ... ) INTERNAL_CATCH_TEST( "CATCH_REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, __VA_ARGS__ ) + +#define CATCH_REQUIRE_THROWS( ... ) INTERNAL_CATCH_THROWS( "CATCH_REQUIRE_THROWS", Catch::ResultDisposition::Normal, "", __VA_ARGS__ ) +#define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CATCH_REQUIRE_THROWS_AS", exceptionType, Catch::ResultDisposition::Normal, expr ) +#define CATCH_REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( "CATCH_REQUIRE_THROWS_WITH", Catch::ResultDisposition::Normal, matcher, expr ) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CATCH_REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( "CATCH_REQUIRE_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::Normal, matcher, expr ) +#endif// CATCH_CONFIG_DISABLE_MATCHERS +#define CATCH_REQUIRE_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "CATCH_REQUIRE_NOTHROW", Catch::ResultDisposition::Normal, __VA_ARGS__ ) + +#define CATCH_CHECK( ... ) INTERNAL_CATCH_TEST( "CATCH_CHECK", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define CATCH_CHECK_FALSE( ... ) INTERNAL_CATCH_TEST( "CATCH_CHECK_FALSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, __VA_ARGS__ ) +#define CATCH_CHECKED_IF( ... ) INTERNAL_CATCH_IF( "CATCH_CHECKED_IF", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define CATCH_CHECKED_ELSE( ... ) INTERNAL_CATCH_ELSE( "CATCH_CHECKED_ELSE", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define CATCH_CHECK_NOFAIL( ... ) INTERNAL_CATCH_TEST( "CATCH_CHECK_NOFAIL", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ ) + +#define CATCH_CHECK_THROWS( ... ) INTERNAL_CATCH_THROWS( "CATCH_CHECK_THROWS", Catch::ResultDisposition::ContinueOnFailure, "", __VA_ARGS__ ) +#define CATCH_CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CATCH_CHECK_THROWS_AS", exceptionType, Catch::ResultDisposition::ContinueOnFailure, expr ) +#define CATCH_CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( "CATCH_CHECK_THROWS_WITH", Catch::ResultDisposition::ContinueOnFailure, matcher, expr ) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CATCH_CHECK_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( "CATCH_CHECK_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::ContinueOnFailure, matcher, expr ) +#endif // CATCH_CONFIG_DISABLE_MATCHERS +#define CATCH_CHECK_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "CATCH_CHECK_NOTHROW", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CATCH_CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CATCH_CHECK_THAT", matcher, Catch::ResultDisposition::ContinueOnFailure, arg ) + +#define CATCH_REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CATCH_REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg ) +#endif // CATCH_CONFIG_DISABLE_MATCHERS + +#define CATCH_INFO( msg ) INTERNAL_CATCH_INFO( "CATCH_INFO", msg ) +#define CATCH_WARN( msg ) INTERNAL_CATCH_MSG( "CATCH_WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg ) +#define CATCH_CAPTURE( ... ) INTERNAL_CATCH_CAPTURE( INTERNAL_CATCH_UNIQUE_NAME(capturer), "CATCH_CAPTURE",__VA_ARGS__ ) + +#define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) +#define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) +#define CATCH_METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) +#define CATCH_REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ ) +#define CATCH_SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) +#define CATCH_DYNAMIC_SECTION( ... ) INTERNAL_CATCH_DYNAMIC_SECTION( __VA_ARGS__ ) +#define CATCH_FAIL( ... ) INTERNAL_CATCH_MSG( "CATCH_FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ ) +#define CATCH_FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( "CATCH_FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define CATCH_SUCCEED( ... ) INTERNAL_CATCH_MSG( "CATCH_SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + +#define CATCH_ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE() + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define CATCH_TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ ) +#define CATCH_TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ ) +#else +#define CATCH_TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ ) ) +#define CATCH_TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ ) ) +#endif + +#if !defined(CATCH_CONFIG_RUNTIME_STATIC_REQUIRE) +#define CATCH_STATIC_REQUIRE( ... ) static_assert( __VA_ARGS__ , #__VA_ARGS__ ); CATCH_SUCCEED( #__VA_ARGS__ ) +#define CATCH_STATIC_REQUIRE_FALSE( ... ) static_assert( !(__VA_ARGS__), "!(" #__VA_ARGS__ ")" ); CATCH_SUCCEED( #__VA_ARGS__ ) +#else +#define CATCH_STATIC_REQUIRE( ... ) CATCH_REQUIRE( __VA_ARGS__ ) +#define CATCH_STATIC_REQUIRE_FALSE( ... ) CATCH_REQUIRE_FALSE( __VA_ARGS__ ) +#endif + +// "BDD-style" convenience wrappers +#define CATCH_SCENARIO( ... ) CATCH_TEST_CASE( "Scenario: " __VA_ARGS__ ) +#define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ ) +#define CATCH_GIVEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " Given: " << desc ) +#define CATCH_AND_GIVEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( "And given: " << desc ) +#define CATCH_WHEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " When: " << desc ) +#define CATCH_AND_WHEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " And when: " << desc ) +#define CATCH_THEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " Then: " << desc ) +#define CATCH_AND_THEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " And: " << desc ) + +// If CATCH_CONFIG_PREFIX_ALL is not defined then the CATCH_ prefix is not required +#else + +#define REQUIRE( ... ) INTERNAL_CATCH_TEST( "REQUIRE", Catch::ResultDisposition::Normal, __VA_ARGS__ ) +#define REQUIRE_FALSE( ... ) INTERNAL_CATCH_TEST( "REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, __VA_ARGS__ ) + +#define REQUIRE_THROWS( ... ) INTERNAL_CATCH_THROWS( "REQUIRE_THROWS", Catch::ResultDisposition::Normal, __VA_ARGS__ ) +#define REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "REQUIRE_THROWS_AS", exceptionType, Catch::ResultDisposition::Normal, expr ) +#define REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( "REQUIRE_THROWS_WITH", Catch::ResultDisposition::Normal, matcher, expr ) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( "REQUIRE_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::Normal, matcher, expr ) +#endif // CATCH_CONFIG_DISABLE_MATCHERS +#define REQUIRE_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "REQUIRE_NOTHROW", Catch::ResultDisposition::Normal, __VA_ARGS__ ) + +#define CHECK( ... ) INTERNAL_CATCH_TEST( "CHECK", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define CHECK_FALSE( ... ) INTERNAL_CATCH_TEST( "CHECK_FALSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, __VA_ARGS__ ) +#define CHECKED_IF( ... ) INTERNAL_CATCH_IF( "CHECKED_IF", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define CHECKED_ELSE( ... ) INTERNAL_CATCH_ELSE( "CHECKED_ELSE", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define CHECK_NOFAIL( ... ) INTERNAL_CATCH_TEST( "CHECK_NOFAIL", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ ) + +#define CHECK_THROWS( ... ) INTERNAL_CATCH_THROWS( "CHECK_THROWS", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CHECK_THROWS_AS", exceptionType, Catch::ResultDisposition::ContinueOnFailure, expr ) +#define CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( "CHECK_THROWS_WITH", Catch::ResultDisposition::ContinueOnFailure, matcher, expr ) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CHECK_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( "CHECK_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::ContinueOnFailure, matcher, expr ) +#endif // CATCH_CONFIG_DISABLE_MATCHERS +#define CHECK_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "CHECK_NOTHROW", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CHECK_THAT", matcher, Catch::ResultDisposition::ContinueOnFailure, arg ) + +#define REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg ) +#endif // CATCH_CONFIG_DISABLE_MATCHERS + +#define INFO( msg ) INTERNAL_CATCH_INFO( "INFO", msg ) +#define WARN( msg ) INTERNAL_CATCH_MSG( "WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg ) +#define CAPTURE( ... ) INTERNAL_CATCH_CAPTURE( INTERNAL_CATCH_UNIQUE_NAME(capturer), "CAPTURE",__VA_ARGS__ ) + +#define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) +#define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) +#define METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) +#define REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ ) +#define SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) +#define DYNAMIC_SECTION( ... ) INTERNAL_CATCH_DYNAMIC_SECTION( __VA_ARGS__ ) +#define FAIL( ... ) INTERNAL_CATCH_MSG( "FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ ) +#define FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( "FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define SUCCEED( ... ) INTERNAL_CATCH_MSG( "SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE() + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ ) +#define TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ ) +#else +#define TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ ) ) +#define TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ ) ) +#endif + +#if !defined(CATCH_CONFIG_RUNTIME_STATIC_REQUIRE) +#define STATIC_REQUIRE( ... ) static_assert( __VA_ARGS__, #__VA_ARGS__ ); SUCCEED( #__VA_ARGS__ ) +#define STATIC_REQUIRE_FALSE( ... ) static_assert( !(__VA_ARGS__), "!(" #__VA_ARGS__ ")" ); SUCCEED( "!(" #__VA_ARGS__ ")" ) +#else +#define STATIC_REQUIRE( ... ) REQUIRE( __VA_ARGS__ ) +#define STATIC_REQUIRE_FALSE( ... ) REQUIRE_FALSE( __VA_ARGS__ ) +#endif + +#endif + +#define CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) + +// "BDD-style" convenience wrappers +#define SCENARIO( ... ) TEST_CASE( "Scenario: " __VA_ARGS__ ) +#define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ ) + +#define GIVEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " Given: " << desc ) +#define AND_GIVEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( "And given: " << desc ) +#define WHEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " When: " << desc ) +#define AND_WHEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " And when: " << desc ) +#define THEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " Then: " << desc ) +#define AND_THEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " And: " << desc ) + +using Catch::Detail::Approx; + +#else // CATCH_CONFIG_DISABLE + +////// +// If this config identifier is defined then all CATCH macros are prefixed with CATCH_ +#ifdef CATCH_CONFIG_PREFIX_ALL + +#define CATCH_REQUIRE( ... ) (void)(0) +#define CATCH_REQUIRE_FALSE( ... ) (void)(0) + +#define CATCH_REQUIRE_THROWS( ... ) (void)(0) +#define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) (void)(0) +#define CATCH_REQUIRE_THROWS_WITH( expr, matcher ) (void)(0) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CATCH_REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) (void)(0) +#endif// CATCH_CONFIG_DISABLE_MATCHERS +#define CATCH_REQUIRE_NOTHROW( ... ) (void)(0) + +#define CATCH_CHECK( ... ) (void)(0) +#define CATCH_CHECK_FALSE( ... ) (void)(0) +#define CATCH_CHECKED_IF( ... ) if (__VA_ARGS__) +#define CATCH_CHECKED_ELSE( ... ) if (!(__VA_ARGS__)) +#define CATCH_CHECK_NOFAIL( ... ) (void)(0) + +#define CATCH_CHECK_THROWS( ... ) (void)(0) +#define CATCH_CHECK_THROWS_AS( expr, exceptionType ) (void)(0) +#define CATCH_CHECK_THROWS_WITH( expr, matcher ) (void)(0) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CATCH_CHECK_THROWS_MATCHES( expr, exceptionType, matcher ) (void)(0) +#endif // CATCH_CONFIG_DISABLE_MATCHERS +#define CATCH_CHECK_NOTHROW( ... ) (void)(0) + +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CATCH_CHECK_THAT( arg, matcher ) (void)(0) + +#define CATCH_REQUIRE_THAT( arg, matcher ) (void)(0) +#endif // CATCH_CONFIG_DISABLE_MATCHERS + +#define CATCH_INFO( msg ) (void)(0) +#define CATCH_WARN( msg ) (void)(0) +#define CATCH_CAPTURE( msg ) (void)(0) + +#define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) +#define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) +#define CATCH_METHOD_AS_TEST_CASE( method, ... ) +#define CATCH_REGISTER_TEST_CASE( Function, ... ) (void)(0) +#define CATCH_SECTION( ... ) +#define CATCH_DYNAMIC_SECTION( ... ) +#define CATCH_FAIL( ... ) (void)(0) +#define CATCH_FAIL_CHECK( ... ) (void)(0) +#define CATCH_SUCCEED( ... ) (void)(0) + +#define CATCH_ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define CATCH_TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) ) +#define CATCH_TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), className ) +#else +#define CATCH_TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) ) ) +#define CATCH_TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), className ) ) +#endif + +// "BDD-style" convenience wrappers +#define CATCH_SCENARIO( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) +#define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), className ) +#define CATCH_GIVEN( desc ) +#define CATCH_AND_GIVEN( desc ) +#define CATCH_WHEN( desc ) +#define CATCH_AND_WHEN( desc ) +#define CATCH_THEN( desc ) +#define CATCH_AND_THEN( desc ) + +#define CATCH_STATIC_REQUIRE( ... ) (void)(0) +#define CATCH_STATIC_REQUIRE_FALSE( ... ) (void)(0) + +// If CATCH_CONFIG_PREFIX_ALL is not defined then the CATCH_ prefix is not required +#else + +#define REQUIRE( ... ) (void)(0) +#define REQUIRE_FALSE( ... ) (void)(0) + +#define REQUIRE_THROWS( ... ) (void)(0) +#define REQUIRE_THROWS_AS( expr, exceptionType ) (void)(0) +#define REQUIRE_THROWS_WITH( expr, matcher ) (void)(0) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) (void)(0) +#endif // CATCH_CONFIG_DISABLE_MATCHERS +#define REQUIRE_NOTHROW( ... ) (void)(0) + +#define CHECK( ... ) (void)(0) +#define CHECK_FALSE( ... ) (void)(0) +#define CHECKED_IF( ... ) if (__VA_ARGS__) +#define CHECKED_ELSE( ... ) if (!(__VA_ARGS__)) +#define CHECK_NOFAIL( ... ) (void)(0) + +#define CHECK_THROWS( ... ) (void)(0) +#define CHECK_THROWS_AS( expr, exceptionType ) (void)(0) +#define CHECK_THROWS_WITH( expr, matcher ) (void)(0) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CHECK_THROWS_MATCHES( expr, exceptionType, matcher ) (void)(0) +#endif // CATCH_CONFIG_DISABLE_MATCHERS +#define CHECK_NOTHROW( ... ) (void)(0) + +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CHECK_THAT( arg, matcher ) (void)(0) + +#define REQUIRE_THAT( arg, matcher ) (void)(0) +#endif // CATCH_CONFIG_DISABLE_MATCHERS + +#define INFO( msg ) (void)(0) +#define WARN( msg ) (void)(0) +#define CAPTURE( msg ) (void)(0) + +#define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) +#define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) +#define METHOD_AS_TEST_CASE( method, ... ) +#define REGISTER_TEST_CASE( Function, ... ) (void)(0) +#define SECTION( ... ) +#define DYNAMIC_SECTION( ... ) +#define FAIL( ... ) (void)(0) +#define FAIL_CHECK( ... ) (void)(0) +#define SUCCEED( ... ) (void)(0) +#define ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) ) +#define TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), className ) +#else +#define TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) ) ) +#define TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), className ) ) +#endif + +#define STATIC_REQUIRE( ... ) (void)(0) +#define STATIC_REQUIRE_FALSE( ... ) (void)(0) + +#endif + +#define CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION_NO_REG( INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ), signature ) + +// "BDD-style" convenience wrappers +#define SCENARIO( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ) ) +#define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), className ) + +#define GIVEN( desc ) +#define AND_GIVEN( desc ) +#define WHEN( desc ) +#define AND_WHEN( desc ) +#define THEN( desc ) +#define AND_THEN( desc ) + +using Catch::Detail::Approx; + +#endif + +#endif // ! CATCH_CONFIG_IMPL_ONLY + +// start catch_reenable_warnings.h + + +#ifdef __clang__ +# ifdef __ICC // icpc defines the __clang__ macro +# pragma warning(pop) +# else +# pragma clang diagnostic pop +# endif +#elif defined __GNUC__ +# pragma GCC diagnostic pop +#endif + +// end catch_reenable_warnings.h +// end catch.hpp +#endif // TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED + diff --git a/components/streams/test/filestream_unit.cpp b/components/streams/test/filestream_unit.cpp new file mode 100644 index 0000000000000000000000000000000000000000..54895357e8a29cb8b4a49672b1d6e7872127f9ee --- /dev/null +++ b/components/streams/test/filestream_unit.cpp @@ -0,0 +1,124 @@ +#include "catch.hpp" + +#include <ftl/streams/filestream.hpp> + +using ftl::stream::File; +using ftl::stream::Stream; +using ftl::config::json_t; + +TEST_CASE("ftl::stream::File write and read", "[stream]") { + json_t global = json_t{{"$id","ftl://test"}}; + ftl::config::configure(global); + + json_t cfg = json_t{ + {"$id","ftl://test/1"} + }; + json_t cfg2 = json_t{ + {"$id","ftl://test/2"} + }; + + auto *reader = ftl::create<File>(cfg); + REQUIRE(reader); + auto *writer = ftl::create<File>(cfg2); + REQUIRE(writer); + + SECTION("write read single packet") { + writer->set("filename", "/tmp/ftl_file_stream_test.ftl"); + writer->setMode(File::Mode::Write); + + REQUIRE( writer->begin() ); + + REQUIRE( writer->post({4,ftl::timer::get_time(),2,1,ftl::codecs::Channel::Confidence},{ftl::codecs::codec_t::Any, ftl::codecs::definition_t::Any, 0, 0, 0, {'f'}}) ); + + writer->end(); + + reader->set("filename", "/tmp/ftl_file_stream_test.ftl"); + + ftl::codecs::StreamPacket tspkt = {4,0,0,1,ftl::codecs::Channel::Colour}; + REQUIRE( reader->onPacket([&tspkt](const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) { + tspkt = spkt; + }) ); + REQUIRE( reader->begin(false) ); + + //reader->tick(); + + //REQUIRE( tspkt.timestamp == 0 ); + REQUIRE( tspkt.streamID == (uint8_t)2 ); + REQUIRE( tspkt.channel == ftl::codecs::Channel::Confidence ); + } + + SECTION("write read multiple packets at same timestamp") { + writer->set("filename", "/tmp/ftl_file_stream_test.ftl"); + writer->setMode(File::Mode::Write); + + REQUIRE( writer->begin() ); + + REQUIRE( writer->post({4,0,0,1,ftl::codecs::Channel::Confidence},{ftl::codecs::codec_t::Any, ftl::codecs::definition_t::Any, 0, 0, 0, {'f'}}) ); + REQUIRE( writer->post({4,0,1,1,ftl::codecs::Channel::Depth},{ftl::codecs::codec_t::Any, ftl::codecs::definition_t::Any, 0, 0, 0, {'f'}}) ); + REQUIRE( writer->post({4,0,2,1,ftl::codecs::Channel::Screen},{ftl::codecs::codec_t::Any, ftl::codecs::definition_t::Any, 0, 0, 0, {'f'}}) ); + + writer->end(); + + reader->set("filename", "/tmp/ftl_file_stream_test.ftl"); + + ftl::codecs::StreamPacket tspkt = {4,0,0,1,ftl::codecs::Channel::Colour}; + int count = 0; + REQUIRE( reader->onPacket([&tspkt,&count](const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) { + tspkt = spkt; + ++count; + }) ); + REQUIRE( reader->begin(false) ); + + //reader->tick(); + + REQUIRE( count == 3 ); + REQUIRE( tspkt.timestamp == 0 ); + REQUIRE( tspkt.streamID == 2 ); + REQUIRE( tspkt.channel == ftl::codecs::Channel::Screen ); + } + + SECTION("write read multiple packets at different timestamps") { + writer->set("filename", "/tmp/ftl_file_stream_test.ftl"); + writer->setMode(File::Mode::Write); + + REQUIRE( writer->begin() ); + + auto time = ftl::timer::get_time(); + REQUIRE( writer->post({4,time,0,1,ftl::codecs::Channel::Confidence},{ftl::codecs::codec_t::Any, ftl::codecs::definition_t::Any, 0, 0, 0, {'f'}}) ); + REQUIRE( writer->post({4,time+ftl::timer::getInterval(),0,1,ftl::codecs::Channel::Depth},{ftl::codecs::codec_t::Any, ftl::codecs::definition_t::Any, 0, 0, 0, {'f'}}) ); + REQUIRE( writer->post({4,time+2*ftl::timer::getInterval(),0,1,ftl::codecs::Channel::Screen},{ftl::codecs::codec_t::Any, ftl::codecs::definition_t::Any, 0, 0, 0, {'f'}}) ); + + writer->end(); + + reader->set("filename", "/tmp/ftl_file_stream_test.ftl"); + + ftl::codecs::StreamPacket tspkt = {4,0,0,1,ftl::codecs::Channel::Colour}; + int count = 0; + REQUIRE( reader->onPacket([&tspkt,&count](const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) { + tspkt = spkt; + ++count; + }) ); + REQUIRE( reader->begin(false) ); + + //reader->tick(); + + REQUIRE( count == 1 ); + //REQUIRE( tspkt.timestamp == 0 ); + auto itime = tspkt.timestamp; + + count = 0; + reader->tick(); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + + REQUIRE( count == 1 ); + REQUIRE( tspkt.timestamp == itime+ftl::timer::getInterval() ); + + count = 0; + reader->tick(); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + + REQUIRE( count == 1 ); + REQUIRE( tspkt.timestamp == itime+2*ftl::timer::getInterval() ); + + } +} diff --git a/components/streams/test/netstream_unit.cpp b/components/streams/test/netstream_unit.cpp new file mode 100644 index 0000000000000000000000000000000000000000..33fbeb4c95029cf7c6900dcfb724038fdecff889 --- /dev/null +++ b/components/streams/test/netstream_unit.cpp @@ -0,0 +1,47 @@ +#include "catch.hpp" + +#define _FTL_NET_UNIVERSE_HPP_ + +#include <ftl/uuid.hpp> + +// Mock the net universe class + +namespace ftl { +namespace net { + +class Peer { + +}; + +class Universe { + public: +}; + +} +} + +#include <ftl/streams/netstream.hpp> +#include "../src/netstream.cpp" + +using ftl::stream::Net; +using ftl::stream::Stream; +using ftl::config::json_t; + +TEST_CASE("ftl::stream::Net post", "[stream]") { + json_t global = json_t{{"$id","ftl://test"}}; + ftl::config::configure(global); + + json_t cfg = json_t{ + {"$id","ftl://test/1"} + }; + + ftl::net::Universe mocknet; + + Net *s = ftl::create<Net>(cfg, &mocknet); + s->set("uri", "ftl://dummy"); + + REQUIRE(s->begin()); + + delete s; +} + diff --git a/components/streams/test/stream_unit.cpp b/components/streams/test/stream_unit.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a34fb73ce0098564cfeab0d45efe1e05437f935a --- /dev/null +++ b/components/streams/test/stream_unit.cpp @@ -0,0 +1,239 @@ +#include "catch.hpp" + +#include <ftl/streams/stream.hpp> + +using ftl::stream::Muxer; +using ftl::stream::Broadcast; +using ftl::stream::Stream; +using ftl::config::json_t; + +class TestStream : public ftl::stream::Stream { + public: + TestStream(nlohmann::json &config) : ftl::stream::Stream(config) {}; + ~TestStream() {}; + + bool onPacket(const std::function<void(const ftl::codecs::StreamPacket &, const ftl::codecs::Packet &)> &cb) { + cb_ = cb; + return true; + } + + bool post(const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) { + available(spkt.streamID) += spkt.channel; + if (cb_) cb_(spkt, pkt); + return true; + } + + bool begin() override { return true; } + bool end() override { return true; } + bool active() override { return true; } + + private: + std::function<void(const ftl::codecs::StreamPacket &, const ftl::codecs::Packet &)> cb_; +}; + +TEST_CASE("ftl::stream::Muxer()::write", "[stream]") { + json_t global = json_t{{"$id","ftl://test"}}; + ftl::config::configure(global); + + json_t cfg = json_t{ + {"$id","ftl://test/1"} + }; + + Muxer *mux = ftl::create<Muxer>(cfg); + REQUIRE(mux); + + SECTION("write with one stream") { + json_t cfg = json_t{ + {"$id","ftl://test/2"} + }; + + Stream *s = ftl::create<TestStream>(cfg); + REQUIRE(s); + + mux->add(s); + + ftl::codecs::StreamPacket tspkt = {4,0,0,1,ftl::codecs::Channel::Colour};; + + s->onPacket([&tspkt](const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) { + tspkt = spkt; + }); + + REQUIRE( !mux->post({4,100,0,1,ftl::codecs::Channel::Colour},{}) ); + REQUIRE( tspkt.timestamp == 0 ); + } + + SECTION("write to previously read") { + json_t cfg1 = json_t{ + {"$id","ftl://test/2"} + }; + json_t cfg2 = json_t{ + {"$id","ftl://test/3"} + }; + + Stream *s1 = ftl::create<TestStream>(cfg1); + REQUIRE(s1); + Stream *s2 = ftl::create<TestStream>(cfg2); + REQUIRE(s2); + + mux->add(s1); + mux->add(s2); + + ftl::codecs::StreamPacket tspkt = {4,0,0,1,ftl::codecs::Channel::Colour}; + mux->onPacket([&tspkt](const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) { + tspkt = spkt; + }); + + REQUIRE( s1->post({4,100,0,0,ftl::codecs::Channel::Colour},{}) ); + REQUIRE( tspkt.timestamp == 100 ); + REQUIRE( tspkt.frame_number == 0 ); + + REQUIRE( s2->post({4,101,0,0,ftl::codecs::Channel::Colour},{}) ); + REQUIRE( tspkt.timestamp == 101 ); + REQUIRE( tspkt.frame_number == 1 ); + + ftl::codecs::StreamPacket tspkt2 = {4,0,0,1,ftl::codecs::Channel::Colour}; + ftl::codecs::StreamPacket tspkt3 = {4,0,0,1,ftl::codecs::Channel::Colour}; + s1->onPacket([&tspkt2](const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) { + tspkt2 = spkt; + }); + s2->onPacket([&tspkt3](const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) { + tspkt3 = spkt; + }); + + REQUIRE( mux->post({4,200,1,1,ftl::codecs::Channel::Colour},{}) ); + REQUIRE( tspkt3.timestamp == 200 ); + REQUIRE( tspkt3.frame_number == 0 ); + } +} + +TEST_CASE("ftl::stream::Muxer()::read", "[stream]") { + json_t global = json_t{{"$id","ftl://test"}}; + ftl::config::configure(global); + + json_t cfg = json_t{ + {"$id","ftl://test/1"} + }; + + Muxer *mux = ftl::create<Muxer>(cfg); + REQUIRE(mux); + + SECTION("read with two writing streams") { + json_t cfg1 = json_t{ + {"$id","ftl://test/2"} + }; + json_t cfg2 = json_t{ + {"$id","ftl://test/3"} + }; + + Stream *s1 = ftl::create<TestStream>(cfg1); + REQUIRE(s1); + Stream *s2 = ftl::create<TestStream>(cfg2); + REQUIRE(s2); + + mux->add(s1); + mux->add(s2); + + ftl::codecs::StreamPacket tspkt = {4,0,0,1,ftl::codecs::Channel::Colour}; + mux->onPacket([&tspkt](const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) { + tspkt = spkt; + }); + + REQUIRE( s1->post({4,100,0,0,ftl::codecs::Channel::Colour},{}) ); + REQUIRE( tspkt.timestamp == 100 ); + REQUIRE( tspkt.frame_number == 0 ); + + REQUIRE( s2->post({4,101,0,0,ftl::codecs::Channel::Colour},{}) ); + REQUIRE( tspkt.timestamp == 101 ); + REQUIRE( tspkt.frame_number == 1 ); + + REQUIRE( s1->post({4,102,0,1,ftl::codecs::Channel::Colour},{}) ); + REQUIRE( tspkt.timestamp == 102 ); + REQUIRE( tspkt.frame_number == 2 ); + + REQUIRE( s2->post({4,103,0,1,ftl::codecs::Channel::Colour},{}) ); + REQUIRE( tspkt.timestamp == 103 ); + REQUIRE( tspkt.frame_number == 3 ); + } + + SECTION("read consistency with two writing streams") { + json_t cfg1 = json_t{ + {"$id","ftl://test/2"} + }; + json_t cfg2 = json_t{ + {"$id","ftl://test/3"} + }; + + Stream *s1 = ftl::create<TestStream>(cfg1); + REQUIRE(s1); + Stream *s2 = ftl::create<TestStream>(cfg2); + REQUIRE(s2); + + mux->add(s1); + mux->add(s2); + + ftl::codecs::StreamPacket tspkt = {4,0,0,1,ftl::codecs::Channel::Colour}; + mux->onPacket([&tspkt](const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) { + tspkt = spkt; + }); + + REQUIRE( s1->post({4,100,0,0,ftl::codecs::Channel::Colour},{}) ); + REQUIRE( tspkt.timestamp == 100 ); + REQUIRE( tspkt.frame_number == 0 ); + + REQUIRE( s2->post({4,101,0,0,ftl::codecs::Channel::Colour},{}) ); + REQUIRE( tspkt.timestamp == 101 ); + REQUIRE( tspkt.frame_number == 1 ); + + REQUIRE( s1->post({4,102,0,0,ftl::codecs::Channel::Colour},{}) ); + REQUIRE( tspkt.timestamp == 102 ); + REQUIRE( tspkt.frame_number == 0 ); + + REQUIRE( s2->post({4,103,0,0,ftl::codecs::Channel::Colour},{}) ); + REQUIRE( tspkt.timestamp == 103 ); + REQUIRE( tspkt.frame_number == 1 ); + } +} + +TEST_CASE("ftl::stream::Broadcast()::write", "[stream]") { + json_t global = json_t{{"$id","ftl://test"}}; + ftl::config::configure(global); + + json_t cfg = json_t{ + {"$id","ftl://test/1"} + }; + + Broadcast *mux = ftl::create<Broadcast>(cfg); + REQUIRE(mux); + + SECTION("write with two streams") { + json_t cfg1 = json_t{ + {"$id","ftl://test/2"} + }; + json_t cfg2 = json_t{ + {"$id","ftl://test/3"} + }; + + Stream *s1 = ftl::create<TestStream>(cfg1); + REQUIRE(s1); + Stream *s2 = ftl::create<TestStream>(cfg2); + REQUIRE(s2); + + mux->add(s1); + mux->add(s2); + + ftl::codecs::StreamPacket tspkt1 = {4,0,0,1,ftl::codecs::Channel::Colour}; + ftl::codecs::StreamPacket tspkt2 = {4,0,0,1,ftl::codecs::Channel::Colour}; + + s1->onPacket([&tspkt1](const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) { + tspkt1 = spkt; + }); + s2->onPacket([&tspkt2](const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) { + tspkt2 = spkt; + }); + + REQUIRE( mux->post({4,100,0,1,ftl::codecs::Channel::Colour},{}) ); + REQUIRE( tspkt1.timestamp == 100 ); + REQUIRE( tspkt2.timestamp == 100 ); + } + +} diff --git a/components/streams/test/tests.cpp b/components/streams/test/tests.cpp new file mode 100644 index 0000000000000000000000000000000000000000..178916eab8b9c7aabb87ff99894b48443ad6ecb6 --- /dev/null +++ b/components/streams/test/tests.cpp @@ -0,0 +1,3 @@ +#define CATCH_CONFIG_MAIN +#include "catch.hpp" + diff --git a/components/streams/test/transcoder_unit.cpp b/components/streams/test/transcoder_unit.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391