Skip to content

[draft] Prototype for better TLine::ExecuteEvent#21883

Closed
linev wants to merge 2 commits intoroot-project:masterfrom
linev:improve_graphics
Closed

[draft] Prototype for better TLine::ExecuteEvent#21883
linev wants to merge 2 commits intoroot-project:masterfrom
linev:improve_graphics

Conversation

@linev
Copy link
Copy Markdown
Member

@linev linev commented Apr 10, 2026

Interactive objects moving require to have some temporary variables stored.
Up to now many static variables were defined in TLine::ExecuteEvent which
were reused by consequent methods calls.

There are many disadvantages of such approach:

  • there is no guarantee from ROOT that always same TLine object instance called
  • values in static members remain and can be by mistake used by other objects
  • there is no chance to handle multiple gui event loops from different threads

Trying to make different approach when "interactive" object preserved as
transient member of the class with std::unique_ptr<>. It solves all mentioned
problems. The only disadvantage - memory consumption for each TLine
object increased by 8 bytes.

If one agrees on using of such approach - one can adjust other ROOT classes making them
more robust and moving us forward to graphics multithreading

linev added 2 commits April 10, 2026 09:47
Use PaintArrowNDC in the TArrow::Paint
Use attributes ModifyOn methods
Shorten options handling code
Interactive objects moving require to have some temporary variables stored.
Up to now many static variables were defined in TLine::ExecuteEvent which
were reused by consequent methods calls.

There are many disadvantages of such approach:
- there is no guarantee from ROOT that always same TLine object instance called
- values in static members remain and can be by mistake used by other objects
- there is no chance to handle multiple gui event loops from different threads

Trying to make different approach when "interactive" object preserved as
transient member of the class with std::unique_ptr<>. It let solves all of mentioned
problems. The only disadvantage - memory consumption for each TLine
object increased by 8 bytes.
@linev linev self-assigned this Apr 10, 2026
@linev linev requested a review from couet as a code owner April 10, 2026 11:05
@linev linev marked this pull request as draft April 10, 2026 11:05
/// Line default constructor.
/// Defined here to correctly instantiate fInteractive member

TLine::TLine()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can be = default?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Never for TObject-based classes

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but does it need to be defined in this class at all?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, std::unique_ptr<> need to be initialized for class which only described in CXX file

Int_t px1, px2, py1, py2, pxold, pyold;
Double_t oldX1, oldY1, oldX2, oldY2;
Bool_t hasOld = kFALSE;
Int_t selectPoint = 0;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are hasOld and selectPoint default-initialized but the other fields aren't?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While other depends on these two

Bool_t opaque = parent->OpaqueMoving();
ROOT::Internal::TLineInteractive *i;

auto get_interactive = [this, &i](bool force) -> bool {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be called getInteractive. Also I would rename force to createIfNull or something like that .

Although, I'm not sure this lambda should exist at all: why not simply embed the fields of TLineInteractive in TLine? (see comment above)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not simply embed the fields of TLineInteractive in TLine? (see comment above)

Because then each instance of TLine will get many extra fields which never used.
So one tries to decrease memory consumption by such technique

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How many such instances do we expect in the worst-case scenario? With 64 extra bytes per instance, you'd have to have more than 16k instances before you waste 1MiB of memory

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TLine is very basic object and appears everywhere. But just double or triple size just of unused data? I really do not like this.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, but, for one I'm not sure why we need to store oldX* as double (float precision should be more than enough for NDC coordinates);
also, I'm not sure I understand it correctly: do we ever need more than 1 instance of this data per thread at once? Or is this data only relevant while interacting with the line?
Because if that's the case I'd argue it should only live during the interaction itself (e.g. create a thread_local instance when interaction start, destroy it when interaction end).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be different approach.
Maybe it is even better.

if (!fInteractive && force)
fInteractive = std::make_unique<ROOT::Internal::TLineInteractive>();
i = fInteractive.get();
return i != nullptr;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of setting i and returning a bool, can't this directly return i? I don't like that the captured variable gets set as a side effect: it's very hard to track (since it's not even a function out parameter). I would instead do:

if (auto lineInteractive = getInteractive(false)) {
  // ...
}

or something along these lines

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By the way, i is a very bad name imho; if you want a short name since it's going to be used a lot, at least I'd call it line

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i here is for "interactive". If in general approach is fine I will change commit anyway.

if (auto lineInteractive = getInteractive(false)) {
// ...
}

This does not work good in case selectors without break.
And not very important here.

Double_t fY1{0}; ///< Y of 1st point
Double_t fX2{0}; ///< X of 2nd point
Double_t fY2{0}; ///< Y of 2nd point
std::unique_ptr<ROOT::Internal::TLineInteractive> fInteractive; ///<! temporary object for interactivity handling
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a strong reason to make it a unique_ptr rather than embedding the fields directly? (or as a struct value)

Sure, you'd have to define the internal class in the header, and it would take up more space in the TLine instance, but is that really a problem?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mention - I see it as template for other classes.
There one can have big arrays used only during interactivity.
Also for TLine it is an issue - if you have 1000 of them at once.
Use of std::unique_ptr<> introduces only 8 extra bytes in size - which remain constant disregard of complexity of TLineInteractive class

@linev
Copy link
Copy Markdown
Member Author

linev commented Apr 10, 2026

I closing this PR - probably idea with thread_local is more elegant.

@linev linev closed this Apr 10, 2026
@linev linev deleted the improve_graphics branch April 10, 2026 15:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants