Hello All!
So I have recently started learning how to work with ROOT in C++ for a project of mine. I have been using ROOT to plot data in real time from a simulation that is running in the background. One of the key parts of the simulation is that each loop must run within 16ms. Now when plotting with ROOT the longest part of updating the points is when calling for a canvas the Update()
function. Now with one canvas this isn’t really an issue because it’s below the 16ms at around 10ms but when you start to compound a lot of the different canvas’s together each at about 10ms it means that updating all the figures ends up being in my case well above 200ms.
So I spent some time looking around and I couldn’t find anything on google or on these forums that was related to my issue so I figured I would make a post asking about it. Is there anyway to batch update the plots while in standalone mode? Or make Update()
much faster?
I have tried several things and the closest idea was to create a thread for every canvas->Update();
but what I ran into is that while yes each canvas gets updated apparently, behind the scenes, there is a global pointer that can cause issues when trying to update every canvas at the same time as I was getting a Warning in <TROOT::Append>: Replacing existing TH1: Graph (Potential memory leak).
error and each canvas were alternating between plotting.
Any ideas would be appreciated!
I have attached the important parts of my code below.
// Common Includes
#include "../includes/main.hpp"
#include "../includes/common.hpp"
// CTRL-C signal handler
static void sig_handler(int sig_number)
{
printf("\n\nCTRL-C pressed!\nShutting down the Simulator!\n\n");
}
// Main Function
int main(int argc, char **argv)
{
// Send the message that the simulator is starting
std::cout << "Starting the Simulator...\n\nPress CTRL-C to quit...\n\n" << std::endl;
// Initialize the ROOT plotting application
// TApplication rootapp("SIM_CORE", &argc, argv);
int itmp = (argc > 1 ? 1 : argc);
TApplication *app = new TApplication("SIM_CORE", &itmp, argv);
// This activates implicit multi-threading for ROOT
ROOT::EnableImplicitMT();
ROOT::EnableThreadSafety();
// Create the plot object
SIM_PLOTTING *test_plots = new SIM_PLOTTING(150, false);
// Create the first plot window
int figure_one = test_plots->figure("Aircraft Linear Position", "Aircraft Linear Position", 1024, 768, 3);
// Create the first plot, 0 on first figure, 0
test_plots->create_graph(figure_one, 0);
test_plots->set_labels(figure_one, 0, "x [ft]", "time [s]", "Linear Position, x [ft]");
test_plots->set_graph_line(figure_one, 0, 632, 1, 1);
test_plots->set_graph_marker(figure_one, 0, 632, 3, 1);
// Create the second plot, 1 on first figure, 0
test_plots->create_graph(figure_one, 1);
test_plots->set_labels(figure_one, 1, "y [ft]", "time [s]", "Linear Position, y [ft]");
test_plots->set_graph_line(figure_one, 1, 800, 1, 1);
test_plots->set_graph_marker(figure_one, 1, 800, 3, 1);
// Create the third plot, 2 on first figure, 0
test_plots->create_graph(figure_one, 2);
test_plots->set_labels(figure_one, 2, "z [ft]", "time [s]", "Linear Position, z [ft]");
test_plots->set_graph_line(figure_one, 2, 860, 1, 1);
test_plots->set_graph_marker(figure_one, 2, 860, 3, 1);
// Display the figure
test_plots->update_figure(figure_one);
// Create the second graph, 1
test_plots->figure("Aircraft Angular Position", "Aircraft Angular Position", 700, 500, 1);
test_plots->create_graph(1, 0);
test_plots->set_labels(1, 0, "p [deg/s]", "time [s]", "Angular Rate, p [deg/s]");
test_plots->update_figure(1);
int i = 0;
while(true)
{
test_plots->update_plot(figure_one, 0, i, rand() % 100);
test_plots->update_plot(figure_one, 1, i, rand() % 100);
test_plots->update_plot(figure_one, 2, i, rand() % 100);
test_plots->update_plot(1, 0, i, rand() % 100);
// test_plots->update_figure(figure_one);
// test_plots->update_figure(1);
test_plots->update_figures();
// Update i
i++;
}
// Wait for Ctrl-C, the signal handler must be defined in order to properly shut down the sim...
signal(SIGINT, sig_handler);
pause();
}
/**
* @file sim_plotting.cpp
* @brief The main source file for the function definition of the plotting functions / classes
*/
#include "../../includes/core/sim_plotting.hpp"
/*****************************************************************************/
/* CONSTRUCTORS */
/*****************************************************************************/
SIM_PLOTTING::SIM_PLOTTING()
{
/* DO NOTHING - DEFAULT CONSTRUCTOR */
}
SIM_PLOTTING::SIM_PLOTTING(ssize_t num_points, bool save_flag)
{
// Set the variables
/*
this->num_canvas = number_canvas;
this->x_size = x_size;
this->y_size = y_size;
*/
this->number_points = num_points;
this->save_flag = save_flag;
/*
// Create the canvas pointers if specified, can also not create the pointers and then create the canvas later
if (this->num_canvas > 0)
{
this->create_canvas_pointers();
}*/
}
/*****************************************************************************/
/* DESTRUCTORS */
/*****************************************************************************/
SIM_PLOTTING::~SIM_PLOTTING()
{
/* DO NOTHING - DEFAULT DESTRUCTOR */
}
/*****************************************************************************/
/* PUBLIC FUNCTIONS */
/*****************************************************************************/
int SIM_PLOTTING::figure(char* plot_name, char* title, double x_size, double y_size, int num_plots)
{
// Get the length of the canvas vector and convert to string
std::string canvas_name = "C" + std::to_string((this->canvas_pointers).size() + 1);
// Create the canvas
TCanvas* single_canvas_pointer = new TCanvas(canvas_name.c_str(), plot_name, x_size, y_size);
// Enable the grid on the canvas
single_canvas_pointer->SetGrid();
// Divide the canvas vertically if num of plots is greater than 1
if (num_plots > 1)
{
single_canvas_pointer->Divide(1, num_plots);
}
// Push back the pointer to the canvas
(this->canvas_pointers).push_back(single_canvas_pointer);
// Increment the number of canvas
this->num_canvas++;
// Create a vector to push back for graph pointers in this canvas
std::vector<TGraphErrors*> canvas_graph_vector;
// Push back a vector for the canvas graphs
(this->graph_pointers).push_back(canvas_graph_vector);
// Return the index of the canvas
return this->num_canvas - 1;
}
void SIM_PLOTTING::create_graph(int canvas_index, int subgraph_index)
{
// Create a vector of zeros to init plot with zeros
std::vector<double> x_init_points;
x_init_points.resize(this->number_points);
std::fill(x_init_points.begin(), x_init_points.end(), 0.0);
std::vector<double> y_init_points;
y_init_points.resize(this->number_points);
std::fill(y_init_points.begin(), y_init_points.end(), 0.0);
// Create the graph
TGraphErrors *single_graph_pointer = new TGraphErrors(this->number_points, &x_init_points[0], &y_init_points[0]);
// Push back the pointer to the graph
(this->graph_pointers[canvas_index]).push_back(single_graph_pointer);
// Register Graph
(this->canvas_pointers[canvas_index])->cd(subgraph_index + 1); // Set current canvas
(this->canvas_pointers[canvas_index])->Pad()->SetLeftMargin(0.1);
(this->canvas_pointers[canvas_index])->Pad()->SetRightMargin(0.05);
(this->graph_pointers[canvas_index].back())->Draw("APL");
}
void SIM_PLOTTING::set_multigraph(int canvas_index, int multi_graph_index, ssize_t graph_index_start, ssize_t graph_index_end)
{
if (multi_graph_index == 0)
{
// Create the multigraph
TMultiGraph* single_multigraph_pointer = new TMultiGraph();
// Create the multigraph vector
std::vector<TMultiGraph*> single_multigraph_vector;
// Push back the created multigraph pointer
single_multigraph_vector.push_back(single_multigraph_pointer);
// Push back the pointer vector for a new multigraph
(this->multigraph_pointers).push_back(single_multigraph_vector);
}
else
{
// Create the multigraph
TMultiGraph* single_multigraph_pointer = new TMultiGraph();
// Push back the pointer vector for another multigraph in the same index
(this->multigraph_pointers[canvas_index]).push_back(single_multigraph_pointer);
}
// Set the graphs in this multi-graph
for (ssize_t i = graph_index_start; i < graph_index_end; i++)
{
// Add each graph specified to this multi-graph
(this->multigraph_pointers[canvas_index][multi_graph_index])->Add(this->graph_pointers[canvas_index][i]);
}
}
int SIM_PLOTTING::get_num_graphs_on_canvas(int canvas_index)
{
return (this->graph_pointers[canvas_index]).size();
}
void SIM_PLOTTING::set_labels(int canvas_index, int graph_index, char* title, char* x_label, char* y_label)
{
(this->graph_pointers[canvas_index][graph_index])->SetTitle(title);
(this->graph_pointers[canvas_index][graph_index])->GetXaxis()->SetTitle(x_label);
(this->graph_pointers[canvas_index][graph_index])->GetYaxis()->SetTitle(y_label);
}
void SIM_PLOTTING::set_graph_line(int canvas_index, int graph_index, int color, int style, int width)
{
(this->graph_pointers[canvas_index][graph_index])->SetLineColor(color);
(this->graph_pointers[canvas_index][graph_index])->SetLineStyle(style);
(this->graph_pointers[canvas_index][graph_index])->SetLineWidth(width);
}
void SIM_PLOTTING::set_graph_marker(int canvas_index, int graph_index, int color, int style, int size)
{
(this->graph_pointers[canvas_index][graph_index])->SetMarkerColor(color);
(this->graph_pointers[canvas_index][graph_index])->SetMarkerStyle(style);
(this->graph_pointers[canvas_index][graph_index])->SetMarkerSize(size);
}
void SIM_PLOTTING::update_plot(int canvas_index, int graph_index, double x, double y)
{
// [DEPRECATED] Move every point down one
// KEPT THIS DEPRECRATED IN FOR FUTURE REFERENCE
/*for (ssize_t i = 0; i < this->number_points; i++)
{
// Get the index + 1 data point
double x_1 = 0.0;
double y_1 = 0.0;
(this->graph_pointers[canvas_index][graph_index])->GetPoint(i + 1, x_1, y_1);
// Set the current index point data based on the index + 1
(this->graph_pointers[canvas_index][graph_index])->SetPoint(i, x_1, y_1);
}*/
// Remove the first index, to then append the last index to keep at number_points size
// THIS METHOD REPLACED THE ABOVE [DEPRECATED] method
(this->graph_pointers[canvas_index][graph_index])->RemovePoint(0);
// Append the last index according to the new data point
(this->graph_pointers[canvas_index][graph_index])->SetPoint(this->number_points - 1, x, y);
}
void SIM_PLOTTING::update_figures()
{
std::vector<std::thread> threads;
for (int i = 0; i < this->canvas_pointers.size(); i++)
{
threads.push_back(std::thread(&SIM_PLOTTING::update_figure_thread, this, this->canvas_pointers[i]));
}
for (auto &th : threads)
{
th.join();
}
// Process the system events
gSystem->ProcessEvents();
}
void SIM_PLOTTING::update_figure_thread(TCanvas* canvas_pointer)
{
canvas_pointer->cd();
canvas_pointer->Modified();
canvas_pointer->Update();
// canvas_pointer->Pad()->Draw("APL");
}
void SIM_PLOTTING::update_figure(int canvas_index)
{
// Update the Figure
(this->canvas_pointers[canvas_index])->cd();
(this->canvas_pointers[canvas_index])->Update();
(this->canvas_pointers[canvas_index])->Pad()->Draw("APL");
// Process the system events
gSystem->ProcessEvents();
}
/* sim_plotting.hpp */
/* This file is used to declare all the plotting functions / classes for the simulation */
#ifndef SIM_PLOTTING_HPP
#define SIM_PLOTTING_HPP
// Includes
#include "../common.hpp"
class SIM_PLOTTING
{
public:
/************************/
/* CONSTRUCTORS */
/************************/
// Default Constructor
SIM_PLOTTING();
// Realtime (Dynamic) Plotting Constructor
// SIM_PLOTTING(ssize_t number_canvas, ssize_t number_points, double x_size, double y_size, bool save_flag);
SIM_PLOTTING(ssize_t num_points, bool save_flag);
// Static Plotting Constructor
SIM_PLOTTING(char* plot_name, bool save_flag);
/************************/
/* DESTRUCTORS */
/************************/
~SIM_PLOTTING(); // Default Destructor
/************************/
/* PUBLIC FUNCTIONS */
/************************/
int figure(char* plot_name, char* title, double x_size, double y_size, int num_plots);
void create_graph(int canvas_index, int subgraph_index);
void set_multigraph(int canvas_index, int multi_graph_index, ssize_t graph_index_start, ssize_t graph_index_end);
int get_num_graphs_on_canvas(int canvas_index);
// Plot Editing Functions
void set_labels(int canvas_index, int graph_index, char* title, char* x_label, char* y_label);
void set_graph_line(int canvas_index, int graph_index, int color, int style, int width);
void set_graph_marker(int canvas_index, int graph_index, int color, int style, int size);
// Updating functions
void update_plot(int canvas_index, int graph_index, double x, double y);
void update_figures();
void update_figure_thread(TCanvas* canvas_pointer);
void update_figure(int canvas_index);
/************************/
/* PUBLIC VARIABLES */
/************************/
private:
/************************/
/* PRIVATE FUNCTIONS */
/************************/
void create_canvas_pointers();
/************************/
/* PRIVATE VARIABLES */
/************************/
ssize_t num_canvas {0}; // The number of canvases to make (how many different windows essentially)
ssize_t number_points {0}; // The number of points to display at one time in the graph
double x_size {0.0}; // The x size of the canvas in pixels
double y_size {0.0}; // The y size of the canvas in pixels
bool separate_canvas {false}; // Seperate the Canvas
bool save_flag {false}; // Whether or not to save the canvas
std::vector<TCanvas*> canvas_pointers; // A vector to store all the canvas pointers
// Multi-layer vectors, first layer is the layer is canvas(window) index, second layer are the graphs for the first layer
std::vector<std::vector<TMultiGraph*>> multigraph_pointers; // A vector to store all the multigraph pointers
std::vector<std::vector<TGraphErrors*>> graph_pointers; // A vector to store all the graph pointers
protected:
};
#endif
ROOT Version: 6.26/10
Platform: Ubuntu Server 20.04.1 LTS x86_64
Kernel: Linux 6.0.5-rt14 SMP PREEMPT_RT
Compiler: g++ (Ubuntu 11.3.0-1ubuntu1~22.04) 11.3.0