時間軸対応機能¶
🎯 概要¶
時間軸対応機能は、Foxglove Studioの時間シーク操作に完全対応し、任意の時点での正確な可視化状態を再現する機能です。複数トピック統合システムと連携して、スナップショットと更新データから指定時刻の状態を動的に合成します。
⏰ 時間シーク機能の仕組み¶
基本原理¶
- 時刻変更検出: Foxglove Studioの
currentTime監視 - 基準点検索: 指定時刻以前の最新スナップショット
- 更新適用: スナップショット以降の増分更新を時系列順に適用
- 状態合成: 最終的な可視化状態を構築
timeline
title 時間シーク処理フロー
section スナップショット
0秒 : 完全状態A
5秒 : 完全状態B
10秒 : 完全状態C
section 更新トピック
1秒 : ロボット移動
2秒 : ボール更新
3秒 : 軌跡追加
6秒 : 状態変更
7秒 : マーカー追加
section シーク処理
7.5秒時点 : スナップショットB + 更新(6秒,7秒)
🔄 双方向時間シーク対応¶
順方向シーク(通常再生)¶
通常の時間進行に従って状態を更新
// 時刻7.5秒にシーク
const targetTime = 7500; // ミリ秒
const composedState = composeMessagesAtTime(targetTime);
逆方向シーク(巻き戻し)¶
過去の時点への移動でも正確な状態復元
// 時刻3.2秒に巻き戻し
const targetTime = 3200; // ミリ秒
// スナップショット0秒 + 更新(1秒,2秒,3秒)を合成
const composedState = composeMessagesAtTime(targetTime);
ジャンプシーク(任意時点への移動)¶
タイムライン上の任意の点への瞬間移動
// 時刻50分32.5秒へジャンプ
const targetTime = (50 * 60 + 32) * 1000 + 500;
const composedState = composeMessagesAtTime(targetTime);
スナップショット不在時のフォールバック¶
スナップショット(/aggregated_svgs)が履歴内に存在しない場合でも、更新トピック(/visualizer_svgs)のみから仮状態を構築します。
- 適用方針:
replaceとclearのみを時系列に適用し、appendは無視 - ねらい:
replaceを自己完結(レイヤー全体の完全置換)とすることで、ベース不在でも意味のある表示を維持 - 効果: 長距離巻き戻しやスナップショット欠損時の視覚破綻を回避
// aggregated が見つからない場合の合成(概念)
const updates = getUpdatesInRange(-Infinity, targetTime).sort((a, b) => a.time - b.time);
const layerMap = new Map<string, string[]>();
for (const u of updates) {
switch (u.operation) {
case 'replace': layerMap.set(u.layer, [...u.svg_primitives]); break;
case 'clear': layerMap.set(u.layer, []); break;
case 'append': /* ベース不在では無視 */ break;
}
}
return buildFinalState(layerMap); // 空なら undefined
🏗️ 実装アーキテクチャ¶
時間管理システム¶
時刻検出機構¶
// Foxglove Studioからの時刻変更を監視
context.onRender = (renderState, done) => {
if (renderState.currentTime !== undefined) {
const newCurrentTime = renderState.currentTime.sec * 1000 +
renderState.currentTime.nsec / 1000000;
setSeekTime(newCurrentTime);
}
};
context.watch("currentTime");
状態合成エンジン¶
const composeMessagesAtTime = useCallback((targetTime: number): SvgLayerArray | undefined => {
try {
// 1. 基準スナップショットの検索
const baseSnapshot = findLatestSnapshotBefore(targetTime);
if (!baseSnapshot) return undefined;
// 2. レイヤー状態の初期化
const layerMap = initializeLayerMap(baseSnapshot);
// 3. 関連する更新の抽出と適用
const updates = extractRelevantUpdates(baseSnapshot.timestamp, targetTime);
applyUpdatesInOrder(layerMap, updates);
// 4. 最終状態の構築
return buildFinalState(layerMap);
} catch (error) {
console.error('Time seek composition error:', error);
return undefined;
}
}, [aggregatedMessages, updateMessages, config.enableUpdateTopic]);
メッセージ履歴管理¶
タイムスタンプベース索引¶
// 効率的な時間ベース検索のためのMap構造
const aggregatedMessages = new Map<number, MessageEvent>();
const updateMessages = new Map<number, MessageEvent>();
// メッセージ受信時のタイムスタンプ計算
const timestamp = message.receiveTime.sec * 1000 + message.receiveTime.nsec / 1000000;
履歴検索アルゴリズム¶
const findLatestSnapshotBefore = (targetTime: number) => {
let latestTime = -1;
let latestSnapshot: SvgLayerArray | undefined;
for (const [timestamp, message] of aggregatedMessages) {
if (timestamp <= targetTime && timestamp > latestTime) {
latestTime = timestamp;
latestSnapshot = message.message as SvgLayerArray;
}
}
return { snapshot: latestSnapshot, timestamp: latestTime };
};
📊 パフォーマンス最適化¶
効率的な状態合成¶
レイヤーマップ最適化¶
// 初期化時の効率的なコピー
const layerMap = new Map<string, string[]>();
baseSnapshot.svg_primitive_arrays.forEach(array => {
if (array?.layer && Array.isArray(array.svg_primitives)) {
layerMap.set(array.layer, [...array.svg_primitives]);
}
});
更新処理の最適化¶
// 操作別の効率的な処理
switch (update.operation) {
case "replace":
// 完全置換(最も効率的)
layerMap.set(update.layer, [...update.svg_primitives]);
break;
case "append":
// 追加処理(既存配列との結合)
const existing = layerMap.get(update.layer) || [];
layerMap.set(update.layer, [...existing, ...update.svg_primitives]);
break;
case "clear":
// クリア処理(空配列設定)
layerMap.set(update.layer, []);
break;
}
メモリ効率化¶
自動履歴管理¶
// 設定可能な履歴制限
const config = {
maxHistoryDuration: 300, // 5分間
maxHistorySize: 1000 // 最大1000メッセージ
};
// 定期的なクリーンアップ(30秒間隔)
useEffect(() => {
const interval = setInterval(cleanupHistory, 30000);
return () => clearInterval(interval);
}, [cleanupHistory]);
スマートキャッシング¶
// 最後に合成した状態をキャッシュ
const [lastComposedTime, setLastComposedTime] = useState<number>();
const [lastComposedState, setLastComposedState] = useState<SvgLayerArray>();
// 同じ時刻への重複シークを回避
if (targetTime === lastComposedTime && lastComposedState) {
return lastComposedState;
}
🎛️ ユーザーインターフェース¶
時間情報表示¶
// パネル上での時間情報表示
{seekTime !== undefined && (
<p>Seek Time: {new Date(seekTime).toISOString()}</p>
)}
履歴状況監視¶
// リアルタイム履歴サイズ表示
<p>History: Aggregated({aggregatedMessages.size}), Updates({updateMessages.size})</p>
デバッグ情報¶
// 開発時の詳細情報表示
if (process.env.NODE_ENV === 'development') {
console.log(`Composing state for time: ${targetTime}`);
console.log(`Base snapshot at: ${baseSnapshotTime}`);
console.log(`Applying ${relevantUpdates.length} updates`);
}
🚀 高度な活用例¶
精密分析モード¶
// ミリ秒単位での精密な時間分析
const precisionSeek = (targetTimeMs: number) => {
const composedState = composeMessagesAtTime(targetTimeMs);
// 特定レイヤーの詳細分析
const robotLayer = composedState?.svg_primitive_arrays
.find(layer => layer.layer.startsWith('robots/'));
if (robotLayer) {
analyzeRobotPositions(robotLayer.svg_primitives);
}
};
比較分析¶
// 複数時点の状態比較
const compareTimePoints = (time1: number, time2: number) => {
const state1 = composeMessagesAtTime(time1);
const state2 = composeMessagesAtTime(time2);
return {
differences: findStateDifferences(state1, state2),
movements: calculateMovements(state1, state2),
timeline: time2 - time1
};
};
パフォーマンス分析¶
// 時間シーク性能の監視
const measureSeekPerformance = (targetTime: number) => {
const startTime = performance.now();
const result = composeMessagesAtTime(targetTime);
const endTime = performance.now();
console.log(`Seek to ${targetTime}ms took ${endTime - startTime}ms`);
return result;
};
💡 ベストプラクティス¶
効率的な時間移動¶
- 段階的移動: 大きな時間ジャンプより小刻みな移動
- キーフレーム活用: スナップショット時刻への移動を優先
- 履歴制限調整: 分析期間に応じた適切な設定
データ設計の推奨事項¶
- 適切なスナップショット間隔: 3-10秒(データ量とのバランス)
- レイヤー分割: 更新頻度に応じたレイヤー設計
- 更新最適化: 不要な更新の削減
デバッグとトラブルシューティング¶
- ログ出力: 重要な時間イベントのログ記録
- 状態検証: 合成結果の妥当性確認
- パフォーマンス監視: 処理時間の定期的な測定
この時間軸対応機能により、Foxglove Studioでの詳細な時系列分析と効率的なデバッグワークフローを実現しています。