goblinfactory / konsole

Home of the simple console library consisting of ProgressBar, Window, Form, Draw & MockConsole (C# console progress bar with support for single or multithreaded progress updates) Window is a 100%-ish console compatible window, supporting all normal console writing to a windowed section of the screen, supporting scrolling and clipping of console output.
721 stars 62 forks source link

Cursor keeps in bottom of console, causing the scroll to be Stuck #11

Open Dangelo123 opened 7 years ago

Dangelo123 commented 7 years ago

Hi,

When there are many progressBars, so you cant see all the info and bars in the window, the cursor keeps refreshing on the bottom of console, causing the scroll to be stuck there in way we cannot scroll back up.

The behavior is recorded on the gif below

http://gph.is/2vGsKSU

goblinfactory commented 7 years ago

looking into this now, thank you for the gif, that is most helpful.

goblinfactory commented 7 years ago

if I understand the problem it can be described as follows

 // progressbars should not stop the screen from being scrolled

  given more progressbars than the height of the console window
  when the user scrolls to the top of the console window causing content to scroll off the bottom
  and the progressbars are refreshed or updated
  then the scroll position should remain at the top of the screen

  when the user scrolls to the middle of the content height causing content to hidden from the top and the bottom of the console window
  and the progressbars are refreshed or updated
  then the scroll position should remain in the middle of the screen

  when the user scrolls to the bottom of the content  causing content to scrolled off (clipped) from the top of the console window
  and the progressbars are refreshed or updated
  then the scroll position should remain at the bottom of the screen
Dangelo123 commented 7 years ago

Perfect, that's right!

I'm sorry if my writing early was a little bit confusing, english is not my mother language. If you need any more further clarification feel free to ask me

goblinfactory commented 7 years ago

ok, spent a bit of time looking into this, and it's a trickly one indeed. The issue is cause (i think) by the work I did to make sure the progress bar is threadsafe, specifically this bit of code


            lock (_locker)
            {                
                _item = item;
                var state = _console.State;
                _current = current.Max(Max);
                try
                {
                    float perc = (float) _current/(float) _max;
                    int barWidth = _console.WindowWidth - (TextWidth+8);
                    var bar = _current > 0
                        ? new string(_character, (int) ((float) (barWidth)*perc)).PadRight(barWidth)
                        : new string(' ', barWidth);
                    var text = string.Format("{0} ({1,-3}%) ", clippedText, (int) (perc*100));
                    _console.CursorTop = _y;
                    _console.CursorLeft = 0; 
                    _console.ForegroundColor = _c;
                    _console.Write(text);
                    _console.ForegroundColor = ConsoleColor.Green;
                    _console.Write(bar);
                    _line = $"{text} {bar}";
                }
                finally
                {
                    _console.State = state;
                }
            }

The responsibility in the code above that causes the issue is that the lock is held on the progressbar in order to reset the state, including the cursor positions, left and top, so that if any other thread writes to any window or console or via the .net Console class, that the position is correct.

What happens as a result, is that in your demo, and what I've managed to reproduce, if you have hundreds of progress bars, (e.g. 1 per file with hundreds of files), then when you click to drag the console window, the click and drag takes a very long time by pc standards, and during the 'dragging' of the scrollbar, there's about 5 or 6 cusor top and left's being set.

The only-ish solution that I can think of right now would require writing directly to the low level windows buffer, and-or disabling scrolling while you're dragging, and then re-enabling it after you let go. Something that's out of scope of any reasonable return on effort for an open source project.

Also, I think the use case of hundreds of progress-bars, makes for an unusable experience for the user, and results in a too-much information.

WorkAround Displaying the progress of the total number of files in a collection, using the two-line ProgressBar instead of ProgressBarSlim would make much more sense, imho, and result in much fewer writes per second, allowing for easy dragging of the scrollbar if needed.

Also, the user would be able to more easily see at a glance more useful information e.g. total number of wav files, background images, or sprites, etc. Write a complete text logfile of the actual files downloaded, _log.Info('starting downloading x'); _log.Info('finished downloading x'); and update the high level summary progresses using ProgressBar instead of relying on outputting detailed logging to the console.

If you can let me know exactly why you need to have so many progressbars, then I'll consider alternatives and-or re-open this issue. In the meantime closing this, if you agree. ;)

cheers,

Alan

Dangelo123 commented 7 years ago

Thank you for you time looking at the issue.

Before I opened the issue I also took a look in the code myself, and based in the little that I've learned I agree with your statements. But I consider my motivation to use a few dozens of bars, in the most, 50-60, a valid one. It's not like a crucial functionality, more like a bonus to the user. My application is based on Entities, and each entity has 'n' validations. Each validation is a complex and independent operation that reads and write thousands of data in the database. I could simply display Entities, and hide validations (and that is probably what I'm going to do right now), but for the user it's simply better to have the complete picture of the progress.

Again, thanxs for the feedback and of course for the useful library! If someday I reach a viable solution I'll surely post here, but for now, you can close the issue.

goblinfactory commented 7 years ago

Silly question, but have you tried increasing the height of the default console window? Or tried splitting the window into two, left and right?

for example: I created this sample with 60 progress bars avoiding scrolling (code below the screenshot)

screen shot 2017-09-05 at 00 27 55
        private static void SixtyProgressBars()
        {
            var con = new Window();
            var left = con.SplitLeft("Entities");
            var right = con.SplitRight("Validations");
            var t1 = Task.Run(()=> DoTheThingToTheDbWithTheStuff(left, 30));
            var t2 = Task.Run(() => DoTheThingToTheDbWithTheStuff(right, 30));
            Task.WaitAll(t1, t2);
            Console.WriteLine("All done");
        }
        private static void DoTheThingToTheDbWithTheStuff(IConsole window, int cnt)
        {
            var r = new Random();
            var pbs = Enumerable.Range(1, cnt).Select(i =>
            {
                var pb = new ProgressBar(window, r.Next(200) + 1);
                pb.Refresh(1, TestData.RandomName);
                return pb;
            }).ToArray();

            for (int i = 0; i < cnt * 100; i++)
            {
                Thread.Sleep(r.Next(200));
                i = r.Next(cnt);
                var pb = pbs[i];
                if(pb.Current<100) pb.Next(TestData.RandomName);
            }
        }