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

Commit 352b85a

Browse files
author
Jamie Brynes
authored
Add list support to Worker Inspector (#1396)
1 parent 8adf7d4 commit 352b85a

15 files changed

+479
-16
lines changed

workers/unity/Packages/io.improbable.gdk.debug/.codegen/Source/Generators/DebugExtensions/FieldTypeHandler.cs

+53-3
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,18 @@ public string ToFieldDeclaration(UnityFieldDetails fieldDetails)
4343
var element = optionFieldType.IsNullable ? "NullableVisualElement" : "OptionVisualElement";
4444

4545
return $"private readonly {element}<{innerUiType}, {optionFieldType.ContainedType.FqnType}> {fieldDetails.CamelCaseName}Field;";
46+
case ListFieldType listFieldType:
47+
var innerListType = GetUiFieldType(listFieldType.ContainedType);
48+
49+
if (innerListType == "")
50+
{
51+
// TODO: Eliminate this case by supporting 'Entity'.
52+
return "";
53+
}
54+
55+
return $"private readonly PaginatedListView<{innerListType}, {listFieldType.ContainedType.FqnType}> {fieldDetails.CamelCaseName}Field;";
4656
default:
47-
// TODO: Lists and maps.
57+
// TODO: Maps.
4858
return "";
4959
}
5060
}
@@ -105,8 +115,40 @@ public IEnumerable<string> ToFieldInitialisation(UnityFieldDetails fieldDetails,
105115
$"{fieldDetails.CamelCaseName}Field = new {element}<{innerUiType}, {optionFieldType.ContainedType.FqnType}>(\"{Formatting.SnakeCaseToHumanReadable(fieldDetails.Name)}\", {fieldDetails.CamelCaseName}InnerField, (element, data) => {{ {ContainedTypeToUiFieldUpdate(optionFieldType.ContainedType, "element", "data")} }});";
106116
yield return $"{parentContainer}.Add({fieldDetails.CamelCaseName}Field);";
107117
break;
118+
case ListFieldType listFieldType:
119+
var innerListType = GetUiFieldType(listFieldType.ContainedType);
120+
121+
if (innerListType == "")
122+
{
123+
// TODO: Eliminate this case by supporting 'Entity'.
124+
yield break;
125+
}
126+
127+
yield return
128+
$"{fieldDetails.CamelCaseName}Field = new PaginatedListView<{innerListType}, {listFieldType.ContainedType.FqnType}>(\"{Formatting.SnakeCaseToHumanReadable(fieldDetails.Name)}\", () => {{ var inner = new {innerListType}(\"\");";
129+
130+
// These lines are part of the func to create an inner list item.
131+
if (listFieldType.ContainedType.Category != ValueType.Type)
132+
{
133+
yield return "inner.SetEnabled(false);";
134+
}
135+
136+
if (listFieldType.ContainedType.Category == ValueType.Enum)
137+
{
138+
yield return $"inner.Init(default({listFieldType.ContainedType.FqnType}));";
139+
}
140+
141+
yield return "return inner; }, (index, data, element) => {";
142+
143+
// These lines are part of the binding.
144+
var labelBinding = listFieldType.ContainedType.Category == ValueType.Type ? "Label" : "label";
145+
yield return $"element.{labelBinding} = $\"Item {{index + 1}}\";";
146+
yield return ContainedTypeToUiFieldUpdate(listFieldType.ContainedType, "element", "data");
147+
yield return "});";
148+
yield return $"{parentContainer}.Add({fieldDetails.CamelCaseName}Field);";
149+
break;
108150
default:
109-
// TODO: Lists and maps.
151+
// TODO: Maps.
110152
yield break;
111153
}
112154
}
@@ -125,9 +167,17 @@ public string ToUiFieldUpdate(UnityFieldDetails fieldDetails, string fieldParent
125167
return "";
126168
}
127169

