/// <summary>
        /// Fired by the memory manager when we have memory pressure.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private async void MemoryMan_OnMemoryCleanUpRequest(object sender, OnMemoryCleanupRequestArgs e)
        {
            // Only care if we are medium or higher
            if(e.CurrentPressure >= MemoryPressureStates.Medium)
            {
                // A list of panels to destroy and hosts to tell.
                List<Tuple<IContentPanelBase, IContentPanelHost>> destroyList = new List<Tuple<IContentPanelBase, IContentPanelHost>>();

                lock(m_currentPanelList)
                {
                    // Loop through the list and see if we have any large panels.
                    foreach(KeyValuePair<string, ContentListElement> pair in m_currentPanelList)
                    {
                        // Make sure we have a panel.
                        if(pair.Value.PanelBase == null)
                        {
                            continue;
                        }

                        bool destroyPanel = false;

                        if(pair.Value.IsVisible)
                        { 
                            // If we are visible only destroy the panel if we are at high memory pressure
                            if (e.CurrentPressure == MemoryPressureStates.HighNoAllocations &&
                                pair.Value.PanelBase.PanelMemorySize > PanelMemorySizes.Small)
                            {
                                destroyPanel = true;
                            }                        
                        }
                        else
                        {
                            // The memory pressure is medium or high. If it is hidden and larger 
                            // than small destroy it.
                            if (pair.Value.PanelBase.PanelMemorySize > PanelMemorySizes.Small)
                            {
                                destroyPanel = true;
                            }
                        }

                        // If we need to destroy
                        if(destroyPanel)
                        {
                            // Add to our list.
                            destroyList.Add(new Tuple<IContentPanelBase, IContentPanelHost>(pair.Value.PanelBase, pair.Value.Host));

                            // Null the panel
                            pair.Value.PanelBase = null;

                            // Update the state
                            pair.Value.State = ContentState.Unloaded;
                        }
                    }
                }

                // Now out of lock, kill the panels.
                foreach(Tuple<IContentPanelBase, IContentPanelHost> tuple in destroyList)
                {
                    // If we have a host...
                    if (tuple.Item2 != null)
                    {
                        // Remove from the host
                        await FireOnRemovePanel(tuple.Item2, tuple.Item1);

                        // Tell the panel it was unloaded.
                        FireOnPanelUnloaded(tuple.Item2);
                    }

                    // And Destroy the panel
                    await FireOnDestroyContent(tuple.Item1);
                }
            }
        }
Exemple #2
0
        /// <summary>
        /// Fired when the memory manager wants some memory back. Lets see if we can help him out.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private async void MemoryMan_OnMemoryCleanUpRequest(object sender, BaconBackend.Managers.OnMemoryCleanupRequestArgs e)
        {
            // Memory pressure
            //  Low - Only clean up pages that are very old and user forgot about
            //  Medium - Clean up pages that are older and the user hopefully forgot about
            //  High - Clean up just about everything we can.

            List <IPanel> cleanupPanelList = new List <IPanel>();

            lock (m_panelStack)
            {
                if (m_state != State.Idle && e.CurrentPressure != MemoryPressureStates.HighNoAllocations)
                {
                    // If we are not idle return unless we are high, then
                    // we will take our chances.
                    return;
                }

                // Figure out how many panels we need to keep.
                int minNumHistoryPages = 0;
                switch (e.CurrentPressure)
                {
                default:
                case MemoryPressureStates.None:
                case MemoryPressureStates.VeryLow:
                    minNumHistoryPages = c_veryLowMemoryHistoryPagesLimit;
                    break;

                case MemoryPressureStates.Low:
                    minNumHistoryPages = c_lowMemoryHistoryPagesLimit;
                    break;

                case MemoryPressureStates.Medium:
                    minNumHistoryPages = c_mediumMemoryHistoryPagesLimit;
                    break;

                case MemoryPressureStates.HighNoAllocations:
                    minNumHistoryPages = c_highMemoryHistoryPagesLimit;
                    break;
                }

                // Do a quick check to ensure we can do work.
                // Panel count - 4 (worse case we have 4 we have to keep) is the worse case # of history pages. (this can be (-))
                // If that count is less then the min just return because we have nothing to do.
                if (m_panelStack.Count - 4 < minNumHistoryPages)
                {
                    return;
                }

                // First find the most recent subreddit panel and the panel we are looking at.
                // If we are in split view we will always have two, if in single view we will only have one.
                Tuple <Type, string> currentPanel               = null;
                PanelType            currentPanelPanelType      = PanelType.None;
                Tuple <Type, string> splitViewOtherCurrentPanel = null;
                for (int count = m_panelStack.Count - 1; count > 0; count--)
                {
                    StackItem item = m_panelStack[count];

                    // If this is first this is what we are looking at.
                    if (currentPanel == null)
                    {
                        currentPanel          = new Tuple <Type, string>(item.Panel.GetType(), item.Id);
                        currentPanelPanelType = GetPanelType(item.Panel);

                        // If we are in single mode we only care about the first one.
                        if (m_screenMode == ScreenMode.Single)
                        {
                            break;
                        }
                    }
                    else
                    {
                        // If we get here we are in split view and are looking for the latest panel that is not the same type as the current panel.
                        if (GetPanelType(item.Panel) != currentPanelPanelType)
                        {
                            splitViewOtherCurrentPanel = new Tuple <Type, string>(item.Panel.GetType(), item.Id);
                            break;
                        }
                    }
                }

                // Check that we are good.
                if (currentPanel == null)
                {
                    App.BaconMan.TelemetryMan.ReportUnExpectedEvent(this, "MemoryCleanupCurrentPanelNull");
                    return;
                }

                // We will always keep the welcome screen, the first subreddit.
                // We also might need to keep the two visible panels, but they might also be the same.
                int numRequiredPages = 2;

                // Now loop though all of the panels and kill them.
                for (int count = 0; count < m_panelStack.Count; count++)
                {
                    StackItem item = m_panelStack[count];
                    if ((item.Panel as WelcomePanel) != null)
                    {
                        // This is the welcome panel, skip
                        continue;
                    }
                    else if (count == 1 && (item.Panel as SubredditPanel) != null)
                    {
                        // If this subreddit isn't the first visible panel we have one more required page.
                        if (!item.Id.Equals(currentPanel.Item2) || !item.Panel.GetType().Equals(currentPanel.Item1))
                        {
                            numRequiredPages++;
                        }

                        // If this subreddit also isn't the second visible panel we have one more required page.
                        if (splitViewOtherCurrentPanel != null && (!item.Id.Equals(splitViewOtherCurrentPanel.Item2) || !item.Panel.GetType().Equals(splitViewOtherCurrentPanel.Item1)))
                        {
                            numRequiredPages++;
                        }

                        // Skip the first subreddit panel also.
                        continue;
                    }

                    // Check if we are done, if our list is smaller than req + min history.
                    if (m_panelStack.Count <= minNumHistoryPages + numRequiredPages)
                    {
                        break;
                    }

                    if (item.Id.Equals(currentPanel.Item2) && item.Panel.GetType().Equals(currentPanel.Item1))
                    {
                        // If this is visible panel 1 don't kill it.
                        continue;
                    }

                    if (splitViewOtherCurrentPanel != null && item.Id.Equals(splitViewOtherCurrentPanel.Item2) && item.Panel.GetType().Equals(splitViewOtherCurrentPanel.Item1))
                    {
                        // If this is visible panel 2 don't kill it.
                        continue;
                    }

                    // We found one we can kill from the list and back down the count.
                    m_panelStack.RemoveAt(count);
                    count--;

                    // Now make sure there isn't another instance of it in the list, if so don't add
                    // it to the clean up list.
                    bool addToCleanUp = true;
                    foreach (StackItem searchItem in m_panelStack)
                    {
                        if (item.Id.Equals(searchItem.Id) && item.Panel.GetType().Equals(searchItem.Panel.GetType()))
                        {
                            addToCleanUp = false;
                            break;
                        }
                    }

                    if (addToCleanUp)
                    {
                        cleanupPanelList.Add(item.Panel);
                    }
                }
            }

            // Now that we are out of lock, delete the panels if we have any.
            if (cleanupPanelList.Count > 0)
            {
                // Keep track of how many pages have been cleaned up.
                App.BaconMan.UiSettingsMan.PagesMemoryCleanedUp += cleanupPanelList.Count;

                // Jump to the UI thread to do this.
                await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
                {
                    foreach (IPanel panel in cleanupPanelList)
                    {
                        // Fire cleanup on the panel
                        FireOnCleanupPanel(panel);
                    }

                    // Also update the back button.
                    UpdateBackButton();
                });
            }
        }