Files
UmaViewer/Assets/Scripts/Audio/CRIMW/CriWare/Editor/CriProfiler.cs
T
2022-09-10 16:59:44 +08:00

1435 lines
46 KiB
C#

/****************************************************************************
*
* Copyright (c) 2018 CRI Middleware Co., Ltd.
*
****************************************************************************/
#if UNITY_EDITOR
using System;
using System.IO;
using System.Linq;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Diagnostics;
using UnityEngine;
namespace CriWare {
public partial class CriProfiler
{
private static CriProfiler singleton = new CriProfiler();
public static CriProfiler GetSingleton() { return singleton; }
#region Structs and Classes
// TCP connection parameters to be send to the thread
protected struct TcpParams {
public readonly IPAddress ipAddress;
public readonly int port;
public readonly int chunkSize;
public readonly int connectionTimeoutMillisec;
public readonly int connectionRetryIntervalMillisec;
public readonly int connectionRetryLimit;
public readonly int enBufferIntervalMillisec;
public TcpParams(
IPAddress ipAddress,
int port,
int chunkSize,
int connectionTimeoutMillisec,
int connectionRetryIntervalMillisec,
int connectionRetryLimit,
int enBufferIntervalMillisec)
{
this.ipAddress = ipAddress;
this.port = port;
this.chunkSize = chunkSize;
this.connectionTimeoutMillisec = connectionTimeoutMillisec;
this.connectionRetryIntervalMillisec = connectionRetryIntervalMillisec;
this.connectionRetryLimit = connectionRetryLimit;
this.enBufferIntervalMillisec = enBufferIntervalMillisec;
}
}
// TCP data buffer
private class RingBuffer<T> {
protected T[] data;
protected int size;
protected int head;
protected int tail;
protected int mask; /* for remainder operation */
static private int RegulateToPow2(uint n) {
int res = 0;
for (n -= 1; n != 0; n >>= 1) {
res = (res << 1) + 1;
}
return res + 1;
}
// Specify the size of the buffer.
// <para>The resulted size will be rounded up to the nearest 2^n number for the sake of performance.</para>
public RingBuffer(int _size) {
size = RegulateToPow2((uint)_size);
data = new T[size];
head = 0;
tail = 0;
mask = size - 1;
}
// Get the number of elements in the buffer
public int Count {
get {
int _count = this.tail - this.head;
if (_count < 0) _count += this.size;
return _count;
}
}
public int Capacity {
get {
return size - 1;
}
}
// Index access to the buffer.
public T this[int i] {
get {
return this.data[(i + this.head) & this.mask];
}
private set {
this.data[(i + this.head) & this.mask] = value;
}
}
// Copy [inputSize] of elements from inputData to the tail of the buffer.
// return false if the buffer is full.
public bool EnBuffer(T[] inputData, int inputSize) {
if (this.Count + inputSize >= this.size) {
return false;
}
for (int i = 0; i < inputSize; ++i) {
this.data[this.tail] = inputData[i];
this.tail = (this.tail + 1) & this.mask;
}
return true;
}
// Read [outputSize] of elements and delete it from the head of the buffer.
// <para>Return null if there is nothing to read.</para>
// <para>The resulted array may be smaller in length than requested.</para>
public T[] DeBuffer(int outputSize) {
if (outputSize <= 0 || this.Count <= 0) {
return null;
}
if (outputSize > this.Count) {
return null;
}
T[] res = new T[outputSize];
for (int i = 0; i < outputSize; ++i) {
res[i] = this[i];
}
this.head = (this.head + outputSize) & this.mask;
return res;
}
// Clear the buffer. Memory spaces will still be occupied.
public void Clear() {
this.head = 0;
this.tail = 0;
}
}
private class RingBufferAutoDequeue<T> : RingBuffer<T> {
public RingBufferAutoDequeue(int size) : base(size) { }
public new bool EnBuffer(T[] inputData, int inputSize) {
if (this.Count + inputSize >= this.size) {
this.DeBuffer(this.Count + inputSize - this.size + 1);
}
for (int i = 0; i < inputSize; ++i) {
this.data[this.tail] = inputData[i];
this.tail = (this.tail + 1) & this.mask;
}
return true;
}
public void FillWithZeros() {
T[] zeros = new T[Capacity];
this.EnBuffer(zeros, Capacity);
}
}
#region Loading / Saving data from big-endian data stream
public static Byte LoadBigEndianByte(Byte[] data, int ofst) {
return data[ofst];
}
public static void SaveBigEndianByte(Byte[] data, int ofst, Byte input) {
data[ofst] = input;
}
public static UInt16 LoadBigEndianUInt16(Byte[] data, int ofst) {
return (UInt16)( ((data[ofst + 0]) << 8)
| (data[ofst + 1]));
}
public static void SaveBigEndianUInt16(Byte[] data, int ofst, UInt16 input) {
data[ofst + 0] = (byte)((input >> 8) & 0xff);
data[ofst + 1] = (byte)( input & 0xff);
}
public static UInt32 LoadBigEndianUInt32(Byte[] data, int ofst) {
return (UInt32)( ((data[ofst + 0]) << 24)
| ((data[ofst + 1]) << 16)
| ((data[ofst + 2]) << 8)
| (data[ofst + 3]));
}
public static void SaveBigEndianUInt32(Byte[] data, int ofst, UInt32 input) {
data[ofst + 0] = (byte)((input >> 24) & 0xff);
data[ofst + 1] = (byte)((input >> 16) & 0xff);
data[ofst + 2] = (byte)((input >> 8) & 0xff);
data[ofst + 3] = (byte)( input & 0xff);
}
public static Single LoadBigEndianSingle(Byte[] data, int ofst) {
/* convert to little-endian for System.BitConverter */
byte[] byteVals = new[] { data[ofst + 3], data[ofst + 2], data[ofst + 1], data[ofst + 0] };
return BitConverter.ToSingle(byteVals, 0);
}
public static UInt64 LoadBigEndianUInt64(Byte[] data, int ofst) {
return (UInt64)( ((data[ofst + 0]) << 56)
| ((data[ofst + 1]) << 48)
| ((data[ofst + 2]) << 40)
| ((data[ofst + 3]) << 32)
| ((data[ofst + 4]) << 24)
| ((data[ofst + 5]) << 16)
| ((data[ofst + 6]) << 8)
| (data[ofst + 7]));
}
public static void SaveBigEndianUInt64(Byte[] data, int ofst, UInt64 input) {
data[ofst + 0] = (byte)((input >> 56) & 0xff);
data[ofst + 1] = (byte)((input >> 48) & 0xff);
data[ofst + 2] = (byte)((input >> 40) & 0xff);
data[ofst + 3] = (byte)((input >> 32) & 0xff);
data[ofst + 4] = (byte)((input >> 24) & 0xff);
data[ofst + 5] = (byte)((input >> 16) & 0xff);
data[ofst + 6] = (byte)((input >> 8) & 0xff);
data[ofst + 7] = (byte)( input & 0xff);
}
#endregion
protected struct TcpLogHeader {
public const int properSize
/* = sizeof(UInt32) + sizeof(UInt16) + sizeof(Byte) + sizeof(Byte) + sizeof(UInt64)
* + sizeof(UInt16) + sizeof(UInt16) + sizeof(UInt32) + sizeof(UInt64);
*/ = 32;
public readonly UInt32 packetSize;
public readonly UInt16 command;
public readonly Byte ptr_size;
public readonly Byte log_type;
public readonly UInt64 time;
public readonly UInt16 function_id;
public readonly UInt16 padding_size;
public readonly UInt32 control_id;
public readonly UInt64 thread_id;
public readonly bool isPtr64;
public readonly int size;
public TcpLogHeader(byte[] data) {
int offset = 0;
isPtr64 = false;
packetSize = LoadBigEndianUInt32(data, offset);
offset += sizeof(UInt32);
command = LoadBigEndianUInt16(data, offset);
offset += sizeof(UInt16);
ptr_size = LoadBigEndianByte(data, offset);
isPtr64 = ((ptr_size - sizeof(UInt32)) == 0) ? false : true;
offset += sizeof(Byte);
log_type = LoadBigEndianByte(data, offset);
offset += sizeof(Byte);
time = LoadBigEndianUInt64(data, offset);
offset += sizeof(UInt64);
function_id = LoadBigEndianUInt16(data, offset);
offset += sizeof(UInt16);
padding_size = LoadBigEndianUInt16(data, offset);
offset += sizeof(UInt16);
control_id = LoadBigEndianUInt32(data, offset);
offset += sizeof(UInt32);
if(isPtr64 == true) {
thread_id = LoadBigEndianUInt64(data, offset);
offset += sizeof(UInt64);
} else {
offset += sizeof(UInt32);
thread_id = LoadBigEndianUInt32(data, offset);
offset += sizeof(UInt32);
}
size = offset;
if (size != properSize) {
UnityEngine.Debug.LogError("[CRIWARE] (Profiler) Corrupted log header");
}
}
}
private class ParamIdDoesNotMatchException : Exception {
public ParamIdDoesNotMatchException() { }
public ParamIdDoesNotMatchException(string message) : base(message) { }
public ParamIdDoesNotMatchException(string message, Exception inner) : base(message, inner) { }
}
public class PlaybackInfo {
public readonly UInt32 playbackId;
public readonly String cueName;
public readonly String cueSheetName;
public readonly UInt64 timestamp;
public int? StreamType;
public PlaybackInfo(UInt32 playbackId, String cueName, String cueSheetName, UInt64 timestamp)
{
this.playbackId = playbackId;
this.cueName = cueName;
this.cueSheetName = cueSheetName;
this.timestamp = timestamp;
this.StreamType = null;
}
}
public class VoicePoolInfo {
public UInt32 soundFormat;
public int numUsedVoices;
public int? StreamType;
public VoicePoolInfo(UInt32 soundFormat, int? StreamType = null)
{
this.soundFormat = soundFormat;
this.StreamType = StreamType;
this.numUsedVoices = -1; /* <0: no data yet */
}
}
public struct LevelInfo {
public Single levelPeak;
public Single levelRms;
public Single levelPeakHold;
public LevelInfo(Single levelPeak, Single levelRms, Single levelPeakHold)
{
this.levelPeak = levelPeak;
this.levelRms = levelRms;
this.levelPeakHold = levelPeakHold;
}
}
#endregion
#region Internal data
private Thread threadTcpClient;
private Thread threadPacketReading;
private ManualResetEvent threadTerminator = new ManualResetEvent(false);
private RingBuffer<byte> TcpBuffer = new RingBuffer<byte>(16384);
private List<byte[]> eventLogList = new List<byte[]>();
private bool isProfiling = false;
private bool isConnected = false;
private bool isBufferCorrupted = false;
private bool doSaveLog;
private FileStream logStream = null;
private const int cRwLockTimeoutMs = 100;
public bool IsProfiling {
get { return isProfiling; }
}
#endregion
#region Fields / Properties
public UInt64 timestamp { get; private set; }
public UInt64 timestampStart { get; private set; }
public Single cpu_load { get; private set; }
private RingBufferAutoDequeue<Single> cpuLoadHistory = new RingBufferAutoDequeue<float>(100);
public Single[] CpuLoadHistory {
get {
lock (cpuLoadHistory) {
Single[] histArray = new Single[cpuLoadHistory.Count];
for (int i = 0; i < cpuLoadHistory.Count; ++i) {
histArray[i] = cpuLoadHistory[i];
}
return histArray;
}
}
}
public UInt32 num_used_voices { get; private set; }
private RingBufferAutoDequeue<UInt32> voiceUsageHistory = new RingBufferAutoDequeue<UInt32>(100);
public UInt32[] VoiceUsageHistory {
get {
lock (voiceUsageHistory) {
UInt32[] histArray = new UInt32[voiceUsageHistory.Count];
for (int i = 0; i < voiceUsageHistory.Count; ++i) {
histArray[i] = voiceUsageHistory[i];
}
return histArray;
}
}
}
public UInt32 num_used_streams { get; private set; }
public Single total_bps { get; private set; }
public UInt32 maxVoice_stdOnMemory { get; private set; }
public UInt32 maxVoice_stdStreaming { get; private set; }
public UInt32 maxVoice_hcamxOnMemory { get; private set; }
public UInt32 maxVoice_hcamxStreaming { get; private set; }
public Single loudness_momentary { get; private set; }
public Single loudness_shortTerm { get; private set; }
public Single loudness_integrated { get; private set; }
public int numChMasterOut { get; private set; }
private List<LevelInfo> levels = new List<LevelInfo>(); /* Channel => Levels */
public LevelInfo[] LevelsAllCh {
get {
if(levels.Count > 0) {
LevelInfo[] levelsCopy;
lock (levels) {
levelsCopy = new LevelInfo[levels.Count];
levels.CopyTo(levelsCopy);
}
return levelsCopy;
} else {
return null;
}
}
}
public String cuename_lastPlayed { get; private set; }
public String cuesheetName_lastPlayed { get; private set; }
private Hashtable playbackHashtable = new Hashtable(); /* Playback ID => Playback Info */
public PlaybackInfo[] PlaybackList {
get {
lock (playbackHashtable) {
PlaybackInfo[] playbackList = new PlaybackInfo[playbackHashtable.Count];
playbackHashtable.Values.CopyTo(playbackList, 0);
return playbackList.OrderBy(x => x.timestamp).ToArray();
}
}
}
private Dictionary<string, CueSheetGroup> multiLaneTimeline = new Dictionary<string, CueSheetGroup>();
private Hashtable voicePoolInfoTable = new Hashtable(); /* VoicePool Handle => VoicePool Info */
public enum VoicePoolFormat {
Standard,
HcaMx
}
public int GetVoicePoolUsage(VoicePoolFormat format) {
int usage = 0;
bool haveData = false;
UInt32 formatBits = FORMAT_NONE;
switch (format) {
case VoicePoolFormat.Standard:
formatBits = FORMAT_ADX | FORMAT_HCA;
break;
case VoicePoolFormat.HcaMx:
formatBits = FORMAT_HCA_MX;
break;
default:
break;
}
lock (voicePoolInfoTable) {
foreach(VoicePoolInfo elem in voicePoolInfoTable.Values) {
if (elem.soundFormat == formatBits && elem.numUsedVoices >= 0) {
usage += (int)elem.numUsedVoices;
haveData = true;
}
}
}
if(haveData) {
return usage;
} else {
return -1;
}
}
public List<byte[]> GetLogList() {
return eventLogList;
}
public List<byte[]> GetLogList(int offset, int count) {
try {
offset = Mathf.Clamp(offset, 0, eventLogList.Count - 1);
count = Mathf.Clamp(count, 0, eventLogList.Count - offset);
return eventLogList.GetRange(offset, count);
} catch {
return new List<byte[]>();
}
}
public void ClearLog() {
eventLogList.Clear();
}
#endregion Fields / Properties
#region Data Initialization
private void ResetVals() {
this.InitVals();
}
private void InitVals() {
this.timestamp = 0;
this.timestampStart = 0;
this.cpu_load = 0;
this.cpuLoadHistory.FillWithZeros();
this.num_used_voices = 0;
this.voiceUsageHistory.FillWithZeros();
this.num_used_streams = 0;
this.total_bps = 0.0f;
this.maxVoice_stdOnMemory = 0;
this.maxVoice_stdStreaming = 0;
this.maxVoice_hcamxOnMemory = 0;
this.maxVoice_hcamxStreaming = 0;
this.loudness_momentary = Mathf.NegativeInfinity;
this.loudness_shortTerm = Mathf.NegativeInfinity;
this.loudness_integrated = Mathf.NegativeInfinity;
this.cuename_lastPlayed = "";
this.numChMasterOut = 0;
this.voicePoolInfoTable.Clear();
this.playbackHashtable.Clear();
this.multiLaneTimeline.Clear();
this.levels.Clear();
this.eventLogList.Clear();
}
#endregion
#region Profiler Control
/* connection settings */
private const int TCP_PORT = 2002;
private const int TCP_CHUNK = 2048;
private const int TCP_CONNECT_TIMEOUT_MS = 500;
private const int TCP_RETRY_INTERVAL_MS = 2000;
private const int TCP_RETRY = 5;
private const int BUFFERING_INTERVAL_MS = 10;
public const string LOG_FILE_EXTENSION = "crimon";
public string ipAddressString = "127.0.0.1";
public string LogFileSavePath { get; private set; }
// Starting the TCP sub thread.
public void StartProfiling(bool saveLog, string logDirPath = null) {
IPAddress validIp;
if(IPAddress.TryParse(ipAddressString, out validIp) == false) {
UnityEngine.Debug.LogWarning("[CRIWARE] (Profiler) IP address not valid - connection aborted. ");
return;
}
this.doSaveLog = saveLog;
if (this.doSaveLog) {
if (string.IsNullOrEmpty(logDirPath)) {
/* default log file path */
logDirPath = Path.Combine(Directory.GetParent(Application.dataPath).ToString(), "CriWareProfilerLog");
}
this.LogFileSavePath = logDirPath;
if (Directory.Exists(LogFileSavePath) == false) {
try {
Directory.CreateDirectory(LogFileSavePath);
} catch (Exception e) {
UnityEngine.Debug.LogError("[CRIWARE] Profiler: failed to create directory for log files. Msg: " + e.Message);
}
}
var filePath = Path.Combine(LogFileSavePath, "CriProfilerLog-" + DateTime.Now.ToString("yyyy-MM-dd-HHmmss") + "." + LOG_FILE_EXTENSION);
try {
this.logStream = new FileStream(filePath, FileMode.Create, FileAccess.Write);
} catch (Exception e) {
UnityEngine.Debug.LogError("[CRIWARE] Profiler: failed to create log file. Msg: " + e.Message);
this.logStream = null;
this.doSaveLog = false;
}
}
TcpParams tcpParams = new TcpParams(
validIp,
TCP_PORT,
TCP_CHUNK,
TCP_CONNECT_TIMEOUT_MS,
TCP_RETRY_INTERVAL_MS,
TCP_RETRY,
BUFFERING_INTERVAL_MS);
const int READ_INTERVAL_MS = 0;
if ((threadTcpClient == null || threadTcpClient.IsAlive == false) &&
(threadPacketReading == null || threadPacketReading.IsAlive == false)
) {
InitVals();
threadTerminator.Reset();
threadTcpClient = new Thread(() => TaskTcpClient(tcpParams));
threadPacketReading = new Thread(() => TaskPacketReading(READ_INTERVAL_MS));
threadTcpClient.Start();
threadPacketReading.Start();
}
}
// Stopping the TCP sub thread.
public void StopProfiling() {
threadTerminator.Set();
ResetVals();
if (this.logStream != null) {
this.doSaveLog = false;
try {
this.logStream.Flush();
this.logStream.Close();
this.logStream = null;
} catch (Exception e) {
UnityEngine.Debug.LogError("[CRIWARE] Profiler: failed to flush log file. Msg: " + e.Message);
}
}
}
private CriProfiler() {
/* hide constructor for singleton */
InitVals();
}
~CriProfiler() {
this.StopProfiling();
}
#endregion
#region TCP Connection Management
// TCP connection sub thread task.
private void TaskTcpClient(TcpParams tcpParams) {
int failedConnectCnt = 0;
this.isProfiling = true;
using (var tcpClient = new TcpClient()) {
while (true) {
try {
tcpClient.Connect(tcpParams.ipAddress, tcpParams.port);
this.isConnected = true;
UnityEngine.Debug.Log("[CRIWARE] CRI Profiler connected.");
BufferingLoop(tcpClient, tcpParams);
} catch {
failedConnectCnt += 1;
if(failedConnectCnt > tcpParams.connectionRetryLimit) {
UnityEngine.Debug.LogWarning("[CRIWARE] Retry count exceeded limit(" + tcpParams.connectionRetryLimit + "); Stopped.");
threadTerminator.Set();
} else {
UnityEngine.Debug.LogWarning("[CRIWARE] Unable to connect CRI profiler. (Check if \"Uses In Game Preview\" is turned on)"
+"\nWaiting for " + (tcpParams.connectionRetryIntervalMillisec / 1000.0f).ToString("F1") + " seconds to reconnect... (" + failedConnectCnt+ "/" + tcpParams.connectionRetryLimit + " tries)");
}
}
this.isConnected = false;
if (threadTerminator.WaitOne(tcpParams.connectionRetryIntervalMillisec)) {
UnityEngine.Debug.Log("[CRIWARE] CRI Profiler disconnected.");
this.isProfiling = false;
break;
}
}
}
}
// Looping process to push the socket input to the buffer.
private void BufferingLoop(TcpClient tcpClient, TcpParams tcpParams) {
int numBytesRead = 0;
byte[] chunk = new byte[tcpParams.chunkSize];
byte[] sendPacket;
bool enbufferRes = true;
this.TcpBuffer.Clear();
using (var netStream = tcpClient.GetStream()) {
if (netStream == null) {
UnityEngine.Debug.LogError("[CRIWARE] (Profiler) TCP connection not available.");
return;
}
if (netStream.CanRead == false) {
UnityEngine.Debug.LogError("[CRIWARE] (Profiler) TCP connection not readable.");
return;
}
sendPacket = MakeLogPacket4StartStopLog(SendPacketType.StartLog);
if (sendPacket != null && netStream.CanWrite) {
netStream.Write(sendPacket, 0, sendPacket.Length);
}
while (true) {
try {
if (netStream.DataAvailable) {
numBytesRead = netStream.Read(chunk, 0, chunk.Length);
if (this.isBufferCorrupted == false) {
lock (this.TcpBuffer) {
enbufferRes = this.TcpBuffer.EnBuffer(chunk, numBytesRead);
}
}
if (enbufferRes == false) {
UnityEngine.Debug.LogWarning("[CRIWARE] TCP reading buffer overflowed.");
// -- buffer corrupted -> prepare to reset --
// Enbuffer failure may cause incomplete packets in the packet queue.
// The solution we take is skipping all incoming data until a "tail" chunk (followed by no data) arrives,
// right after which the buffer will be reset.
this.isBufferCorrupted = true;
}
} else {
if (this.isBufferCorrupted == true) {
lock (this.TcpBuffer) {
/* reset the corrupted buffer when all available data are received */
this.TcpBuffer.Clear();
}
this.isBufferCorrupted = false;
}
}
} catch (System.IO.IOException) {
threadTerminator.Set();
}
if (threadTerminator.WaitOne(tcpParams.enBufferIntervalMillisec)) {
break;
}
}
sendPacket = MakeLogPacket4StartStopLog(SendPacketType.StopLog);
if (sendPacket != null && netStream.CanWrite) {
netStream.Write(sendPacket, 0, sendPacket.Length);
}
}
}
// Sending control packet to start / stop receiving log data
private enum SendPacketType {
StartLog,
StopLog
}
private byte[] MakeLogPacket4StartStopLog(SendPacketType sendType) {
int ptr_size = IntPtr.Size;
const LogTypes log_type = LogTypes.NON;
const UInt64 time = 0;
const LogFuncId function_id = LogFuncId.LOG_COMMAND_Non;
const UInt32 control_id = 0;
const UInt64 thread_id = 0;
int size;
TcpCommandId command;
int padding_size;
switch (sendType) {
case SendPacketType.StartLog:
size = TcpLogHeader.properSize
/* + sizeof(UInt16)
* + CriProfiler.LogParams[(int)LogParamItems.LOG_STRINGS_ITEM_LogRecordMode].size32/64;
*/ + 6;
command = TcpCommandId.CRITCP_MAIL_START_LOG_RECORD;
padding_size = 2; /* 8 - (38 mod 8) */
size += padding_size;
break;
case SendPacketType.StopLog:
size = TcpLogHeader.properSize;
command = TcpCommandId.CRITCP_MAIL_STOP_LOG_RECORD;
padding_size = 0; /* 8 - (32 mod 8) */
size += padding_size;
break;
default:
return null;
}
byte[] packetBytes = new byte[size];
int offset = 0;
SaveBigEndianUInt32(packetBytes, offset, (UInt32)size);
offset += sizeof(UInt32);
SaveBigEndianUInt16(packetBytes, offset, (UInt16)command);
offset += sizeof(UInt16);
SaveBigEndianByte(packetBytes, offset, (Byte)ptr_size);
offset += sizeof(Byte);
SaveBigEndianByte(packetBytes, offset, (Byte)log_type);
offset += sizeof(Byte);
SaveBigEndianUInt64(packetBytes, offset, (UInt64)time);
offset += sizeof(UInt64);
SaveBigEndianUInt16(packetBytes, offset, (UInt16)(function_id + CriProfiler.logFuncBaseNum));
offset += sizeof(UInt16);
SaveBigEndianUInt16(packetBytes, offset, (UInt16)padding_size);
offset += sizeof(UInt16);
SaveBigEndianUInt32(packetBytes, offset, (UInt32)control_id);
offset += sizeof(UInt32);
SaveBigEndianUInt64(packetBytes, offset, (UInt64)thread_id);
offset += sizeof(UInt64);
switch (sendType) {
case SendPacketType.StartLog:
SaveBigEndianUInt16(packetBytes, offset, (UInt16)LogParamId.LOG_STRINGS_ITEM_LogRecordMode);
offset += sizeof(UInt16);
SaveBigEndianUInt32(packetBytes, offset, (UInt32)LOG_MODE_ALL); /* size of LOG_STRINGS_ITEM_LogRecordMode */
offset += sizeof(UInt32);
break;
case SendPacketType.StopLog:
break;
default:
return null;
}
for (int i = 0; i < padding_size; ++i) {
packetBytes[offset] = 0;
offset += 1;
}
if (offset == size) {
return packetBytes;
} else {
return null;
}
}
#endregion
#region Parser
// Sub thread task to extract TCP packets from the buffer.
private void TaskPacketReading(int readIntervalMillisec) {
Stopwatch stopwatch = new Stopwatch();
const int threadMaxLifeMs = 20000; /* should be longer than total TCP retrying time */
const int threadRetryLifeMs = 500;
byte[] data = null;
TcpLogHeader packetHeader;
int packetSize = 0;
stopwatch.Start();
while (true) {
if (this.isBufferCorrupted == false) {
lock (this.TcpBuffer) {
packetSize = GetPacketSize(TcpBuffer);
data = this.TcpBuffer.DeBuffer(packetSize);
}
if (data != null) {
stopwatch.Reset();
stopwatch.Start();
packetHeader = new TcpLogHeader(data);
try {
if (packetHeader.size > 0) {
if (this.doSaveLog && this.logStream != null) {
try {
this.logStream.Write(data, 0, data.Length);
} catch (Exception e) {
UnityEngine.Debug.LogError("[CRIWARE] Profiler: failed to write logs. Msg: " + e.Message);
this.doSaveLog = false;
}
}
this.Parser(data, packetHeader);
} /* if headerSize > 0 */
} catch (Exception ex) { /* Any parsing error */
if(ex is ParamIdDoesNotMatchException) {
UnityEngine.Debug.LogWarning("[CRIWARE] " + ex.Message.ToString());
}
UnityEngine.Debug.LogWarning("[CRIWARE] (Profiler) Packet parsing failed: buffer may be corrupted. Reconstructing buffer..."
+"\nLog Function: " + GetLogFuncId(packetHeader.function_id).ToString());
this.isBufferCorrupted = true;
}
} else {
/* kill all threads if waited for too long */
if (this.isConnected == true) {
if (stopwatch.ElapsedMilliseconds > threadRetryLifeMs) {
this.isProfiling = false;
threadTerminator.Set();
}
} else {
if (stopwatch.ElapsedMilliseconds > threadMaxLifeMs) {
this.isProfiling = false;
threadTerminator.Set();
}
}
} /* if data != null */
} /* if isBufferCorrupted == false */
if (threadTerminator.WaitOne(readIntervalMillisec)) {
this.isProfiling = false;
break;
}
}
}
// Get the size of the packet from the header.
// Return 0 if there is no enough data to determine the size.
private int GetPacketSize(RingBuffer<byte> buffer) {
if (buffer == null || buffer.Count < DATA_LENGTH_PARAM_SIZE) {
return 0;
}
uint result = buffer[0];
for (int i = 1; i < DATA_LENGTH_PARAM_SIZE; ++i) {
result <<= 8;
result += buffer[i];
}
return (int)result;
}
private int GetPacketSize(byte[] data, uint offset = 0) {
if (data == null || data.Length - offset < DATA_LENGTH_PARAM_SIZE) {
return 0;
}
uint result = data[offset];
for (int i = 0; i < DATA_LENGTH_PARAM_SIZE; i++) {
result <<= 8;
result += data[i + offset];
}
return (int)result;
}
public void LoadLogFromFile(string path) {
if (string.IsNullOrEmpty(path)) {
UnityEngine.Debug.LogError("[CRIWARE] Profiler: Log file path cannot be empty");
}
try {
using (var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read)) {
if (fileStream.CanRead) {
eventLogList.Clear();
byte[] reusedSizeData = new byte[DATA_LENGTH_PARAM_SIZE];
while (fileStream.Position + DATA_LENGTH_PARAM_SIZE < fileStream.Length) {
var read = fileStream.Read(reusedSizeData, 0, DATA_LENGTH_PARAM_SIZE);
/* roll back read position */
fileStream.Position -= read;
/* get actual data size */
var dataSize = GetPacketSize(reusedSizeData);
if (dataSize > 0) {
byte[] data = new byte[dataSize];
fileStream.Read(data, 0, dataSize);
this.LogParser(data);
} else {
UnityEngine.Debug.LogError("[CRIWARE] Profiler: Error while reading log");
break;
}
}
} else {
UnityEngine.Debug.LogError("[CRIWARE] Profiler: Log file cannot be read");
}
}
} catch (Exception e) {
UnityEngine.Debug.LogError("[CRIWARE] Profiler: Error while opening log: " + e.Message);
}
}
private void LogParser(byte[] data) {
var header = new TcpLogHeader(data);
switch ((TcpCommandId)header.command) {
case TcpCommandId.CRITCP_MAIL_SEND_LOG:
switch (GetLogFuncId(header.function_id)) {
case LogFuncId.LOG_COMMAND_ExPlaybackId:
this.AddEventLog(data);
break;
default:
break;
}
break;
case TcpCommandId.CRITCP_MAIL_MONITOR_ATOM_EXPLAYBACKINFO_PLAY_END:
switch (GetLogFuncId(header.function_id)) {
case LogFuncId.LOG_COMMAND_ExPlaybackInfo_FreeInfo:
this.AddEventLog(data);
break;
default:
break;
}
break;
default:
break;
}
}
private void Parser(byte[] data, TcpLogHeader header) {
int offset = 0;
int offset2 = 0;
int dataSize = 0;
int dataSize2 = 0;
UInt32 playbackId;
StreamTypes streamType;
VoicePoolInfo voicePoolInfo;
UInt64 voicePoolHn;
Single levelPeak;
Single levelRms;
Single levelPeakHold;
int numCh = 0;
int numOutputCh;
int numMaxCh = 0;
switch ((TcpCommandId)header.command) {
case TcpCommandId.CRITCP_MAIL_PREVIEW_CPU_LOAD:
this.timestamp = header.time;
if (this.timestampStart == 0) {
this.timestampStart = this.timestamp;
}
if (GetLogFuncId(header.function_id) == LogFuncId.LOG_COMMAND_CpuLoadAndNumUsedVoices) {
this.cpu_load = LoadBigEndianSingle(data, FindParamOffset(data, header, LogParamId.LOG_STRINGS_ITEM_CpuLoad, out offset));
this.num_used_voices = LoadBigEndianUInt32(data, GetNextParamOffset(data, header, LogParamId.LOG_STRINGS_ITEM_NumUsedVoices, ref offset));
lock (cpuLoadHistory) {
cpuLoadHistory.EnBuffer(new Single[] { cpu_load }, 1);
}
lock (voiceUsageHistory) {
voiceUsageHistory.EnBuffer(new UInt32[] { num_used_voices }, 1);
}
}
break;
case TcpCommandId.CRITCP_MAIL_SEND_LOG:
this.timestamp = header.time;
if (this.timestampStart == 0) {
this.timestampStart = this.timestamp;
}
switch (GetLogFuncId(header.function_id)) {
case LogFuncId.LOG_COMMAND_LoudnessInfo:
this.loudness_momentary = LoadBigEndianSingle(data, FindParamOffset(data, header, LogParamId.LOG_STRINGS_ITEM_MomentaryValue, out offset));
this.loudness_shortTerm = LoadBigEndianSingle(data, GetNextParamOffset(data, header, LogParamId.LOG_STRINGS_ITEM_ShortTermValue, ref offset));
this.loudness_integrated = LoadBigEndianSingle(data, GetNextParamOffset(data, header, LogParamId.LOG_STRINGS_ITEM_IntegratedValue, ref offset));
break;
case LogFuncId.LOG_COMMAND_StreamingInfo:
this.num_used_streams = LoadBigEndianUInt32(data, FindParamOffset(data, header, LogParamId.LOG_STRINGS_ITEM_NumUsedVoices, out offset));
this.total_bps = LoadBigEndianSingle(data, GetNextParamOffset(data, header, LogParamId.LOG_STRINGS_ITEM_TotalBps, ref offset));
break;
case LogFuncId.LOG_COMMAND_AsrBusAnalyzeInfoAllCh:
if (LoadBigEndianByte(data, FindParamOffset(data, header, LogParamId.LOG_STRINGS_ITEM_BusNo, out offset)) == 0) { /* if the bus is MasterOut(bus0) */
numCh = LoadBigEndianByte(data, FindParamOffset(data, header, LogParamId.LOG_STRINGS_ITEM_NumCh, out offset));
lock (levels) {
if (numMaxCh == 0) {
numMaxCh = numCh;
levels.Clear();
for (int i = 0; i < numMaxCh; ++i) {
levels.Add(new LevelInfo(Mathf.NegativeInfinity, Mathf.NegativeInfinity, Mathf.NegativeInfinity));
}
}
if (numMaxCh == numCh) {
for (int i = 0; i < numCh; ++i) {
levelPeak = LoadBigEndianSingle(data, GetNextParamOffset(data, header, LogParamId.LOG_STRINGS_ITEM_PeakLevel, ref offset));
levelRms = LoadBigEndianSingle(data, GetNextParamOffset(data, header, LogParamId.LOG_STRINGS_ITEM_RmsLevel, ref offset));
levelPeakHold = LoadBigEndianSingle(data, GetNextParamOffset(data, header, LogParamId.LOG_STRINGS_ITEM_PeakHoldLevel, ref offset));
levels[i] = new LevelInfo(levelPeak, levelRms, levelPeakHold);
}
}
}
}
break;
case LogFuncId.LOG_COMMAND_ExStandardVoicePoolConfig:
if (Convert.ToBoolean(LoadBigEndianByte(data, FindParamOffset(data, header, LogParamId.LOG_STRINGS_ITEM_StreamingFlag, out offset))) == false) {
this.maxVoice_stdOnMemory = LoadBigEndianUInt32(data, FindParamOffset(data, header, LogParamId.LOG_STRINGS_ITEM_NumVoices, out offset));
} else {
this.maxVoice_stdStreaming = LoadBigEndianUInt32(data, FindParamOffset(data, header, LogParamId.LOG_STRINGS_ITEM_NumVoices, out offset));
}
break;
case LogFuncId.LOG_COMMAND_ExHcaMxVoicePoolConfig:
if (Convert.ToBoolean(LoadBigEndianByte(data, FindParamOffset(data, header, LogParamId.LOG_STRINGS_ITEM_StreamingFlag, out offset))) == false) {
this.maxVoice_hcamxOnMemory = LoadBigEndianUInt32(data, FindParamOffset(data, header, LogParamId.LOG_STRINGS_ITEM_NumVoices, out offset));
} else {
this.maxVoice_hcamxStreaming = LoadBigEndianUInt32(data, FindParamOffset(data, header, LogParamId.LOG_STRINGS_ITEM_NumVoices, out offset));
}
break;
case LogFuncId.LOG_COMMAND_ExVoicePoolHn:
try {
voicePoolHn = LoadBigEndianUInt64(data, FindParamOffset(data, header, LogParamId.LOG_STRINGS_ITEM_ExVoicePoolHn, out offset));
voicePoolInfo = new VoicePoolInfo(LoadBigEndianUInt32(data, GetNextParamOffset(data, header, LogParamId.LOG_STRINGS_ITEM_SoundFormat, ref offset)), null);
lock (voicePoolInfoTable) {
voicePoolInfoTable.Add(voicePoolHn, voicePoolInfo);
}
} catch (ArgumentException) {
/* Voice pool info already exists; Do nothing */
}
break;
case LogFuncId.LOG_COMMAND_ExVoicePool_Free:
voicePoolHn = LoadBigEndianUInt64(data, FindParamOffset(data, header, LogParamId.LOG_STRINGS_ITEM_ExVoicePoolHn, out offset));
lock (voicePoolInfoTable) {
voicePoolInfoTable.Remove(voicePoolHn);
}
break;
case LogFuncId.LOG_COMMAND_PlayerPool_NumVoices:
voicePoolHn = LoadBigEndianUInt64(data, FindParamOffset(data, header, LogParamId.LOG_STRINGS_ITEM_ExVoicePoolHn, out offset));
lock (voicePoolInfoTable) {
if (voicePoolInfoTable[voicePoolHn] != null) {
(voicePoolInfoTable[voicePoolHn] as VoicePoolInfo).numUsedVoices = (int)LoadBigEndianUInt32(data, GetNextParamOffset(data, header, LogParamId.LOG_STRINGS_ITEM_NumUsedVoices, ref offset));
}
}
break;
case LogFuncId.LOG_COMMAND_ExPlaybackId:
playbackId = LoadBigEndianUInt32(data, FindParamOffset(data, header, LogParamId.LOG_STRINGS_ITEM_ExPlaybackId, out offset));
dataSize = LoadBigEndianUInt16(data, FindParamOffset(data, header, LogParamId.LOG_STRINGS_ITEM_CueName, out offset)); /* cue name */
dataSize2 = LoadBigEndianUInt16(data, FindParamOffset(data, header, LogParamId.LOG_STRINGS_ITEM_ExAcbName, out offset2)); /* cuesheet name */
if (dataSize > 1 && dataSize2 > 1) {
this.cuename_lastPlayed = System.Text.Encoding.Default.GetString(data, GetStrDataOffset(header, offset), dataSize);
this.cuesheetName_lastPlayed = System.Text.Encoding.Default.GetString(data, GetStrDataOffset(header, offset2), dataSize2);
try {
lock (playbackHashtable) {
this.playbackHashtable.Add(playbackId, new PlaybackInfo(playbackId, cuename_lastPlayed, cuesheetName_lastPlayed, header.time));
}
} catch (ArgumentException) {
/* Duplicated playback info received; Do nothing */
}
this.AddEventLog(data);
this.AddPlaybackToTimeline(new Playback(playbackId, header.time, true, cuename_lastPlayed, cuesheetName_lastPlayed));
} else {
this.cuename_lastPlayed = "";
}
break;
case LogFuncId.LOG_COMMAND_SoundVoice_Allocate:
playbackId = LoadBigEndianUInt32(data, FindParamOffset(data, header, LogParamId.LOG_STRINGS_ITEM_ExPlaybackId, out offset));
streamType = (StreamTypes)LoadBigEndianByte(data, FindParamOffset(data, header, LogParamId.LOG_STRINGS_ITEM_StreamType, out offset));
lock (playbackHashtable) {
if (playbackHashtable.ContainsKey(playbackId)) {
(playbackHashtable[playbackId] as PlaybackInfo).StreamType = (int)streamType;
}
}
break;
case LogFuncId.LOG_COMMAND_ExAsrConfig:
numOutputCh = LoadBigEndianByte(data, FindParamOffset(data, header, LogParamId.LOG_STRINGS_ITEM_OutputChannels, out offset));
if (numOutputCh > this.numChMasterOut) {
numChMasterOut = numOutputCh;
}
break;
default:
break;
}
break;
case TcpCommandId.CRITCP_MAIL_MONITOR_ATOM_EXPLAYBACKINFO_PLAY_END:
this.timestamp = header.time;
if (this.timestampStart == 0) {
this.timestampStart = this.timestamp;
}
switch (GetLogFuncId(header.function_id)) {
case LogFuncId.LOG_COMMAND_ExPlaybackInfo_FreeInfo:
playbackId = LoadBigEndianUInt32(data, FindParamOffset(data, header, LogParamId.LOG_STRINGS_ITEM_ExPlaybackId, out offset));
lock (playbackHashtable) {
if (playbackHashtable.ContainsKey(playbackId)) {
var info = playbackHashtable[playbackId] as PlaybackInfo;
this.AddEventLog(data);
this.AddPlaybackToTimeline(new Playback(playbackId, header.time, false, info.cueName, info.cueSheetName));
playbackHashtable.Remove(playbackId);
}
}
break;
default:
break;
}
break;
default:
UserDefinedParser(data, header);
break;
}
}
// Override this method to add new parsing procedure
protected virtual void UserDefinedParser(byte[] data, TcpLogHeader packetHeader) {
// e.g.
//int offset;
//switch ((TcpCommandId)packetHeader.command) {
// case TcpCommandId.<CRITCP_MAIL_xxx>:
// switch (GetLogFuncId(packetHeader.function_id)) {
// case LogFuncId.<LOG_COMMAND_xxx>:
// <parsing>
// break;
// default:
// break;
// }
// break;
// default:
// break;
//}
}
private void AddEventLog(byte[] data) {
this.eventLogList.Add(data);
}
static private LogFuncId GetLogFuncId(UInt16 rawId) {
return (LogFuncId)(rawId - CriProfiler.logFuncBaseNum);
}
public string FilteredLog2String(byte[] data) {
if (data == null || data.Length < TcpLogHeader.properSize) { return string.Empty; }
var header = new TcpLogHeader(data);
var funcId = GetLogFuncId(header.function_id);
uint playbackId;
int dataSize, dataSize2, offset, offset2;
string str1, str2;
string output = string.Empty;
switch (funcId) {
case LogFuncId.LOG_COMMAND_ExPlaybackId:
playbackId = LoadBigEndianUInt32(data, FindParamOffset(data, header, LogParamId.LOG_STRINGS_ITEM_ExPlaybackId, out offset));
dataSize = LoadBigEndianUInt16(data, FindParamOffset(data, header, LogParamId.LOG_STRINGS_ITEM_CueName, out offset)); /* cue name */
dataSize2 = LoadBigEndianUInt16(data, FindParamOffset(data, header, LogParamId.LOG_STRINGS_ITEM_ExAcbName, out offset2)); /* cuesheet name */
str1 = System.Text.Encoding.Default.GetString(data, GetStrDataOffset(header, offset), dataSize);
str2 = System.Text.Encoding.Default.GetString(data, GetStrDataOffset(header, offset2), dataSize2);
output = MicroSec2String(header.time) + " | Start ID : " + playbackId + " | cuename : " + str1 + " | cuesheet : " + str2;
break;
case LogFuncId.LOG_COMMAND_ExPlaybackInfo_FreeInfo:
playbackId = LoadBigEndianUInt32(data, FindParamOffset(data, header, LogParamId.LOG_STRINGS_ITEM_ExPlaybackId, out offset));
output = MicroSec2String(header.time) + " | End ID : " + playbackId;
break;
default:
break;
}
return output;
}
static private string MicroSec2String(ulong microSec) {
ulong hour = microSec / 3600000000;
ulong min = (microSec % 3600000000) / 60000000;
ulong sec = (microSec % 60000000) / 1000000;
ulong msec = (microSec % 1000000) / 1000;
return string.Format("{0}:{1}:{2}.{3}", hour, min, sec, msec);
}
#region Methods for data offset calculation
private const int paramIdSize = 2;
private int GetStrDataOffset(TcpLogHeader header, int paramIdOffset) {
return paramIdOffset + CriProfiler.paramIdSize + (header.isPtr64
? ParamTypeSizes64[(int)CriProfiler.LogParamTypes.TYPE_CHAR]
: ParamTypeSizes32[(int)CriProfiler.LogParamTypes.TYPE_CHAR]);
}
private int FindParamOffset(byte[] data, TcpLogHeader header, LogParamId logParamId, out int currentOffset) {
if (data.Length < TcpLogHeader.properSize) {
throw new ParamIdDoesNotMatchException("Could not find parameter: " + logParamId.ToString());
}
int offset = header.size;
int paramId;
int dataSize;
while (offset < header.packetSize) {
paramId = LoadBigEndianUInt16(data, offset);
if ((LogParamId)paramId == logParamId) {
currentOffset = offset;
return offset + paramIdSize;
}
dataSize = header.isPtr64 ? LogParams[paramId].size64 : LogParams[paramId].size32;
if (LogParams[paramId].type == LogParamTypes.TYPE_CHAR) {
dataSize += LoadBigEndianUInt16(data, offset + paramIdSize);
}
offset += paramIdSize + dataSize;
}
throw new ParamIdDoesNotMatchException("Could not find parameter: " + logParamId.ToString());
}
private int GetNextParamOffset(byte[] data, TcpLogHeader header, LogParamId logParamId, ref int currentOffset) {
if (currentOffset >= header.packetSize) {
throw new ParamIdDoesNotMatchException("Could not find parameter: " + logParamId.ToString());
}
int paramId = LoadBigEndianUInt16(data, currentOffset);
int dataSize = header.isPtr64 ? CriProfiler.LogParams[paramId].size64 : CriProfiler.LogParams[paramId].size32;
if (CriProfiler.LogParams[paramId].type == LogParamTypes.TYPE_CHAR) {
dataSize += LoadBigEndianUInt16(data, currentOffset + paramIdSize);
}
/* set parameter id offset */
currentOffset += paramIdSize + dataSize;
if ((LogParamId)LoadBigEndianUInt16(data, currentOffset) != logParamId) {
throw new ParamIdDoesNotMatchException("Parsed parameter ID does not match with " + logParamId.ToString());
}
/* return parameter data offset */
return currentOffset + paramIdSize;
}
#endregion Methods for data offset calculation
#endregion Parser
#region Timeline
/* CueSheet - Cue - Playback Lane (multiple non-overlapping playback lists) - Timestamp (true = start, false = end) */
private void AddPlaybackToTimeline(Playback playback) {
CueSheetGroup group;
if (multiLaneTimeline.TryGetValue(playback.cuesheet, out group) == false) {
group = new CueSheetGroup(playback.cuesheet);
multiLaneTimeline.Add(playback.cuesheet, group);
}
group.AddPlayback(playback);
}
public Dictionary<string, CueSheetGroup> TimelineDataSlice {
get {
var output = new Dictionary<string, CueSheetGroup>();
foreach (var elem in multiLaneTimeline) {
output.Add(elem.Key, elem.Value.Clone());
}
return output;
}
}
public class CueSheetGroup : ICloneable {
public string name;
public Color cuesheetColor = Color.white;
public Dictionary<string, CueLane> cueLaneDict;
static private readonly Color baseColor = new Color(0.5412f, 1f, 0.3451f);
public CueSheetGroup(string name) {
this.name = name;
{/* set distinguishing color */
float h, s, v;
Color.RGBToHSV(baseColor, out h, out s, out v);
System.Random rand = new System.Random();
this.cuesheetColor = Color.HSVToRGB(rand.Next(0, 10000) / 10000f, s, v);
}
cueLaneDict = new Dictionary<string, CueLane>();
}
public CueSheetGroup(CueSheetGroup group) {
this.name = group.name;
this.cuesheetColor = group.cuesheetColor;
this.cueLaneDict = new Dictionary<string, CueLane>();
foreach (var elem in group.cueLaneDict) {
cueLaneDict.Add(elem.Key, elem.Value.Clone());
}
}
public CueSheetGroup Clone() { return new CueSheetGroup(this); }
object ICloneable.Clone() { return Clone(); }
public void AddPlayback(Playback playback) {
CueLane lane;
if (cueLaneDict.TryGetValue(playback.cuename, out lane) == false) {
lane = new CueLane(playback.cuename, playback.cuesheet);
cueLaneDict.Add(playback.cuename, lane);
}
lane.AddPlayback(playback);
}
public int TotalLaneCount {
get {
int cnt = 0;
foreach (var elem in cueLaneDict) {
cnt += elem.Value.playbackLaneList.Count;
}
return cnt;
}
}
}
public class CueLane : ICloneable {
public string name;
public string cuesheet;
public Color cueColor;
public List<PlaybackLane> playbackLaneList;
static private readonly Color baseColor = new Color(0.537f, 0.922f, 0.424f);
private Dictionary<uint, int> playingDict; /* <playback id, lane index>; wont be cloned */
public CueLane(string name, string cuesheet) {
this.name = name;
this.cuesheet = cuesheet;
{/* set distinguishing color */
float h, s, v;
Color.RGBToHSV(baseColor, out h, out s, out v);
System.Random rand = new System.Random();
this.cueColor = Color.HSVToRGB(rand.Next(0, 10000) / 10000f, s, v);
}
playbackLaneList = new List<PlaybackLane>();
playingDict = new Dictionary<uint, int>();
}
public CueLane(CueLane cuelane) {
this.name = cuelane.name;
this.cuesheet = cuelane.cuesheet;
this.cueColor = cuelane.cueColor;
this.playbackLaneList = new List<PlaybackLane>();
this.playbackLaneList.AddRange(cuelane.playbackLaneList.Select(i => i.Clone()));
}
public CueLane Clone() { return new CueLane(this); }
object ICloneable.Clone() { return Clone(); }
public void AddPlayback(Playback playback) {
if (playback.isStart) { /* playback start */
bool added = false;
for (int i = 0; i < playbackLaneList.Count; ++i) {
if (playbackLaneList[i].isPlaying == false) {
if (playbackLaneList[i].AddPlayback(playback)) {
playingDict.Add(playback.id, i);
added = true;
break;
}
}
}
if (added == false) { /* no empty lane */
playbackLaneList.Add(new PlaybackLane());
if (playbackLaneList[playbackLaneList.Count - 1].AddPlayback(playback)) {
playingDict.Add(playback.id, playbackLaneList.Count - 1);
}
}
} else { /* playback end */
int laneId;
if (playingDict.TryGetValue(playback.id, out laneId)) {
playbackLaneList[laneId].AddPlayback(playback);
playingDict.Remove(playback.id);
}
}
}
}
public class PlaybackLane : ICloneable {
public List<Playback> playbackList;
private int balance;
public bool isConsistent {
get {
if (balance > 1 || balance < 0) {
return false;
}
return true;
}
}
public PlaybackLane() {
playbackList = new List<Playback>();
balance = 0;
}
public PlaybackLane(PlaybackLane playbackLane) {
this.playbackList = new List<Playback>(playbackLane.playbackList);
this.balance = playbackLane.balance;
}
public PlaybackLane Clone() { return new PlaybackLane(this); }
object ICloneable.Clone() { return Clone(); }
public bool AddPlayback(Playback playback) {
if (playbackList.Count > 0 &&
(playbackList[playbackList.Count - 1].timestamp > playback.timestamp ||
playbackList[playbackList.Count - 1].isStart == playback.isStart)) {
UnityEngine.Debug.LogError("[CRIWARE] Profiler internal: playback timestamp order error");
return false;
}
playbackList.Add(playback);
balance += playback.isStart ? 1 : -1;
return true;
}
public bool isPlaying {
get {
return playbackList[playbackList.Count - 1].isStart;
}
}
}
public struct Playback {
public uint id;
public ulong timestamp;
public bool isStart;
public string cuename;
public string cuesheet;
public Playback(uint id, ulong timestamp, bool isStart, string cuename, string cuesheet) {
this.id = id;
this.timestamp = timestamp;
this.isStart = isStart;
this.cuename = cuename;
this.cuesheet = cuesheet;
}
}
#endregion
}
} //namespace CriWare
#endif