422 lines
16 KiB
C#
422 lines
16 KiB
C#
using System;
|
|
using System.Collections.Concurrent;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using UnityEngine;
|
|
using Random = UnityEngine.Random;
|
|
|
|
namespace Ingvar.LiveWatch.Editor
|
|
{
|
|
public class WatchPreviewDrawer
|
|
{
|
|
private static int PreviousStartIndex;
|
|
private static float StartIndexChangedTime;
|
|
|
|
public bool Search { get; set; }
|
|
|
|
private ConcurrentDictionary<WatchVariable, PreviewInfo> _previewInfos = new();
|
|
|
|
public void DrawPreview(Rect rect, Rect drawRect, WatchVariable variable, List<int> indicesToDisplay, int columnWidth)
|
|
{
|
|
if (Event.current.type != EventType.Repaint)
|
|
return;
|
|
|
|
if (PreviousStartIndex != indicesToDisplay[0])
|
|
{
|
|
PreviousStartIndex = indicesToDisplay[0];
|
|
StartIndexChangedTime = Time.realtimeSinceStartup;
|
|
}
|
|
|
|
if (_previewInfos.TryGetValue(variable, out var previewInfo)
|
|
&& IsValidPreview(previewInfo, rect, indicesToDisplay)
|
|
&& (previewInfo.Texture != null || previewInfo.State is not PreviewInfo.StateType.ReadyToUse))
|
|
{
|
|
if (previewInfo.State is PreviewInfo.StateType.Computing)
|
|
return;
|
|
|
|
if (previewInfo.State is PreviewInfo.StateType.ReadyToSetPixels)
|
|
{
|
|
if (previewInfo.MinReadyTime > Time.realtimeSinceStartup)
|
|
return;
|
|
|
|
PrepareTexture(previewInfo.Size, ref previewInfo.Texture);
|
|
previewInfo.Texture.SetPixels32(0, 0, previewInfo.Size.x, previewInfo.Size.y, previewInfo.Colors);
|
|
previewInfo.Texture.Apply(false);
|
|
previewInfo.State = PreviewInfo.StateType.ReadyToUse;
|
|
}
|
|
|
|
GUI.DrawTexture(drawRect, previewInfo.Texture);
|
|
}
|
|
|
|
if (previewInfo == null)
|
|
{
|
|
previewInfo = new PreviewInfo();
|
|
_previewInfos.TryAdd(variable, previewInfo);
|
|
}
|
|
|
|
previewInfo.Token?.Cancel();
|
|
previewInfo.Token?.Dispose();
|
|
previewInfo.Variable = variable;
|
|
previewInfo.Rect = rect;
|
|
previewInfo.StartIndex = indicesToDisplay[0];
|
|
previewInfo.EndIndex = indicesToDisplay[^1];
|
|
previewInfo.Token = new CancellationTokenSource();
|
|
previewInfo.MinReadyTime = StartIndexChangedTime + 0.2f;
|
|
|
|
if (IsValidPreview(previewInfo, rect, indicesToDisplay) )
|
|
previewInfo.State = PreviewInfo.StateType.ComputingSilently;
|
|
else
|
|
previewInfo.State = PreviewInfo.StateType.Computing;
|
|
|
|
Task.Factory.StartNew(() => BuildPreviewTexture(previewInfo, indicesToDisplay, columnWidth));
|
|
}
|
|
|
|
private void BuildPreviewTexture(PreviewInfo preview, List<int> keysToDisplay, int columnWidth)
|
|
{
|
|
preview.CalcMeta.KeysToDisplay = keysToDisplay.ToList();
|
|
keysToDisplay = preview.CalcMeta.KeysToDisplay;
|
|
|
|
if (preview.Token.IsCancellationRequested)
|
|
return;
|
|
|
|
PrepareColors(preview.Size, ref preview.Colors);
|
|
RefreshPixelInfos(preview);
|
|
|
|
if (preview.Token.IsCancellationRequested)
|
|
return;
|
|
|
|
var blocksCount = preview.CalcMeta.PixelBlockCount;
|
|
var pixelBlocks = preview.CalcMeta.PixelBlocks;
|
|
|
|
for (var k = 0; k < keysToDisplay.Count; k++)
|
|
{
|
|
if (preview.Token.IsCancellationRequested)
|
|
return;
|
|
|
|
var startX = k * columnWidth;
|
|
var endX = startX + columnWidth - 1;
|
|
|
|
var currentBlockTopY = preview.Size.y - 1;
|
|
for (var b = 0; b < blocksCount; b++)
|
|
{
|
|
if (preview.Token.IsCancellationRequested)
|
|
return;
|
|
|
|
var endY = currentBlockTopY;
|
|
var startY = endY - pixelBlocks[b].HeightPixels + 1;
|
|
|
|
var mode = ValueModeType.None;
|
|
GetValueModForChilds(preview.Variable, keysToDisplay[k], pixelBlocks[b].ChildNames, ref mode);
|
|
|
|
if (!preview.HasSearchResults)
|
|
mode &= ~ValueModeType.Searched;
|
|
|
|
var blockColor = GetColorFromMode(mode, b % 2 == 0);
|
|
|
|
for (var x = startX; x <= endX; x++)
|
|
{
|
|
if (preview.Token.IsCancellationRequested)
|
|
return;
|
|
|
|
for (var y = startY; y <= endY; y++)
|
|
{
|
|
if (preview.Token.IsCancellationRequested)
|
|
return;
|
|
|
|
var pixelIndex = GetPixelIndex(x, y, preview.Size);
|
|
|
|
if ((mode & ValueModeType.HasValue) == ValueModeType.HasValue && x == startX && columnWidth > 1)
|
|
{
|
|
preview.Colors[pixelIndex] = Colors.PreviewOriginalEdge;
|
|
}
|
|
else
|
|
{
|
|
preview.Colors[pixelIndex] = blockColor;
|
|
}
|
|
}
|
|
}
|
|
|
|
currentBlockTopY = startY - 1;
|
|
}
|
|
}
|
|
|
|
if (preview.Token.IsCancellationRequested)
|
|
return;
|
|
|
|
preview.State = PreviewInfo.StateType.ReadyToSetPixels;
|
|
LiveWatchWindow.IsRepaintRequested = true;
|
|
}
|
|
|
|
private int GetPixelIndex(int x, int y, Vector2Int textureSize)
|
|
{
|
|
return y * textureSize.x + x;
|
|
}
|
|
|
|
private Color32 SumColors(Color32 left, Color32 right)
|
|
{
|
|
return new Color32(
|
|
(byte)Mathf.Clamp(left.r + right.r, 0, 255),
|
|
(byte)Mathf.Clamp(left.g + right.g, 0, 255),
|
|
(byte)Mathf.Clamp(left.b + right.b, 0, 255),
|
|
(byte)Mathf.Clamp(left.a + right.a, 0, 255));
|
|
}
|
|
|
|
private void RefreshPixelInfos(PreviewInfo preview)
|
|
{
|
|
var cm = preview.CalcMeta;
|
|
|
|
var pixelsPerChild = (float)preview.Size.y / preview.Variable.Childs.Count;
|
|
|
|
if (pixelsPerChild > 1f)
|
|
{
|
|
cm.PixelBlockCount = preview.Variable.Childs.Count;
|
|
PreparePixelBlocks(cm.PixelBlockCount);
|
|
|
|
var roundedPixelsPerChild = Mathf.FloorToInt(pixelsPerChild);
|
|
|
|
for (var i = 0; i < cm.PixelBlockCount; i++)
|
|
{
|
|
if (preview.Token.IsCancellationRequested)
|
|
return;
|
|
|
|
cm.PixelBlocks[i] ??= new PixelBlockInfo();
|
|
cm.PixelBlocks[i].Clear();
|
|
cm.PixelBlocks[i].HeightPixels = roundedPixelsPerChild;
|
|
cm.PixelBlocks[i].ChildNames.Add(preview.Variable.Childs.SortedNames[i]);
|
|
}
|
|
|
|
var pixelsLeft = preview.Size.y - roundedPixelsPerChild * preview.Variable.Childs.Count;
|
|
|
|
var currentBlockIndex = 0;
|
|
while (pixelsLeft > 0)
|
|
{
|
|
if (preview.Token.IsCancellationRequested)
|
|
return;
|
|
|
|
pixelsLeft--;
|
|
cm.PixelBlocks[currentBlockIndex].HeightPixels++;
|
|
|
|
currentBlockIndex++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
cm.PixelBlockCount = preview.Size.y;
|
|
PreparePixelBlocks(cm.PixelBlockCount);
|
|
|
|
cm.ChildCountPerBlock ??= new List<int>();
|
|
cm.ChildCountPerBlock.Clear();
|
|
|
|
var childPerPixel = 1f / pixelsPerChild;
|
|
var roundedChildPerBlock = Mathf.FloorToInt(childPerPixel);
|
|
|
|
for (var i = 0; i < cm.PixelBlockCount; i++)
|
|
{
|
|
if (preview.Token.IsCancellationRequested)
|
|
return;
|
|
|
|
cm.PixelBlocks[i] ??= new PixelBlockInfo();
|
|
cm.PixelBlocks[i].HeightPixels = 1;
|
|
cm.ChildCountPerBlock.Add(roundedChildPerBlock);
|
|
}
|
|
|
|
var childLeft = preview.Variable.Childs.Count - cm.PixelBlockCount * roundedChildPerBlock;
|
|
var currentBlockIndex = cm.PixelBlockCount - 1;
|
|
|
|
while (childLeft > 0)
|
|
{
|
|
if (preview.Token.IsCancellationRequested)
|
|
return;
|
|
|
|
childLeft--;
|
|
cm.ChildCountPerBlock[currentBlockIndex]++;
|
|
currentBlockIndex--;
|
|
}
|
|
|
|
var currentChildIndex = 0;
|
|
|
|
for (var i = 0; i < cm.PixelBlockCount; i++)
|
|
{
|
|
if (preview.Token.IsCancellationRequested)
|
|
return;
|
|
|
|
cm.PixelBlocks[i].Clear();
|
|
|
|
for (var c = 0; c < cm.ChildCountPerBlock[i]; c++)
|
|
{
|
|
if (preview.Token.IsCancellationRequested)
|
|
return;
|
|
|
|
cm.PixelBlocks[i].ChildNames.Add(preview.Variable.Childs.SortedNames[currentChildIndex++]);
|
|
}
|
|
}
|
|
}
|
|
|
|
void PreparePixelBlocks(int count)
|
|
{
|
|
if (cm.PixelBlocks == null)
|
|
cm.PixelBlocks = new PixelBlockInfo[count];
|
|
else if (cm.PixelBlocks.Length < count)
|
|
Array.Resize(ref cm.PixelBlocks, count);
|
|
}
|
|
}
|
|
|
|
private void PrepareColors(Vector2Int size, ref Color32[] colors)
|
|
{
|
|
var targetCount = Mathf.CeilToInt(size.x) * Mathf.CeilToInt(size.y) * 2;
|
|
|
|
if (colors == null)
|
|
colors = new Color32[targetCount];
|
|
else if (colors.Length < targetCount)
|
|
Array.Resize(ref colors, targetCount);
|
|
|
|
for (var i = 0; i < colors.Length; i++)
|
|
colors[i] = new Color32(0, 0, 0, 0);
|
|
}
|
|
|
|
private void PrepareTexture(Vector2Int size, ref Texture2D texture)
|
|
{
|
|
if (texture == null)
|
|
{
|
|
texture = new Texture2D(size.x, size.y);
|
|
}
|
|
else if (texture.width != size.x || texture.height != size.y)
|
|
{
|
|
texture.Reinitialize(size.x, size.y);
|
|
texture.Apply(false);
|
|
}
|
|
}
|
|
|
|
private bool IsValidPreview(PreviewInfo preview, Rect rect, List<int> indicesToDisplay)
|
|
{
|
|
return preview.Size.x == Mathf.CeilToInt(rect.width)
|
|
&& preview.Size.y == Mathf.CeilToInt(rect.height)
|
|
&& preview.StartIndex == indicesToDisplay[0]
|
|
&& preview.EndIndex == indicesToDisplay[^1];
|
|
}
|
|
|
|
|
|
private Color32 GetColorFromMode(ValueModeType mode, bool isOdd)
|
|
{
|
|
var resultColor = Colors.PreviewCellEmpty;
|
|
|
|
if ((mode & ValueModeType.EmptyValue) == ValueModeType.EmptyValue)
|
|
resultColor = Colors.PreviewCellEmpty;
|
|
|
|
if ((mode & ValueModeType.HasValue) != ValueModeType.HasValue)
|
|
return resultColor;
|
|
|
|
resultColor = isOdd ? Colors.PreviewCellHasValueOdd : Colors.PreviewCellHasValue;
|
|
|
|
if ((mode & ValueModeType.Original) == ValueModeType.Original)
|
|
resultColor = isOdd ? Colors.PreviewCellOriginalOdd : Colors.PreviewCellOriginal;
|
|
|
|
if ((mode & ValueModeType.Searched) == ValueModeType.Searched)
|
|
{
|
|
resultColor = isOdd
|
|
? ((mode & ValueModeType.Original) == ValueModeType.Original) ? Colors.PreviewCellSearchedOriginalOdd : Colors.PreviewCellSearchedOdd
|
|
: ((mode & ValueModeType.Original) == ValueModeType.Original) ? Colors.PreviewCellSearchedOriginal : Colors.PreviewCellSearched;
|
|
}
|
|
|
|
return resultColor;
|
|
}
|
|
|
|
private void GetValueModForChilds(WatchVariable variable, int key, List<string> childNames, ref ValueModeType mode)
|
|
{
|
|
foreach (var childName in childNames)
|
|
{
|
|
var child = variable.Childs.Get(childName);
|
|
GetValueModeRecursive(child, key, ref mode);
|
|
}
|
|
}
|
|
|
|
private void GetValueModeRecursive(WatchVariable variable, int key, ref ValueModeType mode)
|
|
{
|
|
if (variable.HasChilds)
|
|
{
|
|
foreach (var childName in variable.Childs.SortedNames)
|
|
{
|
|
var child = variable.Childs.Get(childName);
|
|
GetValueModeRecursive(child, key, ref mode);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!variable.Values.AnyAt(key))
|
|
return;
|
|
|
|
if (variable.Values.IsEmptyAt(key))
|
|
mode |= ValueModeType.EmptyValue;
|
|
else
|
|
mode |= ValueModeType.HasValue;
|
|
|
|
if (variable.Values.IsOriginalAt(key) && key > 0 && !variable.Values.IsEmptyAt(key - 1))
|
|
mode |= ValueModeType.Original;
|
|
|
|
if (variable.EditorMeta.SearchResult.ValueResults != null
|
|
&& variable.EditorMeta.SearchResult.ValueResults.ContainsKey(variable.Values.GetOriginalKey(key)))
|
|
mode |= ValueModeType.Searched;
|
|
}
|
|
}
|
|
|
|
private class PreviewInfo
|
|
{
|
|
public StateType State;
|
|
public float MinReadyTime;
|
|
public WatchVariable Variable;
|
|
public Rect Rect;
|
|
public int StartIndex;
|
|
public int EndIndex;
|
|
public bool HasSearchResults;
|
|
public int SearchId;
|
|
public Texture2D Texture;
|
|
public Color32[] Colors;
|
|
public CancellationTokenSource Token;
|
|
public PreviewCalcMeta CalcMeta = new();
|
|
public Vector2Int Size => new (Mathf.CeilToInt(Rect.width), Mathf.CeilToInt(Rect.height));
|
|
|
|
public enum StateType
|
|
{
|
|
Computing,
|
|
ComputingSilently,
|
|
ReadyToSetPixels,
|
|
ReadyToUse
|
|
}
|
|
}
|
|
|
|
private class PreviewCalcMeta
|
|
{
|
|
public List<int> KeysToDisplay;
|
|
public List<int> ChildCountPerBlock;
|
|
public int PixelBlockCount;
|
|
public PixelBlockInfo[] PixelBlocks;
|
|
}
|
|
|
|
private class PixelBlockInfo
|
|
{
|
|
public int HeightPixels;
|
|
public List<string> ChildNames = new();
|
|
public Dictionary<int, ValueModeType> ModePerKeys = new();
|
|
|
|
public void Clear()
|
|
{
|
|
ChildNames.Clear();
|
|
ModePerKeys.Clear();
|
|
}
|
|
}
|
|
|
|
[Flags]
|
|
private enum ValueModeType
|
|
{
|
|
None = 0,
|
|
HasValue = 1 << 0,
|
|
EmptyValue = 1 << 1,
|
|
Original = 1 << 2,
|
|
Searched = 1 << 3,
|
|
|
|
OriginalWithValue = HasValue | Original,
|
|
}
|
|
}
|
|
} |