170+
return $"{uiElementName}.Update({fieldParent}.{fieldDetails.PascalCaseName});";
171+
case ListFieldType listFieldType:
172+
if (GetUiFieldType(listFieldType.ContainedType) == "")
173+
{
174+
// TODO: Eliminate this case by supporting 'Entity'.
175+
return "";
176+
}
177+
128178
return $"{uiElementName}.Update({fieldParent}.{fieldDetails.PascalCaseName});";
129179
default:
130-
// TODO: Lists and maps.
180+
// TODO: Maps.
131181
return "";
132182
}
133183
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
using System.Runtime.CompilerServices;
2+
3+
[assembly: InternalsVisibleTo("Improbable.Gdk.Debug.WorkerInspector.Codegen.EditmodeTests")]

workers/unity/Packages/io.improbable.gdk.debug/WorkerInspector/Codegen/AssemblyInfo.cs.meta

+3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using UnityEditor;
4+
using UnityEngine;
5+
using UnityEngine.UIElements;
6+
7+
namespace Improbable.Gdk.Debug.WorkerInspector.Codegen
8+
{
9+
public class PaginatedListView<TElement, TData> : VisualElement
10+
where TElement : VisualElement
11+
{
12+
private const string UxmlPath =
13+
"Packages/io.improbable.gdk.debug/WorkerInspector/Templates/PaginatedListView.uxml";
14+
15+
private List<TData> data;
16+
17+
private readonly Action<int, TData, TElement> bindElement;
18+
private readonly VisualElement container;
19+
private readonly ElementPool<TElement> elementPool;
20+
private readonly int elementsPerPage;
21+
22+
private readonly VisualElement controlsContainer;
23+
private readonly Button forwardButton;
24+
private readonly Button backButton;
25+
private readonly Label pageCounter;
26+
27+
private int currentPage = 0;
28+
private int numPages = 0;
29+
30+
public PaginatedListView(string label, Func<TElement> makeElement, Action<int, TData, TElement> bindElement, int elementsPerPage = 5)
31+
{
32+
this.bindElement = bindElement;
33+
this.elementsPerPage = elementsPerPage;
34+
35+
var template = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(UxmlPath);
36+
template.CloneTree(this);
37+
38+
this.Q<Label>(name: "list-name").text = label;
39+
container = this.Q<VisualElement>(className: "user-defined-type-container-data");
40+
41+
controlsContainer = this.Q<VisualElement>(className: "paginated-list-controls");
42+
pageCounter = this.Q<Label>(name: "page-counter");
43+
44+
backButton = this.Q<Button>(name: "back-button");
45+
backButton.clickable.clicked += () => ChangePageCount(-1);
46+
47+
forwardButton = this.Q<Button>(name: "forward-button");
48+
forwardButton.clickable.clicked += () => ChangePageCount(1);
49+
50+
elementPool = new ElementPool<TElement>(makeElement);
51+
}
52+
53+
public void Update(List<TData> newData)
54+
{
55+
data = newData;
56+
57+
if (data.Count == 0)
58+
{
59+
controlsContainer.AddToClassList("hidden");
60+
}
61+
else
62+
{
63+
controlsContainer.RemoveFromClassList("hidden");
64+
}
65+
66+
CalculatePages();
67+
RefreshView();
68+
}
69+
70+
internal void ChangePageCount(int diff)
71+
{
72+
currentPage += diff;
73+
currentPage = Mathf.Clamp(currentPage, 0, numPages - 1);
74+
CalculatePages();
75+
RefreshView();
76+
}
77+
78+
private void CalculatePages()
79+
{
80+
numPages = Mathf.CeilToInt((float) data.Count / elementsPerPage);
81+
numPages = Mathf.Clamp(numPages, 1, numPages);
82+
currentPage = Mathf.Clamp(currentPage, 0, numPages - 1);
83+
84+
pageCounter.text = $"{currentPage + 1}/{numPages}";
85+
}
86+
87+
private void RefreshView()
88+
{
89+
// Calculate slice of list to be rendered.
90+
var firstIndex = currentPage * elementsPerPage;
91+
var length = Math.Min(elementsPerPage, data.Count - firstIndex);
92+
93+
// If the child count is the same, don't adjust it.
94+
// If the child count is less, add the requisite number.
95+
// If the child count is more, pop elements off the end.
96+
97+
var diff = container.childCount - length;
98+
99+
if (diff > 0)
100+
{
101+
for (var i = 0; i < diff; i++)
102+
{
103+
var element = container.ElementAt(container.childCount - 1);
104+
container.RemoveAt(container.childCount - 1);
105+
elementPool.Return((TElement) element);
106+
}
107+
}
108+
else if (diff < 0)
109+
{
110+
for (var i = diff; i < 0; i++)
111+
{
112+
container.Add(elementPool.GetOrCreate());
113+
}
114+
}
115+
116+
// At this point, container.Children() has the same length as the slice.
117+
var elementIndex = firstIndex;
118+
foreach (var child in container.Children())
119+
{
120+
bindElement(elementIndex, data[elementIndex], (TElement) child);
121+
elementIndex++;
122+
}
123+
124+
backButton.SetEnabled(currentPage != 0);
125+
forwardButton.SetEnabled(currentPage != numPages - 1);
126+
}
127+
}
128+
129+
internal class ElementPool<TElement> where TElement : VisualElement
130+
{
131+
private readonly Stack<TElement> pool = new Stack<TElement>();
132+
private readonly Func<TElement> makeElement;
133+
134+
public ElementPool(Func<TElement> makeElement)
135+
{
136+
this.makeElement = makeElement;
137+
}
138+
139+
public TElement GetOrCreate()
140+
{
141+
return pool.Count == 0 ? makeElement() : pool.Pop();
142+
}
143+
144+
public void Return(TElement element)
145+
{
146+
pool.Push(element);
147+
}
148+
}
149+
}

