import * as core from '../core';
import CountLimiter from './limiters/CountLimiter';
const SharedTicker = core.ticker.shared;
const DEFAULT_UPLOADS_PER_FRAME = 4;
/**
* The prepare manager provides functionality to upload content to the GPU. BasePrepare handles
* basic queuing functionality and is extended by {@link PIXI.prepare.WebGLPrepare} and {@link PIXI.prepare.CanvasPrepare}
* to provide preparation capabilities specific to their respective renderers.
*
* @abstract
* @class
* @memberof PIXI
*/
export default class BasePrepare
{
/**
* @param {PIXI.SystemRenderer} renderer - A reference to the current renderer
*/
constructor(renderer)
{
/**
* The limiter to be used to control how quickly items are prepared.
* @type {PIXI.prepare.CountLimiter|PIXI.prepare.TimeLimiter}
*/
this.limiter = new CountLimiter(DEFAULT_UPLOADS_PER_FRAME);
/**
* Reference to the renderer.
* @type {PIXI.SystemRenderer}
* @protected
*/
this.renderer = renderer;
/**
* The only real difference between CanvasPrepare and WebGLPrepare is what they pass
* to upload hooks. That different parameter is stored here.
* @type {PIXI.prepare.CanvasPrepare|PIXI.WebGLRenderer}
* @protected
*/
this.uploadHookSource = null;
/**
* Collection of items to uploads at once.
* @type {Array<*>}
* @private
*/
this.queue = [];
/**
* Collection of additional hooks for finding assets.
* @type {Array<Function>}
* @private
*/
this.addHooks = [];
/**
* Collection of additional hooks for processing assets.
* @type {Array<Function>}
* @private
*/
this.uploadHooks = [];
/**
* Callback to call after completed.
* @type {Array<Function>}
* @private
*/
this.completes = [];
/**
* If prepare is ticking (running).
* @type {boolean}
* @private
*/
this.ticking = false;
}
/**
* Upload all the textures and graphics to the GPU.
*
* @param {Function|PIXI.DisplayObject|PIXI.Container} item - Either
* the container or display object to search for items to upload or
* the callback function, if items have been added using `prepare.add`.
* @param {Function} [done] - Optional callback when all queued uploads have completed
*/
upload(item, done)
{
if (typeof item === 'function')
{
done = item;
item = null;
}
// If a display object, search for items
// that we could upload
if (item)
{
this.add(item);
}
// Get the items for upload from the display
if (this.queue.length)
{
if (done)
{
this.completes.push(done);
}
if (!this.ticking)
{
this.ticking = true;
SharedTicker.add(this.tick, this);
}
}
else if (done)
{
done();
}
}
/**
* Handle tick update
*
* @private
*/
tick()
{
this.limiter.beginFrame();
// Upload the graphics
while (this.queue.length && this.limiter.allowedToUpload())
{
const item = this.queue[0];
let uploaded = false;
for (let i = 0, len = this.uploadHooks.length; i < len; i++)
{
if (this.uploadHooks[i](this.uploadHookSource, item))
{
this.queue.shift();
uploaded = true;
break;
}
}
if (!uploaded)
{
this.queue.shift();
}
}
// We're finished
if (!this.queue.length)
{
this.ticking = false;
SharedTicker.remove(this.tick, this);
const completes = this.completes.slice(0);
this.completes.length = 0;
for (let i = 0, len = completes.length; i < len; i++)
{
completes[i]();
}
}
}
/**
* Adds hooks for finding and uploading items.
*
* @param {Function} [addHook] - Function call that takes two parameters: `item:*, queue:Array`
function must return `true` if it was able to add item to the queue.
* @param {Function} [uploadHook] - Function call that takes two parameters: `prepare:CanvasPrepare, item:*` and
* function must return `true` if it was able to handle upload of item.
* @return {PIXI.CanvasPrepare} Instance of plugin for chaining.
*/
register(addHook, uploadHook)
{
if (addHook)
{
this.addHooks.push(addHook);
}
if (uploadHook)
{
this.uploadHooks.push(uploadHook);
}
return this;
}
/**
* Manually add an item to the uploading queue.
*
* @param {PIXI.DisplayObject|PIXI.Container|*} item - Object to add to the queue
* @return {PIXI.CanvasPrepare} Instance of plugin for chaining.
*/
add(item)
{
// Add additional hooks for finding elements on special
// types of objects that
for (let i = 0, len = this.addHooks.length; i < len; i++)
{
if (this.addHooks[i](item, this.queue))
{
break;
}
}
// Get childen recursively
if (item instanceof core.Container)
{
for (let i = item.children.length - 1; i >= 0; i--)
{
this.add(item.children[i]);
}
}
return this;
}
/**
* Destroys the plugin, don't use after this.
*
*/
destroy()
{
if (this.ticking)
{
SharedTicker.remove(this.tick, this);
}
this.ticking = false;
this.addHooks = null;
this.uploadHooks = null;
this.renderer = null;
this.completes = null;
this.queue = null;
this.limiter = null;
this.uploadHookSource = null;
}
}