maint: added livewatch asset

This commit is contained in:
Chris
2025-08-31 18:14:07 -04:00
parent 7f5d95787b
commit ae2371a6fa
385 changed files with 150792 additions and 0 deletions

View File

@@ -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,
}
}
}