Skip to content
This repository has been archived by the owner on Jan 18, 2022. It is now read-only.

Add list support to Worker Inspector #1396

Merged
merged 7 commits into from
Jun 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,18 @@ public string ToFieldDeclaration(UnityFieldDetails fieldDetails)
var element = optionFieldType.IsNullable ? "NullableVisualElement" : "OptionVisualElement";

return $"private readonly {element}<{innerUiType}, {optionFieldType.ContainedType.FqnType}> {fieldDetails.CamelCaseName}Field;";
case ListFieldType listFieldType:
var innerListType = GetUiFieldType(listFieldType.ContainedType);

if (innerListType == "")
{
// TODO: Eliminate this case by supporting 'Entity'.
return "";
}

return $"private readonly PaginatedListView<{innerListType}, {listFieldType.ContainedType.FqnType}> {fieldDetails.CamelCaseName}Field;";
default:
// TODO: Lists and maps.
// TODO: Maps.
return "";
}
}
Expand Down Expand Up @@ -105,8 +115,40 @@ public IEnumerable<string> ToFieldInitialisation(UnityFieldDetails fieldDetails,
$"{fieldDetails.CamelCaseName}Field = new {element}<{innerUiType}, {optionFieldType.ContainedType.FqnType}>(\"{Formatting.SnakeCaseToHumanReadable(fieldDetails.Name)}\", {fieldDetails.CamelCaseName}InnerField, (element, data) => {{ {ContainedTypeToUiFieldUpdate(optionFieldType.ContainedType, "element", "data")} }});";
yield return $"{parentContainer}.Add({fieldDetails.CamelCaseName}Field);";
break;
case ListFieldType listFieldType:
var innerListType = GetUiFieldType(listFieldType.ContainedType);

if (innerListType == "")
{
// TODO: Eliminate this case by supporting 'Entity'.
yield break;
}

yield return
$"{fieldDetails.CamelCaseName}Field = new PaginatedListView<{innerListType}, {listFieldType.ContainedType.FqnType}>(\"{Formatting.SnakeCaseToHumanReadable(fieldDetails.Name)}\", () => {{ var inner = new {innerListType}(\"\");";

// These lines are part of the func to create an inner list item.
if (listFieldType.ContainedType.Category != ValueType.Type)
{
yield return "inner.SetEnabled(false);";
}

if (listFieldType.ContainedType.Category == ValueType.Enum)
{
yield return $"inner.Init(default({listFieldType.ContainedType.FqnType}));";
}

yield return "return inner; }, (index, data, element) => {";

// These lines are part of the binding.
var labelBinding = listFieldType.ContainedType.Category == ValueType.Type ? "Label" : "label";
yield return $"element.{labelBinding} = $\"Item {{index + 1}}\";";
yield return ContainedTypeToUiFieldUpdate(listFieldType.ContainedType, "element", "data");
yield return "});";
yield return $"{parentContainer}.Add({fieldDetails.CamelCaseName}Field);";
break;
default:
// TODO: Lists and maps.
// TODO: Maps.
yield break;
}
}
Expand All @@ -125,9 +167,17 @@ public string ToUiFieldUpdate(UnityFieldDetails fieldDetails, string fieldParent
return "";
}

return $"{uiElementName}.Update({fieldParent}.{fieldDetails.PascalCaseName});";
case ListFieldType listFieldType:
if (GetUiFieldType(listFieldType.ContainedType) == "")
{
// TODO: Eliminate this case by supporting 'Entity'.
return "";
}

return $"{uiElementName}.Update({fieldParent}.{fieldDetails.PascalCaseName});";
default:
// TODO: Lists and maps.
// TODO: Maps.
return "";
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("Improbable.Gdk.Debug.WorkerInspector.Codegen.EditmodeTests")]

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;

