import { Container , DisplayObject } from '@pixi/display' ; import { Renderer , MaskData } from '@pixi/core' ; import { Rectangle } from '@pixi/math' ; /** Interface for a child of a LinkedListContainer (has the prev/next properties added) */ export interface LinkedListChild extends DisplayObject { nextChild : LinkedListChild | null ; prevChild : LinkedListChild | null ; } /**
 * A semi-experimental Container that uses a doubly linked list to manage children instead of an
 * array. This means that adding/removing children often is not the same performance hit that
 * it would to be continually pushing/splicing.
 * However, this is primarily intended to be used for heavy particle usage, and may not handle
 * edge cases well if used as a complete Container replacement.
 */ export class LinkedListContainer extends Container { private _firstChild : LinkedListChild | null = null ; private _lastChild : LinkedListChild | null = null ; private _childCount = 0 ; public get firstChild ( ) : LinkedListChild { return this . _firstChild ; } public get lastChild ( ) : LinkedListChild { return this . _lastChild ; } public get childCount ( ) : number { return this . _childCount ; } public addChild < T extends DisplayObject [ ] > ( ... children ) { if ( children . length > 1 ) { for ( let i = 0 ; i < children . length ; i ++ ) { this . addChild ( children [ i ] ) ; } } else { const child = children [ 0 ] as LinkedListChild ; if ( child . parent ) { child . parent . removeChild ( child ) ; } child . parent = this ; this . sortDirty = true ; child . transform . _parentID = - 1 ; if ( this . _lastChild ) { this . _lastChild . nextChild = child ; child . prevChild = this . _lastChild ; this . _lastChild = child ; } else { this . _firstChild = this . _lastChild = child ; } ++ this . _childCount ; this . _boundsID ++ ; this . onChildrenChange ( ) ; this . emit ( 'childAdded' , child , this , this . _childCount ) ; child . emit ( 'added' , this ) ; } return children [ 0 ] ; } public addChildAt < T extends DisplayObject > ( child , index ) { if ( index < 0 || index > this . _childCount ) { throw new Error ( `addChildAt: The index ${ index } supplied is out of bounds ${ this . _childCount }` ) ; } if ( child . parent ) { child . parent . removeChild ( child ) ; } child . parent = this ; this . sortDirty = true ; child . transform . _parentID = - 1 ; const c = ( child as any ) as LinkedListChild ; if ( ! this . _firstChild ) { this . _firstChild = this . _lastChild = c ; } else if ( index === 0 ) { this . _firstChild . prevChild = c ; c . nextChild = this . _firstChild ; this . _firstChild = c ; } else if ( index === this . _childCount ) { this . _lastChild . nextChild = c ; c . prevChild = this . _lastChild ; this . _lastChild = c ; } else { let i = 0 ; let target = this . _firstChild ; while ( i < index ) { target = target . nextChild ; ++ i ; } target . prevChild . nextChild = c ; c . prevChild = target . prevChild ; c . nextChild = target ; target . prevChild = c ; } ++ this . _childCount ; this . _boundsID ++ ; this . onChildrenChange ( index ) ; child . emit ( 'added' , this ) ; this . emit ( 'childAdded' , child , this , index ) ; return child ; } param child param relative return public addChildBelow < T extends DisplayObject > ( child , relative ) { if ( relative . parent !== this ) { throw new Error ( `addChildBelow: The relative target must be a child of this parent` ) ; } if ( child . parent ) { child . parent . removeChild ( child ) ; } child . parent = this ; this . sortDirty = true ; child . transform . _parentID = - 1 ; ( relative as LinkedListChild ) . prevChild . nextChild = ( child as any as LinkedListChild ) ; ( child as any as LinkedListChild ) . prevChild = ( relative as LinkedListChild ) . prevChild ; ( child as any as LinkedListChild ) . nextChild = ( relative as LinkedListChild ) ; ( relative as LinkedListChild ) . prevChild = ( child as any as LinkedListChild ) ; if ( this . _firstChild === relative ) { this . _firstChild = ( child as any as LinkedListChild ) ; } ++ this . _childCount ; this . _boundsID ++ ; this . onChildrenChange ( ) ; this . emit ( 'childAdded' , child , this , this . _childCount ) ; child . emit ( 'added' , this ) ; return child ; } param child param relative return public addChildAbove < T extends DisplayObject > ( child , relative ) { if ( relative . parent !== this ) { throw new Error ( `addChildBelow: The relative target must be a child of this parent` ) ; } if ( child . parent ) { child . parent . removeChild ( child ) ; } child . parent = this ; this . sortDirty = true ; child . transform . _parentID = - 1 ; ( relative as LinkedListChild ) . nextChild . prevChild = ( child as any as LinkedListChild ) ; ( child as any as LinkedListChild ) . nextChild = ( relative as LinkedListChild ) . nextChild ; ( child as any as LinkedListChild ) . prevChild = ( relative as LinkedListChild ) ; ( relative as LinkedListChild ) . nextChild = ( child as any as LinkedListChild ) ; if ( this . _lastChild === relative ) { this . _lastChild = ( child as any as LinkedListChild ) ; } ++ this . _childCount ; this . _boundsID ++ ; this . onChildrenChange ( ) ; this . emit ( 'childAdded' , child , this , this . _childCount ) ; child . emit ( 'added' , this ) ; return child ; } public swapChildren ( child , child2 ) { if ( child === child2 || child . parent !== this || child2 . parent !== this ) { return ; } const { prevChild , nextChild } = ( child as LinkedListChild ) ; ( child as LinkedListChild ) . prevChild = ( child2 as LinkedListChild ) . prevChild ; ( child as LinkedListChild ) . nextChild = ( child2 as LinkedListChild ) . nextChild ; ( child2 as LinkedListChild ) . prevChild = prevChild ; ( child2 as LinkedListChild ) . nextChild = nextChild ; if ( this . _firstChild === child ) { this . _firstChild = child2 as LinkedListChild ; } else if ( this . _firstChild === child2 ) { this . _firstChild = child as LinkedListChild ; } if ( this . _lastChild === child ) { this . _lastChild = child2 as LinkedListChild ; } else if ( this . _lastChild === child2 ) { this . _lastChild = child as LinkedListChild ; } this . onChildrenChange ( ) ; } public getChildIndex ( child ) { let index = 0 ; let test = this . _firstChild ; while ( test ) { if ( test === child ) { break ; } test = test . nextChild ; ++ index ; } if ( ! test ) { throw new Error ( 'The supplied DisplayObject must be a child of the caller' ) ; } return index ; } setChildIndex ( child , index ) { if ( index < 0 || index >= this . _childCount ) { throw new Error ( `The index ${ index } supplied is out of bounds ${ this . _childCount }` ) ; } if ( child . parent !== this ) { throw new Error ( 'The supplied DisplayObject must be a child of the caller' ) ; } if ( ( child as LinkedListChild ) . nextChild ) { ( child as LinkedListChild ) . nextChild . prevChild = ( child as LinkedListChild ) . prevChild ; } if ( ( child as LinkedListChild ) . prevChild ) { ( child as LinkedListChild ) . prevChild . nextChild = ( child as LinkedListChild ) . nextChild ; } if ( this . _firstChild === ( child as LinkedListChild ) ) { this . _firstChild = ( child as LinkedListChild ) . nextChild ; } if ( this . _lastChild === ( child as LinkedListChild ) ) { this . _lastChild = ( child as LinkedListChild ) . prevChild ; } ( child as LinkedListChild ) . nextChild = null ; ( child as LinkedListChild ) . prevChild = null ; if ( ! this . _firstChild ) { this . _firstChild = this . _lastChild = ( child as LinkedListChild ) ; } else if ( index === 0 ) { this . _firstChild . prevChild = ( child as LinkedListChild ) ; ( child as LinkedListChild ) . nextChild = this . _firstChild ; this . _firstChild = ( child as LinkedListChild ) ; } else if ( index === this . _childCount ) { this . _lastChild . nextChild = ( child as LinkedListChild ) ; ( child as LinkedListChild ) . prevChild = this . _lastChild ; this . _lastChild = ( child as LinkedListChild ) ; } else { let i = 0 ; let target = this . _firstChild ; while ( i < index ) { target = target . nextChild ; ++ i ; } target . prevChild . nextChild = ( child as LinkedListChild ) ; ( child as LinkedListChild ) . prevChild = target . prevChild ; ( child as LinkedListChild ) . nextChild = target ; target . prevChild = ( child as LinkedListChild ) ; } this . onChildrenChange ( index ) ; } public removeChild < T extends DisplayObject [ ] > ( ... children ) { if ( children . length > 1 ) { for ( let i = 0 ; i < children . length ; i ++ ) { this . removeChild ( children [ i ] ) ; } } else { const child = children [ 0 ] as LinkedListChild ; if ( child . parent !== this ) return null ; child . parent = null ; child . transform . _parentID = - 1 ; if ( child . nextChild ) { child . nextChild . prevChild = child . prevChild ; } if ( child . prevChild ) { child . prevChild . nextChild = child . nextChild ; } if ( this . _firstChild === child ) { this . _firstChild = child . nextChild ; } if ( this . _lastChild === child ) { this . _lastChild = child . prevChild ; } child . nextChild = null ; child . prevChild = null ; -- this . _childCount ; this . _boundsID ++ ; this . onChildrenChange ( ) ; child . emit ( 'removed' , this ) ; this . emit ( 'childRemoved' , child , this ) ; } return children [ 0 ] ; } public getChildAt ( index ) { if ( index < 0 || index >= this . _childCount ) { throw new Error ( `getChildAt: Index (${ index }) does not exist.` ) ; } if ( index === 0 ) { return this . _firstChild ; } else if ( index === this . _childCount ) { return this . _lastChild ; } let i = 0 ; let target = this . _firstChild ; while ( i < index ) { target = target . nextChild ; ++ i ; } return target ; } public removeChildAt ( index ) { const child = this . getChildAt ( index ) as LinkedListChild ; child . parent = null ; child . transform . _parentID = - 1 ; if ( child . nextChild ) { child . nextChild . prevChild = child . prevChild ; } if ( child . prevChild ) { child . prevChild . nextChild = child . nextChild ; } if ( this . _firstChild === child ) { this . _firstChild = child . nextChild ; } if ( this . _lastChild === child ) { this . _lastChild = child . prevChild ; } child . nextChild = null ; child . prevChild = null ; -- this . _childCount ; this . _boundsID ++ ; this . onChildrenChange ( index ) ; child . emit ( 'removed' , this ) ; this . emit ( 'childRemoved' , child , this , index ) ; return child ; } public removeChildren ( beginIndex = 0 , endIndex = this . _childCount ) { const begin = beginIndex ; const end = endIndex ; const range = end - begin ; if ( range > 0 && range <= end ) { const removed = [ ] ; let child = this . _firstChild ; for ( let i = 0 ; i <= end && child ; ++ i , child = child . nextChild ) { if ( i >= begin ) { removed . push ( child ) ; } } const prevChild = removed [ 0 ] . prevChild ; const nextChild = removed [ removed . length - 1 ] . nextChild ; if ( ! nextChild ) { this . _lastChild = prevChild ; } else { nextChild . prevChild = prevChild ; } if ( ! prevChild ) { this . _firstChild = nextChild ; } else { prevChild . nextChild = nextChild ; } for ( let i = 0 ; i < removed . length ; ++ i ) { removed [ i ] . parent = null ; if ( removed [ i ] . transform ) { removed [ i ] . transform . _parentID = - 1 ; } removed [ i ] . nextChild = null ; removed [ i ] . prevChild = null ; } this . _boundsID ++ ; this . onChildrenChange ( beginIndex ) ; for ( let i = 0 ; i < removed . length ; ++ i ) { removed [ i ] . emit ( 'removed' , this ) ; this . emit ( 'childRemoved' , removed [ i ] , this , i ) ; } return removed ; } else if ( range === 0 && this . _childCount === 0 ) { return [ ] ; } throw new RangeError ( 'removeChildren: numeric values are outside the acceptable range.' ) ; } /**
     * Updates the transform on all children of this container for rendering.
     * Copied from and overrides PixiJS v5 method (v4 method is identical)
     */ updateTransform ( ) { this . _boundsID ++ ; this . transform . updateTransform ( this . parent . transform ) ; this . worldAlpha = this . alpha * this . parent . worldAlpha ; let child ; let next ; for ( child = this . _firstChild ; child ; child = next ) { next = child . nextChild ; if ( child . visible ) { child . updateTransform ( ) ; } } } /**
     * Recalculates the bounds of the container.
     * Copied from and overrides PixiJS v5 method (v4 method is identical)
     */ calculateBounds ( ) { this . _bounds . clear ( ) ; this . _calculateBounds ( ) ; let child ; let next ; for ( child = this . _firstChild ; child ; child = next ) { next = child . nextChild ; if ( ! child . visible || ! child . renderable ) { continue ; } child . calculateBounds ( ) ; if ( child . _mask ) { const maskObject = ( ( child . _mask as MaskData ) . maskObject || child . _mask ) as Container ; maskObject . calculateBounds ( ) ; this . _bounds . addBoundsMask ( child . _bounds , maskObject . _bounds ) ; } else if ( child . filterArea ) { this . _bounds . addBoundsArea ( child . _bounds , child . filterArea ) ; } else { this . _bounds . addBounds ( child . _bounds ) ; } } this . _bounds . updateID = this . _boundsID ; } /**
     * Retrieves the local bounds of the displayObject as a rectangle object. Copied from and overrides PixiJS v5 method
     */ public getLocalBounds ( rect ? , skipChildrenUpdate = false ) { const result = DisplayObject . prototype . getLocalBounds . call ( this , rect ) ; if ( ! skipChildrenUpdate ) { let child ; let next ; for ( child = this . _firstChild ; child ; child = next ) { next = child . nextChild ; if ( child . visible ) { child . updateTransform ( ) ; } } } return result ; } /**
     * Renders the object using the WebGL renderer. Copied from and overrides PixiJS v5 method
     */ render ( renderer ) { if ( ! this . visible || this . worldAlpha <= 0 || ! this . renderable ) { return ; } if ( this . _mask || ( this . filters && this . filters . length ) ) { this . renderAdvanced ( renderer ) ; } else { this . _render ( renderer ) ; let child ; let next ; for ( child = this . _firstChild ; child ; child = next ) { next = child . nextChild ; child . render ( renderer ) ; } } } /**
     * Render the object using the WebGL renderer and advanced features. Copied from and overrides PixiJS v5 method
     */ protected renderAdvanced ( renderer ) { renderer . batch . flush ( ) ; const filters = this . filters ; const mask = this . _mask ; if ( filters ) { if ( ! this . _enabledFilters ) { this . _enabledFilters = [ ] ; } this . _enabledFilters . length = 0 ; for ( let i = 0 ; i < filters . length ; i ++ ) { if ( filters [ i ] . enabled ) { this . _enabledFilters . push ( filters [ i ] ) ; } } if ( this . _enabledFilters . length ) { renderer . filter . push ( this , this . _enabledFilters ) ; } } if ( mask ) { renderer . mask . push ( this , this . _mask ) ; } this . _render ( renderer ) ; let child ; let next ; for ( child = this . _firstChild ; child ; child = next ) { next = child . nextChild ; child . render ( renderer ) ; } renderer . batch . flush ( ) ; if ( mask ) { renderer . mask . pop ( this ) ; } if ( filters && this . _enabledFilters && this . _enabledFilters . length ) { renderer . filter . pop ( ) ; } } /**
     * Renders the object using the Canvas renderer. Copied from and overrides PixiJS Canvas mixin in V5 and V6.
     */ renderCanvas ( renderer ) { if ( ! this . visible || this . worldAlpha <= 0 || ! this . renderable ) { return ; } if ( this . _mask ) { renderer . maskManager . pushMask ( this . _mask ) ; } ( this as any ) . _renderCanvas ( renderer ) ; let child ; let next ; for ( child = this . _firstChild ; child ; child = next ) { next = child . nextChild ; ( child as any ) . renderCanvas ( renderer ) ; } if ( this . _mask ) { renderer . maskManager . popMask ( renderer ) ; } } }