# 機能設計書 7-Timer

## 概要

本ドキュメントは、Three.jsライブラリにおける高精度時間計測機能を提供するTimerクラスの機能設計について記述する。TimerはClockクラスの代替として設計され、より堅牢なAPIと Page Visibility API への対応を提供する。

### 本機能の処理概要

Timerクラスは、アニメーションループで使用するデルタ時間と累積経過時間を計測する。Clockクラスの設計上の問題点（1フレームで複数回getDelta()を呼ぶと異なる値が返る、タブ非表示時の大きなデルタ値など）を解決するために設計された。

**業務上の目的・背景**：Clockクラスは長年使用されてきたが、API設計に起因するいくつかの問題が明らかになった。特に、getDelta()を複数回呼ぶと毎回異なる値が返る問題や、ブラウザのタブが非表示になった後に大きなデルタ値が発生する問題があった。Timerクラスはこれらの問題を解決し、より直感的で堅牢な時間計測APIを提供する。

**機能の利用シーン**：
- requestAnimationFrameを使用したアニメーションループ
- 物理シミュレーションの時間ステップ計算
- タイムスケール機能を使用したスローモーション/早送り
- タブ非表示時のアニメーション一時停止

**主要な処理内容**：
1. **時間更新**: update()による内部状態の更新
2. **デルタ時間取得**: getDelta()による前回update()からの経過時間取得（秒）
3. **経過時間取得**: getElapsed()による総経過時間取得（秒）
4. **タイムスケール**: setTimescale()によるデルタ時間のスケーリング
5. **Page Visibility対応**: connect()によるタブ非表示時の対応
6. **リセット**: reset()による時間計測のリセット

**関連システム・外部連携**：
- requestAnimationFrameコールバック（timestampを直接渡せる）
- Page Visibility API
- Document オブジェクト

**権限による制御**：特になし（ライブラリレベルの機能のため、アプリケーション側での権限管理は行わない）

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 21 | WebGL基本サンプル | 補助機能 | アニメーションループの時間計測 |

## 機能種別

時間管理 / ユーティリティ

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| document | Document | No | connect()で使用するDocumentオブジェクト | Page Visibility API対応用 |
| timescale | number | No | setTimescale()で設定するスケール値 | デフォルト: 1 |
| timestamp | number | No | update()に渡すタイムスタンプ（ミリ秒） | 省略時はperformance.now()使用 |

### 入力データソース

- コンストラクタ呼び出し
- update()メソッドの呼び出し（requestAnimationFrameのtimestamp）
- connect()メソッドの呼び出し（Document）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| getDelta戻り値 | number | update()間の経過時間（秒、timescale適用済み） |
| getElapsed戻り値 | number | 総経過時間（秒） |
| getTimescale戻り値 | number | 現在のタイムスケール値 |

### 出力先

- getDelta()、getElapsed()、getTimescale()の戻り値

## 処理フロー

### 処理シーケンス

```
1. Timerインスタンス生成
   └─ コンストラクタ()
       └─ _previousTime = 0
       └─ _currentTime = 0
       └─ _startTime = performance.now()
       └─ _delta = 0
       └─ _elapsed = 0
       └─ _timescale = 1
       └─ _document = null
       └─ _pageVisibilityHandler = null

2. Page Visibility APIへの接続（オプション）
   └─ connect(document)
       └─ _document = document
       └─ document.hidden !== undefined の場合
           └─ visibilitychangeイベントリスナー登録
           └─ _pageVisibilityHandler を設定

3. 時間更新（毎フレーム呼び出し）
   └─ update(timestamp)
       └─ ドキュメントが非表示の場合
           └─ _delta = 0
       └─ ドキュメントが表示中の場合
           └─ _previousTime = _currentTime
           └─ _currentTime = timestamp - _startTime
           └─ _delta = (_currentTime - _previousTime) * _timescale
           └─ _elapsed += _delta

4. 時間取得
   └─ getDelta()
       └─ return _delta / 1000 （ミリ秒→秒）
   └─ getElapsed()
       └─ return _elapsed / 1000 （ミリ秒→秒）

5. リセット
   └─ reset()
       └─ _currentTime = performance.now() - _startTime

6. 破棄
   └─ dispose()
       └─ disconnect() を呼び出し
```

