View Issue Details
ID | Project | Category | View Status | Date Submitted | Last Update |
---|---|---|---|---|---|
0019012 | MMW 5 | Other | public | 2022-04-24 16:06 | 2022-06-20 17:24 |
Reporter | zvezdan | Assigned To | |||
Priority | urgent | Severity | minor | Reproducibility | N/A |
Status | closed | Resolution | no change required | ||
Product Version | 5.0.3 | ||||
Target Version | 5.0.4 | ||||
Summary | 0019012: Progress circle has slow response | ||||
Description | The 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 Reproduce | Here 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); }); }); }); | ||||
Tags | No tags attached. | ||||
Fixed in build | |||||
|
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? |
|
It appears in half a second when using just that line. |
|
Zvezdan, can you please re-test this on the 5.0.3 release build and make sure it's still reproducible for you? |
|
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. |
|
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. |
|
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 |
|
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 |
|
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. |
|
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. |
|
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. |
|
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. |