jroimartin / gocui

Minimalist Go package aimed at creating Console User Interfaces.
BSD 3-Clause "New" or "Revised" License
9.91k stars 610 forks source link

force update #222

Open ripienaar opened 4 years ago

ripienaar commented 4 years ago

I used g.Update to append lines to a text box that scrolls - like a tail -f.

Periodically i remove lines from buffer so the size doesnt grow forever, however the fact that g.Update() creates a go routine leaks lots of memory over time. After a few 10k lines you end up with 10k go routines worth of descriptors in memory that will never be freed.

Editing the code and changing update to:

func (g *Gui) Update(f func(*Gui) error) {
    g.userEvents <- userEvent{f: f}
}

improves things for me drastically without impacting anything noticable. Can you perhaps tell me the reason why Update does this in a go routine? Might we add a SyncUpdate or similar that doesnt create a go routine?

dsouzae commented 4 years ago

I implemented something similar but left the original Update function alone (for backwards compatibility), and created an "Update2" function of that does the same code.

Below is code I have to trim the maximum lines in the buffer, with some other bug fixes. I should make a PR for this.

diff --git a/view.go b/view.go
index 42082f8..0b81d66 100644
--- a/view.go
+++ b/view.go
@@ -71,6 +71,9 @@ type View struct {
        // If Mask is true, the View will display the mask instead of the real
        // content
        Mask rune
+
+       // Set the maximum number of lines to store
+       MaxLines int
 }

 type viewLine struct {
@@ -215,7 +218,7 @@ func (v *View) Write(p []byte) (n int, err error) {
                        }
                default:
                        cells := v.parseInput(ch)
-                       if cells == nil {
+                       if cells == nil || len(cells) <= 0 {
                                continue
                        }

@@ -227,6 +230,12 @@ func (v *View) Write(p []byte) (n int, err error) {
                        }
                }
        }
+
+       if v.MaxLines > 0 && len(v.lines) > v.MaxLines {
+               cut := len(v.lines) - v.MaxLines
+               v.lines = v.lines[cut:]
+       }
+
        return len(p), nil
 }

@@ -322,7 +331,7 @@ func (v *View) draw() error {
        }

        if v.Autoscroll && len(v.viewLines) > maxY {
-               v.oy = len(v.viewLines) - maxY
+               v.oy = len(v.viewLines) - maxY - 1
        }
        y := 0
        for i, vline := range v.viewLines {
glvr182 commented 4 years ago

I would be able to add a function called UpdateAsync(), this way we wouldn't change any existing code and still have the wanted functionalities.

What are your thoughts

ripienaar commented 4 years ago

Ideally to avoid a unbounded growth it would be nice if there was a maxlines setting as suggested.

But yes, the UpdateAsync() would be great. If you agree with that I can send a PR

glvr182 commented 4 years ago

Yea totally! Go for it!

glvr182 commented 4 years ago

Can you open it on @awesome-gocui we run a fork of this project theere since the original ownwer hasnt responded to anything in a while

ripienaar commented 4 years ago

OK, I opened https://github.com/awesome-gocui/gocui/pull/67