/
githubmirror
/
pixijs
Обзор
Документация
Войти
/
githubmirror
/
pixijs
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
dev
src/scene/container/__tests__/RenderGroup.test.ts
795 строк
24 KB
Zyie
fix: prevent duplicate onRender registrations from same-parent reorders (#12120)
11 авг 2026, 15:37
Не верифицирован
11 авг 2026, 15:37
3151a0f
Код
Авторство
О чём код?
import { Sprite } from '../../sprite/Sprite'; import { Container } from '../Container'; import { RenderGroup } from '../RenderGroup'; import { updateRenderGroupTransforms } from '../utils/updateRenderGroupTransforms'; import { getWebGLRenderer } from '@test-utils'; import { Texture } from '~/rendering'; import { BigPool } from '~/utils'; // now that we don't actually remove the items, but instead ensure that they are skipped // in the update loop, this function will return the new list and index removing items that are intended to be skipped // when the update function is called. // function _processUpdateList(toUpdateList: {list: Container[], index: number}, renderGroup: RenderGroup) { const newP = toUpdateList.list.filter((c) => { if (c.renderGroup) return false; const r = c.parentRenderGroup; return (r === renderGroup); }); return { list: newP, index: newP.length }; } function compareUpdateList(expected: Container[], renderGroup: RenderGroup, index: number, debug = false) { const childrenToUpdate = _processUpdateList(renderGroup.childrenToUpdate[index], renderGroup); if (debug) { // eslint-disable-next-line no-console console.log(renderGroup.childrenToUpdate[index].list.map((c) => c.label)); // eslint-disable-next-line no-console console.log(childrenToUpdate.list.map((c) => c.label)); } expect(childrenToUpdate.list).toEqual(expected); expect(childrenToUpdate.index).toEqual(expected.length); } describe('RenderGroup', () => { it('should set correct render group a child', async () => { const container = new Container(); const childPre = new Container(); container.addChild(childPre); container.isRenderGroup = true; const childPost = new Container(); container.addChild(childPost); expect(container.children).toHaveLength(2); expect(container.renderGroup.getChildren()).toHaveLength(2); expect(childPre.parentRenderGroup).toEqual(container.renderGroup); expect(childPost.parentRenderGroup).toEqual(container.renderGroup); }); it('should not enable a render group twice', async () => { const container = new Container(); container.isRenderGroup = true; const layerGroup = container.renderGroup; container.isRenderGroup = true; expect(layerGroup).toBe(container.renderGroup); }); it('should set correct render groups a child with a more complex scene graph', async () => { const container = new Container(); container.isRenderGroup = true; const container2 = new Container(); const child = new Container(); const child2 = new Container(); const child3 = new Container(); // |- container // |- child // |- container2 // |- child2 // |- child3 container.addChild(child); container.addChild(container2); container2.addChild(child2); container2.addChild(child3); expect(container.children).toHaveLength(2); expect(container.renderGroup.getChildren()).toHaveLength(4); expect(child2.parentRenderGroup).toEqual(container.renderGroup); expect(child3.parentRenderGroup).toEqual(container.renderGroup); expect(container.relativeRenderGroupDepth).toEqual(0); expect(container2.relativeRenderGroupDepth).toEqual(1); expect(child2.relativeRenderGroupDepth).toEqual(2); expect(child3.relativeRenderGroupDepth).toEqual(2); }); it('should set multiple correct layer group a child with a more complex scene graph', async () => { const container = new Container(); container.isRenderGroup = true; const container2 = new Container(); container2.isRenderGroup = true; const child = new Container(); const child2 = new Container(); const child3 = new Container(); // |- container // renderGroup // |- child // |- container2 // renderGroup // |- child2 // |- child3 container.addChild(child); container.addChild(container2); container2.addChild(child2); container2.addChild(child3); expect(container.renderGroup === container2.renderGroup).toBeFalse(); expect(container.renderGroup.getChildren()).toHaveLength(2); expect(container2.renderGroup.getChildren()).toHaveLength(2); expect(container.renderGroup).toEqual(container.renderGroup); expect(container2.renderGroup).toEqual(container2.renderGroup); expect(container2.parentRenderGroup).toEqual(container.renderGroup); expect(child.parentRenderGroup).toEqual(container.renderGroup); expect(child2.parentRenderGroup).toEqual(container2.renderGroup); expect(child3.parentRenderGroup).toEqual(container2.renderGroup); expect(container.renderGroup.renderGroupChildren).toHaveLength(1); expect(container2.renderGroup.renderGroupChildren).toHaveLength(0); }); it('should set reparent from one render group to another group', async () => { const container = new Container(); container.isRenderGroup = true; const container2 = new Container(); container2.isRenderGroup = true; const child = new Container(); const child2 = new Container(); const child3 = new Container(); // |- container // renderGroup // |- child // |- container2 // renderGroup // |- child2 // |- child3 container.addChild(child); container.addChild(container2); container2.addChild(child2); container2.addChild(child3); // |- container // renderGroup // |- container2 // renderGroup // |- child2 // |- child3 // |- child container2.addChild(child); expect(container.renderGroup === container2.renderGroup).toBeFalse(); expect(container.renderGroup.getChildren()).toHaveLength(1); expect(container2.renderGroup.getChildren()).toHaveLength(3); expect(container.renderGroup).toEqual(container.renderGroup); expect(container2.parentRenderGroup).toEqual(container.renderGroup); // expect(child.renderGroup).toEqual(container2.renderGroup); expect(child2.renderGroup).toBeNull(); expect(child3.renderGroup).toBeNull(); expect(container.parentRenderGroup).toBeNull(); expect(child2.parentRenderGroup).toEqual(container2.renderGroup); expect(child3.parentRenderGroup).toEqual(container2.renderGroup); expect(container.renderGroup.renderGroupChildren).toHaveLength(1); expect(container2.renderGroup.renderGroupChildren).toHaveLength(0); // |- container // renderGroup // |- container2 // renderGroup // |- child2 // |- child // |- child3 container.addChild(child3); expect(container.renderGroup.getChildren()).toHaveLength(2); expect(container2.renderGroup.getChildren()).toHaveLength(2); expect(container.renderGroup).toEqual(container.renderGroup); expect(container2.parentRenderGroup).toEqual(container.renderGroup); expect(child2.parentRenderGroup).toEqual(container2.renderGroup); expect(child3.parentRenderGroup).toEqual(container.renderGroup); expect(container.renderGroup.renderGroupChildren).toHaveLength(1); expect(container2.renderGroup.renderGroupChildren).toHaveLength(0); }); // REparents a rendergroup.. it('should set reparent on render group to another', async () => { const container = new Container(); container.isRenderGroup = true; const container2 = new Container(); container2.isRenderGroup = true; const child = new Container(); const child2 = new Container(); const child3 = new Container(); container.addChild(child); container.addChild(container2); container2.addChild(child2); container2.addChild(child3); // |- container // renderGroup // |- child // |- container2 // renderGroup // |- child2 // |- child3 // child.addChild(container2); // |- container // renderGroup // |- child // |- container2 // renderGroup // |- child2 // |- child3 // expect(child.parentRenderGroup).toBe(container.renderGroup); expect(container2.parentRenderGroup).toBe(container.renderGroup); expect(container.parentRenderGroup).toBeNull(); expect(child2.parentRenderGroup).toBe(container2.renderGroup); expect(child3.parentRenderGroup).toBe(container2.renderGroup); expect(container.renderGroup === container2.renderGroup).toBeFalse(); expect(container.renderGroup.getChildren()).toHaveLength(2); expect(container2.renderGroup.getChildren()).toHaveLength(2); expect(container2.parent).toBe(child); expect(child.children).toHaveLength(1); expect(container.renderGroup.renderGroupChildren).toHaveLength(1); expect(container2.renderGroup.renderGroupChildren).toHaveLength(0); child.isRenderGroup = true; // |- container // renderGroup depth: 0 // |- child // renderGroup // |- container2 // renderGroup // |- child2 // |- child3 expect(container.renderGroup.renderGroupChildren).toHaveLength(1); expect(child.renderGroup.renderGroupChildren).toHaveLength(1); expect(container2.renderGroup.renderGroupChildren).toHaveLength(0); expect(child.renderGroup.getChildren()).toHaveLength(1); expect(container2.renderGroup.getChildren()).toHaveLength(2); container.addChild(container2); expect(container.renderGroup.renderGroupChildren).toHaveLength(2); expect(container.children).toHaveLength(2); expect(child.renderGroup.renderGroupChildren).toHaveLength(0); expect(container2.renderGroup.renderGroupChildren).toHaveLength(0); expect(child.renderGroup.getChildren()).toHaveLength(0); expect(container2.renderGroup.getChildren()).toHaveLength(2); expect(container.relativeRenderGroupDepth).toEqual(0); expect(child.relativeRenderGroupDepth).toEqual(1); expect(container2.relativeRenderGroupDepth).toEqual(1); expect(child3.relativeRenderGroupDepth).toEqual(1); }); // eslint-disable-next-line jest/expect-expect it('should register update in the update array..', async () => { const container = new Container({ label: 'container' }); container.isRenderGroup = true; const container2 = new Container({ label: 'container2' }); const child = new Container({ label: 'child' }); const child2 = new Container({ label: 'child2' }); const child3 = new Container({ label: 'child3' }); container.addChild(child); compareUpdateList([child], container.renderGroup, 1); container.addChild(container2); compareUpdateList([child, container2], container.renderGroup, 1); container2.addChild(child2); container2.addChild(child3); compareUpdateList([child, container2], container.renderGroup, 1); compareUpdateList([child2, child3], container.renderGroup, 2); // return; // |- container // renderGroup // |- child // |- container2 // |- child2 // |- child3 // container.x = 100; child.x = 100; compareUpdateList([child, container2], container.renderGroup, 1); child.x = 110; compareUpdateList([child, container2], container.renderGroup, 1); child3.x = 30; compareUpdateList([child, container2], container.renderGroup, 1); compareUpdateList([child2, child3], container.renderGroup, 2); }); it('should appear in update array correctly if it changes layer group', async () => { const container = new Container(); container.isRenderGroup = true; const container2 = new Container(); container2.isRenderGroup = true; // |- container // renderGroup // |- child // |- container2 // render group // |- child2 // |- child3 // const child = new Container(); const child2 = new Container(); const child3 = new Container(); container.addChild(child); container.addChild(container2); container2.addChild(child2); container2.addChild(child3); child.x = 100; compareUpdateList([child], container.renderGroup, 1); expect(container.renderGroup.structureDidChange).toBeTrue(); // |- container // renderGroup // |- container2 // render group // |- child2 // |- child // |- child3 child2.addChild(child); compareUpdateList([], container.renderGroup, 1); compareUpdateList([child], container2.renderGroup, 2); }); // eslint-disable-next-line jest/expect-expect it('should appear in update array correctly if it changes layer group parents', async () => { const container = new Container({ label: 'container' }); container.isRenderGroup = true; const container2 = new Container({ label: 'container2' }); container2.isRenderGroup = true; // |- container // renderGroup // |- child // |- container2 // render group // |- child2 // render group // |- child3 // const child = new Container({ label: 'child' }); const child2 = new Container({ label: 'child2' }); child2.isRenderGroup = true; const child3 = new Container({ label: 'child3' }); container.addChild(child); container.addChild(container2); container2.addChild(child2); container2.addChild(child3); child2.x = 100; // |- container // renderGroup // |- child // |- child2 // render group // |- container2 // render group // |- child3 // container.addChild(child2); compareUpdateList([child], container.renderGroup, 1); compareUpdateList([child3], container2.renderGroup, 1); child2.x = 110; compareUpdateList([child], container.renderGroup, 1); compareUpdateList([child3], container2.renderGroup, 1); }); it('should update layers correctly if you attach a render group and it has updated children..', async () => { const container = new Container(); container.isRenderGroup = true; const container2 = new Container({ label: 'container2' }); // |- container // renderGroup // |- child // |- container2 // |- child2 // |- child3 // const child = new Container({ label: 'child' }); const child2 = new Container({ label: 'child2' }); const child3 = new Container({ label: 'child3' }); container.addChild(child); container.addChild(container2); container2.addChild(child2); container2.addChild(child3); child2.x = 100; compareUpdateList([child2, child3], container.renderGroup, 2); compareUpdateList([child, container2], container.renderGroup, 1); container2.isRenderGroup = true; // |- container // renderGroup // |- child // |- container2 // renderGroup // |- child2 // |- child3 compareUpdateList([], container.renderGroup, 2); compareUpdateList([child], container.renderGroup, 1); container.removeChild(container2); compareUpdateList([child], container.renderGroup, 1); expect(true).toBeTrue(); }); it('should update isSimple when enabling the layer', async () => { const container = new Container(); container.isRenderGroup = true; expect(container.isSimple).toEqual(false); container.isRenderGroup = false; expect(container.isSimple).toEqual(true); }); it('should add and then remove the render group correctly', async () => { const container = new Container(); container.isRenderGroup = true; const container2 = new Container({ label: 'container2' }); // |- container // renderGroup // |- child // |- container2 // |- child2 // |- child3 // const child = new Container({ label: 'child' }); const child2 = new Container({ label: 'child2' }); const child3 = new Container({ label: 'child3' }); container.addChild(child); container.addChild(container2); container2.addChild(child2); container2.addChild(child3); // child2.x = 100; compareUpdateList([child, container2], container.renderGroup, 1, true); container2.isRenderGroup = true; // |- container // renderGroup // |- child // |- container2 // renderGroup // |- child2 // |- child3 compareUpdateList([child], container.renderGroup, 1, true); container2.isRenderGroup = false; // |- container // renderGroup // |- child // |- container2 // |- child2 // |- child3 expect(container2.parentRenderGroup).toBe(container.renderGroup); expect(container2.renderGroup).toBe(null); expect(child2.parentRenderGroup).toBe(container.renderGroup); expect(child3.parentRenderGroup).toBe(container.renderGroup); // the three updates here include the container being added / removed / added. // not really an issue as the loop will skip the container2 updates after the first. // this is faster to do this than splice the update instances every time something is added or removed compareUpdateList([child, container2, container2, container2], container.renderGroup, 1, true); }); it('should remove references to items in the update children list if no longer used', async () => { const renderer = await getWebGLRenderer(); BigPool.getPool(RenderGroup).clear(); const container = new Container({ isRenderGroup: true }); const sprite = new Sprite(Texture.WHITE); container.addChild(sprite); renderer.render(container); sprite.texture = Texture.EMPTY; expect(container.renderGroup.childrenToUpdate[1].list[0]).toBe(sprite); expect(container.renderGroup.childrenRenderablesToUpdate.list[0]).toBe(sprite); container.removeChild(sprite); renderer.render(container); expect(container.renderGroup.childrenToUpdate[1].list.length).toBe(1); expect(container.renderGroup.childrenToUpdate[1].list[0]).toBe(null); expect(container.renderGroup.childrenRenderablesToUpdate.list.length).toBe(1); expect(container.renderGroup.childrenRenderablesToUpdate.list[0]).toBe(null); renderer.destroy(); }); it('should flag a render group as dirty if a order is changed', async () => { const renderer = await getWebGLRenderer(); BigPool.getPool(RenderGroup).clear(); const container = new Container({ isRenderGroup: true }); const a = new Container(); const b = new Container(); container.addChild(a); container.addChild(b); renderer.render(container); // should now be at the top! container.addChild(a); // this should now be true as the order has changed expect(container.renderGroup.structureDidChange).toBeTrue(); }); it('should update the children on the first pass after conversion to render group', async () => { const root = new Container({ isRenderGroup: true }); const container = new Container(); root.addChild(container); const child = new Container(); container.addChild(child); expect(child._updateFlags).toEqual(0b1111); updateRenderGroupTransforms(root.renderGroup, false); expect(child._updateFlags).toEqual(0b0); container.enableRenderGroup(); expect(child._updateFlags).toEqual(0b1111); }); describe('same-parent reorders', () => { it('should not duplicate onRender containers when reordering via addChildAt', () => { const container = new Container({ isRenderGroup: true }); const a = new Container(); const b = new Container(); a.onRender = jest.fn(); container.addChild(a, b); expect(container.renderGroup['_onRenderContainers']).toEqual([a]); container.addChildAt(a, 1); container.addChildAt(a, 0); expect(container.renderGroup['_onRenderContainers']).toEqual([a]); }); it('should not duplicate onRender containers when reordering via setChildIndex', () => { const container = new Container({ isRenderGroup: true }); const a = new Container(); const b = new Container(); a.onRender = jest.fn(); container.addChild(a, b); container.setChildIndex(a, 1); container.setChildIndex(a, 0); expect(container.renderGroup['_onRenderContainers']).toEqual([a]); }); it('should flag a render group as dirty and reorder children via addChildAt', async () => { const renderer = await getWebGLRenderer(); const container = new Container({ isRenderGroup: true }); const a = new Container(); const b = new Container(); const onRender = jest.fn(); a.onRender = onRender; container.addChild(a, b); renderer.render(container); expect(container.renderGroup.structureDidChange).toBeFalse(); container.addChildAt(a, 1); expect(container.renderGroup.structureDidChange).toBeTrue(); expect(container.children).toEqual([b, a]); renderer.render(container); expect(onRender).toHaveBeenCalledTimes(2); renderer.destroy(); }); it('should unregister onRender via removeChild after reorders', () => { const container = new Container({ isRenderGroup: true }); const a = new Container(); const b = new Container(); a.onRender = jest.fn(); container.addChild(a, b); container.addChildAt(a, 1); container.setChildIndex(a, 0); container.removeChild(a); expect(container.renderGroup['_onRenderContainers']).toEqual([]); }); }); it('removeOnRender should leave the list unchanged when the container is not registered', async () => { const renderer = await getWebGLRenderer(); BigPool.getPool(RenderGroup).clear(); const container = new Container({ isRenderGroup: true }); const a = new Container(); const b = new Container(); const c = new Container(); a.onRender = () => { /* noop */ }; b.onRender = () => { /* noop */ }; c.onRender = () => { /* noop */ }; container.addChild(a); container.addChild(b); // `c` is intentionally never parented under `container` renderer.render(container); const before = [...container.renderGroup['_onRenderContainers']]; // `c` was never registered in this renderGroup; this should be a no-op container.renderGroup.removeOnRender(c); expect(container.renderGroup['_onRenderContainers']).toEqual(before); renderer.destroy(); }); });