Skip to content

Commit

Permalink
Refactor isRenderable detection (#479)
Browse files Browse the repository at this point in the history
✨ What changed? ✨

* Decouple **isRenderable** from **hasRenderableOwner** as textures can
be loaded asynchronously.
* Refactored the `isRenderable` detection:
  - If a node has `alpha = 0` or is off screen its not renderable
- If a node has dimensions and color properties, its marked renderable
and we assume its a `ColorTexture`.
- If a node has a texture, it will be marked as a renderable owner.
Which will trigger loading of the texture (if its the first owner)
    - If the texture is loaded, it is marked renderable
* On `renderQuads` we assume everything has a texture if not we set the
defaultTexture for 1x1 sampling. This is implicit but matches the old
behaviour.
* `CoreTextNode` overwrites behaviour and checks for text !== ''.
* Also fixed a bug where the default texture was still loading but
already referenced.
* Added a `CoreNode` unit test to validate this more rapidly.

🔧 Addresses the following
Fixes #477
  • Loading branch information
wouterlucas authored Jan 10, 2025
2 parents 6174898 + a168da4 commit ad55881
Show file tree
Hide file tree
Showing 6 changed files with 260 additions and 125 deletions.
188 changes: 146 additions & 42 deletions src/core/CoreNode.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,55 +17,73 @@
* limitations under the License.
*/

import { describe, expect, it } from 'vitest';
import { describe, expect, it, vi } from 'vitest';
import { CoreNode, type CoreNodeProps, UpdateType } from './CoreNode.js';
import { Stage } from './Stage.js';
import { mock } from 'vitest-mock-extended';
import { type TextureOptions } from './CoreTextureManager.js';
import { type BaseShaderController } from '../main-api/ShaderController';
import { createBound } from './lib/utils.js';
import { ImageTexture } from './textures/ImageTexture.js';

describe('set color()', () => {
const defaultProps: CoreNodeProps = {
alpha: 0,
autosize: false,
clipping: false,
color: 0,
colorBl: 0,
colorBottom: 0,
colorBr: 0,
colorLeft: 0,
colorRight: 0,
colorTl: 0,
colorTop: 0,
colorTr: 0,
height: 0,
mount: 0,
mountX: 0,
mountY: 0,
parent: null,
pivot: 0,
pivotX: 0,
pivotY: 0,
rotation: 0,
rtt: false,
scale: 0,
scaleX: 0,
scaleY: 0,
shader: mock<BaseShaderController>(),
src: '',
texture: null,
textureOptions: {} as TextureOptions,
width: 0,
x: 0,
y: 0,
zIndex: 0,
zIndexLocked: 0,
preventCleanup: false,
strictBounds: false,
};
const defaultProps: CoreNodeProps = {
alpha: 0,
autosize: false,
clipping: false,
color: 0,
colorBl: 0,
colorBottom: 0,
colorBr: 0,
colorLeft: 0,
colorRight: 0,
colorTl: 0,
colorTop: 0,
colorTr: 0,
height: 0,
mount: 0,
mountX: 0,
mountY: 0,
parent: null,
pivot: 0,
pivotX: 0,
pivotY: 0,
rotation: 0,
rtt: false,
scale: 0,
scaleX: 0,
scaleY: 0,
shader: mock<BaseShaderController>(),
src: '',
texture: null,
textureOptions: {} as TextureOptions,
width: 0,
x: 0,
y: 0,
zIndex: 0,
zIndexLocked: 0,
preventCleanup: false,
strictBounds: false,
};

const clippingRect = {
x: 0,
y: 0,
width: 200,
height: 200,
valid: false,
};

const stage = mock<Stage>({
strictBound: createBound(0, 0, 200, 200),
preloadBound: createBound(0, 0, 200, 200),
defaultTexture: {
state: 'loaded',
},
});

describe('set color()', () => {
it('should set all color subcomponents.', () => {
const node = new CoreNode(mock<Stage>(), defaultProps);
const node = new CoreNode(stage, defaultProps);
node.colorBl = 0x99aabbff;
node.colorBr = 0xaabbccff;
node.colorTl = 0xbbcceeff;
Expand All @@ -85,11 +103,97 @@ describe('set color()', () => {
});

it('should set update type.', () => {
const node = new CoreNode(mock<Stage>(), defaultProps);
const node = new CoreNode(stage, defaultProps);
node.updateType = 0;

node.color = 0xffffffff;

expect(node.updateType).toBe(UpdateType.PremultipliedColors);
});
});

describe('isRenderable checks', () => {
it('should return false if node is not renderable', () => {
const node = new CoreNode(stage, defaultProps);
expect(node.isRenderable).toBe(false);
});

it('visible node that is a color texture', () => {
const node = new CoreNode(stage, defaultProps);
node.alpha = 1;
node.x = 0;
node.y = 0;
node.width = 100;
node.height = 100;
node.color = 0xffffffff;

node.update(0, clippingRect);
expect(node.isRenderable).toBe(true);
});

it('visible node that is a texture', () => {
const node = new CoreNode(stage, defaultProps);
node.alpha = 1;
node.x = 0;
node.y = 0;
node.width = 100;
node.height = 100;
node.texture = mock<ImageTexture>({
state: 'initial',
});

node.update(0, clippingRect);
expect(node.isRenderable).toBe(false);

node.texture.state = 'loaded';
node.setUpdateType(UpdateType.IsRenderable);
node.update(1, clippingRect);

expect(node.isRenderable).toBe(true);
});

it('a node with a texture with alpha 0 should not be renderable', () => {
const node = new CoreNode(stage, defaultProps);
node.alpha = 0;
node.x = 0;
node.y = 0;
node.width = 100;
node.height = 100;
node.texture = mock<ImageTexture>({
state: 'loaded',
});

node.update(0, clippingRect);
expect(node.isRenderable).toBe(false);
});

it('a node with a texture that is OutOfBounds should not be renderable', () => {
const node = new CoreNode(stage, defaultProps);
node.alpha = 1;
node.x = 300;
node.y = 300;
node.width = 100;
node.height = 100;
node.texture = mock<ImageTexture>({
state: 'loaded',
});

node.update(0, clippingRect);
expect(node.isRenderable).toBe(false);
});

it('a node with a freed texture should not be renderable', () => {
const node = new CoreNode(stage, defaultProps);
node.alpha = 1;
node.x = 0;
node.y = 0;
node.width = 100;
node.height = 100;
node.texture = mock<ImageTexture>({
state: 'freed',
});

node.update(0, clippingRect);
expect(node.isRenderable).toBe(false);
});
});
Loading

0 comments on commit ad55881

Please sign in to comment.