Skip to content

Commit

Permalink
[#582] Make overriding lifecycle and event hooks safe and typesafe (#935
Browse files Browse the repository at this point in the history
)

Closes #582 

## Changes:

- Added lifecycle interfaces to describe flows
- Refactor types so `onEventType` is always safe to override
- Added tests
  • Loading branch information
eonarheim authored Mar 31, 2018
1 parent 98ddb29 commit 2e98e60
Show file tree
Hide file tree
Showing 18 changed files with 1,193 additions and 63 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).

## Added

- New typesafe and override safe event lifecycle overriding, all `onEventName` handlers will no longer be dangerous to override ([#582](https://github.com/excaliburjs/Excalibur/issues/582))
* New lifecycle event `onPreKill` and `onPostKill`
- SpriteSheets can now produce animations from bespoke sprite coordinates `SpriteSheet.getAnimationByCoords(engine, coords[], speed)` ([#918](https://github.com/excaliburjs/Excalibur/issues/918))
- New Event `enter`
- New Event `leave`
Expand All @@ -33,6 +35,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).

## Fixed

- Added missing lifecycle event handlers on Actors, Triggers, Scenes, Engine, and Camera ([#582](https://github.com/excaliburjs/Excalibur/issues/582))

<!--------------------------------- DO NOT EDIT BELOW THIS LINE --------------------------------->

## [0.15.0] - 2018-02-16
Expand Down
4 changes: 1 addition & 3 deletions sandbox/tests/anchors/anchors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,9 @@ class Cross extends ex.Actor {
constructor(x, y) {
super(x, y, 40, 40);

this.on('predraw', this.onPreDraw);
}

onPreDraw(ev: ex.PreDrawEvent) {
var ctx = ev.ctx;
onPreDraw(ctx: CanvasRenderingContext2D, delta: number) {

ctx.beginPath();
ctx.lineTo(this.getWidth() / 2, 0);
Expand Down
217 changes: 203 additions & 14 deletions src/engine/Actor.ts

Large diffs are not rendered by default.

96 changes: 95 additions & 1 deletion src/engine/Camera.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import { IPromise, Promise, PromiseState } from './Promises';
import { Vector } from './Algebra';
import { Actor } from './Actor';
import { removeItemFromArray } from './Util/Util';
import { ICanUpdate, ICanInitialize } from './Interfaces/LifecycleEvents';
import { PreUpdateEvent, PostUpdateEvent, GameEvent, InitializeEvent } from './Events';
import { Class } from './Class';

/**
* Interface that describes a custom camera strategy for tracking targets
Expand Down Expand Up @@ -180,7 +183,7 @@ export class RadiusAroundActorStrategy implements ICameraStrategy<Actor> {
*
* [[include:Cameras.md]]
*/
export class BaseCamera {
export class BaseCamera extends Class implements ICanUpdate, ICanInitialize {
protected _follow: Actor;

private _cameraStrategies: ICameraStrategy<any>[] = [];
Expand Down Expand Up @@ -224,6 +227,7 @@ export class BaseCamera {
private _zoomPromise: Promise<boolean>;
private _zoomIncrement: number = 0.01;
private _easing: EasingFunction = EasingFunctions.EaseInOutCubic;


/**
* Get the camera's x position
Expand Down Expand Up @@ -396,7 +400,95 @@ export class BaseCamera {
this._cameraStrategies.length = 0;
}

/**
* It is not recommended that internal excalibur methods be overriden, do so at your own risk.
*
* Internal _preupdate handler for [[onPreUpdate]] lifecycle event
* @internal
*/
public _preupdate(engine: Engine, delta: number): void {
this.emit('preupdate', new PreUpdateEvent(engine, delta, this));
this.onPreUpdate(engine, delta);
}


/**
* Safe to override onPreUpdate lifecycle event handler. Synonymous with `.on('preupdate', (evt) =>{...})`
*
* `onPreUpdate` is called directly before a scene is updated.
*/
public onPreUpdate(_engine: Engine, _delta: number): void {
// Overridable
}

/**
* It is not recommended that internal excalibur methods be overriden, do so at your own risk.
*
* Internal _preupdate handler for [[onPostUpdate]] lifecycle event
* @internal
*/
public _postupdate(engine: Engine, delta: number): void {
this.emit('postupdate', new PostUpdateEvent(engine, delta, this));
this.onPostUpdate(engine, delta);
}

/**
* Safe to override onPostUpdate lifecycle event handler. Synonymous with `.on('preupdate', (evt) =>{...})`
*
* `onPostUpdate` is called directly after a scene is updated.
*/
public onPostUpdate(_engine: Engine, _delta: number): void {
// Overridable
}

private _isInitialized = false;
public get isInitialized() {
return this._isInitialized;
}

public _initialize(_engine: Engine) {
if (!this.isInitialized) {
this.onInitialize(_engine);
super.emit('initialize', new InitializeEvent(_engine, this));
this._isInitialized = true;
}
}

/**
* Safe to override onPostUpdate lifecycle event handler. Synonymous with `.on('preupdate', (evt) =>{...})`
*
* `onPostUpdate` is called directly after a scene is updated.
*/
public onInitialize(_engine: Engine) {
// Overridable
}

public on(eventName: 'initialize', handler: (event?: InitializeEvent) => void): void;
public on(eventName: 'preupdate', handler: (event?: PreUpdateEvent) => void): void;
public on(eventName: 'postupdate', handler: (event?: PostUpdateEvent) => void): void;
public on(eventName: any, handler: any) {
super.on(eventName, handler);
}

public off(eventName: 'initialize', handler?: (event?: InitializeEvent) => void): void;
public off(eventName: 'preupdate', handler?: (event?: PreUpdateEvent) => void): void;
public off(eventName: 'postupdate', handler?: (event?: PostUpdateEvent) => void): void;
public off(eventName: string, handler: (event?: GameEvent<any>) => void): void;
public off(eventName: string, handler: (event?: any) => void): void {
super.off(eventName, handler);
}

public once(eventName: 'initialize', handler: (event?: InitializeEvent) => void): void;
public once(eventName: 'preupdate', handler: (event?: PreUpdateEvent) => void): void;
public once(eventName: 'postupdate', handler: (event?: PostUpdateEvent) => void): void;
public once(eventName: string, handler: (event?: GameEvent<any>) => void): void;
public once(eventName: string, handler: (event?: any) => void): void {
super.once(eventName, handler);
}

public update(_engine: Engine, delta: number) {
this._initialize(_engine);
this._preupdate(_engine, delta);

// Update placements based on linear algebra
this._x += this.dx * delta / 1000;
Expand Down Expand Up @@ -475,6 +567,8 @@ export class BaseCamera {
for (let s of this._cameraStrategies) {
this.pos = s.action.call(s, s.target, this, _engine, delta);
}

this._postupdate(_engine, delta);
}

/**
Expand Down
129 changes: 121 additions & 8 deletions src/engine/Engine.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { EX_VERSION } from './Index';
import { ICanUpdate, ICanDraw, ICanInitialize } from './Interfaces/LifecycleEvents';
import { ILoadable } from './Interfaces/ILoadable';
import { Promise } from './Promises';
import { Vector } from './Algebra';
Expand All @@ -9,8 +10,13 @@ import { TileMap } from './TileMap';
import { Animation } from './Drawing/Animation';
import { Loader } from './Loader';
import { Detector } from './Util/Detector';
import { VisibleEvent, HiddenEvent, GameStartEvent, GameStopEvent, PreUpdateEvent,
PostUpdateEvent, PreFrameEvent, PostFrameEvent, GameEvent, DeactivateEvent, ActivateEvent, PreDrawEvent, PostDrawEvent } from './Events';
import { VisibleEvent, HiddenEvent,
GameStartEvent, GameStopEvent,
PreUpdateEvent, PostUpdateEvent,
PreFrameEvent, PostFrameEvent,
GameEvent, DeactivateEvent,
ActivateEvent, PreDrawEvent,
PostDrawEvent, InitializeEvent } from './Events';
import { ILoader } from './Interfaces/ILoader';
import { Logger, LogLevel } from './Util/Log';
import { Color } from './Drawing/Color';
Expand Down Expand Up @@ -156,7 +162,7 @@ export interface IEngineOptions {
*
* [[include:Engine.md]]
*/
export class Engine extends Class {
export class Engine extends Class implements ICanInitialize, ICanUpdate, ICanDraw {

/**
* Direct access to the engine's canvas element
Expand Down Expand Up @@ -351,6 +357,10 @@ export class Engine extends Class {
private _loader: ILoader;
private _isLoading: boolean = false;

private _isInitialized: boolean = false;


public on(eventName: Events.initialize, handler: (event?: Events.InitializeEvent) => void): void;
public on(eventName: Events.visible, handler: (event?: VisibleEvent) => void): void;
public on(eventName: Events.hidden, handler: (event?: HiddenEvent) => void): void;
public on(eventName: Events.start, handler: (event?: GameStartEvent) => void): void;
Expand All @@ -366,6 +376,38 @@ export class Engine extends Class {
super.on(eventName, handler);
}

public once(eventName: Events.initialize, handler: (event?: Events.InitializeEvent) => void): void;
public once(eventName: Events.visible, handler: (event?: VisibleEvent) => void): void;
public once(eventName: Events.hidden, handler: (event?: HiddenEvent) => void): void;
public once(eventName: Events.start, handler: (event?: GameStartEvent) => void): void;
public once(eventName: Events.stop, handler: (event?: GameStopEvent) => void): void;
public once(eventName: Events.preupdate, handler: (event?: PreUpdateEvent) => void): void;
public once(eventName: Events.postupdate, handler: (event?: PostUpdateEvent) => void): void;
public once(eventName: Events.preframe, handler: (event?: PreFrameEvent) => void): void;
public once(eventName: Events.postframe, handler: (event?: PostFrameEvent) => void): void;
public once(eventName: Events.predraw, handler: (event?: PreDrawEvent) => void): void;
public once(eventName: Events.postdraw, handler: (event?: PostDrawEvent) => void): void;
public once(eventName: string, handler: (event?: GameEvent<any>) => void): void;
public once(eventName: string, handler: (event?: any) => void): void {
super.once(eventName, handler);
}

public off(eventName: Events.initialize, handler?: (event?: Events.InitializeEvent) => void): void;
public off(eventName: Events.visible, handler?: (event?: VisibleEvent) => void): void;
public off(eventName: Events.hidden, handler?: (event?: HiddenEvent) => void): void;
public off(eventName: Events.start, handler?: (event?: GameStartEvent) => void): void;
public off(eventName: Events.stop, handler?: (event?: GameStopEvent) => void): void;
public off(eventName: Events.preupdate, handler?: (event?: PreUpdateEvent) => void): void;
public off(eventName: Events.postupdate, handler?: (event?: PostUpdateEvent) => void): void;
public off(eventName: Events.preframe, handler?: (event?: PreFrameEvent) => void): void;
public off(eventName: Events.postframe, handler?: (event?: PostFrameEvent) => void): void;
public off(eventName: Events.predraw, handler?: (event?: PreDrawEvent) => void): void;
public off(eventName: Events.postdraw, handler?: (event?: PostDrawEvent) => void): void;
public off(eventName: string, handler?: (event?: GameEvent<any>) => void): void;
public off(eventName: string, handler?: (event?: any) => void): void {
super.off(eventName, handler);
}

/**
* Default [[IEngineOptions]]
*/
Expand Down Expand Up @@ -769,7 +811,7 @@ O|===|* >________________>\n\

// only deactivate when initialized
if (this.currentScene.isInitialized) {
this.currentScene.onDeactivate.call(this.currentScene);
this.currentScene._deactivate.call(this.currentScene, [oldScene, newScene]);
this.currentScene.eventDispatcher.emit('deactivate', new DeactivateEvent(newScene, this.currentScene));
}

Expand All @@ -779,7 +821,7 @@ O|===|* >________________>\n\
// initialize the current scene if has not been already
this.currentScene._initialize(this);

this.currentScene.onActivate.call(this.currentScene);
this.currentScene._activate.call(this.currentScene, [oldScene, newScene]);
this.currentScene.eventDispatcher.emit('activate', new ActivateEvent(oldScene, this.currentScene));
} else {
this._logger.error('Scene', key, 'does not exist!');
Expand Down Expand Up @@ -930,7 +972,10 @@ O|===|* >________________>\n\
if (!this.canvasElementId) {
document.body.appendChild(this.canvas);
}
}

public onInitialize(_engine: Engine) {
// Override me
}

private _intializeDisplayModePosition(options: IEngineOptions) {
Expand Down Expand Up @@ -1058,6 +1103,22 @@ O|===|* >________________>\n\
}


/**
* Gets whether the actor is Initialized
*/
public get isInitialized(): boolean {
return this._isInitialized;
}

private _overrideInitialize(engine: Engine) {
if (!this.isInitialized) {
this.onInitialize(engine);
super.emit('initialize', new InitializeEvent(engine, this));
this._isInitialized = true;
}

}

/**
* Updates the entire state of the game
* @param delta Number of milliseconds elapsed since the last update.
Expand All @@ -1072,7 +1133,10 @@ O|===|* >________________>\n\
this.input.gamepads.update();
return;
}
this.emit('preupdate', new PreUpdateEvent(this, delta, this));
this._overrideInitialize(this);
// Publish preupdate events
this._preupdate(delta);

// process engine level events
this.currentScene.update(this, delta);

Expand All @@ -1087,7 +1151,31 @@ O|===|* >________________>\n\
this.input.gamepads.update();

// Publish update event
this._postupdate(delta);
}

/**
* @internal
*/
public _preupdate(delta: number) {
this.emit('preupdate', new PreUpdateEvent(this, delta, this));
this.onPreUpdate(this, delta);
}

public onPreUpdate(_engine: Engine, _delta: number) {
// Override me
}

/**
* @internal
*/
public _postupdate(delta: number) {
this.emit('postupdate', new PostUpdateEvent(this, delta, this));
this.onPostUpdate(this, delta);
}

public onPostUpdate(_engine: Engine, _delta: number) {
// Override me
}

/**
Expand All @@ -1096,7 +1184,8 @@ O|===|* >________________>\n\
*/
private _draw(delta: number) {
var ctx = this.ctx;
this.emit('predraw', new PreDrawEvent(ctx, delta, this));
this._predraw(ctx, delta);

if (this._isLoading) {
this._loader.draw(ctx, delta);
// Drawing nothing else while loading
Expand Down Expand Up @@ -1133,7 +1222,31 @@ O|===|* >________________>\n\
this.postProcessors[i].process(this.ctx.getImageData(0, 0, this.canvasWidth, this.canvasHeight), this.ctx);
}

this.emit('postdraw', new PostDrawEvent(ctx, delta, this));
this._postdraw(ctx, delta);
}

/**
* @internal
*/
public _predraw(_ctx: CanvasRenderingContext2D, delta: number) {
this.emit('predraw', new PreDrawEvent(_ctx, delta, this));
this.onPreDraw(_ctx, delta);
}

public onPreDraw(_ctx: CanvasRenderingContext2D, _delta: number) {
// Override me
}

/**
* @internal
*/
public _postdraw(_ctx: CanvasRenderingContext2D, delta: number) {
this.emit('postdraw', new PostDrawEvent(_ctx, delta, this));
this.onPostDraw(_ctx, delta);
}

public onPostDraw(_ctx: CanvasRenderingContext2D, _delta: number) {
// Override me
}

/**
Expand Down
Loading

0 comments on commit 2e98e60

Please sign in to comment.