Skip to content

Commit a11dab7

Browse files
committed
refactor(render): Refactor resource loading and animation metadata processing logic
- Cache loaded image resources in AssetProvider to avoid duplicate loading - Remove `using` statements in MeasureImageNode and RenderImageNode, and rely on caching instead - Inherit frame data and metadata (GIF/PNG/WebP) from the source image when rendering animated images - Simplify parameter passing in the Drawer class, remove redundant calculations, and use the IList interface - Remove deprecated image resizing logic in ServerStreamWriterExtensions - Simplify value parsing logic for SetNode in TemplateReader by using the expression engine directly - Optimize function call processing flow in AsyncNCalcEngine
1 parent b1648fd commit a11dab7

File tree

9 files changed

+172
-117
lines changed

9 files changed

+172
-117
lines changed

src/Render/AssetProvider.cs

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ namespace Limekuma.Render;
88

99
public sealed class AssetProvider
1010
{
11+
private readonly ConcurrentDictionary<string, Image> _assets;
1112
private readonly ConcurrentDictionary<string, LoadedFont> _fontCache;
1213
private readonly Dictionary<string, (string, List<string>?)> _fontRules;
1314
private readonly Dictionary<string, (string, string?)> _pathRules;
@@ -20,6 +21,7 @@ public AssetProvider(string resourcePath)
2021
{
2122
_fontRules = [];
2223
_pathRules = [];
24+
_assets = [];
2325
_fontCache = [];
2426
LoadResources(resourcePath);
2527
}
@@ -29,7 +31,7 @@ public AssetProvider(string resourcePath)
2931
public Image LoadImage(string ns, string key)
3032
{
3133
string path = ResolveResourcePath(ns, key);
32-
return Image.Load(path);
34+
return LoadImage(path);
3335
}
3436

3537
public (FontFamily, List<FontFamily>) ResolveFont(string key)
@@ -55,7 +57,7 @@ public Size Measure(string text, string family, float size)
5557
(FontFamily fontFamily, List<FontFamily> fallbacks) = ResolveFont(family);
5658
float scaledSize = (float)(size * 72 / 300d);
5759
Font font = fontFamily.CreateFont(scaledSize);
58-
FontRectangle rect = TextMeasurer.MeasureAdvance(text, new TextOptions(font)
60+
FontRectangle rect = TextMeasurer.MeasureAdvance(text, new(font)
5961
{
6062
FallbackFontFamilies = fallbacks,
6163
Dpi = 300
@@ -71,7 +73,7 @@ public Size Measure(string text, string family, float size)
7173
}
7274

7375
(string path, _) = pathRule;
74-
return Path.GetFullPath(path);
76+
return path;
7577
}
7678

7779
private void LoadResources(string resourcePath)
@@ -103,7 +105,7 @@ private void LoadPathRules(XElement resourcesNode)
103105
continue;
104106
}
105107

106-
_pathRules[ns] = new(Path.GetFullPath(path), node.Attribute("rule")?.Value);
108+
_pathRules[ns] = new(path, node.Attribute("rule")?.Value);
107109
}
108110
}
109111

@@ -140,7 +142,7 @@ private string ResolveResourcePath(string ns, string key)
140142
{
141143
if (!_pathRules.TryGetValue(ns, out (string, string?) pathRule))
142144
{
143-
return Path.GetFullPath(key);
145+
return key;
144146
}
145147

146148
(string path, string? rule) = pathRule;
@@ -152,10 +154,20 @@ private string ResolveResourcePath(string ns, string key)
152154
return Path.Combine(path, Smart.Format(rule, new { key }));
153155
}
154156

