mirror of
https://github.com/beefytech/Beef.git
synced 2025-06-10 12:32:20 +02:00
543 lines
11 KiB
Beef
543 lines
11 KiB
Beef
using System;
|
|
using System.Collections;
|
|
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 .() ~ DeleteDictionaryAndKeysAndValues!(_);
|
|
public Dictionary<String, Cmd> mCmds = new .() ~ DeleteDictionaryAndKeysAndValues!(_);
|
|
}
|
|
|
|
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()
|
|
{
|
|
ClearAndDeleteItems!(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();
|
|
}
|
|
}
|
|
}
|