import Cadence from './Cadence'
import Timeblock from './Timeblock'
import { iterateConditional, arrayRemove, arrayFindByKey, arrayRemoveByKey, millisecondsPerFrame, toggleLife, isTypeSeconds } from './utils'
import 'raf-polyfill'
import Hummingbird from 'hummingbird/lib/Hummingbird'
// ----------------------------------------------------------------------------------------------------
let _isPaused = true
let _isActive = true
let _raf = undefined
let _cadencePool = []
let _timerPool = []
// ----------------------------------------------------------------------------------------------------
// PUBLIC METHODS
export const enlist = (singleBird, skipFinalCheck, fromWake) => {
// TODO - possibly remove skipFinalCheck as unnecessary?
const speed = singleBird.speed()
// find the cadence from active pool
let singleCadence = arrayFindByKey(_cadencePool, 'speed', speed)
// if none found, make a new one
if (!singleCadence) {
singleCadence = new Cadence(speed)
_cadencePool.push(singleCadence)
}
// assign the cadence to the single bird for ref
singleBird._parent = singleCadence
// TODO - SHOULD NOT BE CALLED ON CREATE?
// - now unnecessary?
// un-pause the potential life timer
toggleLife(singleBird, false)
if (fromWake) {
// move from the single cadence sleep pool to active pool
singleBird._parent.wakeSingle(singleBird)
return singleBird
}
// check if it already exists in this cadence?
// if (singleCadence.hasSingle(singleBird)) {
// console.log('hasSingle:')
// return singleBird
// }
singleCadence.enlist(singleBird)
if (_isActive || !skipFinalCheck) wake('isEnlist')
return singleBird
}
/**
* Will stop a method from being called by the engine. Removes the instances
* from the internal pools.
* @category API
* @function
* @param {Hummingbird|Flock} [args] - Any number of instances to be removed from the engine to no longer be called
* @example
* function foo() { ... }
* function bar() { ... }
*
* const fooBird = Hummingbird(foo)
* const barBird = Hummingbird(bar)
*
* // release all by naming each
* Hummingbird.release(foo, bar)
* // or omit and will release everything
* Hummingbird.release()
*/
export const release = (...args) => {
// console.log('release(), args:', args, '| args.length:', args.length)
// check if no args first, release everything
if (args.length === 0) {
_cadencePool.length = 0
// TODO - iterate through pool to destroy refences for garbage collection?
} else {
iterateConditional(
item => {
releaseSingle(item, true)
},
item => {
// remove the whole cadence instance
arrayRemoveByKey(_cadencePool, 'speed', item)
},
...args
)
}
checkCadenceTotal()
}
// only interanlly called from Hummingbird instance
export const releaseSingle = (singleBird, skipFinalCheck) => {
const singleCadence = singleBird._parent
if (singleCadence) {
singleCadence.releaseSingle(singleBird)
// removes the Category object with no handlers
// TODO - check both pools?
if (singleCadence.isIdle()) {
arrayRemove(_cadencePool, singleCadence)
}
if (!skipFinalCheck) {
checkCadenceTotal()
}
return true
} else {
return false
}
}
/**
* Will stop a method from being called by the engine temporarily.
* @category API
* @function
* @param {Hummingbird|Flock} [args] - Any number of Hummingbird instances to be removed from loop
* @example
* function foo() { ... }
* function bar() { ... }
*
* const fooBird = Hummingbird(foo)
* const barBird = Hummingbird(bar)
*
* // sleep all by naming each
* Hummingbird.sleep(foo, bar)
* // or omit and will sleep everything
* Hummingbird.sleep()
*/
export const sleep = (...args) => {
if (args.length > 0) {
iterateConditional(
// Hummingbird instance
singleBird => {
singleBird._parent.sleepSingle(singleBird)
// toggleLife(singleBird, true)
},
// Number for Cadence
item => {
const singeCadence = arrayFindByKey(_cadencePool, 'speed', item)
if (singeCadence) {
singeCadence.sleep()
}
},
...args
)
_isPaused = true
// assume the whole system is paused THEN
// loop of each cadence checking their paused state
for (var i = 0, k = _cadencePool.length; i < k; i++) {
if (!_cadencePool[i].engine.paused) {
_isPaused = false
break
}
}
} else {
// sleep everything
for (var i = 0, k = _cadencePool.length; i < k; i++) {
_cadencePool[i].sleep()
}
_isPaused = true
}
if (_isPaused) {
// console.log('----------- CANCEL RAF -----------')
_isActive = false
window.cancelAnimationFrame(_raf)
}
}
/**
* Will resume a method being called by the engine.
* @category API
* @function
* @param {Hummingbird|Flock} [args] - Any number of Hummingbird instances to be removed from loop
* @example
* function foo() { ... }
* function bar() { ... }
*
* const fooBird = Hummingbird(foo)
* const barBird = Hummingbird(bar)
*
* Hummingbird.sleep()
*
* // wake by naming each
* Hummingbird.wake(foo, bar)
* // or omit and will wake everything
* Hummingbird.wake()
*/
export const wake = (...args) => {
// store current paused to avoid multiple/stacked RAF calls
var _currentlyPaused = _isPaused
if (args.length > 0) {
if (args[0] === 'isEnlist') {
// called when a new hummingbird is created
_isPaused = false
} else {
// check each arg for type
iterateConditional(
// is a Hummingbird
item => {
_isPaused = false
enlist(item, true, true)
},
// is a Number
item => {
// then wake that cadence, it will wake any sleep pool
const found = arrayFindByKey(_cadencePool, 'speed', item)
if (found) {
found.wake()
_isPaused = false
}
},
// items
...args
)
}
} else {
// wake everything
for (var i = 0, k = _cadencePool.length; i < k; i++) {
_cadencePool[i].wake()
}
_isPaused = false
}
// TODO - check if args is more than 0, set _osPaused = false once, here
if (_currentlyPaused) {
// console.log('----------- START RAF -----------')
_isActive = true
_raf = window.requestAnimationFrame(handleRAF)
}
}
export const createTimer = singleBird => {
// get the time as milliseconds
let lifeMilli
if (isTypeSeconds(singleBird.lifeSpanIn)) {
lifeMilli = singleBird.lifeSpan * 1000
} else {
// minus 1 because of when the timer gets called in loop logic
lifeMilli = millisecondsPerFrame(singleBird._speed) * Math.max(singleBird.lifeSpan - 1, 1)
}
const singleTimer = new Timeblock(lifeMilli, handleTimer, singleBird)
addTimer(singleTimer)
return singleTimer
}
// abstracted for re-adding to the pool on a wake()
export const addTimer = singleTimer => {
_timerPool.push(singleTimer)
}
// ----------------------------------------------------------------------------------------------------
// PRIVATE/HANDLER METHODS
function checkCadenceTotal() {
if (_cadencePool.length === 0) {
sleep()
_isActive = true
}
}
function handleRAF() {
// console.log('handleRAF()')
// console.log('handleRAF(), _isPaused:', _isPaused, '| _isActive:', _isActive, '| _cadences:', _cadencePool)
if (!_isPaused) {
// loop through arrays backwards incase of removals mid loop
for (var i = _cadencePool.length - 1; i >= 0; i--) {
_cadencePool[i].tick()
}
// especially timers
for (var i = _timerPool.length - 1; i >= 0; i--) {
_timerPool[i].tick()
}
// this SHOULD never be hit, but warns that RAF is still running with no cadences
if (_cadencePool.length === 0) {
console.warn('handleRAF()')
}
window.requestAnimationFrame(handleRAF)
}
}
function handleTimer(singleTimer) {
// console.log(':: handleTimer() ::', _timerPool.length, singleTimer)
// remove the timer from pool
arrayRemove(_timerPool, singleTimer)
// this scope is single Hummingbird instance
const singleBird = this
// call the onComplete on the bird
singleBird.onComplete.apply(singleBird, singleBird.onCompleteParams)
// release the bird
singleBird.release()
}
Source