workers/unity/Packages/io.improbable.gdk.debug/WorkerInspector/Codegen/PaginatedListView.cs.meta

+3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

workers/unity/Packages/io.improbable.gdk.debug/WorkerInspector/Codegen/SchemaTypeVisualElement.cs

+9-1
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,21 @@ namespace Improbable.Gdk.Debug.WorkerInspector.Codegen
44
{
55
public abstract class SchemaTypeVisualElement<T> : VisualElement
66
{
7+
public string Label
8+
{
9+
get => labelElement.text;
10+
set => labelElement.text = value;
11+
}
12+
713
protected readonly VisualElement Container;
14+
private readonly Label labelElement;
815

916
protected SchemaTypeVisualElement(string label)
1017
{
1118
AddToClassList("user-defined-type-container");
1219

13-
Add(new Label(label));
20+
labelElement = new Label(label);
21+
Add(labelElement);
1422

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

workers/unity/Packages/io.improbable.gdk.debug/WorkerInspector/Codegen/Tests.meta

+8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"name": "Improbable.Gdk.Debug.WorkerInspector.Codegen.EditmodeTests",
3+
"references": [
4+
"Improbable.Gdk.Debug.WorkerInspector.Codegen"
5+
],
6+
"includePlatforms": [
7+
"Editor"
8+
],
9+
"excludePlatforms": [],
10+
"allowUnsafeCode": false,
11+
"overrideReferences": true,
12+
"precompiledReferences": [
13+
"nunit.framework.dll"
14+
],
15+
"autoReferenced": false,
16+
"defineConstraints": [],
17+
"versionDefines": [],
18+
"noEngineReferences": false
19+
}

workers/unity/Packages/io.improbable.gdk.debug/WorkerInspector/Codegen/Tests/Improbable.Gdk.Debug.WorkerInspector.Codegen.EditmodeTests.asmdef.meta

+7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)