66#include " VectorChangedEventArgs.h"
77#include < algorithm>
88
9- // Nearly all Vector need to set DependencyObjectBase flag
9+ // Nearly all Vector need to set DependencyObjectBase flag
1010// to make DependencyObject as ComposableBase
1111// Otherwise we may hit memory leak when interact with .net.
1212// One exception is AcrylicBrush::CreateAcrylicBrushWorker
1313// because we know we don't have memory leak.
1414enum class VectorFlag {
1515 None = 0 , // not Observable, not Bindable and not DependencyObjectBase
16- Observable = 1 ,
17- DependencyObjectBase = 2 ,
16+ Observable = 1 ,
17+ DependencyObjectBase = 2 ,
1818 Bindable = 4 ,
1919 NoTrackerRef = 8
2020};
@@ -121,7 +121,7 @@ struct ObservableTraits<T, true, isBindable>
121121 winrt::BindableVectorChangedEventHandler,
122122 winrt::VectorChangedEventHandler<T>
123123 >::type;
124-
124+
125125 using EventToken = typename winrt::event_token;
126126
127127 static void RaiseEvent (EventSource* e, SenderType sender, winrt::CollectionChange collectionChange, uint32_t index)
@@ -223,6 +223,53 @@ struct VectorInnerImpl
223223 return false ;
224224 }
225225
226+ bool IndexOf_OptimizedForRemove (typename T_type const & value, uint32_t & index)
227+ {
228+ index = 0 ;
229+
230+ if (m_optimizedIndexOf_StartNextSearchAt >= m_vector.size ())
231+ {
232+ if (m_vector.size () == 0 )
233+ {
234+ m_optimizedIndexOf_StartNextSearchAt = 0 ;
235+ }
236+ else
237+ {
238+ m_optimizedIndexOf_StartNextSearchAt = static_cast <unsigned int >(m_vector.size () - 1 );
239+ }
240+ }
241+
242+ const auto & searchFor = wrap (value);
243+
244+ auto it = std::find (m_vector.begin () + m_optimizedIndexOf_StartNextSearchAt, m_vector.end (), searchFor);
245+ bool found = (it != m_vector.end ());
246+ if (!found && m_optimizedIndexOf_StartNextSearchAt > 0 )
247+ {
248+ // Note: if std::find fails, it returns the second iterator and not always the end of the vector.
249+ it = std::find (m_vector.begin (), m_vector.begin () + m_optimizedIndexOf_StartNextSearchAt, searchFor);
250+ found = (it != m_vector.begin () + m_optimizedIndexOf_StartNextSearchAt);
251+ }
252+
253+ if (found)
254+ {
255+ index = (uint32_t )(it - m_vector.begin ());
256+ if (index > 0 )
257+ {
258+ // Next time we search start near this index. We assume that the remove is being processed from the end
259+ // of the vector towards index 0, so the next item being removed is probably at "index - 1". Start the
260+ // next search there.
261+ m_optimizedIndexOf_StartNextSearchAt = index - 1 ;
262+ }
263+ else
264+ {
265+ m_optimizedIndexOf_StartNextSearchAt = index;
266+ }
267+ return true ;
268+ }
269+
270+ return false ;
271+ }
272+
226273 uint32_t GetMany (uint32_t const startIndex, winrt::array_view<T_type> values)
227274 {
228275 if (startIndex >= m_vector.size ())
@@ -255,7 +302,7 @@ struct VectorInnerImpl
255302 {
256303 throw winrt::hresult_out_of_bounds ();
257304 }
258-
305+
259306 }
260307
261308 void RemoveAt (uint32_t const index)
@@ -313,6 +360,35 @@ struct VectorInnerImpl
313360
314361 std::vector<T_Storage> m_vector;
315362 ITrackerHandleManager* m_trackerHandleManager{ nullptr };
363+
364+ //
365+ // IndexOf optimization for scenarios where lots of items are removed from a large vector, such as unchecking a
366+ // selected subtree in a large TreeView
367+ //
368+ // e.g. A TreeView has a single root with 10 children, each with 10,000 children inside them. The entire TreeView
369+ // is selected, and the user unchecks one of the 10 first-level children. The SelectedItems vector of the TreeView
370+ // has all 100,000 (roughly - it includes the parent nodes too) items, and we're going to remove 10,000. TreeView's
371+ // deselection logic makes sure an item is selected before removing it from the SelectedItems vector, and it does
372+ // this with an IndexOf. So naively, unchecking one top-level child is going to run 10,000 IndexOf operations into
373+ // a vector of 100,000. If the child being unchecked is near the end of the TreeView, then its entries are near the
374+ // end of the vector of 100,000, and each IndexOf will have to search linearly to the end of the vector, making the
375+ // operation extremely slow.
376+ //
377+ // Instead, we optimize by starting the search not at index 0 but partway through the list, specifically near where
378+ // we found a previous hit. The assumption is that all 10,000 items being unchecked were checked when the top-level
379+ // child was checked, so they're all next to each other in the SelectedItems vector, and we can find them quickly
380+ // if we start near where we found the previous hit.
381+ //
382+ // Note that it's _near_ the previous hit and not _at_ the previous hit because of another optimization. Removing
383+ // 10,000 items means shuffling the vector 10,000 times, which is also a slow operation. The last item being removed
384+ // is going to be shuffled up 9,999 times only to be deleted in the end. To avoid wasted shuffling, we iterate the
385+ // child collection backwards when deselecting. But this also means IndexOf can't start the next search at the same
386+ // index but rather at that index - 1. In this example, item 89,999 was just removed, and next we're going to remove
387+ // item 89,998. Starting the next search at index 89,999 actualy has the worst performance, because it guarantees that
388+ // we're going to search linearly from 89,999 to the end of the vector, then wrap around at the beginning and search
389+ // through every item in the vector before finding our hit at the last possible index 89,998.
390+ //
391+ unsigned int m_optimizedIndexOf_StartNextSearchAt = 0 ;
316392};
317393
318394// Vector Inner implementation with Observable function
@@ -341,7 +417,7 @@ struct ObservableVectorInnerImpl:
341417 Traits::RaiseEvent (m_pIVectorExternal->GetVectorEventSource (), sender, collectionChange, index);
342418 }
343419 }
344-
420+
345421 winrt::event_token AddEventHandler (EventHandler const & handler)
346422 {
347423 return Traits::AddEventHandler (m_pIVectorExternal->GetVectorEventSource (), handler);
@@ -389,7 +465,7 @@ struct ComposableBasePointersImplTType
389465 using WinRTBaseFactoryInterface = typename winrt::IDependencyObjectFactory;
390466};
391467
392- template <>
468+ template <>
393469struct ComposableBasePointersImplTType <false >
394470{
395471 using WinRTBase = typename winrt::IInspectable;
@@ -417,7 +493,7 @@ struct VectorOptionsBase: VectorInterfaceHelper<T, isBindable>, ComposableBasePo
417493
418494template <typename T, bool isObservable, bool isBindable, bool isDependencyObjectBase, bool isNoTrackerRef>
419495struct VectorOptions : VectorOptionsBase<T, isObservable, isBindable, isDependencyObjectBase, isNoTrackerRef>
420- {
496+ {
421497};
422498
423499template <typename T, bool isObservable, bool isDependencyObjectBase, bool isNoTrackerRef>
@@ -532,7 +608,7 @@ struct VectorOptionsFromFlag :
532608 } \
533609 private:
534610
535- // Implement IVectorOwner, also define the Inner Vector and Event Source
611+ // Implement IVectorOwner, also define the Inner Vector and Event Source
536612#define Implement_Vector_External (Options ) \
537613 private: \
538614 using VectorInnerType = ObservableVectorInnerImpl<##Options##>; \
@@ -552,22 +628,22 @@ struct VectorOptionsFromFlag :
552628 Implement_IVector_Modify_Functions(##Options##) \
553629 Implement_IIterator(##Options##) \
554630 Implement_IObservable(##Options##) \
555- Implement_Vector_External(##Options##)
631+ Implement_Vector_External(##Options##)
556632
557633// Implement all interfaces for IXXVector/IXXIterator/IXXObservable except those which will modify the vector
558634// Like TreeViewNode, we need do additional work before Add/Remove/Modify the vector
559635#define Implement_Vector_Read (Options ) \
560636 Implement_IVector_Read_Functions (##Options##) \
561637 Implement_IIterator(##Options##) \
562638 Implement_IObservable(##Options##) \
563- Implement_Vector_External(##Options##)
639+ Implement_Vector_External(##Options##)
564640
565641// Implement all interfaces for IXXVector/IXXIterator/IXXObservable except those which will modify the vector
566642// Like TreeViewNode, we need do additional work before Add/Remove/Modify the vector
567643#define Implement_Vector_Read_NoObservable (Options ) \
568644 Implement_IVector_Read_Functions (##Options##) \
569645 Implement_IIterator(##Options##) \
570- Implement_Vector_External(##Options##)
646+ Implement_Vector_External(##Options##)
571647
572648
573649template <typename T, bool isObservable, bool isBindable, bool isDependencyObjectBase, bool isNoTrackerRef, typename Options = VectorOptions<T, isObservable, isBindable, isDependencyObjectBase, isNoTrackerRef>>
@@ -602,8 +678,8 @@ class VectorBase :
602678};
603679
604680
605- template <typename T,
606- int flags = MakeVectorParam<VectorFlag::Observable, VectorFlag::DependencyObjectBase>(),
681+ template <typename T,
682+ int flags = MakeVectorParam<VectorFlag::Observable, VectorFlag::DependencyObjectBase>(),
607683 typename Helper = VectorFlagHelper<flags>>
608684class Vector :
609685 public VectorBase<T, Helper::isObservable, Helper::isBindable, Helper::isDependencyObjectBase, Helper::isNoTrackerRef>
@@ -613,18 +689,18 @@ class Vector :
613689 Vector (uint32_t capacity) : VectorBase<T, Helper::isObservable, Helper::isBindable, Helper::isDependencyObjectBase, Helper::isNoTrackerRef>(capacity) {}
614690
615691 // The same copy of data for NavigationView split into two parts in top navigationview. So two or more vectors are created to provide multiple datasource for controls.
616- // InspectingDataSource is converting C# collections to Vector<winrt::IInspectable>. When GetAt(index) for things like string, a new IInspectable is always returned by C# projection.
692+ // InspectingDataSource is converting C# collections to Vector<winrt::IInspectable>. When GetAt(index) for things like string, a new IInspectable is always returned by C# projection.
617693 // ListView use indexOf for selection, so a copied/filtered view of C# collection doesn't work for SelectedItem(s) anymore because IInspectable comparsion always return false.
618- // As a workaround, the copied/filtered vector requires others help to IndexOf the orignial C# collection.
694+ // As a workaround, the copied/filtered vector requires others help to IndexOf the orignial C# collection.
619695 // So the comparison is done by C# vector other than Inspectable directly comparision. Here is an example:
620- // Raw data A is: Home-Apps-Music-Sports
696+ // Raw data A is: Home-Apps-Music-Sports
621697 // data is splitted two vectors: B and C. B includes Homes, and C includes Apps-Music-Sports
622698 // Music is the selected item. SplitDataSource is the class help to manage the raw data and provides splitted vectors to ListViews
623699 // ListView call C.indexOf("Music")
624700 // C ask SplitDataSource.IndexOf
625701 // SplitDataSource calls A.IndexOf (C# provided it)
626702 // SpiltDataSource help to convert the indexInRawData to indexInC
627- // return index in C
703+ // return index in C
628704 Vector (std::function<int (typename T const & value)> indexOfFunction) : m_indexOfFunction(indexOfFunction)
629705 {
630706 if (m_indexOfFunction)
0 commit comments