/** * An abstract pattern for primary layers of the game canvas to implement. * @category - Canvas * @abstract * @interface */ class CanvasLayer extends PIXI.Container { /** * Options for this layer instance. * @type {{name: string}} */ options = this.constructor.layerOptions; // Default interactivity interactiveChildren = false; /* -------------------------------------------- */ /* Layer Attributes */ /* -------------------------------------------- */ /** * Customize behaviors of this CanvasLayer by modifying some behaviors at a class level. * @type {{name: string}} */ static get layerOptions() { return { name: "", baseClass: CanvasLayer }; } /* -------------------------------------------- */ /** * Return a reference to the active instance of this canvas layer * @type {CanvasLayer} */ static get instance() { return canvas[this.layerOptions.name]; } /* -------------------------------------------- */ /** * The canonical name of the CanvasLayer is the name of the constructor that is the immediate child of the * defined baseClass for the layer type. * @type {string} * * @example * canvas.lighting.name -> "LightingLayer" */ get name() { const baseCls = this.constructor.layerOptions.baseClass; let cls = Object.getPrototypeOf(this.constructor); let name = this.constructor.name; while ( cls ) { if ( cls !== baseCls ) { name = cls.name; cls = Object.getPrototypeOf(cls); } else break; } return name; } /* -------------------------------------------- */ /** * The name used by hooks to construct their hook string. * Note: You should override this getter if hookName should not return the class constructor name. * @type {string} */ get hookName() { return this.name; } /* -------------------------------------------- */ /** * An internal reference to a Promise in-progress to draw the CanvasLayer. * @type {Promise} */ #drawing = Promise.resolve(this); /* -------------------------------------------- */ /** * Is the layer drawn? * @type {boolean} */ #drawn = false; /* -------------------------------------------- */ /* Rendering /* -------------------------------------------- */ /** * Draw the canvas layer, rendering its internal components and returning a Promise. * The Promise resolves to the drawn layer once its contents are successfully rendered. * @param {object} [options] Options which configure how the layer is drawn * @returns {Promise} */ async draw(options={}) { return this.#drawing = this.#drawing.finally(async () => { console.log(`${vtt} | Drawing the ${this.constructor.name} canvas layer`); await this.tearDown(); await this._draw(options); Hooks.callAll(`draw${this.hookName}`, this); this.#drawn = true; }); } /** * The inner _draw method which must be defined by each CanvasLayer subclass. * @param {object} options Options which configure how the layer is drawn * @abstract * @protected */ async _draw(options) { throw new Error(`The ${this.constructor.name} subclass of CanvasLayer must define the _draw method`); } /* -------------------------------------------- */ /** * Deconstruct data used in the current layer in preparation to re-draw the canvas * @param {object} [options] Options which configure how the layer is deconstructed * @returns {Promise} */ async tearDown(options={}) { if ( !this.#drawn ) return this; MouseInteractionManager.emulateMoveEvent(); this.#drawn = false; this.renderable = false; await this._tearDown(options); Hooks.callAll(`tearDown${this.hookName}`, this); this.renderable = true; MouseInteractionManager.emulateMoveEvent(); return this; } /** * The inner _tearDown method which may be customized by each CanvasLayer subclass. * @param {object} options Options which configure how the layer is deconstructed * @protected */ async _tearDown(options) { this.removeChildren().forEach(c => c.destroy({children: true})); } }