1
0
Fork 0
mirror of https://github.com/beefytech/Beef.git synced 2025-07-06 16:25:59 +02:00

Initial checkin

This commit is contained in:
Brian Fiete 2019-08-23 11:56:54 -07:00
parent c74712dad9
commit 078564ac9e
3242 changed files with 1616395 additions and 0 deletions

42
BeefPerf/BeefProj.toml Normal file
View file

@ -0,0 +1,42 @@
FileVersion = 1
Dependencies = {corlib = "*", Beefy2D = "*"}
[Project]
Name = "BeefPerf"
TargetType = "BeefWindowsApplication"
StartupObject = "BeefPerf.Program"
[Platform.Windows]
IconFile = "res/BeefPerf.ico"
ManifestFile = "res/BeefPerf.manifest"
[Configs.Debug.Win64]
TargetDirectory = "$(WorkspaceDir)/../IDE/dist"
TargetName = "$(ProjectName)_d"
OtherLinkFlags = "$(LinkFlags) BeefySysLib64_d.lib"
BeefLibType = "DynamicDebug"
[Configs.Debug.Win32]
CLibType = "Static"
BeefLibType = "Static"
[Configs.Paranoid.Win32]
CLibType = "Static"
BeefLibType = "Static"
[Configs.Paranoid.Win64]
CLibType = "Static"
BeefLibType = "Static"
[Configs.Test.Win32]
CLibType = "Static"
BeefLibType = "Static"
[Configs.Test.Win64]
CLibType = "Static"
BeefLibType = "Static"
[Configs.Release.Win64]
TargetDirectory = "$(WorkspaceDir)/../IDE/dist"
OtherLinkFlags = "$(LinkFlags) BeefySysLib64.lib"
BeefLibType = "Dynamic"

6
BeefPerf/BeefSpace.toml Normal file
View file

@ -0,0 +1,6 @@
FileVersion = 1
Projects = {BeefPerf = {Path = "."}, Beefy2D = {Path = "../BeefLibs/Beefy2D"}}
Unlocked = ["corlib"]
[Workspace]
StartupProject = "BeefPerf"

BIN
BeefPerf/res/BeefPerf.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

View file

@ -0,0 +1,10 @@
<?xml version='1.0' encoding='UTF-8' standalone='yes'?>
<assembly xmlns='urn:schemas-microsoft-com:asm.v1' manifestVersion='1.0'>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level='asInvoker' uiAccess='false' />
</requestedPrivileges>
</security>
</trustInfo>
</assembly>

998
BeefPerf/src/BPApp.bf Normal file
View file

@ -0,0 +1,998 @@
using System;
using Beefy;
using Beefy.widgets;
using Beefy.gfx;
using Beefy.theme.dark;
using Beefy.theme;
using System.IO;
using System.Threading;
using System.Collections.Generic;
using System.Diagnostics;
using Beefy.events;
using Beefy.sys;
using System.Text;
using System.Net;
namespace BeefPerf
{
class BPApp : BFApp
{
public new static BPApp sApp;
public float mTimePerFrame = 1.0f / 60;
public bool mWantsFullscreen = false;
public int32 mListenPort = 4208;
//public int32 mListenPort = 135;
WidgetWindow mMainWindow;
Widget mRootWidget;
public Widget mMainWidget;
public List<String> mLogLines = new List<String>() ~ DeleteContainerAndItems!(_);
//public PopupMenuManager mPopupMenuManager;
//public Trainer mTrainer ~ delete _;
public ScriptManager mScriptManager = new ScriptManager() ~ delete _;
public DarkDockingFrame mDockingFrame;
public MainFrame mMainFrame;
public WorkspacePanel mWorkspacePanel;
public Board mBoard;
public ProfilePanel mProfilePanel;
public FindPanel mFindPanel;
public Windows.ProcessInformation mProcessInformation;
public Socket mListenSocket ~ delete _;
public Thread mSocketThread ~ delete _;
public List<BpClient> mClients = new List<BpClient>() ~ delete _;
public List<BpSession> mSessions = new List<BpSession> ~ DeleteContainerAndItems!(_);
public Monitor mClientMonitor = new Monitor() ~ delete _;
public WaitEvent mShutdownEvent = new WaitEvent() ~ delete _;
public BpSession mCurSession;
bool mEnableGCCollect = true;
public bool mIsAutoOpened;
public int32 mStatBytesReceived;
public int32 mStatReportTick;
public int32 mStatBytesPerSec;
public bool mFailed;
public int32 mFailIdx;
public bool Listening
{
get
{
return mListenSocket.IsOpen;
}
set
{
if (value)
{
if (!mListenSocket.IsOpen)
{
if (mListenSocket.Listen(mListenPort) case .Err)
{
Fail(scope String()..AppendF("Failed to listen on port {0}", mListenPort));
}
}
}
else
{
if (mListenSocket.IsOpen)
{
mListenSocket.Close();
}
}
}
}
public this()
{
sApp = this;
gApp = this;
//RefreshRate = 120;
}
public ~this()
{
if (mSocketThread != null)
mSocketThread.Join();
for (var client in mClients)
{
if (!client.IsSessionInitialized)
delete client;
}
Widget.RemoveAndDelete(mWorkspacePanel);
Widget.RemoveAndDelete(mBoard);
Widget.RemoveAndDelete(mProfilePanel);
Widget.RemoveAndDelete(mFindPanel);
if (!mLogLines.IsEmpty)
{
var fs = scope FileStream();
if (fs.Open("BeefPerf.txt", .Append, .ReadWrite, .ReadWrite, 4096, .None, null) case .Ok)
{
var streamWriter = scope StreamWriter(fs, Encoding.UTF8, 4096);
for (var line in mLogLines)
streamWriter.WriteLine(line);
}
}
}
static uint32 TimeToUnixTime(DateTime ft)
{
// takes the last modified date
int64 date = ft.ToFileTime();
// 100-nanoseconds = milliseconds * 10000
int64 adjust = 11644473600000L * 10000;
// removes the diff between 1970 and 1601
date -= adjust;
// converts back from 100-nanoseconds to seconds
return (uint32)(date / 10000000L);
}
public void LogLine(String text)
{
mLogLines.Add(new String(text));
}
public void Fail(String text)
{
//MessageBeep(MessageBeepType.Error);
// Always write to STDOUT even if we're running as a GUI, allowing cases like RunAndWait to pass us a stdout handle
Console.WriteLine("ERROR: {0}", text);
mFailIdx++;
if (gApp.mScriptManager.mRunningCommand)
return;
if (mMainWindow == null)
{
LogLine(text);
mFailed = true;
if (!mShuttingDown)
Shutdown();
return;
}
Dialog aDialog = ThemeFactory.mDefault.CreateDialog("ERROR", text, DarkTheme.sDarkTheme.mIconError);
aDialog.mDefaultButton = aDialog.AddButton("OK");
aDialog.mEscButton = aDialog.mDefaultButton;
aDialog.PopupWindow(mMainWindow);
}
void StartFakeClient()
{
return;
#unwarn
ProcessStartInfo procInfo = scope ProcessStartInfo();
procInfo.UseShellExecute = false;
procInfo.RedirectStandardError = true;
procInfo.SetFileName(@"c:\proj\BeefPerf\x64\Debug\BeefPerf.exe");
procInfo.CreateNoWindow = true;
var spawnedProcess = scope SpawnedProcess();
spawnedProcess.Start(procInfo).IgnoreError();
//Process process = Process.Start(procInfo).GetValueOrDefault();
//delete process;
}
public void UpdateTitle()
{
String title = scope String();
if ((mBoard.mPerfView != null) && (mBoard.mPerfView.mSession.mClientName != null))
{
title.Append(mBoard.mPerfView.mSession.mClientName);
title.Append(" - ");
}
title.Append("BeefPerf");
mMainWindow.SetTitle(title);
}
public override void Init()
{
/*uint timeNow = GetCurrentUnixTime();
FitEntry[] fitEntries = new FitEntry[200];
for (int i = 0; i < fitEntries.Length; i++)
{
fitEntries[i].mElapsedTimeMS = i * 500;
fitEntries[i].mPower = i;
fitEntries[i].mCadence = 90;
}
int[] lapOffsets = scope int[] {100};
WriteFit("c:\\temp\\test.fit", UnixTimeToFitTime(timeNow), fitEntries.CArray(), fitEntries.Length, lapOffsets.CArray(), lapOffsets.Length);*/
base.Init();
mAutoDirty = false;
Socket.Init();
mListenSocket = new Socket();
Listening = true;
mSocketThread = new Thread(new => SocketThreadProc);
mSocketThread.Start(false);
DarkTheme aTheme = new DarkTheme();
aTheme.Init();
ThemeFactory.mDefault = aTheme;
mMainFrame = new MainFrame();
mDockingFrame = mMainFrame.mDockingFrame;
BFWindow.Flags windowFlags = BFWindow.Flags.Border | BFWindow.Flags.SysMenu | //| BFWindow.Flags.CaptureMediaKeys |
BFWindow.Flags.Caption | BFWindow.Flags.Minimize | BFWindow.Flags.QuitOnClose | BFWindowBase.Flags.Resizable |
BFWindow.Flags.Menu | BFWindow.Flags.SysMenu;
if (mWantsFullscreen)
windowFlags |= BFWindowBase.Flags.Fullscreen;
//mRootWidget = new Widget();
mWorkspacePanel = new WorkspacePanel();
mBoard = new Board();
mProfilePanel = new ProfilePanel();
mFindPanel = new FindPanel();
//mRootWidget = mBoard;
mMainWindow = new WidgetWindow(null, "BeefPerf", 0, 0, 1600, 1200, windowFlags, mMainFrame);
mMainWindow.mOnWindowKeyDown.Add(new => SysKeyDown);
mMainWindow.SetMinimumSize(480, 360);
mMainWindow.mIsMainWindow = true;
CreateMenu();
ShowWorkspacePanel();
ShowTimelinePanel();
ShowProfilePanel();
ShowFindPanel();
StartFakeClient();
GC.SetAutoCollectPeriod(mEnableGCCollect ? 20 : -1);
GC.SetCollectFreeThreshold(mEnableGCCollect ? 64*1024*1024 : -1);
}
public override void Stop()
{
base.Stop();
mListenSocket.Close();
}
void ShowWorkspacePanel()
{
if (mWorkspacePanel.mWidgetWindow != null)
{
return;
}
TabbedView tabbedView = CreateTabbedView();
SetupTab(tabbedView, "Workspace", mWorkspacePanel);
tabbedView.SetRequestedSize(320, 320);
if ((mDockingFrame.mSplitType == .Vert) || (mDockingFrame.mDockedWidgets.Count == 1))
mDockingFrame.AddDockedWidget(tabbedView, mDockingFrame.mDockedWidgets[0], DockingFrame.WidgetAlign.Left);
else
mDockingFrame.AddDockedWidget(tabbedView, null, DockingFrame.WidgetAlign.Top);
}
void ShowTimelinePanel()
{
if (mBoard.mWidgetWindow != null)
{
return;
}
TabbedView tabbedView = CreateTabbedView();
SetupTab(tabbedView, "Timeline", mBoard);
tabbedView.SetRequestedSize(250, 250);
tabbedView.mIsFillWidget = true;
if ((mDockingFrame.mSplitType == .Vert) || (mDockingFrame.mDockedWidgets.Count == 1))
mDockingFrame.AddDockedWidget(tabbedView, mDockingFrame.mDockedWidgets[0], DockingFrame.WidgetAlign.Right);
else
mDockingFrame.AddDockedWidget(tabbedView, null, DockingFrame.WidgetAlign.Top);
}
void ShowProfilePanel()
{
if (mProfilePanel.mWidgetWindow != null)
{
return;
}
var tabbedView = CreateTabbedView();
SetupTab(tabbedView, "Profile", mProfilePanel);
tabbedView.SetRequestedSize(300, 300);
if ((mDockingFrame.mSplitType == .Vert) && (mDockingFrame.mDockedWidgets.Count > 0))
mDockingFrame.AddDockedWidget(tabbedView, mDockingFrame.mDockedWidgets.Back, DockingFrame.WidgetAlign.Left);
else
mDockingFrame.AddDockedWidget(tabbedView, null, DockingFrame.WidgetAlign.Bottom);
}
void ShowFindPanel()
{
if (mFindPanel.mWidgetWindow != null)
{
return;
}
var tabbedView = CreateTabbedView();
SetupTab(tabbedView, "Find", mFindPanel);
tabbedView.SetRequestedSize(300, 300);
if ((mDockingFrame.mSplitType == .Vert) && (mDockingFrame.mDockedWidgets.Count > 0))
mDockingFrame.AddDockedWidget(tabbedView, mDockingFrame.mDockedWidgets.Back, DockingFrame.WidgetAlign.Right);
else
mDockingFrame.AddDockedWidget(tabbedView, null, DockingFrame.WidgetAlign.Bottom);
}
void SetupTab(TabbedView tabbedView, String label, Widget widget)
{
var tabButton = tabbedView.AddTab(label, 0, widget, false);
tabButton.mCloseClickedEvent.Add(new () =>
{
var tabbedView = tabButton.mTabbedView;
tabbedView.RemoveTab(tabButton);
if (tabbedView.mTabs.IsEmpty)
{
tabbedView.mParentDockingFrame.RemoveDockedWidget(tabbedView);
gApp.DeferDelete(tabbedView);
}
});
}
void ToggleCheck(IMenu menu, ref bool checkVal)
{
checkVal = !checkVal;
var sysMenu = (SysMenu)menu;
sysMenu.Modify(null, null, null, true, checkVal ? 1 : 0);
}
void ShowInfo()
{
int streamSize = 0;
int lodStreamSize = 0;
for (var client in mClients)
{
for (var thread in client.mThreads)
{
for (var streamData in thread.mStreamDataList)
{
streamSize += streamData.mBuffer.Count;
}
for (var streamLOD in thread.mStreamLODs)
{
for (var streamData in streamLOD.mStreamDataList)
{
lodStreamSize += streamData.mBuffer.Count;
}
}
}
}
Debug.WriteLine("Stream Size: {0}k", streamSize / 1024);
Debug.WriteLine("LOD Stream Size: {0}k", lodStreamSize / 1024);
}
public void CloseSession()
{
SetSession(null);
}
public void OpenSession()
{
List<int> iList = scope List<int>();
iList.Add(123);
var fileDialog = scope OpenFileDialog();
fileDialog.ShowReadOnly = false;
fileDialog.Title = "Open Session";
fileDialog.Multiselect = true;
fileDialog.ValidateNames = true;
fileDialog.DefaultExt = ".bfps";
fileDialog.SetFilter("BeefPerf Session (*.bfps)|*.bfps|All files (*.*)|*.*");
if (fileDialog.ShowDialog() case .Ok)
{
for (String origProjFilePath in fileDialog.FileNames)
{
var session = new BpSession(false);
switch (session.Load(origProjFilePath))
{
case .Ok:
mSessions.Add(session);
MarkDirty();
case .Err(let err):
switch (err)
{
case .FileNotFound, .FileError:
Fail(scope String()..AppendF("Failed to open file '{0}'", origProjFilePath));
case .InvalidData:
Fail(scope String()..AppendF("Invalid data in file '{0}'", origProjFilePath));
case .InvalidVersion:
Fail(scope String()..AppendF("Unsupported version in file '{0}'", origProjFilePath));
}
delete session;
}
}
}
}
public void SaveSession()
{
if (mCurSession == null)
return;
var fileDialog = scope SaveFileDialog();
//fileDialog.ShowReadOnly = false;
fileDialog.Title = "Save Session";
fileDialog.Multiselect = true;
fileDialog.ValidateNames = true;
fileDialog.DefaultExt = ".bfps";
fileDialog.SetFilter("BeefPerf Session (*.bfps)|*.bfps|All files (*.*)|*.*");
if (fileDialog.ShowDialog() case .Ok)
{
for (String origProjFilePath in fileDialog.FileNames)
{
if (mCurSession.Save(origProjFilePath) case .Err)
{
Fail(scope String()..AppendF("Failed to save file '{0}'", origProjFilePath));
}
}
}
}
public void RemoveSession()
{
if (mCurSession == null)
return;
var session = mCurSession;
SetSession(null);
mSessions.Remove(session);
using (mClientMonitor.Enter())
{
var client = session as BpClient;
if (client != null)
mClients.Remove(client);
delete session;
}
MarkDirty();
}
public void RemoveAllSessions()
{
SetSession(null);
using (mClientMonitor.Enter())
{
ClearAndDeleteItems(mSessions);
mSessions.Clear();
mClients.Clear();
}
MarkDirty();
}
public void CreateMenu()
{
SysMenu root = mMainWindow.mSysMenu;
SysMenu subMenu = root.AddMenuItem("&File");
subMenu.AddMenuItem("&Open Session", "Ctrl+O", new (menu) => { OpenSession(); });
subMenu.AddMenuItem("&Save Session", "Ctrl+S", new (menu) => { SaveSession(); });
subMenu.AddMenuItem("&Close Session", "Ctrl+W", new (menu) => { CloseSession(); });
subMenu.AddMenuItem("&Remove Session", null, new (menu) => { RemoveSession(); });
subMenu.AddMenuItem("Remove &All Sessions", null, new (menu) => { RemoveAllSessions(); });
subMenu.AddMenuItem("&Info", "Ctrl+I", new (menu) => { ShowInfo(); });
subMenu.AddMenuItem("E&xit", null, new (menu) => { mMainWindow.Close(); });
subMenu = root.AddMenuItem("&View");
subMenu.AddMenuItem("&Workspace", "Ctrl+Alt+W", new (menu) => { ShowWorkspacePanel(); });
subMenu.AddMenuItem("&Timeline", "Ctrl+Alt+T", new (menu) => { ShowTimelinePanel(); });
subMenu.AddMenuItem("&Profile", "Ctrl+Alt+P", new (menu) => { ShowProfilePanel(); });
subMenu.AddMenuItem("&Find", "Ctrl+Alt+F", new (menu) => { ShowFindPanel(); });
subMenu = root.AddMenuItem("&Debug");
subMenu.AddMenuItem("GC Collect", null, new (menu) =>
{
if (Profiler.StartSampling() case .Ok(let id))
{
GC.Collect();
id.Dispose();
}
});
subMenu.AddMenuItem("Enable GC Collect", null, new (menu) => { ToggleCheck(menu, ref mEnableGCCollect); }, null, null, true, mEnableGCCollect ? 1 : 0);
}
public override void Shutdown()
{
base.Shutdown();
mShutdownEvent.Set(true);
}
public override bool HandleCommandLineParam(String key, String value)
{
switch (key)
{
case "-autoOpened":
mIsAutoOpened = true;
return true;
case "-cmd":
if (value == null)
return false;
Socket.Init();
LogLine(scope String()..AppendF("Cmd: {0}", value));
bool alreadyRunning = false;
//
{
Socket socket = scope Socket();
if (socket.Listen(mListenPort) case .Err)
alreadyRunning = true;
}
if (!alreadyRunning)
{
LogLine("Launching BeefPerf process");
var exePath = scope String();
var curProcess = scope Process();
curProcess.GetProcessById(Process.CurrentId);
exePath.Append(curProcess.ProcessName);
ProcessStartInfo procInfo = scope ProcessStartInfo();
procInfo.SetFileName(exePath);
procInfo.SetArguments("-autoOpened");
SpawnedProcess spawnedProcess = scope SpawnedProcess();
spawnedProcess.Start(procInfo);
}
// Try for 10 seconds
for (int i < 100)
{
Socket socket = scope Socket();
if (socket.Connect("127.0.0.1", mListenPort) case .Err)
{
Thread.Sleep(100);
continue;
}
DynMemStream memStream = scope DynMemStream();
memStream.Write((int32)0); // Size placeholder
memStream.Write((uint8)BpCmd.Cmd);
String cmdStr = value;
memStream.Write(cmdStr);
memStream.Write((uint8)0);
(*(int32*)memStream.Ptr) = (int32)memStream.Length - 4;
// Try 10 seconds to send
for (int sendItr < 100)
{
if (memStream.IsEmpty)
break;
switch (socket.Send(memStream.Ptr, memStream.Length))
{
case .Ok(let sendLen):
memStream.RemoveFromStart(sendLen);
case .Err:
Fail("Failed to send command");
}
if (memStream.IsEmpty)
break;
Thread.Sleep(100); // Wait until we can send more...
}
// Try 10 seconds for a response
bool gotResult = false;
RecvLoop: for (int recvStr < 100)
{
int result = 0;
switch (socket.Recv(&result, 1))
{
case .Ok(let recvLen):
if (recvLen > 0)
{
if (result != 1)
Fail("Command failed");
gotResult = true;
break RecvLoop;
}
case .Err:
break;
}
Thread.Sleep(100);
}
if (!gotResult)
{
Fail("Failed to receive command response");
}
if (!mShuttingDown)
Shutdown();
return true;
}
Fail("Failed to connect to BeefPerf");
case "-fullscreen":
mWantsFullscreen = true;
case "-windowed":
mWantsFullscreen = false;
default:
return base.HandleCommandLineParam(key, value);
}
return true;
}
public override void UnhandledCommandLine(String key, String value)
{
Fail(StackStringFormat!("Unhandled command line param: {0}", key));
}
void SetupNewWindow(WidgetWindow window)
{
window.mOnWindowKeyDown.Add(new => SysKeyDown);
//window.mWindowCloseQueryHandler.Add(new => SecondaryAllowClose);
}
DarkTabbedView CreateTabbedView()
{
var tabbedView = new DarkTabbedView();
tabbedView.mSharedData.mOpenNewWindowDelegate.Add(new (fromTabbedView, newWindow) => SetupNewWindow(newWindow));
return tabbedView;
}
public void PushMainWidget(Widget widget)
{
if (mMainWidget != null)
{
mMainWidget.RemoveSelf();
DeferDelete(mMainWidget);
}
mMainWidget = widget;
mRootWidget.AddWidget(widget);
widget.Resize(0, 0, mRootWidget.mWidth, mRootWidget.mHeight);
widget.SetFocus();
}
void SocketThreadProc()
{
while (true)
{
if (mShutdownEvent.WaitFor(0))
return;
//TODO: Add client sockets to the select set
var readSet = default(Socket.FDSet);
var writeSet = default(Socket.FDSet);
var exceptSet = default(Socket.FDSet);
void Add(Socket socket)
{
readSet.Add(socket.[Friend]mHandle);
exceptSet.Add(socket.[Friend]mHandle);
}
Add(mListenSocket);
using (mClientMonitor.Enter())
{
for (var client in mClients)
Add(client.mSocket);
}
#unwarn
int selectResult = Socket.Select(&readSet, &writeSet, &exceptSet, 20*1000);
int clientIdx = 0;
while (true)
{
BpClient client = null;
using (mClientMonitor.Enter())
{
if (clientIdx < mClients.Count)
{
client = mClients[clientIdx++];
}
if (client == null)
break;
client.TryRecv();
}
}
}
}
public override void Update(bool batchStart)
{
base.Update(batchStart);
/*if (!mListenSocket.IsConnected)
{
Listen();
}*/
Socket newConnection = new Socket();
if (newConnection.AcceptFrom(mListenSocket) case .Ok)
{
using (mClientMonitor.Enter())
{
BpClient client = new BpClient();
//client.mProfileId = Profiler.StartSampling();
client.mConnectTime = DateTime.Now;
client.mSocket = newConnection;
mClients.Add(client);
}
/*{
// Kill all old clients - remove this when we support switching between clients
ClearAndDeleteItems(mClients);
mFindPanel.Clear();
mProfilePanel.Clear();
mFindPanel.mNeedsRestartSearch = true;
BpClient client = new BpClient();
client.mSocket = newConnection;
mClients.Add(client);
SetClient(client);
}*/
}
else
delete newConnection;
using (mClientMonitor.Enter())
{
for (var client in mClients)
{
client.Update();
if (client.mSessionOver)
{
if (!client.IsSessionInitialized)
delete client;
@client.Remove();
if (mClients.Count == 0)
{
gApp.mStatBytesPerSec = 0;
}
MarkDirty();
}
if (mUpdateCnt % 20 == 0)
MarkDirty();
}
}
if (mUpdateCnt - mStatReportTick >= 60)
{
mStatBytesPerSec = mStatBytesReceived;
mStatBytesReceived = 0;
mStatReportTick = mUpdateCnt;
}
}
void SysKeyDown(KeyDownEvent evt)
{
var window = (WidgetWindow)evt.mSender;
#unwarn
Widget focusWidget = window.mFocusWidget;
if (evt.mKeyFlags == 0) // No ctrl/shift/alt
{
switch (evt.mKeyCode)
{
default:
}
}
if (evt.mKeyFlags == .Ctrl)
{
switch (evt.mKeyCode)
{
case (KeyCode)'F':
ShowFind();
case (KeyCode)'C':
if (DarkTooltipManager.sTooltip != null)
{
SetClipboardText(DarkTooltipManager.sTooltip.mText);
}
default:
}
}
}
enum ShowTabResult
{
Existing,
OpenedNew
}
public void WithDocumentTabbedViews(Action<DarkTabbedView> func)
{
for (int32 windowIdx = 0; windowIdx < mWindows.Count; windowIdx++)
{
var window = mWindows[windowIdx];
var widgetWindow = window as WidgetWindow;
if (widgetWindow != null)
{
var darkDockingFrame = widgetWindow.mRootWidget as DarkDockingFrame;
if (widgetWindow == mMainWindow)
darkDockingFrame = mDockingFrame;
if (darkDockingFrame != null)
{
darkDockingFrame.WithAllDockedWidgets(scope (dockedWidget) =>
{
var tabbedView = dockedWidget as DarkTabbedView;
if (tabbedView != null)
func(tabbedView);
});
}
}
}
}
public void WithTabs(Action<TabbedView.TabButton> func)
{
WithDocumentTabbedViews(scope (documentTabbedView) =>
{
documentTabbedView.WithTabs(func);
});
}
public TabbedView.TabButton GetTab(Widget content)
{
TabbedView.TabButton tab = null;
WithTabs(scope [&] (checkTab) =>
{
if (checkTab.mContent == content)
tab = checkTab;
});
return tab;
}
TabbedView FindTabbedView(DockingFrame dockingFrame, int32 xDir, int32 yDir)
{
bool useFirst = true;
if (dockingFrame.mSplitType == DockingFrame.SplitType.Horz)
{
useFirst = xDir > 0;
}
else
useFirst = yDir > 0;
for (int32 pass = 0; pass < 2; pass++)
{
for (int32 i = 0; i < dockingFrame.mDockedWidgets.Count; i++)
{
if ((useFirst) && (i == 0) && (pass == 0))
continue;
var widget = dockingFrame.mDockedWidgets[i];
if (widget is TabbedView)
return (TabbedView)widget;
DockingFrame childFrame = widget as DockingFrame;
if (childFrame != null)
{
TabbedView tabbedView = FindTabbedView(childFrame, xDir, yDir);
if (tabbedView != null)
return tabbedView;
}
}
}
return null;
}
ShowTabResult ShowTab(Widget tabContent, String name, float width, bool ownsContent)
{
var result = ShowTabResult.Existing;
var tabButton = GetTab(tabContent);
if (tabButton == null)
{
TabbedView tabbedView = FindTabbedView(mDockingFrame, -1, 1);
if (tabbedView != null)
{
tabButton = tabbedView.AddTab(name, width, tabContent, ownsContent);
result = ShowTabResult.OpenedNew;
}
}
if (tabButton != null)
tabButton.Activate();
return result;
}
void ShowPanel(Widget panel, String label)
{
//RecordHistoryLocation();
ShowTab(panel, label, 150, false);
//panel.FocusForKeyboard();
}
public void ShowFind()
{
ShowPanel(mFindPanel, "Find");
mFindPanel.mEntryEdit.SetFocus();
}
public void SetSession(BpSession session)
{
if (session == mCurSession)
return;
mFindPanel.Clear();
mProfilePanel.Clear();
mFindPanel.mNeedsRestartSearch = true;
mCurSession = session;
mBoard.ShowSession(session);
mFindPanel.Show(mBoard.mPerfView);
mFindPanel.Clear();
MarkDirty();
/*if (mBoard.mPerfView != null)
{
mBoard.mPerfView.SaveSummary("BfCompiler_Compile", @"c:\temp\save.txt");
}*/
}
public void ZoneSelected(PerfView perfView, BPSelection selection)
{
mProfilePanel.Show(perfView, selection);
}
public void ReportBytesReceived(int32 count)
{
mStatBytesReceived += count;
}
public bool HandleClientCommand(BpClient client, StringView cmd)
{
int failIdx = mFailIdx;
mScriptManager.Exec(cmd);
return failIdx == mFailIdx;
}
public void SessionInitialized(BpClient bpClient)
{
mSessions.Add(bpClient);
UpdateTitle();
MarkDirty();
}
}
}
static
{
static BeefPerf.BPApp gApp;
}

1635
BeefPerf/src/BPClient.bf Normal file

File diff suppressed because it is too large Load diff

103
BeefPerf/src/BPUtils.bf Normal file
View file

@ -0,0 +1,103 @@
using Beefy.gfx;
using System;
using Beefy.theme.dark;
namespace BeefPerf
{
class BPUtils
{
public static void DrawWait(Graphics g, float x, float y)
{
for (int segIdx < 8)
{
int relSegIdx = (segIdx + (gApp.mUpdateCnt / 10)) % 8;
using (g.PushColor(Color.Get(1.0f - (float)relSegIdx * 0.1f)))
using (g.PushRotate((float)Math.PI_f * -(float)segIdx / 4, x + 10, y + 10))
g.Draw(DarkTheme.sDarkTheme.GetImage(.WaitBar), x, y);
}
}
static Image sScratchHiliteImage ~ delete _;
static Image GetOutlineHiliteImage(Image hiliteImage, int ofs, int maxWidth)
{
int xOfs = ofs % 18;
int width = Math.Min(maxWidth, 18 - xOfs);
if (sScratchHiliteImage == null)
{
sScratchHiliteImage = hiliteImage.CreateImageSegment(xOfs, 0, width, 2);
}
else
{
sScratchHiliteImage.Modify(hiliteImage, xOfs, 0, width, 2);
}
return sScratchHiliteImage;
}
static void DrawOutlineSegment(Graphics g, Image hiliteImage, int len, int ofs)
{
int curOfs = ofs;
int lenLeft = len;
int xOfs = 0;
while (lenLeft > 0)
{
var image = GetOutlineHiliteImage(hiliteImage, curOfs, lenLeft);
g.Draw(image, (float)xOfs, 0);
lenLeft -= (int)image.mWidth;
xOfs += (int)image.mWidth;
curOfs = 0;
}
}
public static void DrawOutlineHilite(Graphics g, float x, float y, float width, float height)
{
var hiliteImage = DarkTheme.sDarkTheme.GetImage((Math.Min(width, height) > 2.5f) ? DarkTheme.ImageIdx.HiliteOutline : DarkTheme.ImageIdx.HiliteOutlineThin);
Matrix mat;
int ofs = 17 - (gApp.mUpdateCnt / 6)%18;
if (width > 1.5f)
{
mat = Matrix.IdentityMatrix;
mat.Translate(x, y);
using (g.PushMatrix(mat))
{
DrawOutlineSegment(g, hiliteImage, (int)width, ofs);
ofs += (int)width;
}
}
mat = Matrix.IdentityMatrix;
mat.Rotate((float)Math.PI_f * 0.5f);
mat.Translate(x + width, y + 1);
using (g.PushMatrix(mat))
{
DrawOutlineSegment(g, hiliteImage, (int)height - 1, ofs);
ofs += (int)height - 1;
}
if (width < 1.5f)
return;
mat = Matrix.IdentityMatrix;
mat.Rotate((float)Math.PI_f);
mat.Translate(x + width - 1, y + height);
using (g.PushMatrix(mat))
{
DrawOutlineSegment(g, hiliteImage, (int)width - 1, ofs);
ofs += (int)width - 1;
}
mat = Matrix.IdentityMatrix;
mat.Rotate((float)Math.PI_f * 1.5f);
mat.Translate(x, y + height - 1);
using (g.PushMatrix(mat))
{
DrawOutlineSegment(g, hiliteImage, (int)height - 2, ofs);
ofs += (int)height - 2;
}
}
}
}

82
BeefPerf/src/Board.bf Normal file
View file

@ -0,0 +1,82 @@
using System;
using Beefy;
using Beefy.gfx;
using Beefy.widgets;
using Beefy.geom;
using System.Collections.Generic;
using Beefy.utils;
using System.Diagnostics;
using Beefy.theme.dark;
namespace BeefPerf
{
struct BPEntry
{
public int32 mZoneNameId;
public int32 mParamsReadPos;
public int64 mStartTick;
}
struct BPSelection
{
public int32 mThreadIdx;
public int64 mTickStart;
public int64 mTickEnd;
public int32 mDepth;
}
class Board : Widget
{
public PerfView mPerfView;
public this()
{
}
public override void Resize(float x, float y, float width, float height)
{
base.Resize(x, y, width, height);
ResizeComponents();
}
public override void Draw(Graphics g)
{
base.Draw(g);
g.DrawBox(DarkTheme.sDarkTheme.GetImage(DarkTheme.ImageIdx.Bkg), 0, 0, mWidth, mHeight);
}
void GetData()
{
}
public void ShowSession(BpSession session)
{
if (mPerfView != null)
{
mPerfView.RemoveSelf();
DeleteAndNullify!(mPerfView);
}
if (session != null)
{
mPerfView = new PerfView(this, session);
mPerfView.mSession = session;
AddWidget(mPerfView);
ResizeComponents();
if (mWidgetWindow != null)
mPerfView.SetFocus();
}
}
void ResizeComponents()
{
if (mPerfView != null)
mPerfView.Resize(0, 0, mWidth, mHeight);
}
}
}

View file

@ -0,0 +1,242 @@
using System.Threading;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Beefy;
namespace BeefPerf
{
class BPStateContext
{
public enum Event
{
case Enter(int64 startTick, int32 strIdx);
case Leave(int64 endTick);
case PrevFrameTick(int64 tick);
case FrameTick(int64 tick);
case LODSmallEntry(int64 startTick, int64 endTick, int32 stackDepth);
case Event(int64 tick, char8* name, char8* details);
case LODEvent(int64 tick, int32 paramsOfs);
case EndOfStream;
}
public BpSession mSession;
public BpStreamData mStreamData;
public uint8* mReadPtr;
public uint8* mReadStart;
public uint8* mReadEnd;
public uint8* mParamsPtr;
public int64 mCurTick;
public int32 mPendingParamsSize;
public int32 mSplitCarryoverCount;
public int mDepth;
public bool mAutoLeave = true;
public bool mTimeInferred;
public this()
{
}
public this(BpSession session, BpStreamData streamData)
{
mSession = session;
mStreamData = streamData;
mCurTick = streamData.mStartTick;
if (mStreamData.mBuffer.Count > 0)
mReadPtr = &mStreamData.mBuffer[0];
mReadStart = mReadPtr;
mReadEnd = mReadPtr + mStreamData.mBuffer.Count;
}
public int32 ReadPos
{
get
{
return (int32)(mReadPtr - mReadStart);
}
set
{
mReadPtr = mReadStart + value;
}
}
[Inline]
public uint8 Read()
{
return *(mReadPtr++);
}
public void Read(void* ptr, int32 size)
{
Internal.MemCpy(ptr, mReadPtr, size);
mReadPtr += size;
}
public int64 ReadSLEB128()
{
int64 value = 0;
int32 shift = 0;
int64 curByte;
repeat
{
curByte = *(mReadPtr++);
value |= ((curByte & 0x7f) << shift);
shift += 7;
} while (curByte >= 128);
// Sign extend negative numbers.
if (((curByte & 0x40) != 0) && (shift < 64))
value |= ~0L << shift;
return value;
}
void ReadTickDelta()
{
int64 tickDelta = ReadSLEB128() * BpClient.cBufTickScale;
mCurTick += tickDelta;
}
public void MoveToParamData()
{
if (mPendingParamsSize == -1)
mPendingParamsSize = (int32)ReadSLEB128();
}
public Event GetNextEvent()
{
while (true)
{
if (mPendingParamsSize == -1)
mPendingParamsSize = (int32)ReadSLEB128();
if (mPendingParamsSize != 0)
{
Debug.Assert(mPendingParamsSize >= 0);
mReadPtr += mPendingParamsSize;
mPendingParamsSize = 0;
}
if (mReadPtr == mReadEnd)
{
if ((mDepth > 0) && (mAutoLeave))
{
--mDepth;
Debug.Assert(mDepth >= 0); // ??
mTimeInferred = true;
return .Leave(mSession.mCurTick);
}
return .EndOfStream;
}
BpCmd cmd = (BpCmd)Read();
switch (cmd)
{
case .Enter:
mDepth++;
ReadTickDelta();
int32 zoneNameId = (int32)ReadSLEB128();
if (zoneNameId < 0)
{
mPendingParamsSize = -1;
mReadPtr += -zoneNameId;
}
else
{
let zoneName = mSession.mZoneNames[zoneNameId];
mPendingParamsSize = zoneName.mParamsSize;
}
return .Enter(mCurTick, zoneNameId);
case .Leave:
mDepth--;
Debug.Assert(mDepth >= 0);
ReadTickDelta();
return .Leave(mCurTick);
case .LODSmallEntry:
ReadTickDelta();
int64 tickStart = mCurTick;
ReadTickDelta();
int32 stackDepth = (int32)ReadSLEB128();
return .LODSmallEntry(tickStart, mCurTick, stackDepth);
case .FrameTick:
ReadTickDelta();
return .FrameTick(mCurTick);
case .PrevFrameTick:
ReadTickDelta();
return .PrevFrameTick(mCurTick);
case .StreamSplitInfo:
mSplitCarryoverCount = (int32)ReadSLEB128();
case .Event:
ReadTickDelta();
mParamsPtr = mReadPtr;
char8* name = (char8*)mReadPtr;
int32 nameLen = String.StrLen(name);
mReadPtr += nameLen + 1;
char8* details = (char8*)mReadPtr;
int32 detailsLen = String.StrLen(details);
mReadPtr += detailsLen + 1;
return .Event(mCurTick, name, details);
case .LODEvent:
ReadTickDelta();
int32 paramsOfs = (int32)ReadSLEB128();
return .LODEvent(mCurTick, paramsOfs);
default:
Runtime.FatalError("Not handled");
}
}
}
public void FormatStr(int32 paramReadPos, int paramSize, String fmtStr, String outStr)
{
int32 prevReadPos = ReadPos;
ReadPos = paramReadPos;
defer { ReadPos = prevReadPos; }
if (paramSize == -1)
ReadSLEB128();
for (int idx = 0; idx < fmtStr.Length; idx++)
{
let c = fmtStr[idx];
if (c == '%')
{
let cNext = fmtStr[idx + 1];
idx++;
if (cNext == '%')
{
outStr.Append('%');
}
else if (cNext == 'd')
{
int32 val = 0;
Read(&val, 4);
val.ToString(outStr);
}
else if (cNext == 'f')
{
float val = 0;
Read(&val, 4);
val.ToString(outStr);
}
else if (cNext == 's')
{
while (true)
{
char8 paramC = (char8)Read();
if (paramC == 0)
break;
outStr.Append(paramC);
}
}
}
else
outStr.Append(c);
}
}
}
}

View file

@ -0,0 +1,278 @@
using System;
using System.Diagnostics;
using System.Collections.Generic;
namespace BeefPerf
{
class CircularBuffer
{
public class View
{
public uint8* mPtr;
public int32 mSrcIdx;
public int32 mSrcSize;
public uint8* mTempBuf ~ delete _;
public int32 mTempBufSize;
public CircularBuffer mCircularBuffer;
//void Commit(int size = -1);
};
uint8* mBuffer;
int32 mTail;
int32 mDataSize;
int32 mBufSize;
public this()
{
mTail = 0;
mBufSize = 0;
mDataSize = 0;
mBuffer = null;
}
public ~this()
{
delete mBuffer;
}
public void Resize(int32 newSize)
{
uint8* newBuffer = new uint8[newSize]*;
Read(newBuffer, 0, mDataSize);
delete mBuffer;
mBuffer = newBuffer;
mBufSize = newSize;
mTail = 0;
}
public void GrowReserve(int32 addSize)
{
if (mDataSize + addSize <= mBufSize)
return;
Debug.WriteLine("GrowReserve");
Resize(Math.Max(mDataSize + addSize, mDataSize + mDataSize/2));
}
public void Grow(int32 addSize)
{
GrowReserve(addSize);
mDataSize += addSize;
}
/// This ensures that we have a span that does not wrap around. This ensures we can decode
/// an entire block without wrap checks.
public void EnsureSpan(int32 spanSize)
{
//Debug.WriteLine("Span Size:{0} Tail:{1} DataSize:{2} BufSize:{3}", spanSize, mTail, mDataSize, mBufSize);
if (mDataSize + spanSize > mBufSize)
{
int32 newSize = (int32)Math.Max(mDataSize + spanSize, (int32)mBufSize * 1.5);
//Debug.WriteLine("Resizing {0}", newSize);
Resize(newSize);
return;
}
int head = mTail + mDataSize;
if (head + spanSize > mBufSize)
{
// If we are sufficiently full then keep the buffer at the same size
if (mDataSize + spanSize < mBufSize / 2)
{
// If our buffer doesn't wrap around then we can just pull back inplace
if (mTail + mDataSize <= mBufSize)
{
if (mTail != 0)
{
//Debug.WriteLine("MemMove");
Internal.MemMove(mBuffer, mBuffer + mTail, mDataSize);
mTail = 0;
}
return;
}
//Debug.WriteLine("MakeLinear");
MakeLinear();
return;
}
int32 newSize = (int32)Math.Max(mDataSize + spanSize, (int32)mBufSize * 1.5);
//Debug.WriteLine("Resizing2 {0}", newSize);
Resize(newSize);
return;
}
/*if (mTail + spanSize > mBufSize)
{
/*Debug.WriteLine("EnsureSpan {0} {1} {2} {3}", mTail, spanSize, mBufSize, mDataSize);
// If we are sufficiently full then keep the buffer at the same size
if (mDataSize + spanSize < mBufSize / 2)
{
// If our buffer doesn't wrap around then we can just pull back inplace
if (mTail + mDataSize <= mBufSize)
{
if (mTail != 0)
{
Internal.MemMove(mBuffer, mBuffer + mTail, mDataSize);
mTail = 0;
}
return;
}
MakeLinear();
return;
}*/
int32 newBufSize = (int32)((mTail + spanSize) * 1.5);
Resize(newBufSize);
}*/
}
public void MakeLinear()
{
if (mDataSize == 0)
{
mTail = 0;
return;
}
Resize(mBufSize);
}
public void GrowFront(int32 addSize)
{
if (mDataSize + addSize > mBufSize)
{
Resize(mDataSize + addSize);
}
mDataSize += addSize;
mTail = (mTail + mBufSize - addSize) % mBufSize;
}
public int32 GetSize()
{
return mDataSize;
}
public int32 GetBufSize()
{
return mBufSize;
}
public uint8* GetPtr()
{
return mBuffer + mTail;
}
public void SetPtr(uint8* ptr)
{
int32 newTail = (int32)(ptr - mBuffer);
mDataSize -= newTail - mTail;
Debug.Assert(mDataSize >= 0);
if (newTail == mBufSize)
newTail = 0;
mTail = newTail;
Debug.Assert(mTail < mBufSize);
}
public void MapView(int32 idx, int32 len, View view)
{
view.mCircularBuffer = this;
view.mSrcIdx = idx;
view.mSrcSize = len;
if (mTail + idx + len <= mBufSize)
{
view.mPtr = mBuffer + mTail + idx;
}
else
{
if (view.mTempBufSize < len)
{
delete view.mTempBuf;
view.mTempBuf = new uint8[len]*;
view.mTempBufSize = len;
}
view.mPtr = view.mTempBuf;
Read(view.mTempBuf, idx, len);
}
}
public void Read(void* ptr, int32 idx, int32 len)
{
Debug.Assert(len <= mBufSize);
if (len == 0)
return;
int absIdx = (mTail + idx) % mBufSize;
if (absIdx + len > mBufSize)
{
int lowSize = mBufSize - absIdx;
Internal.MemCpy(ptr, mBuffer + absIdx, lowSize);
Internal.MemCpy((uint8*)ptr + lowSize, mBuffer, len - lowSize);
}
else
{
Internal.MemCpy(ptr, mBuffer + absIdx, len);
}
}
public uint8 Read()
{
uint8 val = mBuffer[mTail];
mTail = (mTail + 1) % mBufSize;
mDataSize--;
return val;
}
public int64 ReadSLEB128()
{
int64 value = 0;
int32 shift = 0;
int64 curByte;
repeat
{
curByte = Read();
value |= ((curByte & 0x7f) << shift);
shift += 7;
} while (curByte >= 128);
// Sign extend negative numbers.
if (((curByte & 0x40) != 0) && (shift < 64))
value |= ~0L << shift;
return value;
}
public void Write(void* ptr, int32 idx, int32 len)
{
Debug.Assert(len <= mBufSize);
if (len == 0)
return;
int absIdx = (mTail + idx) % mBufSize;
if (absIdx + len > mBufSize)
{
int lowSize = mBufSize - absIdx;
Internal.MemCpy(mBuffer + absIdx, ptr, lowSize);
Internal.MemCpy(mBuffer, (uint8*)ptr + lowSize, len - lowSize);
}
else
{
Internal.MemCpy(mBuffer + absIdx, ptr, len);
}
}
public void RemoveFront(int32 len)
{
mTail = (mTail + len) % mBufSize;
mDataSize -= len;
}
public void RemoveBack(int32 len)
{
mDataSize -= len;
}
}
}

4323
BeefPerf/src/FMod.bf Normal file

File diff suppressed because it is too large Load diff

1892
BeefPerf/src/FMod_DSP.bf Normal file

File diff suppressed because it is too large Load diff

105
BeefPerf/src/FMod_Errors.bf Normal file
View file

@ -0,0 +1,105 @@
using System;
/* =================================================================================================== */
/* FMOD Studio - Error string header file. Copyright (c), Firelight Technologies Pty, Ltd. 2004-2016. */
/* */
/* Use this header if you want to store or display a string version / english explanation of */
/* the FMOD error codes. */
/* */
/* =================================================================================================== */
namespace FMOD
{
public class Error
{
public static String String(FMOD.RESULT errcode)
{
switch (errcode)
{
case FMOD.RESULT.OK: return "No errors.";
case FMOD.RESULT.ERR_BADCOMMAND: return "Tried to call a function on a data type that does not allow this type of functionality (ie calling Sound::lock on a streaming sound).";
case FMOD.RESULT.ERR_CHANNEL_ALLOC: return "Error trying to allocate a channel.";
case FMOD.RESULT.ERR_CHANNEL_STOLEN: return "The specified channel has been reused to play another sound.";
case FMOD.RESULT.ERR_DMA: return "DMA Failure. See debug output for more information.";
case FMOD.RESULT.ERR_DSP_CONNECTION: return "DSP connection error. Connection possibly caused a cyclic dependency or connected dsps with incompatible buffer counts.";
case FMOD.RESULT.ERR_DSP_DONTPROCESS: return "DSP return code from a DSP process query callback. Tells mixer not to call the process callback and therefore not consume CPU. Use this to optimize the DSP graph.";
case FMOD.RESULT.ERR_DSP_FORMAT: return "DSP Format error. A DSP unit may have attempted to connect to this network with the wrong format, or a matrix may have been set with the wrong size if the target unit has a specified channel map.";
case FMOD.RESULT.ERR_DSP_INUSE: return "DSP is already in the mixer's DSP network. It must be removed before being reinserted or released.";
case FMOD.RESULT.ERR_DSP_NOTFOUND: return "DSP connection error. Couldn't find the DSP unit specified.";
case FMOD.RESULT.ERR_DSP_RESERVED: return "DSP operation error. Cannot perform operation on this DSP as it is reserved by the system.";
case FMOD.RESULT.ERR_DSP_SILENCE: return "DSP return code from a DSP process query callback. Tells mixer silence would be produced from read, so go idle and not consume CPU. Use this to optimize the DSP graph.";
case FMOD.RESULT.ERR_DSP_TYPE: return "DSP operation cannot be performed on a DSP of this type.";
case FMOD.RESULT.ERR_FILE_BAD: return "Error loading file.";
case FMOD.RESULT.ERR_FILE_COULDNOTSEEK: return "Couldn't perform seek operation. This is a limitation of the medium (ie netstreams) or the file format.";
case FMOD.RESULT.ERR_FILE_DISKEJECTED: return "Media was ejected while reading.";
case FMOD.RESULT.ERR_FILE_EOF: return "End of file unexpectedly reached while trying to read essential data (truncated?).";
case FMOD.RESULT.ERR_FILE_ENDOFDATA: return "End of current chunk reached while trying to read data.";
case FMOD.RESULT.ERR_FILE_NOTFOUND: return "File not found.";
case FMOD.RESULT.ERR_FORMAT: return "Unsupported file or audio format.";
case FMOD.RESULT.ERR_HEADER_MISMATCH: return "There is a version mismatch between the FMOD header and either the FMOD Studio library or the FMOD Low Level library.";
case FMOD.RESULT.ERR_HTTP: return "A HTTP error occurred. This is a catch-all for HTTP errors not listed elsewhere.";
case FMOD.RESULT.ERR_HTTP_ACCESS: return "The specified resource requires authentication or is forbidden.";
case FMOD.RESULT.ERR_HTTP_PROXY_AUTH: return "Proxy authentication is required to access the specified resource.";
case FMOD.RESULT.ERR_HTTP_SERVER_ERROR: return "A HTTP server error occurred.";
case FMOD.RESULT.ERR_HTTP_TIMEOUT: return "The HTTP request timed out.";
case FMOD.RESULT.ERR_INITIALIZATION: return "FMOD was not initialized correctly to support this function.";
case FMOD.RESULT.ERR_INITIALIZED: return "Cannot call this command after System::init.";
case FMOD.RESULT.ERR_INTERNAL: return "An error occurred that wasn't supposed to. Contact support.";
case FMOD.RESULT.ERR_INVALID_FLOAT: return "Value passed in was a NaN, Inf or denormalized float.";
case FMOD.RESULT.ERR_INVALID_HANDLE: return "An invalid object handle was used.";
case FMOD.RESULT.ERR_INVALID_PARAM: return "An invalid parameter was passed to this function.";
case FMOD.RESULT.ERR_INVALID_POSITION: return "An invalid seek position was passed to this function.";
case FMOD.RESULT.ERR_INVALID_SPEAKER: return "An invalid speaker was passed to this function based on the current speaker mode.";
case FMOD.RESULT.ERR_INVALID_SYNCPOINT: return "The syncpoint did not come from this sound handle.";
case FMOD.RESULT.ERR_INVALID_THREAD: return "Tried to call a function on a thread that is not supported.";
case FMOD.RESULT.ERR_INVALID_VECTOR: return "The vectors passed in are not unit length, or perpendicular.";
case FMOD.RESULT.ERR_MAXAUDIBLE: return "Reached maximum audible playback count for this sound's soundgroup.";
case FMOD.RESULT.ERR_MEMORY: return "Not enough memory or resources.";
case FMOD.RESULT.ERR_MEMORY_CANTPOINT: return "Can't use FMOD_OPENMEMORY_POINT on non PCM source data, or non mp3/xma/adpcm data if FMOD_CREATECOMPRESSEDSAMPLE was used.";
case FMOD.RESULT.ERR_NEEDS3D: return "Tried to call a command on a 2d sound when the command was meant for 3d sound.";
case FMOD.RESULT.ERR_NEEDSHARDWARE: return "Tried to use a feature that requires hardware support.";
case FMOD.RESULT.ERR_NET_CONNECT: return "Couldn't connect to the specified host.";
case FMOD.RESULT.ERR_NET_SOCKET_ERROR: return "A socket error occurred. This is a catch-all for socket-related errors not listed elsewhere.";
case FMOD.RESULT.ERR_NET_URL: return "The specified URL couldn't be resolved.";
case FMOD.RESULT.ERR_NET_WOULD_BLOCK: return "Operation on a non-blocking socket could not complete immediately.";
case FMOD.RESULT.ERR_NOTREADY: return "Operation could not be performed because specified sound/DSP connection is not ready.";
case FMOD.RESULT.ERR_OUTPUT_ALLOCATED: return "Error initializing output device, but more specifically, the output device is already in use and cannot be reused.";
case FMOD.RESULT.ERR_OUTPUT_CREATEBUFFER: return "Error creating hardware sound buffer.";
case FMOD.RESULT.ERR_OUTPUT_DRIVERCALL: return "A call to a standard soundcard driver failed, which could possibly mean a bug in the driver or resources were missing or exhausted.";
case FMOD.RESULT.ERR_OUTPUT_FORMAT: return "Soundcard does not support the specified format.";
case FMOD.RESULT.ERR_OUTPUT_INIT: return "Error initializing output device.";
case FMOD.RESULT.ERR_OUTPUT_NODRIVERS: return "The output device has no drivers installed. If pre-init, FMOD_OUTPUT_NOSOUND is selected as the output mode. If post-init, the function just fails.";
case FMOD.RESULT.ERR_PLUGIN: return "An unspecified error has been returned from a plugin.";
case FMOD.RESULT.ERR_PLUGIN_MISSING: return "A requested output, dsp unit type or codec was not available.";
case FMOD.RESULT.ERR_PLUGIN_RESOURCE: return "A resource that the plugin requires cannot be found. (ie the DLS file for MIDI playback)";
case FMOD.RESULT.ERR_PLUGIN_VERSION: return "A plugin was built with an unsupported SDK version.";
case FMOD.RESULT.ERR_RECORD: return "An error occurred trying to initialize the recording device.";
case FMOD.RESULT.ERR_REVERB_CHANNELGROUP: return "Reverb properties cannot be set on this channel because a parent channelgroup owns the reverb connection.";
case FMOD.RESULT.ERR_REVERB_INSTANCE: return "Specified instance in FMOD_REVERB_PROPERTIES couldn't be set. Most likely because it is an invalid instance number or the reverb doesn't exist.";
case FMOD.RESULT.ERR_SUBSOUNDS: return "The error occurred because the sound referenced contains subsounds when it shouldn't have, or it doesn't contain subsounds when it should have. The operation may also not be able to be performed on a parent sound.";
case FMOD.RESULT.ERR_SUBSOUND_ALLOCATED: return "This subsound is already being used by another sound, you cannot have more than one parent to a sound. Null out the other parent's entry first.";
case FMOD.RESULT.ERR_SUBSOUND_CANTMOVE: return "Shared subsounds cannot be replaced or moved from their parent stream, such as when the parent stream is an FSB file.";
case FMOD.RESULT.ERR_TAGNOTFOUND: return "The specified tag could not be found or there are no tags.";
case FMOD.RESULT.ERR_TOOMANYCHANNELS: return "The sound created exceeds the allowable input channel count. This can be increased using the 'maxinputchannels' parameter in System::setSoftwareFormat.";
case FMOD.RESULT.ERR_TRUNCATED: return "The retrieved string is too long to fit in the supplied buffer and has been truncated.";
case FMOD.RESULT.ERR_UNIMPLEMENTED: return "Something in FMOD hasn't been implemented when it should be! contact support!";
case FMOD.RESULT.ERR_UNINITIALIZED: return "This command failed because System::init or System::setDriver was not called.";
case FMOD.RESULT.ERR_UNSUPPORTED: return "A command issued was not supported by this object. Possibly a plugin without certain callbacks specified.";
case FMOD.RESULT.ERR_VERSION: return "The version number of this file format is not supported.";
case FMOD.RESULT.ERR_EVENT_ALREADY_LOADED: return "The specified bank has already been loaded.";
case FMOD.RESULT.ERR_EVENT_LIVEUPDATE_BUSY: return "The live update connection failed due to the game already being connected.";
case FMOD.RESULT.ERR_EVENT_LIVEUPDATE_MISMATCH: return "The live update connection failed due to the game data being out of sync with the tool.";
case FMOD.RESULT.ERR_EVENT_LIVEUPDATE_TIMEOUT: return "The live update connection timed out.";
case FMOD.RESULT.ERR_EVENT_NOTFOUND: return "The requested event, bus or vca could not be found.";
case FMOD.RESULT.ERR_STUDIO_UNINITIALIZED: return "The Studio::System object is not yet initialized.";
case FMOD.RESULT.ERR_STUDIO_NOT_LOADED: return "The specified resource is not loaded, so it can't be unloaded.";
case FMOD.RESULT.ERR_INVALID_STRING: return "An invalid string was passed to this function.";
case FMOD.RESULT.ERR_ALREADY_LOCKED: return "The specified resource is already locked.";
case FMOD.RESULT.ERR_NOT_LOCKED: return "The specified resource is not locked, so it can't be unlocked.";
case FMOD.RESULT.ERR_RECORD_DISCONNECTED: return "The specified recording driver has been disconnected.";
case FMOD.RESULT.ERR_TOOMANYSAMPLES: return "The length provided exceed the allowable limit.";
default: return "Unknown error.";
}
}
}
}

1173
BeefPerf/src/FindPanel.bf Normal file

File diff suppressed because it is too large Load diff

11
BeefPerf/src/Images.bf Normal file
View file

@ -0,0 +1,11 @@
using Beefy.gfx;
namespace Mercury
{
class Images
{
public static Image sGrass ~ delete _;
public static Image[,] sPeople ~ DeleteContainerAndItems!(_);
public static Image sStone ~ delete _;
}
}

38
BeefPerf/src/MainFrame.bf Normal file
View file

@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Beefy;
using Beefy.gfx;
using Beefy.widgets;
using Beefy.theme;
using Beefy.theme.dark;
namespace BeefPerf
{
public class MainFrame : Widget
{
public StatusBar mStatusBar;
public DarkDockingFrame mDockingFrame;
public this()
{
mStatusBar = new StatusBar();
AddWidget(mStatusBar);
mDockingFrame = (DarkDockingFrame)ThemeFactory.mDefault.CreateDockingFrame();
AddWidget(mDockingFrame);
}
public ~this()
{
}
public override void Resize(float x, float y, float width, float height)
{
base.Resize(x, y, width, height);
int32 statusHeight = 20;
mDockingFrame.Resize(0, 0, width, height - statusHeight);
mStatusBar.Resize(0, mHeight - statusHeight, width, statusHeight);
}
}
}

2507
BeefPerf/src/PerfView.bf Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,805 @@
using Beefy.widgets;
using Beefy.theme.dark;
using Beefy.gfx;
using System.Collections.Generic;
using System;
using System.Diagnostics;
using Beefy.events;
using System.Threading;
using Beefy;
namespace BeefPerf
{
class ProfilePanel : Widget
{
public class ProfileListViewItem : DarkVirtualListViewItem
{
public override void MouseDown(float x, float y, int32 btn, int32 btnCount)
{
((ProfileListView)mListView).mProfilePanel.ItemClicked(this, btn, btnCount, x, y);
}
public override bool Selected
{
set
{
if (value)
{
int32 selectedIdx = mVirtualIdx;
var profilePanel = ((ProfileListView)mListView).mProfilePanel;
var result = profilePanel.mProfileCtx.mResults[selectedIdx];
DeleteAndNullify!(profilePanel.mPerfView.mProfileHiliteZone);
profilePanel.mPerfView.mProfileHiliteZone = new PerfView.HiliteZone(result.mZoneNameId, result.mUnformattedName);
//result.mName
}
base.Selected = value;
//int32 selectedIdx = item.mVirtualIdx;
//var foundEntry = mSearchState.mFoundEntries[selectedIdx];
}
}
}
public class ProfileListView : DarkVirtualListView
{
public ProfilePanel mProfilePanel;
public override void ChangeSort(DarkListView.SortType sortType)
{
base.ChangeSort(sortType);
mSortType = sortType;
mProfilePanel.RefreshList();
}
protected override ListViewItem CreateListViewItem()
{
return new ProfileListViewItem();
}
public override void PopulateVirtualItem(DarkVirtualListViewItem listViewItem)
{
base.PopulateVirtualItem(listViewItem);
var client = mProfilePanel.mPerfView.mSession;
var perfInfo = mProfilePanel.mProfileCtx.mResults[listViewItem.mVirtualIdx];
listViewItem.Label = perfInfo.mName;
var subItem = listViewItem.CreateSubItem(1);
subItem.mLabel = new String();
subItem.mLabel.AppendF("{0}", perfInfo.mCount);
subItem = listViewItem.CreateSubItem(2);
subItem.mLabel = new String();
client.ElapsedTicksToStr(perfInfo.mTicks, subItem.mLabel);
subItem = listViewItem.CreateSubItem(3);
subItem.mLabel = new String();
client.ElapsedTicksToStr(perfInfo.mTicks - perfInfo.mChildTicks, subItem.mLabel);
}
public override void DrawAll(Graphics g)
{
base.DrawAll(g);
if (((mProfilePanel.mProfileCtx != null) && (!mProfilePanel.mProfileCtx.mDone)) || (mProfilePanel.mSorting))
{
using (g.PushColor(0xA0595959))
g.FillRect(0, 20, mWidth - 20, mHeight - 20);
BPUtils.DrawWait(g, mWidth / 2, mHeight / 2);
}
}
public override void KeyDown(KeyCode keyCode, bool isRepeat)
{
base.KeyDown(keyCode, isRepeat);
if (keyCode == .Escape)
{
mProfilePanel.RemoveFocus();
}
}
public void GetSummaryString(String str)
{
str.Append("Name____________________________________Count_______Total________Self\n");
for (var entry in mProfilePanel.mProfileCtx.mResults)
{
str.Append(entry.mName);
str.Append(' ', Math.Max(38 - entry.mName.Length, 1));
var entryStr = scope String();
entry.mCount.ToString(entryStr);
str.Append(' ', Math.Max(7 - entryStr.Length, 1));
str.Append(entryStr);
entryStr.Clear();
mProfilePanel.mProfileCtx.mSession.ElapsedTicksToStr(entry.mTicks, entryStr);
str.Append(' ', Math.Max(12 - entryStr.Length, 1));
str.Append(entryStr);
entryStr.Clear();
mProfilePanel.mProfileCtx.mSession.ElapsedTicksToStr(entry.mTicks - entry.mChildTicks, entryStr);
str.Append(' ', Math.Max(12 - entryStr.Length, 1));
str.Append(entryStr);
str.Append('\n');
}
str.Append("---------------------------------------------------------------------\n");
str.Append("Total Time: ");
mProfilePanel.mProfileCtx.mSession.ElapsedTicksToStr(mProfilePanel.mSelection.mTickEnd - mProfilePanel.mSelection.mTickStart, str);
}
public void AddStaticMenu(Menu menu)
{
var menuItem = menu.AddItem("Copy to Clipboard");
menuItem.mOnMenuItemSelected.Add(new (item) =>
{
String str = scope String();
GetSummaryString(str);
gApp.SetClipboardText(str);
//Debug.WriteLine(str);
});
}
public override void MouseDown(float x, float y, int32 btn, int32 btnCount)
{
base.MouseDown(x, y, btn, btnCount);
GetRoot().SelectItemExclusively(null);
if (btn == 1)
{
Menu menu = new Menu();
AddStaticMenu(menu);
MenuWidget menuWidget = DarkTheme.sDarkTheme.CreateMenuWidget(menu);
menuWidget.Init(this, x, y);
}
}
}
DarkCheckBox mFormatCheckbox;
ProfileListView mListView;
DarkButton mGetButton;
PerfView mPerfView;
BPSelection mSelection;
bool mSelectionDirty;
int64 mActiveLastCurTick;
ProfileContext mProfileCtx ~ delete _;
public WaitEvent mSortDoneHandle = new WaitEvent() ~ delete _;
public bool mSorting;
public this()
{
mFormatCheckbox = new DarkCheckBox();
mFormatCheckbox.Checked = true;
mFormatCheckbox.Label = "Format Strings";
mFormatCheckbox.mOnMouseClick.Add(new [&] (evt) => { mSelectionDirty = true; } );
AddWidget(mFormatCheckbox);
mListView = new ProfileListView();
mListView.mProfilePanel = this;
mListView.mOnLostFocus.Add(new (evt) => { RemoveFocus(); });
mListView.mSortType.mColumn = 2;
mListView.mSortType.mReverse = true;
AddWidget(mListView);
mListView.AddColumn(200, "Name");
mListView.AddColumn(100, "Count");
mListView.AddColumn(150, "Total");
mListView.AddColumn(150, "Self");
mListView.InitScrollbars(false, true);
}
public ~this()
{
FinishSorting();
}
void FinishSorting()
{
if (mSorting)
{
mSortDoneHandle.WaitFor();
mSorting = false;
mSortDoneHandle.Reset();
}
}
public void RemoveFocus()
{
mListView.GetRoot().SelectItemExclusively(null);
if (mPerfView != null)
DeleteAndNullify!(mPerfView.mProfileHiliteZone);
}
public override void Resize(float x, float y, float width, float height)
{
base.Resize(x, y, width, height);
mListView.ResizeClamped(0, 20, width, height - 20);
mFormatCheckbox.Resize(0, 0, 20, 20);
}
public override void Draw(Graphics g)
{
base.Draw(g);
g.SetFont(DarkTheme.sDarkTheme.mSmallFont);
if (mProfileCtx != null)
{
String str = scope String();
str.Append("Total Time: ");
mProfileCtx.mSession.ElapsedTicksToStr(mSelection.mTickEnd - mSelection.mTickStart, str);
g.DrawString(str, 150, 0);
}
//g.DrawBox(DarkTheme.sDarkTheme.GetImage(DarkTheme.ImageIdx.Bkg), 0, 0, mWidth, mHeight);
}
public override void DrawAll(Graphics g)
{
base.DrawAll(g);
}
public void Show(PerfView perfView, BPSelection selection)
{
Debug.Assert(perfView != null);
mPerfView = perfView;
mSelection = selection;
mSelectionDirty = true;
}
void GetData()
{
mListView.GetRoot().Clear();
}
struct PerfInfo
{
public String mName;
public int32 mCount;
public int64 mTicks;
public int64 mChildTicks;
public int32 mStackCount; // Number of times this entry appears in entryStack
public int32 mZoneNameId;
public String mUnformattedName;
}
struct BPPerfEntry
{
public int32 mZoneNameId;
public int64 mTickStart;
public int64 mChildTicks;
public int32 mParamsReadPos;
public PerfInfo* mPerfInfo;
}
class ProfileContext
{
BumpAllocator mAlloc = new BumpAllocator() ~ delete _;
public BpSession mSession;
public Dictionary<String, PerfInfo*> mPerfDict = new .() ~ delete _;
String mTempStr = new String() ~ delete _;
String mTempDynStr = new String() ~ delete _;
public List<PerfInfo*> mResults = new List<PerfInfo*>() ~ delete _;
public List<PerfInfo*> mSortingResults = new List<PerfInfo*>() ~ delete _;
public int32 mStreamDataIdx;
public bool mDone;
public bool mFormatStrings;
public bool mHasSelectionEndChanged;
public PerfInfo* GetPerfInfo(BPPerfEntry entry, BPStateContext stateCtx)
{
int32 paramsSize;
String str;
if (entry.mZoneNameId < 0)
{
int32 nameLen = -entry.mZoneNameId;
str = mTempDynStr;
str.Reference((char8*)stateCtx.mReadStart + entry.mParamsReadPos - nameLen, nameLen, 0);
paramsSize = -1;
}
else
{
let zoneName = mSession.mZoneNames[entry.mZoneNameId];
str = zoneName.mName;
paramsSize = zoneName.mParamsSize;
}
//bool dbgStr = str == "DeepStack0 %d";
String unformattedStr = str;
if ((paramsSize != 0) && (mFormatStrings))
{
mTempStr.Clear();
stateCtx.FormatStr(entry.mParamsReadPos, paramsSize, str, mTempStr);
str = mTempStr;
}
String* keyPtr;
PerfInfo* perfInfo;
PerfInfo** perfInfoPtr;
if (mPerfDict.TryAdd(str, out keyPtr, out perfInfoPtr))
{
perfInfo = new:mAlloc PerfInfo();
*perfInfoPtr = perfInfo;
if (str == (Object)mTempStr)
{
String heapStr = new:mAlloc String(str);
*keyPtr = heapStr;
perfInfo.mName = heapStr;
}
else
perfInfo.mName = str;
if ((unformattedStr == (Object)mTempDynStr) && (unformattedStr != (Object)mTempStr))
{
String heapStr = new:mAlloc String(unformattedStr);
perfInfo.mUnformattedName = heapStr;
}
else
perfInfo.mUnformattedName = unformattedStr;
perfInfo.mZoneNameId = entry.mZoneNameId;
}
else
{
perfInfo = *perfInfoPtr;
if ((entry.mZoneNameId > 0) && (perfInfo.mZoneNameId < 0))
{
// Set to a valid zoneNameId if the inserting entry was a dynamic string but this one isn't
perfInfo.mZoneNameId = entry.mZoneNameId;
}
}
return perfInfo;
}
}
void UpdateProfileCtx()
{
Stopwatch stopwatch = scope Stopwatch();
stopwatch.Start();
var client = mPerfView.mSession;
var thread = client.mThreads[mSelection.mThreadIdx];
bool isRecording = false;
bool isFirstDrawn = mProfileCtx.mStreamDataIdx == 0;
bool isManualSelection = mSelection.mDepth == -1;
StreamLoop: while (mProfileCtx.mStreamDataIdx < thread.mStreamDataList.Count)
{
int streamDataListIdx = mProfileCtx.mStreamDataIdx++;
var streamData = thread.mStreamDataList[streamDataListIdx];
if ((streamData.mSplitTick > 0) && (streamData.mSplitTick < mSelection.mTickStart))
continue; // All data is to the left
BPStateContext stateCtx = scope BPStateContext(client, streamData);
List<BPPerfEntry> entryStack = scope List<BPPerfEntry>();
int32 stackDepth = 0;
CmdLoop: while (true)
{
switch (stateCtx.GetNextEvent())
{
case let .Enter(startTick, strIdx):
int stackPos = stackDepth++;
if (((mSelection.mDepth == -1) && (startTick >= mSelection.mTickStart)) ||
((startTick == mSelection.mTickStart) && (stackPos == mSelection.mDepth)))
{
isRecording = true;
}
if (isRecording)
{
BPPerfEntry entry;
entry.mTickStart = startTick;
entry.mZoneNameId = strIdx;
entry.mChildTicks = 0;
//stateCtx.MoveToParamData();
entry.mParamsReadPos = stateCtx.ReadPos;
entry.mPerfInfo = null;
entry.mPerfInfo = mProfileCtx.GetPerfInfo(entry, stateCtx);
entry.mPerfInfo.mStackCount++;
entryStack.Add(entry);
}
case let .Leave(endTick):
stackDepth--;
if (isRecording)
{
let entry = entryStack.PopBack();
entry.mPerfInfo.mStackCount--;
if ((entry.mTickStart == mSelection.mTickStart) && (stackDepth == mSelection.mDepth))
{
if (client.mCurTick == endTick)
{
mProfileCtx.mHasSelectionEndChanged = true;
mSelection.mTickEnd = endTick;
}
}
int64 ticks = endTick - entry.mTickStart;
if (isManualSelection)
{
int64 tickStart = Math.Max(entry.mTickStart, mSelection.mTickStart);
int64 tickEnd = Math.Min(endTick, mSelection.mTickEnd);
ticks = tickEnd - tickStart;
if (ticks <= 0)
continue;
}
//PerfInfo* perfInfo = mProfileCtx.GetPerfInfo(entry, stateCtx);
PerfInfo* perfInfo = entry.mPerfInfo;
bool isOld = ((entry.mTickStart <= streamData.mStartTick) && (stackDepth < stateCtx.mSplitCarryoverCount));
// Is this a duplicate spanned entry? If so, we don't add it's stats but we still
// must process it as a parent to keep track of mChildTicks for new children
if ((isFirstDrawn) || (!isOld))
{
perfInfo.mCount++;
if (perfInfo.mStackCount != 0)
{
// Total time is already handled by outer scope
}
else
perfInfo.mTicks += ticks;
}
if (entryStack.Count > 0)
{
var prevEntry = entryStack[entryStack.Count - 1];
//bool prevIsOld = ((prevEntry.mTickStart <= streamData.mStartTick) && (stackDepth - 1 < stateCtx.mSplitCarryoverCount));
//if ((isFirstDrawn) || (!prevIsOld))
if (!isOld)
{
//PerfInfo* prevPerfInfo = mProfileCtx.GetPerfInfo(prevEntry, stateCtx);
PerfInfo* prevPerfInfo = prevEntry.mPerfInfo;
prevPerfInfo.mChildTicks += ticks;
if (perfInfo.mStackCount != 0)
{
// We have an instance of ourselves on an outer scope, so time we thought was child time actually isn't
perfInfo.mChildTicks -= ticks;
}
}
}
if (isManualSelection)
{
if ((stackDepth == 0) && (endTick >= mSelection.mTickEnd))
{
if (endTick <= streamData.mSplitTick)
break StreamLoop;
}
//isRecording = false;
}
if (stackDepth <= mSelection.mDepth)
{
if (endTick <= streamData.mSplitTick)
break StreamLoop;
isRecording = false;
}
}
case .EndOfStream:
break CmdLoop;
default:
}
}
isFirstDrawn = false;
if (stopwatch.ElapsedMilliseconds > (int)(gApp.mTimePerFrame * 1000))
{
return;
}
}
mProfileCtx.mDone = true;
for (var value in mProfileCtx.mPerfDict.Keys)
{
var perfInfo = @value.Value;
Debug.Assert(perfInfo.mName != null);
mProfileCtx.mResults.Add(perfInfo);
}
RefreshList();
}
void RefreshData()
{
mListView.GetRoot().Clear();
if (mPerfView == null)
return;
var session = mPerfView.mSession;
delete mProfileCtx;
mProfileCtx = new ProfileContext();
mProfileCtx.mFormatStrings = mFormatCheckbox.Checked;
mProfileCtx.mSession = session;
}
public void ItemClicked(ProfileListViewItem clickedItem, int32 btn, int32 btnCount, float x, float y)
{
if (clickedItem.mParentItem == null)
return;
ProfileListViewItem item = (ProfileListViewItem)clickedItem.GetSubItem(0);
mListView.GetRoot().SelectItemExclusively(item);
mListView.SetFocus();
if (btn == 1)
{
Menu menu = new Menu();
var menuItem = menu.AddItem("Find Instances");
menuItem.mOnMenuItemSelected.Add(new (selectedItem) =>
{
var find = gApp.mFindPanel;
var str = scope String();
var client = mPerfView.mSession;
var thread = client.mThreads[mSelection.mThreadIdx];
str.Clear();
var perfInfo = mProfileCtx.mResults[item.mVirtualIdx];
str.Append('=');
str.Append(perfInfo.mName);
if (str.Contains(' '))
{
str.Insert(1, '\"');
str.Append('"');
}
if (mSelection.mDepth > 0)
{
str.Append(" Depth>");
mSelection.mDepth.ToString(str);
}
find.mEntryEdit.SetText(str);
str.Clear();
str.Append('=');
thread.GetName(str);
if (str.Contains(' '))
{
str.Insert(1, '\"');
str.Append('"');
}
find.mTrackEdit.mEditWidget.SetText(str);
str.Clear();
client.TicksToStr(mSelection.mTickStart, str);
find.mTimeFromEdit.mEditWidget.SetText(str);
str.Clear();
client.TicksToStr(mSelection.mTickEnd, str);
find.mTimeToEdit.mEditWidget.SetText(str);
find.mFormatCheckbox.Checked = true;
find.mZonesCheckbox.Checked = true;
find.mEventsCheckbox.Checked = false;
find.mNeedsRestartSearch = true;
});
menu.AddItem();
mListView.AddStaticMenu(menu);
MenuWidget menuWidget = DarkTheme.sDarkTheme.CreateMenuWidget(menu);
menuWidget.Init(clickedItem, x, y);
}
}
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) && (theEvent.mBtnCount > 1))
{
for (int32 childIdx = 1; childIdx < mListView.GetRoot().GetChildCount(); childIdx++)
{
var checkListViewItem = mListView.GetRoot().GetChildAtIndex(childIdx);
checkListViewItem.IconImage = null;
}
/*int32 selectedIdx = item.mVirtualIdx;
var foundEntry = mSearchState.mFoundEntries[selectedIdx];
mPerfView.ZoomTo(foundEntry.mStartTick, foundEntry.mEndTick);
BPSelection selection;
selection.mStartTick = foundEntry.mStartTick;
selection.mEndTick = foundEntry.mEndTick;
selection.mDepth = foundEntry.mDepth;
selection.mThreadIdx = foundEntry.mTrackIdx;
mPerfView.mSelection = selection;*/
}
if (theEvent.mBtn == 1)
{
Menu menu = new Menu();
#unwarn
var menuItem = menu.AddItem("Set Track Color ...");
MenuWidget menuWidget = DarkTheme.sDarkTheme.CreateMenuWidget(menu);
menuWidget.Init(this, theEvent.mX, theEvent.mY);
}
}
int EntryCompare(PerfInfo* lhs, PerfInfo* rhs)
{
int64 result = 0;
if (mListView.mSortType.mColumn == 0)
{
result = String.Compare(lhs.mName, rhs.mName, true);
if (result == 0)
result = lhs.mTicks - rhs.mTicks;
}
else if (mListView.mSortType.mColumn == 1)
{
result = lhs.mCount - rhs.mCount;
}
else if (mListView.mSortType.mColumn == 2)
{
result = lhs.mTicks - rhs.mTicks;
}
else
{
result = (lhs.mTicks - lhs.mChildTicks) - (rhs.mTicks - rhs.mChildTicks);
}
if (mListView.mSortType.mReverse)
result = -result;
return (int)result;
}
void SortList()
{
mProfileCtx.mSortingResults.Sort(scope => EntryCompare);
mSortDoneHandle.Set(true);
}
void CheckSorting(int32 waitMS = 0)
{
if (mSorting)
{
if (mSortDoneHandle.WaitFor(waitMS))
{
mSorting = false;
mSortDoneHandle.Reset();
Debug.Assert(mProfileCtx.mResults.Count == mProfileCtx.mSortingResults.Count);
Swap!(mProfileCtx.mResults, mProfileCtx.mSortingResults);
mListView.GetRoot().Clear();
if (mProfileCtx.mResults.Count > 0)
{
var listViewItem = (DarkVirtualListViewItem)mListView.GetRoot().CreateChildItem();
listViewItem.mVirtualHeadItem = listViewItem;
listViewItem.mVirtualCount = (int32)mProfileCtx.mResults.Count;
mListView.PopulateVirtualItem(listViewItem);
}
}
}
}
void RefreshList()
{
if (mPerfView == null)
{
mListView.GetRoot().Clear();
return;
}
FinishSorting();
mSorting = true;
mProfileCtx.mSortingResults.Clear();
mProfileCtx.mSortingResults.GrowUnitialized(mProfileCtx.mResults.Count);
for (int i < mProfileCtx.mResults.Count)
mProfileCtx.mSortingResults[i] = mProfileCtx.mResults[i];
ThreadPool.QueueUserWorkItem(new => SortList);
CheckSorting(20);
/*for (var strId in iList)
{
var childItem = mListView.GetRoot().CreateChildItem();
childItem.Label = strId;
var perfInfo = ref perfDict[strId];
var subItem = childItem.CreateSubItem(1);
subItem.mLabel = new String();
subItem.mLabel.FormatInto("{0}", perfInfo.mCount);
subItem.mMouseDownHandler.Add(new => ValueClicked);
subItem = childItem.CreateSubItem(2);
subItem.mLabel = new String();
client.ElapsedTicksToStr(perfInfo.mTicks, subItem.mLabel);
subItem.mMouseDownHandler.Add(new => ValueClicked);
subItem = childItem.CreateSubItem(3);
subItem.mLabel = new String();
client.ElapsedTicksToStr(perfInfo.mTicks - perfInfo.mChildTicks, subItem.mLabel);
subItem.mMouseDownHandler.Add(new => ValueClicked);
}*/
}
public override void Update()
{
base.Update();
if (mPerfView == null)
return;
// Were we awaiting more data to refresh
var client = mPerfView.mSession;
if ((mActiveLastCurTick != 0) && (mActiveLastCurTick != client.mCurTick))
{
mActiveLastCurTick = 0;
mSelectionDirty = true;
}
if (mSorting)
{
CheckSorting();
}
else
{
if ((mSelectionDirty) && (gApp.mIsUpdateBatchStart) &&
((mProfileCtx == null) || (mProfileCtx.mDone)))
{
mSelectionDirty = false;
RefreshData();
}
if ((mProfileCtx != null) && (!mProfileCtx.mDone))
{
if (gApp.mIsUpdateBatchStart)
UpdateProfileCtx();
if (mProfileCtx.mDone)
{
if (mProfileCtx.mHasSelectionEndChanged)
{
mActiveLastCurTick = client.mCurTick;
}
else
mActiveLastCurTick = 0;
}
}
}
}
public void Clear()
{
mListView.GetRoot().Clear();
DeleteAndNullify!(mProfileCtx);
mActiveLastCurTick = 0;
mPerfView = null;
}
}
}

24
BeefPerf/src/Program.bf Normal file
View file

@ -0,0 +1,24 @@
//
using System;
using System.Threading;
namespace BeefPerf
{
class Program
{
public static int32 Main(String[] args)
{
BPApp mApp = new BPApp();
mApp.ParseCommandLine(args);
if (!mApp.mShuttingDown)
{
mApp.Init();
mApp.Run();
mApp.Shutdown();
}
bool failed = mApp.mFailed;
delete mApp;
return failed ? 1 : 0;
}
}
}

View file

@ -0,0 +1,543 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.IO;
using System.Diagnostics;
using Beefy;
using Beefy.widgets;
using Beefy.theme.dark;
namespace BeefPerf
{
[AttributeUsage(.Method, .ReflectAttribute | .AlwaysIncludeTarget, ReflectUser=.All)]
struct BpCommandAttribute : Attribute
{
}
class ScriptManager
{
class Target
{
public class Cmd
{
public MethodInfo mMethodInfo;
public Object mTargetObject;
}
public Dictionary<String, Target> mTargets = new .() ~ DeleteDictionyAndKeysAndItems!(_);
public Dictionary<String, Cmd> mCmds = new .() ~ DeleteDictionyAndKeysAndItems!(_);
}
public class QueuedCmd
{
public bool mHandled = true;
public String mCmd ~ delete _;
public String mSrcFile ~ delete _;
public int mLineNum = -1;
public int mIntParam;
public bool mNoWait;
}
ScriptHelper mScriptHelper = new ScriptHelper() ~ delete _;
Target mRoot = new Target() ~ delete _;
Dictionary<String, Variant> mVars = new .() ~
{
for (var kv in _)
{
delete kv.key;
kv.value.Dispose();
}
delete _;
};
List<QueuedCmd> mCmdList = new .() ~ DeleteContainerAndItems!(_);
bool mFailed;
public QueuedCmd mCurCmd;
public Stopwatch mTimeoutStopwatch ~ delete _;
public int mTimeoutMS;
public String mExpectingError ~ delete _;
public bool mHadExpectingError;
public int mDoneTicks;
public bool mRunningCommand;
public bool Failed
{
get
{
return mFailed;
}
}
public bool HasQueuedCommands
{
get
{
return !mCmdList.IsEmpty;
}
}
public this()
{
AddTarget(mScriptHelper);
//Exec("OutputLine(\"Hey bro!\", 2)");
}
public void MarkNotHandled()
{
if (mCurCmd != null)
mCurCmd.mHandled = false;
}
public void AddTarget(Object targetObject)
{
var targetType = targetObject.GetType();
for (var methodInfo in targetType.GetMethods(.Instance | .Public | .NonPublic))
{
var methodName = methodInfo.Name;
if (methodName.StartsWith("Cmd_"))
methodName = .(methodName, 4);
Target curTarget = mRoot;
for (var cmdPart in methodName.Split('_'))
{
if (@cmdPart.HasMore)
{
String* keyPtr;
Target* targetPtr;
if (curTarget.mTargets.TryAdd(scope String(cmdPart), out keyPtr, out targetPtr))
{
*keyPtr = new String(cmdPart);
*targetPtr = new Target();
}
curTarget = *targetPtr;
}
else
{
String* keyPtr;
Target.Cmd* cmdPtr;
if (curTarget.mCmds.TryAdd(scope String(cmdPart), out keyPtr, out cmdPtr))
{
*keyPtr = new String(cmdPart);
*cmdPtr = new .();
let cmd = *cmdPtr;
cmd.mMethodInfo = methodInfo;
cmd.mTargetObject = targetObject;
}
}
}
}
}
public void Fail(StringView err)
{
if (mFailed)
return;
mFailed = true;
var errStr = scope String(err);
if (mCurCmd != null)
{
errStr.AppendF(" at line {0} in {1}\n\t{2}", mCurCmd.mLineNum + 1, mCurCmd.mSrcFile, mCurCmd.mCmd);
}
gApp.Fail(errStr);
}
public void Fail(StringView fmt, params Object[] args)
{
Fail(scope String()..AppendF(fmt, params args));
}
public void Clear()
{
DeleteAndClearItems!(mCmdList);
}
public void QueueCommandFile(StringView filePath)
{
int lineNum = 0;
let streamReader = scope StreamReader();
if (streamReader.Open(filePath) case .Err)
{
Fail("Unable to open command file '{0}'", filePath);
return;
}
for (var lineResult in streamReader.Lines)
{
switch (lineResult)
{
case .Ok(var line):
line.Trim();
if ((!line.IsEmpty) && (!line.StartsWith("#")))
{
QueuedCmd queuedCmd = new .();
if (line.StartsWith("nowait "))
{
queuedCmd.mNoWait = true;
line.RemoveFromStart("no wait".Length);
}
queuedCmd.mCmd = new String(line);
queuedCmd.mSrcFile = new String(filePath);
queuedCmd.mLineNum = lineNum;
mCmdList.Add(queuedCmd);
}
lineNum++;
case .Err:
Fail("Failed reading from file '{0}'", filePath);
}
}
}
public void SetTimeoutMS(int timeoutMS)
{
if (mTimeoutStopwatch == null)
{
mTimeoutStopwatch = new .();
mTimeoutStopwatch.Start();
}
mTimeoutMS = timeoutMS;
}
public void Exec(StringView cmdLineView)
{
var cmdLineView;
cmdLineView.Trim();
if ((cmdLineView.StartsWith("#")) || (cmdLineView.IsEmpty))
return;
bool inQuote = false;
for (int i < cmdLineView.Length - 1)
{
char8 c = cmdLineView[i];
if (!inQuote)
{
if (c == '"')
inQuote = true;
else if (c == ';')
{
Exec(.(cmdLineView, 0, i));
cmdLineView.RemoveFromStart(i + 1);
cmdLineView.Trim();
i = -1;
continue;
}
}
else
{
if (c == '\\')
{
i++;
continue;
}
if (c == '"')
{
inQuote = false;
continue;
}
}
}
StringView varName = .();
/*int eqPos = cmdLineView.IndexOf('=');
if (eqPos != -1)
{
varName = StringView(cmdLineView, eqPos);
varName.Trim();
cmdLineView.RemoveFromStart(eqPos + 1);
cmdLineView.Clear();
}*/
StringView methodName;
List<Object> args = scope .();
int parenPos = cmdLineView.IndexOf('(');
if (parenPos != -1)
{
methodName = cmdLineView.Substring(0, parenPos);
methodName.Trim();
int endParenPos = cmdLineView.LastIndexOf(')');
if (endParenPos == -1)
{
Fail("Missing argument end ')'");
return;
}
var postStr = StringView(cmdLineView, endParenPos + 1);
postStr.Trim();
if ((!postStr.IsEmpty) && (!postStr.StartsWith("#")))
{
Fail("Invalid string following command");
return;
}
bool isLiteralString = false;
bool inQuotes = false;
int startIdx = parenPos;
for (int idx = parenPos; idx <= endParenPos; idx++)
{
char8 c = cmdLineView[idx];
if ((c == '\\') && (!isLiteralString))
{
// Skip past slashed strings
idx++;
continue;
}
else if (c == '"')
{
if (!inQuotes)
{
isLiteralString = ((idx > 0) && (cmdLineView[idx - 1] == '@'));
}
inQuotes ^= true;
}
else if (((c == ',') || (c == ')')) && (!inQuotes))
{
StringView argView = cmdLineView.Substring(startIdx + 1, idx - startIdx - 1);
argView.Trim();
if (argView.IsEmpty)
continue;
if ((argView.StartsWith("\"")) || (isLiteralString))
{
var str = scope::String();
if (argView.UnQuoteString(str) case .Err)
Fail("Failed to unquote string");
args.Add(str);
isLiteralString = false;
}
else if (argView.EndsWith('f'))
{
switch (float.Parse(argView))
{
case .Ok(let val):
args.Add(scope:: box val);
case .Err:
Fail("Failed to parse float");
return;
}
}
else if (argView.Contains('.'))
{
switch (double.Parse(argView))
{
case .Ok(let val):
args.Add(scope:: box val);
case .Err:
Fail("Failed to parse double");
return;
}
}
else // Integer
{
switch (int.Parse(argView))
{
case .Ok(let val):
args.Add(scope:: box val);
case .Err:
Fail("Failed to parse int");
return;
}
}
startIdx = idx;
}
}
/*for (var argView in cmdLineView.Substring(parenPos + 1, endParenPos - parenPos - 1).Split(','))
{
HandleArg(argView);
}*/
}
else
{
methodName = cmdLineView;
}
Target curTarget = mRoot;
for (var cmdPart in methodName.Split('.'))
{
if (@cmdPart.HasMore)
{
if (!curTarget.mTargets.TryGetValue(scope String(cmdPart), out curTarget))
{
Fail("Unable to find target '{0}'", cmdPart);
return;
}
}
else
{
bool prevRunningCommand = mRunningCommand;
mRunningCommand = true;
defer { mRunningCommand = prevRunningCommand; }
Target.Cmd cmd;
if (!curTarget.mCmds.TryGetValue(scope String(cmdPart), out cmd))
{
Fail("Unable to find command '{0}'", cmdPart);
return;
}
Object[] argsArr = scope Object[args.Count];
args.CopyTo(argsArr);
switch (cmd.mMethodInfo.Invoke(cmd.mTargetObject, params argsArr))
{
case .Err:
Fail("Failed to invoke command");
return;
case .Ok(var result):
if (!varName.IsEmpty)
{
String* keyPtr;
Variant* valuePtr;
if (mVars.TryAdd(scope String(varName), out keyPtr, out valuePtr))
*keyPtr = new String(varName);
else
valuePtr.Dispose();
*valuePtr = result;
}
else
result.Dispose();
}
}
}
}
public void Update()
{
if (mFailed)
return;
/*if ((mTimeoutMS > 0) && (gApp.mRunningTestScript))
{
if (mTimeoutStopwatch.ElapsedMilliseconds >= mTimeoutMS)
Fail("Script has timed out: {0}ms", mTimeoutStopwatch.ElapsedMilliseconds);
}*/
while ((!mCmdList.IsEmpty) && (!mFailed))
{
mCurCmd = mCmdList[0];
mCurCmd.mHandled = true;
if (!mCurCmd.mNoWait)
{
if (!mScriptHelper.IsPaused())
break;
// Only do a wait for the initial execution
// This is required for things like AssertEvalEquals that will be handled internally by repeated
// calls where 'mHandled = false' is set
mCurCmd.mNoWait = true;
}
Exec(mCurCmd.mCmd);
if (mCmdList.IsEmpty)
break;
if (!mCurCmd.mHandled)
break; // Try again next update
mCmdList.RemoveAt(0);
delete mCurCmd;
mCurCmd = null;
}
}
}
class ScriptHelper
{
public EditWidgetContent.LineAndColumn mMarkedPos;
void FixFilePath(String fileName, String outFilePath)
{
outFilePath.Append(fileName);
if (File.Exists(outFilePath))
return;
outFilePath.Clear();
Path.GetAbsolutePath(fileName, gApp.mInstallDir, outFilePath);
if (!File.Exists(outFilePath))
{
gApp.mScriptManager.Fail("Unable to locate file '{0}'", outFilePath);
}
}
public bool IsPaused()
{
return true;
}
[BpCommand]
public void Nop()
{
}
[BpCommand]
public void Close()
{
gApp.Stop();
}
[BpCommand]
public void CloseIfAutoOpened()
{
if (gApp.mIsAutoOpened)
gApp.Stop();
}
[BpCommand]
public void SelectLastSession()
{
if (gApp.mSessions.IsEmpty)
{
gApp.Fail("No sessions recorded");
return;
}
gApp.SetSession(gApp.mSessions.Back);
}
[BpCommand]
public void SaveSession(String outPath)
{
if (gApp.mCurSession == null)
{
gApp.Fail("No session selected");
return;
}
if (gApp.mCurSession.Save(outPath) case .Err)
gApp.mScriptManager.Fail("Unable to save file '{0}'", outPath);
}
[BpCommand]
public void SaveEntrySummary(String entryName, String outPath)
{
if (gApp.mCurSession == null)
{
gApp.Fail("No session selected");
return;
}
gApp.mBoard.mPerfView.SaveEntrySummary(entryName, outPath);
}
[BpCommand]
public void Stop()
{
gApp.mScriptManager.Clear();
}
[BpCommand]
public void Exit()
{
gApp.Stop();
}
}
}

