Skip to content

Commit 1cd0621

Browse files
committed
cache each player+env_id combo to persist player between updates. playback speed based on moving average of time between updates. players removed from cache after 2 mins
1 parent 5638334 commit 1cd0621

File tree

1 file changed

+136
-48
lines changed

1 file changed

+136
-48
lines changed

visualizer_live.js

Lines changed: 136 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,11 @@ let curStats = {envs: 0, viewers: 0};
1919
let backgroundSharp = null;
2020
let backgroundSmooth = null;
2121

22-
// animate each batch of updates for 12 seconds
23-
const animationDuration = 30000;
22+
// Default animation duration - will be adjusted based on batch timing
23+
const defaultAnimationDuration = 30000;
24+
25+
// Sprite cache: Maps "user-envID" to sprite state
26+
const spriteCache = new Map();
2427

2528
const container = new PIXI.Container();
2629
// scale and center container initially
@@ -49,13 +52,27 @@ function smoothstep(min, max, value) {
4952

5053
let userFilter = new RegExp("");
5154
let activeSprites = [];
55+
56+
// Helper function to calculate exponential moving average
57+
function updateMovingAverage(currentAvg, newValue, alpha = 0.3) {
58+
if (currentAvg === null) return newValue;
59+
return alpha * newValue + (1 - alpha) * currentAvg;
60+
}
61+
62+
// Helper function to get cache key
63+
function getCacheKey(meta) {
64+
const envID = meta.env_id !== undefined ? meta.env_id : "default";
65+
return `${meta.user}-${envID}`;
66+
}
67+
5268
function setUserFilter(value) {
5369
userFilter = new RegExp(value);
5470
activeSprites.forEach(obj => {
5571
container.removeChild(obj.subContainer); // Remove sprite from the scene
5672
obj.subContainer.destroy({ children: true }); // Optional: frees up memory used by the sprite
5773
});
58-
activeSprites = []
74+
activeSprites = [];
75+
spriteCache.clear(); // Clear the cache as well
5976
}
6077

6178
app.view.addEventListener('wheel', (e) => {
@@ -258,7 +275,7 @@ PIXI.Assets.load([
258275
const path = data["coords"];
259276
const meta = data["metadata"];
260277
console.log(meta);
261-
if (Date.now() - lastFrameTime < 2 * animationDuration) {
278+
if (Date.now() - lastFrameTime < 2 * defaultAnimationDuration) {
262279
startAnimationForPath(path, meta);
263280
}
264281
}
@@ -279,8 +296,6 @@ PIXI.Assets.load([
279296
// Refresh WebSocket connection every 2 minutes (120000 milliseconds)
280297
setInterval(refreshWS, 120000);
281298

282-
283-
284299
let baseTextureChar = new PIXI.BaseTexture("assets/characters_transparent.png", {
285300
scaleMode: PIXI.SCALE_MODES.NEAREST,
286301
});
@@ -304,51 +319,106 @@ PIXI.Assets.load([
304319

305320
// Check if meta is defined and has a 'user' key
306321
if (meta && meta.user !== undefined && typeof(meta.user) === "string") {
307-
// Create a text label
308322
const envID = meta.env_id !== undefined ? `-${meta.env_id}` : "";
309323
const extraInfo = meta.extra !== undefined ? ` ${meta.extra}` : "";
310324
const color = (meta.color && CSS.supports('color', meta.color)) ? meta.color : "0x000000";
311325

312326
const labelText = meta.user + envID + extraInfo;
313327
if (userFilter.exec(labelText) !== null) {
328+
const cacheKey = getCacheKey(meta);
329+
const currentTime = Date.now();
330+
314331
let spriteIdx = 0;
315332
if (meta.sprite_id !== undefined) {
316333
let parsed = parseInt(meta.sprite_id, 10);
317334
if (!isNaN(parsed) && parsed > 0 && parsed < 50) {
318335
spriteIdx = parsed;
319336
}
320337
}
321-
const sprite = new PIXI.Sprite(textureCharsDown[spriteIdx]);
322-
//sprite.x = charOffset * 40;
323-
sprite.anchor.set(0.5);
324-
//sprite.scale.set(0.5); // Adjust scale as needed
325-
const subContainer = new PIXI.Container();
326-
327-
subContainer.addChild(sprite);
328-
const label = new PIXI.Text(
329-
labelText,
330-
{
331-
fontFamily: 'Arial',
332-
fontSize: 14,
333-
fill: color,
334-
align: 'center',
335-
});
336-
label.x = sprite.x + sprite.width * 0.5; // Position the label next to the sprite
337-
label.y -= sprite.height; // Adjust the label position as needed
338-
subContainer.addChild(label);
339-
container.addChild(subContainer);
340-
341-
activeSprites.push({ subContainer, sprite, spriteIdx, path, startTime: null });
338+
339+
// Check if sprite already exists in cache
340+
if (spriteCache.has(cacheKey)) {
341+
const cached = spriteCache.get(cacheKey);
342+
343+
// Calculate time since last update
344+
const timeSinceLastUpdate = currentTime - cached.lastUpdateTime;
345+
346+
// Update moving average of batch timing
347+
cached.batchTimingAvg = updateMovingAverage(cached.batchTimingAvg, timeSinceLastUpdate);
348+
349+
// Update the path and reset animation with dynamic duration
350+
cached.path = path;
351+
cached.startTime = null; // Reset to restart animation
352+
cached.lastUpdateTime = currentTime;
353+
cached.animationDuration = Math.max(1000, cached.batchTimingAvg * 0.95); // 95% of avg to finish slightly early
354+
355+
// Update sprite index if it changed, preserving current direction
356+
if (cached.spriteIdx !== spriteIdx) {
357+
cached.spriteIdx = spriteIdx;
358+
// Update texture with new sprite ID but maintain direction
359+
const directionTextures = {
360+
'down': textureCharsDown,
361+
'up': textureCharsUp,
362+
'left': textureCharsLeft,
363+
'right': textureCharsRight
364+
};
365+
cached.sprite.texture = directionTextures[cached.currentDirection][spriteIdx];
366+
}
367+
368+
// Update label text and color if changed
369+
if (cached.label) {
370+
cached.label.text = labelText;
371+
cached.label.style.fill = color;
372+
}
373+
} else {
374+
// Create new sprite
375+
const sprite = new PIXI.Sprite(textureCharsDown[spriteIdx]);
376+
sprite.anchor.set(0.5);
377+
const subContainer = new PIXI.Container();
378+
379+
subContainer.addChild(sprite);
380+
const label = new PIXI.Text(
381+
labelText,
382+
{
383+
fontFamily: 'Arial',
384+
fontSize: 14,
385+
fill: color,
386+
align: 'center',
387+
});
388+
label.x = sprite.x + sprite.width * 0.5;
389+
label.y -= sprite.height;
390+
subContainer.addChild(label);
391+
container.addChild(subContainer);
392+
393+
// Initialize sprite in cache
394+
const newCachedSprite = {
395+
subContainer,
396+
sprite,
397+
label,
398+
spriteIdx,
399+
path,
400+
startTime: null,
401+
lastUpdateTime: currentTime,
402+
batchTimingAvg: null, // Will be set on second update
403+
animationDuration: defaultAnimationDuration,
404+
currentDirection: 'down'
405+
};
406+
407+
spriteCache.set(cacheKey, newCachedSprite);
408+
activeSprites.push(newCachedSprite);
409+
}
342410
}
343411
}
344412

345413
}
346414

347415
function animate(time) {
416+
const currentTime = Date.now();
417+
348418
activeSprites.forEach(obj => {
349419
if (!obj.startTime) obj.startTime = time;
350420
const timeDelta = time - obj.startTime;
351-
const progress = Math.min(timeDelta / animationDuration, 1);
421+
const progress = Math.min(timeDelta / obj.animationDuration, 1);
352422

353423
// Calculate the current position
354424
const currentIndex = Math.floor(progress * (obj.path.length - 1));
@@ -365,33 +435,51 @@ PIXI.Assets.load([
365435
const dx = nextPoint[0] - currentPoint[0];
366436
const dy = nextPoint[1] - currentPoint[1];
367437

368-
// Determine which direction is dominant
369-
if (Math.abs(dx) > Math.abs(dy)) {
370-
// Horizontal movement is dominant
371-
if (dx > 0) {
372-
obj.sprite.texture = textureCharsRight[obj.spriteIdx];
373-
} else {
374-
obj.sprite.texture = textureCharsLeft[obj.spriteIdx];
375-
}
376-
} else {
377-
// Vertical movement is dominant
378-
if (dy > 0) {
379-
obj.sprite.texture = textureCharsDown[obj.spriteIdx];
438+
// Only update direction if there's actual movement
439+
if (dx !== 0 || dy !== 0) {
440+
// Determine which direction is dominant
441+
let newDirection = obj.currentDirection;
442+
if (Math.abs(dx) > Math.abs(dy)) {
443+
// Horizontal movement is dominant
444+
if (dx > 0) {
445+
obj.sprite.texture = textureCharsRight[obj.spriteIdx];
446+
newDirection = 'right';
447+
} else {
448+
obj.sprite.texture = textureCharsLeft[obj.spriteIdx];
449+
newDirection = 'left';
450+
}
380451
} else {
381-
obj.sprite.texture = textureCharsUp[obj.spriteIdx];
452+
// Vertical movement is dominant
453+
if (dy > 0) {
454+
obj.sprite.texture = textureCharsDown[obj.spriteIdx];
455+
newDirection = 'down';
456+
} else {
457+
obj.sprite.texture = textureCharsUp[obj.spriteIdx];
458+
newDirection = 'up';
459+
}
382460
}
461+
obj.currentDirection = newDirection;
383462
}
384463
}
464+
});
385465

386-
if (progress >= 1) {
387-
container.removeChild(obj.subContainer); // Remove sprite from the scene
388-
obj.subContainer.destroy({ children: true }); // Optional: frees up memory used by the sprite
389-
}
466+
// Clean up stale sprites
467+
const staleThreshold = 120000; // 2 minutes
468+
const keysToRemove = [];
390469

470+
spriteCache.forEach((cached, key) => {
471+
if (currentTime - cached.lastUpdateTime > staleThreshold) {
472+
container.removeChild(cached.subContainer);
473+
cached.subContainer.destroy({ children: true });
474+
keysToRemove.push(key);
475+
}
391476
});
392477

393-
// Remove sprites that have completed their animation
394-
activeSprites = activeSprites.filter(obj => (time - obj.startTime) < animationDuration);
478+
keysToRemove.forEach(key => spriteCache.delete(key));
479+
480+
// Update activeSprites to match spriteCache
481+
activeSprites = Array.from(spriteCache.values());
482+
395483
lastFrameTime = Date.now();
396484
requestAnimationFrame(animate);
397485
}

0 commit comments

Comments
 (0)