Skip to content

Commit 504dc79

Browse files
committed
Optimize parser func_name allocation; add comments to parser, viewport, graph cache, evaluator, graph widget
Made-with: Cursor
1 parent fe9e057 commit 504dc79

5 files changed

Lines changed: 24 additions & 18 deletions

File tree

src/core/evaluator.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ double Evaluator::evaluate(const ASTNode* node, double x) {
2525
}
2626
}
2727

28+
// Sample the function at num_points over [x_min, x_max]. Pre-sized vector and index assignment
29+
// avoid repeated growth and can be friendlier to the cache in tight loops.
2830
std::vector<Point> Evaluator::generate_points(const ASTNode* node,
2931
double x_min,
3032
double x_max,
@@ -35,15 +37,13 @@ std::vector<Point> Evaluator::generate_points(const ASTNode* node,
3537

3638
std::vector<Point> points;
3739
points.resize(static_cast<std::size_t>(num_points));
38-
3940
double step = (x_max - x_min) / (num_points - 1);
4041

4142
for (int i = 0; i < num_points; ++i) {
4243
double x = x_min + i * step;
4344
double y = evaluate(node, x);
4445
points[static_cast<std::size_t>(i)] = Point{x, y};
4546
}
46-
4747
return points;
4848
}
4949

src/core/parser.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
namespace plot_genius {
1111
namespace core {
1212

13+
// Recursive-descent parser: expression (lowest precedence) -> term -> factor (highest).
14+
// Precedence: + - (expression) < * / (term) < unary, number, func, x, (), ^ (factor).
15+
1316
std::unique_ptr<ASTNode> Parser::parse(const std::string& equation) {
1417
m_input = equation;
1518
m_position = 0;
@@ -26,6 +29,7 @@ std::unique_ptr<ASTNode> Parser::parse(const std::string& equation) {
2629
return node;
2730
}
2831

32+
// Expression = term (('+' | '-') term)* -- additive precedence level.
2933
std::unique_ptr<ASTNode> Parser::parse_expression() {
3034
auto left = parse_term();
3135
if (!left) return nullptr;
@@ -60,6 +64,7 @@ std::unique_ptr<ASTNode> Parser::parse_expression() {
6064
return left;
6165
}
6266

67+
// Term = factor (('*' | '/') factor)* -- multiplicative precedence level.
6368
std::unique_ptr<ASTNode> Parser::parse_term() {
6469
auto left = parse_factor();
6570
if (!left) return nullptr;
@@ -94,6 +99,7 @@ std::unique_ptr<ASTNode> Parser::parse_term() {
9499
return left;
95100
}
96101

102+
// Factor: optional unary minus, then base (number | function | constant | 'x' | '(' expr ')'), then optional '^' factor.
97103
std::unique_ptr<ASTNode> Parser::parse_factor() {
98104
skip_whitespace();
99105

@@ -191,6 +197,7 @@ std::unique_ptr<ASTNode> Parser::parse_function() {
191197
skip_whitespace();
192198

193199
std::string func_name;
200+
func_name.reserve(8); // Avoid realloc for common names: sin, cos, sqrt, etc.
194201
size_t start = m_position;
195202

196203
while (m_position < m_input.length() && std::isalpha(m_input[m_position])) {

src/graph/graph.cpp

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,19 +46,19 @@ void Graph::set_equation_enabled(size_t index, bool enabled) {
4646
}
4747
}
4848

49+
// Generate sampled points for all enabled equations. Uses a cache keyed by viewport range,
50+
// sample count, and equation identity (AST pointer + enabled flag) to skip recomputation on repaint.
4951
std::vector<std::vector<core::Point>> Graph::generate_all_points(int num_points) const {
5052
double x_min, x_max, y_min, y_max;
5153
m_viewport.get_range(x_min, x_max, y_min, y_max);
5254

53-
// Reuse cached series when viewport and equation set have not changed.
5455
if (m_points_cache) {
5556
const auto& cache = *m_points_cache;
5657
if (cache.num_points == num_points &&
5758
cache.x_min == x_min &&
5859
cache.x_max == x_max &&
5960
cache.ast_snapshot.size() == m_equations.size() &&
6061
cache.enabled_snapshot.size() == m_equations.size()) {
61-
6262
bool matches = true;
6363
for (std::size_t i = 0; i < m_equations.size(); ++i) {
6464
const auto& eq = m_equations[i];
@@ -68,10 +68,8 @@ std::vector<std::vector<core::Point>> Graph::generate_all_points(int num_points)
6868
break;
6969
}
7070
}
71-
72-
if (matches) {
71+
if (matches)
7372
return cache.series;
74-
}
7573
}
7674
}
7775

@@ -87,6 +85,7 @@ std::vector<std::vector<core::Point>> Graph::generate_all_points(int num_points)
8785
}
8886
}
8987

88+
// Store snapshot of AST pointers (identity) and enabled flags so we can detect changes.
9089
PointsCache cache;
9190
cache.x_min = x_min;
9291
cache.x_max = x_max;

src/graph/viewport.cpp

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ void Viewport::set_range(double x_min, double x_max, double y_min, double y_max)
2929
}
3030
}
3131

32+
// Pan: shift visible range by a fraction of the current range (dx, dy in [-1,1]).
3233
void Viewport::pan(double dx, double dy) {
3334
double x_range = m_x_max - m_x_min;
3435
double y_range = m_y_max - m_y_min;
@@ -39,6 +40,7 @@ void Viewport::pan(double dx, double dy) {
3940
m_y_max += dy * y_range;
4041
}
4142

43+
// Zoom: scale distance from (center_x, center_y). factor > 1 zooms in, < 1 zooms out.
4244
void Viewport::zoom(double factor, double center_x, double center_y) {
4345
if (factor <= 0.0) return;
4446

@@ -61,6 +63,7 @@ void Viewport::reset() {
6163
m_y_max = DEFAULT_Y_MAX;
6264
}
6365

66+
// Map world (math) coords to pixel coords. Y is flipped so +y is up in world, down on screen.
6467
void Viewport::world_to_screen(double world_x, double world_y,
6568
int screen_width, int screen_height,
6669
int& screen_x, int& screen_y) const {
@@ -74,12 +77,12 @@ void Viewport::world_to_screen(double world_x, double world_y,
7477
}
7578

7679
double normalized_x = (world_x - m_x_min) / x_range;
77-
double normalized_y = 1.0 - (world_y - m_y_min) / y_range; // Flip Y axis
78-
80+
double normalized_y = 1.0 - (world_y - m_y_min) / y_range; // Flip Y: world +y = screen top
7981
screen_x = static_cast<int>(normalized_x * screen_width);
8082
screen_y = static_cast<int>(normalized_y * screen_height);
8183
}
8284

85+
// Map pixel coords back to world coords (inverse of world_to_screen, same Y flip).
8386
void Viewport::screen_to_world(int screen_x, int screen_y,
8487
int screen_width, int screen_height,
8588
double& world_x, double& world_y) const {
@@ -93,8 +96,7 @@ void Viewport::screen_to_world(int screen_x, int screen_y,
9396
}
9497

9598
double normalized_x = static_cast<double>(screen_x) / screen_width;
96-
double normalized_y = 1.0 - static_cast<double>(screen_y) / screen_height; // Flip Y axis
97-
99+
double normalized_y = 1.0 - static_cast<double>(screen_y) / screen_height;
98100
world_x = m_x_min + normalized_x * x_range;
99101
world_y = m_y_min + normalized_y * y_range;
100102
}

src/ui/graph_widget.cpp

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -317,14 +317,15 @@ void GraphWidget::draw_grid(QPainter& painter) {
317317
}
318318
}
319319

320+
// Pick a grid step that gives ~target_lines and rounds to "nice" values (1, 2, 5, 10 * 10^n).
321+
// Uses magnitude = 10^floor(log10(ideal)) and normalizes to choose 1, 2, 5, or 10.
320322
double GraphWidget::calculate_grid_step(double range, int target_lines) const {
321323
if (range <= 0 || target_lines <= 0) return 1.0;
322324

323325
double ideal_step = range / target_lines;
324326
double magnitude = std::pow(10.0, std::floor(std::log10(ideal_step)));
325327
double normalized = ideal_step / magnitude;
326328

327-
// Round to nice numbers: 1, 2, 5, 10
328329
if (normalized <= 1.5) {
329330
return magnitude;
330331
} else if (normalized <= 3.0) {
@@ -414,12 +415,13 @@ void GraphWidget::draw_axis_labels(QPainter& painter) {
414415
}
415416
}
416417

418+
// Draw each equation as a polyline. Points are in world coords; we transform to screen,
419+
// skip segments far off-widget to avoid huge paths, and use moveTo/lineTo for disconnected segments.
417420
void GraphWidget::draw_graphs(QPainter& painter) {
418421
if (!m_graph) return;
419422

420423
auto all_points = m_graph->generate_all_points(m_config.points_per_graph);
421424

422-
// Track basic performance metrics: total points rendered
423425
int total_points = 0;
424426
for (const auto& series : all_points) {
425427
total_points += static_cast<int>(series.size());
@@ -430,27 +432,23 @@ void GraphWidget::draw_graphs(QPainter& painter) {
430432
for (const auto& points : all_points) {
431433
if (points.empty()) continue;
432434

433-
// Use smooth, high-quality line rendering
434435
QPen pen(m_config.equation_colors[color_index % 5], m_config.line_width);
435436
pen.setCapStyle(Qt::RoundCap);
436437
pen.setJoinStyle(Qt::RoundJoin);
437438
painter.setPen(pen);
438439

439-
// Build path for smooth curves
440440
QPainterPath path;
441441
bool first_point = true;
442442

443443
for (const auto& point : points) {
444444
int screen_x, screen_y;
445445
viewport.world_to_screen(point.x, point.y, width(), height(), screen_x, screen_y);
446-
447-
// Skip points outside visible area for performance
446+
// Clip to widget + margin so we start a new segment when re-entering view instead of one long line.
448447
if (screen_x < -100 || screen_x > width() + 100 ||
449448
screen_y < -100 || screen_y > height() + 100) {
450449
first_point = true;
451450
continue;
452451
}
453-
454452
QPointF screen_point(screen_x, screen_y);
455453
if (first_point) {
456454
path.moveTo(screen_point);

0 commit comments

Comments
 (0)