# 機能設計書 87-タイマー

## 概要

本ドキュメントは、lua-language-serverにおけるタイマー機能について記載する。この機能は、ミリ秒単位の高精度タイマーを提供し、遅延実行、定期実行、タイマー管理を可能にする。

### 本機能の処理概要

**業務上の目的・背景**：Language Serverでは、デバウンス処理（入力後の遅延診断）、定期的なバックグラウンド処理、タイムアウト制御など、時間ベースの処理が必要となる。本機能は、協調的マルチタスク環境で動作する高精度タイマーを提供し、非同期処理フレームワーク（await.lua）やプロトコル処理（proto.lua）と連携して時間制御を実現する。

**機能の利用シーン**：診断のデバウンス処理、メソッドキューの遅延処理、非同期sleep、定期的なGCトリガー、タイムアウト検出など、時間ベースの制御が必要なすべての場面で利用される。

**主要な処理内容**：
1. 単発タイマー（wait）- 指定時間後に1回だけ実行
2. ループタイマー（loop）- 指定間隔で繰り返し実行
3. 回数制限タイマー（timer）- 指定回数だけ繰り返し実行
4. タイマーの一時停止・再開（pause/resume）
5. タイマーの再スタート（restart）
6. タイマーの削除（remove）
7. フレームベースの時間管理（update）

**関連システム・外部連携**：bee.timeモジュールで高精度タイムスタンプを取得。非同期処理（await.lua）のsleep関数から呼び出される。

**権限による制御**：特になし。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | - | - | 本機能は画面を持たず、システム全体の時間制御基盤として動作 |

## 機能種別

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

## 入力仕様

### 入力パラメータ

#### timer.wait関数

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| timeout | number | Yes | タイムアウト時間（秒） | 正の数値 |
| onTimer | function | No | タイムアウト時に呼び出されるコールバック | 関数であること |

#### timer.loop関数

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| timeout | number | Yes | 実行間隔（秒） | 正の数値 |
| onTimer | function | No | 定期的に呼び出されるコールバック | 関数であること |

#### timer.timer関数

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| timeout | number | Yes | 実行間隔（秒） | 正の数値 |
| count | number | Yes | 実行回数（0で無限ループ） | 0以上の整数 |
| onTimer | function | No | コールバック関数 | 関数であること |

### 入力データソース

- 各モジュールからのタイマー登録リクエスト
- bee.time.monotonicからの現在時刻

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| timer | table | タイマーオブジェクト（操作用メソッドを持つ） |

### タイマーオブジェクトのメソッド

| メソッド名 | 説明 |
|-----------|------|
| timer:remove() | タイマーを削除 |
| timer:pause() | タイマーを一時停止 |
| timer:resume() | タイマーを再開 |
| timer:restart() | タイマーをリスタート |
| timer:remaining() | 残り時間を取得（秒） |
| timer:onTimer() | コールバックを手動実行 |

### 出力先

- コールバック関数の呼び出し
- タイマーオブジェクトの返却

## 処理フロー

### 処理シーケンス

```
タイマー登録処理:
1. タイムアウト時間をミリ秒に変換
   └─ _timeout = mathFloor(timeout * 1000.0)
2. タイマーオブジェクトを作成
   └─ setmetatable({...}, mt)
3. 発火フレームを計算
   └─ ti = tarFrame + _timeout
4. タイマーキューに登録
   └─ timer[ti][#timer[ti]+1] = self

update処理（毎フレーム）:
1. 現在時刻を取得
   └─ monotonic() + fwFrame
2. デルタ時間を計算
   └─ delta = currentClock - lastClock
3. maxFrameを更新
   └─ maxFrame = maxFrame + delta
4. curFrame < maxFrameの間ループ
   └─ curFrame = curFrame + 1; onTick()

onTick処理:
1. 該当フレームのキューを取得
   └─ q = timer[curFrame]
2. キュー内のタイマーを実行
   └─ mWakeup(callback)
3. キューをクリア
   └─ freeQueueに返却
```

### フローチャート

