kontra
Version:
Kontra HTML5 game development library
164 lines (142 loc) • 4.78 kB
JavaScript
/**
* A plugin system based on the [interceptor pattern](https://en.wikipedia.org/wiki/Interceptor_pattern), designed to share reusable code such as more advance collision detection or a 2D physics engine.
*
* ```js
* import { registerPlugin, Sprite } from 'kontra';
* import loggingPlugin from 'path/to/plugin/code.js'
*
* // register a plugin that adds logging to all Sprites
* registerPlugin(Sprite, loggingPlugin);
* ```
* @sectionName Plugin
*/
/**
* @docs docs/api_docs/plugin.js
*/
/**
* Get the kontra object method name from the plugin.
*
* @param {String} methodName - Before/After function name
*
* @returns {String}
*/
function getMethod(methodName) {
let methodTitle = methodName.substr( methodName.search(/[A-Z]/) );
return methodTitle[0].toLowerCase() + methodTitle.substr(1);
}
/**
* Remove an interceptor.
*
* @param {function[]} interceptors - Before/After interceptor list
* @param {function} fn - Interceptor function
*/
function removeInterceptor(interceptors, fn) {
let index = interceptors.indexOf(fn);
if (index !== -1) {
interceptors.splice(index, 1);
}
}
/**
* Register a plugin to run a set of functions before or after the Kontra object functions.
* @function registerPlugin
*
* @param {Object} kontraObj - Kontra object to attach the plugin to.
* @param {Object} pluginObj - Plugin object with before and after intercept functions.
*/
export function registerPlugin(kontraObj, pluginObj) {
let objectProto = kontraObj.prototype;
if (!objectProto) return;
// create interceptor list and functions
if (!objectProto._inc) {
objectProto._inc = {};
objectProto._bInc = function beforePlugins(context, method, ...args) {
return this._inc[method].before.reduce((acc, fn) => {
let newArgs = fn(context, ...acc);
return newArgs ? newArgs : acc;
}, args);
};
objectProto._aInc = function afterPlugins(context, method, result, ...args) {
return this._inc[method].after.reduce((acc, fn) => {
let newResult = fn(context, acc, ...args);
return newResult ? newResult : acc;
}, result);
};
}
// add plugin to interceptors
Object.getOwnPropertyNames(pluginObj).forEach(methodName => {
let method = getMethod(methodName);
if (!objectProto[method]) return;
// override original method
if (!objectProto['_o' + method]) {
objectProto['_o' + method] = objectProto[method];
objectProto[method] = function interceptedFn(...args) {
// call before interceptors
let alteredArgs = this._bInc(this, method, ...args);
let result = objectProto['_o' + method].call(this, ...alteredArgs);
// call after interceptors
return this._aInc(this, method, result, ...args);
};
}
// create interceptors for the method
if (!objectProto._inc[method]) {
objectProto._inc[method] = {
before: [],
after: []
};
}
if (methodName.startsWith('before')) {
objectProto._inc[method].before.push(pluginObj[methodName]);
}
else if (methodName.startsWith('after')) {
objectProto._inc[method].after.push(pluginObj[methodName]);
}
});
}
/**
* Unregister a plugin from a Kontra object.
* @function unregisterPlugin
*
* @param {Object} kontraObj - Kontra object to detach plugin from.
* @param {Object} pluginObj - The plugin object that was passed during registration.
*/
export function unregisterPlugin(kontraObj, pluginObj) {
let objectProto = kontraObj.prototype;
if (!objectProto || !objectProto._inc) return;
// remove plugin from interceptors
Object.getOwnPropertyNames(pluginObj).forEach(methodName => {
let method = getMethod(methodName);
if (methodName.startsWith('before')) {
removeInterceptor(objectProto._inc[method].before, pluginObj[methodName]);
}
else if (methodName.startsWith('after')) {
removeInterceptor(objectProto._inc[method].after, pluginObj[methodName]);
}
});
}
/**
* Safely extend the functionality of a Kontra object. Any properties that already exist on the Kontra object will not be added.
*
* ```js
* import { extendObject, Vector } from 'kontra';
*
* // add a subtract function to all Vectors
* extendObject(Vector, {
* subtract(vec) {
* return Vector(this.x - vec.x, this.y - vec.y);
* }
* });
* ```
* @function extendObject
*
* @param {Object} kontraObj - Kontra object to extend
* @param {Object} properties - Properties to add.
*/
export function extendObject(kontraObj, properties) {
let objectProto = kontraObj.prototype;
if (!objectProto) return;
Object.getOwnPropertyNames(properties).forEach(prop => {
if (!objectProto[prop]) {
objectProto[prop] = properties[prop];
}
});
}