Asynchronous loading and lookup
v1 introduce asynchronous loading by using the AsyncObservableCollection, which load the entries (in a linq statement) in background thread, and notify UI thread to add or remove an item in UI thread. This is required because UI thread shouldn't be blocked by a slow operation, and the UI code fails if an UI object is changed by a non-UI thread.

v2 uses the same loading mechanism too, but they use a different mechanism to find and select an entry.

To lookup an entry, v1 create a new thread (backgroundworker) to load the appropriate entries down the hierarchy tree, till it found the requested entry.

(DirTreeViewModel.LookupChild() method)
public override TreeViewItemViewModel LookupChild(IFileSystemInfoExA node, Func cancelCheck)
{
    ...
    foreach (TreeViewItemViewModel subModel in tvm)
    {
        if (subModel.Equals(node))
            return subModel;
        else
            if (subModel is DirTreeViewModel)
            {
                DirTreeViewModel subDirModel = subModel as DirTreeViewModel;

                IDirectoryInfoExA subDir = subDirModel.EmbeddedDirModel.EmbeddedDirEntry;
                if (node.Equals(subDir))
                    return subDirModel;
                else if (subDirModel.CanLookUp && COFETools.HasParent(node, subDir))
                    return subDirModel.LookupChild(node, cancelCheck);
            }
	...
}
The problem is that:

Because the lookup is done synchronously, only one lookup can be run at a time, and the directory tree may look freezing while lookuping.
Only one level of hierarchy can be processed at a time, so if a directory contains 2048 sub-directories, it will continues to load the next 2047 sub-directories even if the requested entry is under the first sub-directory, before continue the lookup in the first sub-directory.

Bounty system
v2 solved the problem by reversing the process, the root model now contains a property named Bounty, which represents the entry requested to be selected, a PlaceBounty() method is available to set this property:
public virtual void PlaceBounty(EntryModel bountyModel)
{
    if (bountyModel is DirectoryModel)
    {

        if (SelectedNavViewModel != null &&
            SelectedNavViewModel.EmbeddedDirModel.EmbeddedDir.Equals(bountyModel))
        {
            //False alarm, already Selected
            SelectedNavViewModel.IsSelected = true;
            return;
        }
        else
            if (SelectedNavViewModel != null &&
            SelectedNavViewModel.EmbeddedDirModel.HasChild(bountyModel.EmbeddedEntry))
            {
                //Fast mode, item is subentry of current selected
                Bounty = (DirectoryModel)bountyModel;
                SelectedNavViewModel.IsSelected = false;
                SelectedNavViewModel.PlaceBounty();

            }
            else
            {
                //Slow mode, iterate from root
                if (SelectedNavViewModel != null)
                    SelectedNavViewModel.IsSelected = false;

                Bounty = (DirectoryModel)bountyModel;

                foreach (NavigationItemViewModel subDir in
                    _subDirectories.OrderBy((nivm) => { return -nivm.EmbeddedEntryModel.CustomPosition; }))
                    if (subDir.EmbeddedDirModel.EqualsOrHasChild(bountyModel))
                    {
                        subDir.PlaceBounty();
                        break;
                    }
                    else subDir.CollapseAll();
            }
    }
}
The subdir's PlaceBouny() method notify them a new bounty is available, so they will perform a lookup:
public void PlaceBounty()
{
    if (_rootModel == null || _rootModel.Bounty == null || !IsDirectory || _isSeparator)
        return;

    if (EmbeddedDirModel.EqualsOrHasChild(_rootModel.Bounty))
    {
        if (EmbeddedDirModel.Equals(_rootModel.Bounty))
        {
            //Exchange bounty if cached.
            if (!EmbeddedDirModel.IsCached && _rootModel.Bounty.IsCached)
                _embeddedDirModel = _rootModel.Bounty;
            _rootModel.RequestBountyReward(this);
        }
        else
        {
            IsSelected = false;
            if (_isInited)
            {
                IsExpanded = true;
                foreach (NavigationItemViewModel subDirVM in
                    _subDirectories.OrderBy((nivm) 
                     => { return -nivm.EmbeddedEntryModel.CustomPosition; }))
                if (subDirVM.EmbeddedDirModel.EqualsOrHasChild(_rootModel.Bounty))
                    {
                        subDirVM.PlaceBounty(); //Tell sub-directory bounty is up.
                        break;
                    }
                    else subDirVM.CollapseAll(); //NOYB, collapse!
            }
            else
            {
                //Let greed does all the work....
                IsExpanded = true;
            }
        }
    }
    else IsSelected = false;
}

Notice that, instead of loading the subentries, it change the IsExpanded property (in background thread, which is bound to the UI), which will trigger the subdirectory in UI to be expanded. UI thread(not the background thread) will then load sub-folders, and continue looking up for bounty.

When an entry figured out that it's the bounty, it calls RootModel's RequestBountyReward(this) method to notify the requested entry is found, so RootModel can clear the Bounty.

So instead of using a thread to perform a lookup from top to bottom of hierarchy, expanding them manually, v2 allows each level of child item to use their IsExpanded property to locate the entry, and thread issue is avoided.

Last edited Nov 25, 2012 at 10:41 AM by lycj, version 2

Comments

No comments yet.