```mermaid
flowchart TD
    subgraph register["タイマー登録"]
        R1[timeout受け取り] --> R2[ミリ秒変換]
        R2 --> R3[タイマーオブジェクト作成]
        R3 --> R4[発火フレーム計算]
        R4 --> R5[キューに追加]
        R5 --> R6[タイマー返却]
    end

    subgraph update["update処理"]
        U1[monotonic取得] --> U2[delta計算]
        U2 --> U3[maxFrame更新]
        U3 --> U4{curFrame < maxFrame?}
        U4 -->|Yes| U5[curFrame++]
        U5 --> U6[onTick]
        U6 --> U4
        U4 -->|No| U7[完了]
    end

    subgraph onTick["onTick処理"]
        T1[キュー取得] --> T2{キューあり?}
        T2 -->|No| T3[リターン]
        T2 -->|Yes| T4[タイマー取り出し]
        T4 --> T5{有効?}
        T5 -->|No| T6[スキップ]
        T5 -->|Yes| T7[mWakeup呼び出し]
        T7 --> T8[onTimer実行]
        T8 --> T9{ループ?}
        T9 -->|Yes| T10[再登録]
        T9 -->|No| T11[_removed=true]
    end

    subgraph wakeup["mWakeup処理"]
        W1[_running=false] --> W2[onTimer呼び出し]
        W2 --> W3{_removed?}
        W3 -->|Yes| W4[終了]
        W3 -->|No| W5{_timerCount?}
        W5 -->|Yes| W6[カウントダウン]
        W6 --> W7{残り > 0?}
        W7 -->|Yes| W8[mTimeout再登録]
        W7 -->|No| W9[_removed=true]
        W5 -->|No| W10[mTimeout再登録]
    end
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-87-01 | ミリ秒管理 | 内部では時間をミリ秒単位で管理 | 全タイマー処理 |
| BR-87-02 | 最小タイムアウト | waitのタイムアウトは最低1ミリ秒 | timer.wait呼び出し時 |
| BR-87-03 | ループタイマー | count=0のタイマーは無限ループ | timer.timer呼び出し時 |
| BR-87-04 | pauseRemaining | pause時に残り時間を保持 | timer:pause呼び出し時 |
| BR-87-05 | キュー再利用 | 使用済みキューはfreeQueueで再利用 | onTick完了時 |
| BR-87-06 | xpcallでエラー捕捉 | タイマーコールバックのエラーはlog.errorで記録 | mWakeup実行時 |

### 計算ロジック

発火フレーム計算:
```lua
local ti = tarFrame + timeout  -- tarFrameは現在の目標フレーム
timer[ti][#timer[ti]+1] = self
```

残り時間計算:
```lua
function getRemaining(self)
    if self._removed then return 0 end
    if self._pauseRemaining then return self._pauseRemaining end
    if self._timeoutFrame == curFrame then return self._timeout or 0 end
    return self._timeoutFrame - curFrame
end
```

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

本機能はデータベース操作を行わない。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | コールバックエラー | onTimer関数内で例外発生 | xpcallでキャッチしlog.errorで記録 |

### リトライ仕様

リトライは行わない。エラー発生時はログに記録して処理を継続する。

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

本機能はトランザクションを使用しない。

## パフォーマンス要件

- update呼び出しは高頻度で行われるため、効率的に実装
- キューの再利用により、メモリアロケーションを最小化

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

特になし。

## 備考

本機能はフレームベースの時間管理を採用している。1フレーム=1ミリ秒として管理され、monotonicからの経過時間でフレームを進める。timeJump関数でデバッグ用の時間ジャンプも可能。

---

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

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

### 推奨読解順序

#### Step 1: データ構造を理解する

タイマーモジュールの状態変数とタイマーオブジェクトの構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | timer.lua | `script/timer.lua` | モジュール変数（11-18行目） |

**読解のコツ**:
- `curFrame`: 現在のフレーム番号
- `maxFrame`: 最大フレーム番号（monotonicベース）
- `tarFrame`: 目標フレーム番号
- `timer`: フレーム番号→タイマーリストのマッピング
- `freeQueue`: 再利用可能なキューのプール

#### Step 2: タイマーオブジェクトを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | timer.lua | `script/timer.lua` | タイマーメタテーブル（104-184行目） |

**主要処理フロー**:
- **104-111行目**: timerクラス定義（フィールド）
- **113-115行目**: __tostring
- **117-121行目**: __call - コールバック手動実行
- **123-126行目**: remove - タイマー削除
- **128-146行目**: pause - 一時停止
- **148-156行目**: resume - 再開
- **158-175行目**: restart - リスタート
- **177-179行目**: remaining - 残り時間取得

#### Step 3: タイマー登録関数を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | timer.lua | `script/timer.lua` | wait/loop/timer関数（185-218行目） |

**主要処理フロー**:
- **185-194行目**: wait - 単発タイマー（_timerCount=1）
- **196-204行目**: loop - ループタイマー（_timerCountなし）
- **206-218行目**: timer - 回数制限タイマー

#### Step 4: 内部処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | timer.lua | `script/timer.lua` | mTimeout, mWakeup, onTick（31-99行目） |

**主要処理フロー**:
- **31-44行目**: mTimeout - キューへの登録
- **46-67行目**: mWakeup - タイマー発火処理
- **82-99行目**: onTick - フレーム処理

#### Step 5: 更新処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | timer.lua | `script/timer.lua` | update, clock, timeJump（220-243行目） |

**主要処理フロー**:
- **220-222行目**: clock - 現在時刻取得（秒）
- **224-238行目**: update - フレーム更新
- **240-242行目**: timeJump - デバッグ用時間ジャンプ

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

```
timer.wait(timeout, onTimer)
    │
    ├─ mathFloor(timeout * 1000.0) → _timeout
    ├─ setmetatable({...}, mt) → タイマーオブジェクト
    └─ mTimeout(t, _timeout)
           ├─ ti = tarFrame + timeout
           ├─ timer[ti] = allocQueue()
           └─ timer[ti][#timer[ti]+1] = self

timer.update()
    │
    ├─ monotonic() + fwFrame → currentClock
    ├─ delta = currentClock - lastClock
    ├─ maxFrame = maxFrame + delta
    └─ while curFrame < maxFrame
           ├─ curFrame++
           └─ onTick()
                  ├─ q = timer[curFrame]
                  └─ mWakeup(callback)
                         ├─ self._running = false
                         ├─ xpcall(self._onTimer, logError, self)
                         └─ [loop] mTimeout(self, self._timeout)
```

### データフロー図

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

timer.wait(sec, cb)
    │
    └───▶ mTimeout ───▶ timer[frame]追加

monotonic() ───▶ update() ───▶ curFrame更新
                     │
                     └───▶ onTick() ───▶ mWakeup() ───▶ callback実行
                                               │
                                               └─ [loop] mTimeout再登録
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| timer.lua | `script/timer.lua` | ソース | タイマー機能の実装 |
| await.lua | `script/await.lua` | ソース | 非同期処理（sleepでtimerを使用） |
| proto.lua | `script/proto/proto.lua` | ソース | メソッドキューの遅延処理 |
| bee.time | `(外部モジュール)` | ライブラリ | 高精度タイムスタンプ |
