Skip to content

Commit 172b99b

Browse files
authored
Refactor ShapeResult for less allocations. (OrchardCMS#18867)
1 parent f54231c commit 172b99b

File tree

1 file changed

+199
-126
lines changed

1 file changed

+199
-126
lines changed

src/OrchardCore/OrchardCore.DisplayManagement/Views/ShapeResult.cs

Lines changed: 199 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,12 @@ public class ShapeResult : IDisplayResult
1919
private string _differentiator;
2020
private string _prefix;
2121
private string _cacheId;
22-
private Dictionary<string, string> _otherLocations;
2322
private StringValues _groupIds;
23+
private Dictionary<string, string> _otherLocations;
24+
private string _firstDisplayType;
25+
private string _firstLocation;
26+
private string _secondDisplayType;
27+
private string _secondLocation;
2428

2529
private Action<CacheContext> _cache;
2630
private Action<ShapeDisplayContext> _displaying;
@@ -63,7 +67,159 @@ public Task ApplyAsync(BuildEditorContext context)
6367
return ApplyImplementationAsync(context, "Edit");
6468
}
6569

66-
private async Task ApplyImplementationAsync(BuildShapeContext context, string displayType)
70+
/// <summary>
71+
/// Sets the prefix of the form elements rendered in the shape.
72+
/// </summary>
73+
/// <remarks>
74+
/// The goal is to isolate each shape when edited together.
75+
/// </remarks>
76+
public ShapeResult Prefix(string prefix)
77+
{
78+
_prefix = prefix;
79+
return this;
80+
}
81+
82+
/// <summary>
83+
/// Sets the default location of the shape when no specific placement applies.
84+
/// </summary>
85+
public ShapeResult Location(string location)
86+
{
87+
_defaultLocation = location;
88+
return this;
89+
}
90+
91+
/// <summary>
92+
/// Sets the location to use for a matching display type.
93+
/// </summary>
94+
public ShapeResult Location(string displayType, string location)
95+
{
96+
if (_otherLocations != null)
97+
{
98+
_otherLocations[displayType] = location;
99+
}
100+
else if (_firstDisplayType == null)
101+
{
102+
_firstDisplayType = displayType;
103+
_firstLocation = location;
104+
}
105+
else if (_secondDisplayType == null)
106+
{
107+
_secondDisplayType = displayType;
108+
_secondLocation = location;
109+
}
110+
else
111+
{
112+
_otherLocations = new Dictionary<string, string>(4)
113+
{
114+
[_firstDisplayType] = _firstLocation,
115+
[_secondDisplayType] = _secondLocation,
116+
[displayType] = location,
117+
};
118+
_firstDisplayType = null;
119+
_firstLocation = null;
120+
_secondDisplayType = null;
121+
_secondLocation = null;
122+
}
123+
124+
return this;
125+
}
126+
127+
/// <summary>
128+
/// Sets the delegate to be executed when the shape is being displayed.
129+
/// </summary>
130+
public ShapeResult Displaying(Action<ShapeDisplayContext> displaying)
131+
{
132+
_displaying = displaying;
133+
return this;
134+
}
135+
136+
/// <summary>
137+
/// Sets the delegate to be executed when the shape is rendered (not cached).
138+
/// </summary>
139+
public ShapeResult Processing(Func<IShape, Task> processing)
140+
{
141+
_processingAsync = processing;
142+
return this;
143+
}
144+
145+
/// <summary>
146+
/// Sets the delegate to be executed when the shape is rendered (not cached).
147+
/// </summary>
148+
public ShapeResult Processing<T>(Func<T, Task> processing)
149+
{
150+
_processingAsync = shape => processing?.Invoke((T)shape);
151+
return this;
152+
}
153+
154+
/// <summary>
155+
/// Sets the shape name regardless its 'Differentiator'.
156+
/// </summary>
157+
public ShapeResult Name(string name)
158+
{
159+
_name = name;
160+
return this;
161+
}
162+
163+
/// <summary>
164+
/// Sets a discriminator that is used to find the location of the shape when two shapes of the same type are displayed.
165+
/// </summary>
166+
public ShapeResult Differentiator(string differentiator)
167+
{
168+
_differentiator = differentiator;
169+
return this;
170+
}
171+
172+
/// <summary>
173+
/// Adds the specified group identifier to the current shape result and returns the updated result.
174+
/// </summary>
175+
/// <param name="groupId">The group identifier to add.</param>
176+
/// <returns>
177+
/// The current <see cref="ShapeResult"/> instance with the specified group identifier added to its list of group identifiers.
178+
/// </returns>
179+
public ShapeResult OnGroup(string groupId)
180+
{
181+
_groupIds = StringValues.Concat(_groupIds, groupId);
182+
183+
return this;
184+
}
185+
186+
/// <summary>
187+
/// Sets the group identifiers the shape will be rendered in.
188+
/// </summary>
189+
/// <param name="groupIds"></param>
190+
/// <returns></returns>
191+
public ShapeResult OnGroup(params string[] groupIds)
192+
{
193+
ArgumentNullException.ThrowIfNull(groupIds);
194+
195+
_groupIds = StringValues.Concat(_groupIds, groupIds);
196+
197+
return this;
198+
}
199+
200+
/// <summary>
201+
/// Sets the caching properties of the shape to render.
202+
/// </summary>
203+
public ShapeResult Cache(string cacheId, Action<CacheContext> cache = null)
204+
{
205+
_cacheId = cacheId;
206+
_cache = cache;
207+
return this;
208+
}
209+
210+
/// <summary>
211+
/// Sets a condition that must return true for the shape to render.
212+
/// The condition is only evaluated if the shape has been placed.
213+
/// </summary>
214+
public ShapeResult RenderWhen(Func<Task<bool>> renderPredicateAsync)
215+
{
216+
_renderPredicateAsync = renderPredicateAsync;
217+
return this;
218+
}
219+
220+
public IShape Shape { get; private set; }
221+
222+
private Task ApplyImplementationAsync(BuildShapeContext context, string displayType)
67223
{
68224
// If no location is set from the driver, use the one from the context.
69225
if (string.IsNullOrEmpty(_defaultLocation))
@@ -77,12 +233,22 @@ private async Task ApplyImplementationAsync(BuildShapeContext context, string di
77233
// Look for mapped display type locations.
78234
if (_otherLocations != null)
79235
{
80-
string displayTypePlacement;
81-
if (_otherLocations.TryGetValue(displayType, out displayTypePlacement))
236+
if (_otherLocations.TryGetValue(displayType, out var displayTypePlacement))
82237
{
83238
_defaultLocation = displayTypePlacement;
84239
}
85240
}
241+
else if (_firstDisplayType != null)
242+
{
243+
if (string.Equals(_firstDisplayType, displayType, StringComparison.Ordinal))
244+
{
245+
_defaultLocation = _firstLocation;
246+
}
247+
else if (_secondDisplayType != null && string.Equals(_secondDisplayType, displayType, StringComparison.Ordinal))
248+
{
249+
_defaultLocation = _secondLocation;
250+
}
251+
}
86252

87253
// If no placement is found, use the default location.
88254
placement ??= new PlacementInfo
@@ -99,7 +265,7 @@ private async Task ApplyImplementationAsync(BuildShapeContext context, string di
99265
// If the placement should be hidden, then stop rendering execution.
100266
if (placement.IsHidden())
101267
{
102-
return;
268+
return Task.CompletedTask;
103269
}
104270

105271
// Parse group placement.
@@ -111,7 +277,7 @@ private async Task ApplyImplementationAsync(BuildShapeContext context, string di
111277
OnGroup(groupId);
112278
}
113279

114-
bool hasGroupConstraints = !StringValues.IsNullOrEmpty(_groupIds);
280+
var hasGroupConstraints = !StringValues.IsNullOrEmpty(_groupIds);
115281

116282
// If no specific group is requested, use "" as it represents "any group" when applied on a shape.
117283
// This allows to render shapes when no shape constraints are set and also on specific groups.
@@ -120,17 +286,41 @@ private async Task ApplyImplementationAsync(BuildShapeContext context, string di
120286
// If the shape's group doesn't match the currently rendered one, return.
121287
if (hasGroupConstraints && !_groupIds.Contains(requestedGroup, StringComparer.OrdinalIgnoreCase))
122288
{
123-
return;
289+
return Task.CompletedTask;
124290
}
125291

126292
// If we try to render the shape without a group, but we require one, don't render it
127293
if (!hasGroupConstraints && !string.IsNullOrEmpty(context.GroupId))
128294
{
129-
return;
295+
return Task.CompletedTask;
130296
}
131297

132298
// If a condition has been applied to this result evaluate it only if the shape has been placed.
133-
if (_renderPredicateAsync != null && !await _renderPredicateAsync())
299+
if (_renderPredicateAsync != null)
300+
{
301+
var renderPredicateTask = _renderPredicateAsync();
302+
303+
if (renderPredicateTask.IsCompletedSuccessfully)
304+
{
305+
if (!renderPredicateTask.Result)
306+
{
307+
return Task.CompletedTask;
308+
}
309+
310+
// Predicate evaluated synchronously and returned true, continue without task.
311+
return BuildAndAddShapeAsync(context, displayType, placement, renderPredicateTask: null);
312+
}
313+
314+
// Predicate needs async evaluation, pass task to be awaited later.
315+
return BuildAndAddShapeAsync(context, displayType, placement, renderPredicateTask);
316+
}
317+
318+
return BuildAndAddShapeAsync(context, displayType, placement, renderPredicateTask: null);
319+
}
320+
321+
private async Task BuildAndAddShapeAsync(BuildShapeContext context, string displayType, PlacementInfo placement, Task<bool> renderPredicateTask)
322+
{
323+
if (renderPredicateTask != null && !await renderPredicateTask)
134324
{
135325
return;
136326
}
@@ -226,126 +416,9 @@ private async Task ApplyImplementationAsync(BuildShapeContext context, string di
226416
}
227417
}
228418

229-
position = !string.IsNullOrEmpty(position) ? position : null;
230-
231419
if (parentShape is Shape shape)
232420
{
233421
await shape.AddAsync(newShape, position);
234422
}
235423
}
236-
237-
/// <summary>
238-
/// Sets the prefix of the form elements rendered in the shape.
239-
/// </summary>
240-
/// <remarks>
241-
/// The goal is to isolate each shape when edited together.
242-
/// </remarks>
243-
public ShapeResult Prefix(string prefix)
244-
{
245-
_prefix = prefix;
246-
return this;
247-
}
248-
249-
/// <summary>
250-
/// Sets the default location of the shape when no specific placement applies.
251-
/// </summary>
252-
public ShapeResult Location(string location)
253-
{
254-
_defaultLocation = location;
255-
return this;
256-
}
257-
258-
/// <summary>
259-
/// Sets the location to use for a matching display type.
260-
/// </summary>
261-
public ShapeResult Location(string displayType, string location)
262-
{
263-
_otherLocations ??= new Dictionary<string, string>(2);
264-
_otherLocations[displayType] = location;
265-
return this;
266-
}
267-
268-
/// <summary>
269-
/// Sets the delegate to be executed when the shape is being displayed.
270-
/// </summary>
271-
public ShapeResult Displaying(Action<ShapeDisplayContext> displaying)
272-
{
273-
_displaying = displaying;
274-
275-
return this;
276-
}
277-
278-
/// <summary>
279-
/// Sets the delegate to be executed when the shape is rendered (not cached).
280-
/// </summary>
281-
public ShapeResult Processing(Func<IShape, Task> processing)
282-
{
283-
_processingAsync = processing;
284-
285-
return this;
286-
}
287-
288-
/// <summary>
289-
/// Sets the delegate to be executed when the shape is rendered (not cached).
290-
/// </summary>
291-
public ShapeResult Processing<T>(Func<T, Task> processing)
292-
{
293-
_processingAsync = shape => processing?.Invoke((T)shape);
294-
295-
return this;
296-
}
297-
298-
/// <summary>
299-
/// Sets the shape name regardless its 'Differentiator'.
300-
/// </summary>
301-
public ShapeResult Name(string name)
302-
{
303-
_name = name;
304-
return this;
305-
}
306-
307-
/// <summary>
308-
/// Sets a discriminator that is used to find the location of the shape when two shapes of the same type are displayed.
309-
/// </summary>
310-
public ShapeResult Differentiator(string differentiator)
311-
{
312-
_differentiator = differentiator;
313-
return this;
314-
}
315-
316-
/// <summary>
317-
/// Sets the group identifiers the shape will be rendered in.
318-
/// </summary>
319-
/// <param name="groupIds"></param>
320-
/// <returns></returns>
321-
public ShapeResult OnGroup(params string[] groupIds)
322-
{
323-
ArgumentNullException.ThrowIfNull(groupIds);
324-
325-
_groupIds = StringValues.Concat(_groupIds, groupIds);
326-
327-
return this;
328-
}
329-
330-
/// <summary>
331-
/// Sets the caching properties of the shape to render.
332-
/// </summary>
333-
public ShapeResult Cache(string cacheId, Action<CacheContext> cache = null)
334-
{
335-
_cacheId = cacheId;
336-
_cache = cache;
337-
return this;
338-
}
339-
340-
/// <summary>
341-
/// Sets a condition that must return true for the shape to render.
342-
/// The condition is only evaluated if the shape has been placed.
343-
/// </summary>
344-
public ShapeResult RenderWhen(Func<Task<bool>> renderPredicateAsync)
345-
{
346-
_renderPredicateAsync = renderPredicateAsync;
347-
return this;
348-
}
349-
350-
public IShape Shape { get; private set; }
351424
}

0 commit comments

Comments
 (0)