80
BeefPerf/src/StatusBar.bf Normal file
View file

@ -0,0 +1,80 @@
using Beefy.widgets;
using Beefy.theme.dark;
using System;
namespace BeefPerf
{
class StatusBar : Widget
{
public double mShowTime;
public double mSelTime;
public int64 mSelTick;
public override void Draw(Beefy.gfx.Graphics g)
{
base.Draw(g);
uint32 bkgColor = 0xFF404040;
using (g.PushColor(bkgColor))
g.FillRect(0, 0, mWidth, mHeight);
g.SetFont(DarkTheme.sDarkTheme.mSmallFont);
g.DrawString(scope String..AppendF("FPS: {0}", gApp.mLastFPS), 4, 0);
//
g.DrawString(scope String..AppendF("{0}", gApp.mUpdateCnt), 0, 0, .Right, mWidth - 8);
float curY = 64;
if (!gApp.mListenSocket.IsConnected)
{
using (g.PushColor(0xFFFF4040))
g.DrawString(scope String..AppendF("Failed to listen on port {0}", gApp.mListenPort), 0, 0, .Right, mWidth - 8);
}
else
{
g.DrawString(scope String..AppendF("Clients: {0}", gApp.mClients.Count), curY, 0);
curY += 64;
float kPerSec = gApp.mStatBytesPerSec / 1024.0f;
if ((kPerSec > 0) && (kPerSec < 0.1f))
kPerSec = 0.1f;
g.DrawString(scope String..AppendF("BPS: {0:0.0}k", kPerSec), curY, 0);
curY += 80;
}
if (gApp.mBoard.mPerfView != null)
{
var client = gApp.mBoard.mPerfView.mSession;
g.DrawString(scope String..AppendF("Zones: {0}", (int32)client.mNumZones), curY, 0);
curY += 118;
var str = scope String();
BpClient.TimeToStr(client.TicksToUS(client.mCurTick - client.mFirstTick), str);
g.DrawString(str, curY, 0);
curY += 108;
}
if (mShowTime != 0)
{
var str = scope String();
BpClient.TimeToStr(mShowTime, str);
g.DrawString(str, curY, 0);
curY += 108;
}
if (mSelTime != 0)
{
var str = scope String();
BpClient.ElapsedTimeToStr(mSelTime, str);
g.DrawString(str, curY, 0);
curY += 108;
}
/*{
var str = scope String();
str.FormatInto("{0}", mSelTick);
g.DrawString(str, 550, 0);
}*/
}
}
}

View file

@ -0,0 +1,234 @@
using Beefy.widgets;
using Beefy.gfx;
using Beefy.theme.dark;
using System;
namespace BeefPerf
{
class WorkspaceWidget : Widget
{
DarkEditWidget mClientEdit;
DarkEditWidget mSessionEdit;
DarkCheckBox mEnableCB;
int EntriesYStart
{
get
{
return GS!(110);
}
}
public this()
{
mEnableCB = new DarkCheckBox();
mEnableCB.Label = "Allow Connections";
mEnableCB.Checked = gApp.Listening;
mEnableCB.mOnValueChanged.Add(new () =>
{
gApp.Listening = mEnableCB.Checked;
mEnableCB.Checked = gApp.Listening;
});
AddWidget(mEnableCB);
mClientEdit = new DarkEditWidget();
AddWidget(mClientEdit);
mSessionEdit = new DarkEditWidget();
AddWidget(mSessionEdit);
}
public override void Draw(Graphics g)
{
base.Draw(g);
g.DrawString("Client Name Filter", mClientEdit.mX, mClientEdit.mY - GS!(20));
g.DrawString("Session Name Filter", mSessionEdit.mX, mSessionEdit.mY - GS!(20));
/*using (g.PushColor(0xFFFF0000))
g.FillRect(0, 0, mWidth, mHeight);
using (g.PushColor(0xFFFF00FF))
g.FillRect(0, 0, mWidth, 20);*/
g.SetFont(DarkTheme.sDarkTheme.mSmallFont);
float curY = EntriesYStart;
float boxHeight = 60;
for (int clientIdx = gApp.mSessions.Count - 1; clientIdx >= 0; clientIdx--)
{
var session = gApp.mSessions[clientIdx];
using (g.PushTranslate(8, curY))
{
float width = Math.Max(mParent.mWidth - 16, 140);
g.DrawBox(DarkTheme.sDarkTheme.GetImage(.Bkg), 0, 0, width, boxHeight);
if (gApp.mCurSession == session)
{
using (g.PushColor(DarkTheme.COLOR_SELECTED_OUTLINE))
g.DrawBox(DarkTheme.sDarkTheme.GetImage(DarkTheme.ImageIdx.Outline), 0, 0, width, boxHeight);
}
var sessionName = scope String();
if (session.mSessionName != null)
sessionName.Append(session.mSessionName);
if (sessionName.IsWhiteSpace)
sessionName.Append("<Unnamed Session>");
var clientName = scope String();
if (session.mClientName != null)
clientName.Append(session.mClientName);
if (clientName.IsWhiteSpace)
clientName.Append("<Unnamed Client>");
//clientName.Append("This makes it a long string!");
//sessionName.Append("LONG!!!!");
float clientNameWidth = Math.Min(g.mFont.GetWidth(clientName), width * 0.5f);
g.DrawString(sessionName, 4, 2, .Left, width - clientNameWidth - GS!(12), .Ellipsis);
g.DrawString(clientName, width - clientNameWidth - 4, 2, .Left, clientNameWidth, .Ellipsis);
if (!session.mSessionOver)
{
//using (g.PushColor(((mUpdateCnt / 20) % 2 == 0) ? 0xFFFFFFFF : 0xD0FFFFFF))
//g.Draw(DarkTheme.sDarkTheme.GetImage(.RedDot), width - 20, 0);
using (g.PushColor(((mUpdateCnt / 20) % 2 == 0) ? 0xFFFF0000 : 0xD0FF0000))
{
//g.Draw(DarkTheme.sDarkTheme.GetImage(.RedDot), width - 20, 0);
g.FillRect(-3, 0, 5, boxHeight);
}
}
var startTime = scope String();
session.mConnectTime.ToString(startTime, "dddd, MMMM d, yyyy @ h:mm:ss tt");
g.DrawString(startTime, 4, 20, .Left, width - 8, .Ellipsis);
String timeStr = scope String();
double timeUS = session.TicksToUS(session.mCurTick - session.mFirstTick);
BpClient.TimeToStr(timeUS, timeStr, false);
g.DrawString(timeStr, 4, 40, .Left, width - 8, .Ellipsis);
g.DrawString(scope String..AppendF("BPS: {0}k", session.mDispBPS), 4, 40, .Right, width - 8);
}
curY += boxHeight + 4;
}
}
BpSession GetSessionIdxAt(float x, float y)
{
if ((x < 8) || (x > mWidth - 8))
return null;
int32 boxHeight = 60;
int32 spacing = boxHeight + 4;
int yStart = EntriesYStart;
if (y < yStart)
return null;
int idx = (int32)(y - yStart) / spacing;
int ofs = (int32)(y - yStart) % spacing;
if (idx >= gApp.mSessions.Count)
return null;
if (ofs > boxHeight)
return null;
return gApp.mSessions[gApp.mSessions.Count - idx - 1];
}
public float GetWantHeight()
{
int32 boxHeight = 60;
int32 spacing = boxHeight + 4;
return EntriesYStart + spacing * gApp.mSessions.Count;
}
public override void MouseDown(float x, float y, int32 btn, int32 btnCount)
{
base.MouseDown(x, y, btn, btnCount);
var session = GetSessionIdxAt(x, y);
if (session == null)
return;
gApp.SetSession(session);
}
public override void Resize(float x, float y, float width, float height)
{
base.Resize(x, y, width, height);
ResizeComponents();
}
public void ResizeComponents()
{
mEnableCB.Resize(4, 0, mWidth - 8, 20);
mClientEdit.Resize(4, 40, mWidth - 8, 20);
mSessionEdit.Resize(4, 80, mWidth - 8, 20);
}
}
class WorkspacePanel : Widget
{
ScrollableWidget mScrollableWidget;
WorkspaceWidget mWorkspaceWidget;
public this()
{
mWorkspaceWidget = new WorkspaceWidget();
mWorkspaceWidget.Resize(0, 0, 200, 200);
mScrollableWidget = new ScrollableWidget();
mScrollableWidget.InitScrollbars(false, true);
mScrollableWidget.mScrollContent = mWorkspaceWidget;
mScrollableWidget.mScrollContentContainer.AddWidget(mWorkspaceWidget);
AddWidget(mScrollableWidget);
}
public override void Draw(Graphics g)
{
base.Draw(g);
//g.DrawBox(DarkTheme.sDarkTheme.GetImage(.Bkg), 0, 0, mWidth, mHeight);
}
public override void Resize(float x, float y, float width, float height)
{
base.Resize(x, y, width, height);
mScrollableWidget.Resize(0, 0, width, height - 0);
}
public override void Update()
{
base.Update();
mWorkspaceWidget.mWidth = mWorkspaceWidget.mParent.mWidth;
mWorkspaceWidget.mHeight = mWorkspaceWidget.GetWantHeight();
}
public bool PassesFilter(BpClient client)
{
String clientFilter = scope .();
mWorkspaceWidget.[Friend]mClientEdit.GetText(clientFilter);
clientFilter.Trim();
if (!clientFilter.IsEmpty)
{
if ((client.mClientName == null) || (!client.mClientName.Contains(clientFilter, true)))
return false;
}
String sessionFilter = scope .();
mWorkspaceWidget.[Friend]mSessionEdit.GetText(sessionFilter);
sessionFilter.Trim();
if (!sessionFilter.IsEmpty)
{
if ((client.mSessionName == null) || (!client.mSessionName.Contains(sessionFilter, true)))
return false;
}
return true;
}
}
}