157+
private Image LoadImage(string path)
158+
{
159+
Image loaded = _assets.GetOrAdd(path, p =>
160+
{
161+
Image image = Image.Load(path);
162+
return image;
163+
});
164+
165+
return loaded;
166+
}
167+
155168
private FontFamily LoadFont(string path)
156169
{
157-
string fullPath = Path.GetFullPath(path);
158-
LoadedFont loaded = _fontCache.GetOrAdd(fullPath, p =>
170+
LoadedFont loaded = _fontCache.GetOrAdd(path, p =>
159171
{
160172
FontCollection collection = new();
161173
FontFamily family = collection.Add(p);

src/Render/Drawer.cs

Lines changed: 46 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -2,67 +2,47 @@
22
using Limekuma.Render.ExpressionEngine;
33
using Limekuma.Render.Nodes;
44
using SixLabors.ImageSharp;
5+
using System.Collections;
56

67
namespace Limekuma.Render;
78

89
public sealed class Drawer
910
{
10-
public async Task<Image> DrawBestsAsync(CommonUser user, IEnumerable<CommonRecord> ever,
11-
IEnumerable<CommonRecord> current, int everTotal, int currentTotal, string typename, string prober) =>
11+
public async Task<Image> DrawBestsAsync(CommonUser user, IList<CommonRecord> ever,
12+
IList<CommonRecord> current, int everTotal, int currentTotal, string typename, string prober) =>
1213
await DrawBestsAsync(user, ever, current, everTotal, currentTotal, typename, prober, false, false);
1314

14-
public async Task<Image> DrawBestsAsync(CommonUser user, IEnumerable<CommonRecord> ever,
15-
IEnumerable<CommonRecord> current, int everTotal, int currentTotal, string typename, string prober,
15+
public async Task<Image> DrawBestsAsync(CommonUser user, IList<CommonRecord> ever,
16+
IList<CommonRecord> current, int everTotal, int currentTotal, string typename, string prober,
1617
bool isAnime) =>
1718
await DrawBestsAsync(user, ever, current, everTotal, currentTotal, typename, prober, isAnime, false);
1819

19-
public async Task<Image> DrawBestsAsync(CommonUser user, IEnumerable<CommonRecord> ever,
20-
IEnumerable<CommonRecord> current, int everTotal, int currentTotal, string typename, string prober,
20+
public async Task<Image> DrawBestsAsync(CommonUser user, IList<CommonRecord> ever,
21+
IList<CommonRecord> current, int everTotal, int currentTotal, string typename, string prober,
2122
bool isAnime, bool drawLevelSeg) => await DrawBestsAsync(user, ever, current, everTotal, currentTotal, typename,
2223
prober, isAnime, drawLevelSeg, "./Resources/Layouts/bests.xml");
2324

24-
public async Task<Image> DrawBestsAsync(CommonUser user, IEnumerable<CommonRecord> ever,
25-
IEnumerable<CommonRecord> current, int everTotal, int currentTotal, string typename, string prober,
25+
public async Task<Image> DrawBestsAsync(CommonUser user, IList<CommonRecord> ever,
26+
IList<CommonRecord> current, int everTotal, int currentTotal, string typename, string prober,
2627
bool isAnime, bool drawLevelSeg, string xmlPath)
2728
{
28-
List<CommonRecord> everList = [.. ever];
29-
List<CommonRecord> currentList = [.. current];
30-
List<object> everCards = [.. everList.Select((record, idx) => new { Record = record, Index = idx + 1 })];
31-
List<object> currentCards =
32-
[.. currentList.Select((record, idx) => new { Record = record, Index = idx + everList.Count + 1 })];
33-
int everDelta = everList.Count > 34 ? everList[0].DXRating - everList[^1].DXRating : everList.Count > 0 ? everList[0].DXRating : 0;
34-
int currentDelta = currentList.Count > 14 ? currentList[0].DXRating - currentList[^1].DXRating : currentList.Count > 0 ? currentList[0].DXRating : 0;
35-
int everMax = everList.Count > 0 ? everList[0].DXRating : 0;
36-
int everMin = everList.Count > 0 ? everList[^1].DXRating : 0;
37-
int currentMax = currentList.Count > 0 ? currentList[0].DXRating : 0;
38-
int currentMin = currentList.Count > 0 ? currentList[^1].DXRating : 0;
39-
int realRating = everTotal + currentTotal;
40-
string proberState = "on";
41-
if (user.Rating != realRating)
29+
int everMax = ever.Count > 0 ? ever[0].DXRating : 0;
30+
int everMin = ever.Count > 0 ? ever[^1].DXRating : 0;
31+
int currentMax = current.Count > 0 ? current[0].DXRating : 0;
32+
int currentMin = current.Count > 0 ? current[^1].DXRating : 0;
33+
bool mayMask = ever.Any(r => r.DXScore is 0 && (r.DXStar > 0 || r.Rank > Ranks.A)) || current.Any(r => r.DXScore is 0 && (r.DXStar > 0 || r.Rank > Ranks.A));
34+
Dictionary<string, object> scope = new(StringComparer.OrdinalIgnoreCase)
4235
{
43-
proberState = "off";
44-
}
45-
else if (everList.Any(r => r.DXScore is 0 && (r.DXStar > 0 || r.Rank < Ranks.C)) ||
46-
currentList.Any(r => r.DXScore is 0 && (r.DXStar > 0 || r.Rank < Ranks.C)))
47-
{
48-
proberState = "warning";
49-
}
50-
51-
Dictionary<string, object?> scope = new(StringComparer.OrdinalIgnoreCase)
52-
{
53-
["user"] = user,
54-
["everCards"] = everCards,
55-
["currentCards"] = currentCards,
56-
["everDelta"] = everDelta,
57-
["currentDelta"] = currentDelta,
58-
["everTotal"] = everTotal,
59-
["currentTotal"] = currentTotal,
60-
["realRating"] = realRating,
61-
["typename"] = typename,
62-
["prober"] = prober,
63-
["isAnime"] = isAnime,
64-
["drawLevelSeg"] = drawLevelSeg,
65-
["proberState"] = proberState,
36+
["userInfo"] = user,
37+
["everRecords"] = ever,
38+
["currentRecords"] = current,
39+
["everRating"] = everTotal,
40+
["currentRating"] = currentTotal,
41+
["typeName"] = typename,
42+
["proberName"] = prober,
43+
["animeMode"] = isAnime,
44+
["needSuggestion"] = drawLevelSeg,
45+
["mayMask"] = mayMask,
6646
["everMax"] = everMax,
6747
["everMin"] = everMin,
6848
["currentMax"] = currentMax,
@@ -71,35 +51,34 @@ public async Task<Image> DrawBestsAsync(CommonUser user, IEnumerable<CommonRecor
7151
return await DrawAsync(scope, xmlPath);
7252
}
7353

74-
public async Task<Image> DrawListAsync(CommonUser user, IEnumerable<CommonRecord> records, int page, int total,
75-
IEnumerable<int> counts, string level, string prober) => await DrawListAsync(user, records, page, total, counts,
76-
level, prober, "./Resources/Layouts/list.xml");
54+
public async Task<Image> DrawListAsync(CommonUser user, IList<CommonRecord> records, int page, int total,
55+
IList<int> counts, int startIndex, string level, string prober) => await DrawListAsync(user, records, page, total, counts,
56+
startIndex, level, prober, "./Resources/Layouts/list.xml");
7757

78-
public async Task<Image> DrawListAsync(CommonUser user, IEnumerable<CommonRecord> records, int page, int total,
79-
IEnumerable<int> counts, string level, string prober, string xmlPath)
58+
public async Task<Image> DrawListAsync(CommonUser user, IList<CommonRecord> records, int page, int total,
59+
IList<int> counts, int startIndex, string level, string prober, string xmlPath)
8060
{
81-
List<CommonRecord> list = [.. records];
82-
List<object> recordCards = [.. list.Select((record, idx) => new { Record = record, Index = idx + 1 })];
83-
List<int> countList = counts.ToList();
84-
int totalCount = countList.Count > 0 ? countList[^1] : 0;
85-
bool warning = list.Any(r => r.DXScore is 0 && (r.DXStar > 0 || r.Rank < Ranks.C));
86-
Dictionary<string, object?> scope = new(StringComparer.OrdinalIgnoreCase)
61+
List<object> recordCards = [.. records.Select((record, idx) => new { Record = record, Index = idx + startIndex + 1 })];
62+
List<int> countList = [.. counts];
63+
int totalCount = counts.Count > 0 ? counts[^1] : 0;
64+
bool mayMask = records.Any(r => r.DXScore is 0 && (r.DXStar > 0 || r.Rank > Ranks.A));
65+
Dictionary<string, object> scope = new(StringComparer.OrdinalIgnoreCase)
8766
{
88-
["user"] = user,
89-
["recordCards"] = recordCards,
90-
["page"] = page,
91-
["total"] = total,
92-
["counts"] = countList[..^1],
93-
["statsTotalCount"] = totalCount,
67+
["userInfo"] = user,
68+
["pageRecords"] = recordCards,
69+
["pageNumber"] = page,
70+
["totalPages"] = total,
71+
["statCounts"] = countList[..^1],
72+
["totalCount"] = totalCount,
9473
["level"] = level,
95-
["prober"] = prober,
96-
["proberState"] = warning ? "warning" : "on",
97-
["isAnime"] = false,
74+
["proberName"] = prober,
75+
["mayMask"] = mayMask,
76+
["animeMode"] = false,
9877
};
9978
return await DrawAsync(scope, xmlPath);
10079
}
10180

102-
private async Task<Image> DrawAsync(Dictionary<string, object?> scope, string xmlPath)
81+
private async Task<Image> DrawAsync(IDictionary<string, object> scope, string xmlPath)
10382
{
10483
AsyncNCalcEngine expr = new();
10584
RegisterFunctions(expr);
@@ -112,5 +91,6 @@ private async Task<Image> DrawAsync(Dictionary<string, object?> scope, string xm
11291
private void RegisterFunctions(AsyncNCalcEngine expr)
11392
{
11493
expr.RegisterFunction("ToString", (object x) => Convert.ToString(x));
94+
expr.RegisterFunction("Count", (IList x) => x.Count);
11595
}
11696
}

src/Render/ExpressionEngine/AsyncNCalcEngine.cs

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -52,20 +52,22 @@ public sealed class AsyncNCalcEngine
5252
}
5353
expression.EvaluateFunctionAsync += async (name, args) =>
5454
{
55-
if (_functions.TryGetValue(name, out Delegate? func))
55+
if (!_functions.TryGetValue(name, out Delegate? func))
5656
{
57-
ParameterInfo[] parameters = func.Method.GetParameters();
58-
object?[] funcArgs = new object[args.Parameters.Length];
59-
for (int i = 0; i < args.Parameters.Length; ++i)
60-
{
61-
object? paramValue = await args.Parameters[i].EvaluateAsync();
62-
funcArgs[i] = CoerceValue(paramValue,
63-
i < parameters.Length ? parameters[i].ParameterType : typeof(object));
64-
}
57+
return;
58+
}
6559

66-
object? result = func.DynamicInvoke(funcArgs);
67-
args.Result = result;
60+
ParameterInfo[] parameters = func.Method.GetParameters();
61+
object?[] funcArgs = new object[args.Parameters.Length];
62+
for (int i = 0; i < args.Parameters.Length; ++i)
63+
{
64+
object? paramValue = await args.Parameters[i].EvaluateAsync();
65+
funcArgs[i] = CoerceValue(paramValue,
66+
i < parameters.Length ? parameters[i].ParameterType : typeof(object));
6867
}
68+
69+
object? result = func.DynamicInvoke(funcArgs);
70+
args.Result = result;
6971
};
7072

7173
return await expression.EvaluateAsync();

src/Render/NodeRenderer.Measurement.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public static partial class NodeRenderer
2020

2121
private static Size MeasureImageNode(ImageNode image, AssetProvider assets)
2222
{
23-
using Image img = assets.LoadImage(image.Namespace, image.ResourceKey);
23+
Image img = assets.LoadImage(image.Namespace, image.ResourceKey);
2424
return new(img.Width, img.Height);
2525
}
2626

0 commit comments

Comments
 (0)