Skip to content

Commit 0f899e4

Browse files
authored
feat: [#3596] Add Timer events support (#3616)
closes: #3596 ## Changes: - Added new Timer events! ```typescript const timer = new ex.Timer({...}); timer.events.on('complete', () => {...}); // after the last repeat timer.events.on('action', () => {...}); // every fire of the timer timer.events.on('start', () => {...}); // after the timer is started timer.events.on('stop', () => {...}); // after the timer is stopped timer.events.on('pause', () => {...}); // after every pause timer.events.on('resume', () => {...}); // after every resume timer.events.on('cancel', () => {...}); // after cancel // or specify the onComplete in the constructor const timer2 = new ex.Timer({ onComplete: () => {...}, ... }); ```
1 parent 3ba41a7 commit 0f899e4

File tree

3 files changed

+243
-0
lines changed

3 files changed

+243
-0
lines changed

CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,23 @@ This project adheres to [Semantic Versioning](http://semver.org/).
1717
### Added
1818

1919

20+
- Added new Timer events!
21+
```typescript
22+
const timer = new ex.Timer({...});
23+
timer.events.on('complete', () => {...}); // after the last repeat
24+
timer.events.on('action', () => {...}); // every fire of the timer
25+
timer.events.on('start', () => {...}); // after the timer is started
26+
timer.events.on('stop', () => {...}); // after the timer is stopped
27+
timer.events.on('pause', () => {...}); // after every pause
28+
timer.events.on('resume', () => {...}); // after every resume
29+
timer.events.on('cancel', () => {...}); // after cancel
30+
31+
// or specify the onComplete in the constructor
32+
const timer2 = new ex.Timer({
33+
onComplete: () => {...},
34+
...
35+
});
36+
```
2037
- Added a way to configure general debug settings on text
2138
```typescript
2239
class DebugConfig {

src/engine/Timer.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,32 @@ import type { Scene } from './Scene';
22
import { Logger } from './Util/Log';
33
import type * as ex from './index';
44
import { Random } from './Math/Random';
5+
import { EventEmitter } from './EventEmitter';
6+
7+
/**
8+
* Built in events supported by all entities
9+
*/
10+
export interface TimerEvents {
11+
start: void;
12+
stop: void;
13+
pause: void;
14+
resume: void;
15+
cancel: void;
16+
17+
action: void;
18+
complete: void;
19+
}
20+
21+
export const TimerEvents = {
22+
Start: 'start',
23+
Stop: 'stop',
24+
Pause: 'pause',
25+
Resume: 'resume',
26+
Cancel: 'cancel',
27+
28+
Action: 'action',
29+
Complete: 'complete'
30+
} as const;
531

632
export interface TimerOptions {
733
/**
@@ -32,6 +58,10 @@ export interface TimerOptions {
3258
* Optionally provide a random instance to use for random behavior, otherwise a new random will be created seeded from the current time.
3359
*/
3460
random?: ex.Random;
61+
/**
62+
* Optionally provide a callback to fire once when the timer completes its last action callback.
63+
*/
64+
onComplete?: () => void;
3565
}
3666

3767
/**
@@ -42,6 +72,7 @@ export class Timer {
4272
private _logger = Logger.getInstance();
4373
private static _MAX_ID: number = 0;
4474
public id: number = 0;
75+
public events = new EventEmitter<TimerEvents>();
4576

4677
private _elapsedTime: number = 0;
4778
private _totalTimeAlive: number = 0;
@@ -61,6 +92,8 @@ export class Timer {
6192
return this._baseInterval + this.random.integer(this.randomRange[0], this.randomRange[1]);
6293
};
6394

95+
// eslint-disable-next-line @typescript-eslint/no-empty-function
96+
private _onComplete: () => void = () => {};
6497
private _complete = false;
6598
public get complete() {
6699
return this._complete;
@@ -75,6 +108,7 @@ export class Timer {
75108
const numberOfRepeats = options.numberOfRepeats;
76109
const randomRange = options.randomRange;
77110
const random = options.random;
111+
this._onComplete = options.onComplete ?? this._onComplete;
78112

79113
if (!!numberOfRepeats && numberOfRepeats >= 0) {
80114
this.maxNumberOfRepeats = numberOfRepeats;
@@ -134,19 +168,24 @@ export class Timer {
134168
this._complete = true;
135169
this._running = false;
136170
this._elapsedTime = 0;
171+
this._onComplete();
172+
this.events.emit('complete');
137173
}
138174

139175
if (!this.complete && this._elapsedTime >= this.interval) {
140176
this._callbacks.forEach((c) => {
141177
c.call(this);
142178
});
143179
this._numberOfTicks++;
180+
this.events.emit('action');
144181
if (this.repeats) {
145182
this._elapsedTime = 0;
146183
} else {
147184
this._complete = true;
148185
this._running = false;
149186
this._elapsedTime = 0;
187+
this._onComplete();
188+
this.events.emit('complete');
150189
}
151190
}
152191
}
@@ -210,6 +249,7 @@ export class Timer {
210249
*/
211250
public pause(): Timer {
212251
this._running = false;
252+
this.events.emit('pause');
213253
return this;
214254
}
215255

@@ -218,6 +258,7 @@ export class Timer {
218258
*/
219259
public resume(): Timer {
220260
this._running = true;
261+
this.events.emit('resume');
221262
return this;
222263
}
223264

@@ -234,6 +275,8 @@ export class Timer {
234275
this._complete = false;
235276
this._elapsedTime = 0;
236277
this._numberOfTicks = 0;
278+
} else {
279+
this.events.emit('start');
237280
}
238281

239282
return this;
@@ -246,6 +289,7 @@ export class Timer {
246289
this._running = false;
247290
this._elapsedTime = 0;
248291
this._numberOfTicks = 0;
292+
this.events.emit('stop');
249293
return this;
250294
}
251295

@@ -256,6 +300,7 @@ export class Timer {
256300
this.pause();
257301
if (this.scene) {
258302
this.scene.cancelTimer(this);
303+
this.events.emit('cancel');
259304
}
260305
}
261306
}

src/spec/vitest/TimerSpec.ts

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -595,4 +595,185 @@ describe('A Timer', () => {
595595
expect(timer.interval).toBe(905);
596596
timer.update(905);
597597
});
598+
599+
describe('events', () => {
600+
it("fires 'action' event for every timer tick", () => {
601+
const actionSpy = vi.fn();
602+
const actionEventSpy = vi.fn();
603+
604+
const sut = new ex.Timer({
605+
interval: 42,
606+
repeats: true,
607+
numberOfRepeats: 2,
608+
action: actionSpy
609+
});
610+
sut.events.on('action', actionEventSpy);
611+
612+
scene.add(sut);
613+
614+
expect(actionSpy).not.toHaveBeenCalled();
615+
expect(actionEventSpy).not.toHaveBeenCalled();
616+
sut.start();
617+
expect(actionSpy).not.toHaveBeenCalled();
618+
expect(actionEventSpy).not.toHaveBeenCalledOnce();
619+
620+
scene.update(engine, 43);
621+
scene.update(engine, 43);
622+
scene.update(engine, 43);
623+
scene.update(engine, 43);
624+
625+
expect(actionSpy).toHaveBeenCalledTimes(2);
626+
expect(actionEventSpy).toHaveBeenCalledTimes(2);
627+
});
628+
629+
it("fires 'complete' event when the timer has fired its last action", () => {
630+
const completeSpy = vi.fn();
631+
const completeEventSpy = vi.fn();
632+
633+
const sut = new ex.Timer({
634+
interval: 42,
635+
repeats: true,
636+
numberOfRepeats: 2,
637+
onComplete: completeSpy
638+
});
639+
sut.events.on('complete', completeEventSpy);
640+
641+
scene.add(sut);
642+
643+
expect(completeSpy).not.toHaveBeenCalled();
644+
expect(completeEventSpy).not.toHaveBeenCalled();
645+
sut.start();
646+
expect(completeSpy).not.toHaveBeenCalled();
647+
expect(completeEventSpy).not.toHaveBeenCalledOnce();
648+
649+
scene.update(engine, 43);
650+
scene.update(engine, 43);
651+
scene.update(engine, 43);
652+
scene.update(engine, 43);
653+
654+
expect(completeSpy).toHaveBeenCalledTimes(1);
655+
expect(completeEventSpy).toHaveBeenCalledTimes(1);
656+
});
657+
658+
it("fires 'start' event when started", () => {
659+
const startSpy = vi.fn();
660+
661+
const sut = new ex.Timer({
662+
interval: 42
663+
});
664+
sut.events.on('start', startSpy);
665+
666+
scene.add(sut);
667+
668+
expect(startSpy).not.toHaveBeenCalled();
669+
sut.start();
670+
expect(startSpy).toHaveBeenCalledOnce();
671+
});
672+
673+
it("fires 'pause' event when paused", () => {
674+
const pauseSpy = vi.fn();
675+
676+
const sut = new ex.Timer({
677+
interval: 42
678+
});
679+
sut.events.on('pause', pauseSpy);
680+
681+
scene.add(sut);
682+
683+
expect(pauseSpy).not.toHaveBeenCalled();
684+
sut.start();
685+
expect(pauseSpy).not.toHaveBeenCalled();
686+
687+
scene.update(engine, 40);
688+
689+
sut.pause();
690+
expect(pauseSpy).toHaveBeenCalledOnce();
691+
});
692+
693+
it("fires 'resume' event when resumed", () => {
694+
const resumeSpy = vi.fn();
695+
696+
const sut = new ex.Timer({
697+
interval: 42
698+
});
699+
sut.events.on('resume', resumeSpy);
700+
701+
scene.add(sut);
702+
703+
expect(resumeSpy).not.toHaveBeenCalled();
704+
sut.start();
705+
expect(resumeSpy).not.toHaveBeenCalled();
706+
707+
scene.update(engine, 40);
708+
709+
sut.pause();
710+
expect(resumeSpy).not.toHaveBeenCalledOnce();
711+
712+
scene.update(engine, 40);
713+
sut.resume();
714+
715+
expect(resumeSpy).toHaveBeenCalledOnce();
716+
});
717+
718+
it("fires 'stop' event when stopped", () => {
719+
const stopSpy = vi.fn();
720+
721+
const sut = new ex.Timer({
722+
interval: 42
723+
});
724+
sut.events.on('stop', stopSpy);
725+
726+
scene.add(sut);
727+
728+
expect(stopSpy).not.toHaveBeenCalled();
729+
sut.start();
730+
expect(stopSpy).not.toHaveBeenCalled();
731+
732+
scene.update(engine, 40);
733+
734+
sut.pause();
735+
expect(stopSpy).not.toHaveBeenCalledOnce();
736+
737+
scene.update(engine, 40);
738+
sut.resume();
739+
740+
expect(stopSpy).not.toHaveBeenCalledOnce();
741+
742+
scene.update(engine, 40);
743+
sut.stop();
744+
745+
expect(stopSpy).toHaveBeenCalledOnce();
746+
});
747+
748+
it("fires 'cancel' event when cancelled", () => {
749+
const cancelSpy = vi.fn();
750+
751+
const sut = new ex.Timer({
752+
interval: 42
753+
});
754+
sut.events.on('cancel', cancelSpy);
755+
756+
scene.add(sut);
757+
758+
expect(cancelSpy).not.toHaveBeenCalled();
759+
sut.start();
760+
expect(cancelSpy).not.toHaveBeenCalled();
761+
762+
scene.update(engine, 40);
763+
764+
sut.pause();
765+
expect(cancelSpy).not.toHaveBeenCalledOnce();
766+
767+
scene.update(engine, 40);
768+
sut.resume();
769+
770+
expect(cancelSpy).not.toHaveBeenCalledOnce();
771+
772+
scene.update(engine, 40);
773+
sut.stop();
774+
sut.cancel();
775+
776+
expect(cancelSpy).toHaveBeenCalledOnce();
777+
});
778+
});
598779
});

0 commit comments

Comments
 (0)