Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,21 @@ export class MyCustomTexture extends Texture {

private props: Required<MyCustomTextureProps>;

constructor(txManager: CoreTextureManager, props: MyCustomTextureProps) {
constructor(
txManager: CoreTextureManager,
props: Required<MyCustomTextureProps>,
) {
super(txManager);
this.props = MyCustomTexture.resolveDefaults(props);
this.props = props;
}

override async getTextureSource(): Promise<TextureData> {
const { percent, width, height } = this.props;
const radius = Math.min(width, height) / 2;
const { percent, w, h } = this.props;
const radius = Math.min(w, h) / 2;
const angle = 2 * Math.PI * (percent / 100);
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
canvas.width = w;
canvas.height = h;
const ctx = canvas.getContext('2d');
assertTruthy(ctx);
ctx.beginPath();
Expand All @@ -49,11 +52,6 @@ export class MyCustomTexture extends Texture {
ctx.fillStyle = 'blue';
ctx.fill();

this.setState('fetched', {
width,
height,
});

return {
data: ctx.getImageData(0, 0, canvas.width, canvas.height),
};
Expand All @@ -71,8 +69,8 @@ export class MyCustomTexture extends Texture {
): Required<MyCustomTextureProps> {
return {
percent: props.percent ?? 20,
w: props.width,
h: props.height,
w: props.w,
h: props.h,
};
}
}
130 changes: 87 additions & 43 deletions src/core/CoreNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -823,45 +823,52 @@ export class CoreNode extends EventEmitter {

//#region Textures
loadTexture(): void {
const { texture } = this.props;
if (!texture) {
if (this.props.texture === null) {
return;
}

// If texture is already loaded / failed, trigger loaded event manually
// so that users get a consistent event experience.
// We do this in a microtask to allow listeners to be attached in the same
// synchronous task after calling loadTexture()
queueMicrotask(() => {
if (this.textureOptions.preload === true) {
this.stage.txManager.loadTexture(texture);
}
queueMicrotask(this.loadTextureTask);
}

texture.preventCleanup =
this.props.textureOptions?.preventCleanup ?? false;
texture.on('loaded', this.onTextureLoaded);
texture.on('failed', this.onTextureFailed);
texture.on('freed', this.onTextureFreed);

// If the parent is a render texture, the initial texture status
// will be set to freed until the texture is processed by the
// Render RTT nodes. So we only need to listen fo changes and
// no need to check the texture.state until we restructure how
// textures are being processed.
if (this.parentHasRenderTexture) {
this.notifyParentRTTOfUpdate();
return;
}
/**
* Task for queueMicrotask to loadTexture
*
* @remarks
* This method is called in a microtask to release the texture.
*/
private loadTextureTask = (): void => {
const texture = this.texture as Texture;
if (this.textureOptions.preload === true) {
this.stage.txManager.loadTexture(texture);
}

if (texture.state === 'loaded') {
this.onTextureLoaded(texture, texture.dimensions!);
} else if (texture.state === 'failed') {
this.onTextureFailed(texture, texture.error!);
} else if (texture.state === 'freed') {
this.onTextureFreed(texture);
}
});
}
texture.preventCleanup = this.props.textureOptions?.preventCleanup ?? false;
texture.on('loaded', this.onTextureLoaded);
texture.on('failed', this.onTextureFailed);
texture.on('freed', this.onTextureFreed);

// If the parent is a render texture, the initial texture status
// will be set to freed until the texture is processed by the
// Render RTT nodes. So we only need to listen fo changes and
// no need to check the texture.state until we restructure how
// textures are being processed.
if (this.parentHasRenderTexture) {
this.notifyParentRTTOfUpdate();
return;
}

if (texture.state === 'loaded') {
this.onTextureLoaded(texture, texture.dimensions!);
} else if (texture.state === 'failed') {
this.onTextureFailed(texture, texture.error!);
} else if (texture.state === 'freed') {
this.onTextureFreed(texture);
}
};

unloadTexture(): void {
if (this.texture === null) {
Expand All @@ -872,7 +879,7 @@ export class CoreNode extends EventEmitter {
texture.off('loaded', this.onTextureLoaded);
texture.off('failed', this.onTextureFailed);
texture.off('freed', this.onTextureFreed);
texture.setRenderableOwner(this, false);
texture.setRenderableOwner(this._id, false);
}

protected onTextureLoaded: TextureLoadedEventHandler = (_, dimensions) => {
Expand Down Expand Up @@ -917,23 +924,30 @@ export class CoreNode extends EventEmitter {
// immediately set isRenderable to false, so that we handle the error
// without waiting for the next frame loop
this.isRenderable = false;
this.updateTextureOwnership(false);
this.setUpdateType(UpdateType.IsRenderable);

// If parent has a render texture, flag that we need to update
if (this.parentHasRenderTexture) {
this.notifyParentRTTOfUpdate();
}

this.emit('failed', {
type: 'texture',
error,
} satisfies NodeTextureFailedPayload);
if (
this.texture !== null &&
this.texture.retryCount > this.texture.maxRetryCount
) {
this.emit('failed', {
type: 'texture',
error,
} satisfies NodeTextureFailedPayload);
}
};

private onTextureFreed: TextureFreedEventHandler = () => {
// immediately set isRenderable to false, so that we handle the error
// without waiting for the next frame loop
this.isRenderable = false;
this.updateTextureOwnership(false);
this.setUpdateType(UpdateType.IsRenderable);

// If parent has a render texture, flag that we need to update
Expand Down Expand Up @@ -1442,6 +1456,17 @@ export class CoreNode extends EventEmitter {
});
}

/**
* Checks if the node is renderable based on world alpha, dimensions and out of bounds status.
*/
checkBasicRenderability(): boolean {
if (this.worldAlpha === 0 || this.isOutOfBounds() === true) {
return false;
} else {
return true;
}
}

/**
* Updates the `isRenderable` property based on various conditions.
*/
Expand All @@ -1450,25 +1475,30 @@ export class CoreNode extends EventEmitter {
let needsTextureOwnership = false;

// If the node is out of bounds or has an alpha of 0, it is not renderable
if (
this.worldAlpha === 0 ||
this.renderState <= CoreNodeRenderState.OutOfBounds
) {
if (this.checkBasicRenderability() === false) {
this.updateTextureOwnership(false);
this.setRenderable(false);
return;
}

if (this.texture !== null) {
needsTextureOwnership = true;
// preemptive check for failed textures this will mark the current node as non-renderable
// and will prevent further checks until the texture is reloaded or retry is reset on the texture
if (this.texture.retryCount > this.texture.maxRetryCount) {
// texture has failed to load, we cannot render
this.updateTextureOwnership(false);
this.setRenderable(false);
return;
}

needsTextureOwnership = true;
// we're only renderable if the texture state is loaded
newIsRenderable = this.texture.state === 'loaded';
} else if (
// check shader
(this.props.shader !== null || this.hasColorProps === true) &&
// check dimensions
(this.props.w !== 0 && this.props.h !== 0) === true
this.hasDimensions() === true
) {
// This mean we have dimensions and a color set, so we can render a ColorTexture
if (
Expand Down Expand Up @@ -1504,7 +1534,21 @@ export class CoreNode extends EventEmitter {
* Changes the renderable state of the node.
*/
updateTextureOwnership(isRenderable: boolean) {
this.texture?.setRenderableOwner(this, isRenderable);
this.texture?.setRenderableOwner(this._id, isRenderable);
}

/**
* Checks if the node is out of the viewport bounds.
*/
isOutOfBounds(): boolean {
return this.renderState <= CoreNodeRenderState.OutOfBounds;
}

/**
* Checks if the node has dimensions (width/height)
*/
hasDimensions(): boolean {
return this.props.w !== 0 && this.props.h !== 0;
}

calculateRenderCoords() {
Expand Down Expand Up @@ -2414,7 +2458,7 @@ export class CoreNode extends EventEmitter {
this.textureCoords = undefined;
this.props.texture = value;
if (value !== null) {
value.setRenderableOwner(this, this.isRenderable);
value.setRenderableOwner(this._id, this.isRenderable);
this.loadTexture();
}

Expand Down
2 changes: 1 addition & 1 deletion src/core/CoreTextNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {

if (this.renderState > CoreNodeRenderState.OutOfBounds) {
// We do want the texture to load immediately
this.texture.setRenderableOwner(this, true);
this.texture.setRenderableOwner(this._id, true);
}
}

Expand Down
Loading