Skip to content

perf(view): avoid unnecessary Layout() calls in Set* methods. #83#85

Open
imicky wants to merge 2 commits into
yohamta0:mainfrom
imicky:perf/layout
Open

perf(view): avoid unnecessary Layout() calls in Set* methods. #83#85
imicky wants to merge 2 commits into
yohamta0:mainfrom
imicky:perf/layout

Conversation

@imicky

@imicky imicky commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

perf(view): avoid unnecessary Layout() calls in Set* methods. #83
debug: make Tree() safe for nil pointer fields.

@yohamta0 yohamta0 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Amazing improvements! I have left some comments.

Comment thread view.go
sb.WriteString(
fmt.Sprintf("left: %d, right: %d, top: %d, bottom: %d, width: %d, height: %d, marginLeft: %d, marginTop: %d, marginRight: %d, marginBottom: %d, position: %s, direction: %s, wrap: %s, justify: %s, alignItems: %s, alignContent: %s, grow: %f, shrink: %f",
cfg.Left, *cfg.Right, cfg.Top, *cfg.Bottom, cfg.Width, cfg.Height, cfg.MarginLeft, cfg.MarginTop, cfg.MarginRight, cfg.MarginBottom, cfg.Position, cfg.Direction, cfg.Wrap, cfg.Justify, cfg.AlignItems, cfg.AlignContent, cfg.Grow, cfg.Shrink))
intPtrString := func(p *int) string {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Just curious, why not make it a regular method like func formatIntPtr(p *int) string? Is it performance reason or something?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It's not about performance. I just thought it returns the string "nil" when p == nil – just for the debug output in Tree(). Not sure if this is generic enough. Maybe in another case, someone might want it to be "none", "0", or even "-"... Am I overthinking this? Haha.

Comment thread view.go

// SetLeft sets the left position of the view.
func (v *View) SetLeft(left int) {
if v.Left == left {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

I'm wondering if we should refactor it to save the previous state at the end of View.startLayout(), and then compare it with the new state inside View.Layout() to determine whether it's dirty. What do you think?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Let's assume this snapshot approach is scoped only to the basic fields listed below, and does not cover complex structures like the children slice.

	// TODO: Remove these fields in the future.
	Left         int
	Right        *int
	Top          int
	Bottom       *int
	Width        int
	WidthInPct   float64
	Height       int
	HeightInPct  float64
	MarginLeft   int
	MarginTop    int
	MarginRight  int
	MarginBottom int
	Position     Position
	Direction    Direction
	Wrap         FlexWrap
	Justify      Justify
	AlignItems   AlignItem
	AlignContent AlignContent
	Grow         float64
	Shrink       float64
	Display      Display
	
	Hidden  bool

Given this scope, here are my thoughts on the pros and cons:
Pros:

  • Detects direct field assignment(e.g., view.Width = 50) and triggers re-layout.
  • Reduces setter boilerplate.
  • Centralizes dirty-check logic for basic field changes. (However, structural changes like AddChild/RemoveChild, etc. still require manually setting isDirty, so it's not fully unified.)

Cons:

  • Ongoing maintenance burden – a shadow snapshot must be kept in sync with every field. Any newly added field requires updating both the snapshot copy and the comparison logic – easily overlooked and leads to subtle bugs.
  • Overhead of full-field comparison per Setter – a single SetWidth currently compares only 1 field; with snapshot, it would compare all ~20 fields. Feels like "doing more work than necessary" on every Set* calls.

It seems technically feasible, but I wonder if it's worth it for the added complexity – what do you think?

Plus, I see the // TODO: Remove these fields in the future. – does that mean this discussion might become irrelevant once those fields change?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants