Skip to content

Commit

Permalink
[#1063] Clean up handlers (#1159)
Browse files Browse the repository at this point in the history
Closes #1063

## Changes:

- Implementation of handler cleanup
  • Loading branch information
eonarheim authored Jun 8, 2019
1 parent 85b28b8 commit 08e964f
Show file tree
Hide file tree
Showing 5 changed files with 192 additions and 6 deletions.
4 changes: 1 addition & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,9 @@ This project adheres to [Semantic Versioning](http://semver.org/).

### Fixed

- Fixed issue where leaking window/document handlers was possible when calling `ex.Engine.stop()` and `ex.Engine.start()`. ([#1063](https://github.com/excaliburjs/Excalibur/issues/1120))
- Fixed wrong `Camera` and `Loader` scaling on HiDPI screens when option `suppressHiDPIScaling` is set. ([#1120](https://github.com/excaliburjs/Excalibur/issues/1120))
- Fixed polyfill application by exporting a `polyfill()` function that can be called. ([#1132](https://github.com/excaliburjs/Excalibur/issues/1132))

<!--- Bug fixes here --->

- Fixed Color.lighten() ([#1084])

<!--------------------------------- DO NOT EDIT BELOW THIS LINE --------------------------------->
Expand Down
16 changes: 13 additions & 3 deletions src/engine/Engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import * as Input from './Input/Index';
import * as Util from './Util/Util';
import * as Events from './Events';
import { BoundingBox } from './Collision/BoundingBox';
import { BrowserEvents } from './Util/Browser';

/**
* Enum representing the different display modes available to Excalibur
Expand Down Expand Up @@ -183,6 +184,11 @@ export interface EngineOptions {
* [[include:Engine.md]]
*/
export class Engine extends Class implements CanInitialize, CanUpdate, CanDraw {
/**
*
*/
public browser: BrowserEvents;

/**
* Direct access to the engine's canvas element
*/
Expand Down Expand Up @@ -488,6 +494,9 @@ export class Engine extends Class implements CanInitialize, CanUpdate, CanDraw {

options = Util.extend({}, Engine._DefaultEngineOptions, options);

// Initialize browser events facade
this.browser = new BrowserEvents(window, document);

// Check compatibility
const detector = new Detector();
if (!options.suppressMinimumBrowserFeatureDetection && !(this._compatible = detector.test())) {
Expand Down Expand Up @@ -952,7 +961,7 @@ O|===|* >________________>\n\

this._setHeightByDisplayMode(parent);

window.addEventListener('resize', () => {
this.browser.window.on('resize', () => {
this._logger.debug('View port resized');
this._setHeightByDisplayMode(parent);
this._logger.info('parent.clientHeight ' + parent.clientHeight);
Expand Down Expand Up @@ -990,7 +999,7 @@ O|===|* >________________>\n\
visibilityChange = 'webkitvisibilitychange';
}

document.addEventListener(visibilityChange, () => {
this.browser.document.on(visibilityChange, () => {
if (document[hidden]) {
this.eventDispatcher.emit('hidden', new HiddenEvent(this));
this._logger.debug('Window hidden');
Expand Down Expand Up @@ -1309,7 +1318,7 @@ O|===|* >________________>\n\
if (!this._hasStarted) {
this._hasStarted = true;
this._logger.debug('Starting game...');

this.browser.resume();
Engine.createMainLoop(this, window.requestAnimationFrame, Date.now)();

this._logger.debug('Game started');
Expand Down Expand Up @@ -1376,6 +1385,7 @@ O|===|* >________________>\n\
public stop() {
if (this._hasStarted) {
this.emit('stop', new GameStopEvent(this));
this.browser.pause();
this._hasStarted = false;
this._logger.debug('Game stopped');
}
Expand Down
80 changes: 80 additions & 0 deletions src/engine/Util/Browser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
export interface NativeEventable {
addEventListener(name: string, handler: (...any: any[]) => any): any;
removeEventListener(name: string, handler: (...any: any[]) => any): any;
}

export class BrowserComponent<T extends NativeEventable> {
private _paused = false;
private _nativeHandlers: { [key: string]: (handler: any) => void } = {};

on(eventName: string, handler: (evt: any) => void): void {
if (this._nativeHandlers[eventName]) {
this.off(eventName, this._nativeHandlers[eventName]);
}
this._nativeHandlers[eventName] = this._decorate(handler);
this.nativeComponet.addEventListener(eventName, this._nativeHandlers[eventName]);
}
off(eventName: string, handler?: (event: any) => void): void {
if (!handler) {
handler = this._nativeHandlers[eventName];
}
this.nativeComponet.removeEventListener(eventName, handler);
this._nativeHandlers[eventName] = null;
}

private _decorate(handler: (evt: any) => void): (evt: any) => void {
return (evt: any) => {
if (!this._paused) {
handler(evt);
}
};
}

public pause() {
this._paused = true;
}

public resume() {
this._paused = false;
}

public clear() {
for (const event in this._nativeHandlers) {
this.off(event);
}
}

constructor(public nativeComponet: T) {}
}

export class BrowserEvents {
private _windowComponent: BrowserComponent<Window>;
private _documentComponent: BrowserComponent<Document>;
constructor(private _windowGlobal: Window, private _documentGlobal: Document) {
this._windowComponent = new BrowserComponent(this._windowGlobal);
this._documentComponent = new BrowserComponent(this._documentGlobal);
}

public get window(): BrowserComponent<Window> {
return this._windowComponent;
}

public get document(): BrowserComponent<Document> {
return this._documentComponent;
}

public pause() {
this.window.pause();
this.document.pause();
}

public resume() {
this.window.resume();
this.document.resume();
}

public clear() {
this.window.clear();
this.document.clear();
}
}
1 change: 1 addition & 0 deletions src/engine/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export { traits as Traits };
import * as util from './Util/Index';
export { util as Util };

export * from './Util/Browser';
export * from './Util/Decorators';
export * from './Util/Detector';
export * from './Util/CullingBox';
Expand Down
97 changes: 97 additions & 0 deletions src/spec/BrowserEventsSpec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import * as ex from '../../build/dist/excalibur';

describe('The BrowserEvents facade', () => {
let browser: ex.BrowserEvents;
beforeEach(() => {
browser = new ex.BrowserEvents(window, document);
});

afterEach(() => {
browser.clear();
});

it('should exist', () => {
expect(ex.BrowserEvents).toBeDefined();
});

it('can be created', () => {
expect(browser).toBeDefined();
});

it('can register handlers on window', (done) => {
browser.window.on('someevent', () => {
done();
});

window.dispatchEvent(new Event('someevent'));
});

it('can pause handlers on window', () => {
browser.window.on('somewindowevent', () => {
fail();
});

browser.pause();
window.dispatchEvent(new Event('somewindowevent'));
});

it('can register handlers on document', (done) => {
browser.document.on('someevent', () => {
done();
});

document.dispatchEvent(new Event('someevent'));
});

it('can pause handlers on document', () => {
browser.document.on('somedocumentevent', () => {
fail();
});

browser.pause();
window.dispatchEvent(new Event('somedocumentevent'));
});

it('can clear handlers on window', () => {
browser.window.on('somewindowevent2', () => {
fail();
});

browser.clear();
window.dispatchEvent(new Event('somewindowevent2'));
});

it('can clear handlers on window', () => {
browser.document.on('somedocevent2', () => {
fail();
});

browser.clear();
document.dispatchEvent(new Event('somedocevent2'));
});

it('can only have 1 handler per event name at a time', (done) => {
browser.window.on('couldfailevent', () => {
fail();
});

browser.window.on('couldfailevent', () => {
done();
});

window.dispatchEvent(new Event('couldfailevent'));
});

it('can resume handlers', () => {
const spy = jasmine.createSpy();
browser.document.on('somedocumentevent', spy);

browser.pause();
document.dispatchEvent(new Event('somedocumentevent'));

browser.resume();
document.dispatchEvent(new Event('somedocumentevent'));

expect(spy).toHaveBeenCalledTimes(1);
});
});

0 comments on commit 08e964f

Please sign in to comment.