353 lines
7.5 KiB
Markdown
353 lines
7.5 KiB
Markdown
|
|
# Timer System
|
||
|
|
|
||
|
|
`@esengine/timer` provides a flexible timer and cooldown system for delayed execution, repeating tasks, skill cooldowns, and more.
|
||
|
|
|
||
|
|
## Installation
|
||
|
|
|
||
|
|
```bash
|
||
|
|
npm install @esengine/timer
|
||
|
|
```
|
||
|
|
|
||
|
|
## Quick Start
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
import { createTimerService } from '@esengine/timer';
|
||
|
|
|
||
|
|
// Create timer service
|
||
|
|
const timerService = createTimerService();
|
||
|
|
|
||
|
|
// One-time timer (executes after 1 second)
|
||
|
|
const handle = timerService.schedule('myTimer', 1000, () => {
|
||
|
|
console.log('Timer fired!');
|
||
|
|
});
|
||
|
|
|
||
|
|
// Repeating timer (every 100ms)
|
||
|
|
timerService.scheduleRepeating('heartbeat', 100, () => {
|
||
|
|
console.log('Tick');
|
||
|
|
});
|
||
|
|
|
||
|
|
// Cooldown system (5 second cooldown)
|
||
|
|
timerService.startCooldown('skill_fireball', 5000);
|
||
|
|
|
||
|
|
if (timerService.isCooldownReady('skill_fireball')) {
|
||
|
|
useFireball();
|
||
|
|
timerService.startCooldown('skill_fireball', 5000);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Update in game loop
|
||
|
|
function gameLoop(deltaTime: number) {
|
||
|
|
timerService.update(deltaTime);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## Core Concepts
|
||
|
|
|
||
|
|
### Timer vs Cooldown
|
||
|
|
|
||
|
|
| Feature | Timer | Cooldown |
|
||
|
|
|---------|-------|----------|
|
||
|
|
| Purpose | Delayed code execution | Rate limiting |
|
||
|
|
| Callback | Has callback function | No callback |
|
||
|
|
| Repeat | Supports repeating | One-time |
|
||
|
|
| Query | Query remaining time | Query progress/ready status |
|
||
|
|
|
||
|
|
### TimerHandle
|
||
|
|
|
||
|
|
Handle object returned when scheduling a timer:
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
interface TimerHandle {
|
||
|
|
readonly id: string; // Timer ID
|
||
|
|
readonly isValid: boolean; // Whether valid (not cancelled)
|
||
|
|
cancel(): void; // Cancel timer
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### TimerInfo
|
||
|
|
|
||
|
|
Timer information object:
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
interface TimerInfo {
|
||
|
|
readonly id: string; // Timer ID
|
||
|
|
readonly remaining: number; // Remaining time (ms)
|
||
|
|
readonly repeating: boolean; // Whether repeating
|
||
|
|
readonly interval?: number; // Interval (repeating only)
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### CooldownInfo
|
||
|
|
|
||
|
|
Cooldown information object:
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
interface CooldownInfo {
|
||
|
|
readonly id: string; // Cooldown ID
|
||
|
|
readonly duration: number; // Total duration (ms)
|
||
|
|
readonly remaining: number; // Remaining time (ms)
|
||
|
|
readonly progress: number; // Progress (0-1, 0=started, 1=finished)
|
||
|
|
readonly isReady: boolean; // Whether ready
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## API Reference
|
||
|
|
|
||
|
|
### createTimerService
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
function createTimerService(config?: TimerServiceConfig): ITimerService
|
||
|
|
```
|
||
|
|
|
||
|
|
**Configuration:**
|
||
|
|
|
||
|
|
| Property | Type | Default | Description |
|
||
|
|
|----------|------|---------|-------------|
|
||
|
|
| `maxTimers` | `number` | `0` | Maximum timer count (0 = unlimited) |
|
||
|
|
| `maxCooldowns` | `number` | `0` | Maximum cooldown count (0 = unlimited) |
|
||
|
|
|
||
|
|
### Timer API
|
||
|
|
|
||
|
|
#### schedule
|
||
|
|
|
||
|
|
Schedule a one-time timer:
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
const handle = timerService.schedule('explosion', 2000, () => {
|
||
|
|
createExplosion();
|
||
|
|
});
|
||
|
|
|
||
|
|
// Cancel early
|
||
|
|
handle.cancel();
|
||
|
|
```
|
||
|
|
|
||
|
|
#### scheduleRepeating
|
||
|
|
|
||
|
|
Schedule a repeating timer:
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// Execute every second
|
||
|
|
timerService.scheduleRepeating('regen', 1000, () => {
|
||
|
|
player.hp += 5;
|
||
|
|
});
|
||
|
|
|
||
|
|
// Execute immediately once, then repeat every second
|
||
|
|
timerService.scheduleRepeating('tick', 1000, () => {
|
||
|
|
console.log('Tick');
|
||
|
|
}, true); // immediate = true
|
||
|
|
```
|
||
|
|
|
||
|
|
#### cancel / cancelById
|
||
|
|
|
||
|
|
Cancel timers:
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// Cancel by handle
|
||
|
|
handle.cancel();
|
||
|
|
// or
|
||
|
|
timerService.cancel(handle);
|
||
|
|
|
||
|
|
// Cancel by ID
|
||
|
|
timerService.cancelById('regen');
|
||
|
|
```
|
||
|
|
|
||
|
|
#### hasTimer
|
||
|
|
|
||
|
|
Check if timer exists:
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
if (timerService.hasTimer('explosion')) {
|
||
|
|
console.log('Explosion is pending');
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
#### getTimerInfo
|
||
|
|
|
||
|
|
Get timer information:
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
const info = timerService.getTimerInfo('explosion');
|
||
|
|
if (info) {
|
||
|
|
console.log(`Remaining: ${info.remaining}ms`);
|
||
|
|
console.log(`Repeating: ${info.repeating}`);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Cooldown API
|
||
|
|
|
||
|
|
#### startCooldown
|
||
|
|
|
||
|
|
Start a cooldown:
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
timerService.startCooldown('skill_fireball', 5000);
|
||
|
|
```
|
||
|
|
|
||
|
|
#### isCooldownReady / isOnCooldown
|
||
|
|
|
||
|
|
Check cooldown status:
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
if (timerService.isCooldownReady('skill_fireball')) {
|
||
|
|
castFireball();
|
||
|
|
timerService.startCooldown('skill_fireball', 5000);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (timerService.isOnCooldown('skill_fireball')) {
|
||
|
|
console.log('On cooldown...');
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
#### getCooldownProgress / getCooldownRemaining
|
||
|
|
|
||
|
|
Get cooldown progress:
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// Progress 0-1 (0=started, 1=complete)
|
||
|
|
const progress = timerService.getCooldownProgress('skill_fireball');
|
||
|
|
console.log(`Progress: ${(progress * 100).toFixed(0)}%`);
|
||
|
|
|
||
|
|
// Remaining time (ms)
|
||
|
|
const remaining = timerService.getCooldownRemaining('skill_fireball');
|
||
|
|
console.log(`Remaining: ${(remaining / 1000).toFixed(1)}s`);
|
||
|
|
```
|
||
|
|
|
||
|
|
#### getCooldownInfo
|
||
|
|
|
||
|
|
Get complete cooldown info:
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
const info = timerService.getCooldownInfo('skill_fireball');
|
||
|
|
if (info) {
|
||
|
|
console.log(`Duration: ${info.duration}ms`);
|
||
|
|
console.log(`Remaining: ${info.remaining}ms`);
|
||
|
|
console.log(`Progress: ${info.progress}`);
|
||
|
|
console.log(`Ready: ${info.isReady}`);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
#### resetCooldown / clearAllCooldowns
|
||
|
|
|
||
|
|
Reset cooldowns:
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// Reset single cooldown
|
||
|
|
timerService.resetCooldown('skill_fireball');
|
||
|
|
|
||
|
|
// Clear all cooldowns (e.g., on respawn)
|
||
|
|
timerService.clearAllCooldowns();
|
||
|
|
```
|
||
|
|
|
||
|
|
### Lifecycle
|
||
|
|
|
||
|
|
#### update
|
||
|
|
|
||
|
|
Update timer service (call every frame):
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
function gameLoop(deltaTime: number) {
|
||
|
|
timerService.update(deltaTime); // deltaTime in ms
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
#### clear
|
||
|
|
|
||
|
|
Clear all timers and cooldowns:
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
timerService.clear();
|
||
|
|
```
|
||
|
|
|
||
|
|
### Debug Properties
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
console.log(timerService.activeTimerCount);
|
||
|
|
console.log(timerService.activeCooldownCount);
|
||
|
|
const timerIds = timerService.getActiveTimerIds();
|
||
|
|
const cooldownIds = timerService.getActiveCooldownIds();
|
||
|
|
```
|
||
|
|
|
||
|
|
## Practical Examples
|
||
|
|
|
||
|
|
### Skill Cooldown System
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
import { createTimerService, type ITimerService } from '@esengine/timer';
|
||
|
|
|
||
|
|
class SkillSystem {
|
||
|
|
private timerService: ITimerService;
|
||
|
|
private skills: Map<string, SkillData> = new Map();
|
||
|
|
|
||
|
|
constructor() {
|
||
|
|
this.timerService = createTimerService();
|
||
|
|
}
|
||
|
|
|
||
|
|
useSkill(skillId: string): boolean {
|
||
|
|
const skill = this.skills.get(skillId);
|
||
|
|
if (!skill) return false;
|
||
|
|
|
||
|
|
if (!this.timerService.isCooldownReady(skillId)) {
|
||
|
|
const remaining = this.timerService.getCooldownRemaining(skillId);
|
||
|
|
console.log(`Skill ${skillId} on cooldown, ${remaining}ms remaining`);
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
this.executeSkill(skill);
|
||
|
|
this.timerService.startCooldown(skillId, skill.cooldown);
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
update(dt: number): void {
|
||
|
|
this.timerService.update(dt);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### DOT Effects
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
class EffectSystem {
|
||
|
|
private timerService: ITimerService;
|
||
|
|
|
||
|
|
applyDOT(target: Entity, damage: number, duration: number): void {
|
||
|
|
const dotId = `dot_${target.id}_${Date.now()}`;
|
||
|
|
let elapsed = 0;
|
||
|
|
|
||
|
|
this.timerService.scheduleRepeating(dotId, 1000, () => {
|
||
|
|
elapsed += 1000;
|
||
|
|
target.takeDamage(damage);
|
||
|
|
|
||
|
|
if (elapsed >= duration) {
|
||
|
|
this.timerService.cancelById(dotId);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## Blueprint Nodes
|
||
|
|
|
||
|
|
### Cooldown Nodes
|
||
|
|
|
||
|
|
- `StartCooldown` - Start cooldown
|
||
|
|
- `IsCooldownReady` - Check if cooldown is ready
|
||
|
|
- `GetCooldownProgress` - Get cooldown progress
|
||
|
|
- `GetCooldownInfo` - Get cooldown info
|
||
|
|
- `ResetCooldown` - Reset cooldown
|
||
|
|
|
||
|
|
### Timer Nodes
|
||
|
|
|
||
|
|
- `HasTimer` - Check if timer exists
|
||
|
|
- `CancelTimer` - Cancel timer
|
||
|
|
- `GetTimerRemaining` - Get timer remaining time
|
||
|
|
|
||
|
|
## Service Token
|
||
|
|
|
||
|
|
For dependency injection:
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
import { TimerServiceToken, createTimerService } from '@esengine/timer';
|
||
|
|
|
||
|
|
services.register(TimerServiceToken, createTimerService());
|
||
|
|
const timerService = services.get(TimerServiceToken);
|
||
|
|
```
|