scrawl-canvas
Version:
Version 8.9.4 - 19 Nov 2022
903 lines (638 loc) • 29 kB
JavaScript
// # Ticker factory
// Ticker objects represent a timeline against which [Tween](./tween.html) and [Action](./action.html) objects will run.
// + ___A Ticker is an animation___ (but not a Scrawl-canvas [Animation](./animation.html)) object; it defines a `fn` function internally which will check through all Tween and Action objects subscribing to it and, where appropriate, trigger their `update` functions.
// + This module defines and launches a `coreTickersAnimation` Animation object; all Ticker objects get added to this object when their `run` or `resume` functions are triggered.
// + The `coreTickersAnimation` object runs in the Scrawl-canvas `animationLoop` which is tied to the browser/device's requestAnimationFrame (RAF) functionality.
// + `coreTickersAnimation` runs before other Animation objects, thus ___Tween/Action updates happen before any Display cycle functionality___.
// + Unlike Animation objects, ___Ticker objects do not run automatically___ as soon as they have been created.
// + To trigger a Ticker object, invoke its `run` or `resume` functions.
//
// Ticker objects have an ___effective duration___ - a set number of milliseconds for which they will run.
// + We can set this value directly, using the `duration` attribute.
// + We can also ask the Ticker to calculate its own effective duration, taking into accout the start times and duration of its currently subscribed Tweens and Actions
// + By default a Ticker will run once, then terminate (remove itself from the `coreTickersAnimation` function). This counts as 1 cycle.
// + We can get the Ticker to run multiple times before terminating, by setting its `cycles` attribute to a positive integer Number value.
// + Setting the `cycles` attribute to `0` causes the Ticker to run continuously once started, until told to stop.
// + Note that Tickers always run forwards, never backwards. Tweens and Actions can be reversed by setting the appropriate flags on them.
//
// Tickers (unlike Animations) take part in Scrawl-canvas packet functionality; they can be saved, restored and cloned.
//
// Tickers can be controlled through a set of trigger functions: __run, halt, reverse, resume, seekTo, seekFor, complete, reset__.
// + We can add ___Ticker hook functions___ to each of these trigger functions.
//
// A Ticker can be made to dispatch a [DOM Custom Event](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent) at regular intervals as it runs, by setting its `eventChoke` attribute to a Number value (representing milliseconds) greater than `0`.
// + The Custom Event is named `tickerupdate`.
// + Its `detail` object includes the following attributes: __name__ (Ticker name-String); __type__ (`Ticker`); __tick__ (milliseconds since Ticker started running); __reverseTick__ (milliseconds remaining until Ticker completes its current cycle).
// + The Event bubbles, and is cancelable.
//
// Tickers are very closely associated with Tweens.
// + Each Ticker can have more than one Tween (and 0 or more Actions) subscribed to it.
// + Tweens and Actions can be told to subscribe to a Ticker when they are created; they can change their Ticker subscription at any time.
// + ___A Tween can make its own Ticker when it is created.___
// TODO: basic packet and kill functionality tested in Demo DOM-004, but there's a lot of Ticker/Tween/Action functionality that needs to be explored and tested further:
// + If we kill a Ticker but leave associated Tweens untouched, will running those Tweens crash the script?
// + Can we successfully clone a Tween whose Ticker has been killed?
// + A Ticker can have more than one subscribed Tween/Action; can (should?) Tweens/Actions be able to subscribe to more than one Ticker? _Initial thoughts: no - if such functionality required, clone the Tween/Action_
// + Can Tickers be nested? How could we use nested Tickers? _Initial thoughts: yes to nesting, possibly use an Action on one ticker to run/halt a second Ticker; one use case may be to break up a complex animation into smaller, more discrete parts? But this all begs the question: if we halt a nested Ticker (via user interaction), how would we cascade that through to parent/sibling Tickers?_
// + Possible additional demo - tie a more complex Ticker/Tween/Action sequence to a graphical timeline - see blog post [Adding some canvas love to Hexo](https://blog.rikworks.co.uk/2018/05/26/Adding-some-canvas-love-to-Hexo/).
// + Would be a Big Win if we can tether Ticker/Tween/Actions to progress while a video plays - opens up the world of ___interactive video___.
// #### Demos:
// + All Tween and Action-related Demos necessarily include Tickers
// + [Canvas-005](../../demo/canvas-005.html) - Cell-locked, and Entity-locked, gradients; animating gradients by delta, and by tween
// + [Canvas-006](../../demo/canvas-006.html) - Canvas tween stress test
// + [DOM-004](../../demo/dom-004.html) - Limitless rockets (clone and destroy elements, tweens, tickers)
// + [DOM-005](../../demo/dom-005.html) - DOM tween stress test
// + [DOM-006](../../demo/dom-006.html) - Tween actions on a DOM element; tracking tween and ticker activity (analytics)
// + [Snippets-001](../../demo/snippets-001.html) - Scrawl-canvas DOM element snippets
// #### Imports
import { constructors, animationtickers, tween } from '../core/library.js';
import { mergeOver, pushUnique, removeItem, xt, xtGet, isa_obj, convertTime, Ωempty } from '../core/utilities.js';
import { makeAnimation } from './animation.js';
import baseMix from '../mixin/base.js';
// #### Ticker constructor
const Ticker = function (items = Ωempty) {
this.makeName(items.name);
this.register();
this.subscribers = [];
this.subscriberObjects = [];
this.set(this.defs);
this.set(items);
this.cycleCount = 0;
this.active = false;
this.effectiveDuration = 0;
this.startTime = 0;
this.currentTime = 0;
this.tick = 0;
this.lastEvent = 0;
if (items.subscribers) this.subscribe(items.subscribers);
this.setEffectiveDuration();
return this;
};
// #### Ticker prototype
let P = Ticker.prototype = Object.create(Object.prototype);
P.type = 'Ticker';
P.lib = 'animationtickers';
P.isArtefact = false;
P.isAsset = false;
// #### Mixins
P = baseMix(P);
// #### Ticker attributes
// + Attributes defined in the [base mixin](../mixin/base.html): __name__.
let defaultAttributes = {
// __order__ - positive integer Number - determines the order in which each Ticker animation object will be actioned before the Display cycle starts.
// + Higher order Tickers will be processed after lower order Tickers.
// + Tickers with the same `order` value will be processed in the order in which they were defined in code.
order: 1,
// __duration__ - can accept a variety of values:
// + Number, representing milliseconds.
// + String time value, for example `'500ms', '0.5s'`.
// + (% String values cannot be used with Ticker objects - they have nothing to measure such a relative value against).
duration: 0,
// __subscribers__ - Array of Tween and Action name-Strings. Use `subscribe` and `unsubscribe` functions rather than the `set` function to add/remove Tweens and/or Actions to/from the Ticker
subscribers: null,
// __killOnComplete__ - Boolean flag. When set, the Ticker will kill both itself and all Tweens and Actions associated with it at the end of its run
killOnComplete: false,
// __cycles__ - positive integer Number representing the number of cycles the Ticker will run before it completes.
// + A value of `0` indicates that the Ticker should repeat itself forever, until its `halt`, `seekTo`, `seekFor`, `complete` or `reset` functions are triggered.
// + Note that Tween and Action animation direction is determined by those objects (via their `reverseOnCycleEnd` and `reversed` flags). Tickers always repeat in a forwards direction - they loop back to their start; they never reverse time.
cycles: 1,
// __eventChoke__ - positive Number representing the time to elapse before the Ticker creates and emits another event. A value of `0` stops the Ticker emitting events as it runs.
eventChoke: 0,
// The Ticker object supports some __hook functions__:
// + __onRun__ - triggers each time the Ticker's `run` function is invoked
// + __onHalt__ - triggers each time the Ticker's `halt` function is invoked
// + __onReverse__ - triggers each time the Ticker's `reverse` function is invoked
// + __onResume__ - triggers each time the Ticker's `resume` function is invoked
// + __onSeekTo__ - triggers each time the Ticker's `seekTo` function is invoked
// + __onSeekFor__ - triggers each time the Ticker's `seekFor` function is invoked
// + __onComplete__ - triggers each time the Ticker's `complete` function is invoked
// + __onReset__ - triggers each time the Ticker's `reset` function is invoked
onRun: null,
onHalt: null,
onReverse: null,
onResume: null,
onSeekTo: null,
onSeekFor: null,
onComplete: null,
onReset: null,
};
P.defs = mergeOver(P.defs, defaultAttributes);
// #### Packet management
P.packetExclusions = pushUnique(P.packetExclusions, ['subscribers']);
P.packetFunctions = pushUnique(P.packetFunctions, ['onRun', 'onHalt', 'onReverse', 'onResume', 'onSeekTo', 'onSeekFor', 'onComplete', 'onReset']);
// #### Clone management
// No additional clone functionality required.
// #### Kill management
// `kill` - remove Ticker from Scrawl-canvas system.
P.kill = function () {
if (this.active) this.halt();
removeItem(tickerAnimations, this.name);
tickerAnimationsFlag = true;
this.deregister();
return true;
};
// `killTweens` - remove a Ticker's subscribed Tweens from Scrawl-canvas system.
// + If the function is invoked with a truthy argument, the Ticker will also be removed from the system.
P.killTweens = function(autokill = false) {
let i, iz, sub;
for (i = 0, iz = this.subscribers.length; i < iz; i++) {
sub = tween[this.subscribers[i]];
sub.completeAction();
sub.kill();
}
if (autokill) {
this.kill();
return true;
}
return this;
};
// #### Get, Set, deltaSet
let G = P.getters,
S = P.setters;
// __subscribers__ - see also the `subscribe` and `unsubscribe` functions below
// + getter returns a copy of the `subscribers` Array, containing Tween and Action object name-Strings.
// + setter accepts a Tween or Action name-String, or an Array of such Strings. Will replace the existing `subscribers` Array with this new data.
G.subscribers = function () {
return [].concat(this.subscribers);
};
S.subscribers = function (item) {
this.subscribers = [];
this.subscribe(item);
};
// __order__
S.order = function (item) {
this.order = item;
if (this.active) tickerAnimationsFlag = true;
};
// __cycles__
S.cycles = function (item) {
this.cycles = item;
if (!this.cycles) this.cycleCount = 0;
};
// __duration__ - changes to the `duration` (and as a consequence `effectiveDuration`) attributes will be cascaded down to subscribed Tweens and Actions immediately.
S.duration = function (item) {
let i, iz, target,
subscribers = this.subscribers;
this.duration = item;
this.setEffectiveDuration();
if(xt(subscribers)){
for (i = 0, iz = subscribers.length; i < iz; i++) {
target = tween[subscribers[i]];
if (target) {
target.calculateEffectiveTime();
if (target.type === 'Tween') target.calculateEffectiveDuration();
}
}
}
};
// #### Subscription management
// `subscribe` - accepts a Tween or Action name-String, or an Array of such Strings.
P.subscribe = function (items) {
let myItems = [].concat(items),
i, iz, item, name;
for (i = 0, iz = myItems.length; i < iz; i++) {
item = myItems[i];
if(item != null){
if (item.substring) name = item;
else name = (isa_obj(item) && item.name) ? item.name : false;
if (name) pushUnique(this.subscribers, name);
}
}
if (myItems.length) {
this.sortSubscribers();
this.recalculateEffectiveDuration();
}
return this;
};
// `unsubscribe` - accepts a Tween or Action name-String, or an Array of such Strings.
P.unsubscribe = function (items) {
var myItems = [].concat(items),
i, iz, item, name;
for (i = 0, iz = myItems.length; i < iz; i++) {
item = items[i];
if (item.substring) name = item;
else name = (isa_obj(item) && item.name) ? item.name : false;
if (name) removeItem(this.subscribers, name);
}
if (myItems.length) {
this.sortSubscribers();
this.recalculateEffectiveDuration();
}
return this;
};
// `repopulateSubscriberObjects`
P.repopulateSubscriberObjects = function () {
let arr = this.subscriberObjects,
subs = this.subscribers,
t;
arr.length = 0;
subs.forEach(sub => {
t = tween[sub];
if (t) arr.push(t);
});
};
// `getSubscriberObjects`
P.getSubscriberObjects = function () {
if (this.subscribers.length && !this.subscriberObjects.length) this.repopulateSubscriberObjects();
return this.subscriberObjects;
};
// `sortSubscribers` - internal Helper function called by `subscribe` and `unsubscribe`
P.sortSubscribers = function () {
let mysubscribers = this.subscribers;
if(mysubscribers.length > 1) {
let subs = [].concat(mysubscribers),
floor = Math.floor,
buckets = [];
subs.forEach(obj => {
let effectiveTime = floor(obj.effectiveTime) || 0;
if (!buckets[effectiveTime]) buckets[effectiveTime] = [];
buckets[effectiveTime].push(obj);
});
this.subscribers = buckets.reduce((a, v) => a.concat(v), []);
}
this.repopulateSubscriberObjects();
};
// `updateSubscribers` - internal function called by the `run`, `reset` and `complete` functions below.
// + First argument is an object that gets applied as the argument to each Tween/Action object's `set` function.
// + Second argument is a Boolean; when set, subscribed Tween/Actions will be told to reverse their current direction.
P.updateSubscribers = function(items, reversed) {
reversed = (xt(reversed)) ? reversed : false;
let subs = this.getSubscriberObjects(),
i, iz;
if (reversed) {
for (i = subs.length - 1; i >= 0; i--) {
subs[i].set(items);
}
}
else{
for (i = 0, iz = subs.length; i < iz; i++) {
subs[i].set(items);
}
}
return this;
};
// `changeSubscriberDirection` - internal function - when invoked, Tween/Actions will be told to reverse their current direction.
P.changeSubscriberDirection = function () {
let subs = this.getSubscriberObjects();
subs.forEach(sub => sub.reversed = !sub.reversed);
return this;
};
// #### Events
// `makeTickerUpdateEvent` - internal function - generates a new CustomEvent object.
P.makeTickerUpdateEvent = function() {
return new CustomEvent('tickerupdate', {
detail: {
name: this.name,
type: 'Ticker',
tick: this.tick,
reverseTick: this.effectiveDuration - this.tick
},
bubbles: true,
cancelable: true
});
};
// #### Animation
// `recalculateEffectiveDuration` - where a Ticker has not been given a `duration` value, it needs to consult its subscribed Tween/Action objects to calculate an `effectiveDuration` value with sufficient time allocated for each Tween to run to completion, and each Action to trigger.
// + Tween/Actions with a relative `time` attribute - eg: `30%` - will not be included in the calculation.
// + Tweens can overlap - they do not all have to start and end at the same time, nor do they need to run sequentially.
P.recalculateEffectiveDuration = function() {
let durationValue,
subs = this.getSubscriberObjects(),
duration = 0;
if (!this.duration) {
subs.forEach(sub => {
durationValue = sub.getEndTime();
if (durationValue > duration) duration = durationValue;
});
this.effectiveDuration = duration;
}
// Shouldn't cause an infinite loop ...
else this.setEffectiveDuration();
return this;
};
// `setEffectiveDuration` - internal helper function - convert `duration` value into `effectiveDuration` value.
P.setEffectiveDuration = function() {
let temp;
if (this.duration) {
temp = convertTime(this.duration);
// Cannot use %-String values for Ticker `duration` attribute
if (temp[0] === '%') {
this.duration = 0
this.recalculateEffectiveDuration();
}
else this.effectiveDuration = temp[1];
}
return this;
};
// `fn` - internal - the __animation function__ will trigger once per RequestAnimationFrame (RAF) tick - approximately 60 times a second, depending on other calculation work.
// + Only triggers when the Ticker is running in a qualifying state.
// + __reverseOrder__ argument is a Boolean value; when set, subscribed Tween/Action objects will be processed in reverse order.
P.fn = function (reverseOrder) {
// Request a `result` object from the pool.
let result = requestResultObject();
// Determine the order in which subscribed objects will be processed
reverseOrder = xt(reverseOrder) ? reverseOrder : false;
let i, iz, subs, eTime, now, e,
active = this.active,
startTime = this.startTime,
currentTime, tick,
cycles = this.cycles,
cycleCount = this.cycleCount,
effectiveDuration = this.effectiveDuration,
eventChoke = this.eventChoke;
// Process only if the Ticker is currently ___active___ and has a ___startTime___ value assigned to it.
if (active && startTime) {
// Process only if the Ticker's `cycles` attribute has been set to `0`, or if the Ticker has not yet completed all its cycles.
if (!cycles || cycleCount < cycles) {
currentTime = this.currentTime = Date.now();
tick = this.tick = currentTime - startTime;
// Update the results object
// + Functionality performed if the ___Tween is not on its final cycle___.
if (!cycles || cycleCount + 1 < cycles) {
if (tick >= effectiveDuration) {
tick = this.tick = 0;
this.startTime = this.currentTime;
result.tick = effectiveDuration;
result.reverseTick = 0;
result.willLoop = true;
if (cycles) {
cycleCount++;
this.cycleCount = cycleCount;
}
}
else {
result.tick = tick;
result.reverseTick = effectiveDuration - tick;
}
result.next = true;
}
// + Functionality performed only when the ___Tween is on its final cycle___.
else {
if (tick >= effectiveDuration) {
result.tick = effectiveDuration;
result.reverseTick = 0;
active = this.active = false;
if (cycles) {
cycleCount++
this.cycleCount = cycleCount;
}
}
else {
result.tick = tick;
result.reverseTick = effectiveDuration - tick;
result.next = true;
}
}
// Invoke the `update` function on each subscribed Tween/Action
subs = this.getSubscriberObjects();
if (reverseOrder) {
for (i = subs.length - 1; i >= 0; i--) {
subs[i].update(result);
}
}
else{
for (i = 0, iz = subs.length; i < iz; i++) {
subs[i].update(result);
}
}
// Dispatch the Ticker Event, if required.
if (eventChoke) {
eTime = this.lastEvent + eventChoke;
now = Date.now();
if (eTime < now) {
e = this.makeTickerUpdateEvent();
window.dispatchEvent(e);
this.lastEvent = now;
}
}
// If this invocation of the function has completed the Ticker's run, switch it off.
if (!active) this.halt();
// If the Ticker's run is completed and the `killOnComplete` flag is set, kill everything.
if (this.killOnComplete && cycleCount >= cycles) this.killTweens(true);
}
}
// Release the `result` object back to the pool.
releaseResultObject(result);
};
// #### Animation control
// `run`
// + Start the Ticker from time 0.
// + Trigger the object's `onRun` function.
P.run = function () {
if (!this.active) {
this.startTime = this.currentTime = Date.now();
this.cycleCount = 0;
this.updateSubscribers({
reversed: false
});
this.active = true;
pushUnique(tickerAnimations, this.name);
tickerAnimationsFlag = true;
if (typeof this.onRun === 'function') this.onRun();
}
return this;
};
// `isRunning` - check to see if Ticker is in a running state.
P.isRunning = function () {
return this.active;
};
// `reset`
// + Halt the Ticker, if it is running.
// + Set all attributes to their initial values.
// + Update subscribed Tween/Actions.
// + Trigger the object's `onReset` function.
P.reset = function () {
if (this.active) this.halt();
this.startTime = this.currentTime = Date.now();
this.cycleCount = 0;
this.updateSubscribers({
reversed: false
});
this.active = true;
this.fn(true);
this.active = false;
if (typeof this.onReset === 'function') this.onReset();
return this;
};
// `complete`
// + Halt the Ticker, if it is running.
// + Set all attributes to their initial values.
// + Update subscribed Tween/Actions.
// + Trigger the object's `onComplete` function.
P.complete = function () {
if (this.active) this.halt();
this.startTime = this.currentTime = Date.now();
this.cycleCount = 0;
this.updateSubscribers({
reversed: true
});
this.active = true;
this.fn();
this.active = false;
if (typeof this.onComplete === 'function') this.onComplete();
return this;
};
// `reverse` - simulates a reversal in the Ticker's animation.
// + Halt the Ticker, if it is running.
// + Trigger the object's `onReverse` function.
// + after recalculation, resume the Ticker - if required.
// + Function accepts a Boolean argument - if true, Ticker will start playing "in reverse".
// + Directionality is determined by Tween/Action object attribute settings, not the Ticker.
P.reverse = function (resume = false) {
let timePlayed;
resume = xtGet(resume, false);
if (this.active) {
this.halt();
}
timePlayed = this.currentTime - this.startTime;
this.startTime = this.currentTime - (this.effectiveDuration - timePlayed);
this.changeSubscriberDirection();
this.active = true;
this.fn();
this.active = false;
if (typeof this.onReverse === 'function') this.onReverse();
if (resume) this.resume();
return this;
};
// `halt`
// + Stop the Ticker at its current point in time
// + Trigger the object's `onHalt` function.
P.halt = function () {
this.active = false;
pushUnique(tickerAnimations, this.name);
tickerAnimationsFlag = true;
if (typeof this.onHalt === 'function') this.onHalt();
return this;
};
// `resume` - this function can also be triggered by the `reverse`, `seekTo` and `seekFor` functions
// + Start the Ticker from its current point in time
// + Trigger the object's `onResume` function.
P.resume = function () {
let now, current, start;
if (!this.active) {
now = Date.now(),
current = this.currentTime,
start = this.startTime;
this.startTime = now - (current - start);
this.currentTime = now;
this.active = true;
pushUnique(tickerAnimations, this.name);
tickerAnimationsFlag = true;
if (typeof this.onResume === 'function') this.onResume();
}
return this;
};
// `seekTo`
// + First argument - Number representing the millisecond time to move to on the Ticker's timeline
// + Second argument - Boolean - if true, Ticker will resume playing from new point
// + Halt the Ticker, if it is running.
// + Update the Ticker's `currentTime`, `startTime` attributes
// + Trigger the object's `onSeekTo` function.
// + Resume the Ticker - if required.
P.seekTo = function (milliseconds, resume = false) {
let backwards = false;
milliseconds = xtGet(milliseconds, 0);
if (this.active) this.halt();
if (this.cycles && this.cycleCount >= this.cycles) this.cycleCount = this.cycles - 1;
if (milliseconds < this.tick) backwards = true;
this.currentTime = Date.now();
this.startTime = this.currentTime - milliseconds;
this.active = true;
this.fn(backwards);
this.active = false;
if (typeof this.onSeekTo === 'function') this.onSeekTo();
if (resume) this.resume();
return this;
};
// `seekFor`
// + First argument - Number representing the number of milliseconds to move along the Ticker's timeline (forwards or backwards)
// + Second argument - Boolean - if true, Ticker will resume playing from new point
// + Halt the Ticker, if it is running.
// + Update the Ticker's `currentTime`, `startTime` attributes
// + Trigger the object's `onSeekFor` function.
// + Resume the Ticker - if required.
P.seekFor = function (milliseconds, resume = false) {
let backwards = false;
milliseconds = __xtGet(milliseconds, 0);
if (this.active) this.halt();
if (this.cycles && this.cycleCount >= this.cycles) this.cycleCount = this.cycles - 1;
this.startTime -= milliseconds;
if (milliseconds < 0) backwards = true;
this.active = true;
this.fn(backwards);
this.active = false;
if (typeof this.onSeekFor === 'function') this.onSeekFor();
if (resume) this.resume();
return this;
};
// #### Ticker animation controller
let tickerAnimations = [];
let tickerAnimationsFlag = true;
// `coreTickersAnimation`
const coreTickersAnimation = makeAnimation({
name: 'coreTickersAnimation',
order: 0,
fn: function () {
// We only sort active Ticker objects when absolutely necessary.
// + Sorted using a bucket sort algorithm.
if (tickerAnimationsFlag) {
tickerAnimationsFlag = false;
let tans = [].concat(tickerAnimations),
floor = Math.floor,
buckets = [];
tans.forEach(name => {
let obj = animationtickers[name];
if (xt(obj)) {
let order = floor(obj.order) || 0;
if (!buckets[order]) buckets[order] = [];
buckets[order].push(obj.name);
}
});
tickerAnimations = buckets.reduce((a, v) => a.concat(v), []);
}
// Invoke each Ticker's `fn` function.
// + It's up to the Ticker object to decide whether it's active
for (let i = 0, iz = tickerAnimations.length, n, t; i < iz; i++) {
n = tickerAnimations[i];
t = animationtickers[n];
if (t && t.fn) t.fn();
}
}
});
// #### ResultObject pool
// ... Because we generate so many of them.
// + To use a pool result object, request it using `requestResultObject` function.
// + It is imperative that requested result objects are released - `releaseResultObject` - once work with them completes.
const resultObjectPool = [];
// `requestResultObject`
const requestResultObject = function () {
if (!resultObjectPool.length) {
resultObjectPool.push({
tick: 0,
reverseTick: 0,
willLoop: false,
next: false
});
}
return resultObjectPool.shift();
};
// `releaseResultObject`
const releaseResultObject = function (r) {
if (r) {
r.tick = 0;
r.reverseTick = 0;
r.willLoop = false;
r.next = false;
resultObjectPool.push(r);
}
};
// #### Factory
const makeTicker = function (items) {
if (!items) return false;
return new Ticker(items);
};
constructors.Ticker = Ticker;
// #### Exports
export {
makeTicker,
};