mirror of
https://github.com/beefytech/Beef.git
synced 2025-06-17 15:46:05 +02:00
1635 lines
41 KiB
Beef
1635 lines
41 KiB
Beef
#define LINEAR_OPT
|
|
|
|
using System.Threading;
|
|
using System;
|
|
using System.Collections;
|
|
using System.Diagnostics;
|
|
using System.Net;
|
|
using Beefy;
|
|
using System.IO;
|
|
|
|
namespace BeefPerf
|
|
{
|
|
enum BpCmd : uint8
|
|
{
|
|
Init,
|
|
Enter,
|
|
EnterDyn,
|
|
Leave,
|
|
LODSmallEntry,
|
|
StrEntry,
|
|
ThreadAdd,
|
|
ThreadRemove,
|
|
SetThread,
|
|
ThreadName,
|
|
FrameTick,
|
|
PrevFrameTick,
|
|
KeepAlive,
|
|
ClockInfo,
|
|
StreamSplitInfo,
|
|
Event,
|
|
LODEvent,
|
|
Cmd,
|
|
}
|
|
|
|
class BpStreamData
|
|
{
|
|
public List<uint8> mBuffer ~ delete _;
|
|
|
|
public int64 mStartTick;
|
|
public int64 mEndTick;
|
|
public int64 mSplitTick;
|
|
public int64 mSizeOverride = -1; // To reserve size to close out entries
|
|
public int32 mPendingLeaveDepth;
|
|
public int32 mNumZones;
|
|
public int64 mMinTicks;
|
|
|
|
public void Write(uint8 val)
|
|
{
|
|
mBuffer.Add(val);
|
|
}
|
|
|
|
public void Write(void* ptr, int size)
|
|
{
|
|
int curSize = mBuffer.Count;
|
|
mBuffer.GrowUnitialized((int32)size);
|
|
var destPtr = &mBuffer[curSize];
|
|
Internal.MemCpy(destPtr, ptr, size);
|
|
}
|
|
|
|
public void WriteTickDelta(int64 tick)
|
|
{
|
|
int64 encodedDelta = (tick - mEndTick) / BpClient.cBufTickScale;
|
|
WriteSLEB128(encodedDelta);
|
|
mEndTick += encodedDelta * BpClient.cBufTickScale;
|
|
}
|
|
|
|
public void WriteSLEB128(int64 value)
|
|
{
|
|
int64 valLeft = value;
|
|
bool hasMore;
|
|
repeat
|
|
{
|
|
uint8 curByte = (uint8)(valLeft & 0x7f);
|
|
valLeft >>= 7;
|
|
hasMore = !((((valLeft == 0) && ((curByte & 0x40) == 0)) ||
|
|
((valLeft == -1) && ((curByte & 0x40) != 0))));
|
|
if (hasMore)
|
|
curByte |= 0x80;
|
|
mBuffer.Add(curByte);
|
|
}
|
|
while (hasMore);
|
|
}
|
|
}
|
|
|
|
class BpStreamLOD
|
|
{
|
|
struct LODEntry
|
|
{
|
|
public int32 mZoneNameId;
|
|
public int32 mParamsReadPos;
|
|
public int64 mStartTick;
|
|
public bool mWritten;
|
|
}
|
|
|
|
struct LODSmallEntry
|
|
{
|
|
public int64 mStartTick;
|
|
public int64 mEndTick;
|
|
}
|
|
|
|
public int64 mMinTicks;
|
|
public List<BpStreamData> mStreamDataList = new List<BpStreamData>() ~ DeleteContainerAndItems!(_);
|
|
|
|
void FlushSmallEntry(BpStreamData destStreamData, List<LODSmallEntry> smallEntryStack, int32 stackDepth)
|
|
{
|
|
if (stackDepth >= smallEntryStack.Count)
|
|
return;
|
|
var smallEntry = ref smallEntryStack[stackDepth];
|
|
if (smallEntry.mStartTick == 0)
|
|
return;
|
|
|
|
destStreamData.Write((uint8)BpCmd.LODSmallEntry);
|
|
destStreamData.WriteTickDelta(smallEntry.mStartTick);
|
|
destStreamData.WriteTickDelta(smallEntry.mEndTick);
|
|
destStreamData.WriteSLEB128(stackDepth);
|
|
|
|
smallEntry.mStartTick = 0;
|
|
}
|
|
|
|
void FlushLODEntry(BpCmdTarget cmdTarget, BpStreamData srcStreamData, BpStreamData destStreamData, List<LODEntry> entryStack, List<LODSmallEntry> smallEntryList)
|
|
{
|
|
int32 stackDepth;
|
|
for (stackDepth = (int32)entryStack.Count - 1; stackDepth >= 0; stackDepth--)
|
|
{
|
|
var entry = ref entryStack[stackDepth];
|
|
if (entry.mWritten)
|
|
break;
|
|
}
|
|
stackDepth++;
|
|
|
|
while (stackDepth < entryStack.Count)
|
|
{
|
|
FlushSmallEntry(destStreamData, smallEntryList, stackDepth);
|
|
var entry = ref entryStack[stackDepth];
|
|
destStreamData.mNumZones++;
|
|
|
|
destStreamData.Write((uint8)BpCmd.Enter);
|
|
destStreamData.WriteTickDelta(entry.mStartTick);
|
|
destStreamData.WriteSLEB128(entry.mZoneNameId);
|
|
|
|
int32 paramSize = 0;
|
|
|
|
if (entry.mZoneNameId >= 0)
|
|
{
|
|
let zoneName = cmdTarget.mSession.mZoneNames[entry.mZoneNameId];
|
|
paramSize = zoneName.mParamsSize;
|
|
}
|
|
else if (entry.mZoneNameId < 0)
|
|
{
|
|
int32 nameLen = -entry.mZoneNameId;
|
|
destStreamData.Write(&srcStreamData.mBuffer[entry.mParamsReadPos - nameLen], nameLen);
|
|
paramSize = -1;
|
|
}
|
|
|
|
int paramReadPos = entry.mParamsReadPos;
|
|
if (paramSize == -1)
|
|
{
|
|
uint8* paramPtr = &srcStreamData.mBuffer[paramReadPos];
|
|
uint8* readPtr = paramPtr;
|
|
paramSize = (int32)Utils.DecodeInt64(ref readPtr);
|
|
destStreamData.WriteSLEB128(paramSize);
|
|
paramReadPos += readPtr - paramPtr;
|
|
}
|
|
|
|
if (paramSize != 0)
|
|
{
|
|
destStreamData.Write(&srcStreamData.mBuffer[paramReadPos], paramSize);
|
|
}
|
|
|
|
entry.mWritten = true;
|
|
stackDepth++;
|
|
}
|
|
}
|
|
|
|
public BpStreamData DoGenerate(BpCmdTarget cmdTarget, List<BpStreamData> srcStreamDataList, int32 idx, int64 overrideMinTicks = -1)
|
|
{
|
|
int64 minTicks = (overrideMinTicks != -1) ? overrideMinTicks : mMinTicks;
|
|
if (minTicks == -1)
|
|
minTicks = mMinTicks;
|
|
|
|
if (idx <= mStreamDataList.Count)
|
|
{
|
|
if (mStreamDataList.Capacity == 0)
|
|
mStreamDataList.Capacity = srcStreamDataList.Count;
|
|
else
|
|
mStreamDataList.EnsureCapacity(srcStreamDataList.Count, true);
|
|
mStreamDataList.GrowUnitialized(idx - (int32)mStreamDataList.Count + 1);
|
|
}
|
|
|
|
let tempData = scope List<uint8>(srcStreamDataList.Count);
|
|
|
|
let srcStreamData = srcStreamDataList[idx];
|
|
let destStreamData = new BpStreamData();
|
|
destStreamData.mMinTicks = minTicks;
|
|
destStreamData.mBuffer = tempData;
|
|
destStreamData.mStartTick = srcStreamData.mStartTick;
|
|
destStreamData.mEndTick = destStreamData.mStartTick; // Init to startTick for proper encoding of tick deltas
|
|
destStreamData.mSplitTick = srcStreamData.mSplitTick;
|
|
mStreamDataList[idx] = destStreamData;
|
|
|
|
BPStateContext stateCtx = scope BPStateContext(cmdTarget.mSession, srcStreamData);
|
|
stateCtx.mAutoLeave = false;
|
|
|
|
List<LODEntry> entryStack = scope List<LODEntry>();
|
|
List<LODSmallEntry> smallEntryStack = scope List<LODSmallEntry>();
|
|
|
|
int readCount = 0;
|
|
|
|
int64 lastEventTick = 0;
|
|
|
|
CmdLoop: while (true)
|
|
{
|
|
switch (stateCtx.GetNextEvent())
|
|
{
|
|
case let .Enter(startTick, strIdx):
|
|
readCount++;
|
|
LODEntry entry;
|
|
entry.mStartTick = startTick;
|
|
entry.mZoneNameId = strIdx;
|
|
entry.mParamsReadPos = stateCtx.ReadPos;
|
|
entry.mWritten = false;
|
|
entryStack.Add(entry);
|
|
case let .Leave(endTick):
|
|
int32 stackDepth = (int32)entryStack.Count - 1;
|
|
var entry = ref entryStack[stackDepth];
|
|
|
|
int64 ticks = endTick - entry.mStartTick;
|
|
// Keep either long or spanning-from-previous entries
|
|
if ((ticks >= minTicks) || (entry.mStartTick <= srcStreamData.mStartTick))
|
|
{
|
|
FlushLODEntry(cmdTarget, srcStreamData, destStreamData, entryStack, smallEntryStack);
|
|
}
|
|
else
|
|
{
|
|
while (stackDepth >= smallEntryStack.Count)
|
|
smallEntryStack.Add(LODSmallEntry());
|
|
var smallEntry = ref smallEntryStack[stackDepth];
|
|
if (smallEntry.mStartTick != 0)
|
|
{
|
|
int64 spaceTicks = entry.mStartTick - smallEntry.mEndTick;
|
|
Debug.Assert(spaceTicks >= 0);
|
|
if (spaceTicks > minTicks)
|
|
{
|
|
FlushSmallEntry(destStreamData, smallEntryStack, stackDepth);
|
|
}
|
|
}
|
|
if (smallEntry.mStartTick == 0)
|
|
smallEntry.mStartTick = entry.mStartTick;
|
|
Debug.Assert(endTick >= smallEntry.mEndTick);
|
|
smallEntry.mEndTick = endTick;
|
|
}
|
|
|
|
if (entry.mWritten)
|
|
{
|
|
destStreamData.Write((uint8)BpCmd.Leave);
|
|
destStreamData.WriteTickDelta(endTick);
|
|
|
|
/*int readPos = stateCtx.ReadPos;
|
|
int writePos = destStreamData.mBuffer.Count;
|
|
Debug.Assert(readPos == writePos);*/
|
|
}
|
|
|
|
entryStack.RemoveAt(stackDepth);
|
|
case .EndOfStream:
|
|
break CmdLoop;
|
|
case .FrameTick(int64 tick):
|
|
destStreamData.Write((uint8)BpCmd.FrameTick);
|
|
destStreamData.WriteTickDelta(tick);
|
|
case .PrevFrameTick(int64 tick):
|
|
destStreamData.Write((uint8)BpCmd.PrevFrameTick);
|
|
destStreamData.WriteTickDelta(tick);
|
|
case let .LODSmallEntry(startTick, endTick, stackDepth):
|
|
while (stackDepth >= smallEntryStack.Count)
|
|
smallEntryStack.Add(LODSmallEntry());
|
|
var smallEntry = ref smallEntryStack[stackDepth];
|
|
if (smallEntry.mStartTick != 0)
|
|
{
|
|
int64 spaceTicks = startTick - smallEntry.mEndTick;
|
|
Debug.Assert(spaceTicks >= 0);
|
|
if (spaceTicks > minTicks)
|
|
{
|
|
FlushSmallEntry(destStreamData, smallEntryStack, stackDepth);
|
|
}
|
|
}
|
|
if (smallEntry.mStartTick == 0)
|
|
smallEntry.mStartTick = startTick;
|
|
Debug.Assert(endTick >= smallEntry.mEndTick);
|
|
smallEntry.mEndTick = endTick;
|
|
case let .Event(tick, name, details):
|
|
if (tick - lastEventTick >= minTicks)
|
|
{
|
|
destStreamData.Write((uint8)BpCmd.LODEvent);
|
|
destStreamData.WriteTickDelta(tick);
|
|
destStreamData.WriteSLEB128(stateCtx.mParamsPtr - stateCtx.mReadStart);
|
|
lastEventTick = tick;
|
|
}
|
|
case let .LODEvent(tick, paramsOfs):
|
|
if (tick - lastEventTick >= minTicks)
|
|
{
|
|
destStreamData.Write((uint8)BpCmd.LODEvent);
|
|
destStreamData.WriteTickDelta(tick);
|
|
destStreamData.WriteSLEB128(paramsOfs);
|
|
lastEventTick = tick;
|
|
}
|
|
default:
|
|
Runtime.FatalError();
|
|
}
|
|
}
|
|
|
|
FlushLODEntry(cmdTarget, srcStreamData, destStreamData, entryStack, smallEntryStack);
|
|
for (int32 stackDepth < (int32)smallEntryStack.Count)
|
|
FlushSmallEntry(destStreamData, smallEntryStack, stackDepth);
|
|
|
|
int sizeAdd = (int)(srcStreamData.mSizeOverride - srcStreamData.mBuffer.Count);
|
|
int wantSize = destStreamData.mBuffer.Count + sizeAdd;
|
|
destStreamData.mSizeOverride = wantSize;
|
|
|
|
let outData = new List<uint8>((int32)wantSize);
|
|
tempData.CopyTo(outData);
|
|
destStreamData.mBuffer = outData;
|
|
|
|
// Validate stream
|
|
/*if (true)
|
|
{
|
|
BPStateContext validateStateCtx = scope BPStateContext(cmdTarget.mClient, destStreamData);
|
|
CmdLoop: while (true)
|
|
{
|
|
switch (validateStateCtx.GetNextEvent())
|
|
{
|
|
case .EndOfStream:
|
|
break CmdLoop;
|
|
default:
|
|
}
|
|
}
|
|
|
|
//let srcStreamData = srcStreamDataList[idx];
|
|
if (validateStateCtx.mCurTick != destStreamData.mEndTick)
|
|
{
|
|
Runtime.FatalError();
|
|
}
|
|
}*/
|
|
|
|
return destStreamData;
|
|
}
|
|
|
|
public BpStreamData Generate(BpCmdTarget cmdTarget, List<BpStreamData> srcStreamDataList, int32 idx, int64 overrideMinTicks = -1)
|
|
{
|
|
var streamData = DoGenerate(cmdTarget, srcStreamDataList, idx, overrideMinTicks);
|
|
|
|
|
|
|
|
return streamData;
|
|
}
|
|
}
|
|
|
|
class BpCmdTarget
|
|
{
|
|
public BpSession mSession;
|
|
public int32 mNativeThreadId;
|
|
public int64 mCreatedTick;
|
|
public int64 mRemoveTick;
|
|
|
|
//public List<BPEntry> mEntries = new List<BPEntry>() ~ delete _;
|
|
//public List<int> mStack = new List<int>();
|
|
|
|
public List<BpStreamLOD> mStreamLODs = new List<BpStreamLOD>() ~ DeleteContainerAndItems!(_);
|
|
public List<BpStreamData> mStreamDataList = new List<BpStreamData>() ~ DeleteContainerAndItems!(_);
|
|
public BpStreamData mStreamData;
|
|
|
|
public List<BPEntry> mEntryStack = new List<BPEntry>() ~ delete _;
|
|
public int64 mLastFrameTick;
|
|
|
|
public this(BpSession session, bool defaultInit)
|
|
{
|
|
mSession = session;
|
|
|
|
if (defaultInit)
|
|
{
|
|
mStreamData = new BpStreamData();
|
|
mStreamData.mBuffer = new List<uint8>();
|
|
mStreamDataList.Add(mStreamData);
|
|
|
|
// Idx 0 is the 'overview'
|
|
var streamLOD = new BpStreamLOD();
|
|
mStreamLODs.Add(streamLOD);
|
|
|
|
streamLOD = new BpStreamLOD();
|
|
mStreamLODs.Add(streamLOD);
|
|
}
|
|
}
|
|
|
|
public void SplitStreamData()
|
|
{
|
|
var prevStreamData = mStreamData;
|
|
prevStreamData.mSplitTick = prevStreamData.mEndTick;
|
|
// Leave room to add the .Leave commands
|
|
prevStreamData.mSizeOverride = prevStreamData.mBuffer.Count + (mEntryStack.Count * (1 + 8+1));
|
|
|
|
int64 numTicks = mStreamData.mEndTick - mStreamData.mStartTick;
|
|
int64 avgZoneDist = 0;
|
|
if (mStreamData.mNumZones > 0)
|
|
avgZoneDist = numTicks / mStreamData.mNumZones;
|
|
let streamDataLOD0 = mStreamLODs[0].Generate(this, mStreamDataList, (int32)mStreamDataList.Count - 1, avgZoneDist * 10);
|
|
|
|
numTicks = streamDataLOD0.mEndTick - streamDataLOD0.mStartTick;
|
|
avgZoneDist = 0;
|
|
if (streamDataLOD0.mNumZones > 0)
|
|
avgZoneDist = numTicks / streamDataLOD0.mNumZones;
|
|
#unwarn
|
|
let streamDataLOD1 = mStreamLODs[1].Generate(this, mStreamLODs[0].mStreamDataList, (int32)mStreamLODs[0].mStreamDataList.Count - 1, avgZoneDist * 10);
|
|
|
|
mStreamData = new BpStreamData();
|
|
mStreamDataList.Add(mStreamData);
|
|
|
|
// We actually copy the buffer data to place in the old stream data and we keep around the 'scratch buffer'
|
|
// for the newest stream data. This is to avoid keeping around unused bytes in a closed buffer
|
|
var oldBuffer = new List<uint8>();
|
|
oldBuffer.Capacity = (int32)prevStreamData.mSizeOverride;
|
|
prevStreamData.mBuffer.CopyTo(oldBuffer);
|
|
mStreamData.mBuffer = prevStreamData.mBuffer;
|
|
mStreamData.mBuffer.Clear();
|
|
prevStreamData.mBuffer = oldBuffer;
|
|
prevStreamData.mPendingLeaveDepth = (int32)mEntryStack.Count;
|
|
|
|
mStreamData.mStartTick = prevStreamData.mEndTick;
|
|
mStreamData.mEndTick = prevStreamData.mEndTick;
|
|
|
|
if (mEntryStack.Count > 0)
|
|
{
|
|
mStreamData.Write((uint8)BpCmd.StreamSplitInfo);
|
|
mStreamData.WriteSLEB128(mEntryStack.Count);
|
|
}
|
|
|
|
for (var entry in ref mEntryStack)
|
|
{
|
|
mStreamData.Write((uint8)BpCmd.Enter);
|
|
mStreamData.WriteTickDelta(entry.mStartTick);
|
|
mStreamData.WriteSLEB128(entry.mZoneNameId);
|
|
|
|
int32 newParamsPos = (int32)mStreamData.mBuffer.Count;
|
|
|
|
int32 paramSize = 0;
|
|
if (entry.mZoneNameId < 0)
|
|
{
|
|
paramSize = -1;
|
|
int32 nameLen = -entry.mZoneNameId;
|
|
mStreamData.Write(&prevStreamData.mBuffer[entry.mParamsReadPos - nameLen], nameLen);
|
|
}
|
|
else
|
|
{
|
|
var zoneName = mSession.mZoneNames[entry.mZoneNameId];
|
|
paramSize = zoneName.mParamsSize;
|
|
}
|
|
|
|
if (paramSize != 0)
|
|
{
|
|
BPStateContext stateCtx = scope BPStateContext(mSession, prevStreamData);
|
|
stateCtx.ReadPos = entry.mParamsReadPos;
|
|
if (paramSize == -1)
|
|
{
|
|
paramSize = (int32)stateCtx.ReadSLEB128();
|
|
mStreamData.WriteSLEB128(paramSize);
|
|
}
|
|
if (paramSize > 0)
|
|
{
|
|
int32 prevSize = (int32)mStreamData.mBuffer.Count;
|
|
mStreamData.mBuffer.GrowUnitialized(paramSize);
|
|
stateCtx.Read(&mStreamData.mBuffer[prevSize], paramSize);
|
|
}
|
|
entry.mParamsReadPos = newParamsPos;
|
|
}
|
|
}
|
|
|
|
if (mLastFrameTick != 0)
|
|
{
|
|
mStreamData.Write((uint8)BpCmd.PrevFrameTick);
|
|
mStreamData.WriteTickDelta(mLastFrameTick);
|
|
}
|
|
}
|
|
|
|
public void MaybeSplitStreamData()
|
|
{
|
|
if (mStreamData.mBuffer.Count >= 64*1024)
|
|
{
|
|
SplitStreamData();
|
|
|
|
/*if (mStreamDataList.Count == 2)
|
|
{
|
|
Debug.WriteLine("Split Tick: {0}, BufferSize={1}", mStreamDataList[0].mSplitTick, mStreamDataList[0].mBuffer.Count);
|
|
for (var entry in mEntryStack)
|
|
{
|
|
Debug.WriteLine("mEntryStack[{0}] mStartTick={1}", @entry.Index, entry.mStartTick);
|
|
}
|
|
}*/
|
|
}
|
|
}
|
|
}
|
|
|
|
class BpTrack : BpCmdTarget
|
|
{
|
|
public String mName ~ delete _;
|
|
|
|
public this(BpSession session) : base(session, false)
|
|
{
|
|
|
|
}
|
|
|
|
public this(BpSession session, int32 nativeThreadId) : base(session, true)
|
|
{
|
|
mNativeThreadId = nativeThreadId;
|
|
}
|
|
|
|
public void GetName(String str)
|
|
{
|
|
if (mName != null)
|
|
str.Append(mName);
|
|
else
|
|
str.AppendF("Thread {0}", mNativeThreadId);
|
|
}
|
|
|
|
public static int Compare(BpTrack lhs, BpTrack rhs)
|
|
{
|
|
String lhsName = lhs.mName;
|
|
String rhsName = rhs.mName;
|
|
if ((lhsName == null) && (rhsName == null))
|
|
return lhs.mNativeThreadId - rhs.mNativeThreadId;
|
|
if ((lhsName != null) && (rhsName != null))
|
|
return String.Compare(lhsName, rhsName, true);
|
|
if (lhsName == null)
|
|
return String.Compare("Thread ", rhsName, true);
|
|
return String.Compare(lhsName, "Thread ", true);
|
|
}
|
|
}
|
|
|
|
class BpZoneName
|
|
{
|
|
public String mName ~ delete _;
|
|
public int32 mParamsSize; // -1 = variable sized (string)
|
|
}
|
|
|
|
class BpSession
|
|
{
|
|
public enum LoadError
|
|
{
|
|
FileNotFound,
|
|
FileError,
|
|
InvalidData,
|
|
InvalidVersion
|
|
}
|
|
|
|
public Stream mStream ~ delete _;
|
|
|
|
public const int cBufTickScale = 100;
|
|
public const uint32 cFileMarker = 0xBEEF193A;
|
|
|
|
public BpCmdTarget mCurCmdTarget;
|
|
public DateTime mConnectTime;
|
|
public DateTime mDisconnectTime;
|
|
public int64 mNumZones;
|
|
public String mClientEnv ~ delete _;
|
|
public String mClientName ~ delete _;
|
|
public String mSessionId ~ delete _;
|
|
public String mSessionName ~ delete _;
|
|
|
|
public List<BpTrack> mThreads = new List<BpTrack>() ~ DeleteContainerAndItems!(_);
|
|
public BpCmdTarget mRootCmdTarget ~ delete _;
|
|
public List<BpZoneName> mZoneNames = new List<BpZoneName>() ~ DeleteContainerAndItems!(_);
|
|
public int64 mStatTotalBytesReceived;
|
|
public int64 mFirstTick;
|
|
public int64 mCurTick;
|
|
|
|
public int32 mThreadDataVersion;
|
|
public int32 mDispBPS;
|
|
public bool mSessionOver;
|
|
public double mTicksToUSScale;
|
|
|
|
/*public int64 mFirstTimeStamp = -1;
|
|
public uint32 mFirstTickMS;*/
|
|
|
|
private this()
|
|
{
|
|
|
|
}
|
|
|
|
public this(bool defaultInit)
|
|
{
|
|
mRootCmdTarget = new BpCmdTarget(this, defaultInit);
|
|
}
|
|
|
|
protected void ProcessClientEnv()
|
|
{
|
|
for (var line in mClientEnv.Split('\n'))
|
|
{
|
|
var lineEnum = line.Split('\t');
|
|
if ((StringView key = lineEnum.GetNext()) &&
|
|
(StringView value = lineEnum.GetNext()))
|
|
{
|
|
switch (key)
|
|
{
|
|
case "ClientName": mClientName = new String(value);
|
|
case "SessionId": mSessionId = new String(value);
|
|
case "SessionName": mSessionName = new String(value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public double GetTicksToUSScale()
|
|
{
|
|
return mTicksToUSScale;
|
|
}
|
|
|
|
public double TicksToUS(int64 ticks)
|
|
{
|
|
return ticks * mTicksToUSScale;
|
|
}
|
|
|
|
public void CalcBPS()
|
|
{
|
|
TimeSpan elapsed;
|
|
if (mSessionOver)
|
|
elapsed = mDisconnectTime - mConnectTime;
|
|
else
|
|
elapsed = DateTime.Now - mConnectTime;
|
|
|
|
mDispBPS = 0;
|
|
if (elapsed.Milliseconds > 0)
|
|
mDispBPS = (int32)(mStatTotalBytesReceived * 1000 / elapsed.TotalMilliseconds / 1024);
|
|
}
|
|
|
|
public void ElapsedTicksToStr(int64 ticks, String str)
|
|
{
|
|
double timeUS = (double)(ticks * GetTicksToUSScale());
|
|
ElapsedTimeToStr(timeUS, str);
|
|
}
|
|
|
|
public Result<int64> ParseElapsedTicks(String str)
|
|
{
|
|
double timeUS = 0;
|
|
|
|
int numStart = -1;
|
|
int numEnd = -1;
|
|
double curNumber = 0;
|
|
char8 prevChar = 0;
|
|
bool hasNumber = false;
|
|
for (int i < str.Length)
|
|
{
|
|
char8 c = str[i];
|
|
|
|
if (((c >= '0') && (c <= '9')) || (c == '.'))
|
|
{
|
|
if (numStart == -1)
|
|
numStart = i;
|
|
numEnd = i;
|
|
hasNumber = true;
|
|
continue;
|
|
}
|
|
|
|
if (hasNumber)
|
|
{
|
|
if (float.Parse(scope String(str, numStart, numEnd - numStart + 1)) case .Ok(let num))
|
|
curNumber = num;
|
|
else
|
|
return .Err;
|
|
numStart = -1;
|
|
numEnd = -1;
|
|
hasNumber = false;
|
|
}
|
|
|
|
if (c.IsWhiteSpace)
|
|
continue;
|
|
if (c == ':')
|
|
continue;
|
|
|
|
if ((c == 'm') || (c == 'u'))
|
|
{
|
|
if (prevChar != 0)
|
|
return .Err;
|
|
prevChar = c;
|
|
}
|
|
else if (c == 's')
|
|
{
|
|
double secScale;
|
|
if (prevChar == 'u')
|
|
secScale = 1.0;
|
|
else if (prevChar == 'm')
|
|
secScale = 0.001;
|
|
else
|
|
secScale = 0.000001;
|
|
timeUS += curNumber * secScale;
|
|
hasNumber = false;
|
|
}
|
|
else // Invalid char
|
|
return .Err;
|
|
}
|
|
|
|
// Shouldn't have a leftover non-terminated number
|
|
if (hasNumber)
|
|
return .Err;
|
|
|
|
return .Ok((int64)(timeUS / GetTicksToUSScale()));
|
|
}
|
|
|
|
public static void ElapsedTimeToStr(double timeUS, String str)
|
|
{
|
|
int64 intTimeUS = (int64)timeUS;
|
|
|
|
if (intTimeUS < 1000) // < 1ms
|
|
{
|
|
int32 timeNS = (int32)((timeUS - intTimeUS) * 1000);
|
|
str.AppendF("{0}.{1:02}us", intTimeUS, timeNS / 10);
|
|
}
|
|
else if (intTimeUS < 1000*1000) // < 1s
|
|
{
|
|
str.AppendF("{0}.{1:02}ms", (int32)(intTimeUS / 1000), (int32)((intTimeUS / 10) % 100));
|
|
}
|
|
else if (intTimeUS < 10*1000*1000) // < 10s
|
|
{
|
|
str.AppendF("{0}s:{1:03}.{2:01}ms", (int32)(intTimeUS / 1000 / 1000), (int32)((intTimeUS / 1000) % 1000), (int32)((intTimeUS / 100) % 10));
|
|
}
|
|
else if (intTimeUS < 60*1000*1000) // < 1m
|
|
{
|
|
str.AppendF("{0}s:{1:03}ms", (int32)(intTimeUS / 1000 / 1000), (int32)((intTimeUS / 1000) % 1000));
|
|
}
|
|
else
|
|
{
|
|
str.AppendF("{0}m:{1}s:{2}ms", (int32)(intTimeUS / 60 / 60 / 1000 / 1000), (int32)((intTimeUS / 60 / 1000 / 1000) % 60), (int32)((intTimeUS / 1000) % 60));
|
|
}
|
|
}
|
|
|
|
public static void TimeToStr(double timeUS, String str, bool highPrecision = true)
|
|
{
|
|
int64 intTimeUS = (int64)timeUS;
|
|
str.AppendF("{0}:{1:02}:", (int32)((intTimeUS / 60 / 60 / 1000000)), (int32)((intTimeUS / 60 / 1000000) % 60));
|
|
if (highPrecision)
|
|
str.AppendF("{0:02}.{1:06}", (int32)((intTimeUS / 1000000) % 60), (int32)((intTimeUS % 1000000)));
|
|
else
|
|
str.AppendF("{0:02}.{1:02}", (int32)((intTimeUS / 1000000) % 60), (int32)((intTimeUS / 10000 % 100)));
|
|
}
|
|
|
|
public Result<int64> ParseTime(String str)
|
|
{
|
|
double timeUS = 0;
|
|
|
|
int numStart = -1;
|
|
int numEnd = -1;
|
|
double curNumber = 0;
|
|
bool hasParsedNumber = false;
|
|
bool hasNumberChars = false;
|
|
|
|
List<double> sections = scope List<double>(4);
|
|
|
|
for (int i < str.Length)
|
|
{
|
|
char8 c = str[i];
|
|
|
|
bool ending = false;
|
|
if (((c >= '0') && (c <= '9')) || (c == '.'))
|
|
{
|
|
if (hasParsedNumber)
|
|
return .Err;
|
|
if (numStart == -1)
|
|
numStart = i;
|
|
numEnd = i;
|
|
hasNumberChars = true;
|
|
|
|
if (i < str.Length - 1)
|
|
continue;
|
|
ending = true;
|
|
}
|
|
|
|
if (hasNumberChars)
|
|
{
|
|
if (double.Parse(scope String(str, numStart, numEnd - numStart + 1)) case .Ok(let num))
|
|
curNumber = num;
|
|
else
|
|
return .Err;
|
|
numStart = -1;
|
|
numEnd = -1;
|
|
hasNumberChars = false;
|
|
hasParsedNumber = true;
|
|
}
|
|
|
|
if (ending)
|
|
break;
|
|
|
|
if (c.IsWhiteSpace)
|
|
continue;
|
|
if (c == ':')
|
|
{
|
|
if (hasParsedNumber)
|
|
{
|
|
sections.Add(curNumber);
|
|
hasParsedNumber = false;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
else
|
|
return .Err;
|
|
}
|
|
|
|
double seconds = curNumber;
|
|
if (sections.Count == 2)
|
|
{
|
|
seconds += sections[0]*60*60 + sections[1]*60;
|
|
}
|
|
else
|
|
return .Err;
|
|
|
|
timeUS += seconds * 1000000.0;
|
|
|
|
return .Ok((int64)(timeUS / GetTicksToUSScale()) + mFirstTick);
|
|
}
|
|
|
|
public void TicksToStr(int64 tick, String str)
|
|
{
|
|
TimeToStr((tick - mFirstTick) * GetTicksToUSScale(), str);
|
|
}
|
|
|
|
public void ClearData()
|
|
{
|
|
mFirstTick = mCurTick;
|
|
mNumZones = 0;
|
|
ClearCommandTarget(mRootCmdTarget);
|
|
for (var thread in mThreads)
|
|
ClearCommandTarget(thread);
|
|
}
|
|
|
|
void ClearCommandTarget(BpCmdTarget cmdTarget)
|
|
{
|
|
cmdTarget.SplitStreamData(); // Start a new, clear streamData
|
|
while (cmdTarget.mStreamDataList.Count > 1)
|
|
{
|
|
var secondToLastIdx = cmdTarget.mStreamDataList.Count - 2;
|
|
delete cmdTarget.mStreamDataList[secondToLastIdx];
|
|
cmdTarget.mStreamDataList.RemoveAt(secondToLastIdx);
|
|
}
|
|
for (var lod in cmdTarget.mStreamLODs)
|
|
{
|
|
ClearAndDeleteItems(lod.mStreamDataList);
|
|
}
|
|
cmdTarget.mEntryStack.Clear();
|
|
}
|
|
|
|
public Result<void> Save(StringView filePath)
|
|
{
|
|
var fs = scope FileStream();
|
|
|
|
void SaveStreamData(BpStreamData streamData)
|
|
{
|
|
fs.Write((int32)streamData.mBuffer.Count);
|
|
fs.Write((Span<uint8>)streamData.mBuffer);
|
|
|
|
fs.Write(streamData.mStartTick);
|
|
fs.Write(streamData.mEndTick);
|
|
fs.Write(streamData.mSplitTick);
|
|
fs.Write(streamData.mSizeOverride);
|
|
fs.Write(streamData.mPendingLeaveDepth);
|
|
fs.Write(streamData.mNumZones);
|
|
fs.Write(streamData.mMinTicks);
|
|
}
|
|
|
|
void SaveCmdTarget(BpCmdTarget cmdTarget)
|
|
{
|
|
fs.Write(cmdTarget.mNativeThreadId);
|
|
fs.Write(cmdTarget.mCreatedTick);
|
|
fs.Write(cmdTarget.mRemoveTick);
|
|
|
|
fs.Write((int32)cmdTarget.mStreamLODs.Count);
|
|
for (var streamLod in cmdTarget.mStreamLODs)
|
|
{
|
|
fs.Write(streamLod.mMinTicks);
|
|
fs.Write((int32)streamLod.mStreamDataList.Count);
|
|
for (var streamData in streamLod.mStreamDataList)
|
|
SaveStreamData(streamData);
|
|
}
|
|
|
|
fs.Write((int32)cmdTarget.mStreamDataList.Count);
|
|
for (var streamData in cmdTarget.mStreamDataList)
|
|
SaveStreamData(streamData);
|
|
|
|
fs.Write((int32)cmdTarget.mEntryStack.Count);
|
|
if (!cmdTarget.mEntryStack.IsEmpty)
|
|
fs.Write(Span<uint8>((uint8*)&cmdTarget.mEntryStack[0], cmdTarget.mEntryStack.Count * sizeof(BPEntry)));
|
|
|
|
fs.Write(cmdTarget.mLastFrameTick);
|
|
}
|
|
|
|
switch (fs.Create(filePath))
|
|
{
|
|
case .Ok:
|
|
case .Err:
|
|
return .Err;
|
|
}
|
|
|
|
fs.Write((int32)cFileMarker);
|
|
fs.Write((int32)2); // Version
|
|
|
|
fs.Write(mConnectTime.ToBinaryRaw());
|
|
fs.Write(mDisconnectTime.ToBinaryRaw());
|
|
fs.Write(mNumZones);
|
|
fs.WriteStrSized32(mClientEnv);
|
|
|
|
fs.Write((int32)mThreads.Count);
|
|
for (var thread in mThreads)
|
|
{
|
|
fs.WriteStrSized32(thread.mName ?? "");
|
|
SaveCmdTarget(thread);
|
|
}
|
|
SaveCmdTarget(mRootCmdTarget);
|
|
fs.Write((int32)mZoneNames.Count);
|
|
for (var cmdName in mZoneNames)
|
|
{
|
|
fs.WriteStrSized32(cmdName.mName);
|
|
fs.Write(cmdName.mParamsSize);
|
|
}
|
|
fs.Write(mStatTotalBytesReceived);
|
|
fs.Write(mFirstTick);
|
|
fs.Write(mCurTick);
|
|
fs.Write(mClientEnv);
|
|
|
|
return .Ok;
|
|
}
|
|
|
|
public Result<void, LoadError> Load(StringView filePath)
|
|
{
|
|
var fs = scope FileStream();
|
|
bool readFailed = false;
|
|
|
|
switch (fs.Open(filePath, .Read))
|
|
{
|
|
case .Ok:
|
|
case .Err(let err):
|
|
switch (err)
|
|
{
|
|
case .NotFound:
|
|
return .Err(.FileNotFound);
|
|
default:
|
|
return .Err(.FileError);
|
|
}
|
|
}
|
|
|
|
T Read<T>() where T : struct
|
|
{
|
|
switch (fs.Read<T>())
|
|
{
|
|
case .Ok(let setVal):
|
|
return setVal;
|
|
case .Err:
|
|
readFailed = true;
|
|
}
|
|
return default;
|
|
}
|
|
|
|
void Read<T>(out T val) where T : struct
|
|
{
|
|
switch (fs.Read<T>())
|
|
{
|
|
case .Ok(let setVal):
|
|
val = setVal;
|
|
return;
|
|
case .Err:
|
|
val = default;
|
|
readFailed = true;
|
|
}
|
|
}
|
|
|
|
void ReadStrSized32(String s)
|
|
{
|
|
switch (fs.Read<int32>())
|
|
{
|
|
case .Ok(let len):
|
|
char8* ptr = s.PrepareBuffer(len);
|
|
switch (fs.TryRead(Span<uint8>((uint8*)ptr, len)))
|
|
{
|
|
case .Ok:
|
|
case .Err:
|
|
readFailed = true;
|
|
return;
|
|
}
|
|
case .Err:
|
|
readFailed = true;
|
|
}
|
|
}
|
|
|
|
mixin LoadTry(var val)
|
|
{
|
|
if (val case .Err(var err))
|
|
return .Err(.InvalidData);
|
|
val.Get()
|
|
}
|
|
|
|
void Read(Span<uint8> data)
|
|
{
|
|
if (fs.TryRead(data) case .Err)
|
|
readFailed = true;
|
|
}
|
|
|
|
void ReadStreamData(BpStreamData streamData)
|
|
{
|
|
int dataSize = Read<int32>();
|
|
streamData.mBuffer = new List<uint8>();
|
|
streamData.mBuffer.GrowUnitialized(dataSize);
|
|
Read(streamData.mBuffer);
|
|
|
|
Read(out streamData.mStartTick);
|
|
Read(out streamData.mEndTick);
|
|
Read(out streamData.mSplitTick);
|
|
Read(out streamData.mSizeOverride);
|
|
Read(out streamData.mPendingLeaveDepth);
|
|
Read(out streamData.mNumZones);
|
|
Read(out streamData.mMinTicks);
|
|
}
|
|
|
|
void ReadCmdTarget(BpCmdTarget cmdTarget)
|
|
{
|
|
Read(out cmdTarget.mNativeThreadId);
|
|
Read(out cmdTarget.mCreatedTick);
|
|
Read(out cmdTarget.mRemoveTick);
|
|
|
|
int lodCount = Read<int32>();
|
|
for (int lodIdx < lodCount)
|
|
{
|
|
var streamLod = new BpStreamLOD();
|
|
cmdTarget.mStreamLODs.Add(streamLod);
|
|
Read(out streamLod.mMinTicks);
|
|
int dataCount = Read<int32>();
|
|
for (int dataIdx < dataCount)
|
|
{
|
|
var streamData = new BpStreamData();
|
|
ReadStreamData(streamData);
|
|
streamLod.mStreamDataList.Add(streamData);
|
|
}
|
|
}
|
|
|
|
int dataCount = Read<int32>();
|
|
for (int dataIdx < dataCount)
|
|
{
|
|
var streamData = new BpStreamData();
|
|
ReadStreamData(streamData);
|
|
cmdTarget.mStreamDataList.Add(streamData);
|
|
}
|
|
|
|
int stackSize = Read<int32>();
|
|
if (stackSize > 0)
|
|
{
|
|
cmdTarget.mEntryStack.GrowUnitialized(stackSize);
|
|
Read(Span<uint8>((uint8*)&cmdTarget.mEntryStack[0], cmdTarget.mEntryStack.Count * sizeof(BPEntry)));
|
|
}
|
|
|
|
Read(out cmdTarget.mLastFrameTick);
|
|
}
|
|
|
|
uint32 fileMarker = Read<uint32>();
|
|
if (fileMarker != cFileMarker)
|
|
return .Err(.InvalidData);
|
|
int32 version = Read<int32>();
|
|
if ((version < 1) || (version > 2))
|
|
return .Err(.InvalidVersion);
|
|
mConnectTime = LoadTry!(DateTime.FromBinaryRaw(Read<int64>()));
|
|
mDisconnectTime = LoadTry!(DateTime.FromBinaryRaw(Read<int64>()));
|
|
Read(out mNumZones);
|
|
mClientEnv = new String();
|
|
ReadStrSized32(mClientEnv);
|
|
ProcessClientEnv();
|
|
|
|
int threadCount = Read<int32>();
|
|
for (int threadIdx < threadCount)
|
|
{
|
|
var thread = new BpTrack(this);
|
|
thread.mName = new String();
|
|
ReadStrSized32(thread.mName);
|
|
if (thread.mName.IsEmpty)
|
|
{
|
|
DeleteAndNullify!(thread.mName);
|
|
}
|
|
ReadCmdTarget(thread);
|
|
mThreads.Add(thread);
|
|
}
|
|
ReadCmdTarget(mRootCmdTarget);
|
|
int zoneCount = Read<int32>();
|
|
for (int zoneIdx < zoneCount)
|
|
{
|
|
var zoneName = new BpZoneName();
|
|
zoneName.mName = new String();
|
|
ReadStrSized32(zoneName.mName);
|
|
zoneName.mParamsSize = Read<int32>();
|
|
mZoneNames.Add(zoneName);
|
|
}
|
|
Read(out mStatTotalBytesReceived);
|
|
Read(out mFirstTick);
|
|
Read(out mCurTick);
|
|
if (version > 1)
|
|
Read(out mTicksToUSScale);
|
|
else
|
|
mTicksToUSScale = 1 / 4500.0;
|
|
|
|
if (fs.Position != fs.Length)
|
|
return .Err(.InvalidData);
|
|
|
|
if (readFailed)
|
|
return .Err(.InvalidData);
|
|
|
|
mThreadDataVersion = 1;
|
|
mSessionOver = true;
|
|
CalcBPS();
|
|
|
|
return .Ok;
|
|
}
|
|
}
|
|
|
|
class BpClient : BpSession
|
|
{
|
|
public Socket mSocket ~ delete _;
|
|
public Monitor mDataMonitor = new Monitor() ~ delete _;
|
|
public CircularBuffer mCircularBuffer = new CircularBuffer() ~ delete _;
|
|
public int32 mBlockRecvLeft;
|
|
public int32 mCurBlockSize;
|
|
public bool mGettingBlockSize = true;
|
|
public ProfileInstance mProfileId;
|
|
public int32 mValidDataSize;
|
|
|
|
public int32 mUpdateCnt;
|
|
public bool mSessionInitialized;
|
|
|
|
public this() : base(true)
|
|
{
|
|
//mCircularBuffer.Resize(128*1024*1024);
|
|
|
|
mCurCmdTarget = mRootCmdTarget;
|
|
}
|
|
|
|
public ~this()
|
|
{
|
|
if (mProfileId != 0)
|
|
mProfileId.Dispose();
|
|
}
|
|
|
|
public bool IsSessionInitialized
|
|
{
|
|
get
|
|
{
|
|
return mSessionInitialized;
|
|
}
|
|
}
|
|
|
|
public void TryRecv()
|
|
{
|
|
if (!mSocket.IsOpen)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (mSessionOver)
|
|
{
|
|
if (mProfileId != 0)
|
|
{
|
|
mProfileId.Dispose();
|
|
mProfileId = 0;
|
|
}
|
|
mSocket.Close();
|
|
return;
|
|
}
|
|
|
|
let recvSize = 0x10000;
|
|
uint8* data = scope uint8[recvSize]*;
|
|
|
|
while (true)
|
|
{
|
|
if (mGettingBlockSize)
|
|
{
|
|
int32 wantsRecvSize;
|
|
using (mDataMonitor.Enter())
|
|
{
|
|
int32 extraData = mCircularBuffer.GetSize() - mValidDataSize;
|
|
wantsRecvSize = 4 - extraData;
|
|
}
|
|
int32 result = -1;
|
|
if (mSocket.Recv(data, wantsRecvSize) case .Ok(let recvBytes))
|
|
{
|
|
result = (int32)recvBytes;
|
|
}
|
|
else
|
|
{
|
|
if (!mSocket.IsConnected)
|
|
{
|
|
Disconnect();
|
|
}
|
|
}
|
|
using (mDataMonitor.Enter())
|
|
{
|
|
if (result > 0)
|
|
{
|
|
int32 pos = mCircularBuffer.GetSize();
|
|
mCircularBuffer.Grow(result);
|
|
mCircularBuffer.Write(data, pos, result);
|
|
gApp.ReportBytesReceived(result);
|
|
mStatTotalBytesReceived += result;
|
|
}
|
|
|
|
if (result == wantsRecvSize)
|
|
{
|
|
mCircularBuffer.Read(&mCurBlockSize, mValidDataSize, 4);
|
|
mBlockRecvLeft = mCurBlockSize;
|
|
mCircularBuffer.RemoveBack(4);
|
|
|
|
#if LINEAR_OPT
|
|
mCircularBuffer.EnsureSpan(mBlockRecvLeft);
|
|
#endif
|
|
mGettingBlockSize = false;
|
|
|
|
//Debug.WriteLine("Recv block size {0}", mCurBlockSize);
|
|
Debug.Assert(mCurBlockSize < 32*1024*1024); // Just ensure it's reasonable
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mBlockRecvLeft <= 0)
|
|
break;
|
|
|
|
//Thread.Sleep(1);
|
|
|
|
int32 tryRecvSize = (int32)Math.Min(mBlockRecvLeft, recvSize);
|
|
int32 result = -1;
|
|
if (mSocket.Recv(data, tryRecvSize) case .Ok(let recvBytes))
|
|
{
|
|
result = (int32)recvBytes;
|
|
}
|
|
if (result <= 0)
|
|
{
|
|
if (!mSocket.IsConnected)
|
|
{
|
|
Disconnect();
|
|
}
|
|
break;
|
|
}
|
|
|
|
gApp.ReportBytesReceived(result);
|
|
using (mDataMonitor.Enter())
|
|
{
|
|
mStatTotalBytesReceived += result;
|
|
int32 curSize = mCircularBuffer.GetSize();
|
|
mCircularBuffer.Grow(result);
|
|
mCircularBuffer.Write(data, curSize, result);
|
|
mBlockRecvLeft -= result;
|
|
if (mBlockRecvLeft == 0)
|
|
{
|
|
mValidDataSize += mCurBlockSize;
|
|
mCurBlockSize = 0;
|
|
mGettingBlockSize = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ReadStr(String str)
|
|
{
|
|
while (true)
|
|
{
|
|
char8 c = (char8)mCircularBuffer.Read();
|
|
if (c == 0)
|
|
return;
|
|
str.Append(c);
|
|
}
|
|
}
|
|
|
|
int mReadIdx = 0;
|
|
int mFrameTickCount = 0;
|
|
|
|
void ReadTickDelta()
|
|
{
|
|
int64 tickDelta = mCircularBuffer.ReadSLEB128() * cBufTickScale;
|
|
mCurTick += tickDelta;
|
|
}
|
|
|
|
void Disconnect()
|
|
{
|
|
if (mSessionOver)
|
|
return;
|
|
|
|
mDisconnectTime = DateTime.Now;
|
|
mSessionOver = true;
|
|
CalcBPS();
|
|
}
|
|
|
|
void PopCurEntry()
|
|
{
|
|
mCurCmdTarget.mStreamData.Write((uint8)BpCmd.Leave);
|
|
mCurCmdTarget.mStreamData.WriteTickDelta(mCurTick);
|
|
|
|
var entry = mCurCmdTarget.mEntryStack.PopBack();
|
|
int32 stackDepth = (int32)mCurCmdTarget.mEntryStack.Count;
|
|
|
|
if (entry.mStartTick <= mCurCmdTarget.mStreamData.mStartTick)
|
|
{
|
|
// This spans back
|
|
for (int checkStreamIdx = mCurCmdTarget.mStreamDataList.Count - 2; checkStreamIdx >= 0; checkStreamIdx--)
|
|
{
|
|
var streamData = mCurCmdTarget.mStreamDataList[checkStreamIdx];
|
|
if (entry.mStartTick > streamData.mSplitTick)
|
|
break; // Not applicable this far back
|
|
|
|
// Is this the Leave we've been waiting for?
|
|
if (streamData.mPendingLeaveDepth != stackDepth + 1)
|
|
continue;
|
|
streamData.mPendingLeaveDepth--;
|
|
|
|
/*if (checkStreamIdx == 0)
|
|
{
|
|
Debug.WriteLine("Back-propagating Leaving {0} -> {1}", entry.mStartTick, mCurTick);
|
|
}*/
|
|
|
|
streamData.Write((uint8)BpCmd.Leave);
|
|
streamData.WriteTickDelta(mCurTick);
|
|
Debug.Assert(streamData.mBuffer.Count <= streamData.mSizeOverride);
|
|
|
|
for (var lod in mCurCmdTarget.mStreamLODs)
|
|
{
|
|
if (checkStreamIdx < lod.mStreamDataList.Count)
|
|
{
|
|
var lodStreamData = lod.mStreamDataList[checkStreamIdx];
|
|
if (lodStreamData != null)
|
|
{
|
|
lodStreamData.Write((uint8)BpCmd.Leave);
|
|
lodStreamData.WriteTickDelta(mCurTick);
|
|
Debug.Assert(lodStreamData.mBuffer.Count <= lodStreamData.mSizeOverride);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((mCurCmdTarget.mEntryStack.Count == 1) && (mCurCmdTarget.mStreamDataList.Count <= 2))
|
|
{
|
|
//cmdTarget.SplitStreamData();
|
|
}
|
|
|
|
}
|
|
|
|
void ProcessData()
|
|
{
|
|
if (mSessionOver)
|
|
return;
|
|
|
|
Debug.Assert(mValidDataSize <= mCircularBuffer.GetSize());
|
|
|
|
String tempStr = scope String(64);
|
|
|
|
int readIdx = 0;
|
|
|
|
BpCmd prevCmd = default;
|
|
|
|
//return;
|
|
|
|
while (mValidDataSize > 0)
|
|
{
|
|
mReadIdx++;
|
|
readIdx++;
|
|
|
|
int32 startSize = mCircularBuffer.GetSize();
|
|
uint8* curPtr = mCircularBuffer.GetPtr();
|
|
|
|
uint8 Read()
|
|
{
|
|
return *(curPtr++);
|
|
}
|
|
|
|
void ReadStr(String str)
|
|
{
|
|
uint8* ptr = curPtr;
|
|
while (*ptr != 0)
|
|
{
|
|
ptr++;
|
|
}
|
|
|
|
str.Append((char8*)curPtr, ptr - curPtr);
|
|
curPtr = ptr + 1;
|
|
}
|
|
|
|
int64 ReadSLEB128()
|
|
{
|
|
int64 value = 0;
|
|
int32 shift = 0;
|
|
int64 curByte;
|
|
repeat
|
|
{
|
|
curByte = *(curPtr++);
|
|
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() * cBufTickScale;
|
|
mCurTick += tickDelta;
|
|
}
|
|
|
|
BpCmd cmd = (BpCmd)Read();
|
|
switch (cmd)
|
|
{
|
|
case .Init:
|
|
int32 version = (int32)ReadSLEB128();
|
|
if (version < 2)
|
|
{
|
|
Disconnect();
|
|
return;
|
|
}
|
|
Debug.Assert(version == 2);
|
|
Debug.Assert(mClientName == null);
|
|
mClientEnv = new String();
|
|
ReadStr(mClientEnv);
|
|
ProcessClientEnv();
|
|
|
|
if (!gApp.mWorkspacePanel.PassesFilter(this))
|
|
{
|
|
Disconnect();
|
|
break;
|
|
}
|
|
|
|
mSessionInitialized = true;
|
|
gApp.SessionInitialized(this);
|
|
case .StrEntry:
|
|
ReadStr(tempStr);
|
|
BpZoneName zoneName = new BpZoneName();
|
|
zoneName.mName = new String(tempStr);
|
|
bool isDyn = false;
|
|
for (int idx = 0; idx < zoneName.mName.Length; idx++)
|
|
{
|
|
let c = zoneName.mName[idx];
|
|
if (c == '%')
|
|
{
|
|
let cNext = zoneName.mName[idx + 1];
|
|
idx++;
|
|
|
|
if (cNext == 'd')
|
|
zoneName.mParamsSize += 4;
|
|
else if (cNext == 'f')
|
|
zoneName.mParamsSize += 4;
|
|
else if (cNext == 's')
|
|
isDyn = true;
|
|
}
|
|
}
|
|
if (isDyn)
|
|
zoneName.mParamsSize = -1;
|
|
mZoneNames.Add(zoneName);
|
|
tempStr.Clear();
|
|
case .Enter:
|
|
mNumZones++;
|
|
ReadTickDelta();
|
|
int32 zoneNameId = (int32)ReadSLEB128();
|
|
|
|
BPEntry entry;
|
|
entry.mStartTick = mCurTick;
|
|
entry.mZoneNameId = zoneNameId;
|
|
|
|
int32 paramSize = 0;
|
|
if (zoneNameId >= 0)
|
|
{
|
|
let zoneName = mZoneNames[zoneNameId];
|
|
paramSize = zoneName.mParamsSize;
|
|
/*if (paramSize != 0)
|
|
entry.mParamsReadPos = cmdTarget.mStreamData.mBuffer.Count;
|
|
else
|
|
entry.mParamsReadPos = 0;*/
|
|
}
|
|
else
|
|
{
|
|
paramSize = -1;
|
|
//entry.mParamsReadPos = cmdTarget.mStreamData.mBuffer.Count;
|
|
}
|
|
|
|
mCurCmdTarget.mStreamData.mNumZones++;
|
|
mCurCmdTarget.mStreamData.Write((uint8)cmd);
|
|
mCurCmdTarget.mStreamData.WriteTickDelta(mCurTick);
|
|
mCurCmdTarget.mStreamData.WriteSLEB128(zoneNameId);
|
|
if (zoneNameId < 0)
|
|
{
|
|
int32 strLen = -zoneNameId;
|
|
//var view = scope CircularBuffer.View();
|
|
/*mCircularBuffer.MapView(0, strLen, view);
|
|
mCurCmdTarget.mStreamData.Write(view.mPtr, strLen);
|
|
mCircularBuffer.RemoveFront(strLen);*/
|
|
mCurCmdTarget.mStreamData.Write(curPtr, strLen);
|
|
curPtr += strLen;
|
|
}
|
|
|
|
entry.mParamsReadPos = (int32)mCurCmdTarget.mStreamData.mBuffer.Count;
|
|
mCurCmdTarget.mEntryStack.Add(entry);
|
|
|
|
if (paramSize == -1)
|
|
{
|
|
paramSize = (int32)ReadSLEB128();
|
|
mCurCmdTarget.mStreamData.WriteSLEB128(paramSize);
|
|
}
|
|
|
|
if (paramSize != 0)
|
|
{
|
|
/*var view = scope CircularBuffer.View();
|
|
mCircularBuffer.MapView(0, paramSize, view);
|
|
mCurCmdTarget.mStreamData.Write(view.mPtr, paramSize);
|
|
mCircularBuffer.RemoveFront(paramSize);*/
|
|
mCurCmdTarget.mStreamData.Write(curPtr, paramSize);
|
|
curPtr += paramSize;
|
|
}
|
|
|
|
case .Leave:
|
|
ReadTickDelta();
|
|
|
|
// This can happen when we Init after some entries are already in progress
|
|
if (mCurCmdTarget.mEntryStack.Count == 0)
|
|
break;
|
|
|
|
PopCurEntry();
|
|
case .SetThread:
|
|
int64 threadNum = ReadSLEB128();
|
|
if (threadNum == -1)
|
|
mCurCmdTarget = mRootCmdTarget;
|
|
else
|
|
mCurCmdTarget = mThreads[(int)threadNum];
|
|
case .ThreadAdd:
|
|
ReadTickDelta();
|
|
int32 threadId = (int32)ReadSLEB128();
|
|
int32 nativeThreadId = (int32)ReadSLEB128();
|
|
BpTrack newThread = new BpTrack(this, threadId);
|
|
newThread.mCreatedTick = mCurTick;
|
|
newThread.mStreamData.mStartTick = mCurTick;
|
|
newThread.mStreamData.mEndTick = mCurTick;
|
|
newThread.mNativeThreadId = nativeThreadId;
|
|
while (threadId >= mThreads.Count)
|
|
mThreads.Add(null);
|
|
mThreads[threadId] = newThread;
|
|
mThreadDataVersion++;
|
|
case .ThreadRemove:
|
|
ReadTickDelta();
|
|
mCurCmdTarget.mRemoveTick = mCurTick;
|
|
while (mCurCmdTarget.mEntryStack.Count > 0)
|
|
PopCurEntry();
|
|
case .ThreadName:
|
|
var thread = (BpTrack)mCurCmdTarget;
|
|
if (thread.mName == null)
|
|
thread.mName = new String();
|
|
ReadStr(thread.mName);
|
|
mThreadDataVersion++;
|
|
case .FrameTick:
|
|
ReadTickDelta();
|
|
mCurCmdTarget.mStreamData.Write((uint8)cmd);
|
|
mCurCmdTarget.mStreamData.WriteTickDelta(mCurTick);
|
|
mCurCmdTarget.mLastFrameTick = mCurTick;
|
|
|
|
mFrameTickCount++;
|
|
case .KeepAlive:
|
|
ReadTickDelta();
|
|
case .ClockInfo:
|
|
int64 curTick = ReadSLEB128() * 100;
|
|
Debug.Assert(curTick == mCurTick);
|
|
#unwarn
|
|
int64 timeStampNow = ReadSLEB128() * 100;
|
|
#unwarn
|
|
uint32 tickMSNow = (.)ReadSLEB128();
|
|
int64 clockRate = (.)ReadSLEB128();
|
|
|
|
mTicksToUSScale = 1000000.0 / clockRate;
|
|
|
|
/*if (mFirstTimeStamp == -1)
|
|
{
|
|
mFirstTimeStamp = timeStampNow;
|
|
mFirstTickMS = tickMSNow;
|
|
}
|
|
else
|
|
{
|
|
int64 deltaMS = (.)(tickMSNow - mFirstTickMS);
|
|
int64 deltaTimeStamp = timeStampNow - mFirstTimeStamp;
|
|
if (deltaMS > 100)
|
|
{
|
|
|
|
}
|
|
}*/
|
|
|
|
case .Event:
|
|
ReadTickDelta();
|
|
var nameStr = scope String(64);
|
|
var detailsStr = scope String(256);
|
|
ReadStr(nameStr);
|
|
ReadStr(detailsStr);
|
|
|
|
mCurCmdTarget.mStreamData.Write((uint8)cmd);
|
|
mCurCmdTarget.mStreamData.WriteTickDelta(mCurTick);
|
|
mCurCmdTarget.mStreamData.Write(nameStr.CStr(), nameStr.Length + 1);
|
|
mCurCmdTarget.mStreamData.Write(detailsStr.CStr(), detailsStr.Length + 1);
|
|
case .Cmd:
|
|
var cmdStr = scope String();
|
|
ReadStr(cmdStr);
|
|
bool success = gApp.HandleClientCommand(this, cmdStr);
|
|
int result = success ? 1 : 0;
|
|
mSocket.Send(&result, 1).IgnoreError();
|
|
default:
|
|
Runtime.FatalError("Invalid stream byte");
|
|
}
|
|
prevCmd = cmd;
|
|
|
|
mCircularBuffer.SetPtr(curPtr);
|
|
|
|
if (mCurCmdTarget != null)
|
|
mCurCmdTarget.MaybeSplitStreamData();
|
|
|
|
if ((mFirstTick == 0) && (mCurTick != 0))
|
|
mFirstTick = mCurTick;
|
|
|
|
mValidDataSize -= (startSize - mCircularBuffer.GetSize());
|
|
}
|
|
}
|
|
|
|
static int64 sDbgTick = 0;
|
|
|
|
public void Update()
|
|
{
|
|
mUpdateCnt++;
|
|
|
|
using (mDataMonitor.Enter())
|
|
{
|
|
ProcessData();
|
|
}
|
|
|
|
if (gApp.mUpdateCnt % 20 == 0)
|
|
CalcBPS();
|
|
}
|
|
}
|
|
}
|