Spaces:
Running on T4
Running on T4
perf: batch frame fetching to overcome HTTP round-trip latency
Browse files- app.py +11 -7
- static/js/main.js +30 -42
app.py
CHANGED
|
@@ -469,20 +469,24 @@ def get_frame():
|
|
| 469 |
if model_manager is None:
|
| 470 |
return jsonify({"status": "error", "message": "Model not initialized"}), 400
|
| 471 |
|
| 472 |
-
# Get
|
| 473 |
-
|
| 474 |
-
|
| 475 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 476 |
# Update last consumption time
|
| 477 |
with consumption_monitor_lock:
|
| 478 |
last_frame_consumed_time = time.time()
|
| 479 |
|
| 480 |
-
# Convert numpy array to list for JSON
|
| 481 |
-
joints_list = joints.tolist()
|
| 482 |
return jsonify(
|
| 483 |
{
|
| 484 |
"status": "success",
|
| 485 |
-
"
|
| 486 |
"buffer_size": model_manager.frame_buffer.size(),
|
| 487 |
}
|
| 488 |
)
|
|
|
|
| 469 |
if model_manager is None:
|
| 470 |
return jsonify({"status": "error", "message": "Model not initialized"}), 400
|
| 471 |
|
| 472 |
+
# Get batch of frames from buffer (reduces HTTP round-trip overhead)
|
| 473 |
+
count = min(int(request.args.get("count", 8)), 20)
|
| 474 |
+
frames = []
|
| 475 |
+
for _ in range(count):
|
| 476 |
+
joints = model_manager.get_next_frame()
|
| 477 |
+
if joints is None:
|
| 478 |
+
break
|
| 479 |
+
frames.append(joints.tolist())
|
| 480 |
+
|
| 481 |
+
if frames:
|
| 482 |
# Update last consumption time
|
| 483 |
with consumption_monitor_lock:
|
| 484 |
last_frame_consumed_time = time.time()
|
| 485 |
|
|
|
|
|
|
|
| 486 |
return jsonify(
|
| 487 |
{
|
| 488 |
"status": "success",
|
| 489 |
+
"frames": frames,
|
| 490 |
"buffer_size": model_manager.frame_buffer.size(),
|
| 491 |
}
|
| 492 |
)
|
static/js/main.js
CHANGED
|
@@ -18,6 +18,10 @@ class MotionApp {
|
|
| 18 |
// Request throttling
|
| 19 |
this.isFetchingFrame = false; // Prevent concurrent requests
|
| 20 |
this.consecutiveWaiting = 0; // Count consecutive 'waiting' responses
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
|
| 22 |
// Session management
|
| 23 |
this.sessionId = this.generateSessionId();
|
|
@@ -473,68 +477,51 @@ class MotionApp {
|
|
| 473 |
|
| 474 |
fetchFrame() {
|
| 475 |
if (!this.isRunning) return;
|
| 476 |
-
|
| 477 |
const now = performance.now();
|
| 478 |
-
|
| 479 |
-
//
|
| 480 |
-
if (now >= this.nextFetchTime &&
|
| 481 |
-
// Schedule next fetch (maintain fixed rate regardless of delays)
|
| 482 |
this.nextFetchTime += this.frameInterval;
|
| 483 |
-
|
| 484 |
-
// If we've fallen behind, catch up
|
| 485 |
if (this.nextFetchTime < now) {
|
| 486 |
this.nextFetchTime = now + this.frameInterval;
|
| 487 |
}
|
| 488 |
-
|
| 489 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 490 |
this.isFetchingFrame = true;
|
| 491 |
-
|
| 492 |
-
fetch(`/api/get_frame?session_id=${this.sessionId}`)
|
| 493 |
.then(response => response.json())
|
| 494 |
.then(data => {
|
| 495 |
if (data.status === 'success') {
|
| 496 |
-
|
| 497 |
-
|
| 498 |
-
|
| 499 |
-
|
| 500 |
-
// Update motion FPS counter (only when frame consumed)
|
| 501 |
-
|
| 502 |
-
|
| 503 |
-
this.motionFpsCounter++;
|
| 504 |
-
|
| 505 |
-
// Update current root position
|
| 506 |
-
this.currentRootPos.set(
|
| 507 |
-
data.joints[0][0],
|
| 508 |
-
data.joints[0][1],
|
| 509 |
-
data.joints[0][2]
|
| 510 |
-
);
|
| 511 |
-
|
| 512 |
-
// Auto-follow (if user hasn't interacted for a while)
|
| 513 |
-
this.updateAutoFollow();
|
| 514 |
-
|
| 515 |
-
// Reset waiting counter on success
|
| 516 |
this.consecutiveWaiting = 0;
|
| 517 |
} else if (data.status === 'waiting') {
|
| 518 |
-
// No frame available, slow down requests if this happens repeatedly
|
| 519 |
this.consecutiveWaiting++;
|
| 520 |
-
|
| 521 |
-
// If buffer is consistently empty, back off a bit
|
| 522 |
-
if (this.consecutiveWaiting > 5) {
|
| 523 |
-
// Add a small delay to reduce server load
|
| 524 |
-
this.nextFetchTime = now + this.frameInterval * 1.5;
|
| 525 |
-
this.consecutiveWaiting = 0;
|
| 526 |
-
}
|
| 527 |
}
|
| 528 |
})
|
| 529 |
.catch(error => {
|
| 530 |
-
console.error('Error fetching
|
| 531 |
})
|
| 532 |
.finally(() => {
|
| 533 |
-
// Always mark as done fetching
|
| 534 |
this.isFetchingFrame = false;
|
| 535 |
});
|
| 536 |
}
|
| 537 |
-
|
| 538 |
// Use requestAnimationFrame for continuous checking
|
| 539 |
requestAnimationFrame(() => this.fetchFrame());
|
| 540 |
}
|
|
@@ -575,6 +562,7 @@ class MotionApp {
|
|
| 575 |
this.motionFpsCounter = 0;
|
| 576 |
this.isFetchingFrame = false;
|
| 577 |
this.consecutiveWaiting = 0;
|
|
|
|
| 578 |
this.startResetBtn.textContent = 'Start';
|
| 579 |
this.startResetBtn.classList.remove('btn-danger');
|
| 580 |
this.startResetBtn.classList.add('btn-primary');
|
|
|
|
| 18 |
// Request throttling
|
| 19 |
this.isFetchingFrame = false; // Prevent concurrent requests
|
| 20 |
this.consecutiveWaiting = 0; // Count consecutive 'waiting' responses
|
| 21 |
+
|
| 22 |
+
// Local frame queue for batch fetching (reduces HTTP round-trip overhead)
|
| 23 |
+
this.localFrameQueue = [];
|
| 24 |
+
this.batchSize = 8; // Fetch up to 8 frames per request
|
| 25 |
|
| 26 |
// Session management
|
| 27 |
this.sessionId = this.generateSessionId();
|
|
|
|
| 477 |
|
| 478 |
fetchFrame() {
|
| 479 |
if (!this.isRunning) return;
|
| 480 |
+
|
| 481 |
const now = performance.now();
|
| 482 |
+
|
| 483 |
+
// Play back from local queue at target FPS
|
| 484 |
+
if (now >= this.nextFetchTime && this.localFrameQueue.length > 0) {
|
|
|
|
| 485 |
this.nextFetchTime += this.frameInterval;
|
|
|
|
|
|
|
| 486 |
if (this.nextFetchTime < now) {
|
| 487 |
this.nextFetchTime = now + this.frameInterval;
|
| 488 |
}
|
| 489 |
+
|
| 490 |
+
const joints = this.localFrameQueue.shift();
|
| 491 |
+
this.skeleton.updatePose(joints);
|
| 492 |
+
this.frameCount++;
|
| 493 |
+
this.frameCountEl.textContent = this.frameCount;
|
| 494 |
+
this.motionFpsCounter++;
|
| 495 |
+
|
| 496 |
+
this.currentRootPos.set(joints[0][0], joints[0][1], joints[0][2]);
|
| 497 |
+
this.updateAutoFollow();
|
| 498 |
+
}
|
| 499 |
+
|
| 500 |
+
// Fetch a batch from server when local queue is running low
|
| 501 |
+
if (this.localFrameQueue.length < this.batchSize && !this.isFetchingFrame) {
|
| 502 |
this.isFetchingFrame = true;
|
| 503 |
+
|
| 504 |
+
fetch(`/api/get_frame?session_id=${this.sessionId}&count=${this.batchSize}`)
|
| 505 |
.then(response => response.json())
|
| 506 |
.then(data => {
|
| 507 |
if (data.status === 'success') {
|
| 508 |
+
// Push batch of frames into local queue
|
| 509 |
+
for (const frame of data.frames) {
|
| 510 |
+
this.localFrameQueue.push(frame);
|
| 511 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 512 |
this.consecutiveWaiting = 0;
|
| 513 |
} else if (data.status === 'waiting') {
|
|
|
|
| 514 |
this.consecutiveWaiting++;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 515 |
}
|
| 516 |
})
|
| 517 |
.catch(error => {
|
| 518 |
+
console.error('Error fetching frames:', error);
|
| 519 |
})
|
| 520 |
.finally(() => {
|
|
|
|
| 521 |
this.isFetchingFrame = false;
|
| 522 |
});
|
| 523 |
}
|
| 524 |
+
|
| 525 |
// Use requestAnimationFrame for continuous checking
|
| 526 |
requestAnimationFrame(() => this.fetchFrame());
|
| 527 |
}
|
|
|
|
| 562 |
this.motionFpsCounter = 0;
|
| 563 |
this.isFetchingFrame = false;
|
| 564 |
this.consecutiveWaiting = 0;
|
| 565 |
+
this.localFrameQueue = [];
|
| 566 |
this.startResetBtn.textContent = 'Start';
|
| 567 |
this.startResetBtn.classList.remove('btn-danger');
|
| 568 |
this.startResetBtn.classList.add('btn-primary');
|