### フローチャート

```mermaid
flowchart TD
    A[Timer生成] --> B[プロパティ初期化]
    B --> C{connect呼び出し?}
    C -->|Yes| D[Page Visibility登録]
    C -->|No| E[通常動作]
    D --> E
    E --> F{update呼び出し}
    F --> G{document.hidden?}
    G -->|Yes| H[_delta = 0]
    G -->|No| I[時間計算]
    H --> J[次のフレーム]
    I --> K[_delta = diff * timescale]
    K --> L[_elapsed += _delta]
    L --> J
    J --> F
    F -->|getDelta| M[return _delta / 1000]
    F -->|getElapsed| N[return _elapsed / 1000]
    F -->|dispose| O[disconnect]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | update分離 | getDelta/getElapsedは値を返すのみ、update()で状態更新 | 常時 |
| BR-02 | タブ非表示時ゼロデルタ | Page Visibility APIが有効でタブが非表示の場合、デルタは0 | connect()後 |
| BR-03 | 表示復帰時リセット | タブが再表示されるとreset()が呼ばれる | visibilitychange時 |
| BR-04 | タイムスケール適用 | デルタ時間にtimescaleが乗算される | update()時 |
| BR-05 | ミリ秒内部管理 | 内部はミリ秒、APIは秒で提供 | getDelta(), getElapsed() |

### 計算ロジック

**デルタ時間の計算:**
```javascript
this._previousTime = this._currentTime;
this._currentTime = (timestamp !== undefined ? timestamp : performance.now()) - this._startTime;
this._delta = (this._currentTime - this._previousTime) * this._timescale;
this._elapsed += this._delta;
```

**visibilitychange ハンドラ:**
```javascript
function handleVisibilityChange() {
    if (this._document.hidden === false) this.reset();
}
```

## データベース操作仕様

### 操作別データベース影響一覧

該当なし（Timerはデータベースを使用しない）

## エラー処理

### エラーケース一覧

該当なし（Timerは明示的なエラー処理を行わない）

### リトライ仕様

該当なし

## トランザクション仕様

該当なし（メモリ上のオブジェクト操作のみ）

## パフォーマンス要件

- update()は軽量な操作であり、毎フレーム呼び出しても問題ない
- getDelta()、getElapsed()は単純な除算のみで非常に軽量
- Page Visibility APIのイベントリスナーはタブ切り替え時のみ発火

## セキュリティ考慮事項

該当なし（クライアントサイドのグラフィックスライブラリ）

## 備考

- TimerはClockの代替として推奨される（r183以降）
- update()メソッドを毎フレーム呼び出すことで、getDelta()を何度呼んでも同じ値が返る
- requestAnimationFrameのtimestampを直接update()に渡すことで、より正確な時間計測が可能

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: クラス構造を理解する

Timerの基本構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | Timer.js | `src/core/Timer.js` | クラス全体の構造とプロパティ |

**読解のコツ**: Timerは7つのプライベートプロパティ（_previousTime、_currentTime、_startTime、_delta、_elapsed、_timescale、_document、_pageVisibilityHandler）を持つ。プレフィックス`_`はプライベートを示す慣習。

#### Step 2: コンストラクタを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | Timer.js | `src/core/Timer.js` | コンストラクタでのプロパティ初期化 |

**主要処理フロー**:
- **20-34行目**: コンストラクタ
- **22-24行目**: 時間関連プロパティの初期化
- **26-27行目**: _delta、_elapsedの初期化
- **29行目**: _timescaleの初期化（1）
- **31-32行目**: Page Visibility関連の初期化

#### Step 3: Page Visibility対応を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | Timer.js | `src/core/Timer.js` | connect()、disconnect()、handleVisibilityChange() |

**主要処理フロー**:
- **43-57行目**: connect() - Page Visibility APIへの接続
- **49-55行目**: visibilitychangeイベントリスナーの登録
- **62-73行目**: disconnect() - イベントリスナーの解除
- **178-182行目**: handleVisibilityChange() - 可視性変更時のリセット

#### Step 4: 時間更新・取得を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | Timer.js | `src/core/Timer.js` | update()、getDelta()、getElapsed() |

**主要処理フロー**:
- **156-174行目**: update() - 時間状態の更新
- **158-160行目**: 非表示時の処理（delta = 0）
- **164-168行目**: 通常時の時間計算
- **80-84行目**: getDelta() - デルタ時間の取得
- **91-95行目**: getElapsed() - 累積時間の取得

#### Step 5: その他のメソッドを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | Timer.js | `src/core/Timer.js` | setTimescale()、getTimescale()、reset()、dispose() |

**主要処理フロー**:
- **115-121行目**: setTimescale() - タイムスケールの設定
- **102-106行目**: getTimescale() - タイムスケールの取得
- **128-134行目**: reset() - 時間計測のリセット
- **140-144行目**: dispose() - リソース解放

### プログラム呼び出し階層図

```
Timer
    │
    ├─ constructor()
    │      ├─ _startTime = performance.now()
    │      ├─ _timescale = 1
    │      └─ その他プロパティ初期化
    │
    ├─ connect(document)
    │      ├─ _document = document
    │      └─ addEventListener('visibilitychange', handler)
    │
    ├─ disconnect()
    │      ├─ removeEventListener('visibilitychange', handler)
    │      └─ _document = null
    │
    ├─ update(timestamp)
    │      ├─ [if hidden] _delta = 0
    │      └─ [if visible]
    │             ├─ _previousTime = _currentTime
    │             ├─ _currentTime = timestamp - _startTime
    │             ├─ _delta = diff * _timescale
    │             └─ _elapsed += _delta
    │
    ├─ getDelta()
    │      └─ return _delta / 1000
    │
    ├─ getElapsed()
    │      └─ return _elapsed / 1000
    │
    ├─ setTimescale(timescale)
    │      └─ _timescale = timescale
    │
    ├─ reset()
    │      └─ _currentTime = performance.now() - _startTime
    │
    └─ dispose()
           └─ disconnect()

