/// <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); } } }
/// <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(); }); } }