maint: added livewatch asset
This commit is contained in:
@@ -0,0 +1,422 @@
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user