Proposed AddGadgetItems API (batch add items)
BlitzMax Forums/MaxGUI Module/Proposed AddGadgetItems API (batch add items)
| ||
I have been playing around with maxgui recently and discovered that it really doesn't perform very well with adding moderatly high amounts of data to gadgets. I have a standard list adding 1000 items and the delay is pretty bad! I had a poke around and boiled it down to a maxgui issue. If you look at the win32maxguiex.bmx file you will see: Method InsertListItem(index,Text$,tip$,icon,tag:Object) Local it:LVITEMW it=New LVITEMW it.mask=LVIF_TEXT|LVIF_DI_SETITEM it.iItem=index it.pszText=Text.toWString() 'If icon>=0 Then it.mask:|LVIF_IMAGE it.iImage=icon 'EndIf DeSensitize() SendMessageW _hwnd,LVM_INSERTITEMW,0,Int Byte Ptr(it) SendMessageW _hwnd,LVM_SETCOLUMNWIDTH,0,-2 If Not IsSingleSelect() Then SelectionChanged() Sensitize() MemFree it.pszText EndMethod Simply by commenting out the lines: 'DeSensitize() SendMessageW _hwnd,LVM_INSERTITEMW,0,Int Byte Ptr(it) 'SendMessageW _hwnd,LVM_SETCOLUMNWIDTH,0,-2 'If Not IsSingleSelect() Then SelectionChanged() 'Sensitize() It was possible to drastically increase the speed of the list manipulation. Obviously maxgui needs to do its own bits and call those functions which is why I am suggesting that maybe a AddGadgetItems API could be added. AddGadgetItems would prepare the gadget once; add all of the items in a batch; finalise the gadget. This would avoid all those repeated calls to system/driver that have been commented out above. |
| ||
I have written a stand alone function that seems to work on windows: I dunno if this breaks anything but it all seems to work fine on my tests. To use it you do: Local batch:AddListItemsBatch = New AddListItemsBatch batch.Add("item 1") batch.Add("item 2") batch.Add("item 3") batch.Add("item 4") batch.Go(listbox) It only supports the listbox at the moment as I don't need anything else but as a test case here is a comparison. Maxgui adding 10000 items: took 23.8309994 sec Batch adding 10000 items: 1.70700002 sec Obviously there is the argument that your design is broken if your adding 10000 items, but that is just to highlight the drastic difference. Last edited 2011 Last edited 2011 |
| ||
Hi, thanks for sharing this code. Can't one just program some sort of "Locklistbox()" function in order to speed up the progress? - From my experience the constant redrawing is what slows the listbox down. |
| ||
Love it! :-) |
| ||
I always thought there was a lockgadget up until few days ago, b+?? hmm LockGadget() would be ideal! |
| ||
@Seb: Is it possible to do this multi-platform (gui enhancement)? @skn3: I'm poking for such a function for over 5 years now. :O Quote myself: http://www.blitzbasic.com/Community/posts.php?topic=60869#679298 |
| ||
Well, I have been using these functions in my little radio player for some weeks now. All works fine so far (Win7 64Bit). I also see a visual speed benefit while using ~1.200 items: http://188.165.211.46/~knot/prp/prp_win32.zip |
| ||
Excellent news! Can also confirm things working on daily development/usage for past weeks! Hurrah for batch :) |
| ||
Has anyone tested this under Linux / Mac yet? |
| ||
The code should work fine with mac / linux but if you look in the pre conditional compiling, it is just reverting to the default behaviour... If you/anyone do a mac one please let me know and i'll update the thread. |
| ||
I found a tiny "glitch" under Windows: The listbox the items are added to displays a horizontal slider for a second when the gadget is updated. Even if the items don't need one (= item length is fine). - I remember Seb had that glitch in the original MaxGui as well. Not sure how he fixed this. |
| ||
Hmmmm I havnt noticed this? I'll have a look tomorrow when back on pc. What version of windows? |
| ||
System Windows 7 (64Bit) and XP (32Bit) I have put up a simple example program: 1, Run Program 2. When you press the button add batch items you should see the issue. But only the first time! After that it works fine. At least in this example. Thanks for your help! |
| ||
OH AWESOME! After years of using win api calls I never knew windows had a built in ability to disable redraw for a hwnd! See lines 89 and 124 for the changes. That should solve it, I will update the original post at the top too. Last edited 2011 Last edited 2011 Last edited 2011 |
| ||
Works fine under xp and win7 now. Great job! P.S. Is Seb still working on "MaxGui2"? |
| ||
No idea about the maxgui2 thing! It would be cool if he was but Id imagine his time is fairly limited! |
| ||
great work, your modifications from this post and others should be added to the MaxGUI release. Very handy! [edit] couldn't find anything else on Mac OSX disable redrawing, only your question on Stackoverflow Last edited 2012 |
| ||
"only your question on Stackoverflow" haha yes indeed ;) |
| ||
Warning!!! Zombie thread approaching... :) I modified the code add bit, so I can create a list with the items sorted "upwards" or "downwards": Method AddLast(text:String,Flags:Int=0,icon:Int=-1,tip:String="",extra:Object=Null) ' --- create a gadget item so we use it to add later --- 'create the new item Local Item:TGadgetItem = New TGadgetItem Item.Set(text,tip,icon,extra,Flags) 'add it to the batch at the very end items.AddFirst(Item) 'increase the count total :+ 1 End Method Method AddFirst(text:String,Flags:Int=0,icon:Int=-1,tip:String="",extra:Object=Null) ' --- create a gadget item so we use it to add later --- 'create the new item Local Item:TGadgetItem = New TGadgetItem Item.Set(text,tip,icon,extra,Flags) 'add it to the batch list at the very start items.AddFirst(Item) 'increase the count total :+ 1 End Method I'm looking for a fast way to generate a random list of items. I.e. each item that is added will be placed at a random spot of list. I could use Modifygadgetitem() after the list has been created and switching each item of the list afterwards (= ugly and slow). Does someone know how to do this while the list is generated and using the functions above? I need it to work with larger lists (2.000+ items with icons and tooltips). |
| ||
Hello Grisu, here is a one way of doing it: SuperStrict Rem Random list insertation (almost) EndRem Global list:TList = New TList SeedRnd MilliSecs() 'Simulate x number of insertations to listbox For Local i:Int = 1 To 100 Addrandom(String(i)) Next 'Main function Function AddRandom(obj:Object) Local link:TLink = list.firstlink() If Not link Then list.addlast(obj) Return EndIf Local count:Int = Rand(0,list.count()) Local notEven:Int = count Mod 3 For Local i:Int = 0 Until count If link.nextlink() Then link = link.nextlink() Else Exit EndIf Next If Not notEven Then list.insertbeforelink(obj,link) Else list.insertafterlink(obj,link) EndIf EndFunction ' Display results For Local s:String = EachIn list Print s Next EDIT: Or more in context (not tested): EDIT2: NOTE: Need to randomize seed before first entry ( SeedRnd MilliSecs() ) Method AddRandom(text:String,Flags:Int=0,icon:Int=-1,tip:String="",extra:Object=Null) ' --- create a gadget item so we use it to add later --- 'create the new item Local Item:TGadgetItem = New TGadgetItem Item.Set(text,tip,icon,extra,Flags) 'add it to the batch list Local link:TLink = items.firstlink() If Not link Then items.addlast(item) total :+ 1 Return EndIf Local count:Int = Rand(0,items.count()) Local notEven:Int = count Mod 3 For Local i:Int = 0 Until count If link.nextlink() Then link = link.nextlink() Else Exit EndIf Next If Not notEven Then items.insertbeforelink(item,link) Else items.insertafterlink(item,link) EndIf 'increase the count total :+ 1 End Method -Henri |
| ||
Hey, thanks for that demonstration code. As I needed to get this to work with Skn3's batch functions above, I chose a different approach in the end: 1. I create a temporary TList and add all items into it. 2. I pick a random item from that list and remove it afterwards. 3. I look up the random item from my database and add it to the listbox. 4. I repeat steps 2 + 3 until all items have been randomized. 5. I call GoBatch() to make the new listbox visible to the user. Ideas to speed the process up are welcome. Here comes my ugly example code: (from the main app) Local Random_list:TList=CreateList() Local it:trecord Local tmp_str:String Local counter:Int ' Create a temp list with all station filenames present, so we can select a random one out of it later For it:trecord=EachIn MapValues(trecord.map_Station) ListAddLast Random_list, Upper(it.filename) Next Local Max_items:Int=CountList(Random_list) Local Rnd_item:Int While Max_items>0 Rnd_item:Int=Rand(1,Max_items) counter:Int=1 For tmp_str:String = EachIn Random_list If counter=Rnd_item Then ListRemove( Random_list:TList, tmp_str ) Max_items:Int=Max_items-1 'Print Tmp_str+" found! "+counter+" Max: "+Max_items Exit EndIf counter=counter+1 Next it:trecord=trecord(MapValueForKey(trecord.map_Station,tmp_str)) ' Grab Record according to filename If it Then ?Not Linux If it.fav=0 Then batch.Add it.name,GADGETITEM_NORMAL,it.rating,ConvertGenreToString(it.genre), it.filename Else batch.Add it.name,GADGETITEM_NORMAL,(it.rating+6),ConvertGenreToString(it.genre), it.filename ? ?Linux If it.fav=0 Then batch.Add it.name,GADGETITEM_NORMAL,it.rating,ConvertGenreToString(it.genre)+" ("+Get_Rating_text(it.rating)+")", it.filename Else batch.Add it.name,GADGETITEM_NORMAL,(it.rating+6),ConvertGenreToString(it.genre)+" ("+Get_Rating_text(it.rating)+")", it.filename ? it=Null EndIf Wend ' Until no items left in random_list |
| ||
Does your solution work at satisfactory speed ? It's hard to test when you are relying on other things not visible here. -Henri |
| ||
It works, but for me it's quite slow (~30ms) and I'm on a high end PC. I have no idea how it will run on lower end systems. So I'm worried users may be disappointed. I created a standalone app. So the results can be checked. Please select "Randomise List" to generate a new list and post your results (non-debugmode). Download Source: http://www.mediafire.com/download/lsr5n28rsbb20yg/RandomTestV3.zip |Edit| Degac's changes from below are now included! |
| ||
Just tested your source code on my computer (AMD PhenomII X4 955) At startup debug on: 38-54ms debug off: 13ms When clicked on random mode debug off: 132ms - 190ms |
| ||
Some questions about your source code. 1. why do you use ReadFile? It seems useless as you use LoadText(). If you want to check if file is present, just use FileType() 2. your record is already 'fixed size'... just replace with this in release mode now I get 11 ms (min was 8ms!) (at loading!) Same in function ConvertGenreToString: just use an array string... the Select..Case statement is not needed here (and no call to a function also if Index are bounded at loading time. |
| ||
Hi Christian, thanks a lot for the input! Will test the changes. Please select "Randomised List" from the Combobox. This creates the random list and should be slower. I get ~10ms for the "Normal list" generation. P.S. Ahm, for the readfile, don't I need this or Openfile() in order to use LoadText()? |
| ||
Looking at your source code I think it's better to use LINK.Remove() instead of ListRemove() Now (in release) I got 107-109ms every 'random' call |
| ||
Hi yes I posted and then edited my results! Sorry. I realized that you mean the 'reload time' when you choose 'randomised list' afted I posted! About LoadText() If you look at the source code you'll see that LoadText 'open' a stream... so it's a double work. |
| ||
Added all your changes and reuploaded the source code (see initial post). The Link.remove is much faster than the old code! My delay dropped from ~30ms to ~20ms. Btw: Is "(Casuale)" the right translation for "(Random)" in Italian?. I need this new phrase for the selection box. :) |
| ||
Yes, the translation is correct. |
| ||
Is there a way to access the temporary item index of the listbox before it's passed to the station ListBox itself via BatchGo()? I think it would be faster to: 1. Go through the whole station map item by item and... - Count the temorary list batch of items - Insert the new item to a random place (1..max present) 3. Call BatchGo() to move the item array to the station list. This way there is no need for the additional temporary TList (overhead). |
| ||
Ok, well, the code creates double entries when the list gets randomised... :/ Still trying to figure out why... |
| ||
Just found this http://www.blitzbasic.com/Community/posts.php?topic=69137 about scrambling an array... this is source code changed The 'core' is that function It's damned faster! |
| ||
I searched the forums for code before posting here, but I never used the term "shuffle". ;) Apart from the little overhead, this is nearly as fast as the normal list creation for me. To test the code under real-life conditions, I created a PRP Build: (deleted) Works fine so far. Let me know, if you see any screen tearing. Grazie! |
| ||
Well... on my dad's computer (PhenomX6 WinXP) debug off, original random 27ms, new one 6ms. To me seems quite fast :P Just downloaded your latest PRP. Everything seems to work perfectly. No tearing, no interruptions of sort. |
| ||
Thanks for the feedback. After using the test version for several hours. I found a bug. :) 1. Extract the zip to a fresh folder. 2. Start the app and select the favourite station list. 3. Select "Random" list. 4. Delete one station from this station list. -> "Right mouse click" -> "Delete station". 5. Select the "Random" list again. 6. There will be more (double) items inside the station list then before. Will get some sleep and start fresh tomorrow. |
| ||
If you are using my latest code with array I suppose I found the problem. When you delete a station you need to . delete the array station . rebuild it again Otherwise that station will be present. And putting the array entry to null I don't know what other problems can raise |
| ||
This "should" work now... (deleted) I was think, instead of rebuilding the array station, can't I just remove the one item that got deleted? This should be much faster. Can I shrink the array by the one item that was deleted? Example: ' Remove a station MapRemove map_Station,Upper(_fna),st 'Not sure how to delete an item here array_station=array_station[..ID_counter-1 array_station[ID_counter]=st ID_counter:-1 ' Add a station MapInsert map_Station,Upper(_fna),st array_station=array_station[..ID_counter+1] array_station[ID_counter]=st ID_counter:+1 |
| ||
Sure... but 1st example I think is wrong. You should do something like this |
| ||
Thanks again! I found another method. At least it should be a bit faster than the old code. Last update for the weekend: http://www.mediafire.com/download/v8w91drqm96h53f/PRP_Test4.zip - I'm going to stress test the new build more next week. |