handleVisibilityChange() [外部関数]
    └─ if (hidden === false) reset()
```

### データフロー図

```
[入力]                      [処理]                        [出力]

                    ┌─────────────────────┐
                    │ constructor()       │
performance.now() ──→│ _startTime = now   │
                    │ _timescale = 1      │
                    └─────────────────────┘
                              │
                              ↓
                    ┌─────────────────────┐
Document ──────────→│ connect(document)   │
                    │ addEventListener    │
                    │ ('visibilitychange')│
                    └─────────────────────┘
                              │
                              ↓
           ┌─────────────────────────────────────┐
           │            update(timestamp)        │
           │                                     │
timestamp ─→│ [visible]                          │
           │   _currentTime = timestamp - _start │
           │   _delta = diff * _timescale        │
           │   _elapsed += _delta                │
           │                                     │
           │ [hidden]                            │
           │   _delta = 0                        │
           └─────────────────────────────────────┘
                              │
            ┌─────────────────┼─────────────────┐
            ↓                 ↓                 ↓
     getDelta()        getElapsed()      getTimescale()
            │                 │                 │
            ↓                 ↓                 ↓
    _delta / 1000    _elapsed / 1000     _timescale
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| Timer.js | `src/core/Timer.js` | ソース | Timerクラス本体 |
| Clock.js | `src/core/Clock.js` | ソース | 旧クラス（非推奨） |