namespace Improbable.Gdk.Debug.WorkerInspector.Codegen
{
public class PaginatedListView<TElement, TData> : VisualElement
where TElement : VisualElement
{
private const string UxmlPath =
"Packages/io.improbable.gdk.debug/WorkerInspector/Templates/PaginatedListView.uxml";

private List<TData> data;

private readonly Action<int, TData, TElement> bindElement;
private readonly VisualElement container;
private readonly ElementPool<TElement> elementPool;
private readonly int elementsPerPage;

private readonly VisualElement controlsContainer;
private readonly Button forwardButton;
private readonly Button backButton;
private readonly Label pageCounter;

private int currentPage = 0;
private int numPages = 0;

public PaginatedListView(string label, Func<TElement> makeElement, Action<int, TData, TElement> bindElement, int elementsPerPage = 5)
{
this.bindElement = bindElement;
this.elementsPerPage = elementsPerPage;

var template = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(UxmlPath);
template.CloneTree(this);

this.Q<Label>(name: "list-name").text = label;
container = this.Q<VisualElement>(className: "user-defined-type-container-data");

controlsContainer = this.Q<VisualElement>(className: "paginated-list-controls");
pageCounter = this.Q<Label>(name: "page-counter");

backButton = this.Q<Button>(name: "back-button");
backButton.clickable.clicked += () => ChangePageCount(-1);

forwardButton = this.Q<Button>(name: "forward-button");
forwardButton.clickable.clicked += () => ChangePageCount(1);

elementPool = new ElementPool<TElement>(makeElement);
}

public void Update(List<TData> newData)
{
data = newData;

if (data.Count == 0)
{
controlsContainer.AddToClassList("hidden");
}
else
{
controlsContainer.RemoveFromClassList("hidden");
}

CalculatePages();
RefreshView();
}

internal void ChangePageCount(int diff)
{
currentPage += diff;
currentPage = Mathf.Clamp(currentPage, 0, numPages - 1);
CalculatePages();
RefreshView();
}

private void CalculatePages()
{
numPages = Mathf.CeilToInt((float) data.Count / elementsPerPage);
numPages = Mathf.Clamp(numPages, 1, numPages);
currentPage = Mathf.Clamp(currentPage, 0, numPages - 1);

pageCounter.text = $"{currentPage + 1}/{numPages}";
}

private void RefreshView()
{
// Calculate slice of list to be rendered.
var firstIndex = currentPage * elementsPerPage;
var length = Math.Min(elementsPerPage, data.Count - firstIndex);

// If the child count is the same, don't adjust it.
// If the child count is less, add the requisite number.
// If the child count is more, pop elements off the end.

var diff = container.childCount - length;

if (diff > 0)
{
for (var i = 0; i < diff; i++)
{
var element = container.ElementAt(container.childCount - 1);
container.RemoveAt(container.childCount - 1);
elementPool.Return((TElement) element);
}
}
else if (diff < 0)
{
for (var i = diff; i < 0; i++)
{
container.Add(elementPool.GetOrCreate());
}
}

// At this point, container.Children() has the same length as the slice.
var elementIndex = firstIndex;
foreach (var child in container.Children())
{
bindElement(elementIndex, data[elementIndex], (TElement) child);
elementIndex++;
}

backButton.SetEnabled(currentPage != 0);
forwardButton.SetEnabled(currentPage != numPages - 1);
}
}

internal class ElementPool<TElement> where TElement : VisualElement
{
private readonly Stack<TElement> pool = new Stack<TElement>();
private readonly Func<TElement> makeElement;

public ElementPool(Func<TElement> makeElement)
{
this.makeElement = makeElement;
}

public TElement GetOrCreate()
{
return pool.Count == 0 ? makeElement() : pool.Pop();
}

public void Return(TElement element)
{
pool.Push(element);
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,21 @@ namespace Improbable.Gdk.Debug.WorkerInspector.Codegen
{
public abstract class SchemaTypeVisualElement<T> : VisualElement
{
public string Label
{
get => labelElement.text;
set => labelElement.text = value;
}

protected readonly VisualElement Container;
private readonly Label labelElement;

protected SchemaTypeVisualElement(string label)
{
AddToClassList("user-defined-type-container");

Add(new Label(label));
labelElement = new Label(label);
Add(labelElement);

Container = new VisualElement();
Container.AddToClassList("user-defined-type-container-data");
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "Improbable.Gdk.Debug.WorkerInspector.Codegen.EditmodeTests",
"references": [
"Improbable.Gdk.Debug.WorkerInspector.Codegen"
],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": true,
"precompiledReferences": [
"nunit.framework.dll"
],
"autoReferenced": false,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading