Skip to content

Commit 7f5435f

Browse files
committed
Minor perf improvements
1 parent d092bdd commit 7f5435f

3 files changed

Lines changed: 59 additions & 58 deletions

File tree

BlazorExperiments/BlazorExperiments.UI.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
<BlazorEnableTimeZoneSupport>false</BlazorEnableTimeZoneSupport>
99
<BlazorWebAssemblyPreserveCollationData>false</BlazorWebAssemblyPreserveCollationData>
1010
<WasmExceptionHandling>true</WasmExceptionHandling>
11-
<WasmSIMD>false</WasmSIMD>
11+
<WasmSIMD>true</WasmSIMD>
1212
</PropertyGroup>
1313

1414
<ItemGroup>

BlazorExperiments/Models/NeonSnakeGame/Snake.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ public class Snake {
4141

4242
public Snake(int cellSize, int visibleCols, int visibleRows) {
4343
CellSize = cellSize;
44-
Cols = visibleCols * 6;
45-
Rows = visibleRows * 6;
44+
Cols = visibleCols * 5;
45+
Rows = visibleRows * 5;
4646
WorldW = Cols * CellSize;
4747
WorldH = Rows * CellSize;
4848
CenterCoords = new Vector2[Cols * Rows];

BlazorExperiments/Pages/NeonSnake.razor.cs

Lines changed: 56 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ public partial class NeonSnake {
1515
const int KeyDown = 40;
1616
const int KeyLeft = 37;
1717
const int KeyRight = 39;
18+
const double DoublePI = Math.PI * 2;
1819
double _screenW, _screenH, _gameAreaH;
1920
int _visibleRows;
2021

@@ -26,20 +27,20 @@ public partial class NeonSnake {
2627
bool _audioEnabled = true;
2728
double _obstacleInset = 0;
2829
double _obstacleSize = 0;
30+
double _baseR, _tailR;
2931
const int ObstaclePadding = 14;
3032
double _cacheW = 0;
3133
double _eggEr, _eggEh, _eggCacheSize;
3234
const int EggCachePadding = 20;
3335
readonly List<Vector2> _bodyPoints = new(256);
34-
static readonly (double Ox, double Oy, double Rx, double Ry, double A)[] EggSpeckles =
35-
[(-0.35, -0.18, 0.18, 0.09, 0.4), (0.38, 0.2, 0.13, 0.07, -0.6), (-0.1, 0.35, 0.1, 0.06, 0.9)];
3636
static readonly (double Offset, string Color)[] TailGradientStops = [(0d, "#9bb6ff"), (1d, Neon.SnakeSecondary)];
3737
int _lastScore, _lastBestScore;
3838
string _scoreText = "Score: 0";
3939
string _bestText = "Best: 0";
4040
const string BestScoreKey = "neonSnakeBestScore";
4141

4242
public async Task InitializeAsync() {
43+
_canvas.Timer.Enabled = false;
4344
_cellSize = _canvas.CellSize;
4445
_screenW = _canvas.Width;
4546
_screenH = _canvas.Height;
@@ -48,10 +49,12 @@ public async Task InitializeAsync() {
4849
_obstacleInset = _cellSize * 0.08;
4950
_obstacleSize = _cellSize - _obstacleInset * 2;
5051
_cacheW = _obstacleSize + ObstaclePadding * 2;
51-
var dpr = _canvas.WindowProperties.DevicePixelRatio;
52-
await JS.InvokeVoidAsync("neonSnakeCache.renderObstacle", _obstacleSize, ObstaclePadding, dpr);
52+
_baseR = _cellSize * 0.44;
53+
_tailR = _baseR * 0.35;
5354
_eggEr = _cellSize * 0.22;
5455
_eggEh = _cellSize * 0.3;
56+
var dpr = _canvas.WindowProperties.DevicePixelRatio;
57+
await JS.InvokeVoidAsync("neonSnakeCache.renderObstacle", _obstacleSize, ObstaclePadding, dpr);
5558
_eggCacheSize = await JS.InvokeAsync<double>("neonSnakeEggCache.renderStaticEgg", _eggEr, _eggEh, EggCachePadding, dpr);
5659
await JS.InvokeVoidAsync("neonSnakeEggCache.renderRunningEgg", _eggEr, _eggEh, EggCachePadding, dpr);
5760
var raw = await JS.InvokeAsync<string?>("localStorage.getItem", BestScoreKey);
@@ -238,16 +241,13 @@ async ValueTask DrawObstaclesAsync(Batch2D ctx) {
238241
}
239242

240243
async ValueTask DrawEggsAsync(Batch2D ctx) {
241-
var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
242-
var er = _eggEr;
243-
var eh = _eggEh;
244244
var halfCache = _eggCacheSize * 0.5;
245245
var camX = _snake.CamX;
246246
var camY = _snake.CamY;
247247
var viewRight = camX + _screenW;
248248
var viewBottom = camY + _gameAreaH;
249249
var cullMargin = _cellSize * 1.5;
250-
var glowMargin = _cellSize * 0.5;
250+
var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
251251

252252
// Pass 1: non-running (static) eggs — use cached sprite
253253
foreach (var egg in _snake.Food) {
@@ -266,11 +266,11 @@ async ValueTask DrawEggsAsync(Batch2D ctx) {
266266

267267
// Timer ring (dynamic per egg)
268268
var frac = Math.Max(0, egg.Timer / 5000);
269-
var ringR = eh * 1.48;
269+
var ringR = _eggEh * 1.48;
270270
await ctx.StrokeStyleAsync("rgba(60,200,175,0.2)");
271271
await ctx.LineWidthAsync(2.2);
272272
await ctx.BeginPathAsync();
273-
await ctx.ArcAsync(cx, dy, ringR, 0, Math.PI * 2);
273+
await ctx.ArcAsync(cx, dy, ringR, 0, DoublePI);
274274
await ctx.StrokeAsync();
275275

276276
var tColor = frac > 0.6 ? "#38ffda" : frac > 0.3 ? "#ffd050" : "#ff6830";
@@ -281,13 +281,13 @@ async ValueTask DrawEggsAsync(Batch2D ctx) {
281281
await ctx.LineWidthAsync(2.2);
282282
await ctx.LineCapAsync(LineCap.Round);
283283
await ctx.BeginPathAsync();
284-
await ctx.ArcAsync(cx, dy, ringR, -Math.PI / 2, -Math.PI / 2 + frac * Math.PI * 2);
284+
await ctx.ArcAsync(cx, dy, ringR, -Math.PI / 2, -Math.PI / 2 + frac * DoublePI);
285285
await ctx.StrokeAsync();
286286
await ctx.RestoreAsync();
287287
}
288288

289289
// Pass 2: running eggs — cached sprite + animated legs & eyes
290-
var walkPhase = (now * 0.007) % (Math.PI * 2);
290+
var walkPhase = (now * 0.007) % DoublePI;
291291
foreach (var egg in _snake.Food) {
292292
if (!egg.Running) continue;
293293
var cx = egg.ScreenX;
@@ -302,18 +302,18 @@ async ValueTask DrawEggsAsync(Batch2D ctx) {
302302
for (var si = 0; si < 2; si++) {
303303
var side = si == 0 ? -1 : 1;
304304
var extend = 0.65 + 0.35 * Math.Sin(walkPhase + (si == 0 ? 0 : Math.PI));
305-
var lx = cx + side * er * 0.5;
306-
var ly = dy + eh * 0.7;
307-
var lw = er * 0.48;
308-
var lh = eh * 0.72 * extend;
305+
var lx = cx + side * _eggEr * 0.5;
306+
var ly = dy + _eggEh * 0.7;
307+
var lw = _eggEr * 0.48;
308+
var lh = _eggEh * 0.72 * extend;
309309

310310
await ctx.FillStyleAsync("#ffdc96");
311311
await ctx.FillRectAsync(lx - lw / 2, ly, lw, lh);
312312

313-
var footDirX = egg.RunDir.X != 0 ? egg.RunDir.X * er * 0.2 : 0;
313+
var footDirX = egg.RunDir.X != 0 ? egg.RunDir.X * _eggEr * 0.2 : 0;
314314
await ctx.FillStyleAsync("#ffb855");
315315
await ctx.BeginPathAsync();
316-
await ctx.EllipseAsync(lx + footDirX, ly + lh, lw * 0.95, lw * 0.52, 0, 0, Math.PI * 2);
316+
await ctx.EllipseAsync(lx + footDirX, ly + lh, lw * 0.95, lw * 0.52, 0, 0, DoublePI);
317317
await ctx.FillAsync(FillRule.NonZero);
318318
}
319319

@@ -322,38 +322,35 @@ async ValueTask DrawEggsAsync(Batch2D ctx) {
322322

323323
// Animated eyes
324324
var rd = egg.RunDir;
325-
var eyeR = er * 0.165;
326-
var eyeCx = cx + rd.X * er * 0.38;
327-
var eyeCy = dy + rd.Y * eh * 0.38;
325+
var eyeR = _eggEr * 0.165;
326+
var eyeCx = cx + rd.X * _eggEr * 0.38;
327+
var eyeCy = dy + rd.Y * _eggEh * 0.38;
328328
var perpX = -rd.Y;
329329
var perpY = rd.X;
330330

331331
for (var side = -1; side <= 1; side += 2) {
332-
var ex = eyeCx + perpX * er * 0.28 * side;
333-
var ey = eyeCy + perpY * eh * 0.28 * side;
332+
var ex = eyeCx + perpX * _eggEr * 0.28 * side;
333+
var ey = eyeCy + perpY * _eggEh * 0.28 * side;
334334
await ctx.FillStyleAsync("rgba(248,252,255,0.95)");
335335
await ctx.BeginPathAsync();
336-
await ctx.ArcAsync(ex, ey, eyeR, 0, Math.PI * 2);
336+
await ctx.ArcAsync(ex, ey, eyeR, 0, DoublePI);
337337
await ctx.FillAsync(FillRule.NonZero);
338338

339339
await ctx.FillStyleAsync("#18103a");
340340
await ctx.BeginPathAsync();
341-
await ctx.ArcAsync(ex + rd.X * eyeR * 0.22, ey + rd.Y * eyeR * 0.22, eyeR * 0.55, 0, Math.PI * 2);
341+
await ctx.ArcAsync(ex + rd.X * eyeR * 0.22, ey + rd.Y * eyeR * 0.22, eyeR * 0.55, 0, DoublePI);
342342
await ctx.FillAsync(FillRule.NonZero);
343343

344344
await ctx.FillStyleAsync("rgba(255,255,255,0.88)");
345345
await ctx.BeginPathAsync();
346-
await ctx.ArcAsync(ex - eyeR * 0.2, ey - eyeR * 0.2, eyeR * 0.24, 0, Math.PI * 2);
346+
await ctx.ArcAsync(ex - eyeR * 0.2, ey - eyeR * 0.2, eyeR * 0.24, 0, DoublePI);
347347
await ctx.FillAsync(FillRule.NonZero);
348348
}
349349
}
350350
}
351351

352-
(double X, double Y)? GetNearestFoodPos() => _snake.GetNearestFoodPos();
353-
354352
async ValueTask DrawSnakeBodyAsync(Batch2D ctx) {
355-
var baseR = _cellSize * 0.44;
356-
var tailR = baseR * 0.35;
353+
357354
var bodyPoints = _bodyPoints;
358355
bodyPoints.Clear();
359356
for (var i = _snake.Parts.Count - 1; i >= 0; i--) {
@@ -386,7 +383,7 @@ async ValueTask TraceBodyPathAsync() {
386383

387384
await ctx.SaveAsync();
388385
await ctx.StrokeStyleAsync("rgba(0,0,0,0.25)");
389-
await ctx.LineWidthAsync(baseR * 2.2);
386+
await ctx.LineWidthAsync(_baseR * 2.2);
390387
await ctx.TranslateAsync(1.5, 2);
391388
await TraceBodyPathAsync();
392389
await ctx.StrokeAsync();
@@ -399,30 +396,30 @@ await ctx.StrokeStyleAsync(tail.X, tail.Y, _snake.Head.CurrentScreenPos.X, _snak
399396
(0d, Neon.SnakeSecondary),
400397
(0.55, "#4fb9ff"),
401398
(1d, Neon.SnakePrimary));
402-
await ctx.LineWidthAsync(baseR * 2.05);
399+
await ctx.LineWidthAsync(_baseR * 2.05);
403400
await TraceBodyPathAsync();
404401
await ctx.StrokeAsync();
405402
await ctx.RestoreAsync();
406403

407404
await ctx.StrokeStyleAsync("rgba(170, 238, 255, 0.7)");
408-
await ctx.LineWidthAsync(baseR * 1.12);
405+
await ctx.LineWidthAsync(_baseR * 1.12);
409406
await TraceBodyPathAsync();
410407
await ctx.StrokeAsync();
411408

412409
await ctx.StrokeStyleAsync("rgba(255,255,255,0.24)");
413-
await ctx.LineWidthAsync(baseR * 0.48);
410+
await ctx.LineWidthAsync(_baseR * 0.48);
414411
await ctx.SaveAsync();
415-
await ctx.TranslateAsync(0, -baseR * 0.14);
412+
await ctx.TranslateAsync(0, -_baseR * 0.14);
416413
await TraceBodyPathAsync();
417414
await ctx.StrokeAsync();
418415
await ctx.RestoreAsync();
419416

420417
await ctx.SaveAsync();
421418
await ctx.TranslateAsync(tail.X, tail.Y);
422419
await ctx.RotateAsync(tailAngle);
423-
await ctx.FillStyleAsync(-tailR * 0.5, -tailR * 0.2, tailR * 0.2, 0, 0, tailR * 1.4, TailGradientStops);
420+
await ctx.FillStyleAsync(-_tailR * 0.5, -_tailR * 0.2, _tailR * 0.2, 0, 0, _tailR * 1.4, TailGradientStops);
424421
await ctx.BeginPathAsync();
425-
await ctx.EllipseAsync(0, 0, tailR * 1.2, tailR * 0.9, 0, 0, Math.PI * 2);
422+
await ctx.EllipseAsync(0, 0, _tailR * 1.2, _tailR * 0.9, 0, 0, DoublePI);
426423
await ctx.FillAsync(FillRule.NonZero);
427424
await ctx.RestoreAsync();
428425
}
@@ -477,18 +474,18 @@ await ctx.FillStyleAsync(-hr * 0.5, -hr * 0.55, hr * 0.6, hr * 0.4,
477474

478475
await ctx.FillStyleAsync("rgba(255,255,255,0.3)");
479476
await ctx.BeginPathAsync();
480-
await ctx.EllipseAsync(-hr * 0.14, -hr * 0.36, hr * 0.4, hr * 0.17, -0.35, 0, Math.PI * 2);
477+
await ctx.EllipseAsync(-hr * 0.14, -hr * 0.36, hr * 0.4, hr * 0.17, -0.35, 0, DoublePI);
481478
await ctx.FillAsync(FillRule.NonZero);
482479

483480
await ctx.FillStyleAsync("rgba(255, 145, 215, 0.28)");
484481
await ctx.BeginPathAsync();
485-
await ctx.EllipseAsync(hr * 0.14, -hr * 0.54, hr * 0.14, hr * 0.09, 0, 0, Math.PI * 2);
482+
await ctx.EllipseAsync(hr * 0.14, -hr * 0.54, hr * 0.14, hr * 0.09, 0, 0, DoublePI);
486483
await ctx.FillAsync(FillRule.NonZero);
487484
await ctx.BeginPathAsync();
488-
await ctx.EllipseAsync(hr * 0.14, hr * 0.54, hr * 0.14, hr * 0.09, 0, 0, Math.PI * 2);
485+
await ctx.EllipseAsync(hr * 0.14, hr * 0.54, hr * 0.14, hr * 0.09, 0, 0, DoublePI);
489486
await ctx.FillAsync(FillRule.NonZero);
490487

491-
var foodPos = GetNearestFoodPos();
488+
var foodPos = _snake.GetNearestFoodPos();
492489
var eyeR = hr * 0.21;
493490
var eyeX = hr * 0.06;
494491
var eyeY = hr * 0.5;
@@ -499,13 +496,13 @@ await ctx.FillStyleAsync(-hr * 0.5, -hr * 0.55, hr * 0.6, hr * 0.4,
499496

500497
await ctx.FillStyleAsync("rgba(238,255,255,0.98)");
501498
await ctx.BeginPathAsync();
502-
await ctx.ArcAsync(ex, ey, eyeR, 0, Math.PI * 2);
499+
await ctx.ArcAsync(ex, ey, eyeR, 0, DoublePI);
503500
await ctx.FillAsync(FillRule.NonZero);
504501

505502
await ctx.StrokeStyleAsync("rgba(130, 195, 255, 0.8)");
506503
await ctx.LineWidthAsync(1.2);
507504
await ctx.BeginPathAsync();
508-
await ctx.ArcAsync(ex, ey, eyeR, 0, Math.PI * 2);
505+
await ctx.ArcAsync(ex, ey, eyeR, 0, DoublePI);
509506
await ctx.StrokeAsync();
510507

511508
var lookX = 1d;
@@ -527,36 +524,36 @@ await ctx.FillStyleAsync(-hr * 0.5, -hr * 0.55, hr * 0.6, hr * 0.4,
527524

528525
await ctx.FillStyleAsync("#2a2060");
529526
await ctx.BeginPathAsync();
530-
await ctx.ArcAsync(pupilX, pupilY, eyeR * 0.48, 0, Math.PI * 2);
527+
await ctx.ArcAsync(pupilX, pupilY, eyeR * 0.48, 0, DoublePI);
531528
await ctx.FillAsync(FillRule.NonZero);
532529

533530
await ctx.FillStyleAsync("rgba(255,255,255,0.92)");
534531
await ctx.BeginPathAsync();
535-
await ctx.ArcAsync(pupilX - eyeR * 0.16, pupilY - eyeR * 0.14, eyeR * 0.18, 0, Math.PI * 2);
532+
await ctx.ArcAsync(pupilX - eyeR * 0.16, pupilY - eyeR * 0.14, eyeR * 0.18, 0, DoublePI);
536533
await ctx.FillAsync(FillRule.NonZero);
537534

538535
await ctx.FillStyleAsync("rgba(255,255,255,0.5)");
539536
await ctx.BeginPathAsync();
540-
await ctx.ArcAsync(pupilX + eyeR * 0.1, pupilY + eyeR * 0.14, eyeR * 0.08, 0, Math.PI * 2);
537+
await ctx.ArcAsync(pupilX + eyeR * 0.1, pupilY + eyeR * 0.14, eyeR * 0.08, 0, DoublePI);
541538
await ctx.FillAsync(FillRule.NonZero);
542539
}
543540

544541
await ctx.FillStyleAsync("rgba(25, 45, 105, 0.5)");
545542
await ctx.BeginPathAsync();
546-
await ctx.ArcAsync(hr * 0.46, -hr * 0.1, hr * 0.042, 0, Math.PI * 2);
543+
await ctx.ArcAsync(hr * 0.46, -hr * 0.1, hr * 0.042, 0, DoublePI);
547544
await ctx.FillAsync(FillRule.NonZero);
548545
await ctx.BeginPathAsync();
549-
await ctx.ArcAsync(hr * 0.46, hr * 0.1, hr * 0.042, 0, Math.PI * 2);
546+
await ctx.ArcAsync(hr * 0.46, hr * 0.1, hr * 0.042, 0, DoublePI);
550547
await ctx.FillAsync(FillRule.NonZero);
551548

552549
if (_snake.EatBounce > 0.3) {
553550
await ctx.FillStyleAsync("#ff60bc");
554551
await ctx.BeginPathAsync();
555-
await ctx.EllipseAsync(hr * 0.76, 0, hr * 0.32, hr * 0.60, 0, 0, Math.PI * 2);
552+
await ctx.EllipseAsync(hr * 0.76, 0, hr * 0.32, hr * 0.60, 0, 0, DoublePI);
556553
await ctx.FillAsync(FillRule.NonZero);
557554
await ctx.FillStyleAsync("#ffd8f4");
558555
await ctx.BeginPathAsync();
559-
await ctx.ArcAsync(hr * 0.76, 0, hr * 0.152, 0, Math.PI * 2);
556+
await ctx.ArcAsync(hr * 0.76, 0, hr * 0.152, 0, DoublePI);
560557
await ctx.FillAsync(FillRule.NonZero);
561558
}
562559

@@ -582,6 +579,8 @@ await ctx.FillStyleAsync(-hr * 0.5, -hr * 0.55, hr * 0.6, hr * 0.4,
582579
}
583580

584581
async ValueTask DrawParticlesAsync(Batch2D ctx) {
582+
if (_snake.EatParticles.Count == 0) return;
583+
585584
foreach (var p in _snake.EatParticles) {
586585
var size = 4 * p.Life;
587586
await ctx.GlobalAlphaAsync(p.Life);
@@ -591,7 +590,7 @@ async ValueTask DrawParticlesAsync(Batch2D ctx) {
591590

592591
await ctx.BeginPathAsync();
593592
for (var s = 0; s < 5; s++) {
594-
var a = (s / 5d) * Math.PI * 2 - Math.PI / 2;
593+
var a = (s / 5d) * DoublePI - Math.PI / 2;
595594
var ox = Math.Cos(a) * size;
596595
var oy = Math.Sin(a) * size;
597596
if (s == 0) {
@@ -614,6 +613,8 @@ async ValueTask DrawParticlesAsync(Batch2D ctx) {
614613
}
615614

616615
async ValueTask DrawHitParticlesAsync(Batch2D ctx) {
616+
if (_snake.HitParticles.Count == 0) return;
617+
617618
foreach (var p in _snake.HitParticles) {
618619
await ctx.GlobalAlphaAsync(p.Life);
619620
await ctx.FillStyleAsync(p.Color);
@@ -658,21 +659,21 @@ async ValueTask DrawDeathSegmentsAsync(Batch2D ctx) {
658659
var r = seg.R * (1 - progress * 0.4);
659660
await ctx.FillStyleAsync("rgba(0,0,0,0.22)");
660661
await ctx.BeginPathAsync();
661-
await ctx.ArcAsync(1.5, 2, r, 0, Math.PI * 2);
662+
await ctx.ArcAsync(1.5, 2, r, 0, DoublePI);
662663
await ctx.FillAsync(FillRule.NonZero);
663664

664665
var baseColor = i % 2 == 0 ? Neon.SnakePrimary : Neon.SnakeSecondary;
665666
await ctx.FillStyleAsync(flashOn ? Neon.Danger : baseColor);
666667
await ctx.ShadowColorAsync(flashOn ? Neon.DangerGlow : Neon.SnakeGlow);
667668
await ctx.ShadowBlurAsync(14);
668669
await ctx.BeginPathAsync();
669-
await ctx.ArcAsync(0, 0, r, 0, Math.PI * 2);
670+
await ctx.ArcAsync(0, 0, r, 0, DoublePI);
670671
await ctx.FillAsync(FillRule.NonZero);
671672
await ctx.ShadowBlurAsync(0);
672673

673674
await ctx.FillStyleAsync("rgba(255,255,255,0.35)");
674675
await ctx.BeginPathAsync();
675-
await ctx.ArcAsync(-r * 0.25, -r * 0.25, r * 0.35, 0, Math.PI * 2);
676+
await ctx.ArcAsync(-r * 0.25, -r * 0.25, r * 0.35, 0, DoublePI);
676677
await ctx.FillAsync(FillRule.NonZero);
677678

678679
if (seg.IsHead && progress < 0.6) {
@@ -758,7 +759,7 @@ async ValueTask DrawHeartAsync(Batch2D ctx, double cx, double cy, double size, s
758759
if (!isOff) {
759760
await ctx.FillStyleAsync("rgba(255,255,255,0.45)");
760761
await ctx.BeginPathAsync();
761-
await ctx.ArcAsync(cx - size * 0.35, cy + size * 0.05, size * 0.25, 0, Math.PI * 2);
762+
await ctx.ArcAsync(cx - size * 0.35, cy + size * 0.05, size * 0.25, 0, DoublePI);
762763
await ctx.FillAsync(FillRule.NonZero);
763764
}
764765
}

0 commit comments

Comments
 (0)