using Beefy.widgets; using Beefy.theme.dark; using Beefy.gfx; using System.Collections; using System; using Beefy.events; using Beefy; using System.Threading; using System.Diagnostics; using Beefy.utils; namespace BeefPerf { class FindPanel : Widget { public class FindListViewItem : DarkVirtualListViewItem { public override void Draw(Graphics g) { base.Draw(g); } public override void DrawAll(Graphics g) { base.DrawAll(g); } public override bool Selected { set { if (value) { var findPanel = ((FindListView)mListView).mFindPanel; findPanel.ItemSelected(this); } base.Selected = value; } } } public class FindListView : DarkVirtualListView { public FindPanel mFindPanel; public bool mIconsDirty; public this(FindPanel findPanel) { mFindPanel = findPanel; } protected override ListViewItem CreateListViewItem() { mIconsDirty = true; return new FindListViewItem(); } public override void PopulateVirtualItem(DarkVirtualListViewItem listViewItem) { base.PopulateVirtualItem(listViewItem); listViewItem.mOnMouseDown.Add(new => mFindPanel.ValueClicked); if (listViewItem.mVirtualIdx >= mFindPanel.mSearchState.mFoundEntries.Count) { listViewItem.mLabel = new String("--- Results Trimmed ---"); listViewItem.mTextColor = 0xFF808080; var subItem = listViewItem.CreateSubItem(1); subItem.mLabel = new String(); subItem = listViewItem.CreateSubItem(2); subItem.mLabel = new String(); return; } var client = mFindPanel.PerfView.mSession; var foundEntry = mFindPanel.mSearchState.mFoundEntries[listViewItem.mVirtualIdx]; listViewItem.mLabel = new String(foundEntry.mZoneStr); var subItem = listViewItem.CreateSubItem(1); subItem.mOnMouseDown.Add(new => mFindPanel.ValueClicked); subItem.mLabel = new String(); BpClient.TimeToStr(client.TicksToUS(foundEntry.mStartTick - client.mFirstTick), subItem.mLabel); subItem = listViewItem.CreateSubItem(2); subItem.mOnMouseDown.Add(new => mFindPanel.ValueClicked); subItem.mLabel = new String(); if (foundEntry.mEndTick != 0) client.ElapsedTicksToStr(foundEntry.mEndTick - foundEntry.mStartTick, subItem.mLabel); var track = mFindPanel.PerfView.mSession.mThreads[foundEntry.mTrackIdx]; subItem = listViewItem.CreateSubItem(3); subItem.mOnMouseDown.Add(new => mFindPanel.ValueClicked); subItem.mLabel = new String(); track.GetName(subItem.mLabel); } public void UpdateItem(FindListViewItem listViewItem) { if (listViewItem.mVirtualIdx >= mFindPanel.mSearchState.mFoundEntries.Count) { listViewItem.mLabel.Set("???"); return; } var client = mFindPanel.PerfView.mSession; var foundEntry = mFindPanel.mSearchState.mFoundEntries[listViewItem.mVirtualIdx]; listViewItem.mLabel.Set(foundEntry.mZoneStr); var subItem = listViewItem.GetSubItem(1); subItem.mLabel.Clear(); BpClient.TimeToStr(client.TicksToUS(foundEntry.mStartTick - client.mFirstTick), subItem.mLabel); subItem = listViewItem.GetSubItem(2); subItem.mLabel.Clear(); if (foundEntry.mEndTick == 0) subItem.mLabel.Clear(); else client.ElapsedTicksToStr(foundEntry.mEndTick - foundEntry.mStartTick, subItem.mLabel); } public override void ChangeSort(SortType sortType) { mSortType = sortType; if (mFindPanel.mSearchState == null) return; if (mFindPanel.mSearchState.mTrimmedResults) mFindPanel.mNeedsRehup = true; else mFindPanel.mNeedsResort = true; } public override void KeyDown(KeyCode keyCode, bool isRepeat) { base.KeyDown(keyCode, isRepeat); if (keyCode == (KeyCode)'D') { for (int32 i < (int32)mFindPanel.mSearchState.mFoundEntries.Count) { //Debug.WriteLine("{0} {1}", i, mFindPanel.mSearchState.mFoundEntries[i].mZoneStr); } } } } struct TrackLoc : IHashable { public int64 mTick; public int32 mDepth; public this(int64 tick, int32 depth) { mTick = tick; mDepth = depth; } public int GetHashCode() { return (int)mTick; } } class FindEntry { public int64 mStartTick; public int64 mEndTick; public int32 mDepth; public String mZoneStr ~ delete _; public int32 mTrackIdx; public bool mTimeDirty; public bool mInDictionary; } class TrackState { public int64 mLastEntryEndTick; public int64 mPrevLastEntryEndTick; public int64 mPrevEndTick; public Dictionary mFindPositions = new Dictionary() ~ delete _; } class SearchState { public int32 mCurThreadIdx; public int32 mCurThreadStreamIdx = -1; public TextSearcher mTextSearch = new TextSearcher() ~ delete _; public TextSearcher mTrackSearch = new TextSearcher() ~ delete _; public int64 mStartTick; public int64 mEndTick; public int32 mMinDepth; public bool mIncludeZones; public bool mIncludeEvents; public List mFoundEntries = new List() ~ DeleteContainerAndItems!(_); public List mSortedEntries ~ delete _; public List mPendingDeleteEntries = new List() ~ DeleteContainerAndItems!(_); public FindEntry mHighestEntry; public List mTrackStates = new List() ~ DeleteContainerAndItems!(_); public bool mTrimmedResults; } FindListView mListView; bool mSelectionDirty; public DarkEditWidget mEntryEdit; public DarkComboBox mTrackEdit; public DarkComboBox mTimeFromEdit; public DarkComboBox mTimeToEdit; public DarkCheckBox mFormatCheckbox; public DarkCheckBox mZonesCheckbox; public DarkCheckBox mEventsCheckbox; SearchState mSearchState ~ delete _; public bool mNeedsRestartSearch; public bool mNeedsRehup; public bool mNeedsResort; int32 mMaxFoundEntries = 100000; bool mSorting; WaitEvent mSortDoneHandle = new WaitEvent() ~ delete _; bool mSearchIncomplete; const int cColumnName = 0; const int cColumnStart = 1; const int cColumnLength = 2; const int cColumnTrack = 2; int mDrawWaitCount = 0; // We draw when this is > 0 bool mFailed; public this() { mEntryEdit = new DarkEditWidget(); AddWidget(mEntryEdit); mEntryEdit.mOnKeyDown.Add(new => KeyDownHandler); mEntryEdit.mOnContentChanged.Add(new => FindContentChanged); mTrackEdit = new DarkComboBox(); mTrackEdit.MakeEditable(); AddWidget(mTrackEdit); mTrackEdit.mEditWidget.mOnContentChanged.Add(new => FindContentChanged); mTrackEdit.mPopulateMenuAction.Add(new => PopulateTrackList); mTimeFromEdit = new DarkComboBox(); mTimeFromEdit.MakeEditable(); AddWidget(mTimeFromEdit); mTimeFromEdit.mEditWidget.mOnContentChanged.Add(new => FindContentChanged); mTimeFromEdit.mPopulateMenuAction.Add(new => PopulateTimeFrom); mTimeToEdit = new DarkComboBox(); mTimeToEdit.MakeEditable(); AddWidget(mTimeToEdit); mTimeToEdit.mEditWidget.mOnContentChanged.Add(new => FindContentChanged); mTimeToEdit.mPopulateMenuAction.Add(new => PopulateTimeTo); mFormatCheckbox = new DarkCheckBox(); AddWidget(mFormatCheckbox); mFormatCheckbox.Checked = true; mFormatCheckbox.Label = "Format Strings"; mFormatCheckbox.mOnMouseUp.Add(new (evt) => { FindOptionsChanged(); }); mZonesCheckbox = new DarkCheckBox(); AddWidget(mZonesCheckbox); mZonesCheckbox.Checked = true; mZonesCheckbox.Label = "Zones"; mZonesCheckbox.mOnMouseUp.Add(new (evt) => { FindOptionsChanged(); }); mEventsCheckbox = new DarkCheckBox(); mEventsCheckbox.Checked = true; mEventsCheckbox.Label = "Events"; AddWidget(mEventsCheckbox); mEventsCheckbox.mOnMouseUp.Add(new (evt) => { FindOptionsChanged(); }); mListView = new FindListView(this); mListView.mOnLostFocus.Add(new (evt) => mListView.GetRoot().SelectItemExclusively(null)); mListView.mSortType.mColumn = 2; mListView.mSortType.mReverse = true; AddWidget(mListView); mListView.AddColumn(200, "Name"); mListView.AddColumn(150, "Start"); mListView.AddColumn(150, "Length"); mListView.AddColumn(150, "Track"); mListView.InitScrollbars(false, true); } public ~this() { FinishSorting(); } PerfView PerfView { get { return gApp.mBoard.mPerfView; } } void PopulateTrackList(Menu menu) { var perfView = PerfView; List threadNames = scope List(); for (var thread in perfView.mSession.mThreads) { var threadName = new String(); thread.GetName(threadName); threadNames.Add(threadName); } threadNames.Sort(scope (lhs, rhs) => String.Compare(lhs, rhs, true)); for (var threadName in threadNames) { #unwarn var menuItem = menu.AddItem(threadName); menuItem.mOnMenuItemSelected.Add(new (item) => { var str = scope String(); str.Append('='); str.Append(((Menu)item).mLabel); if (str.Contains(' ')) { str.Insert(1, '\"'); str.Append('"'); } mTrackEdit.mEditWidget.SetText(str); }); } ClearAndDeleteItems(threadNames); } void SetVisibleLeft() { var perfView = PerfView; var str = scope String(); perfView.mSession.TicksToStr(perfView.mTickOffset, str); mTimeFromEdit.mEditWidget.SetText(str); } void SetVisibleRight() { var perfView = PerfView; var str = scope String(); perfView.mSession.TicksToStr(perfView.mTickOffset + (int64)(perfView.mWidth / perfView.mTickScale), str); mTimeToEdit.mEditWidget.SetText(str); } void SetEndRight() { //var perfView = PerfView; //var str = scope String(); //perfView.mClient.ElapsedTicksToStr(perfView.mClient.mCurTick - perfView.mClient.mFirstTick, str); //mTimeToEdit.mEditWidget.SetText(str); mTimeToEdit.mEditWidget.SetText(""); } void PopulateTimeFrom(Menu menu) { var menuItem = menu.AddItem("Start"); menuItem.mOnMenuItemSelected.Add(new (item) => { mTimeFromEdit.mEditWidget.SetText("0s"); }); menuItem = menu.AddItem("Entire Range"); menuItem.mOnMenuItemSelected.Add(new (item) => { mTimeFromEdit.mEditWidget.SetText("0s"); SetEndRight(); }); menuItem = menu.AddItem("Visible Left"); menuItem.mOnMenuItemSelected.Add(new (item) => { SetVisibleLeft(); }); menuItem = menu.AddItem("Visible Range"); menuItem.mOnMenuItemSelected.Add(new (item) => { SetVisibleLeft(); SetVisibleRight(); }); } void PopulateTimeTo(Menu menu) { var menuItem = menu.AddItem("End"); menuItem.mOnMenuItemSelected.Add(new (item) => { SetEndRight(); }); menuItem = menu.AddItem("Visible Right"); menuItem.mOnMenuItemSelected.Add(new (item) => { SetVisibleRight(); }); } void KeyDownHandler(KeyDownEvent evt) { //if (evt.mKeycode == KeyCode.Escape) //Close(); if (evt.mKeyCode == KeyCode.Return) { //StartSearch(); } } void FindContentChanged(EditEvent editEvent) { mNeedsRestartSearch = true; } void FindOptionsChanged() { mNeedsRestartSearch = true; } public void ItemSelected(FindListViewItem item) { int32 selectedIdx = item.mVirtualIdx; if (selectedIdx >= mSearchState.mFoundEntries.Count) return; // "-- Results Trimmed --" entry var foundEntry = mSearchState.mFoundEntries[selectedIdx]; if (foundEntry.mEndTick == 0) { PerfView.mSelection = .Event(foundEntry.mTrackIdx, foundEntry.mStartTick); } else { BPSelection selection; selection.mTickStart = foundEntry.mStartTick; selection.mTickEnd = foundEntry.mEndTick; selection.mDepth = foundEntry.mDepth; selection.mThreadIdx = foundEntry.mTrackIdx; PerfView.mSelection = .Entry(selection); gApp.mProfilePanel.Show(PerfView, selection); } } public void ValueClicked(MouseEvent theEvent) { DarkVirtualListViewItem clickedItem = (DarkVirtualListViewItem)theEvent.mSender; DarkVirtualListViewItem item = (DarkVirtualListViewItem)clickedItem.GetSubItem(0); mListView.GetRoot().SelectItemExclusively(item); mListView.SetFocus(); if (theEvent.mBtn == 0) { int32 selectedIdx = item.mVirtualIdx; var foundEntry = mSearchState.mFoundEntries[selectedIdx]; if (theEvent.mBtnCount > 1) { PerfView.EnsureVisible(foundEntry.mTrackIdx, foundEntry.mDepth); PerfView.ZoomTo(foundEntry.mStartTick, foundEntry.mEndTick); } if (foundEntry.mEndTick != 0) { BPSelection selection; selection.mTickStart = foundEntry.mStartTick; selection.mTickEnd = foundEntry.mEndTick; selection.mDepth = foundEntry.mDepth; selection.mThreadIdx = foundEntry.mTrackIdx; PerfView.mSelection = .Entry(selection); gApp.mProfilePanel.Show(PerfView, selection); } //IDEApp.sApp.mDebugger.mSelectedCallStackIdx = selectedIdx; //IDEApp.sApp.ShowPCLocation(selectedIdx, false, true); //IDEApp.sApp.StackPositionChanged(); } //UpdateIcons(); } public override void Resize(float x, float y, float width, float height) { base.Resize(x, y, width, height); var font = DarkTheme.sDarkTheme.mSmallFont; String findLabel = "Find"; String trackLabel = "Thread"; float findLabelWidth = font.GetWidth(findLabel) + 27; float trackLabelWidth = font.GetWidth(trackLabel) + 12; float spacingWidth = 8; float availWidth = Math.Max(mWidth - findLabelWidth - spacingWidth - trackLabelWidth, 0); mEntryEdit.Resize(findLabelWidth, 0, availWidth * 0.65f, 20); mTrackEdit.Resize(mEntryEdit.mX + mEntryEdit.mWidth + spacingWidth + trackLabelWidth, 0, availWidth * 0.35f, 20); String timeFromLabel = "Time From"; String timeToLabel = "Time To"; float timeFromLabelWidth = font.GetWidth(timeFromLabel) + 12; float timeToLabelWidth = font.GetWidth(timeToLabel) + 12; float timeEditWidth = 140; mTimeFromEdit.Resize(timeFromLabelWidth, 20, timeEditWidth, 20); mTimeToEdit.Resize(mTimeFromEdit.mX + timeEditWidth + spacingWidth + timeToLabelWidth, 20, timeEditWidth, 20); float formatWidth = font.GetWidth(mFormatCheckbox.mLabel) + 28; float zonesWidth = font.GetWidth(mZonesCheckbox.mLabel) + 28; mFormatCheckbox.Resize(mTimeToEdit.mX + timeEditWidth + 8, 20, 20, 20); mZonesCheckbox.Resize(mFormatCheckbox.mX + formatWidth, 20, 20, 20); mEventsCheckbox.Resize(mZonesCheckbox.mX + zonesWidth, 20, 20, 20); mListView.ResizeClamped(0, 40, width, height - 40); } public override void Draw(Graphics g) { base.Draw(g); //g.DrawBox(DarkTheme.sDarkTheme.GetImage(DarkTheme.ImageIdx.Bkg), 0, 0, mWidth, mHeight); var font = DarkTheme.sDarkTheme.mSmallFont; String findLabel = "Find"; String trackLabel = "Thread"; float findLabelWidth = font.GetWidth(findLabel); float trackLabelWidth = font.GetWidth(trackLabel); g.SetFont(DarkTheme.sDarkTheme.mSmallFont); g.DrawString(findLabel, mEntryEdit.mX - findLabelWidth - 6, 0); g.DrawString(trackLabel, mTrackEdit.mX - trackLabelWidth - 6, 0); String timeFromLabel = "Time From"; String timeToLabel = "Time To"; float timeFromLabelWidth = font.GetWidth(timeFromLabel); float timeToLabelWidth = font.GetWidth(timeToLabel); g.DrawString(timeFromLabel, mTimeFromEdit.mX - timeFromLabelWidth- 6, 20); g.DrawString(timeToLabel, mTimeToEdit.mX - timeToLabelWidth - 6, 20); } public void Show(PerfView perfView) { mSelectionDirty = true; } void GetData() { mListView.GetRoot().Clear(); } bool SearchIn(int32 trackIdx, int32 streamDataIdx) { var client = PerfView.mSession; BpTrack track = client.mThreads[trackIdx]; BpStreamData streamData = track.mStreamDataList[streamDataIdx]; var trackState = mSearchState.mTrackStates[trackIdx]; var curEntryEndTick = ref trackState.mLastEntryEndTick; bool isRecording = false; //bool isFirstDrawn = true; if (mSearchState.mStartTick == 0) isRecording = true; if ((mSearchState.mStartTick != 0) && (streamData.mSplitTick > 0) && (streamData.mSplitTick < mSearchState.mStartTick)) return false; // All data is to the left //if ((mSearchState.mEndTick != 0) && (streamData.mSplitTick < mSearchState.mStartTick)) //return false; // All data is to the right if ((mSearchState.mEndTick != 0) && (streamData.mStartTick > mSearchState.mEndTick)) return true; BPStateContext stateCtx = scope BPStateContext(client, streamData); List entryStack = scope List(); int32 stackDepth = 0; String tempDynStr = scope String(); String tempStr = scope String(); FindEntry tempFindEntry = new FindEntry(); defer { tempFindEntry.mZoneStr = null; delete tempFindEntry; } CmdLoop: while (true) { switch (stateCtx.GetNextEvent()) { case let .Enter(startTick, strIdx): #unwarn int stackPos = stackDepth++; //if ((!isFirstDrawn) && (startTick < streamData.mStartTick)) //continue; // Would have already been handled by a previous streamData if (!mSearchState.mIncludeZones) continue; if ((mSearchState.mStartTick != 0) && (startTick >= mSearchState.mStartTick)) { isRecording = true; } if (isRecording) { BPEntry entry; entry.mStartTick = startTick; entry.mZoneNameId = strIdx; //stateCtx.MoveToParamData(); entry.mParamsReadPos = stateCtx.ReadPos; entryStack.Add(entry); } case let .Leave(endTick): stackDepth--; if (isRecording) { if (entryStack.Count == 0) { continue; } #unwarn let entry = entryStack.PopBack(); #unwarn if (endTick < curEntryEndTick) continue; if ((mSearchState.mEndTick != 0) && (entry.mStartTick > mSearchState.mEndTick)) continue; bool isOld = ((entry.mStartTick <= streamData.mStartTick) && (stackDepth < stateCtx.mSplitCarryoverCount)); if (!stateCtx.mTimeInferred) { if (endTick <= curEntryEndTick) continue; if ((streamData.mSplitTick == 0) || (endTick <= streamData.mSplitTick)) // Don't include entries that span past the end of this stream curEntryEndTick = endTick; if (isOld) continue; } #unwarn int32 stackPos = stackDepth; String str; int32 paramsSize; int32 paramReadPos = entry.mParamsReadPos; if (entry.mZoneNameId < 0) { int32 nameLen = -entry.mZoneNameId; str = tempDynStr; str.Reference((char8*)stateCtx.mReadStart + paramReadPos - nameLen, nameLen, 0); paramsSize = -1; } else { let zoneName = client.mZoneNames[entry.mZoneNameId]; str = zoneName.mName; paramsSize = zoneName.mParamsSize; } if (paramsSize != 0) { tempStr.Clear(); stateCtx.FormatStr(paramReadPos, paramsSize, str, tempStr); str = tempStr; } FindEntry findEntry = tempFindEntry; findEntry.mStartTick = entry.mStartTick; findEntry.mEndTick = endTick; findEntry.mZoneStr = str; findEntry.mTrackIdx = trackIdx; findEntry.mDepth = stackDepth; bool isOverflow = mSearchState.mFoundEntries.Count >= mMaxFoundEntries; if (isOverflow) { // Can we be sure this entry wouldn't be included in the list after sorting and trimming? if (EntryCompare(findEntry, mSearchState.mHighestEntry) >= 0) { continue; } } if (mSearchState.mTextSearch.Matches(str)) { FindEntry* findEntryPtr = null; if ((endTick >= client.mCurTick) || // If it extends off the back (isOld) || (entry.mStartTick <= trackState.mPrevLastEntryEndTick)) // Was already started on the previous update { TrackLoc* trackLoc; if (!trackState.mFindPositions.TryAdd(TrackLoc(entry.mStartTick, stackDepth), out trackLoc, out findEntryPtr)) { findEntry = *findEntryPtr; findEntry.mEndTick = endTick; findEntry.mTimeDirty = true; continue; } if (isOld) continue; } findEntry.mZoneStr = new String(findEntry.mZoneStr); mSearchState.mFoundEntries.Add(findEntry); if (!isOverflow) { if ((mSearchState.mHighestEntry == null) || (EntryCompare(findEntry, mSearchState.mHighestEntry) >= 0)) { // We have a new "highest entry" mSearchState.mHighestEntry = findEntry; } } // Create a pending entry... tempFindEntry = new FindEntry(); if (findEntryPtr != null) { findEntry.mInDictionary = true; *findEntryPtr = findEntry; } } } case let .Event(tick, name, details): if (tick <= curEntryEndTick) continue; if (!mSearchState.mIncludeEvents) continue; if ((mSearchState.mStartTick != 0) && (tick < mSearchState.mStartTick)) continue; if ((mSearchState.mEndTick != 0) && (tick > mSearchState.mEndTick)) continue; curEntryEndTick = tick; var nameStr = scope String(); nameStr.Reference(name); var detailsStr = scope String(); detailsStr.Reference(details); if ((mSearchState.mTextSearch.Matches(nameStr)) || (mSearchState.mTextSearch.Matches(detailsStr))) { let maxDetailChars = 128; let strLen = nameStr.Length + " : ".Length + Math.Min(detailsStr.Length, maxDetailChars + 3); int numDetailChars = Math.Min(detailsStr.Length, maxDetailChars); var foundStr = new String(strLen); foundStr.Append(nameStr, " : "); for (int i < numDetailChars) { char8 c = details[i]; if ((int)c < 32) foundStr.Append(' '); else foundStr.Append(c); } if (detailsStr.Length > maxDetailChars) { foundStr.Append("..."); } var findEntry = new FindEntry(); findEntry.mZoneStr = foundStr; findEntry.mStartTick = tick; findEntry.mTrackIdx = trackIdx; findEntry.mDepth = -1; mSearchState.mFoundEntries.Add(findEntry); } case .EndOfStream: break CmdLoop; default: } } //isFirstDrawn = false; return true; } void StartSearch() { //Debug.WriteLine("StartSearch"); mListView.GetRoot().Clear(); var error = scope String(); Clear(); var perfView = PerfView; if (perfView == null) return; bool hadError = true; do { var client = perfView.mSession; mSearchState = new SearchState(); var str = scope String(); mEntryEdit.GetText(str); if (mSearchState.mTextSearch.Init(str, error) case .Err) break; str.Clear(); mTrackEdit.mEditWidget.GetText(str); if (mSearchState.mTrackSearch.Init(str, error) case .Err) break; str.Clear(); mTimeFromEdit.mEditWidget.GetText(str); if (!str.IsWhiteSpace) { if (client.ParseTime(str) case .Ok(let ticks)) mSearchState.mStartTick = ticks; else { error.Set("Invalid 'Time From' value"); break; } } str.Clear(); mTimeToEdit.mEditWidget.GetText(str); if (!str.IsWhiteSpace) { if (client.ParseTime(str) case .Ok(let ticks)) mSearchState.mEndTick = ticks; else { error.Set("Invalid 'Time To' value"); break; } } mSearchState.mIncludeZones = mZonesCheckbox.Checked; mSearchState.mIncludeEvents = mEventsCheckbox.Checked; hadError = false; } if (hadError) { if (error.IsEmpty) error.Append("Error"); var listViewItem = (DarkListViewItem)mListView.GetRoot().CreateChildItem(); listViewItem.Label = error; listViewItem.mTextColor = 0xFFFF8080; mFailed = true; } SearchIteration(); } int EntryCompare(FindEntry lhs, FindEntry rhs) { int64 result = 0; switch (mListView.mSortType.mColumn) { case 0: result = String.Compare(lhs.mZoneStr, rhs.mZoneStr, true); if (result == 0) result = lhs.mStartTick - rhs.mStartTick; case 1: result = lhs.mStartTick - rhs.mStartTick; case 2: int64 lhsLen = lhs.mEndTick - lhs.mStartTick; int64 rhsLen = rhs.mEndTick - rhs.mStartTick; result = lhsLen - rhsLen; case 3: var lhsTrack = PerfView.mSession.mThreads[lhs.mTrackIdx]; var rhsTrack = PerfView.mSession.mThreads[rhs.mTrackIdx]; result = BpTrack.Compare(lhsTrack, rhsTrack); if (result == 0) result = lhs.mStartTick - rhs.mStartTick; } if (mListView.mSortType.mReverse) result = -result; return (int)result; } void SortProc() { //Debug.WriteLine("SortProc start"); mSearchState.mSortedEntries = new List(); mSearchState.mFoundEntries.CopyTo(mSearchState.mSortedEntries); mSearchState.mSortedEntries.Sort(scope => EntryCompare); mSortDoneHandle.Set(); //Debug.WriteLine("SortProc end"); } bool SearchIteration() { if (mFailed) return false; mSearchIncomplete = false; if (mSorting) mSearchIncomplete = true; if (mSearchState.mTextSearch.IsEmpty) return true; var stopwatch = scope Stopwatch(); stopwatch.Start(); bool refreshItems = false; if (mSearchState.mSortedEntries != null) { Debug.Assert(mSearchState.mSortedEntries.Count == mSearchState.mFoundEntries.Count); delete mSearchState.mFoundEntries; mSearchState.mFoundEntries = mSearchState.mSortedEntries; mSearchState.mSortedEntries = null; mSearchState.mHighestEntry = null; while (mSearchState.mFoundEntries.Count > mMaxFoundEntries) { var entry = mSearchState.mFoundEntries.PopBack(); if (entry.mInDictionary) mSearchState.mPendingDeleteEntries.Add(entry); else delete entry; mSearchState.mTrimmedResults = true; } if (mSearchState.mFoundEntries.Count > 0) mSearchState.mHighestEntry = mSearchState.mFoundEntries.Back; refreshItems = true; } int32 prevFindCount = (int32)mSearchState.mFoundEntries.Count; var client = PerfView.mSession; if (mSearchState.mCurThreadIdx >= client.mThreads.Count) { mSearchState.mCurThreadIdx = 0; mSearchState.mCurThreadStreamIdx = -1; } bool didEarlyBreak = false; TrackLoop: while (mSearchState.mCurThreadIdx < client.mThreads.Count) { int32 threadIdx = mSearchState.mCurThreadIdx; while (threadIdx >= mSearchState.mTrackStates.Count) mSearchState.mTrackStates.Add(new TrackState()); var track = client.mThreads[threadIdx]; var trackState = mSearchState.mTrackStates[threadIdx]; //int32 streamDataIdx = track.mStreamDataList.Count - 1; if (mSearchState.mCurThreadStreamIdx == -1) { trackState.mPrevLastEntryEndTick = trackState.mLastEntryEndTick; mSearchState.mCurThreadStreamIdx = (int32)track.mStreamDataList.Count - 1; while (mSearchState.mCurThreadStreamIdx > 0) { mSearchState.mCurThreadStreamIdx--; var streamData = track.mStreamDataList[mSearchState.mCurThreadStreamIdx]; if ((streamData.mEndTick < trackState.mLastEntryEndTick) || (streamData.mEndTick == trackState.mPrevEndTick)) { mSearchState.mCurThreadStreamIdx++; break; } } //Test //mSearchState.mCurThreadStreamIdx = 0; } bool includeTrack = true; if (!mSearchState.mTrackSearch.IsEmpty) { var trackName = scope String(128); track.GetName(trackName); includeTrack = mSearchState.mTrackSearch.Matches(trackName); } if (includeTrack) { while (mSearchState.mCurThreadStreamIdx < track.mStreamDataList.Count) { SearchIn(threadIdx, mSearchState.mCurThreadStreamIdx); BpStreamData streamData = track.mStreamDataList[mSearchState.mCurThreadStreamIdx]; trackState.mPrevEndTick = streamData.mEndTick; mSearchState.mCurThreadStreamIdx++; if ((mSearchState.mFoundEntries.Count >= mMaxFoundEntries * 2) || (stopwatch.ElapsedMilliseconds > (int)(gApp.mTimePerFrame * 1000))) { didEarlyBreak = true; mSearchIncomplete = true; break TrackLoop; } } } if (!didEarlyBreak) { mSearchState.mCurThreadIdx++; mSearchState.mCurThreadStreamIdx = -1; } } int width; int height; gApp.GetDesktopResolution(out width, out height); //TODO: We really want to set this to the maximum number of possibly visible lines //int32 scrollLinePos = (int32)((height - mListView.mScrollContent.mY) / mListView.mFont.GetLineSpacing()); int numNewEntries = mSearchState.mFoundEntries.Count - prevFindCount; if ((numNewEntries != 0) || (mNeedsResort)) { //mSortThread = new Thread(new => SortProc); //mSortThread.Start(false); mSorting = true; ThreadPool.QueueUserWorkItem(new => SortProc); mNeedsResort = false; mSearchIncomplete = true; } // Add entries if (numNewEntries != 0) { if (mListView.GetRoot().GetChildCount() == 0) { var listViewItem = (DarkVirtualListViewItem)mListView.GetRoot().CreateChildItem(); listViewItem.mVirtualHeadItem = listViewItem; mListView.PopulateVirtualItem(listViewItem); } var listViewItem = (DarkVirtualListViewItem)mListView.GetRoot().GetChildAtIndex(0); listViewItem.mVirtualCount = Math.Min((int32)mSearchState.mFoundEntries.Count, mMaxFoundEntries); if (mSearchState.mTrimmedResults) listViewItem.mVirtualCount++; // Show "trimmed results" message at bottom } for (int itemIdx < mListView.GetRoot().GetChildCount()) { var listViewItem = (FindListViewItem)mListView.GetRoot().GetChildAtIndex((int32)itemIdx); if (listViewItem.mVirtualIdx >= mSearchState.mFoundEntries.Count) continue; var foundEntry = mSearchState.mFoundEntries[listViewItem.mVirtualIdx]; if ((foundEntry.mTimeDirty) || (refreshItems)) { mListView.UpdateItem(listViewItem); if (foundEntry.mTimeDirty) { foundEntry.mTimeDirty = false; if (mListView.mSortType.mColumn == cColumnLength) mNeedsResort = true; } } } return true; } void RefreshData() { mListView.GetRoot().Clear(); } public override void Update() { base.Update(); if (mSelectionDirty) { RefreshData(); mSelectionDirty = false; } if (mSorting) { if (mSortDoneHandle.WaitFor(0)) { mSorting = false; mSortDoneHandle.Reset(); } } if ((mNeedsRestartSearch) && (!mSorting)) { StartSearch(); mNeedsRestartSearch = false; } if ((mNeedsRehup) && (!mSorting)) { if (mSearchState != null) { mSearchState.mHighestEntry = null; ClearAndDeleteItems(mSearchState.mFoundEntries); DeleteAndNullify!(mSearchState.mSortedEntries); for (var trackState in mSearchState.mTrackStates) { trackState.mFindPositions.Clear(); trackState.mLastEntryEndTick = 0; trackState.mPrevLastEntryEndTick = 0; } mListView.GetRoot().Clear(); } mNeedsRehup = false; } if ((mSearchState != null) && (!mSorting)) { if (gApp.mIsUpdateBatchStart) SearchIteration(); } if (mSearchIncomplete) mDrawWaitCount = Math.Min(mDrawWaitCount + 1, 8); else mDrawWaitCount = Math.Max(mDrawWaitCount - 1, 0); } void ClearMem() { } void FinishSorting() { if (mSorting) { mSortDoneHandle.WaitFor(); mSorting = false; mSortDoneHandle.Reset(); } } public void Clear() { mFailed = false; //Debug.WriteLine("FinePanel.Clear"); FinishSorting(); //Debug.WriteLine("FinePanel.Clear - deleting search state"); DeleteAndNullify!(mSearchState); mListView.GetRoot().Clear(); } public override void DrawAll(Graphics g) { base.DrawAll(g); if (mDrawWaitCount > 0) { BPUtils.DrawWait(g, 1, mEntryEdit.mY + 0); } } } }