* Removes (and optionally destroys) a child from the container. * (removal is immediate and unconditional) * Never use keepalive=true with objects from pool. Doing so will create a memory leak. * @param {Renderable|Entity|Sprite|Collectable|Trigger|Draggable|DropTarget|NineSlic
(child, keepalive)
| 789 | * @param {boolean} [keepalive=False] - True to prevent calling child.destroy() |
| 790 | */ |
| 791 | removeChildNow(child, keepalive) { |
| 792 | if (this.hasChild(child) && this.getChildIndex(child) >= 0) { |
| 793 | if (typeof child.onDeactivateEvent === "function") { |
| 794 | child.onDeactivateEvent(); |
| 795 | } |
| 796 | |
| 797 | const root = this.getRootAncestor(); |
| 798 | |
| 799 | // Evict the child (and any descendants if it's a container) |
| 800 | // from the broadphase quadtree. The broadphase is rebuilt |
| 801 | // each `world.update()`, but pointer events and narrow-phase |
| 802 | // queries can fire between the deferred `removeChildNow` and |
| 803 | // the next rebuild — a stale reference there would return a |
| 804 | // destroyed renderable and crash any caller that read its |
| 805 | // `pos` (e.g. `_sortReverseZ`). |
| 806 | if (root?.broadphase) { |
| 807 | if (typeof child.addChild === "function") { |
| 808 | root.broadphase.removeContainer(child); |
| 809 | } |
| 810 | root.broadphase.remove(child); |
| 811 | } |
| 812 | |
| 813 | // Remove the body first to avoid a condition where a body can |
| 814 | // be detached from its parent before it's removed from the |
| 815 | // game world. `child.body` may be a melonJS `Body` (legacy / |
| 816 | // builtin adapter) or an adapter-specific handle (e.g. |
| 817 | // `Matter.Body` under the matter adapter); either way the |
| 818 | // adapter knows how to clean it up by renderable identity. |
| 819 | if (child.body) { |
| 820 | /** @type {{ adapter?: { removeBody?: (r: object) => void } }} */ |
| 821 | const world = root; |
| 822 | if (world?.adapter?.removeBody) { |
| 823 | world.adapter.removeBody(child); |
| 824 | } else if (child.body instanceof Body) { |
| 825 | // Container detached from the world tree — fall back to |
| 826 | // the legacy `world.removeBody(body)` path if available. |
| 827 | // This only handles a legacy `Body`; an adapter-specific |
| 828 | // handle reaching this branch silently leaks (warn so it |
| 829 | // surfaces in development). |
| 830 | root?.removeBody?.(child.body); |
| 831 | } else { |
| 832 | console.warn( |
| 833 | "melonJS: removeChildNow could not clean up an adapter-specific body — the container is not attached to a world with an adapter. The body remains in the physics engine.", |
| 834 | ); |
| 835 | } |
| 836 | } |
| 837 | |
| 838 | if (!keepalive) { |
| 839 | // attempt at recycling the object |
| 840 | if (pool.push(child, false) === false) { |
| 841 | // else just destroy it |
| 842 | if (typeof child.destroy === "function") { |
| 843 | child.destroy(); |
| 844 | } |
| 845 | } |
| 846 | } |
| 847 | |
| 848 | // Don't cache the child index; another element might have been removed |