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 6 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,151 @@
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 Pool 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.text = "<";
jamiebrynes7 marked this conversation as resolved.
Show resolved Hide resolved
backButton.clickable.clicked += () => ChangePageCount(-1);

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

elementPool = new Pool(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 = (int) Math.Ceiling((double) data.Count / elementsPerPage);
jamiebrynes7 marked this conversation as resolved.
Show resolved Hide resolved
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 j = 0;
jamiebrynes7 marked this conversation as resolved.
Show resolved Hide resolved
foreach (var child in container.Children())
{
bindElement(firstIndex + j, data[firstIndex + j], (TElement) child);
j++;
}

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

private class Pool
jamiebrynes7 marked this conversation as resolved.
Show resolved Hide resolved
{
private readonly Stack<TElement> pool = new Stack<TElement>();
private readonly Func<TElement> makeElement;

public Pool(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