View Issue Details

IDProjectCategoryView StatusLast Update
0019012MMW 5Otherpublic2022-06-20 17:24
Reporterzvezdan Assigned To 
PriorityurgentSeverityminorReproducibilityN/A
Status closedResolutionno change required 
Product Version5.0.3 
Target Version5.0.4 
Summary0019012: Progress circle has slow response
DescriptionThe progress indicator needs about 2-3 seconds to appear, almost when the task was already finished, and in meantime there is no any indication that something is happening.

It is recommended that the progress indicator doesn't appear immediately, but after some delay, e.g. 500ms - 1sec. However, 2-3 seconds are too much in my opinion.

Maybe you could add one optional parameter to the createNew method of backgroundTasks, e.g. "delay".

I tried the DelayedProgress class instead, but it is even worse.
Steps To ReproduceHere is the part of code, adding the tracklist with approx. 70 files to the playlist:
                //var progress = new window.DelayedProgress();
                //var taskid = progress.beginTask();
                var progress = app.backgroundTasks.createNew();
                //requestTimeout(() => {}, 10);
                goQueueListPlaylist.clearTracksAsync().then(() => {
                    goQueueListPlaylist.addTracksAsync(oQueueLst).then(() => {
                        goQueueListPlaylist.getTracklist().whenLoaded().then(() => {
                            requestTimeout(() => {
                                console.log('Queue Finished', goQueueListTracks.count);
                                gbQueueSelActive = false;
                                //progress.endTask(taskid);
                                if (!progress.terminated)
                                    progress.terminate();
                            }, 10);
                        });
                    });
                });
TagsNo tags attached.
Fixed in build

Activities

drakinite

2022-04-26 01:10

developer   ~0067781

This must be a bug. For me, the task indicator takes less than half a second to appear when var progress = app.backgroundTasks.createNew(); is called, and less than half a second to disappear after progress.terminate() is called.

What happens to you if you run that code in the devtools console on a debug build? Same result, i.e. 2-3 second delay for it to appear?

zvezdan

2022-04-28 06:54

updater   ~0067839

It appears in half a second when using just that line.

drakinite

2022-06-15 22:10

developer   ~0068547

Zvezdan, can you please re-test this on the 5.0.3 release build and make sure it's still reproducible for you?

zvezdan

2022-06-16 00:33

updater   ~0068552

I posted the code that is not responsible for this issue. It took me a while to detect what is really causing the problem. Here is the new test code, you could create init.js with it; select approx 100 files and click on the toolbar button -> the program will be unresponsive for several seconds and progress circle will not be displayed at all. The program will be longer frozen with more selected files.
    window.uitools.addToolButton('righttoolbuttons', 'play', function () {
        var progress = app.backgroundTasks.createNew();
        var selTracks = window.uitools.getSelectedTracklist();
        var oQueueLst = selTracks.getCopy();
        selTracks.whenLoaded().then(function () {
            selTracks.locked(function () {
                for (var i = 0; i <= selTracks.count - 1; i++) {
                    var oSelTrack = selTracks.getValue(i);
                    oQueueLst.locked(function () {
                        for (var j = 0; j <= oQueueLst.count - 1; j++) {
                            var oPlsTrack = oQueueLst.getValue(j);
                        }
                    });
                    oQueueLst.add(oSelTrack);
                }
            });
            if (!progress.terminated)
                progress.terminate();

            alert('Finished');
        });
    }, 'Test');

This is just a test script to show the problem, please do not question its logic. The point is that the program could be frozen with regular commands and progress circle will not work during that time.

zvezdan

2022-06-16 07:19

updater   ~0068553

And here it is again, slightly modified to be compared with the equivalent code for MM4:
    window.uitools.addToolButton('righttoolbuttons', 'play', function () {
        var dTime = performance.now() / 1000;
        var progress = app.backgroundTasks.createNew();
        var selTracks = window.uitools.getSelectedTracklist();
        var oQueueLst = app.utils.createTracklist(false);
        selTracks.whenLoaded().then(function () {
            // oQueueLst.whenLoaded().then(function () {
                selTracks.locked(function () {
                    for (var i = 0; i <= selTracks.count - 1; i++) {
                        var oSelTrack = selTracks.getValue(i);
                        oQueueLst.locked(function () {
                            for (var j = 0; j <= oQueueLst.count - 1; j++) {
                                var oPlsTrack = oQueueLst.getValue(j);
                            }
                        });
                        oQueueLst.add(oSelTrack);
                    }
                });
                if (!progress.terminated)
                    progress.terminate();

                messageDlg(sprintf(_("Finished in %s seconds."), ((performance.now() / 1000) - dTime).toFixed(2)), 'Information', ['btnOK'], undefined, undefined);
            // });
        });
    }, 'Test');

Here is the equivalent code for MM4, applied on the same database with the exactly same set of selected tracks:
Sub OnStartup()
    Dim oToolbar
    Dim oMenuItem

    Set oToolbar = SDB.UI.Menu_TbNavigation
    SDB.UI.AddMenuItemSep oToolbar, 0, 0
    Set oMenuItem = SDB.UI.AddMenuItem(oToolbar, 0, 0)
    oMenuItem.IconIndex = 33
    oMenuItem.Hint = "Test"
    oMenuItem.UseScript = Script.ScriptPath
    oMenuItem.OnClickFunc = "tbrTest_OnClick"
End Sub

Sub tbrTest_OnClick(oItem)
    Dim selTracks
    Dim oQueueLst
    Dim oSelTrack
    Dim oPlsTrack
    Dim dTime
    Dim i, j

    dTime = Timer
    Set selTracks = SDB.SelectedSongList
    Set oQueueLst = SDB.NewSongList
    For i = 0 To selTracks.Count - 1
        Set oSelTrack = selTracks.Item(i)
        For j = 0 To oQueueLst.Count - 1
            Set oPlsTrack = oQueueLst.Item(j)
        Next
        oQueueLst.Add oSelTrack
    Next
    SDB.MessageBox SDB.LocalizedFormat("Finished in %s seconds.", _
            FormatNumber(Timer - dTime), 0, 0), mtInformation, _
            Array(mbOK)
End Sub

136 files: MM4 executes in 0.05 sec, MM5 executes in 3.47 sec. 255 files: MM executes in 0.10 sec, MM executes in 12.68 sec. So much about the speed improvement of new version.

Ludek

2022-06-17 10:17

developer   ~0068579

Last edited: 2022-06-17 10:19

Yes, the iterations are slower for SharedList descendants. The reason is that for each native object a new JS object needs to be constructed.

For faster iterations use list.getFastObject(idx, obj) or window.fastForEach() , more at: https://www.mediamonkey.com/docs/api/classes/SharedList.html and https://www.mediamonkey.com/docs/api/classes/SharedList.html#method_getFastObject

And if you need to get just a single property of the objects then list.getAllValues('title') should be the fastest way: https://www.mediamonkey.com/docs/api/classes/SharedList.html#method_getAllValues

drakinite

2022-06-18 21:21

developer   ~0068596

Last edited: 2022-06-18 21:26

The reason the progress bar isn't showing is because all of this code is synchronous, meaning it blocks the main UI thread.
You can loop through tracks much faster with the ways Ludek mentioned, but you can also do this:

oQueueLst.addList(selTracks);

to add the entire list to your copy almost instantly.

In the uncommon case where you need to store references to each track later down the line, OR when your background computation takes a long time even with getFastObject/fastForEach, please use listAsyncForEach (https://www.mediamonkey.com/docs/api/classes/Window.html#method_listAsyncForEach). That method is slower, but it runs asynchronously so that it does not block the main UI thread.
More details here: https://www.mediamonkey.com/wiki/Getting_Started_(Addons)#Important_tips

zvezdan

2022-06-18 21:48

updater   ~0068598

Thanks for your advises, but as I said I didn't want that you question the logic of this test code. I made it to be as small as it is possible, the actual code is a way more complicated. I created this test like that to show the point. If you missed it, MM4, which is doing the equivalent synchronous code, is working 69.4 times faster for 136 files and 128.7 times faster with 255 files. MM is doing it in no time, I didn't need to use any tricks with it. I didn't tested larger number of files, but it seems MM5 is exponentially slower proportionally to the number of files. This is not small proportion. I am not talking here about 10% slowness, nor 2 times or 10 times, but 100 times slower.

Your fastForEach and listAsyncForEach are great, but they are not a magic wand. They are useless if the execution of code requires jump out of the loop. I remember someone already told you that in the Forum. Maybe, you should rewrite them to allow such things.

Anyway, please close this issue since all of this that I am saying has nothing with the subject of the issue.

Ludek

2022-06-20 13:14

developer   ~0068611

As for your theory that MM5 is exponentially slower proportionally to the number of files.
No, it isn't, look carefully at your sample code, it has complexity O(n2) because it includes two inner loops:

 For i = 0 To selTracks.Count - 1
        Set oSelTrack = selTracks.Item(i)
        For j = 0 To oQueueLst.Count - 1
            Set oPlsTrack = oQueueLst.Item(j)

So for each track it is doing further oQueueLst.Count iterations.
i.e. for 6 tracks it is not just 6 iterations, but 1+2+3+4+5+6 = 21
-- for 12 tracks it is 78 iterations
-- for 100 tracks it is 4933 iterations

Re: fastForEach and listAsyncForEach: You can break the loop be returning TRUE in the callback function, see definition of those functions in mminit.js
I'll add it to doc to be cleaner.

zvezdan

2022-06-20 16:16

updater   ~0068615

It doesn't matter what order of complexity it is. The complexity of code is the same for both programs. The point is how much slower MM5 is compared to the equivalent code in MM4.

Here are some more data:
68 files - MM4: 0.02 sec, MM5: 0.94 sec - 47 times slower
90 files - MM4: 0.03 sec, MM5: 1.72 sec - 57.3 times slower
358 files - MM4: 0.18 sec, MM5: 26.54 sec - 147.4 times slower
463 files - MM4: 0.31 sec, MM5: 42.66 sec - 137.6 times slower
539 files - MM4: 0.41 sec, MM5: 56.69 sec - 138.3 times slower
586 files - MM4: 0.47 sec, MM5: Error occurred: Callstack: ... Func: eval; What the ...?

You could see them on the attached graph.

How could I use e.g. fastForEach if I have something like this:
    list.locked(function () {
        var itm;
        for (var i = 0; i < list.count; i++) {
            itm = list.getFastObject(i, itm);
            // bunch of code
            if (...) {
                // bunch of another code
                break;
            }
            // bunch of even more code
        }
    });

or even this:
    list.locked(function () {
        var itm;
        for (var i = 0; i < list.count; i++) {
            itm = list.getFastObject(i, itm);
            // bunch of code
            if (...) {
                // bunch of another code
                break;
            } else {
                // bunch of another code
               if (...)
                   break;
            }
            // bunch of even more code
        }
    });

By the way, I must admit that I really don't understand this whole locking thing. MM4 worked perfectly fine without it. Why would I need to lock a list from reading if I have that list just created and used in the same part of my code, without any other part of program being able to read it or even worse to write to it?

I could understand this locking thing when I want access to the tracklist of the Playing list or tracklist of the main tracklist since they are used by other parts of program, but if I create a tracklist with app.utils.createTracklist(false); I assume I have an exclusive access to it.

Ludek

2022-06-20 17:10

developer   ~0068618

Last edited: 2022-06-20 17:24

Re:
586 files (> 170.000 iterations) - MM4: 0.47 sec, MM5: Error occurred: Callstack: ... Func: eval; What the ...?
This generated the error because UI freeze has been detected, the freeze timeout in debug build is 60 seconds by default.


As for the fastForEach , simply replace 'break;' by 'return true;' like this:

fastForEach( list, (itm) => {
            if (...) {
                // bunch of another code
                return true; // break
            } else {
                // bunch of another code
               if (...)
                  return true; // break;
            }
            // bunch of even more code
    });


Re list RW locking: Sure, if you don't need to share the list then you can use e.g. simple JS array and push the items to the array or use list.getAllValues('id') to create JS array of IDs ( https://www.mediamonkey.com/docs/api/classes/SharedList.html#method_getAllValues ).
Iterations through JS array will be very fast then.

But all of this should be rather discussed on developer forum: https://www.mediamonkey.com/forum/viewforum.php?f=27 and not here in Mantis.