I would like to add that not just that Node is heavy, every intermediate Pane also need to be processed by CSS, may receive a CssStyleHelper (e. g. to hold triggerStates) and counting in to the depth. Since this is currently the area I'm looking
I would like to add that not just that Node is heavy, every intermediate Pane also need to be processed by CSS, may receive a CssStyleHelper (e.g. to hold triggerStates) and counting in to the depth. Since this is currently the area I'm looking in, I thought this would be worth to add to the discussion. I still think improving the memory footprint of Node is a good thing. But that is a separate problem IMO. -- Marius Am 16.07.2026 um 20:35 schrieb John Hendrikx: > On 16/07/2026 17:26, Andy Goryachev wrote: >> I see, you want to avoid the overhead of the intermediate pane(s), >> mainly because the Node class is too heavy. > Exactly, and not only that, to avoid the overhead of a graphic peer > (HBox's are still Regions), CSS styling, another layer to go through for > dispatching/bubbling for events, several dozen fields, and many > unavoidable properties that are getting created. A Node can easily > consume upwards of a few kilo bytes to do "nothing". > >> I wonder if a less invasive solution for that problem would be to put >> the rarely used properties into a hash table instead, something along >> the lines of https://github.com/andy-goryachev-oracle/jfx/pull/20 . > I don't see how that would be a less invasive solution, considering > you'd be introducing a hash map lookup to find properties. It also > doesn't solve the problem; a layout container would still have to be a > Node, and no matter how many properties you stash in that map, it will > still consume on the order of 10-30x more memory than a non-Node layout > container. > >> This way we'd eliminate most of the overhead and do it without adding >> any public APIs (apart from exposing the layout classes once they >> mature). The reason I dislike the idea of Layoutable is because it >> impacts all the Node's descendants, even though conceptually it only >> belongs to Parent. > That's not quite correct, every Node, including one without children, > like Shape (not a Parent), Subscene, ImageView, etc, can and must > participate in layout. Every Node has: > > - resizable + resize + resizeRelocate methods > - minWidth/prefWidth/maxWidth etc. > - layout constraint > - layout x/y > > Parent only governs a potential list of children, and governs some flags > to determine when the layout system should be triggered, but that's > orthogonal -- these flags would not be part of the proposed interface. > Parent has nothing to do with whether it can participate in a layout, as > all Nodes already can. The children of an HBox are in > ObservableList<Node> which basically means anything can participate in a > layout, including shapes, subscenes, imageviews, etc. > > The Layoutable interface simply marks something as "being able to > participate in layout". It is not a marker for a layout algorithm(!). > It just so happens though that layout containers themselves (ones that > can do layouts) also themselves can participate in a "grander" layout, > just like an HBox can contain more HBoxes. > > Remember how this started: > > - We discover bugs in LabelSkinBase / TitledPane etc with regards how > they handle graphic Nodes that use more advanced features (like min/max > widths, content bias, alignment). I can put a full scene graph in the > "graphic" part of a Label (although often a HBox is sufficient), and > expect that to be handled correctly without bugs. > > - We find out that these classes badly duplicate the code that is in > HBox, taking shorts cuts like only called `prefWidth(-1)` on the graphic > node and assuming that is sufficient. > > - The reason for that was simple: HBox is too heavy; having a HBox or > VBox in every Labeled would create a massive amount of additional nodes, > it made sense to optimize those away, just like it made sense to change > the Skin API to no longer require that it supplies a Node (it still > works if you do, but none of the FX skins do it that way anymore, again, > it would be wasting precious Nodes). > > - We discuss how it would be nice if the algorithm that HBox has could > be provided without having to use an HBox, so these classes don't have > to poorly duplicate very complicated layout logic > > We're now here with a proposal that almost couldn't be simpler: > introduce an interface so both Nodes and a light-weight layout > containers can have a common ancestor, and have Node implement that; for > Node it would literally mean: > > - Copy a dozen related method signatures + documentation to a new interface > - Add `@Override` to the affected methods in Node, and strip their > documentation (that is on the interface now, you won't notice in Javadoc) > - The change is 100% source and binary compatible, and even has no > performance impact if calling these methods via a Node reference > > Even in core-libs they've done this several times, like introducing > SequencedCollection/SequenceMap/SequencedSet interfaces, or the > AbstractStringBuilder which became the new ancestor of StringBuffer when > StringBuilder was introduced. > > Adding such an interface would simply mark Node as something that "can > participate in layouts", which is already exactly what a Node is (as it > has hard coded all those methods on it already). > > --John > > >> -andy >> >> >> >> >> *From: *John Hendrikx <[email protected]> >> *Date: *Thursday, July 16, 2026 at 03:37 >> *To: *Andy Goryachev <[email protected]>; >> [email protected] <[email protected]> >> *Subject: *Re: [External] : Proposal to split off layout and layout >> metric methods from Node to interfaces it implements >> >> This Message Is From an External Sender >> This message came from outside your organization. >> Report Suspicious >> <https://us-phishalarm-ewt.proofpoint.com/EWT/v1/ACWV5N9M2RV99hQ!NB26dgOv2uIP HxPgvfKeeB8wifO7UNM_qPE-ApXJee7-lnArw9ivFU3vA1OKWzk7SOFWi4LZ-I0apqiGweab_xqLu69y FJ24pTitxw$> >> >> >> On 15/07/2026 20:10, Andy Goryachev wrote: >> >> Right, static layouts will only work in simple cases, the more >> complicated ones will require an instance. >> >> My point is that if we are laying out Nodes, there is no need to >> invent another abstraction layer because Nodes contain all the >> required data. This will eliminate the "Bonus" section which some >> seem to like. >> >> The Bonus section is bonus, all else is still needed. >> >> The problem is: we're not laying out only Nodes. We're also laying >> out potentially light-weight layout containers (that purposely are not >> Nodes). So the list of things to lay out can contain a mix of Nodes >> and Layouts. A layout acts as a Node, but isn't one. >> >> Let's take my example: A TitleSubtileControl that can have a Graphic >> on the left and on the right two lines of text one above the other >> (title and subtitle) >> >> - The TitleSubtileControl is a normal Node, that wants to avoid having >> a nested HBox and VBox so it creates: >> - A HBoxLayout within it nested a VBoxLayout and a Graphic (a Layout >> and a Node) >> - A VBoxLayout within it nested a Title and a Subtitle Text (two Nodes) >> - It adds only the Nodes to the scenegraph as its children (so: >> titleSubtitleControl.getChildren().addAll(graphic, title, subtitle) >> >> Now the problem is, HBoxLayout needs to accept a VBoxLayout (not a >> Node) and a Graphic (a Node). The only way to do that is to store >> them as Objects since they don't share a common ancestor; VBoxLayout >> can't be a Node because that would pull in a zillion private fields >> and properties making it "heavy". >> >> The solution I presented is to create this common ancestor, which I've >> dubbed Layoutable. It contains methods that only layouts need (like >> VBoxLayout or VBox itself). These methods already exist on Node, >> they're just not grouped as part of an interface it implements. Doing >> such a change is source and binary compatible. >> >> So the end state is: >> >> - VBoxLayout implements Layoutable (but is not a Node!) >> - Node implements Layoutable >> >> When VBoxLayout and a Node are nested in HBoxLayout, the HBoxLayout >> can treat both as Layoutable's making Layouts clean (there would have >> to be a million instanceof checks otherwise, or we'd need to wrap >> Nodes in a LayoutableWrapper). >> >> Sorry for the confusion with the bonus section, I hope this clarifies >> why it would a good move to have a common ancestor for Nodes and Layouts. >> >> --John >> >> >> >> I guess what I am trying to say is that code reuse (and removal of >> hard-to-maintain code replication) should be the primary goal, >> while adding new features (Canvas) should come last, if ever. >> >> -andy >> >> >> *From: *John Hendrikx <[email protected]> >> *Date: *Wednesday, July 15, 2026 at 10:49 >> *To: *Andy Goryachev <[email protected]>; >> [email protected] <[email protected]> >> *Subject: *Re: [External] : Proposal to split off layout and >> layout metric methods from Node to interfaces it implements >> >> This Message Is From an External Sender >> This message came from outside your organization. >> Report Suspicious >> <https://us-phishalarm-ewt.proofpoint.com/EWT/v1/ACWV5N9M2RV99hQ!NB26lig kesgE1bPqfVnUuELFLOB1vmX7gUMnmgafz5PcT3tCIOQ6CH3Y_2TlHn6_oY4aATre-aOhU774w6HiWg4 mLvHIFUYvM_SnXw$> >> >> >> >> On 15/07/2026 19:32, Andy Goryachev wrote: >> >> * >> Yes, they don't need to be public immediately, although >> they definitely can be as they're nothing more than a >> "copy" of the layout panes we already have, but packaged >> without the Node dependency. We would need pretty quickly >> the Node implementing the Layoutable interface (which >> opens the path for people writing their own virtual layouts). >> >> >> >> One thing I wanted to mention is that these layout classes can >> probably be static (unless they need to keep some internal >> state like HBox.tempArray). If I understand the logic, there >> is no need for the Layoutable interface either, the owner Node >> is merely delegates layoutChildren() to the corresponding >> layout class, passing all the necessary information (scale, >> snap, etc.) >> >> If you want virtual layouts to compose, you need to be able to >> point at something. Let's say you have a simple layout: >> >> HorizontalLayout[ Graphic + Text ] >> >> This can be relatively static, agreed. >> >> However, if you want something more complicated: >> >> HorizontalLayout[ Graphic + VerticalLayout[Title + Subtitle] ] >> >> ...then how would the static call look like to HorizontalLayout? >> >> Furthermore, HorizontalLayout would need to accept both Node and >> Layout somehow if they don't implement a common interface (so >> Object[] ?) >> >> Also, layouts often have a ton of parameters to tweak: padding, >> alignment, spacing, margins, snapToPixel, etc... that's a lot of >> things to pass in to a static layout. This quickly invites a >> "parameter" object, at which point you can just make the Layout an >> instance (which gives more flexibility for also caching things and >> other interal bookkeeping). >> >> The best you can do as a "static" solution is to have some >> helpers, like the SpaceDistributor class I once created, but the >> actual layouts are *far* more complicated, and I don't think you >> can easily get away with having them just be a static method call. >> >> >> In other words, it might be possible to achieve the main goal >> of code reuse without any changes to the public APIs. The >> layout classes can be made public later, once matured. >> >> Only if you accept the layouts would be extremely limited (no >> mixing of Layouts and Nodes). >> >> Regardless, there wouldn't be any changes to existing public API's >> by having Node implement Layoutable. At most, there would be one >> or two methods to offer a better abstraction for just plainly >> exposing "getProperties()" on Layoutable. Implementing an >> interface that exposes methods that Node already has is a source >> and binary compatible change. >> >> --John >> >> >> What do you think? >> >> -andy > > References Visible links: Hidden links: 2. https://us-phishalarm-ewt.proofpoint.com/EWT/v1/ACWV5N9M2RV99hQ!N727doPOsOLk1ZPh99KUGFxSAPonZG2OpRgnfIayjZjm2sMdFNKyEO4PdWQHj7WicDRDdaYVkbL2xtbfFgRZPBGPs8LYnExgdJ4$
